diff options
456 files changed, 11191 insertions, 3872 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 7005349abd7e..fd92979bd593 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -18,6 +18,7 @@ java_defaults { // Add java_aconfig_libraries to here to add them to the core framework srcs: [ + ":android.os.flags-aconfig-java{.generated_srcjars}", ":android.security.flags-aconfig-java{.generated_srcjars}", ":camera_platform_flags_core_java_lib{.generated_srcjars}", ":com.android.window.flags.window-aconfig-java{.generated_srcjars}", @@ -78,3 +79,16 @@ java_aconfig_library { aconfig_declarations: "android.security.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// OS +aconfig_declarations { + name: "android.os.flags-aconfig", + package: "android.os", + srcs: ["core/java/android/os/*.aconfig"], +} + +java_aconfig_library { + name: "android.os.flags-aconfig-java", + aconfig_declarations: "android.os.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp index 28db61f7d98a..66248128ff29 100644 --- a/cmds/app_process/app_main.cpp +++ b/cmds/app_process/app_main.cpp @@ -49,7 +49,7 @@ public: virtual void onVmCreated(JNIEnv* env) { - if (mClassName.isEmpty()) { + if (mClassName.empty()) { return; // Zygote. Nothing to do here. } @@ -98,7 +98,7 @@ public: virtual void onExit(int code) { - if (mClassName.isEmpty()) { + if (mClassName.empty()) { // if zygote IPCThreadState::self()->stopProcess(); hardware::IPCThreadState::self()->stopProcess(); @@ -282,7 +282,7 @@ int main(int argc, char* const argv[]) } Vector<String8> args; - if (!className.isEmpty()) { + if (!className.empty()) { // We're not in zygote mode, the only argument we need to pass // to RuntimeInit is the application argument. // @@ -328,13 +328,13 @@ int main(int argc, char* const argv[]) } } - if (!niceName.isEmpty()) { + if (!niceName.empty()) { runtime.setArgv0(niceName.string(), true /* setProcName */); } if (zygote) { runtime.start("com.android.internal.os.ZygoteInit", args, zygote); - } else if (!className.isEmpty()) { + } else if (!className.empty()) { runtime.start("com.android.internal.os.RuntimeInit", args, zygote); } else { fprintf(stderr, "Error: no class name or --zygote supplied.\n"); diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index b56dceb9393f..2cbd665ec25a 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -723,7 +723,7 @@ void BootAnimation::resizeSurface(int newWidth, int newHeight) { bool BootAnimation::preloadAnimation() { ATRACE_CALL(); findBootAnimationFile(); - if (!mZipFileName.isEmpty()) { + if (!mZipFileName.empty()) { mAnimation = loadAnimation(mZipFileName); return (mAnimation != nullptr); } @@ -842,7 +842,7 @@ bool BootAnimation::threadLoop() { // We have no bootanimation file, so we use the stock android logo // animation. - if (mZipFileName.isEmpty()) { + if (mZipFileName.empty()) { ALOGD("No animation file"); result = android(); } else { diff --git a/core/api/current.txt b/core/api/current.txt index bcb21e5bd3d1..e00362407fc7 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -10560,6 +10560,7 @@ package android.content { public final class ContextParams { method @Nullable public String getAttributionTag(); method @Nullable public android.content.AttributionSource getNextAttributionSource(); + method @NonNull public boolean shouldRegisterAttributionSource(); } public static final class ContextParams.Builder { @@ -10568,6 +10569,7 @@ package android.content { method @NonNull public android.content.ContextParams build(); method @NonNull public android.content.ContextParams.Builder setAttributionTag(@Nullable String); method @NonNull public android.content.ContextParams.Builder setNextAttributionSource(@Nullable android.content.AttributionSource); + method @NonNull public android.content.ContextParams.Builder setShouldRegisterAttributionSource(boolean); } public class ContextWrapper extends android.content.Context { @@ -39349,8 +39351,10 @@ package android.security { } public final class FileIntegrityManager { + method @FlaggedApi(Flags.FLAG_FSVERITY_API) @Nullable public byte[] getFsverityDigest(@NonNull java.io.File) throws java.io.IOException; method public boolean isApkVeritySupported(); method @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public boolean isAppSourceCertificateTrusted(@NonNull java.security.cert.X509Certificate) throws java.security.cert.CertificateEncodingException; + method @FlaggedApi(Flags.FLAG_FSVERITY_API) public void setupFsverity(@NonNull java.io.File) throws java.io.IOException; } public final class KeyChain { diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index b1feb419ecde..7f1e28961108 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -6,7 +6,9 @@ package android { field public static final String CONTROL_AUTOMOTIVE_GNSS = "android.permission.CONTROL_AUTOMOTIVE_GNSS"; field public static final String GET_INTENT_SENDER_INTENT = "android.permission.GET_INTENT_SENDER_INTENT"; field public static final String MAKE_UID_VISIBLE = "android.permission.MAKE_UID_VISIBLE"; + field public static final String MANAGE_REMOTE_AUTH = "android.permission.MANAGE_REMOTE_AUTH"; field public static final String USE_COMPANION_TRANSPORTS = "android.permission.USE_COMPANION_TRANSPORTS"; + field public static final String USE_REMOTE_AUTH = "android.permission.USE_REMOTE_AUTH"; } } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 2137f47e1d00..adbd06c10f47 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3793,6 +3793,10 @@ package android.content.pm { field @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS) public static final int FLAG_GET_PERSONS_DATA = 2048; // 0x800 } + public class PackageArchiver { + method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException; + } + public class PackageInstaller { method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException; method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException; @@ -3890,6 +3894,7 @@ package android.content.pm { method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public abstract java.util.List<android.content.pm.InstantAppInfo> getInstantApps(); method @Deprecated @NonNull public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(@NonNull String); method @Deprecated @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract int getIntentVerificationStatusAsUser(@NonNull String, int); + method @NonNull public android.content.pm.PackageArchiver getPackageArchiver(); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public int getPackageUidAsUser(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags, int) throws android.content.pm.PackageManager.NameNotFoundException; method @android.content.pm.PackageManager.PermissionFlags @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle); method @NonNull @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] getUnsuspendablePackages(@NonNull String[]); @@ -13257,6 +13262,48 @@ package android.service.voice { method @NonNull public android.service.voice.HotwordRejectedResult.Builder setConfidenceLevel(int); } + public final class HotwordTrainingAudio implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.media.AudioFormat getAudioFormat(); + method @NonNull public int getAudioType(); + method @NonNull public byte[] getHotwordAudio(); + method public int getHotwordOffsetMillis(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordTrainingAudio> CREATOR; + field public static final int HOTWORD_OFFSET_UNSET = -1; // 0xffffffff + } + + public static final class HotwordTrainingAudio.Builder { + ctor public HotwordTrainingAudio.Builder(@NonNull byte[], @NonNull android.media.AudioFormat); + method @NonNull public android.service.voice.HotwordTrainingAudio build(); + method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setAudioFormat(@NonNull android.media.AudioFormat); + method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setAudioType(@NonNull int); + method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setHotwordAudio(@NonNull byte...); + method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setHotwordOffsetMillis(int); + } + + public final class HotwordTrainingData implements android.os.Parcelable { + method public int describeContents(); + method public static int getMaxTrainingDataSize(); + method public int getTimeoutStage(); + method @NonNull public java.util.List<android.service.voice.HotwordTrainingAudio> getTrainingAudios(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordTrainingData> CREATOR; + field public static final int TIMEOUT_STAGE_EARLY = 2; // 0x2 + field public static final int TIMEOUT_STAGE_LATE = 4; // 0x4 + field public static final int TIMEOUT_STAGE_MIDDLE = 3; // 0x3 + field public static final int TIMEOUT_STAGE_UNKNOWN = 0; // 0x0 + field public static final int TIMEOUT_STAGE_VERY_EARLY = 1; // 0x1 + } + + public static final class HotwordTrainingData.Builder { + ctor public HotwordTrainingData.Builder(); + method @NonNull public android.service.voice.HotwordTrainingData.Builder addTrainingAudio(@NonNull android.service.voice.HotwordTrainingAudio); + method @NonNull public android.service.voice.HotwordTrainingData build(); + method @NonNull public android.service.voice.HotwordTrainingData.Builder setTimeoutStage(int); + method @NonNull public android.service.voice.HotwordTrainingData.Builder setTrainingAudios(@NonNull java.util.List<android.service.voice.HotwordTrainingAudio>); + } + public interface SandboxedDetectionInitializer { method public static int getMaxCustomInitializationStatus(); method public void onUpdateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, long, @Nullable java.util.function.IntConsumer); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index cff5e4024866..22d2999e9309 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -28,6 +28,7 @@ package android { field public static final String MANAGE_APP_OPS_MODES = "android.permission.MANAGE_APP_OPS_MODES"; field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES"; field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS"; + field public static final String MANAGE_REMOTE_AUTH = "android.permission.MANAGE_REMOTE_AUTH"; field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS"; field public static final String MANAGE_TOAST_RATE_LIMITING = "android.permission.MANAGE_TOAST_RATE_LIMITING"; field public static final String MODIFY_HDR_CONVERSION_MODE = "android.permission.MODIFY_HDR_CONVERSION_MODE"; @@ -54,6 +55,7 @@ package android { field public static final String TEST_INPUT_METHOD = "android.permission.TEST_INPUT_METHOD"; field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS"; field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS"; + field public static final String USE_REMOTE_AUTH = "android.permission.USE_REMOTE_AUTH"; field public static final String WRITE_ALLOWLISTED_DEVICE_CONFIG = "android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG"; field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG"; field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE"; @@ -497,14 +499,14 @@ package android.app { method public int describeContents(); method public int getActivityType(); method @Nullable public android.graphics.Rect getAppBounds(); - method @NonNull public android.graphics.Rect getBounds(); + method public android.graphics.Rect getBounds(); method @NonNull public android.graphics.Rect getMaxBounds(); method public int getRotation(); method public int getWindowingMode(); method public static boolean isFloating(int); method public void setActivityType(int); method public void setAppBounds(@Nullable android.graphics.Rect); - method public void setBounds(@Nullable android.graphics.Rect); + method public void setBounds(android.graphics.Rect); method public void setMaxBounds(@Nullable android.graphics.Rect); method public void setRotation(int); method public void setTo(android.app.WindowConfiguration); diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 02558602acd3..fcd13b840cb1 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -64,6 +64,7 @@ import android.content.pm.InstrumentationInfo; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.KeySet; import android.content.pm.ModuleInfo; +import android.content.pm.PackageArchiver; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageItemInfo; @@ -172,6 +173,7 @@ public class ApplicationPackageManager extends PackageManager { private volatile UserManager mUserManager; private volatile PermissionManager mPermissionManager; private volatile PackageInstaller mInstaller; + private volatile PackageArchiver mPackageArchiver; private volatile ArtManager mArtManager; private volatile DevicePolicyManager mDevicePolicyManager; private volatile String mPermissionsControllerPackageName; @@ -3282,6 +3284,18 @@ public class ApplicationPackageManager extends PackageManager { } @Override + public PackageArchiver getPackageArchiver() { + if (mPackageArchiver == null) { + try { + mPackageArchiver = new PackageArchiver(mContext, mPM.getPackageArchiverService()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return mPackageArchiver; + } + + @Override public boolean isPackageAvailable(String packageName) { try { return mPM.isPackageAvailable(packageName, getUserId()); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 5feafbed148c..a538247998e6 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -3455,20 +3455,20 @@ class ContextImpl extends Context { mOpPackageName = overrideOpPackageName != null ? overrideOpPackageName : opPackageName; mParams = Objects.requireNonNull(params); mAttributionSource = createAttributionSource(attributionTag, nextAttributionSource, - params.getRenouncedPermissions()); + params.getRenouncedPermissions(), params.shouldRegisterAttributionSource()); mContentResolver = new ApplicationContentResolver(this, mainThread); } private @NonNull AttributionSource createAttributionSource(@Nullable String attributionTag, @Nullable AttributionSource nextAttributionSource, - @Nullable Set<String> renouncedPermissions) { + @Nullable Set<String> renouncedPermissions, boolean shouldRegister) { AttributionSource attributionSource = new AttributionSource(Process.myUid(), Process.myPid(), mOpPackageName, attributionTag, (renouncedPermissions != null) ? renouncedPermissions.toArray(new String[0]) : null, getDeviceId(), nextAttributionSource); // If we want to access protected data on behalf of another app we need to // tell the OS that we opt in to participate in the attribution chain. - if (nextAttributionSource != null) { + if (nextAttributionSource != null || shouldRegister) { attributionSource = getSystemService(PermissionManager.class) .registerAttributionSource(attributionSource); } diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java index 729e555509a6..0857c9655e8d 100644 --- a/core/java/android/app/LocaleConfig.java +++ b/core/java/android/app/LocaleConfig.java @@ -40,7 +40,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Set; @@ -195,7 +195,8 @@ public class LocaleConfig implements Parcelable { XmlUtils.beginDocument(parser, TAG_LOCALE_CONFIG); int outerDepth = parser.getDepth(); AttributeSet attrs = Xml.asAttributeSet(parser); - Set<String> localeNames = new HashSet<String>(); + // LinkedHashSet to preserve insertion order + Set<String> localeNames = new LinkedHashSet<>(); while (XmlUtils.nextElementWithin(parser, outerDepth)) { if (TAG_LOCALE.equals(parser.getName())) { final TypedArray attributes = res.obtainAttributes( diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java index e3055e5042c6..bf238c399ad3 100644 --- a/core/java/android/app/WindowConfiguration.java +++ b/core/java/android/app/WindowConfiguration.java @@ -267,11 +267,12 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu } }; + // TODO(b/297672475): make this take @Nullable /** * Sets the bounds to the provided {@link Rect}. * @param rect the new bounds value. */ - public void setBounds(@Nullable Rect rect) { + public void setBounds(Rect rect) { if (rect == null) { mBounds.setEmpty(); return; @@ -363,8 +364,8 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu return mAppBounds; } + // TODO(b/297672475): make this return @NonNull /** @see #setBounds(Rect) */ - @NonNull public Rect getBounds() { return mBounds; } diff --git a/core/java/android/content/ContextParams.java b/core/java/android/content/ContextParams.java index 5cc3a2420811..988a9c0ab9b3 100644 --- a/core/java/android/content/ContextParams.java +++ b/core/java/android/content/ContextParams.java @@ -50,17 +50,20 @@ public final class ContextParams { private final @Nullable String mAttributionTag; private final @Nullable AttributionSource mNext; private final @NonNull Set<String> mRenouncedPermissions; + private final boolean mShouldRegisterAttributionSource; /** {@hide} */ public static final ContextParams EMPTY = new ContextParams.Builder().build(); private ContextParams(@Nullable String attributionTag, @Nullable AttributionSource next, - @Nullable Set<String> renouncedPermissions) { + @Nullable Set<String> renouncedPermissions, + boolean shouldRegister) { mAttributionTag = attributionTag; mNext = next; mRenouncedPermissions = (renouncedPermissions != null) ? renouncedPermissions : Collections.emptySet(); + mShouldRegisterAttributionSource = shouldRegister; } /** @@ -95,12 +98,22 @@ public final class ContextParams { } /** + * @return Whether the attribution source associated with the Context being created should be + * registered. + */ + @NonNull + public boolean shouldRegisterAttributionSource() { + return mShouldRegisterAttributionSource; + } + + /** * Builder for creating a {@link ContextParams}. */ public static final class Builder { private @Nullable String mAttributionTag; private @NonNull Set<String> mRenouncedPermissions = Collections.emptySet(); private @Nullable AttributionSource mNext; + private boolean mShouldRegisterAttributionSource; /** * Create a new builder. @@ -159,6 +172,19 @@ public final class ContextParams { } /** + * Sets whether the attribution source associated with the context created from these params + * should be registered. + * + * @param shouldRegister Whether the attribution source associated with the Context being + * created should be registered. + */ + @NonNull + public Builder setShouldRegisterAttributionSource(boolean shouldRegister) { + mShouldRegisterAttributionSource = shouldRegister; + return this; + } + + /** * Sets permissions which have been voluntarily "renounced" by the * calling app. * <p> @@ -205,7 +231,7 @@ public final class ContextParams { @NonNull public ContextParams build() { return new ContextParams(mAttributionTag, mNext, - mRenouncedPermissions); + mRenouncedPermissions, mShouldRegisterAttributionSource); } } } diff --git a/core/java/android/content/pm/IPackageArchiverService.aidl b/core/java/android/content/pm/IPackageArchiverService.aidl new file mode 100644 index 000000000000..fc471c451370 --- /dev/null +++ b/core/java/android/content/pm/IPackageArchiverService.aidl @@ -0,0 +1,26 @@ +/* + * 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.content.pm; + +import android.content.IntentSender; +import android.os.UserHandle; + +/** {@hide} */ +interface IPackageArchiverService { + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES,android.Manifest.permission.REQUEST_DELETE_PACKAGES})") + void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle); +}
\ No newline at end of file diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index ea0f5ff2896e..4ed4dd3c015a 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -25,6 +25,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.ChangedPackages; import android.content.pm.InstantAppInfo; import android.content.pm.FeatureInfo; +import android.content.pm.IPackageArchiverService; import android.content.pm.IDexModuleRegisterCallback; import android.content.pm.InstallSourceInfo; import android.content.pm.IOnChecksumsReadyListener; @@ -650,6 +651,8 @@ interface IPackageManager { @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) IPackageInstaller getPackageInstaller(); + IPackageArchiverService getPackageArchiverService(); + @EnforcePermission("DELETE_PACKAGES") boolean setBlockUninstallForUser(String packageName, boolean blockUninstall, int userId); @UnsupportedAppUsage diff --git a/core/java/android/content/pm/PackageArchiver.java b/core/java/android/content/pm/PackageArchiver.java new file mode 100644 index 000000000000..d739d5054800 --- /dev/null +++ b/core/java/android/content/pm/PackageArchiver.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.content.Context; +import android.content.IntentSender; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.ParcelableException; +import android.os.RemoteException; + +/** + * {@code ArchiveManager} is used to archive apps. During the archival process, the apps APKs and + * cache are removed from the device while the user data is kept. Through the + * {@code requestUnarchive()} call, apps can be restored again through their responsible app store. + * + * <p> Archived apps are returned as displayable apps through the {@link LauncherApps} APIs and + * will be displayed to users with UI treatment to highlight that said apps are archived. If + * a user taps on an archived app, the app will be unarchived and the restoration process is + * communicated. + * + * @hide + */ +// TODO(b/278560219) Improve public documentation. +@SystemApi +public class PackageArchiver { + + private final Context mContext; + private final IPackageArchiverService mService; + + /** + * @hide + */ + public PackageArchiver(Context context, IPackageArchiverService service) { + mContext = context; + mService = service; + } + + /** + * Requests to archive a package which is currently installed. + * + * @param statusReceiver Callback used to notify when the operation is completed. + * @throws NameNotFoundException If {@code packageName} isn't found or not available to the + * caller. + * @hide + */ + @RequiresPermission(anyOf = { + Manifest.permission.DELETE_PACKAGES, + Manifest.permission.REQUEST_DELETE_PACKAGES}) + @SystemApi + public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver) + throws NameNotFoundException { + try { + mService.requestArchive(packageName, mContext.getPackageName(), statusReceiver, + mContext.getUser()); + } catch (ParcelableException e) { + e.maybeRethrow(NameNotFoundException.class); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 885e67e1a8c8..d2173a6d44bd 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -9934,6 +9934,16 @@ public abstract class PackageManager { public abstract @NonNull PackageInstaller getPackageInstaller(); /** + * {@link PackageArchiver} can be used to archive and restore archived packages. + * + * @hide + */ + @SystemApi + public @NonNull PackageArchiver getPackageArchiver() { + throw new UnsupportedOperationException( + "getPackageArchiver not implemented in subclass"); + } + /** * Adds a {@code CrossProfileIntentFilter}. After calling this method all * intents sent from the user with id sourceUserId can also be be resolved * by activities in the user with id targetUserId if they match the diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 9387ae1d6ac8..41ba1dc43926 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1456,8 +1456,8 @@ public class PackageParser { private static AssetManager newConfiguredAssetManager() { AssetManager assetManager = new AssetManager(); - assetManager.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Build.VERSION.RESOURCES_SDK_INT); + assetManager.setConfiguration(0, 0, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); return assetManager; } @@ -9011,8 +9011,8 @@ public class PackageParser { } AssetManager assets = new AssetManager(); - assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Build.VERSION.RESOURCES_SDK_INT); + assets.setConfiguration(0, 0, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); assets.setApkAssets(apkAssets, false /*invalidateCaches*/); mCachedAssetManager = assets; @@ -9086,8 +9086,8 @@ public class PackageParser { private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) { final AssetManager assets = new AssetManager(); - assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Build.VERSION.RESOURCES_SDK_INT); + assets.setConfiguration(0, 0, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); assets.setApkAssets(apkAssets, false /*invalidateCaches*/); return assets; } diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index b225de402f17..23b9d0b7c9a7 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -1480,9 +1480,13 @@ public final class AssetManager implements AutoCloseable { int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, int screenLayout, int uiMode, int colorMode, int grammaticalGender, int majorVersion) { - synchronized (this) { - ensureValidLocked(); - nativeSetConfiguration(mObject, mcc, mnc, locale, orientation, touchscreen, density, + if (locale != null) { + setConfiguration(mcc, mnc, null, new String[]{locale}, orientation, touchscreen, + density, keyboard, keyboardHidden, navigation, screenWidth, screenHeight, + smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode, + colorMode, grammaticalGender, majorVersion); + } else { + setConfiguration(mcc, mnc, null, null, orientation, touchscreen, density, keyboard, keyboardHidden, navigation, screenWidth, screenHeight, smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode, colorMode, grammaticalGender, majorVersion); @@ -1490,6 +1494,25 @@ public final class AssetManager implements AutoCloseable { } /** + * Change the configuration used when retrieving resources. Not for use by + * applications. + * @hide + */ + public void setConfiguration(int mcc, int mnc, String defaultLocale, String[] locales, + int orientation, int touchscreen, int density, int keyboard, int keyboardHidden, + int navigation, int screenWidth, int screenHeight, int smallestScreenWidthDp, + int screenWidthDp, int screenHeightDp, int screenLayout, int uiMode, int colorMode, + int grammaticalGender, int majorVersion) { + synchronized (this) { + ensureValidLocked(); + nativeSetConfiguration(mObject, mcc, mnc, defaultLocale, locales, orientation, + touchscreen, density, keyboard, keyboardHidden, navigation, screenWidth, + screenHeight, smallestScreenWidthDp, screenWidthDp, screenHeightDp, + screenLayout, uiMode, colorMode, grammaticalGender, majorVersion); + } + } + + /** * @hide */ @UnsupportedAppUsage @@ -1572,10 +1595,11 @@ public final class AssetManager implements AutoCloseable { private static native void nativeSetApkAssets(long ptr, @NonNull ApkAssets[] apkAssets, boolean invalidateCaches); private static native void nativeSetConfiguration(long ptr, int mcc, int mnc, - @Nullable String locale, int orientation, int touchscreen, int density, int keyboard, - int keyboardHidden, int navigation, int screenWidth, int screenHeight, - int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, int screenLayout, - int uiMode, int colorMode, int grammaticalGender, int majorVersion); + @Nullable String defaultLocale, @NonNull String[] locales, int orientation, + int touchscreen, int density, int keyboard, int keyboardHidden, int navigation, + int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp, + int screenHeightDp, int screenLayout, int uiMode, int colorMode, int grammaticalGender, + int majorVersion); private static native @NonNull SparseArray<String> nativeGetAssignedPackageIdentifiers( long ptr, boolean includeOverlays, boolean includeLoaders); diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index 395fef2d3a68..76b29e683312 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -425,14 +425,12 @@ public class ResourcesImpl { mConfiguration.setLocales(locales); } + String[] selectedLocales = null; + String defaultLocale = null; if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) { if (locales.size() > 1) { String[] availableLocales; - - LocaleList localeList = ResourcesManager.getInstance().getLocaleList(); - if (!localeList.isEmpty()) { - availableLocales = localeList.toLanguageTags().split(","); - } else { + if (ResourcesManager.getInstance().getLocaleList().isEmpty()) { // The LocaleList has changed. We must query the AssetManager's // available Locales and figure out the best matching Locale in the new // LocaleList. @@ -444,13 +442,35 @@ public class ResourcesImpl { availableLocales = null; } } - } - if (availableLocales != null) { - final Locale bestLocale = locales.getFirstMatchWithEnglishSupported( - availableLocales); - if (bestLocale != null && bestLocale != locales.get(0)) { - mConfiguration.setLocales(new LocaleList(bestLocale, locales)); + + if (availableLocales != null) { + final Locale bestLocale = locales.getFirstMatchWithEnglishSupported( + availableLocales); + if (bestLocale != null) { + selectedLocales = new String[]{ + adjustLanguageTag(bestLocale.toLanguageTag())}; + if (!bestLocale.equals(locales.get(0))) { + mConfiguration.setLocales( + new LocaleList(bestLocale, locales)); + } + } } + } else { + selectedLocales = locales.getIntersection( + ResourcesManager.getInstance().getLocaleList()); + defaultLocale = ResourcesManager.getInstance() + .getLocaleList().get(0).toLanguageTag(); + } + } + } + if (selectedLocales == null) { + if (ResourcesManager.getInstance().getLocaleList().isEmpty()) { + selectedLocales = new String[]{ + adjustLanguageTag(locales.get(0).toLanguageTag())}; + } else { + selectedLocales = new String[locales.size()]; + for (int i = 0; i < locales.size(); i++) { + selectedLocales[i] = adjustLanguageTag(locales.get(i).toLanguageTag()); } } } @@ -488,7 +508,8 @@ public class ResourcesImpl { } mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc, - adjustLanguageTag(mConfiguration.getLocales().get(0).toLanguageTag()), + defaultLocale, + selectedLocales, mConfiguration.orientation, mConfiguration.touchscreen, mConfiguration.densityDpi, mConfiguration.keyboard, diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 59408191cdf5..703f16553ddb 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -1468,6 +1468,13 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p>Only constrains auto-exposure (AE) algorithm, not * manual control of {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime} and * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}.</p> + * <p>To start a CaptureSession with a target FPS range different from the + * capture request template's default value, the application + * is strongly recommended to call + * {@link SessionConfiguration#setSessionParameters } + * with the target fps range before creating the capture session. The aeTargetFpsRange is + * typically a session parameter. Specifying it at session creation time helps avoid + * session reconfiguration delays in cases like 60fps or high speed recording.</p> * <p><b>Units</b>: Frames per second (FPS)</p> * <p><b>Range of valid values:</b><br> * Any of the entries in {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES android.control.aeAvailableTargetFpsRanges}</p> @@ -2140,6 +2147,12 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * {@link CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE android.control.videoStabilizationMode} field will return * OFF if the recording output is not stabilized, or if there are no output * Surface types that can be stabilized.</p> + * <p>The application is strongly recommended to call + * {@link SessionConfiguration#setSessionParameters } + * with the desired video stabilization mode before creating the capture session. + * Video stabilization mode is a session parameter on many devices. Specifying + * it at session creation time helps avoid reconfiguration delay caused by difference + * between the default value and the first CaptureRequest.</p> * <p>If a camera device supports both this mode and OIS * ({@link CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE android.lens.opticalStabilizationMode}), turning both modes on may * produce undesirable interaction, so it is recommended not to enable diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 905f98de75ff..746648ba7ac5 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -887,6 +887,13 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>Only constrains auto-exposure (AE) algorithm, not * manual control of {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime} and * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}.</p> + * <p>To start a CaptureSession with a target FPS range different from the + * capture request template's default value, the application + * is strongly recommended to call + * {@link SessionConfiguration#setSessionParameters } + * with the target fps range before creating the capture session. The aeTargetFpsRange is + * typically a session parameter. Specifying it at session creation time helps avoid + * session reconfiguration delays in cases like 60fps or high speed recording.</p> * <p><b>Units</b>: Frames per second (FPS)</p> * <p><b>Range of valid values:</b><br> * Any of the entries in {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES android.control.aeAvailableTargetFpsRanges}</p> @@ -2365,6 +2372,12 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * {@link CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE android.control.videoStabilizationMode} field will return * OFF if the recording output is not stabilized, or if there are no output * Surface types that can be stabilized.</p> + * <p>The application is strongly recommended to call + * {@link SessionConfiguration#setSessionParameters } + * with the desired video stabilization mode before creating the capture session. + * Video stabilization mode is a session parameter on many devices. Specifying + * it at session creation time helps avoid reconfiguration delay caused by difference + * between the default value and the first CaptureRequest.</p> * <p>If a camera device supports both this mode and OIS * ({@link CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE android.lens.opticalStabilizationMode}), turning both modes on may * produce undesirable interaction, so it is recommended not to enable diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 8f653b3808c1..c0a44b178b66 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -650,8 +650,6 @@ public class InputMethodService extends AbstractInputMethodService { private InlineSuggestionSessionController mInlineSuggestionSessionController; - private boolean mHideNavBarForKeyboard; - private boolean mIsAutomotive; private @NonNull OptionalInt mHandwritingRequestId = OptionalInt.empty(); private InputEventReceiver mHandwritingEventReceiver; private Handler mHandler; @@ -1622,7 +1620,7 @@ public class InputMethodService extends AbstractInputMethodService { // shown the first time (cold start). mSettingsObserver.shouldShowImeWithHardKeyboard(); - mHideNavBarForKeyboard = getApplicationContext().getResources().getBoolean( + final boolean hideNavBarForKeyboard = getApplicationContext().getResources().getBoolean( com.android.internal.R.bool.config_hideNavBarForKeyboard); initConfigurationTracker(); @@ -1668,7 +1666,7 @@ public class InputMethodService extends AbstractInputMethodService { // screen real estate. When this happens, the IME window should animate from the // bottom of the screen to reduce the jank that happens from the lack of synchronization // between the bottom system window and the IME window. - if (mHideNavBarForKeyboard) { + if (hideNavBarForKeyboard) { window.setDecorFitsSystemWindows(false); } } @@ -2366,9 +2364,7 @@ public class InputMethodService extends AbstractInputMethodService { public void setExtractView(View view) { mExtractFrame.removeAllViews(); - mExtractFrame.addView(view, new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT)); + mExtractFrame.addView(view, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); mExtractView = view; if (view != null) { mExtractEditText = view.findViewById( @@ -2387,7 +2383,7 @@ public class InputMethodService extends AbstractInputMethodService { mExtractAction = null; } } - + /** * Replaces the current candidates view with a new one. You only need to * call this when dynamically changing the view; normally, you should @@ -2396,11 +2392,9 @@ public class InputMethodService extends AbstractInputMethodService { */ public void setCandidatesView(View view) { mCandidatesFrame.removeAllViews(); - mCandidatesFrame.addView(view, new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); + mCandidatesFrame.addView(view, new FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); } - + /** * Replaces the current input view with a new one. You only need to * call this when dynamically changing the view; normally, you should @@ -2409,12 +2403,10 @@ public class InputMethodService extends AbstractInputMethodService { */ public void setInputView(View view) { mInputFrame.removeAllViews(); - mInputFrame.addView(view, new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); + mInputFrame.addView(view, new FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); mInputView = view; } - + /** * Called by the framework to create the layout for showing extracted text. * Only called when in fullscreen mode. The returned view hierarchy must @@ -3448,9 +3440,12 @@ public class InputMethodService extends AbstractInputMethodService { return false; } + /** + * Not implemented in this class. + */ public void onAppPrivateCommand(String action, Bundle data) { } - + /** * Handle a request by the system to toggle the soft input area. */ @@ -4092,11 +4087,6 @@ public class InputMethodService extends AbstractInputMethodService { | (isInputViewShown() ? IME_VISIBLE : 0); } - private boolean isAutomotive() { - return getApplicationContext().getPackageManager().hasSystemFeature( - PackageManager.FEATURE_AUTOMOTIVE); - } - /** * Performs a dump of the InputMethodService's internal state. Override * to add your own information to the dump. diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java index b74bb333deae..82cdd280a0f3 100644 --- a/core/java/android/os/LocaleList.java +++ b/core/java/android/os/LocaleList.java @@ -30,6 +30,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Locale; /** @@ -151,6 +152,25 @@ public final class LocaleList implements Parcelable { } /** + * Find the intersection between this LocaleList and another + * @return a String array of the Locales in both LocaleLists + * {@hide} + */ + @NonNull + public String[] getIntersection(@NonNull LocaleList other) { + List<String> intersection = new ArrayList<>(); + for (Locale l1 : mList) { + for (Locale l2 : other.mList) { + if (matchesLanguageAndScript(l2, l1)) { + intersection.add(l1.toLanguageTag()); + break; + } + } + } + return intersection.toArray(new String[0]); + } + + /** * Creates a new {@link LocaleList}. * * If two or more same locales are passed, the repeated locales will be dropped. diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig new file mode 100644 index 000000000000..851aa6dce560 --- /dev/null +++ b/core/java/android/os/flags.aconfig @@ -0,0 +1,8 @@ +package: "android.os" + +flag { + name: "disallow_cellular_null_ciphers_restriction" + namespace: "cellular_security" + description: "Guards a new UserManager user restriction that admins can use to require cellular encryption on their managed devices." + bug: "276752881" +} diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java index 22e8251b3f52..8961846728a6 100644 --- a/core/java/android/os/storage/StorageManagerInternal.java +++ b/core/java/android/os/storage/StorageManagerInternal.java @@ -20,8 +20,11 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.pm.UserInfo; +import android.os.IInstalld; import android.os.IVold; +import android.os.ParcelFileDescriptor; +import java.io.IOException; import java.util.List; import java.util.Set; @@ -185,4 +188,17 @@ public abstract class StorageManagerInternal { public abstract void prepareUserStorageForMove(String fromVolumeUuid, String toVolumeUuid, List<UserInfo> users); + /** + * A proxy call to the corresponding method in Installer. + * @see com.android.server.pm.Installer#createFsveritySetupAuthToken() + */ + public abstract IInstalld.IFsveritySetupAuthToken createFsveritySetupAuthToken( + ParcelFileDescriptor authFd, int appUid, @UserIdInt int userId) throws IOException; + + /** + * A proxy call to the corresponding method in Installer. + * @see com.android.server.pm.Installer#enableFsverity() + */ + public abstract int enableFsverity(IInstalld.IFsveritySetupAuthToken authToken, String filePath, + String packageName) throws IOException; } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 45b49359f46e..522caac9f627 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -11656,6 +11656,21 @@ public final class Settings { "accessibility_floating_menu_migration_tooltip_prompt"; /** + * For the force dark theme feature which inverts any apps that don't already support dark + * theme. + * + * If true, it will automatically invert any app that is mainly light. + * + * This is related to the force dark override setting, however it will always force the apps + * colors and will ignore any developer hints or opt-out APIs. + * + * @hide + */ + @Readable + public static final String ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED = + "accessibility_force_invert_color_enabled"; + + /** * Whether the Adaptive connectivity option is enabled. * * @hide diff --git a/core/java/android/security/FileIntegrityManager.java b/core/java/android/security/FileIntegrityManager.java index 266046e57cd5..7869404c265f 100644 --- a/core/java/android/security/FileIntegrityManager.java +++ b/core/java/android/security/FileIntegrityManager.java @@ -16,12 +16,21 @@ package android.security; +import android.annotation.FlaggedApi; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.content.Context; +import android.os.IInstalld.IFsveritySetupAuthToken; +import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.system.ErrnoException; +import com.android.internal.security.VerityUtils; + +import java.io.File; +import java.io.IOException; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; @@ -55,6 +64,67 @@ public final class FileIntegrityManager { } /** + * Enables fs-verity to the owned file under the calling app's private directory. It always uses + * the common configuration, i.e. SHA-256 digest algorithm, 4K block size, and without salt. + * + * The operation can only succeed when the file is not opened as writable by any process. + * + * It takes O(file size) time to build the underlying data structure for continuous + * verification. The operation is atomic, i.e. it's either enabled or not, even in case of + * power failure during or after the call. + * + * Note for the API users: When the file's authenticity is crucial, the app typical needs to + * perform a signature check by itself before using the file. The signature is often delivered + * as a separate file and stored next to the targeting file in the filesystem. The public key of + * the signer (normally the same app developer) can be put in the APK, and the app can use the + * public key to verify the signature to the file's actual fs-verity digest (from {@link + * #getFsverityDigest}) before using the file. The exact format is not prescribed by the + * framework. App developers may choose to use common practices like JCA for the signing and + * verification, or their own preferred approach. + * + * @param file The file to enable fs-verity. It should be an absolute path. + * + * @see <a href="https://www.kernel.org/doc/html/next/filesystems/fsverity.html">Kernel doc</a> + */ + @FlaggedApi(Flags.FLAG_FSVERITY_API) + public void setupFsverity(@NonNull File file) throws IOException { + if (!file.isAbsolute()) { + throw new IllegalArgumentException("Expect an absolute path"); + } + IFsveritySetupAuthToken authToken; + // fs-verity setup requires no writable fd to the file. Make sure it's closed before + // continue. + try (var authFd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE)) { + authToken = mService.createAuthToken(authFd); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + try { + int errno = mService.setupFsverity(authToken, file.getPath(), + mContext.getPackageName()); + if (errno != 0) { + new ErrnoException("setupFsverity", errno).rethrowAsIOException(); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the fs-verity digest for the owned file under the calling app's + * private directory, or null when the file does not have fs-verity enabled. + * + * @param file The file to measure the fs-verity digest. + * @return The fs-verity digeset in byte[], null if none. + * @see <a href="https://www.kernel.org/doc/html/next/filesystems/fsverity.html">Kernel doc</a> + */ + @FlaggedApi(Flags.FLAG_FSVERITY_API) + public @Nullable byte[] getFsverityDigest(@NonNull File file) throws IOException { + return VerityUtils.getFsverityDigest(file.getPath()); + } + + /** * Returns whether the given certificate can be used to prove app's install source. Always * return false if the feature is not supported. * diff --git a/core/java/android/security/IFileIntegrityService.aidl b/core/java/android/security/IFileIntegrityService.aidl index dff347ec758a..1a6cf881e337 100644 --- a/core/java/android/security/IFileIntegrityService.aidl +++ b/core/java/android/security/IFileIntegrityService.aidl @@ -16,6 +16,9 @@ package android.security; +import android.os.ParcelFileDescriptor; +import android.os.IInstalld; + /** * Binder interface to communicate with FileIntegrityService. * @hide @@ -23,4 +26,8 @@ package android.security; interface IFileIntegrityService { boolean isApkVeritySupported(); boolean isAppSourceCertificateTrusted(in byte[] certificateBytes, in String packageName); + + IInstalld.IFsveritySetupAuthToken createAuthToken(in ParcelFileDescriptor authFd); + int setupFsverity(IInstalld.IFsveritySetupAuthToken authToken, in String filePath, + in String packageName); } diff --git a/core/java/android/service/credentials/Action.java b/core/java/android/service/credentials/Action.java index 55133aea4fd6..9b1132a872c1 100644 --- a/core/java/android/service/credentials/Action.java +++ b/core/java/android/service/credentials/Action.java @@ -46,7 +46,14 @@ public final class Action implements Parcelable { * <p> See details on usage of {@code Action} for various actionable entries in * {@link BeginCreateCredentialResponse} and {@link BeginGetCredentialResponse}. * - * @param slice the display content to be displayed on the UI, along with this action + * @param slice the slice containing the metadata to be shown on the UI, must be constructed + * through the {@link androidx.credentials.provider} Jetpack library; + * If constructed manually, the {@code slice} object must + * contain the non-null properties of the + * {@link androidx.credentials.provider.Action} class populated as slice items + * against specific hints as used in the class's {@code toSlice} method, + * since the Android System uses this library to parse the {@code slice} and + * extract the required attributes */ public Action(@NonNull Slice slice) { Objects.requireNonNull(slice, "slice must not be null"); diff --git a/core/java/android/service/credentials/CreateEntry.java b/core/java/android/service/credentials/CreateEntry.java index 6a9f09f257f3..2495c7dd9f25 100644 --- a/core/java/android/service/credentials/CreateEntry.java +++ b/core/java/android/service/credentials/CreateEntry.java @@ -66,7 +66,14 @@ public final class CreateEntry implements Parcelable { /** * Constructs a CreateEntry to be displayed on the UI. * - * @param slice the display content to be displayed on the UI, along with this entry + * @param slice the slice containing the metadata to be shown on the UI, must be constructed + * through the {@link androidx.credentials.provider} Jetpack library; + * If constructed manually, the {@code slice} object must + * contain the non-null properties of the + * {@link androidx.credentials.provider.CreateEntry} class populated as slice items + * against specific hints as used in the class's {@code toSlice} method, + * since the Android System uses this library to parse the {@code slice} and + * extract the required attributes */ public CreateEntry( @NonNull Slice slice) { diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java index 512d8339e944..53094e800cf6 100644 --- a/core/java/android/service/credentials/CredentialEntry.java +++ b/core/java/android/service/credentials/CredentialEntry.java @@ -69,8 +69,14 @@ public final class CredentialEntry implements Parcelable { * receive the complete corresponding * {@link GetCredentialRequest}. * @param type the type of the credential for which this credential entry is being created - * @param slice the slice containing the metadata to be shown on the UI. Must be - * constructed through the androidx.credentials jetpack library. + * @param slice the slice containing the metadata to be shown on the UI, must be constructed + * through the {@link androidx.credentials.provider} Jetpack library; + * If constructed manually, the {@code slice} object must + * contain the non-null properties of the + * {@link androidx.credentials.provider.CredentialEntry} class populated as slice + * items against specific hints as used in the class's {@code toSlice} method, + * since the Android System uses this library to parse the {@code slice} and + * extract the required attributes * * @throws IllegalArgumentException If {@code beginGetCredentialOptionId} or {@code type} * is null, or empty diff --git a/core/java/android/service/credentials/RemoteEntry.java b/core/java/android/service/credentials/RemoteEntry.java index 5b3218ad9aa8..5fd9925d84c9 100644 --- a/core/java/android/service/credentials/RemoteEntry.java +++ b/core/java/android/service/credentials/RemoteEntry.java @@ -73,7 +73,14 @@ public final class RemoteEntry implements Parcelable { /** * Constructs a RemoteEntry to be displayed on the UI. * - * @param slice the display content to be displayed on the UI, along with this entry + * @param slice the slice containing the metadata to be shown on the UI, must be constructed + * through the {@link androidx.credentials.provider} Jetpack library; + * If constructed manually, the {@code slice} object must + * contain the non-null properties of the + * {@link androidx.credentials.provider.RemoteEntry} class populated as slice items + * against specific hints as used in the class's {@code toSlice} method, + * since the Android System uses this library to parse the {@code slice} and + * extract the required attributes */ public RemoteEntry( @NonNull Slice slice) { diff --git a/core/java/android/service/voice/HotwordTrainingAudio.aidl b/core/java/android/service/voice/HotwordTrainingAudio.aidl new file mode 100644 index 000000000000..4dd22897f831 --- /dev/null +++ b/core/java/android/service/voice/HotwordTrainingAudio.aidl @@ -0,0 +1,19 @@ +/* + * 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.service.voice; + +parcelable HotwordTrainingAudio;
\ No newline at end of file diff --git a/core/java/android/service/voice/HotwordTrainingAudio.java b/core/java/android/service/voice/HotwordTrainingAudio.java new file mode 100644 index 000000000000..895b0c0e2d55 --- /dev/null +++ b/core/java/android/service/voice/HotwordTrainingAudio.java @@ -0,0 +1,367 @@ +/* + * 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.service.voice; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.media.AudioFormat; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +/** + * Represents audio supporting hotword model training. + * + * @hide + */ +@DataClass( + genConstructor = false, + genBuilder = true, + genEqualsHashCode = true, + genHiddenConstDefs = true, + genParcelable = true, + genToString = true +) +@SystemApi +public final class HotwordTrainingAudio implements Parcelable { + /** Represents unset value for the hotword offset. */ + public static final int HOTWORD_OFFSET_UNSET = -1; + + /** Buffer of hotword audio data for training models. */ + @NonNull + private final byte[] mHotwordAudio; + + private String hotwordAudioToString() { + return "length=" + mHotwordAudio.length; + } + + /** + * The {@link AudioFormat} of the {@link HotwordTrainingAudio#mHotwordAudio}. + */ + @NonNull + private final AudioFormat mAudioFormat; + + /** + * App-defined identifier to distinguish hotword training audio instances. + */ + @NonNull + private final int mAudioType; + + private static int defaultAudioType() { + return 0; + } + + /** + * App-defined offset in milliseconds relative to start of + * {@link HotwordTrainingAudio#mHotwordAudio}. Default value is + * {@link HotwordTrainingAudio#HOTWORD_OFFSET_UNSET}. + */ + private int mHotwordOffsetMillis = HOTWORD_OFFSET_UNSET; + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/voice/HotwordTrainingAudio.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + /* package-private */ HotwordTrainingAudio( + @NonNull byte[] hotwordAudio, + @NonNull AudioFormat audioFormat, + @NonNull int audioType, + int hotwordOffsetMillis) { + this.mHotwordAudio = hotwordAudio; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mHotwordAudio); + this.mAudioFormat = audioFormat; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mAudioFormat); + this.mAudioType = audioType; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mAudioType); + this.mHotwordOffsetMillis = hotwordOffsetMillis; + + // onConstructed(); // You can define this method to get a callback + } + + /** + * Buffer of hotword audio data for training models. + */ + @DataClass.Generated.Member + public @NonNull byte[] getHotwordAudio() { + return mHotwordAudio; + } + + /** + * The {@link AudioFormat} of the {@link HotwordTrainingAudio#mHotwordAudio}. + */ + @DataClass.Generated.Member + public @NonNull AudioFormat getAudioFormat() { + return mAudioFormat; + } + + /** + * App-defined identifier to distinguish hotword training audio instances. + */ + @DataClass.Generated.Member + public @NonNull int getAudioType() { + return mAudioType; + } + + /** + * App-defined offset in milliseconds relative to start of + * {@link HotwordTrainingAudio#mHotwordAudio}. Default value is + * {@link HotwordTrainingAudio#HOTWORD_OFFSET_UNSET}. + */ + @DataClass.Generated.Member + public int getHotwordOffsetMillis() { + return mHotwordOffsetMillis; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "HotwordTrainingAudio { " + + "hotwordAudio = " + hotwordAudioToString() + ", " + + "audioFormat = " + mAudioFormat + ", " + + "audioType = " + mAudioType + ", " + + "hotwordOffsetMillis = " + mHotwordOffsetMillis + + " }"; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(HotwordTrainingAudio other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + HotwordTrainingAudio that = (HotwordTrainingAudio) o; + //noinspection PointlessBooleanExpression + return true + && java.util.Arrays.equals(mHotwordAudio, that.mHotwordAudio) + && java.util.Objects.equals(mAudioFormat, that.mAudioFormat) + && mAudioType == that.mAudioType + && mHotwordOffsetMillis == that.mHotwordOffsetMillis; + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + java.util.Arrays.hashCode(mHotwordAudio); + _hash = 31 * _hash + java.util.Objects.hashCode(mAudioFormat); + _hash = 31 * _hash + mAudioType; + _hash = 31 * _hash + mHotwordOffsetMillis; + return _hash; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeByteArray(mHotwordAudio); + dest.writeTypedObject(mAudioFormat, flags); + dest.writeInt(mAudioType); + dest.writeInt(mHotwordOffsetMillis); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ HotwordTrainingAudio(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + byte[] hotwordAudio = in.createByteArray(); + AudioFormat audioFormat = (AudioFormat) in.readTypedObject(AudioFormat.CREATOR); + int audioType = in.readInt(); + int hotwordOffsetMillis = in.readInt(); + + this.mHotwordAudio = hotwordAudio; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mHotwordAudio); + this.mAudioFormat = audioFormat; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mAudioFormat); + this.mAudioType = audioType; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mAudioType); + this.mHotwordOffsetMillis = hotwordOffsetMillis; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<HotwordTrainingAudio> CREATOR + = new Parcelable.Creator<HotwordTrainingAudio>() { + @Override + public HotwordTrainingAudio[] newArray(int size) { + return new HotwordTrainingAudio[size]; + } + + @Override + public HotwordTrainingAudio createFromParcel(@NonNull Parcel in) { + return new HotwordTrainingAudio(in); + } + }; + + /** + * A builder for {@link HotwordTrainingAudio} + */ + @SuppressWarnings("WeakerAccess") + @DataClass.Generated.Member + public static final class Builder { + + private @NonNull byte[] mHotwordAudio; + private @NonNull AudioFormat mAudioFormat; + private @NonNull int mAudioType; + private int mHotwordOffsetMillis; + + private long mBuilderFieldsSet = 0L; + + /** + * Creates a new Builder. + * + * @param hotwordAudio + * Buffer of hotword audio data for training models. + * @param audioFormat + * The {@link AudioFormat} of the {@link HotwordTrainingAudio#mHotwordAudio}. + */ + public Builder( + @NonNull byte[] hotwordAudio, + @NonNull AudioFormat audioFormat) { + mHotwordAudio = hotwordAudio; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mHotwordAudio); + mAudioFormat = audioFormat; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mAudioFormat); + } + + /** + * Buffer of hotword audio data for training models. + */ + @DataClass.Generated.Member + public @NonNull Builder setHotwordAudio(@NonNull byte... value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mHotwordAudio = value; + return this; + } + + /** + * The {@link AudioFormat} of the {@link HotwordTrainingAudio#mHotwordAudio}. + */ + @DataClass.Generated.Member + public @NonNull Builder setAudioFormat(@NonNull AudioFormat value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mAudioFormat = value; + return this; + } + + /** + * App-defined identifier to distinguish hotword training audio instances. + */ + @DataClass.Generated.Member + public @NonNull Builder setAudioType(@NonNull int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mAudioType = value; + return this; + } + + /** + * App-defined offset in milliseconds relative to start of + * {@link HotwordTrainingAudio#mHotwordAudio}. Default value is + * {@link HotwordTrainingAudio#HOTWORD_OFFSET_UNSET}. + */ + @DataClass.Generated.Member + public @NonNull Builder setHotwordOffsetMillis(int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x8; + mHotwordOffsetMillis = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull HotwordTrainingAudio build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x10; // Mark builder used + + if ((mBuilderFieldsSet & 0x4) == 0) { + mAudioType = defaultAudioType(); + } + if ((mBuilderFieldsSet & 0x8) == 0) { + mHotwordOffsetMillis = HOTWORD_OFFSET_UNSET; + } + HotwordTrainingAudio o = new HotwordTrainingAudio( + mHotwordAudio, + mAudioFormat, + mAudioType, + mHotwordOffsetMillis); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x10) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } + + @DataClass.Generated( + time = 1692837160437L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/service/voice/HotwordTrainingAudio.java", + inputSignatures = "public static final int HOTWORD_OFFSET_UNSET\nprivate final @android.annotation.NonNull byte[] mHotwordAudio\nprivate final @android.annotation.NonNull android.media.AudioFormat mAudioFormat\nprivate final @android.annotation.NonNull int mAudioType\nprivate int mHotwordOffsetMillis\nprivate java.lang.String hotwordAudioToString()\nprivate static int defaultAudioType()\nclass HotwordTrainingAudio extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/service/voice/HotwordTrainingData.aidl b/core/java/android/service/voice/HotwordTrainingData.aidl new file mode 100644 index 000000000000..03cc8413d780 --- /dev/null +++ b/core/java/android/service/voice/HotwordTrainingData.aidl @@ -0,0 +1,19 @@ +/* + * 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.service.voice; + +parcelable HotwordTrainingData; diff --git a/core/java/android/service/voice/HotwordTrainingData.java b/core/java/android/service/voice/HotwordTrainingData.java new file mode 100644 index 000000000000..9dca77ed7f51 --- /dev/null +++ b/core/java/android/service/voice/HotwordTrainingData.java @@ -0,0 +1,371 @@ +/* + * 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.service.voice; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import com.android.internal.util.DataClass; +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Contains training data related to hotword detection service. + * + * <p>The constructed object's size must be within + * {@link HotwordTrainingData#getMaxTrainingDataSize()} or an + * {@link IllegalArgumentException} will be thrown on construction. Size of the object is calculated + * by converting object to a {@link Parcel} and using the {@link Parcel#dataSize()}. + * + * @hide + */ +@DataClass( + genConstructor = false, + genBuilder = true, + genEqualsHashCode = true, + genHiddenConstDefs = true, + genParcelable = true, + genToString = true) +@SystemApi +public final class HotwordTrainingData implements Parcelable { + /** Max size for hotword training data. */ + public static int getMaxTrainingDataSize() { + return 1024 * 1024; // 1 MB; + } + + /** The list containing hotword audio that is useful for training. */ + @NonNull + @DataClass.PluralOf("trainingAudio") + private final List<HotwordTrainingAudio> mTrainingAudios; + + private static List<HotwordTrainingAudio> defaultTrainingAudios() { + return Collections.emptyList(); + } + + /** Timeout stage is unknown. */ + public static final int TIMEOUT_STAGE_UNKNOWN = 0; + + /** + * Timeout stage value that represents that the model timed out very early while detecting + * hotword. + */ + public static final int TIMEOUT_STAGE_VERY_EARLY = 1; + + /** + * Timeout stage value that represents that the model timed out early while detecting + * hotword. + */ + public static final int TIMEOUT_STAGE_EARLY = 2; + + /** + * Timeout stage value that represents that the model timed out in the middle while detecting + * hotword. + */ + public static final int TIMEOUT_STAGE_MIDDLE = 3; + + /** + * Timeout stage value that represents that the model timed out late while detecting + * hotword. + */ + public static final int TIMEOUT_STAGE_LATE = 4; + + /** @hide */ + @IntDef(prefix = {"TIMEOUT_STAGE"}, value = { + TIMEOUT_STAGE_UNKNOWN, + TIMEOUT_STAGE_VERY_EARLY, + TIMEOUT_STAGE_EARLY, + TIMEOUT_STAGE_MIDDLE, + TIMEOUT_STAGE_LATE, + }) + @interface HotwordTimeoutStage {} + + /** Stage when timeout occurred. */ + @HotwordTimeoutStage + private final int mTimeoutStage; + + private static int defaultTimeoutStage() { + return TIMEOUT_STAGE_UNKNOWN; + } + + private void onConstructed() { + // Verify size of object is within limit. + Parcel parcel = Parcel.obtain(); + parcel.writeValue(this); + int dataSizeBytes = parcel.dataSize(); + parcel.recycle(); + Preconditions.checkArgument( + dataSizeBytes < getMaxTrainingDataSize(), + TextUtils.formatSimple( + "Hotword training data of size %s exceeds size limit of %s!", + dataSizeBytes, getMaxTrainingDataSize())); + } + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/voice/HotwordTrainingData.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** @hide */ + @IntDef(prefix = "TIMEOUT_STAGE_", value = { + TIMEOUT_STAGE_UNKNOWN, + TIMEOUT_STAGE_VERY_EARLY, + TIMEOUT_STAGE_EARLY, + TIMEOUT_STAGE_MIDDLE, + TIMEOUT_STAGE_LATE + }) + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface TimeoutStage {} + + /** @hide */ + @DataClass.Generated.Member + public static String timeoutStageToString(@TimeoutStage int value) { + switch (value) { + case TIMEOUT_STAGE_UNKNOWN: + return "TIMEOUT_STAGE_UNKNOWN"; + case TIMEOUT_STAGE_VERY_EARLY: + return "TIMEOUT_STAGE_VERY_EARLY"; + case TIMEOUT_STAGE_EARLY: + return "TIMEOUT_STAGE_EARLY"; + case TIMEOUT_STAGE_MIDDLE: + return "TIMEOUT_STAGE_MIDDLE"; + case TIMEOUT_STAGE_LATE: + return "TIMEOUT_STAGE_LATE"; + default: return Integer.toHexString(value); + } + } + + @DataClass.Generated.Member + /* package-private */ HotwordTrainingData( + @NonNull List<HotwordTrainingAudio> trainingAudios, + @HotwordTimeoutStage int timeoutStage) { + this.mTrainingAudios = trainingAudios; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mTrainingAudios); + this.mTimeoutStage = timeoutStage; + com.android.internal.util.AnnotationValidations.validate( + HotwordTimeoutStage.class, null, mTimeoutStage); + + onConstructed(); + } + + /** + * The list containing hotword audio that is useful for training. + */ + @DataClass.Generated.Member + public @NonNull List<HotwordTrainingAudio> getTrainingAudios() { + return mTrainingAudios; + } + + /** + * Stage when timeout occurred. + */ + @DataClass.Generated.Member + public @HotwordTimeoutStage int getTimeoutStage() { + return mTimeoutStage; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "HotwordTrainingData { " + + "trainingAudios = " + mTrainingAudios + ", " + + "timeoutStage = " + mTimeoutStage + + " }"; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@android.annotation.Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(HotwordTrainingData other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + HotwordTrainingData that = (HotwordTrainingData) o; + //noinspection PointlessBooleanExpression + return true + && java.util.Objects.equals(mTrainingAudios, that.mTrainingAudios) + && mTimeoutStage == that.mTimeoutStage; + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + java.util.Objects.hashCode(mTrainingAudios); + _hash = 31 * _hash + mTimeoutStage; + return _hash; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeParcelableList(mTrainingAudios, flags); + dest.writeInt(mTimeoutStage); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ HotwordTrainingData(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + List<HotwordTrainingAudio> trainingAudios = new ArrayList<>(); + in.readParcelableList(trainingAudios, HotwordTrainingAudio.class.getClassLoader()); + int timeoutStage = in.readInt(); + + this.mTrainingAudios = trainingAudios; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mTrainingAudios); + this.mTimeoutStage = timeoutStage; + com.android.internal.util.AnnotationValidations.validate( + HotwordTimeoutStage.class, null, mTimeoutStage); + + onConstructed(); + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<HotwordTrainingData> CREATOR + = new Parcelable.Creator<HotwordTrainingData>() { + @Override + public HotwordTrainingData[] newArray(int size) { + return new HotwordTrainingData[size]; + } + + @Override + public HotwordTrainingData createFromParcel(@NonNull Parcel in) { + return new HotwordTrainingData(in); + } + }; + + /** + * A builder for {@link HotwordTrainingData} + */ + @SuppressWarnings("WeakerAccess") + @DataClass.Generated.Member + public static final class Builder { + + private @NonNull List<HotwordTrainingAudio> mTrainingAudios; + private @HotwordTimeoutStage int mTimeoutStage; + + private long mBuilderFieldsSet = 0L; + + public Builder() { + } + + /** + * The list containing hotword audio that is useful for training. + */ + @DataClass.Generated.Member + public @NonNull Builder setTrainingAudios(@NonNull List<HotwordTrainingAudio> value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mTrainingAudios = value; + return this; + } + + /** @see #setTrainingAudios */ + @DataClass.Generated.Member + public @NonNull Builder addTrainingAudio(@NonNull HotwordTrainingAudio value) { + if (mTrainingAudios == null) setTrainingAudios(new ArrayList<>()); + mTrainingAudios.add(value); + return this; + } + + /** + * Stage when timeout occurred. + */ + @DataClass.Generated.Member + public @NonNull Builder setTimeoutStage(@HotwordTimeoutStage int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mTimeoutStage = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull HotwordTrainingData build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; // Mark builder used + + if ((mBuilderFieldsSet & 0x1) == 0) { + mTrainingAudios = defaultTrainingAudios(); + } + if ((mBuilderFieldsSet & 0x2) == 0) { + mTimeoutStage = defaultTimeoutStage(); + } + HotwordTrainingData o = new HotwordTrainingData( + mTrainingAudios, + mTimeoutStage); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x4) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } + + @DataClass.Generated( + time = 1693313864628L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/service/voice/HotwordTrainingData.java", + inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"trainingAudio\") java.util.List<android.service.voice.HotwordTrainingAudio> mTrainingAudios\npublic static final int TIMEOUT_STAGE_UNKNOWN\npublic static final int TIMEOUT_STAGE_VERY_EARLY\npublic static final int TIMEOUT_STAGE_EARLY\npublic static final int TIMEOUT_STAGE_MIDDLE\npublic static final int TIMEOUT_STAGE_LATE\nprivate final @android.service.voice.HotwordTrainingData.HotwordTimeoutStage int mTimeoutStage\npublic static int getMaxTrainingDataSize()\nprivate static java.util.List<android.service.voice.HotwordTrainingAudio> defaultTrainingAudios()\nprivate static int defaultTimeoutStage()\nprivate void onConstructed()\nclass HotwordTrainingData extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 0a69ea8ce633..048912c0b2e2 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -118,9 +118,6 @@ public final class SystemUiDeviceConfigFlags { */ public static final String NAS_DEFAULT_SERVICE = "nas_default_service"; - /** (boolean) Whether notify() calls to NMS should acquire and hold WakeLocks. */ - public static final String NOTIFY_WAKELOCK = "nms_notify_wakelock"; - // Flags related to media notifications /** diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java index 9233050c97ad..8de448be440b 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java @@ -74,10 +74,6 @@ public class SystemUiSystemPropertiesFlags { public static final Flag LOG_DND_STATE_EVENTS = releasedFlag("persist.sysui.notification.log_dnd_state_events"); - /** Gating the holding of WakeLocks until NLSes are told about a new notification. */ - public static final Flag WAKE_LOCK_FOR_POSTING_NOTIFICATION = - releasedFlag("persist.sysui.notification.wake_lock_for_posting_notification"); - /** Gating storing NotificationRankingUpdate ranking map in shared memory. */ public static final Flag RANKING_UPDATE_ASHMEM = devFlag( "persist.sysui.notification.ranking_update_ashmem"); diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java index 116c301c28f0..3e9458dcb7d8 100644 --- a/core/java/com/android/internal/util/LatencyTracker.java +++ b/core/java/com/android/internal/util/LatencyTracker.java @@ -342,7 +342,11 @@ public class LatencyTracker { synchronized (mLock) { int samplingInterval = properties.getInt(SETTINGS_SAMPLING_INTERVAL_KEY, DEFAULT_SAMPLING_INTERVAL); + boolean wasEnabled = mEnabled; mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED); + if (wasEnabled != mEnabled) { + Log.d(TAG, "Latency tracker " + (mEnabled ? "enabled" : "disabled") + "."); + } for (int action : ACTIONS_ALL) { String actionName = getNameOfAction(STATSD_ACTION[action]).toLowerCase(Locale.ROOT); int legacyActionTraceThreshold = properties.getInt( diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp index 1c597424f221..eb49f411159c 100644 --- a/core/jni/android_hardware_Camera.cpp +++ b/core/jni/android_hardware_Camera.cpp @@ -874,7 +874,7 @@ static jstring android_hardware_Camera_getParameters(JNIEnv *env, jobject thiz) if (camera == 0) return 0; String8 params8 = camera->getParameters(); - if (params8.isEmpty()) { + if (params8.empty()) { jniThrowRuntimeException(env, "getParameters failed (empty parameters)"); return 0; } diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp index 5293c583cfd0..3e4c7c7f751e 100644 --- a/core/jni/android_hardware_camera2_CameraMetadata.cpp +++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp @@ -526,7 +526,7 @@ static void CameraMetadata_dump(JNIEnv *env, jclass thiz, jlong ptr) { "Failed to read from fd (errno = %#x, message = '%s')", errno, strerror(errno)); //return; - } else if (!logLine.isEmpty()) { + } else if (!logLine.empty()) { ALOGD("%s", logLine.string()); } diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index f04b901ff0df..3ee15ab734b9 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -347,14 +347,23 @@ static void NativeSetApkAssets(JNIEnv* env, jclass /*clazz*/, jlong ptr, } static void NativeSetConfiguration(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint mcc, jint mnc, - jstring locale, jint orientation, jint touchscreen, jint density, - jint keyboard, jint keyboard_hidden, jint navigation, - jint screen_width, jint screen_height, - jint smallest_screen_width_dp, jint screen_width_dp, - jint screen_height_dp, jint screen_layout, jint ui_mode, - jint color_mode, jint grammatical_gender, jint major_version) { + jstring default_locale, jobjectArray locales, jint orientation, + jint touchscreen, jint density, jint keyboard, + jint keyboard_hidden, jint navigation, jint screen_width, + jint screen_height, jint smallest_screen_width_dp, + jint screen_width_dp, jint screen_height_dp, jint screen_layout, + jint ui_mode, jint color_mode, jint grammatical_gender, + jint major_version) { ATRACE_NAME("AssetManager::SetConfiguration"); + const jsize locale_count = (locales == NULL) ? 0 : env->GetArrayLength(locales); + + // Constants duplicated from Java class android.content.res.Configuration. + static const jint kScreenLayoutRoundMask = 0x300; + static const jint kScreenLayoutRoundShift = 8; + + std::vector<ResTable_config> configs; + ResTable_config configuration; memset(&configuration, 0, sizeof(configuration)); configuration.mcc = static_cast<uint16_t>(mcc); @@ -375,25 +384,37 @@ static void NativeSetConfiguration(JNIEnv* env, jclass /*clazz*/, jlong ptr, jin configuration.colorMode = static_cast<uint8_t>(color_mode); configuration.grammaticalInflection = static_cast<uint8_t>(grammatical_gender); configuration.sdkVersion = static_cast<uint16_t>(major_version); - - if (locale != nullptr) { - ScopedUtfChars locale_utf8(env, locale); - CHECK(locale_utf8.c_str() != nullptr); - configuration.setBcp47Locale(locale_utf8.c_str()); - } - - // Constants duplicated from Java class android.content.res.Configuration. - static const jint kScreenLayoutRoundMask = 0x300; - static const jint kScreenLayoutRoundShift = 8; - // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer // in C++. We must extract the round qualifier out of the Java screenLayout and put it // into screenLayout2. configuration.screenLayout2 = - static_cast<uint8_t>((screen_layout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift); + static_cast<uint8_t>((screen_layout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift); + + if (locale_count > 0) { + configs.resize(locale_count, configuration); + for (int i = 0; i < locale_count; i++) { + jstring locale = (jstring)(env->GetObjectArrayElement(locales, i)); + ScopedUtfChars locale_utf8(env, locale); + CHECK(locale_utf8.c_str() != nullptr); + configs[i].setBcp47Locale(locale_utf8.c_str()); + } + } else { + configs.push_back(configuration); + } + + uint32_t default_locale_int = 0; + if (default_locale != nullptr) { + ResTable_config config; + static_assert(std::is_same_v<decltype(config.locale), decltype(default_locale_int)>); + ScopedUtfChars locale_utf8(env, default_locale); + CHECK(locale_utf8.c_str() != nullptr); + config.setBcp47Locale(locale_utf8.c_str()); + default_locale_int = config.locale; + } auto assetmanager = LockAndStartAssetManager(ptr); - assetmanager->SetConfiguration(configuration); + assetmanager->SetConfigurations(configs); + assetmanager->SetDefaultLocale(default_locale_int); } static jobject NativeGetAssignedPackageIdentifiers(JNIEnv* env, jclass /*clazz*/, jlong ptr, @@ -1498,94 +1519,97 @@ static jlong NativeAssetGetRemainingLength(JNIEnv* /*env*/, jclass /*clazz*/, jl // JNI registration. static const JNINativeMethod gAssetManagerMethods[] = { - // AssetManager setup methods. - {"nativeCreate", "()J", (void*)NativeCreate}, - {"nativeDestroy", "(J)V", (void*)NativeDestroy}, - {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets}, - {"nativeSetConfiguration", "(JIILjava/lang/String;IIIIIIIIIIIIIIII)V", - (void*)NativeSetConfiguration}, - {"nativeGetAssignedPackageIdentifiers", "(JZZ)Landroid/util/SparseArray;", - (void*)NativeGetAssignedPackageIdentifiers}, - - // AssetManager file methods. - {"nativeContainsAllocatedTable", "(J)Z", (void*)ContainsAllocatedTable}, - {"nativeList", "(JLjava/lang/String;)[Ljava/lang/String;", (void*)NativeList}, - {"nativeOpenAsset", "(JLjava/lang/String;I)J", (void*)NativeOpenAsset}, - {"nativeOpenAssetFd", "(JLjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;", - (void*)NativeOpenAssetFd}, - {"nativeOpenNonAsset", "(JILjava/lang/String;I)J", (void*)NativeOpenNonAsset}, - {"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;", - (void*)NativeOpenNonAssetFd}, - {"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset}, - {"nativeOpenXmlAssetFd", "(JILjava/io/FileDescriptor;)J", (void*)NativeOpenXmlAssetFd}, - - // AssetManager resource methods. - {"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I", (void*)NativeGetResourceValue}, - {"nativeGetResourceBagValue", "(JIILandroid/util/TypedValue;)I", - (void*)NativeGetResourceBagValue}, - {"nativeGetStyleAttributes", "(JI)[I", (void*)NativeGetStyleAttributes}, - {"nativeGetResourceStringArray", "(JI)[Ljava/lang/String;", - (void*)NativeGetResourceStringArray}, - {"nativeGetResourceStringArrayInfo", "(JI)[I", (void*)NativeGetResourceStringArrayInfo}, - {"nativeGetResourceIntArray", "(JI)[I", (void*)NativeGetResourceIntArray}, - {"nativeGetResourceArraySize", "(JI)I", (void*)NativeGetResourceArraySize}, - {"nativeGetResourceArray", "(JI[I)I", (void*)NativeGetResourceArray}, - {"nativeGetParentThemeIdentifier", "(JI)I", - (void*)NativeGetParentThemeIdentifier}, - - // AssetManager resource name/ID methods. - {"nativeGetResourceIdentifier", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", - (void*)NativeGetResourceIdentifier}, - {"nativeGetResourceName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceName}, - {"nativeGetResourcePackageName", "(JI)Ljava/lang/String;", (void*)NativeGetResourcePackageName}, - {"nativeGetResourceTypeName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceTypeName}, - {"nativeGetResourceEntryName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceEntryName}, - {"nativeSetResourceResolutionLoggingEnabled", "(JZ)V", - (void*) NativeSetResourceResolutionLoggingEnabled}, - {"nativeGetLastResourceResolution", "(J)Ljava/lang/String;", - (void*) NativeGetLastResourceResolution}, - {"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales}, - {"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;", - (void*)NativeGetSizeConfigurations}, - {"nativeGetSizeAndUiModeConfigurations", "(J)[Landroid/content/res/Configuration;", - (void*)NativeGetSizeAndUiModeConfigurations}, - - // Style attribute related methods. - {"nativeAttributeResolutionStack", "(JJIII)[I", (void*)NativeAttributeResolutionStack}, - {"nativeApplyStyle", "(JJIIJ[IJJ)V", (void*)NativeApplyStyle}, - {"nativeResolveAttrs", "(JJII[I[I[I[I)Z", (void*)NativeResolveAttrs}, - {"nativeRetrieveAttributes", "(JJ[I[I[I)Z", (void*)NativeRetrieveAttributes}, - - // Theme related methods. - {"nativeThemeCreate", "(J)J", (void*)NativeThemeCreate}, - {"nativeGetThemeFreeFunction", "()J", (void*)NativeGetThemeFreeFunction}, - {"nativeThemeApplyStyle", "(JJIZ)V", (void*)NativeThemeApplyStyle}, - {"nativeThemeRebase", "(JJ[I[ZI)V", (void*)NativeThemeRebase}, - - {"nativeThemeCopy", "(JJJJ)V", (void*)NativeThemeCopy}, - {"nativeThemeGetAttributeValue", "(JJILandroid/util/TypedValue;Z)I", - (void*)NativeThemeGetAttributeValue}, - {"nativeThemeDump", "(JJILjava/lang/String;Ljava/lang/String;)V", (void*)NativeThemeDump}, - {"nativeThemeGetChangingConfigurations", "(J)I", (void*)NativeThemeGetChangingConfigurations}, - - // AssetInputStream methods. - {"nativeAssetDestroy", "(J)V", (void*)NativeAssetDestroy}, - {"nativeAssetReadChar", "(J)I", (void*)NativeAssetReadChar}, - {"nativeAssetRead", "(J[BII)I", (void*)NativeAssetRead}, - {"nativeAssetSeek", "(JJI)J", (void*)NativeAssetSeek}, - {"nativeAssetGetLength", "(J)J", (void*)NativeAssetGetLength}, - {"nativeAssetGetRemainingLength", "(J)J", (void*)NativeAssetGetRemainingLength}, - - // System/idmap related methods. - {"nativeGetOverlayableMap", "(JLjava/lang/String;)Ljava/util/Map;", - (void*)NativeGetOverlayableMap}, - {"nativeGetOverlayablesToString", "(JLjava/lang/String;)Ljava/lang/String;", - (void*)NativeGetOverlayablesToString}, - - // Global management/debug methods. - {"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount}, - {"getAssetAllocations", "()Ljava/lang/String;", (void*)NativeGetAssetAllocations}, - {"getGlobalAssetManagerCount", "()I", (void*)NativeGetGlobalAssetManagerCount}, + // AssetManager setup methods. + {"nativeCreate", "()J", (void*)NativeCreate}, + {"nativeDestroy", "(J)V", (void*)NativeDestroy}, + {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets}, + {"nativeSetConfiguration", "(JIILjava/lang/String;[Ljava/lang/String;IIIIIIIIIIIIIIII)V", + (void*)NativeSetConfiguration}, + {"nativeGetAssignedPackageIdentifiers", "(JZZ)Landroid/util/SparseArray;", + (void*)NativeGetAssignedPackageIdentifiers}, + + // AssetManager file methods. + {"nativeContainsAllocatedTable", "(J)Z", (void*)ContainsAllocatedTable}, + {"nativeList", "(JLjava/lang/String;)[Ljava/lang/String;", (void*)NativeList}, + {"nativeOpenAsset", "(JLjava/lang/String;I)J", (void*)NativeOpenAsset}, + {"nativeOpenAssetFd", "(JLjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;", + (void*)NativeOpenAssetFd}, + {"nativeOpenNonAsset", "(JILjava/lang/String;I)J", (void*)NativeOpenNonAsset}, + {"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;", + (void*)NativeOpenNonAssetFd}, + {"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset}, + {"nativeOpenXmlAssetFd", "(JILjava/io/FileDescriptor;)J", (void*)NativeOpenXmlAssetFd}, + + // AssetManager resource methods. + {"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I", + (void*)NativeGetResourceValue}, + {"nativeGetResourceBagValue", "(JIILandroid/util/TypedValue;)I", + (void*)NativeGetResourceBagValue}, + {"nativeGetStyleAttributes", "(JI)[I", (void*)NativeGetStyleAttributes}, + {"nativeGetResourceStringArray", "(JI)[Ljava/lang/String;", + (void*)NativeGetResourceStringArray}, + {"nativeGetResourceStringArrayInfo", "(JI)[I", (void*)NativeGetResourceStringArrayInfo}, + {"nativeGetResourceIntArray", "(JI)[I", (void*)NativeGetResourceIntArray}, + {"nativeGetResourceArraySize", "(JI)I", (void*)NativeGetResourceArraySize}, + {"nativeGetResourceArray", "(JI[I)I", (void*)NativeGetResourceArray}, + {"nativeGetParentThemeIdentifier", "(JI)I", (void*)NativeGetParentThemeIdentifier}, + + // AssetManager resource name/ID methods. + {"nativeGetResourceIdentifier", + "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", + (void*)NativeGetResourceIdentifier}, + {"nativeGetResourceName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceName}, + {"nativeGetResourcePackageName", "(JI)Ljava/lang/String;", + (void*)NativeGetResourcePackageName}, + {"nativeGetResourceTypeName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceTypeName}, + {"nativeGetResourceEntryName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceEntryName}, + {"nativeSetResourceResolutionLoggingEnabled", "(JZ)V", + (void*)NativeSetResourceResolutionLoggingEnabled}, + {"nativeGetLastResourceResolution", "(J)Ljava/lang/String;", + (void*)NativeGetLastResourceResolution}, + {"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales}, + {"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;", + (void*)NativeGetSizeConfigurations}, + {"nativeGetSizeAndUiModeConfigurations", "(J)[Landroid/content/res/Configuration;", + (void*)NativeGetSizeAndUiModeConfigurations}, + + // Style attribute related methods. + {"nativeAttributeResolutionStack", "(JJIII)[I", (void*)NativeAttributeResolutionStack}, + {"nativeApplyStyle", "(JJIIJ[IJJ)V", (void*)NativeApplyStyle}, + {"nativeResolveAttrs", "(JJII[I[I[I[I)Z", (void*)NativeResolveAttrs}, + {"nativeRetrieveAttributes", "(JJ[I[I[I)Z", (void*)NativeRetrieveAttributes}, + + // Theme related methods. + {"nativeThemeCreate", "(J)J", (void*)NativeThemeCreate}, + {"nativeGetThemeFreeFunction", "()J", (void*)NativeGetThemeFreeFunction}, + {"nativeThemeApplyStyle", "(JJIZ)V", (void*)NativeThemeApplyStyle}, + {"nativeThemeRebase", "(JJ[I[ZI)V", (void*)NativeThemeRebase}, + + {"nativeThemeCopy", "(JJJJ)V", (void*)NativeThemeCopy}, + {"nativeThemeGetAttributeValue", "(JJILandroid/util/TypedValue;Z)I", + (void*)NativeThemeGetAttributeValue}, + {"nativeThemeDump", "(JJILjava/lang/String;Ljava/lang/String;)V", (void*)NativeThemeDump}, + {"nativeThemeGetChangingConfigurations", "(J)I", + (void*)NativeThemeGetChangingConfigurations}, + + // AssetInputStream methods. + {"nativeAssetDestroy", "(J)V", (void*)NativeAssetDestroy}, + {"nativeAssetReadChar", "(J)I", (void*)NativeAssetReadChar}, + {"nativeAssetRead", "(J[BII)I", (void*)NativeAssetRead}, + {"nativeAssetSeek", "(JJI)J", (void*)NativeAssetSeek}, + {"nativeAssetGetLength", "(J)J", (void*)NativeAssetGetLength}, + {"nativeAssetGetRemainingLength", "(J)J", (void*)NativeAssetGetRemainingLength}, + + // System/idmap related methods. + {"nativeGetOverlayableMap", "(JLjava/lang/String;)Ljava/util/Map;", + (void*)NativeGetOverlayableMap}, + {"nativeGetOverlayablesToString", "(JLjava/lang/String;)Ljava/lang/String;", + (void*)NativeGetOverlayablesToString}, + + // Global management/debug methods. + {"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount}, + {"getAssetAllocations", "()Ljava/lang/String;", (void*)NativeGetAssetAllocations}, + {"getGlobalAssetManagerCount", "()I", (void*)NativeGetGlobalAssetManagerCount}, }; int register_android_content_AssetManager(JNIEnv* env) { diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index 4f2bf4a4f6cb..b1d3d92023ad 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -583,7 +583,7 @@ void android_os_Process_setArgV0(JNIEnv* env, jobject clazz, jstring name) env->ReleaseStringCritical(name, str); } - if (!name8.isEmpty()) { + if (!name8.empty()) { AndroidRuntime::getRuntime()->setArgv0(name8.string(), true /* setProcName */); } } diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index ed0081c08cad..ad88092fe169 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -97,6 +97,7 @@ message SecureSettingsProto { optional SettingProto accessibility_magnification_joystick_enabled = 50 [ (android.privacy).dest = DEST_AUTOMATIC ]; // Settings for font scaling optional SettingProto accessibility_font_scaling_has_been_changed = 51 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_force_invert_color_enabled = 52 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional Accessibility accessibility = 2; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index dd1a4998e220..d898a23e5bb3 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2094,6 +2094,16 @@ <permission android:name="android.permission.MANAGE_TEST_NETWORKS" android:protectionLevel="signature" /> + <!-- Allows direct access to the <RemoteAuth>Service interfaces. + @hide @TestApi @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) --> + <permission android:name="android.permission.MANAGE_REMOTE_AUTH" + android:protectionLevel="signature" /> + + <!-- Allows direct access to the <RemoteAuth>Service authentication methods. + @hide @TestApi @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) --> + <permission android:name="android.permission.USE_REMOTE_AUTH" + android:protectionLevel="signature" /> + <!-- @SystemApi @hide Allows applications to read Wi-Fi credential. <p>Not for use by third-party applications. --> <permission android:name="android.permission.READ_WIFI_CREDENTIAL" diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index f45499a38312..e54347fc2744 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3537,11 +3537,11 @@ <attr name="preferKeepClear" format="boolean" /> <!-- <p>Whether or not the auto handwriting initiation is enabled in this View. - <p>For a view with active {@link android.view.inputmethod.InputConnection}, - if auto handwriting initiation is enabled stylus movement within its view boundary + <p>For a view with an active {@link android.view.inputmethod.InputConnection}, + if auto handwriting initiation is enabled, stylus movement within its view boundary will automatically trigger the handwriting mode. - <p>This is true by default. - See {@link android.view.View#setAutoHandwritingEnabled}. --> + See {@link android.view.View#setAutoHandwritingEnabled}. + <p>The default value of this flag is configurable by the device manufacturer. --> <attr name="autoHandwritingEnabled" format="boolean" /> <!-- <p>The amount of offset that is applied to the left edge of the view's stylus diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index e67ea82cb6da..a6830a6e3793 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -552,10 +552,6 @@ <color name="accessibility_magnification_thumbnail_container_background_color">#99000000</color> <color name="accessibility_magnification_thumbnail_container_stroke_color">#FFFFFF</color> - <!-- Color of camera light when camera is in use --> - <color name="camera_privacy_light_day">#FFFFFF</color> - <color name="camera_privacy_light_night">#FFFFFF</color> - <!-- Lily Language Picker language item view colors --> <color name="language_picker_item_text_color">#202124</color> <color name="language_picker_item_text_color_secondary">#5F6368</color> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 0ea5b8a6fa06..9e24a7320f6f 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -6534,8 +6534,19 @@ <!-- Interval in milliseconds to average light sensor values for camera light brightness --> <integer name="config_cameraPrivacyLightAlsAveragingIntervalMillis">3000</integer> - <!-- Light sensor's lux value to use as the threshold between using day or night brightness --> - <integer name="config_cameraPrivacyLightAlsNightThreshold">4</integer> + <!-- Ambient Light sensor's lux values to use as the threshold between brightness colors defined + by config_cameraPrivacyLightColors. If the ambient brightness less than the first element + in this array then lights of type "camera" will be set to the color in position 0 of + config_cameraPrivacyLightColors. This array must be strictly increasing and have a length + of zero means there is only one brightness --> + <integer-array name="config_cameraPrivacyLightAlsLuxThresholds"> + </integer-array> + <!-- Colors to configure the camera privacy light at different brightnesses. This array must + have exactly one more entry than config_cameraPrivacyLightAlsLuxThresholds, + or a length of zero if the feature isn't supported. If nonempty and the device doesn't have + an ambient light sensor the last element in this array will be the only one used --> + <array name="config_cameraPrivacyLightColors"> + </array> <!-- List of system components which are allowed to receive ServiceState entries in an un-sanitized form, even if the location toggle is off. This is intended ONLY for system diff --git a/core/res/res/values/config_tv_external_input_logging.xml b/core/res/res/values/config_tv_external_input_logging.xml new file mode 100644 index 000000000000..72e30be00a02 --- /dev/null +++ b/core/res/res/values/config_tv_external_input_logging.xml @@ -0,0 +1,50 @@ +<?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. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. Do not translate. + + NOTE: The naming convention is "config_camelCaseValue". Some legacy + entries do not follow the convention, but all new entries should. --> + +<resources> + <bool name="config_tvExternalInputLoggingDisplayNameFilterEnabled">false</bool> + + <string-array name="config_tvExternalInputLoggingDeviceOnScreenDisplayNames"> + <item>Chromecast</item> + <item>Chromecast HD</item> + <item>SHIELD</item> + <item>Roku</item> + <item>Roku Express 4</item> + <item>Home Theater</item> + <item>Fire TV Stick</item> + <item>PlayStation 5</item> + <item>NintendoSwitch</item> + </string-array> + + <string-array name="config_tvExternalInputLoggingDeviceBrandNames"> + <item>Chromecast</item> + <item>SHIELD</item> + <item>Roku</item> + <item>Apple</item> + <item>Fire TV</item> + <item>PlayStation</item> + <item>Nintendo</item> + </string-array> +</resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 889901af8fd4..ee0563b5d7cd 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -5009,10 +5009,9 @@ <java-symbol type="string" name="vdm_camera_access_denied" /> <java-symbol type="string" name="vdm_secure_window" /> - <java-symbol type="color" name="camera_privacy_light_day"/> - <java-symbol type="color" name="camera_privacy_light_night"/> <java-symbol type="integer" name="config_cameraPrivacyLightAlsAveragingIntervalMillis"/> - <java-symbol type="integer" name="config_cameraPrivacyLightAlsNightThreshold"/> + <java-symbol type="array" name="config_cameraPrivacyLightAlsLuxThresholds"/> + <java-symbol type="array" name="config_cameraPrivacyLightColors"/> <java-symbol type="bool" name="config_bg_current_drain_monitor_enabled" /> <java-symbol type="array" name="config_bg_current_drain_threshold_to_restricted_bucket" /> @@ -5221,4 +5220,9 @@ <!-- Whether we order unlocking and waking --> <java-symbol type="bool" name="config_orderUnlockAndWake" /> + + <!-- External TV Input Logging Configs --> + <java-symbol type="bool" name="config_tvExternalInputLoggingDisplayNameFilterEnabled" /> + <java-symbol type="array" name="config_tvExternalInputLoggingDeviceOnScreenDisplayNames" /> + <java-symbol type="array" name="config_tvExternalInputLoggingDeviceBrandNames" /> </resources> diff --git a/core/tests/coretests/src/android/provider/NameValueCacheTest.java b/core/tests/coretests/src/android/provider/NameValueCacheTest.java index 54a38178e7ce..87e4a42ae0ad 100644 --- a/core/tests/coretests/src/android/provider/NameValueCacheTest.java +++ b/core/tests/coretests/src/android/provider/NameValueCacheTest.java @@ -39,6 +39,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -54,6 +55,7 @@ import java.util.Map; * Due to how the classes are structured, we have to test it in a somewhat roundabout way. We're * mocking out the contentProvider and are handcrafting very specific Bundles to answer the queries. */ +@Ignore("b/297724333") @Presubmit @RunWith(AndroidJUnit4.class) @SmallTest @@ -233,6 +235,7 @@ public class NameValueCacheTest { mConfigsCacheGenerationStore.close(); } + @Ignore("b/297724333") @Test public void testCaching_singleNamespace() throws Exception { HashMap<String, String> keyValues = new HashMap<>(); @@ -270,6 +273,7 @@ public class NameValueCacheTest { assertThat(cachedKeyValues2).containsExactlyEntriesIn(keyValues); } + @Ignore("b/297724333") @Test public void testCaching_multipleNamespaces() throws Exception { HashMap<String, String> keyValues = new HashMap<>(); @@ -309,6 +313,7 @@ public class NameValueCacheTest { assertThat(cachedKeyValues2).containsExactlyEntriesIn(keyValues2); } + @Ignore("b/297724333") @Test public void testCaching_emptyNamespace() throws Exception { Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver, @@ -325,6 +330,7 @@ public class NameValueCacheTest { assertThat(cachedKeyValues).isEmpty(); } + @Ignore("b/297724333") @Test public void testCaching_singleSetting() throws Exception { Settings.Secure.putString(mMockContentResolver, SETTING, "a"); @@ -355,6 +361,7 @@ public class NameValueCacheTest { assertThat(cachedValue2).isEqualTo("b"); } + @Ignore("b/297724333") @Test public void testCaching_multipleSettings() throws Exception { Settings.Secure.putString(mMockContentResolver, SETTING, "a"); @@ -385,6 +392,7 @@ public class NameValueCacheTest { assertThat(cachedValue2).isEqualTo("b"); } + @Ignore("b/297724333") @Test public void testCaching_unsetSetting() throws Exception { String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING); diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java index 246a1e78bd5e..a0e9947cf5d3 100644 --- a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java +++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java @@ -48,7 +48,9 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; @Presubmit @RunWith(AndroidJUnit4.class) @@ -102,6 +104,25 @@ public class OverlayConfigTest { return partitionOrder.toString(); } + // configIndex should come from real time partition order cause partitions could get + // reordered by /product/overlay/partition_order.xml + private Map<String, Integer> createConfigIndexes(OverlayConfig overlayConfig, + String... configPartitions) { + Map<String, Integer> configIndexes = new HashMap<>(); + for (int i = 0; i < configPartitions.length; i++) { + configIndexes.put(configPartitions[i], -1); + } + + String[] partitions = overlayConfig.getPartitionOrder().split(", "); + int index = 0; + for (int i = 0; i < partitions.length; i++) { + if (configIndexes.containsKey(partitions[i])) { + configIndexes.put(partitions[i], index++); + } + } + return configIndexes; + } + @Test public void testImmutableAfterNonImmutableFails() throws IOException { mExpectedException.expect(IllegalStateException.class); @@ -292,11 +313,13 @@ public class OverlayConfigTest { mScannerRule.addOverlay(createFile("/system_ext/overlay/five.apk"), "five"); final OverlayConfig overlayConfig = createConfigImpl(); - assertConfig(overlayConfig, "one", true, true, 0); - assertConfig(overlayConfig, "two", true, true, 1); - assertConfig(overlayConfig, "three", true, true, 2); - assertConfig(overlayConfig, "four", true, true, 3); - assertConfig(overlayConfig, "five", true, true, 4); + Map<String, Integer> configIndexes = createConfigIndexes(overlayConfig, + "vendor", "odm", "oem", "product", "system_ext"); + assertConfig(overlayConfig, "one", true, true, configIndexes.get("vendor")); + assertConfig(overlayConfig, "two", true, true, configIndexes.get("odm")); + assertConfig(overlayConfig, "three", true, true, configIndexes.get("oem")); + assertConfig(overlayConfig, "four", true, true, configIndexes.get("product")); + assertConfig(overlayConfig, "five", true, true, configIndexes.get("system_ext")); } @Test @@ -313,9 +336,11 @@ public class OverlayConfigTest { true, 0); final OverlayConfig overlayConfig = createConfigImpl(); - assertConfig(overlayConfig, "one", false, true, 0); - assertConfig(overlayConfig, "two", true, true, 1); - assertConfig(overlayConfig, "three", false, true, 2); + Map<String, Integer> configIndexes = createConfigIndexes(overlayConfig, + "vendor", "odm", "product"); + assertConfig(overlayConfig, "one", false, true, configIndexes.get("vendor")); + assertConfig(overlayConfig, "two", true, true, configIndexes.get("odm")); + assertConfig(overlayConfig, "three", false, true, configIndexes.get("product")); } @Test @@ -327,9 +352,11 @@ public class OverlayConfigTest { true, 0); final OverlayConfig overlayConfig = createConfigImpl(); - assertConfig(overlayConfig, "one", false, true, 0); - assertConfig(overlayConfig, "two", false, true, 1); - assertConfig(overlayConfig, "three", false, true, 2); + Map<String, Integer> configIndexes = createConfigIndexes(overlayConfig, + "vendor", "odm", "product"); + assertConfig(overlayConfig, "one", false, true, configIndexes.get("vendor")); + assertConfig(overlayConfig, "two", false, true, configIndexes.get("odm")); + assertConfig(overlayConfig, "three", false, true, configIndexes.get("product")); } @Test diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index d0c0c0296935..7edf2fc84f48 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -450,6 +450,10 @@ <dimen name="freeform_resize_corner">44dp</dimen> + <!-- The width of the area at the sides of the screen where a freeform task will transition to + split select if dragged until the touch input is within the range. --> + <dimen name="desktop_mode_transition_area_width">32dp</dimen> + <!-- The height of the area at the top of the screen where a freeform task will transition to fullscreen if dragged until the top bound of the task is within the area. --> <dimen name="desktop_mode_transition_area_height">16dp</dimen> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt index fd000ee1d24f..a8743fbed5e0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt @@ -21,7 +21,6 @@ import android.content.res.Resources import android.graphics.PointF import android.util.Size import com.android.wm.shell.R -import com.android.wm.shell.pip.PipDisplayLayoutState class LegacySizeSpecSource( private val context: Context, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhonePipKeepClearAlgorithm.java index 2d3403599484..133242d15822 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhonePipKeepClearAlgorithm.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.pip.phone; +package com.android.wm.shell.common.pip; import android.content.Context; import android.content.res.Resources; @@ -24,9 +24,6 @@ import android.util.ArraySet; import android.view.Gravity; import com.android.wm.shell.R; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface; import java.util.Set; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt index c5630686f528..18c7bdd6d5ba 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt @@ -21,7 +21,6 @@ import android.content.res.Resources import android.os.SystemProperties import android.util.Size import com.android.wm.shell.R -import com.android.wm.shell.pip.PipDisplayLayoutState import java.io.PrintWriter class PhoneSizeSpecSource( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java index 4fef672b2cd8..a9f687fc9b2d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.pip; +package com.android.wm.shell.common.pip; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,8 +28,6 @@ import android.util.Size; import android.view.Gravity; import com.android.wm.shell.R; -import com.android.wm.shell.common.pip.PipUtils; -import com.android.wm.shell.common.pip.SizeSpecSource; import java.io.PrintWriter; @@ -202,7 +200,8 @@ public class PipBoundsAlgorithm { * * @return {@code false} if the given source is too small to use for the entering animation. */ - static boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint, Rect destinationBounds) { + public static boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint, + Rect destinationBounds) { return sourceRectHint != null && sourceRectHint.width() > destinationBounds.width() && sourceRectHint.height() > destinationBounds.height(); @@ -224,7 +223,7 @@ public class PipBoundsAlgorithm { } /** - * @return whether the given {@param aspectRatio} is valid. + * @return whether the given aspectRatio is valid. */ public boolean isValidPictureInPictureAspectRatio(float aspectRatio) { return Float.compare(mMinAspectRatio, aspectRatio) <= 0 diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java index 279ffc50e07e..3b32b6c7b083 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.pip; +package com.android.wm.shell.common.pip; import android.annotation.IntDef; import android.annotation.NonNull; @@ -36,7 +36,6 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.function.TriConsumer; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; -import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.io.PrintWriter; @@ -314,8 +313,11 @@ public class PipBoundsState { return mPipDisplayLayoutState.getDisplayLayout(); } + /** + * Clears the PiP re-entry state. + */ @VisibleForTesting - void clearReentryState() { + public void clearReentryState() { mPipReentryState = null; } @@ -400,11 +402,18 @@ public class PipBoundsState { mNamedUnrestrictedKeepClearAreas.remove(name); } + + /** + * @return restricted keep clear areas. + */ @NonNull public Set<Rect> getRestrictedKeepClearAreas() { return mRestrictedKeepClearAreas; } + /** + * @return unrestricted keep clear areas. + */ @NonNull public Set<Rect> getUnrestrictedKeepClearAreas() { if (mNamedUnrestrictedKeepClearAreas.isEmpty()) return mUnrestrictedKeepClearAreas; @@ -561,7 +570,11 @@ public class PipBoundsState { } } - static final class PipReentryState { + /** + * Represents the state of pip to potentially restore upon reentry. + */ + @VisibleForTesting + public static final class PipReentryState { private static final String TAG = PipReentryState.class.getSimpleName(); private final @Nullable Size mSize; @@ -573,11 +586,11 @@ public class PipBoundsState { } @Nullable - Size getSize() { + public Size getSize() { return mSize; } - float getSnapFraction() { + public float getSnapFraction() { return mSnapFraction; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDisplayLayoutState.java index 4aa260b44646..ed42117a55af 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDisplayLayoutState.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.pip; +package com.android.wm.shell.common.pip; import static com.android.wm.shell.common.pip.PipUtils.dpToPx; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipKeepClearAlgorithmInterface.java index 5045cf905ee6..954233c04ed4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipKeepClearAlgorithmInterface.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.pip; +package com.android.wm.shell.common.pip; import android.graphics.Rect; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPinchResizingAlgorithm.java index 23153be72890..02b3a8862085 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPinchResizingAlgorithm.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.wm.shell.pip.phone; +package com.android.wm.shell.common.pip; import android.graphics.Point; import android.graphics.PointF; @@ -35,7 +35,7 @@ public class PipPinchResizingAlgorithm { private final PointF mTmpLastCentroid = new PointF(); /** - * Updates {@param resizeBoundsOut} with the new bounds of the PIP, and returns the angle in + * Updates resizeBoundsOut with the new bounds of the PIP, and returns the angle in * degrees that the PIP should be rotated. */ public float calculateBoundsAndAngle(PointF downPoint, PointF downSecondPoint, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipSnapAlgorithm.java index dd30137813e5..007052ee012a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipSnapAlgorithm.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.android.wm.shell.pip; +package com.android.wm.shell.common.pip; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT; import android.graphics.Rect; @@ -39,14 +39,14 @@ public class PipSnapAlgorithm { } /** - * @return returns a fraction that describes where along the {@param movementBounds} the - * {@param stackBounds} are. If the {@param stackBounds} are not currently on the - * {@param movementBounds} exactly, then they will be snapped to the movement bounds. + * @return returns a fraction that describes where along the movementBounds the + * stackBounds are. If the stackBounds are not currently on the + * movementBounds exactly, then they will be snapped to the movement bounds. * stashType dictates whether the PiP is stashed (off-screen) or not. If * that's the case, we will have to do some math to calculate the snap fraction * correctly. * - * The fraction is defined in a clockwise fashion against the {@param movementBounds}: + * The fraction is defined in a clockwise fashion against the movementBounds: * * 0 1 * 4 +---+ 1 @@ -58,10 +58,10 @@ public class PipSnapAlgorithm { @PipBoundsState.StashType int stashType) { final Rect tmpBounds = new Rect(); snapRectToClosestEdge(stackBounds, movementBounds, tmpBounds, stashType); - final float widthFraction = (float) (tmpBounds.left - movementBounds.left) / - movementBounds.width(); - final float heightFraction = (float) (tmpBounds.top - movementBounds.top) / - movementBounds.height(); + final float widthFraction = (float) (tmpBounds.left - movementBounds.left) + / movementBounds.width(); + final float heightFraction = (float) (tmpBounds.top - movementBounds.top) + / movementBounds.height(); if (tmpBounds.top == movementBounds.top) { return widthFraction; } else if (tmpBounds.left == movementBounds.right) { @@ -74,10 +74,10 @@ public class PipSnapAlgorithm { } /** - * Moves the {@param stackBounds} along the {@param movementBounds} to the given snap fraction. + * Moves the stackBounds along the movementBounds to the given snap fraction. * See {@link #getSnapFraction(Rect, Rect)}. * - * The fraction is define in a clockwise fashion against the {@param movementBounds}: + * The fraction is define in a clockwise fashion against the movementBounds: * * 0 1 * 4 +---+ 1 @@ -122,11 +122,11 @@ public class PipSnapAlgorithm { } /** - * Snaps the {@param stackBounds} to the closest edge of the {@param movementBounds} and writes - * the new bounds out to {@param boundsOut}. + * Snaps the stackBounds to the closest edge of the movementBounds and writes + * the new bounds out to boundsOut. */ @VisibleForTesting - void snapRectToClosestEdge(Rect stackBounds, Rect movementBounds, Rect boundsOut, + public void snapRectToClosestEdge(Rect stackBounds, Rect movementBounds, Rect boundsOut, @PipBoundsState.StashType int stashType) { int leftEdge = stackBounds.left; if (stashType == STASH_TYPE_LEFT) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index e28b8d78103c..1c2cee52d68d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -59,8 +59,15 @@ import com.android.wm.shell.common.annotations.ShellAnimationThread; import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.annotations.ShellSplashscreenThread; +import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm; +import com.android.wm.shell.common.pip.PhoneSizeSpecSource; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.PipMediaController; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; import com.android.wm.shell.common.pip.PipUiEventLogger; +import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.compatui.CompatUIConfiguration; import com.android.wm.shell.compatui.CompatUIController; import com.android.wm.shell.compatui.CompatUIShellCommandHandler; @@ -354,6 +361,42 @@ public abstract class WMShellBaseModule { return new PipMediaController(context, mainHandler); } + @WMSingleton + @Provides + static SizeSpecSource provideSizeSpecSource(Context context, + PipDisplayLayoutState pipDisplayLayoutState) { + return new PhoneSizeSpecSource(context, pipDisplayLayoutState); + } + + @WMSingleton + @Provides + static PipBoundsState providePipBoundsState(Context context, + SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) { + return new PipBoundsState(context, sizeSpecSource, pipDisplayLayoutState); + } + + + @WMSingleton + @Provides + static PipSnapAlgorithm providePipSnapAlgorithm() { + return new PipSnapAlgorithm(); + } + + @WMSingleton + @Provides + static PhonePipKeepClearAlgorithm providePhonePipKeepClearAlgorithm(Context context) { + return new PhonePipKeepClearAlgorithm(context); + } + + @WMSingleton + @Provides + static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context, + PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm, + PhonePipKeepClearAlgorithm pipKeepClearAlgorithm, + PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) { + return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm, + pipKeepClearAlgorithm, pipDisplayLayoutState, sizeSpecSource); + } // // Bubbles (optional feature) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java index 4e92ca113114..ba882c403aed 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java @@ -30,9 +30,13 @@ import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TabletopModeController; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.annotations.ShellMainThread; -import com.android.wm.shell.common.pip.PhoneSizeSpecSource; +import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm; import com.android.wm.shell.common.pip.PipAppOpsListener; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.PipMediaController; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.common.pip.SizeSpecSource; @@ -41,17 +45,12 @@ import com.android.wm.shell.dagger.WMSingleton; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipDisplayLayoutState; import com.android.wm.shell.pip.PipParamsChangedForwarder; -import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransition; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; -import com.android.wm.shell.pip.phone.PhonePipKeepClearAlgorithm; import com.android.wm.shell.pip.phone.PhonePipMenuController; import com.android.wm.shell.pip.phone.PipController; import com.android.wm.shell.pip.phone.PipMotionHelper; @@ -119,35 +118,6 @@ public abstract class Pip1Module { } } - @WMSingleton - @Provides - static PipBoundsState providePipBoundsState(Context context, - SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) { - return new PipBoundsState(context, sizeSpecSource, pipDisplayLayoutState); - } - - @WMSingleton - @Provides - static PipSnapAlgorithm providePipSnapAlgorithm() { - return new PipSnapAlgorithm(); - } - - @WMSingleton - @Provides - static PhonePipKeepClearAlgorithm providePhonePipKeepClearAlgorithm(Context context) { - return new PhonePipKeepClearAlgorithm(context); - } - - @WMSingleton - @Provides - static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context, - PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm, - PhonePipKeepClearAlgorithm pipKeepClearAlgorithm, - PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) { - return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm, - pipKeepClearAlgorithm, pipDisplayLayoutState, sizeSpecSource); - } - // Handler is used by Icon.loadDrawableAsync @WMSingleton @Provides @@ -213,13 +183,6 @@ public abstract class Pip1Module { @WMSingleton @Provides - static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper - pipSurfaceTransactionHelper) { - return new PipAnimationController(pipSurfaceTransactionHelper); - } - - @WMSingleton - @Provides static PipTransition providePipTransition(Context context, ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions, PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm, @@ -235,13 +198,6 @@ public abstract class Pip1Module { @WMSingleton @Provides - static SizeSpecSource provideSizeSpecSource(Context context, - PipDisplayLayoutState pipDisplayLayoutState) { - return new PhoneSizeSpecSource(context, pipDisplayLayoutState); - } - - @WMSingleton - @Provides static PipAppOpsListener providePipAppOpsListener(Context context, PipTouchHandler pipTouchHandler, @ShellMainThread ShellExecutor mainExecutor) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java index c4ca5013afb6..b42372b869dd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java @@ -19,6 +19,7 @@ package com.android.wm.shell.dagger.pip; import android.content.Context; import com.android.wm.shell.dagger.WMSingleton; +import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import dagger.Module; @@ -35,4 +36,11 @@ public abstract class Pip1SharedModule { static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) { return new PipSurfaceTransactionHelper(context); } + + @WMSingleton + @Provides + static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper + pipSurfaceTransactionHelper) { + return new PipAnimationController(pipSurfaceTransactionHelper); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java index 8dec4ea542a0..af97cf68915f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java @@ -16,11 +16,16 @@ package com.android.wm.shell.dagger.pip; -import android.annotation.Nullable; +import android.annotation.NonNull; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.dagger.WMShellBaseModule; import com.android.wm.shell.dagger.WMSingleton; import com.android.wm.shell.pip2.PipTransition; +import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; import dagger.Module; import dagger.Provides; @@ -33,8 +38,12 @@ import dagger.Provides; public abstract class Pip2Module { @WMSingleton @Provides - @Nullable - static PipTransition providePipTransition() { - return null; + static PipTransition providePipTransition(@NonNull ShellInit shellInit, + @NonNull ShellTaskOrganizer shellTaskOrganizer, + @NonNull Transitions transitions, + PipBoundsState pipBoundsState, + PipBoundsAlgorithm pipBoundsAlgorithm) { + return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null, + pipBoundsAlgorithm); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java index 9c9364e17e0e..570f0a3db35a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java @@ -16,8 +16,6 @@ package com.android.wm.shell.dagger.pip; -import android.annotation.Nullable; - import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.dagger.WMSingleton; import com.android.wm.shell.pip.PipTransitionController; @@ -38,8 +36,8 @@ public abstract class PipModule { @Provides static PipTransitionController providePipTransitionController( com.android.wm.shell.pip.PipTransition legacyPipTransition, - @Nullable com.android.wm.shell.pip2.PipTransition newPipTransition) { - if (PipUtils.isPip2ExperimentEnabled() && newPipTransition != null) { + com.android.wm.shell.pip2.PipTransition newPipTransition) { + if (PipUtils.isPip2ExperimentEnabled()) { return newPipTransition; } else { return legacyPipTransition; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java index a6ff9ecf7f4f..a9675f976fa9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java @@ -30,16 +30,15 @@ import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.pip.LegacySizeSpecSource; import com.android.wm.shell.common.pip.PipAppOpsListener; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.PipMediaController; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; import com.android.wm.shell.common.pip.PipUiEventLogger; -import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.dagger.WMShellBaseModule; import com.android.wm.shell.dagger.WMSingleton; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.pip.PipDisplayLayoutState; import com.android.wm.shell.pip.PipParamsChangedForwarder; -import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; @@ -131,15 +130,9 @@ public abstract class TvPipModule { @WMSingleton @Provides - static PipSnapAlgorithm providePipSnapAlgorithm() { - return new PipSnapAlgorithm(); - } - - @WMSingleton - @Provides static TvPipBoundsAlgorithm provideTvPipBoundsAlgorithm(Context context, TvPipBoundsState tvPipBoundsState, PipSnapAlgorithm pipSnapAlgorithm, - PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) { + PipDisplayLayoutState pipDisplayLayoutState, LegacySizeSpecSource sizeSpecSource) { return new TvPipBoundsAlgorithm(context, tvPipBoundsState, pipSnapAlgorithm, pipDisplayLayoutState, sizeSpecSource); } @@ -147,13 +140,13 @@ public abstract class TvPipModule { @WMSingleton @Provides static TvPipBoundsState provideTvPipBoundsState(Context context, - SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) { + LegacySizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) { return new TvPipBoundsState(context, sizeSpecSource, pipDisplayLayoutState); } @WMSingleton @Provides - static SizeSpecSource provideSizeSpecSource(Context context, + static LegacySizeSpecSource provideSizeSpecSource(Context context, PipDisplayLayoutState pipDisplayLayoutState) { return new LegacySizeSpecSource(context, pipDisplayLayoutState); } @@ -200,13 +193,6 @@ public abstract class TvPipModule { @WMSingleton @Provides - static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper - pipSurfaceTransactionHelper) { - return new PipAnimationController(pipSurfaceTransactionHelper); - } - - @WMSingleton - @Provides static PipTransitionState providePipTransitionState() { return new PipTransitionState(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java index 0f0d572f8eae..a587bed3fef0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java @@ -27,6 +27,7 @@ import android.app.ActivityManager; import android.content.Context; import android.content.res.Resources; import android.graphics.PixelFormat; +import android.graphics.PointF; import android.graphics.Rect; import android.util.DisplayMetrics; import android.view.SurfaceControl; @@ -47,6 +48,15 @@ import com.android.wm.shell.common.SyncTransactionQueue; * Animated visual indicator for Desktop Mode windowing transitions. */ public class DesktopModeVisualIndicator { + public static final int INVALID_INDICATOR = -1; + /** Indicates impending transition into desktop mode */ + public static final int TO_DESKTOP_INDICATOR = 1; + /** Indicates impending transition into fullscreen */ + public static final int TO_FULLSCREEN_INDICATOR = 2; + /** Indicates impending transition into split select on the left side */ + public static final int TO_SPLIT_LEFT_INDICATOR = 3; + /** Indicates impending transition into split select on the right side */ + public static final int TO_SPLIT_RIGHT_INDICATOR = 4; private final Context mContext; private final DisplayController mDisplayController; @@ -54,6 +64,7 @@ public class DesktopModeVisualIndicator { private final RootTaskDisplayAreaOrganizer mRootTdaOrganizer; private final ActivityManager.RunningTaskInfo mTaskInfo; private final SurfaceControl mTaskSurface; + private final Rect mIndicatorRange = new Rect(); private SurfaceControl mLeash; private final SyncTransactionQueue mSyncQueue; @@ -61,11 +72,12 @@ public class DesktopModeVisualIndicator { private View mView; private boolean mIsFullscreen; + private int mType; public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue, ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController, Context context, SurfaceControl taskSurface, ShellTaskOrganizer taskOrganizer, - RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer) { + RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer, int type) { mSyncQueue = syncQueue; mTaskInfo = taskInfo; mDisplayController = displayController; @@ -73,10 +85,64 @@ public class DesktopModeVisualIndicator { mTaskSurface = taskSurface; mTaskOrganizer = taskOrganizer; mRootTdaOrganizer = taskDisplayAreaOrganizer; + mType = type; + defineIndicatorRange(); createView(); } /** + * If an indicator is warranted based on the input and task bounds, return the type of + * indicator that should be created. + */ + public static int determineIndicatorType(PointF inputCoordinates, Rect taskBounds, + DisplayLayout layout, Context context) { + int transitionAreaHeight = context.getResources().getDimensionPixelSize( + com.android.wm.shell.R.dimen.desktop_mode_transition_area_height); + int transitionAreaWidth = context.getResources().getDimensionPixelSize( + com.android.wm.shell.R.dimen.desktop_mode_transition_area_width); + if (taskBounds.top <= transitionAreaHeight) return TO_FULLSCREEN_INDICATOR; + if (inputCoordinates.x <= transitionAreaWidth) return TO_SPLIT_LEFT_INDICATOR; + if (inputCoordinates.x >= layout.width() - transitionAreaWidth) { + return TO_SPLIT_RIGHT_INDICATOR; + } + return INVALID_INDICATOR; + } + + /** + * Determine range of inputs that will keep this indicator displaying. + */ + private void defineIndicatorRange() { + DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId); + int captionHeight = mContext.getResources().getDimensionPixelSize( + com.android.wm.shell.R.dimen.freeform_decor_caption_height); + int transitionAreaHeight = mContext.getResources().getDimensionPixelSize( + com.android.wm.shell.R.dimen.desktop_mode_transition_area_height); + int transitionAreaWidth = mContext.getResources().getDimensionPixelSize( + com.android.wm.shell.R.dimen.desktop_mode_transition_area_width); + switch (mType) { + case TO_DESKTOP_INDICATOR: + // TO_DESKTOP indicator is only dismissed on release; entire display is valid. + mIndicatorRange.set(0, 0, layout.width(), layout.height()); + break; + case TO_FULLSCREEN_INDICATOR: + // If drag results in caption going above the top edge of the display, we still + // want to transition to fullscreen. + mIndicatorRange.set(0, -captionHeight, layout.width(), transitionAreaHeight); + break; + case TO_SPLIT_LEFT_INDICATOR: + mIndicatorRange.set(0, transitionAreaHeight, transitionAreaWidth, layout.height()); + break; + case TO_SPLIT_RIGHT_INDICATOR: + mIndicatorRange.set(layout.width() - transitionAreaWidth, transitionAreaHeight, + layout.width(), layout.height()); + break; + default: + break; + } + } + + + /** * Create a fullscreen indicator with no animation */ private void createView() { @@ -85,11 +151,30 @@ public class DesktopModeVisualIndicator { final DisplayMetrics metrics = resources.getDisplayMetrics(); final int screenWidth = metrics.widthPixels; final int screenHeight = metrics.heightPixels; + mView = new View(mContext); final SurfaceControl.Builder builder = new SurfaceControl.Builder(); mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder); + String description; + switch (mType) { + case TO_DESKTOP_INDICATOR: + description = "Desktop indicator"; + break; + case TO_FULLSCREEN_INDICATOR: + description = "Fullscreen indicator"; + break; + case TO_SPLIT_LEFT_INDICATOR: + description = "Split Left indicator"; + break; + case TO_SPLIT_RIGHT_INDICATOR: + description = "Split Right indicator"; + break; + default: + description = "Invalid indicator"; + break; + } mLeash = builder - .setName("Fullscreen Indicator") + .setName(description) .setContainerLayer() .build(); t.show(mLeash); @@ -97,14 +182,14 @@ public class DesktopModeVisualIndicator { new WindowManager.LayoutParams(screenWidth, screenHeight, WindowManager.LayoutParams.TYPE_APPLICATION, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT); - lp.setTitle("Fullscreen indicator for Task=" + mTaskInfo.taskId); + lp.setTitle(description + " for Task=" + mTaskInfo.taskId); lp.setTrustedOverlay(); final WindowlessWindowManager windowManager = new WindowlessWindowManager( mTaskInfo.configuration, mLeash, null /* hostInputToken */); mViewHost = new SurfaceControlViewHost(mContext, mDisplayController.getDisplay(mTaskInfo.displayId), windowManager, - "FullscreenVisualIndicator"); + "DesktopModeVisualIndicator"); mViewHost.setView(mView, lp); // We want this indicator to be behind the dragged task, but in front of all others. t.setRelativeLayer(mLeash, mTaskSurface, -1); @@ -116,24 +201,13 @@ public class DesktopModeVisualIndicator { } /** - * Create fullscreen indicator and fades it in. - */ - public void createFullscreenIndicator() { - mIsFullscreen = true; - mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background); - final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFullscreenAnimator( - mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId)); - animator.start(); - } - - /** - * Create a fullscreen indicator. Animator fades it in while expanding the bounds outwards. + * Create an indicator. Animator fades it in while expanding the bounds outwards. */ - public void createFullscreenIndicatorWithAnimatedBounds() { - mIsFullscreen = true; + public void createIndicatorWithAnimatedBounds() { + mIsFullscreen = mType == TO_FULLSCREEN_INDICATOR; mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background); final VisualIndicatorAnimator animator = VisualIndicatorAnimator - .toFullscreenAnimatorWithAnimatedBounds(mView, + .animateBounds(mView, mType, mDisplayController.getDisplayLayout(mTaskInfo.displayId)); animator.start(); } @@ -143,6 +217,7 @@ public class DesktopModeVisualIndicator { */ public void transitionFullscreenIndicatorToFreeform() { mIsFullscreen = false; + mType = TO_DESKTOP_INDICATOR; final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFreeformAnimator( mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId)); animator.start(); @@ -153,6 +228,7 @@ public class DesktopModeVisualIndicator { */ public void transitionFreeformIndicatorToFullscreen() { mIsFullscreen = true; + mType = TO_FULLSCREEN_INDICATOR; final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFullscreenAnimatorWithAnimatedBounds( mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId)); @@ -160,6 +236,14 @@ public class DesktopModeVisualIndicator { } /** + * Determine if a MotionEvent is in the same range that enabled the indicator. + * Used to dismiss the indicator when a transition will no longer result from releasing. + */ + public boolean eventOutsideRange(float x, float y) { + return !mIndicatorRange.contains((int) x, (int) y); + } + + /** * Release the indicator and its components when it is no longer needed. */ public void releaseVisualIndicator(SurfaceControl.Transaction t) { @@ -210,32 +294,45 @@ public class DesktopModeVisualIndicator { * @param view the view for this indicator * @param displayLayout information about the display the transitioning task is currently on */ - public static VisualIndicatorAnimator toFullscreenAnimator(@NonNull View view, - @NonNull DisplayLayout displayLayout) { - final Rect bounds = getMaxBounds(displayLayout); + public static VisualIndicatorAnimator toFullscreenAnimatorWithAnimatedBounds( + @NonNull View view, @NonNull DisplayLayout displayLayout) { + final int padding = displayLayout.stableInsets().top; + Rect startBounds = new Rect(padding, padding, + displayLayout.width() - padding, displayLayout.height() - padding); + view.getBackground().setBounds(startBounds); + final VisualIndicatorAnimator animator = new VisualIndicatorAnimator( - view, bounds, bounds); + view, startBounds, getMaxBounds(startBounds)); animator.setInterpolator(new DecelerateInterpolator()); setupIndicatorAnimation(animator); return animator; } - - /** - * Create animator for visual indicator of fullscreen transition - * - * @param view the view for this indicator - * @param displayLayout information about the display the transitioning task is currently on - */ - public static VisualIndicatorAnimator toFullscreenAnimatorWithAnimatedBounds( - @NonNull View view, @NonNull DisplayLayout displayLayout) { + public static VisualIndicatorAnimator animateBounds( + @NonNull View view, int type, @NonNull DisplayLayout displayLayout) { final int padding = displayLayout.stableInsets().top; - Rect startBounds = new Rect(padding, padding, - displayLayout.width() - padding, displayLayout.height() - padding); + Rect startBounds = new Rect(); + switch (type) { + case TO_FULLSCREEN_INDICATOR: + startBounds.set(padding, padding, + displayLayout.width() - padding, + displayLayout.height() - padding); + break; + case TO_SPLIT_LEFT_INDICATOR: + startBounds.set(padding, padding, + displayLayout.width() / 2 - padding, + displayLayout.height() - padding); + break; + case TO_SPLIT_RIGHT_INDICATOR: + startBounds.set(displayLayout.width() / 2 + padding, padding, + displayLayout.width() - padding, + displayLayout.height() - padding); + break; + } view.getBackground().setBounds(startBounds); final VisualIndicatorAnimator animator = new VisualIndicatorAnimator( - view, startBounds, getMaxBounds(displayLayout)); + view, startBounds, getMaxBounds(startBounds)); animator.setInterpolator(new DecelerateInterpolator()); setupIndicatorAnimation(animator); return animator; @@ -252,12 +349,13 @@ public class DesktopModeVisualIndicator { final float adjustmentPercentage = 1f - FINAL_FREEFORM_SCALE; final int width = displayLayout.width(); final int height = displayLayout.height(); + Rect startBounds = new Rect(0, 0, width, height); Rect endBounds = new Rect((int) (adjustmentPercentage * width / 2), (int) (adjustmentPercentage * height / 2), (int) (displayLayout.width() - (adjustmentPercentage * width / 2)), (int) (displayLayout.height() - (adjustmentPercentage * height / 2))); final VisualIndicatorAnimator animator = new VisualIndicatorAnimator( - view, getMaxBounds(displayLayout), endBounds); + view, startBounds, endBounds); animator.setInterpolator(new DecelerateInterpolator()); setupIndicatorAnimation(animator); return animator; @@ -310,21 +408,17 @@ public class DesktopModeVisualIndicator { } /** - * Return the max bounds of a fullscreen indicator + * Return the max bounds of a visual indicator */ - private static Rect getMaxBounds(@NonNull DisplayLayout displayLayout) { - final int padding = displayLayout.stableInsets().top; - final int width = displayLayout.width() - 2 * padding; - final int height = displayLayout.height() - 2 * padding; - Rect endBounds = new Rect((int) (padding - - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * width)), - (int) (padding - - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * height)), - (int) (displayLayout.width() - padding - + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * width)), - (int) (displayLayout.height() - padding - + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * height))); - return endBounds; + private static Rect getMaxBounds(Rect startBounds) { + return new Rect((int) (startBounds.left + - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.width())), + (int) (startBounds.top + - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.height())), + (int) (startBounds.right + + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.width())), + (int) (startBounds.bottom + + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.height()))); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 4740a9d2e030..236dec0f555b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -29,6 +29,7 @@ import android.app.WindowConfiguration.WindowingMode import android.content.Context import android.content.res.TypedArray import android.graphics.Point +import android.graphics.PointF import android.graphics.Rect import android.graphics.Region import android.os.IBinder @@ -55,7 +56,10 @@ import com.android.wm.shell.common.SingleInstanceRemoteListener import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.common.annotations.ExternalThread import com.android.wm.shell.common.annotations.ShellMainThread +import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT +import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener +import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.TO_DESKTOP_INDICATOR import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.sysui.ShellCommandHandler @@ -108,6 +112,11 @@ class DesktopTasksController( com.android.wm.shell.R.dimen.desktop_mode_transition_area_height ) + private val transitionAreaWidth + get() = context.resources.getDimensionPixelSize( + com.android.wm.shell.R.dimen.desktop_mode_transition_area_width + ) + // This is public to avoid cyclic dependency; it is set by SplitScreenController lateinit var splitScreenController: SplitScreenController @@ -805,7 +814,8 @@ class DesktopTasksController( ) { val wct = WindowContainerTransaction() addMoveToSplitChanges(wct, taskInfo) - splitScreenController.requestEnterSplitSelect(taskInfo, wct) + splitScreenController.requestEnterSplitSelect(taskInfo, wct, + SPLIT_POSITION_BOTTOM_OR_RIGHT, taskInfo.configuration.windowConfiguration.bounds) } } @@ -829,25 +839,36 @@ class DesktopTasksController( /** * Perform checks required on drag move. Create/release fullscreen indicator as needed. + * Different sources for x and y coordinates are used due to different needs for each: + * We want split transitions to be based on input coordinates but fullscreen transition + * to be based on task edge coordinate. * * @param taskInfo the task being dragged. * @param taskSurface SurfaceControl of dragged task. - * @param y coordinate of dragged task. Used for checks against status bar height. + * @param inputCoordinate coordinates of input. Used for checks against left/right edge of screen. + * @param taskBounds bounds of dragged task. Used for checks against status bar height. */ fun onDragPositioningMove( - taskInfo: RunningTaskInfo, - taskSurface: SurfaceControl, - y: Float + taskInfo: RunningTaskInfo, + taskSurface: SurfaceControl, + inputCoordinate: PointF, + taskBounds: Rect ) { - if (taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) { - if (y <= transitionAreaHeight && visualIndicator == null) { - visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo, - displayController, context, taskSurface, shellTaskOrganizer, - rootTaskDisplayAreaOrganizer) - visualIndicator?.createFullscreenIndicatorWithAnimatedBounds() - } else if (y > transitionAreaHeight && visualIndicator != null) { - releaseVisualIndicator() - } + val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return + if (taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) return + var type = DesktopModeVisualIndicator.determineIndicatorType(inputCoordinate, + taskBounds, displayLayout, context) + if (type != DesktopModeVisualIndicator.INVALID_INDICATOR && visualIndicator == null) { + visualIndicator = DesktopModeVisualIndicator( + syncQueue, taskInfo, + displayController, context, taskSurface, shellTaskOrganizer, + rootTaskDisplayAreaOrganizer, type) + visualIndicator?.createIndicatorWithAnimatedBounds() + return + } + if (visualIndicator?.eventOutsideRange(inputCoordinate.x, + taskBounds.top.toFloat()) == true) { + releaseVisualIndicator() } } @@ -856,19 +877,39 @@ class DesktopTasksController( * * @param taskInfo the task being dragged. * @param position position of surface when drag ends. - * @param y the Y position of the top edge of the task + * @param inputCoordinate the coordinates of the motion event + * @param taskBounds the updated bounds of the task being dragged. * @param windowDecor the window decoration for the task being dragged */ fun onDragPositioningEnd( - taskInfo: RunningTaskInfo, - position: Point, - y: Float, - windowDecor: DesktopModeWindowDecoration + taskInfo: RunningTaskInfo, + position: Point, + inputCoordinate: PointF, + taskBounds: Rect, + windowDecor: DesktopModeWindowDecoration ) { - if (y <= transitionAreaHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) { + if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) { + return + } + if (taskBounds.top <= transitionAreaHeight) { windowDecor.incrementRelayoutBlock() moveToFullscreenWithAnimation(taskInfo, position) } + if (inputCoordinate.x <= transitionAreaWidth) { + releaseVisualIndicator() + var wct = WindowContainerTransaction() + addMoveToSplitChanges(wct, taskInfo) + splitScreenController.requestEnterSplitSelect(taskInfo, wct, + SPLIT_POSITION_TOP_OR_LEFT, taskBounds) + } + if (inputCoordinate.x >= (displayController.getDisplayLayout(taskInfo.displayId)?.width() + ?.minus(transitionAreaWidth) ?: return)) { + releaseVisualIndicator() + var wct = WindowContainerTransaction() + addMoveToSplitChanges(wct, taskInfo) + splitScreenController.requestEnterSplitSelect(taskInfo, wct, + SPLIT_POSITION_BOTTOM_OR_RIGHT, taskBounds) + } } /** @@ -892,8 +933,8 @@ class DesktopTasksController( if (visualIndicator == null) { visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo, displayController, context, taskSurface, shellTaskOrganizer, - rootTaskDisplayAreaOrganizer) - visualIndicator?.createFullscreenIndicator() + rootTaskDisplayAreaOrganizer, TO_DESKTOP_INDICATOR) + visualIndicator?.createIndicatorWithAnimatedBounds() } val indicator = visualIndicator ?: return if (y >= getFreeformTransitionStatusBarDragThreshold(taskInfo)) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index ed9ff1c169c6..9e8f9c68d43d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -82,6 +82,9 @@ import com.android.wm.shell.common.ScreenshotUtils; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.phone.PipMotionHelper; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 83e03dc850a1..e3922d65215c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -64,6 +64,9 @@ import androidx.annotation.Nullable; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -89,6 +92,7 @@ public class PipTransition extends PipTransitionController { private final int mEnterExitAnimationDuration; private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; private final Optional<SplitScreenController> mSplitScreenOptional; + private final PipAnimationController mPipAnimationController; private @PipAnimationController.AnimationType int mEnterAnimationType = ANIM_TYPE_BOUNDS; private Transitions.TransitionFinishCallback mFinishCallback; private SurfaceControl.Transaction mFinishTransaction; @@ -137,10 +141,11 @@ public class PipTransition extends PipTransitionController { PipSurfaceTransactionHelper pipSurfaceTransactionHelper, Optional<SplitScreenController> splitScreenOptional) { super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController, - pipBoundsAlgorithm, pipAnimationController); + pipBoundsAlgorithm); mContext = context; mPipTransitionState = pipTransitionState; mPipDisplayLayoutState = pipDisplayLayoutState; + mPipAnimationController = pipAnimationController; mEnterExitAnimationDuration = context.getResources() .getInteger(R.integer.config_pipResizeAnimationDuration); mSurfaceTransactionHelper = pipSurfaceTransactionHelper; @@ -148,6 +153,13 @@ public class PipTransition extends PipTransitionController { } @Override + protected void onInit() { + if (!PipUtils.isPip2ExperimentEnabled()) { + mTransitions.addHandler(this); + } + } + + @Override public void startExitTransition(int type, WindowContainerTransaction out, @Nullable Rect destinationBounds) { if (destinationBounds != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index 64bba672a5b5..20c57fa5e566 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -38,7 +38,8 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.common.pip.PipUtils; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.split.SplitScreenUtils; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; @@ -52,7 +53,6 @@ import java.util.List; */ public abstract class PipTransitionController implements Transitions.TransitionHandler { - protected final PipAnimationController mPipAnimationController; protected final PipBoundsAlgorithm mPipBoundsAlgorithm; protected final PipBoundsState mPipBoundsState; protected final ShellTaskOrganizer mShellTaskOrganizer; @@ -135,22 +135,18 @@ public abstract class PipTransitionController implements Transitions.TransitionH @NonNull ShellTaskOrganizer shellTaskOrganizer, @NonNull Transitions transitions, PipBoundsState pipBoundsState, - PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm, - PipAnimationController pipAnimationController) { + PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm) { mPipBoundsState = pipBoundsState; mPipMenuController = pipMenuController; mShellTaskOrganizer = shellTaskOrganizer; mPipBoundsAlgorithm = pipBoundsAlgorithm; - mPipAnimationController = pipAnimationController; mTransitions = transitions; - if (!PipUtils.isPip2ExperimentEnabled()) { - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - shellInit.addInitCallback(this::onInit, this); - } + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + shellInit.addInitCallback(this::onInit, this); } } - private void onInit() { + protected void onInit() { mTransitions.addHandler(this); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java index cc182ba89985..760652625f9e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java @@ -38,10 +38,10 @@ import android.view.WindowManagerGlobal; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SystemWindows; +import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipMediaController; import com.android.wm.shell.common.pip.PipMediaController.ActionListener; import com.android.wm.shell.common.pip.PipUiEventLogger; -import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipMenuController; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java index 8c2879e0d816..118ad9c4bfe3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java @@ -15,7 +15,7 @@ */ package com.android.wm.shell.pip.phone; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE; import android.annotation.NonNull; import android.content.Context; @@ -36,8 +36,8 @@ import androidx.annotation.BinderThread; import com.android.wm.shell.R; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipSnapAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipTaskOrganizer; import java.util.ArrayList; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index ddea574c3c89..106486714a5c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -76,7 +76,12 @@ import com.android.wm.shell.common.TabletopModeController; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.pip.PipAppOpsListener; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface; import com.android.wm.shell.common.pip.PipMediaController; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; @@ -85,12 +90,7 @@ import com.android.wm.shell.pip.IPipAnimationListener; import com.android.wm.shell.pip.PinnedStackListenerForwarder; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipDisplayLayoutState; -import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface; import com.android.wm.shell.pip.PipParamsChangedForwarder; -import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java index d7d335b856be..1b1ebc39b558 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java @@ -20,7 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.graphics.Rect; -import com.android.wm.shell.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipBoundsState; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java index 8f0a8e11103c..c708b86e88c5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java @@ -20,10 +20,10 @@ import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_NO_B import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW; import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT; import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_DISMISS; import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE; @@ -41,8 +41,8 @@ import com.android.wm.shell.animation.PhysicsAnimator; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; import com.android.wm.shell.common.pip.PipAppOpsListener; -import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipSnapAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index 4e687dda4e77..e5f9fdc7a740 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -46,10 +46,11 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.policy.TaskResizingAlgorithm; import com.android.wm.shell.R; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipPinchResizingAlgorithm; import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipTaskOrganizer; import java.io.PrintWriter; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index cf54a7163d04..2ce4fb9e297b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -18,10 +18,10 @@ package com.android.wm.shell.pip.phone; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASH_MINIMUM_VELOCITY_THRESHOLD; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT; import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL; import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE; import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE; @@ -50,12 +50,12 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java index cd58ff4f5154..a48e969fde35 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java @@ -36,11 +36,11 @@ import androidx.annotation.NonNull; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; import com.android.wm.shell.common.pip.SizeSpecSource; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipDisplayLayoutState; -import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface; -import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java index 8d4a38442ce5..8a215b4b2e25 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java @@ -16,7 +16,7 @@ package com.android.wm.shell.pip.tv; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java index d11f4d59a41d..2b3a93e3c3e8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java @@ -29,10 +29,10 @@ import android.util.Size; import android.view.Gravity; import android.view.View; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.SizeSpecSource; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipDisplayLayoutState; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index 5f5d8ad588dc..72115fdefa05 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -48,11 +48,11 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.pip.PipAppOpsListener; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.PipMediaController; import com.android.wm.shell.pip.PinnedStackListenerForwarder; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.pip.PipDisplayLayoutState; import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt index a94bd6ec1040..93f6826ac96b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt @@ -21,12 +21,12 @@ import android.graphics.Point import android.graphics.Rect import android.util.Size import android.view.Gravity -import com.android.wm.shell.pip.PipBoundsState -import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_BOTTOM -import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT -import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE -import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT -import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_TOP +import com.android.wm.shell.common.pip.PipBoundsState +import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_BOTTOM +import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT +import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE +import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT +import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_TOP import kotlin.math.abs import kotlin.math.max import kotlin.math.min @@ -54,11 +54,11 @@ class TvPipKeepClearAlgorithm() { * the unstash timeout if already stashed. */ data class Placement( - val bounds: Rect, - val anchorBounds: Rect, - @PipBoundsState.StashType val stashType: Int = STASH_TYPE_NONE, - val unstashDestinationBounds: Rect? = null, - val triggerStash: Boolean = false + val bounds: Rect, + val anchorBounds: Rect, + @PipBoundsState.StashType val stashType: Int = STASH_TYPE_NONE, + val unstashDestinationBounds: Rect? = null, + val triggerStash: Boolean = false ) { /** Bounds to use if the PiP should not be stashed. */ fun getUnstashedBounds() = unstashDestinationBounds ?: bounds diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java index 672080407658..f315afba9a03 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java @@ -25,12 +25,12 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipDisplayLayoutState; import com.android.wm.shell.pip.PipMenuController; import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java index d3253a5e4d94..f24b2b385cad 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java @@ -21,8 +21,8 @@ import android.content.Context; import androidx.annotation.NonNull; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.pip.PipDisplayLayoutState; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import com.android.wm.shell.pip.PipTransition; import com.android.wm.shell.pip.PipTransitionState; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java index 8ab85d0829df..b8e4c04ac262 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java @@ -17,27 +17,65 @@ package com.android.wm.shell.pip2; import android.annotation.NonNull; +import android.os.IBinder; +import android.view.SurfaceControl; +import android.window.TransitionInfo; +import android.window.TransitionRequestInfo; +import android.window.WindowContainerTransaction; + +import androidx.annotation.Nullable; import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.PipMenuController; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; /** Placeholder, for demonstrate purpose only. */ -public abstract class PipTransition extends PipTransitionController { +public class PipTransition extends PipTransitionController { public PipTransition( @NonNull ShellInit shellInit, @NonNull ShellTaskOrganizer shellTaskOrganizer, @NonNull Transitions transitions, PipBoundsState pipBoundsState, PipMenuController pipMenuController, - PipBoundsAlgorithm pipBoundsAlgorithm, - PipAnimationController pipAnimationController) { + PipBoundsAlgorithm pipBoundsAlgorithm) { super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController, - pipBoundsAlgorithm, pipAnimationController); + pipBoundsAlgorithm); + } + + @Override + protected void onInit() { + if (PipUtils.isPip2ExperimentEnabled()) { + mTransitions.addHandler(this); + } + } + + @Nullable + @Override + public WindowContainerTransaction handleRequest(@NonNull IBinder transition, + @NonNull TransitionRequestInfo request) { + return null; } + + @Override + public boolean startAnimation(@NonNull IBinder transition, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + return false; + } + + @Override + public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) {} + + @Override + public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, + @Nullable SurfaceControl.Transaction finishT) {} } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl index 7171da5d885d..a25f39148b89 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl @@ -17,7 +17,7 @@ package com.android.wm.shell.splitscreen; import android.app.ActivityManager.RunningTaskInfo; - +import android.graphics.Rect; /** * Listener interface that Launcher attaches to SystemUI to get split-select callbacks. */ @@ -25,5 +25,5 @@ interface ISplitSelectListener { /** * Called when a task requests to enter split select */ - boolean onRequestSplitSelect(in RunningTaskInfo taskInfo); + boolean onRequestSplitSelect(in RunningTaskInfo taskInfo, int splitPosition, in Rect taskBounds); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index f20fe0b88e12..ad4049320d93 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -66,7 +66,8 @@ public interface SplitScreen { /** Callback interface for listening to requests to enter split select */ interface SplitSelectListener { - default boolean onRequestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo) { + default boolean onRequestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo, + int splitPosition, Rect taskBounds) { 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 210bf68f3d4f..f90ee586e696 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 @@ -507,10 +507,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, * Move a task to split select * @param taskInfo the task being moved to split select * @param wct transaction to apply if this is a valid request + * @param splitPosition the split position this task should move to + * @param taskBounds current freeform bounds of the task entering split */ public void requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo, - WindowContainerTransaction wct) { - mStageCoordinator.requestEnterSplitSelect(taskInfo, wct); + WindowContainerTransaction wct, int splitPosition, Rect taskBounds) { + mStageCoordinator.requestEnterSplitSelect(taskInfo, wct, splitPosition, taskBounds); } public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) { @@ -1135,9 +1137,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, new SplitScreen.SplitSelectListener() { @Override public boolean onRequestEnterSplitSelect( - ActivityManager.RunningTaskInfo taskInfo) { + ActivityManager.RunningTaskInfo taskInfo, int splitPosition, + Rect taskBounds) { AtomicBoolean result = new AtomicBoolean(false); - mSelectListener.call(l -> result.set(l.onRequestSplitSelect(taskInfo))); + mSelectListener.call(l -> result.set(l.onRequestSplitSelect(taskInfo, + splitPosition, taskBounds))); return result.get(); } }; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 697006868e5a..842b1bf9e8af 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -466,10 +466,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } void requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo, - WindowContainerTransaction wct) { + WindowContainerTransaction wct, int splitPosition, Rect taskBounds) { boolean enteredSplitSelect = false; for (SplitScreen.SplitSelectListener listener : mSelectListeners) { - enteredSplitSelect |= listener.onRequestEnterSplitSelect(taskInfo); + enteredSplitSelect |= listener.onRequestEnterSplitSelect(taskInfo, splitPosition, + taskBounds); } if (enteredSplitSelect) mTaskOrganizer.applyTransaction(wct); } 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 026e973f881a..92b44d43567d 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 @@ -36,6 +36,7 @@ import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; import android.content.Context; import android.graphics.Point; +import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; import android.hardware.input.InputManager; @@ -507,7 +508,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningMove( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo, - decoration.mTaskSurface, newTaskBounds.top)); + decoration.mTaskSurface, + new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)), + newTaskBounds)); mIsDragging = true; mShouldClick = false; return true; @@ -536,7 +539,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo, - position, newTaskBounds.top, mWindowDecorByTaskId.get(mTaskId))); + position, + new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)), + newTaskBounds, mWindowDecorByTaskId.get(mTaskId))); mIsDragging = false; return true; } diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp index dfbadae2b853..434b008a8e83 100644 --- a/libs/WindowManager/Shell/tests/flicker/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/Android.bp @@ -69,10 +69,22 @@ filegroup { } filegroup { + name: "WMShellFlickerServicePlatinumTests-src", + srcs: [ + "src/com/android/wm/shell/flicker/service/*/platinum/**/*.kt", + "src/com/android/wm/shell/flicker/service/*/scenarios/**/*.kt", + "src/com/android/wm/shell/flicker/service/common/**/*.kt", + ], +} + +filegroup { name: "WMShellFlickerServiceTests-src", srcs: [ "src/com/android/wm/shell/flicker/service/**/*.kt", ], + exclude_srcs: [ + "src/com/android/wm/shell/flicker/service/*/platinum/**/*.kt", + ], } java_library { @@ -143,6 +155,7 @@ android_test { ":WMShellFlickerTestsSplitScreenGroup2-src", ":WMShellFlickerTestsSplitScreenBase-src", ":WMShellFlickerServiceTests-src", + ":WMShellFlickerServicePlatinumTests-src", ], } @@ -210,3 +223,15 @@ android_test { ":WMShellFlickerServiceTests-src", ], } + +android_test { + name: "WMShellFlickerServicePlatinumTests", + defaults: ["WMShellFlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestService.xml"], + package_name: "com.android.wm.shell.flicker.service", + instrumentation_target_package: "com.android.wm.shell.flicker.service", + srcs: [ + ":WMShellFlickerTestsBase-src", + ":WMShellFlickerServicePlatinumTests-src", + ], +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/common/Utils.kt index fa723e3110e0..5f157856aa36 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/common/Utils.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service +package com.android.wm.shell.flicker.service.common import android.app.Instrumentation import android.platform.test.rule.NavigationModeRule diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt index e530f6369609..245184cf0327 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt @@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.Utils +import com.android.wm.shell.flicker.service.common.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt index e9fc43746d27..1f2f1ecc0aab 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt @@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.Utils +import com.android.wm.shell.flicker.service.common.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt index 416692c37b34..ebbf7c5026c7 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt @@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.Utils +import com.android.wm.shell.flicker.service.common.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt index 494a246d2f50..71e701c9e2be 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt @@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.Utils +import com.android.wm.shell.flicker.service.common.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt index 9b438163daa8..c433b211b53d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt @@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.Utils +import com.android.wm.shell.flicker.service.common.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Assume diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt index 50151f1b7efe..3f087a5b7ecd 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt @@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.Utils +import com.android.wm.shell.flicker.service.common.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Assume diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt index 76fbf60897ca..767e7b555618 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt @@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.Utils +import com.android.wm.shell.flicker.service.common.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Assume diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt index f8e43f1207e2..2592fd40d902 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt @@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.Utils +import com.android.wm.shell.flicker.service.common.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Assume diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt index c2100f641a55..f2cbf24bd26e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt @@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.Utils +import com.android.wm.shell.flicker.service.common.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt index 70f3bed9afdc..538ed960dac6 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt @@ -25,7 +25,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.Utils +import com.android.wm.shell.flicker.service.common.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt index 86f394da0231..0dab5ad622ad 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt @@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.Utils +import com.android.wm.shell.flicker.service.common.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt index d7b611e04d9d..ad3a2d41f02a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt @@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.Utils +import com.android.wm.shell.flicker.service.common.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt index 3cc5df09b16e..b780a1681762 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt @@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.Utils +import com.android.wm.shell.flicker.service.common.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt index 4a9c32f10415..329d61dcdbf9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt @@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.Utils +import com.android.wm.shell.flicker.service.common.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt index 383a6b39a2b6..a9933bbe09fc 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt @@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.Utils +import com.android.wm.shell.flicker.service.common.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java index bf1b7f9f86ed..46259a8b177f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java @@ -33,6 +33,11 @@ import com.android.wm.shell.R; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.pip.PhoneSizeSpecSource; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; import com.android.wm.shell.common.pip.SizeSpecSource; import org.junit.Before; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java index 4341c4c8c8aa..d34e27b57071 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java @@ -36,6 +36,8 @@ import com.android.internal.util.function.TriConsumer; import com.android.wm.shell.R; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.pip.PhoneSizeSpecSource; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.SizeSpecSource; import org.junit.Before; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java index b9226d2b9b91..ac13d7ffcd61 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java @@ -25,6 +25,8 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; import org.junit.Before; import org.junit.Test; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index 248d66590542..4e2b7f6d16b2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -53,6 +53,11 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.pip.PhoneSizeSpecSource; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.pip.phone.PhonePipMenuController; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java index cc9e26b2c4f1..8c7b47ea7d84 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java @@ -29,8 +29,9 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; import org.junit.Before; import org.junit.Test; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java index 024cba319ffc..3d5cd6939d1b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java @@ -33,8 +33,8 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.pip.PhoneSizeSpecSource; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.SizeSpecSource; -import com.android.wm.shell.pip.PipDisplayLayoutState; import org.junit.After; import org.junit.Assert; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index 911f5e165ef1..4eb519334e12 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -55,15 +55,16 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TabletopModeController; import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm; import com.android.wm.shell.common.pip.PipAppOpsListener; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.PipMediaController; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipDisplayLayoutState; import com.android.wm.shell.pip.PipParamsChangedForwarder; -import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java index 8ce3ca4bdc00..0f8db85dcef4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java @@ -29,7 +29,7 @@ import android.graphics.Rect; import android.testing.AndroidTestingRunner; import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipBoundsState; import org.junit.Assert; import org.junit.Before; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java index 12b4f3e50f32..6777a5bd8ceb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java @@ -37,13 +37,13 @@ import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PhoneSizeSpecSource; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.common.pip.SizeSpecSource; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipDisplayLayoutState; -import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface; -import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java index 314f195d87ac..9aaabd130527 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java @@ -33,13 +33,13 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PhoneSizeSpecSource; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.common.pip.SizeSpecSource; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipDisplayLayoutState; -import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface; -import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.sysui.ShellInit; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt index 7370ed71bbdd..94f2b9131ebd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt @@ -25,7 +25,7 @@ import android.testing.AndroidTestingRunner import com.android.wm.shell.R import com.android.wm.shell.ShellTestCase -import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT +import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT import com.android.wm.shell.pip.tv.TvPipBoundsController.POSITION_DEBOUNCE_TIMEOUT_MILLIS import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java index 256610b857a1..974539f23b80 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java @@ -27,9 +27,9 @@ import android.view.Gravity; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.pip.LegacySizeSpecSource; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; import com.android.wm.shell.common.pip.SizeSpecSource; -import com.android.wm.shell.pip.PipDisplayLayoutState; -import com.android.wm.shell.pip.PipSnapAlgorithm; import org.junit.Before; import org.junit.Test; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt index aedf65ddc269..998060fdcac2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt @@ -22,10 +22,10 @@ import android.testing.AndroidTestingRunner import android.util.Size import android.view.Gravity import com.android.wm.shell.ShellTestCase -import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_BOTTOM -import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE -import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT -import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_TOP +import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_BOTTOM +import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE +import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT +import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_TOP import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index b1ef4e5cd3a9..652346976c67 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -91,12 +91,17 @@ struct FindEntryResult { StringPoolRef entry_string_ref; }; -AssetManager2::AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration) - : configuration_(configuration) { +AssetManager2::AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration) { + configurations_.push_back(configuration); + // Don't invalidate caches here as there's nothing cached yet. SetApkAssets(apk_assets, false); } +AssetManager2::AssetManager2() { + configurations_.resize(1); +} + bool AssetManager2::SetApkAssets(ApkAssetsList apk_assets, bool invalidate_caches) { BuildDynamicRefTable(apk_assets); RebuildFilterList(); @@ -421,9 +426,16 @@ bool AssetManager2::ContainsAllocatedTable() const { return false; } -void AssetManager2::SetConfiguration(const ResTable_config& configuration) { - const int diff = configuration_.diff(configuration); - configuration_ = configuration; +void AssetManager2::SetConfigurations(std::vector<ResTable_config> configurations) { + int diff = 0; + if (configurations_.size() != configurations.size()) { + diff = -1; + } else { + for (int i = 0; i < configurations_.size(); i++) { + diff |= configurations_[i].diff(configurations[i]); + } + } + configurations_ = std::move(configurations); if (diff) { RebuildFilterList(); @@ -620,16 +632,6 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry( auto op = StartOperation(); - // Might use this if density_override != 0. - ResTable_config density_override_config; - - // Select our configuration or generate a density override configuration. - const ResTable_config* desired_config = &configuration_; - if (density_override != 0 && density_override != configuration_.density) { - density_override_config = configuration_; - density_override_config.density = density_override; - desired_config = &density_override_config; - } // Retrieve the package group from the package id of the resource id. if (UNLIKELY(!is_valid_resid(resid))) { @@ -648,119 +650,160 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry( } const PackageGroup& package_group = package_groups_[package_idx]; - auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config, - stop_at_first_match, ignore_configuration); - if (UNLIKELY(!result.has_value())) { - return base::unexpected(result.error()); - } + std::optional<FindEntryResult> final_result; + bool final_has_locale = false; + bool final_overlaid = false; + for (auto & config : configurations_) { + // Might use this if density_override != 0. + ResTable_config density_override_config; + + // Select our configuration or generate a density override configuration. + const ResTable_config* desired_config = &config; + if (density_override != 0 && density_override != config.density) { + density_override_config = config; + density_override_config.density = density_override; + desired_config = &density_override_config; + } - bool overlaid = false; - if (!stop_at_first_match && !ignore_configuration) { - const auto& assets = GetApkAssets(result->cookie); - if (!assets) { - ALOGE("Found expired ApkAssets #%d for resource ID 0x%08x.", result->cookie, resid); - return base::unexpected(std::nullopt); + auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config, + stop_at_first_match, ignore_configuration); + if (UNLIKELY(!result.has_value())) { + return base::unexpected(result.error()); } - if (!assets->IsLoader()) { - for (const auto& id_map : package_group.overlays_) { - auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid); - if (!overlay_entry) { - // No id map entry exists for this target resource. - continue; - } - if (overlay_entry.IsInlineValue()) { - // The target resource is overlaid by an inline value not represented by a resource. - ConfigDescription best_frro_config; - Res_value best_frro_value; - bool frro_found = false; - for( const auto& [config, value] : overlay_entry.GetInlineValue()) { - if ((!frro_found || config.isBetterThan(best_frro_config, desired_config)) - && config.match(*desired_config)) { - frro_found = true; - best_frro_config = config; - best_frro_value = value; + bool overlaid = false; + if (!stop_at_first_match && !ignore_configuration) { + const auto& assets = GetApkAssets(result->cookie); + if (!assets) { + ALOGE("Found expired ApkAssets #%d for resource ID 0x%08x.", result->cookie, resid); + return base::unexpected(std::nullopt); + } + if (!assets->IsLoader()) { + for (const auto& id_map : package_group.overlays_) { + auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid); + if (!overlay_entry) { + // No id map entry exists for this target resource. + continue; + } + if (overlay_entry.IsInlineValue()) { + // The target resource is overlaid by an inline value not represented by a resource. + ConfigDescription best_frro_config; + Res_value best_frro_value; + bool frro_found = false; + for( const auto& [config, value] : overlay_entry.GetInlineValue()) { + if ((!frro_found || config.isBetterThan(best_frro_config, desired_config)) + && config.match(*desired_config)) { + frro_found = true; + best_frro_config = config; + best_frro_value = value; + } + } + if (!frro_found) { + continue; } + result->entry = best_frro_value; + result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable(); + result->cookie = id_map.cookie; + + if (UNLIKELY(logging_enabled)) { + last_resolution_.steps.push_back(Resolution::Step{ + Resolution::Step::Type::OVERLAID_INLINE, result->cookie, String8()}); + if (auto path = assets->GetPath()) { + const std::string overlay_path = path->data(); + if (IsFabricatedOverlay(overlay_path)) { + // FRRO don't have package name so we use the creating package here. + String8 frro_name = String8("FRRO"); + // Get the first part of it since the expected one should be like + // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro + // under /data/resource-cache/. + const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1); + const size_t end = name.find('-'); + if (frro_name.size() != overlay_path.size() && end != std::string::npos) { + frro_name.append(base::StringPrintf(" created by %s", + name.substr(0 /* pos */, + end).c_str()).c_str()); + } + last_resolution_.best_package_name = frro_name; + } else { + last_resolution_.best_package_name = result->package_name->c_str(); + } + } + overlaid = true; + } + continue; + } + + auto overlay_result = FindEntry(overlay_entry.GetResourceId(), density_override, + false /* stop_at_first_match */, + false /* ignore_configuration */); + if (UNLIKELY(IsIOError(overlay_result))) { + return base::unexpected(overlay_result.error()); + } + if (!overlay_result.has_value()) { + continue; } - if (!frro_found) { + + if (!overlay_result->config.isBetterThan(result->config, desired_config) + && overlay_result->config.compare(result->config) != 0) { + // The configuration of the entry for the overlay must be equal to or better than the + // target configuration to be chosen as the better value. continue; } - result->entry = best_frro_value; + + result->cookie = overlay_result->cookie; + result->entry = overlay_result->entry; + result->config = overlay_result->config; result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable(); - result->cookie = id_map.cookie; if (UNLIKELY(logging_enabled)) { last_resolution_.steps.push_back( - Resolution::Step{Resolution::Step::Type::OVERLAID_INLINE, result->cookie, String8()}); - if (auto path = assets->GetPath()) { - const std::string overlay_path = path->data(); - if (IsFabricatedOverlay(overlay_path)) { - // FRRO don't have package name so we use the creating package here. - String8 frro_name = String8("FRRO"); - // Get the first part of it since the expected one should be like - // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro - // under /data/resource-cache/. - const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1); - const size_t end = name.find('-'); - if (frro_name.size() != overlay_path.size() && end != std::string::npos) { - frro_name.append(base::StringPrintf(" created by %s", - name.substr(0 /* pos */, - end).c_str()).c_str()); - } - last_resolution_.best_package_name = frro_name; - } else { - last_resolution_.best_package_name = result->package_name->c_str(); - } - } + Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->cookie, + overlay_result->config.toString()}); + last_resolution_.best_package_name = + overlay_result->package_name->c_str(); overlaid = true; } - continue; } + } + } - auto overlay_result = FindEntry(overlay_entry.GetResourceId(), density_override, - false /* stop_at_first_match */, - false /* ignore_configuration */); - if (UNLIKELY(IsIOError(overlay_result))) { - return base::unexpected(overlay_result.error()); - } - if (!overlay_result.has_value()) { - continue; - } - - if (!overlay_result->config.isBetterThan(result->config, desired_config) - && overlay_result->config.compare(result->config) != 0) { - // The configuration of the entry for the overlay must be equal to or better than the target - // configuration to be chosen as the better value. - continue; - } - - result->cookie = overlay_result->cookie; - result->entry = overlay_result->entry; - result->config = overlay_result->config; - result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable(); - - if (UNLIKELY(logging_enabled)) { - last_resolution_.steps.push_back( - Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->cookie, - overlay_result->config.toString()}); - last_resolution_.best_package_name = - overlay_result->package_name->c_str(); - overlaid = true; - } + bool has_locale = false; + if (result->config.locale == 0) { + if (default_locale_ != 0) { + ResTable_config conf; + conf.locale = default_locale_; + // Since we know conf has a locale and only a locale, match will tell us if that locale + // matches + has_locale = conf.match(config); } + } else { + has_locale = true; + } + + // if we don't have a result yet + if (!final_result || + // or this config is better before the locale than the existing result + result->config.isBetterThanBeforeLocale(final_result->config, desired_config) || + // or the existing config isn't better before locale and this one specifies a locale + // whereas the existing one doesn't + (!final_result->config.isBetterThanBeforeLocale(result->config, desired_config) + && has_locale && !final_has_locale)) { + final_result = result.value(); + final_overlaid = overlaid; + final_has_locale = has_locale; } } if (UNLIKELY(logging_enabled)) { - last_resolution_.cookie = result->cookie; - last_resolution_.type_string_ref = result->type_string_ref; - last_resolution_.entry_string_ref = result->entry_string_ref; - last_resolution_.best_config_name = result->config.toString(); - if (!overlaid) { - last_resolution_.best_package_name = result->package_name->c_str(); + last_resolution_.cookie = final_result->cookie; + last_resolution_.type_string_ref = final_result->type_string_ref; + last_resolution_.entry_string_ref = final_result->entry_string_ref; + last_resolution_.best_config_name = final_result->config.toString(); + if (!final_overlaid) { + last_resolution_.best_package_name = final_result->package_name->c_str(); } } - return result; + return *final_result; } base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal( @@ -778,8 +821,10 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal( // If `desired_config` is not the same as the set configuration or the caller will accept a value // from any configuration, then we cannot use our filtered list of types since it only it contains // types matched to the set configuration. - const bool use_filtered = !ignore_configuration && &desired_config == &configuration_; - + const bool use_filtered = !ignore_configuration && std::find_if( + configurations_.begin(), configurations_.end(), + [&desired_config](auto& value) { return &desired_config == &value; }) + != configurations_.end(); const size_t package_count = package_group.packages_.size(); for (size_t pi = 0; pi < package_count; pi++) { const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi]; @@ -934,10 +979,22 @@ std::string AssetManager2::GetLastResourceResolution() const { } std::stringstream log_stream; - log_stream << base::StringPrintf("Resolution for 0x%08x %s\n" - "\tFor config - %s", resid, resource_name_string.c_str(), - configuration_.toString().c_str()); - + if (configurations_.size() == 1) { + log_stream << base::StringPrintf("Resolution for 0x%08x %s\n" + "\tFor config - %s", resid, resource_name_string.c_str(), + configurations_[0].toString().c_str()); + } else { + ResTable_config conf = configurations_[0]; + conf.clearLocale(); + log_stream << base::StringPrintf("Resolution for 0x%08x %s\n\tFor config - %s and locales", + resid, resource_name_string.c_str(), conf.toString().c_str()); + char str[40]; + str[0] = '\0'; + for(auto iter = configurations_.begin(); iter < configurations_.end(); iter++) { + iter->getBcp47Locale(str); + log_stream << base::StringPrintf(" %s%s", str, iter < configurations_.end() ? "," : ""); + } + } for (const Resolution::Step& step : last_resolution_.steps) { constexpr static std::array kStepStrings = { "Found initial", @@ -956,13 +1013,13 @@ std::string AssetManager2::GetLastResourceResolution() const { const auto& assets = GetApkAssets(step.cookie); log_stream << "\n\t" << prefix << ": " << (assets ? assets->GetDebugName() : "<null>") << " #" << step.cookie; - if (!step.config_name.isEmpty()) { + if (!step.config_name.empty()) { log_stream << " - " << step.config_name; } } log_stream << "\nBest matching is from " - << (last_resolution_.best_config_name.isEmpty() ? "default" + << (last_resolution_.best_config_name.empty() ? "default" : last_resolution_.best_config_name) << " configuration of " << last_resolution_.best_package_name; return log_stream.str(); @@ -1427,11 +1484,14 @@ void AssetManager2::RebuildFilterList() { package.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) { FilteredConfigGroup* group = nullptr; for (const auto& type_entry : type_spec.type_entries) { - if (type_entry.config.match(configuration_)) { - if (!group) { - group = &package.filtered_configs_.editItemAt(type_id - 1); + for (auto & config : configurations_) { + if (type_entry.config.match(config)) { + if (!group) { + group = &package.filtered_configs_.editItemAt(type_id - 1); + } + group->type_entries.push_back(&type_entry); + break; } - group->type_entries.push_back(&type_entry); } } }); diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 112058fc65c7..ec143160e8a1 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -2568,6 +2568,22 @@ bool ResTable_config::isLocaleBetterThan(const ResTable_config& o, return false; } +bool ResTable_config::isBetterThanBeforeLocale(const ResTable_config& o, + const ResTable_config* requested) const { + if (requested) { + if (imsi || o.imsi) { + if ((mcc != o.mcc) && requested->mcc) { + return (mcc); + } + + if ((mnc != o.mnc) && requested->mnc) { + return (mnc); + } + } + } + return false; +} + bool ResTable_config::isBetterThan(const ResTable_config& o, const ResTable_config* requested) const { if (requested) { diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h index f611d0d8566a..d9ff35b49e0a 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -100,7 +100,7 @@ class AssetManager2 { using ApkAssetsWPtr = wp<const ApkAssets>; using ApkAssetsList = std::span<const ApkAssetsPtr>; - AssetManager2() = default; + AssetManager2(); explicit AssetManager2(AssetManager2&& other) = default; AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration); @@ -156,10 +156,14 @@ class AssetManager2 { // Sets/resets the configuration for this AssetManager. This will cause all // caches that are related to the configuration change to be invalidated. - void SetConfiguration(const ResTable_config& configuration); + void SetConfigurations(std::vector<ResTable_config> configurations); - inline const ResTable_config& GetConfiguration() const { - return configuration_; + inline const std::vector<ResTable_config>& GetConfigurations() const { + return configurations_; + } + + inline void SetDefaultLocale(uint32_t default_locale) { + default_locale_ = default_locale; } // Returns all configurations for which there are resources defined, or an I/O error if reading @@ -465,9 +469,11 @@ class AssetManager2 { // without taking too much memory. std::array<uint8_t, std::numeric_limits<uint8_t>::max() + 1> package_ids_; - // The current configuration set for this AssetManager. When this changes, cached resources + uint32_t default_locale_; + + // The current configurations set for this AssetManager. When this changes, cached resources // may need to be purged. - ResTable_config configuration_ = {}; + std::vector<ResTable_config> configurations_; // Cached set of bags. These are cached because they can inherit keys from parent bags, // which involves some calculation. diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index 6de1d1e927dc..fdb355192676 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -1375,6 +1375,8 @@ struct ResTable_config // match the requested configuration at all. bool isLocaleBetterThan(const ResTable_config& o, const ResTable_config* requested) const; + bool isBetterThanBeforeLocale(const ResTable_config& o, const ResTable_config* requested) const; + String8 toString() const; }; diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp index 6fae72a6d10e..2caa98c35971 100644 --- a/libs/androidfw/tests/AssetManager2_bench.cpp +++ b/libs/androidfw/tests/AssetManager2_bench.cpp @@ -228,10 +228,12 @@ static void BM_AssetManagerSetConfigurationFramework(benchmark::State& state) { ResTable_config config; memset(&config, 0, sizeof(config)); + std::vector<ResTable_config> configs; + configs.push_back(config); while (state.KeepRunning()) { - config.sdkVersion = ~config.sdkVersion; - assets.SetConfiguration(config); + configs[0].sdkVersion = ~configs[0].sdkVersion; + assets.SetConfigurations(configs); } } BENCHMARK(BM_AssetManagerSetConfigurationFramework); diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp index df3fa02ce44c..c62f095e9dac 100644 --- a/libs/androidfw/tests/AssetManager2_test.cpp +++ b/libs/androidfw/tests/AssetManager2_test.cpp @@ -113,7 +113,7 @@ TEST_F(AssetManager2Test, FindsResourceFromSingleApkAssets) { desired_config.language[1] = 'e'; AssetManager2 assetmanager; - assetmanager.SetConfiguration(desired_config); + assetmanager.SetConfigurations({desired_config}); assetmanager.SetApkAssets({basic_assets_}); auto value = assetmanager.GetResource(basic::R::string::test1); @@ -137,7 +137,7 @@ TEST_F(AssetManager2Test, FindsResourceFromMultipleApkAssets) { desired_config.language[1] = 'e'; AssetManager2 assetmanager; - assetmanager.SetConfiguration(desired_config); + assetmanager.SetConfigurations({desired_config}); assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_}); auto value = assetmanager.GetResource(basic::R::string::test1); @@ -466,10 +466,10 @@ TEST_F(AssetManager2Test, ResolveDeepIdReference) { TEST_F(AssetManager2Test, DensityOverride) { AssetManager2 assetmanager; assetmanager.SetApkAssets({basic_assets_, basic_xhdpi_assets_, basic_xxhdpi_assets_}); - assetmanager.SetConfiguration({ + assetmanager.SetConfigurations({{ .density = ResTable_config::DENSITY_XHIGH, .sdkVersion = 21, - }); + }}); auto value = assetmanager.GetResource(basic::R::string::density, false /*may_be_bag*/); ASSERT_TRUE(value.has_value()); @@ -721,7 +721,7 @@ TEST_F(AssetManager2Test, GetLastPathWithoutEnablingReturnsEmpty) { ResTable_config desired_config; AssetManager2 assetmanager; - assetmanager.SetConfiguration(desired_config); + assetmanager.SetConfigurations({desired_config}); assetmanager.SetApkAssets({basic_assets_}); assetmanager.SetResourceResolutionLoggingEnabled(false); @@ -736,7 +736,7 @@ TEST_F(AssetManager2Test, GetLastPathWithoutResolutionReturnsEmpty) { ResTable_config desired_config; AssetManager2 assetmanager; - assetmanager.SetConfiguration(desired_config); + assetmanager.SetConfigurations({desired_config}); assetmanager.SetApkAssets({basic_assets_}); auto result = assetmanager.GetLastResourceResolution(); @@ -751,7 +751,7 @@ TEST_F(AssetManager2Test, GetLastPathWithSingleApkAssets) { AssetManager2 assetmanager; assetmanager.SetResourceResolutionLoggingEnabled(true); - assetmanager.SetConfiguration(desired_config); + assetmanager.SetConfigurations({desired_config}); assetmanager.SetApkAssets({basic_assets_}); auto value = assetmanager.GetResource(basic::R::string::test1); @@ -774,7 +774,7 @@ TEST_F(AssetManager2Test, GetLastPathWithMultipleApkAssets) { AssetManager2 assetmanager; assetmanager.SetResourceResolutionLoggingEnabled(true); - assetmanager.SetConfiguration(desired_config); + assetmanager.SetConfigurations({desired_config}); assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_}); auto value = assetmanager.GetResource(basic::R::string::test1); @@ -796,7 +796,7 @@ TEST_F(AssetManager2Test, GetLastPathAfterDisablingReturnsEmpty) { AssetManager2 assetmanager; assetmanager.SetResourceResolutionLoggingEnabled(true); - assetmanager.SetConfiguration(desired_config); + assetmanager.SetConfigurations({desired_config}); assetmanager.SetApkAssets({basic_assets_}); auto value = assetmanager.GetResource(basic::R::string::test1); @@ -817,7 +817,7 @@ TEST_F(AssetManager2Test, GetOverlayablesToString) { AssetManager2 assetmanager; assetmanager.SetResourceResolutionLoggingEnabled(true); - assetmanager.SetConfiguration(desired_config); + assetmanager.SetConfigurations({desired_config}); assetmanager.SetApkAssets({overlayable_assets_}); const auto map = assetmanager.GetOverlayableMapForPackage(0x7f); diff --git a/libs/androidfw/tests/BenchmarkHelpers.cpp b/libs/androidfw/tests/BenchmarkHelpers.cpp index b97dd96f8934..8b883f4ed1df 100644 --- a/libs/androidfw/tests/BenchmarkHelpers.cpp +++ b/libs/androidfw/tests/BenchmarkHelpers.cpp @@ -66,7 +66,7 @@ void GetResourceBenchmark(const std::vector<std::string>& paths, const ResTable_ AssetManager2 assetmanager; assetmanager.SetApkAssets(apk_assets); if (config != nullptr) { - assetmanager.SetConfiguration(*config); + assetmanager.SetConfigurations({*config}); } while (state.KeepRunning()) { diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp index e08a6a7f277d..181d1411fb91 100644 --- a/libs/androidfw/tests/Theme_test.cpp +++ b/libs/androidfw/tests/Theme_test.cpp @@ -260,7 +260,7 @@ TEST_F(ThemeTest, ThemeRebase) { ResTable_config night{}; night.uiMode = ResTable_config::UI_MODE_NIGHT_YES; night.version = 8u; - am_night.SetConfiguration(night); + am_night.SetConfigurations({night}); auto theme = am.NewTheme(); { diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp index a17f2f776739..7aef7a51b90c 100644 --- a/libs/hwui/jni/Paint.cpp +++ b/libs/hwui/jni/Paint.cpp @@ -203,10 +203,9 @@ namespace PaintGlue { if (advances) { advancesArray.reset(new jfloat[count]); } - minikin::MinikinRect bounds; const float advance = MinikinUtils::measureText( paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, start, count, - contextCount, advancesArray.get(), &bounds); + contextCount, advancesArray.get(), nullptr); if (advances) { env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray.get()); } diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 120d812d3480..d54c5f57c95c 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -162,7 +162,6 @@ void CanvasContext::destroy() { destroyHardwareResources(); mAnimationContext->destroy(); mRenderThread.cacheManager().onContextStopped(this); - mHintSessionWrapper.destroy(); } static void setBufferCount(ANativeWindow* window) { diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index 9695e6f0d733..c41cd0467ed3 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -63,10 +63,10 @@ void PointerController::DisplayInfoListener::onPointerControllerDestroyed() { std::shared_ptr<PointerController> PointerController::create( const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, - SpriteController& spriteController) { + SpriteController& spriteController, bool enabled) { // using 'new' to access non-public constructor std::shared_ptr<PointerController> controller = std::shared_ptr<PointerController>( - new PointerController(policy, looper, spriteController)); + new PointerController(policy, looper, spriteController, enabled)); /* * Now we need to hook up the constructed PointerController object to its callbacks. @@ -85,9 +85,10 @@ std::shared_ptr<PointerController> PointerController::create( } PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy, - const sp<Looper>& looper, SpriteController& spriteController) + const sp<Looper>& looper, SpriteController& spriteController, + bool enabled) : PointerController( - policy, looper, spriteController, + policy, looper, spriteController, enabled, [](const sp<android::gui::WindowInfosListener>& listener) { SurfaceComposerClient::getDefault()->addWindowInfosListener(listener); }, @@ -97,9 +98,10 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>& PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, SpriteController& spriteController, - WindowListenerConsumer registerListener, + bool enabled, WindowListenerConsumer registerListener, WindowListenerConsumer unregisterListener) - : mContext(policy, looper, spriteController, *this), + : mEnabled(enabled), + mContext(policy, looper, spriteController, *this), mCursorController(mContext), mDisplayInfoListener(sp<DisplayInfoListener>::make(this)), mUnregisterWindowInfosListener(std::move(unregisterListener)) { @@ -119,10 +121,14 @@ std::mutex& PointerController::getLock() const { } std::optional<FloatRect> PointerController::getBounds() const { + if (!mEnabled) return {}; + return mCursorController.getBounds(); } void PointerController::move(float deltaX, float deltaY) { + if (!mEnabled) return; + const int32_t displayId = mCursorController.getDisplayId(); vec2 transformed; { @@ -134,6 +140,8 @@ void PointerController::move(float deltaX, float deltaY) { } void PointerController::setPosition(float x, float y) { + if (!mEnabled) return; + const int32_t displayId = mCursorController.getDisplayId(); vec2 transformed; { @@ -145,6 +153,11 @@ void PointerController::setPosition(float x, float y) { } FloatPoint PointerController::getPosition() const { + if (!mEnabled) { + return FloatPoint{AMOTION_EVENT_INVALID_CURSOR_POSITION, + AMOTION_EVENT_INVALID_CURSOR_POSITION}; + } + const int32_t displayId = mCursorController.getDisplayId(); const auto p = mCursorController.getPosition(); { @@ -155,20 +168,28 @@ FloatPoint PointerController::getPosition() const { } int32_t PointerController::getDisplayId() const { + if (!mEnabled) return ADISPLAY_ID_NONE; + return mCursorController.getDisplayId(); } void PointerController::fade(Transition transition) { + if (!mEnabled) return; + std::scoped_lock lock(getLock()); mCursorController.fade(transition); } void PointerController::unfade(Transition transition) { + if (!mEnabled) return; + std::scoped_lock lock(getLock()); mCursorController.unfade(transition); } void PointerController::setPresentation(Presentation presentation) { + if (!mEnabled) return; + std::scoped_lock lock(getLock()); if (mLocked.presentation == presentation) { @@ -193,6 +214,8 @@ void PointerController::setPresentation(Presentation presentation) { void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits, int32_t displayId) { + if (!mEnabled) return; + std::scoped_lock lock(getLock()); std::array<PointerCoords, MAX_POINTERS> outSpotCoords{}; const ui::Transform& transform = getTransformForDisplayLocked(displayId); @@ -216,6 +239,8 @@ void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t } void PointerController::clearSpots() { + if (!mEnabled) return; + std::scoped_lock lock(getLock()); clearSpotsLocked(); } @@ -277,11 +302,15 @@ void PointerController::setDisplayViewport(const DisplayViewport& viewport) { } void PointerController::updatePointerIcon(PointerIconStyle iconId) { + if (!mEnabled) return; + std::scoped_lock lock(getLock()); mCursorController.updatePointerIcon(iconId); } void PointerController::setCustomPointerIcon(const SpriteIcon& icon) { + if (!mEnabled) return; + std::scoped_lock lock(getLock()); mCursorController.setCustomPointerIcon(icon); } @@ -326,6 +355,11 @@ const ui::Transform& PointerController::getTransformForDisplayLocked(int display } void PointerController::dump(std::string& dump) { + if (!mEnabled) { + dump += INDENT "PointerController: DISABLED due to ongoing PointerChoreographer refactor\n"; + return; + } + dump += INDENT "PointerController:\n"; std::scoped_lock lock(getLock()); dump += StringPrintf(INDENT2 "Presentation: %s\n", diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index 01748a8bb709..de39eda75210 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -47,7 +47,7 @@ class PointerController : public PointerControllerInterface { public: static std::shared_ptr<PointerController> create( const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, - SpriteController& spriteController); + SpriteController& spriteController, bool enabled); ~PointerController() override; @@ -83,12 +83,13 @@ protected: // Constructor used to test WindowInfosListener registration. PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, - SpriteController& spriteController, WindowListenerConsumer registerListener, + SpriteController& spriteController, bool enabled, + WindowListenerConsumer registerListener, WindowListenerConsumer unregisterListener); private: PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, - SpriteController& spriteController); + SpriteController& spriteController, bool enabled); friend PointerControllerContext::LooperCallback; friend PointerControllerContext::MessageHandler; @@ -99,6 +100,8 @@ private: // we use the DisplayInfoListener's lock in PointerController. std::mutex& getLock() const; + const bool mEnabled; + PointerControllerContext mContext; MouseCursorController mCursorController; diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp index 3e2e43fdfdca..94faf4a65a1c 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -181,7 +181,8 @@ PointerControllerTest::PointerControllerTest() : mPointerSprite(new NiceMock<Moc EXPECT_CALL(*mSpriteController, createSprite()) .WillOnce(Return(mPointerSprite)); - mPointerController = PointerController::create(mPolicy, mLooper, *mSpriteController); + mPointerController = + PointerController::create(mPolicy, mLooper, *mSpriteController, /*enabled=*/true); } PointerControllerTest::~PointerControllerTest() { @@ -321,6 +322,7 @@ public: const sp<Looper>& looper, SpriteController& spriteController) : PointerController( new MockPointerControllerPolicyInterface(), looper, spriteController, + /*enabled=*/true, [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) { // Register listener registeredListener = listener; diff --git a/native/android/configuration.cpp b/native/android/configuration.cpp index b50514d27bac..283445fc8a9a 100644 --- a/native/android/configuration.cpp +++ b/native/android/configuration.cpp @@ -36,7 +36,7 @@ void AConfiguration_delete(AConfiguration* config) { void AConfiguration_fromAssetManager(AConfiguration* out, AAssetManager* am) { ScopedLock<AssetManager2> locked_mgr(*AssetManagerForNdkAssetManager(am)); - ResTable_config config = locked_mgr->GetConfiguration(); + ResTable_config config = locked_mgr->GetConfigurations()[0]; // AConfiguration is not a virtual subclass, so we can memcpy. memcpy(out, &config, sizeof(config)); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS index 5e6697289d5a..7669e79b42be 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS @@ -3,7 +3,10 @@ siyuanh@google.com hughchen@google.com timhypeng@google.com robertluo@google.com -changbetty@google.com songferngwang@google.com +yqian@google.com +chelseahao@google.com +yiyishen@google.com +hahong@google.com # Emergency approvers in case the above are not available diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.java index 5c48c54bab07..6b855c00ce6e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.java +++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.java @@ -31,6 +31,7 @@ import java.util.HashMap; import java.util.Map; public final class QrCodeGenerator { + private static final int DEFAULT_MARGIN = -1; /** * Generates a barcode image with {@code contents}. * @@ -40,7 +41,20 @@ public final class QrCodeGenerator { */ public static Bitmap encodeQrCode(String contents, int size) throws WriterException, IllegalArgumentException { - return encodeQrCode(contents, size, /*invert=*/false); + return encodeQrCode(contents, size, DEFAULT_MARGIN, /*invert=*/false); + } + + /** + * Generates a barcode image with {@code contents}. + * + * @param contents The contents to encode in the barcode + * @param size The preferred image size in pixels + * @param margin The margin around the actual barcode + * @return Barcode bitmap + */ + public static Bitmap encodeQrCode(String contents, int size, int margin) + throws WriterException, IllegalArgumentException { + return encodeQrCode(contents, size, margin, /*invert=*/false); } /** @@ -53,10 +67,27 @@ public final class QrCodeGenerator { */ public static Bitmap encodeQrCode(String contents, int size, boolean invert) throws WriterException, IllegalArgumentException { + return encodeQrCode(contents, size, DEFAULT_MARGIN, /*invert=*/invert); + } + + /** + * Generates a barcode image with {@code contents}. + * + * @param contents The contents to encode in the barcode + * @param size The preferred image size in pixels + * @param margin The margin around the actual barcode + * @param invert Whether to invert the black/white pixels (e.g. for dark mode) + * @return Barcode bitmap + */ + public static Bitmap encodeQrCode(String contents, int size, int margin, boolean invert) + throws WriterException, IllegalArgumentException { final Map<EncodeHintType, Object> hints = new HashMap<>(); if (!isIso88591(contents)) { hints.put(EncodeHintType.CHARACTER_SET, StandardCharsets.UTF_8.name()); } + if (margin != DEFAULT_MARGIN) { + hints.put(EncodeHintType.MARGIN, margin); + } final BitMatrix qrBits = new MultiFormatWriter().encode(contents, BarcodeFormat.QR_CODE, size, size, hints); diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java index fa2d677e85c2..1d25ac78e7f9 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java @@ -106,5 +106,10 @@ public class GlobalSettings { Settings.Global.Wearable.RTL_SWIPE_TO_DISMISS_ENABLED_DEV, Settings.Global.Wearable.REDUCE_MOTION, Settings.Global.Wearable.WEAR_LAUNCHER_UI_MODE, + Settings.Global.Wearable.USER_HFP_CLIENT_SETTING, + Settings.Global.Wearable.RSB_WAKE_ENABLED, + Settings.Global.Wearable.SCREENSHOT_ENABLED, + Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED, + Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED, }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 2d62e2a95da1..8787c2516f64 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -217,6 +217,7 @@ public class SecureSettings { Settings.Secure.ACCESSIBILITY_FLOATING_MENU_ICON_TYPE, Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY, Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, + Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED, Settings.Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 4494765fd8e6..dfc3cef48426 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -312,6 +312,7 @@ public class SecureSettingsValidators { VALIDATORS.put( Secure.ACCESSIBILITY_BUTTON_TARGETS, ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR); + VALIDATORS.put(Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ONE_HANDED_MODE_ACTIVATED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ONE_HANDED_MODE_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ONE_HANDED_MODE_TIMEOUT, ANY_INTEGER_VALIDATOR); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index d2b444b3c40e..7186abab13fb 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1842,6 +1842,10 @@ class SettingsProtoDumpUtil { SecureSettingsProto.Accessibility .ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED); dumpSetting(s, p, + Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED, + SecureSettingsProto.Accessibility + .ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED); + dumpSetting(s, p, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED, SecureSettingsProto.Accessibility .ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED); diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 203efbf76658..92f65d6cd21a 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -658,7 +658,6 @@ public class SettingsBackupTest { Settings.Global.Wearable.COMPANION_BLE_ROLE, Settings.Global.Wearable.COMPANION_NAME, Settings.Global.Wearable.COMPANION_APP_NAME, - Settings.Global.Wearable.USER_HFP_CLIENT_SETTING, Settings.Global.Wearable.COMPANION_OS_VERSION, Settings.Global.Wearable.ENABLE_ALL_LANGUAGES, Settings.Global.Wearable.SETUP_LOCALE, @@ -673,16 +672,12 @@ public class SettingsBackupTest { Settings.Global.Wearable.CLOCKWORK_LONG_PRESS_TO_ASSISTANT_ENABLED, Settings.Global.Wearable.WET_MODE_ON, Settings.Global.Wearable.COOLDOWN_MODE_ON, - Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED, - Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED, Settings.Global.Wearable.BEDTIME_MODE, Settings.Global.Wearable.BEDTIME_HARD_MODE, - Settings.Global.Wearable.RSB_WAKE_ENABLED, Settings.Global.Wearable.LOCK_SCREEN_STATE, Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_ENABLED, Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_TYPE, Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_SPEED, - Settings.Global.Wearable.SCREENSHOT_ENABLED, Settings.Global.Wearable.DISABLE_AOD_WHILE_PLUGGED, Settings.Global.Wearable.NETWORK_LOCATION_OPT_IN, Settings.Global.Wearable.CUSTOM_COLOR_FOREGROUND, diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 2077af86a653..c92fe2251f68 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -326,6 +326,17 @@ filegroup { "tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt", "tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt", "tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt", + + /* Bouncer UI tests */ + "tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt", + "tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt", + "tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java", + "tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt", + "tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java", + "tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt", + "tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java", + "tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt", + "tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt", ], path: "tests/src", } diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 6778d5a08506..b5b873c8231f 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -349,6 +349,9 @@ <uses-permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT" /> + <!-- Listen to (dis-)connection of external displays and enable / disable them. --> + <uses-permission android:name="android.permission.MANAGE_DISPLAYS" /> + <protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" /> <protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" /> <protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" /> diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt index 7545ff464bab..8a8557aa1f43 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt @@ -21,7 +21,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.TextField @@ -45,7 +44,6 @@ import androidx.compose.ui.unit.dp import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel /** UI for the input part of a password-requiring version of the bouncer. */ -@OptIn(ExperimentalMaterial3Api::class) @Composable internal fun PasswordBouncer( viewModel: PasswordBouncerViewModel, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt index 64227b8c5f2a..03efbe0fe1ff 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt @@ -21,6 +21,8 @@ import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @@ -199,33 +201,39 @@ internal fun PatternBouncer( .onSizeChanged { containerSize = it } .thenIf(isInputEnabled) { Modifier.pointerInput(Unit) { - detectDragGestures( - onDragStart = { start -> - inputPosition = start - viewModel.onDragStart() - }, - onDragEnd = { - inputPosition = null - if (isAnimationEnabled) { - lineFadeOutAnimatables.values.forEach { animatable -> - // Launch using the longer-lived scope because we want these - // animations to proceed to completion even if the surrounding - // scope is canceled. - scope.launch { animatable.animateTo(1f) } + awaitEachGesture { + awaitFirstDown() + viewModel.onDown() + } + } + .pointerInput(Unit) { + detectDragGestures( + onDragStart = { start -> + inputPosition = start + viewModel.onDragStart() + }, + onDragEnd = { + inputPosition = null + if (isAnimationEnabled) { + lineFadeOutAnimatables.values.forEach { animatable -> + // Launch using the longer-lived scope because we want these + // animations to proceed to completion even if the + // surrounding scope is canceled. + scope.launch { animatable.animateTo(1f) } + } } - } - viewModel.onDragEnd() - }, - ) { change, _ -> - inputPosition = change.position - viewModel.onDrag( - xPx = change.position.x, - yPx = change.position.y, - containerSizePx = containerSize.width, - verticalOffsetPx = verticalOffset, - ) + viewModel.onDragEnd() + }, + ) { change, _ -> + inputPosition = change.position + viewModel.onDrag( + xPx = change.position.x, + yPx = change.position.y, + containerSizePx = containerSize.width, + verticalOffsetPx = verticalOffset, + ) + } } - } } ) { if (isAnimationEnabled) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt index ec6e5eda264e..e5c69777503c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt @@ -24,6 +24,8 @@ import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -76,7 +78,13 @@ internal fun PinBouncer( Column( horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier, + modifier = + modifier.pointerInput(Unit) { + awaitEachGesture { + awaitFirstDown() + viewModel.onDown() + } + } ) { PinInputDisplay(viewModel) Spacer(Modifier.height(100.dp)) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt index 0a100babde75..b3b44cba832b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt @@ -14,20 +14,33 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) +@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class) package com.android.systemui.keyguard.ui.composable import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.graphics.toComposeRect +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.view.isVisible import com.android.compose.animation.scene.SceneScope import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.qualifiers.KeyguardRootView +import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey @@ -67,8 +80,8 @@ constructor( modifier: Modifier, ) { LockscreenScene( - viewModel = viewModel, viewProvider = viewProvider, + longPressViewModel = viewModel.longPress, modifier = modifier, ) } @@ -85,23 +98,70 @@ constructor( @Composable private fun LockscreenScene( - viewModel: LockscreenSceneViewModel, viewProvider: () -> View, + longPressViewModel: KeyguardLongPressViewModel, modifier: Modifier = Modifier, ) { - AndroidView( - factory = { _ -> - val keyguardRootView = viewProvider() - // Remove the KeyguardRootView from any parent it might already have in legacy code just - // in case (a view can't have two parents). - (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView) - keyguardRootView - }, - update = { keyguardRootView -> - keyguardRootView.requireViewById<View>(R.id.lock_icon_view).setOnClickListener { - viewModel.onLockButtonClicked() - } - }, + var settingsMenu: View? = null + + Box( modifier = modifier, + ) { + LongPressSurface( + viewModel = longPressViewModel, + isSettingsMenuVisible = { settingsMenu?.isVisible == true }, + settingsMenuBounds = { + val bounds = android.graphics.Rect() + settingsMenu?.getHitRect(bounds) + bounds.toComposeRect() + }, + modifier = Modifier.fillMaxSize(), + ) + + AndroidView( + factory = { _ -> + val keyguardRootView = viewProvider() + // Remove the KeyguardRootView from any parent it might already have in legacy code + // just in case (a view can't have two parents). + (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView) + settingsMenu = keyguardRootView.requireViewById(R.id.keyguard_settings_button) + keyguardRootView + }, + update = { keyguardRootView -> + keyguardRootView.requireViewById<View>(R.id.lock_icon_view) + }, + modifier = Modifier.fillMaxSize(), + ) + } +} + +@Composable +private fun LongPressSurface( + viewModel: KeyguardLongPressViewModel, + isSettingsMenuVisible: () -> Boolean, + settingsMenuBounds: () -> Rect, + modifier: Modifier = Modifier, +) { + val isEnabled: Boolean by viewModel.isLongPressHandlingEnabled.collectAsState(initial = false) + + Box( + modifier = + modifier + .combinedClickable( + enabled = isEnabled, + onLongClick = viewModel::onLongPress, + onClick = {}, + ) + .pointerInput(Unit) { + awaitEachGesture { + val pointerInputChange = awaitFirstDown() + if ( + isSettingsMenuVisible() && + !settingsMenuBounds().contains(pointerInputChange.position) + ) { + viewModel.onTouchedOutside() + } + } + }, ) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt index 774c409f34df..40b0b4a3eaa3 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt @@ -17,11 +17,7 @@ package com.android.systemui.scene.ui.composable import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import com.android.compose.animation.scene.SceneScope import com.android.systemui.dagger.SysUISingleton @@ -54,17 +50,6 @@ class GoneScene @Inject constructor() : ComposableScene { override fun SceneScope.Content( modifier: Modifier, ) { - /* - * TODO(b/279501596): once we start testing with the real Content Dynamics Framework, - * replace this with an error to make sure it doesn't get rendered. - */ - Box(modifier = modifier) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.align(Alignment.Center) - ) { - Text("Gone", style = MaterialTheme.typography.headlineMedium) - } - } + Box(modifier = modifier) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt index c865070b2c91..31cbcb90769a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:OptIn(ExperimentalComposeUiApi::class) + package com.android.systemui.scene.ui.composable import androidx.compose.foundation.layout.fillMaxSize @@ -22,7 +24,12 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.motionEventSpy +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.motionEventSpy +import androidx.compose.ui.input.pointer.pointerInput import com.android.compose.animation.scene.Back import com.android.compose.animation.scene.ObservableTransitionState as SceneTransitionObservableTransitionState import com.android.compose.animation.scene.SceneKey as SceneTransitionSceneKey @@ -56,6 +63,7 @@ import kotlinx.coroutines.flow.map * must have entries in this map. * @param modifier A modifier. */ +@OptIn(ExperimentalComposeUiApi::class) @Composable fun SceneContainer( viewModel: SceneContainerViewModel, @@ -79,7 +87,18 @@ fun SceneContainer( onChangeScene = viewModel::onSceneChanged, transitions = SceneContainerTransitions, state = state, - modifier = modifier.fillMaxSize(), + modifier = + modifier + .fillMaxSize() + .motionEventSpy { event -> viewModel.onMotionEvent(event) } + .pointerInput(Unit) { + awaitPointerEventScope { + while (true) { + awaitPointerEvent(PointerEventPass.Final) + viewModel.onMotionEventComplete() + } + } + } ) { sceneByKey.forEach { (sceneKey, composableScene) -> scene( diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml new file mode 100644 index 000000000000..569dd4cf9252 --- /dev/null +++ b/packages/SystemUI/res/layout/connected_display_dialog.xml @@ -0,0 +1,69 @@ +<!-- + 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. + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center_horizontal" + android:orientation="vertical" + android:paddingHorizontal="@dimen/dialog_side_padding" + android:paddingTop="@dimen/dialog_top_padding" + android:background="@*android:drawable/bottomsheet_background" + android:paddingBottom="@dimen/dialog_bottom_padding"> + + <ImageView + android:id="@+id/connected_display_dialog_icon" + android:layout_width="@dimen/screenrecord_logo_size" + android:layout_height="@dimen/screenrecord_logo_size" + android:importantForAccessibility="no" + android:src="@drawable/stat_sys_connected_display" + android:tint="?androidprv:attr/materialColorPrimary" /> + + <TextView + android:id="@+id/connected_display_dialog_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/screenrecord_title_margin_top" + android:gravity="center" + android:text="@string/connected_display_dialog_start_mirroring" + android:textAppearance="@style/TextAppearance.Dialog.Title" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/screenrecord_buttons_margin_top" + android:orientation="horizontal"> + + <Button + android:id="@+id/cancel" + style="@style/Widget.Dialog.Button.BorderButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/cancel" /> + + <Space + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" /> + + <Button + android:id="@+id/enable_display" + style="@style/Widget.Dialog.Button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/enable_display" /> + </LinearLayout> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer.xml b/packages/SystemUI/res/layout/status_bar_notification_footer.xml index 909048e17915..b00908fd2bfa 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_footer.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_footer.xml @@ -47,7 +47,7 @@ android:layout_height="48dp" android:layout_marginTop="12dp" android:layout_marginStart="16dp" - app:layout_constraintVertical_bias="0.0" + app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_chainStyle="spread_inside" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" @@ -65,7 +65,6 @@ android:layout_height="48dp" android:layout_marginTop="12dp" android:layout_marginEnd="16dp" - app:layout_constraintVertical_bias="1.0" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toEndOf="@id/manage_text" diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index b37aeeecea61..6840108e2149 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3184,6 +3184,12 @@ <!-- Label for a button that, when clicked, sends the user to the app store to install an app. [CHAR LIMIT=64]. --> <string name="install_app">Install app</string> + <!--- Title of the dialog appearing when an external display is connected, asking whether to start mirroring [CHAR LIMIT=NONE]--> + <string name="connected_display_dialog_start_mirroring">Mirror to external display?</string> + + <!--- Label of the "enable display" button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]--> + <string name="enable_display">Enable display</string> + <!-- Title of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=30] --> <string name="privacy_dialog_title">Microphone & Camera</string> <!-- Subtitle of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 06b66922466c..1703b3034775 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -48,6 +48,7 @@ public class KeyguardClockSwitch extends RelativeLayout { private static final long STATUS_AREA_MOVE_UP_MILLIS = 967; private static final long STATUS_AREA_MOVE_DOWN_MILLIS = 467; private static final float SMARTSPACE_TRANSLATION_CENTER_MULTIPLIER = 1.4f; + private static final float SMARTSPACE_TOP_PADDING_MULTIPLIER = 2.625f; @IntDef({LARGE, SMALL}) @Retention(RetentionPolicy.SOURCE) @@ -96,6 +97,14 @@ public class KeyguardClockSwitch extends RelativeLayout { private KeyguardClockFrame mLargeClockFrame; private ClockController mClock; + // It's bc_smartspace_view, assigned by KeyguardClockSwitchController + // to get the top padding for translating smartspace for weather clock + private View mSmartspace; + + // Smartspace in weather clock is translated by this value + // to compensate for the position invisible dateWeatherView + private int mSmartspaceTop = -1; + private KeyguardStatusAreaView mStatusArea; private int mSmartspaceTopOffset; private float mWeatherClockSmartspaceScaling = 1f; @@ -134,8 +143,11 @@ public class KeyguardClockSwitch extends RelativeLayout { public void onConfigChanged() { mClockSwitchYAmount = mContext.getResources().getDimensionPixelSize( R.dimen.keyguard_clock_switch_y_shift); - mSmartspaceTopOffset = mContext.getResources().getDimensionPixelSize( - R.dimen.keyguard_smartspace_top_offset); + mSmartspaceTopOffset = (int) (mContext.getResources().getDimensionPixelSize( + R.dimen.keyguard_smartspace_top_offset) + * mContext.getResources().getConfiguration().fontScale + / mContext.getResources().getDisplayMetrics().density + * SMARTSPACE_TOP_PADDING_MULTIPLIER); mWeatherClockSmartspaceScaling = ResourcesCompat.getFloat( mContext.getResources(), R.dimen.weather_clock_smartspace_scale); mWeatherClockSmartspaceTranslateX = mContext.getResources().getDimensionPixelSize( @@ -145,6 +157,12 @@ public class KeyguardClockSwitch extends RelativeLayout { updateStatusArea(/* animate= */false); } + /** Get bc_smartspace_view from KeyguardClockSwitchController + * Use its top to decide the translation value */ + public void setSmartspace(View smartspace) { + mSmartspace = smartspace; + } + /** Sets whether the large clock is being shown on a connected display. */ public void setLargeClockOnSecondaryDisplay(boolean onSecondaryDisplay) { if (mClock != null) { @@ -295,7 +313,7 @@ public class KeyguardClockSwitch extends RelativeLayout { && mClock.getLargeClock().getConfig().getHasCustomWeatherDataDisplay()) { statusAreaClockScale = mWeatherClockSmartspaceScaling; statusAreaClockTranslateX = mWeatherClockSmartspaceTranslateX; - statusAreaClockTranslateY = mWeatherClockSmartspaceTranslateY; + statusAreaClockTranslateY = mWeatherClockSmartspaceTranslateY - mSmartspaceTop; if (mSplitShadeCentered) { statusAreaClockTranslateX *= SMARTSPACE_TRANSLATION_CENTER_MULTIPLIER; } @@ -418,10 +436,14 @@ public class KeyguardClockSwitch extends RelativeLayout { post(() -> updateClockTargetRegions()); } - if (mDisplayedClockSize != null && !mChildrenAreLaidOut) { + if (mSmartspace != null && mSmartspaceTop != mSmartspace.getTop()) { + mSmartspaceTop = mSmartspace.getTop(); post(() -> updateClockViews(mDisplayedClockSize == LARGE, mAnimateOnLayout)); } + if (mDisplayedClockSize != null && !mChildrenAreLaidOut) { + post(() -> updateClockViews(mDisplayedClockSize == LARGE, mAnimateOnLayout)); + } mChildrenAreLaidOut = true; } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 6d2880e00203..d89796005e25 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -372,6 +372,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mSmartspaceView.setPaddingRelative(startPadding, 0, endPadding, 0); mKeyguardUnlockAnimationController.setLockscreenSmartspace(mSmartspaceView); + mView.setSmartspace(mSmartspaceView); } /** diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java index 8611dbbbcb70..1d37809a382e 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java @@ -142,11 +142,13 @@ public class LockIconView extends FrameLayout implements Dumpable { mLockIconCenter.y + mRadius); final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams(); - lp.width = (int) (mSensorRect.right - mSensorRect.left); - lp.height = (int) (mSensorRect.bottom - mSensorRect.top); - lp.topMargin = (int) mSensorRect.top; - lp.setMarginStart((int) mSensorRect.left); - setLayoutParams(lp); + if (lp != null) { + lp.width = (int) (mSensorRect.right - mSensorRect.left); + lp.height = (int) (mSensorRect.bottom - mSensorRect.top); + lp.topMargin = (int) mSensorRect.top; + lp.setMarginStart((int) mSensorRect.left); + setLayoutParams(lp); + } } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 951a6aeef11b..ab9b647f5c2c 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -28,6 +28,7 @@ import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLE import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; +import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Point; @@ -74,7 +75,6 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.util.ViewController; import com.android.systemui.util.concurrency.DelayableExecutor; import java.io.PrintWriter; @@ -90,7 +90,7 @@ import javax.inject.Inject; * icon will show a set distance from the bottom of the device. */ @SysUISingleton -public class LockIconViewController extends ViewController<LockIconView> implements Dumpable { +public class LockIconViewController implements Dumpable { private static final String TAG = "LockIconViewController"; private static final float sDefaultDensity = (float) DisplayMetrics.DENSITY_DEVICE_STABLE / (float) DisplayMetrics.DENSITY_DEFAULT; @@ -109,6 +109,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @NonNull private final ConfigurationController mConfigurationController; @NonNull private final DelayableExecutor mExecutor; private boolean mUdfpsEnrolled; + private Resources mResources; + private Context mContext; @NonNull private final AnimatedStateListDrawable mIcon; @@ -120,6 +122,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor; @NonNull private final KeyguardTransitionInteractor mTransitionInteractor; @NonNull private final KeyguardInteractor mKeyguardInteractor; + @NonNull private final View.AccessibilityDelegate mAccessibilityDelegate; // Tracks the velocity of a touch to help filter out the touches that move too fast. private VelocityTracker mVelocityTracker; @@ -154,6 +157,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private boolean mDownDetected; private final Rect mSensorTouchLocation = new Rect(); + private LockIconView mView; @VisibleForTesting final Consumer<TransitionStep> mDozeTransitionCallback = (TransitionStep step) -> { @@ -178,7 +182,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @Inject public LockIconViewController( - @Nullable LockIconView view, @NonNull StatusBarStateController statusBarStateController, @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor, @NonNull KeyguardViewController keyguardViewController, @@ -195,9 +198,9 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @NonNull KeyguardTransitionInteractor transitionInteractor, @NonNull KeyguardInteractor keyguardInteractor, @NonNull FeatureFlags featureFlags, - PrimaryBouncerInteractor primaryBouncerInteractor + PrimaryBouncerInteractor primaryBouncerInteractor, + Context context ) { - super(view); mStatusBarStateController = statusBarStateController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mAuthController = authController; @@ -218,16 +221,40 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y); mIcon = (AnimatedStateListDrawable) - resources.getDrawable(R.drawable.super_lock_icon, mView.getContext().getTheme()); - mView.setImageDrawable(mIcon); + resources.getDrawable(R.drawable.super_lock_icon, context.getTheme()); mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button); mLockedLabel = resources.getString(R.string.accessibility_lock_icon); mLongPressTimeout = resources.getInteger(R.integer.config_lockIconLongPress); dumpManager.registerDumpable(TAG, this); + mResources = resources; + mContext = context; + + mAccessibilityDelegate = new View.AccessibilityDelegate() { + private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityAuthenticateHint = + new AccessibilityNodeInfo.AccessibilityAction( + AccessibilityNodeInfoCompat.ACTION_CLICK, + mResources.getString(R.string.accessibility_authenticate_hint)); + private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityEnterHint = + new AccessibilityNodeInfo.AccessibilityAction( + AccessibilityNodeInfoCompat.ACTION_CLICK, + mResources.getString(R.string.accessibility_enter_hint)); + public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(v, info); + if (isActionable()) { + if (mShowLockIcon) { + info.addAction(mAccessibilityAuthenticateHint); + } else if (mShowUnlockIcon) { + info.addAction(mAccessibilityEnterHint); + } + } + } + }; } - @Override - protected void onInit() { + /** Sets the LockIconView to the controller and rebinds any that depend on it. */ + public void setLockIconView(LockIconView lockIconView) { + mView = lockIconView; + mView.setImageDrawable(mIcon); mView.setAccessibilityDelegate(mAccessibilityDelegate); if (mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) { @@ -240,10 +267,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme collectFlow(mView, mKeyguardInteractor.isActiveDreamLockscreenHosted(), mIsActiveDreamLockscreenHostedCallback); } - } - @Override - protected void onViewAttached() { updateIsUdfpsEnrolled(); updateConfiguration(); updateKeyguardShowing(); @@ -256,39 +280,57 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mStatusBarState = mStatusBarStateController.getState(); updateColors(); - mConfigurationController.addCallback(mConfigurationListener); + mDownDetected = false; + updateBurnInOffsets(); + updateVisibility(); + + updateAccessibility(); + + lockIconView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View view) { + registerCallbacks(); + } + @Override + public void onViewDetachedFromWindow(View view) { + unregisterCallbacks(); + } + }); + + if (lockIconView.isAttachedToWindow()) { + registerCallbacks(); + } + } + + private void registerCallbacks() { + mConfigurationController.addCallback(mConfigurationListener); mAuthController.addCallback(mAuthControllerCallback); mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); mStatusBarStateController.addCallback(mStatusBarStateListener); mKeyguardStateController.addCallback(mKeyguardStateCallback); - mDownDetected = false; - updateBurnInOffsets(); - updateVisibility(); - mAccessibilityManager.addAccessibilityStateChangeListener( mAccessibilityStateChangeListener); - updateAccessibility(); - } - private void updateAccessibility() { - if (mAccessibilityManager.isEnabled()) { - mView.setOnClickListener(mA11yClickListener); - } else { - mView.setOnClickListener(null); - } } - @Override - protected void onViewDetached() { + private void unregisterCallbacks() { mAuthController.removeCallback(mAuthControllerCallback); mConfigurationController.removeCallback(mConfigurationListener); mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback); mStatusBarStateController.removeCallback(mStatusBarStateListener); mKeyguardStateController.removeCallback(mKeyguardStateCallback); - mAccessibilityManager.removeAccessibilityStateChangeListener( mAccessibilityStateChangeListener); + + } + + private void updateAccessibility() { + if (mAccessibilityManager.isEnabled()) { + mView.setOnClickListener(mA11yClickListener); + } else { + mView.setOnClickListener(null); + } } public float getTop() { @@ -363,28 +405,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } } - private final View.AccessibilityDelegate mAccessibilityDelegate = - new View.AccessibilityDelegate() { - private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityAuthenticateHint = - new AccessibilityNodeInfo.AccessibilityAction( - AccessibilityNodeInfoCompat.ACTION_CLICK, - getResources().getString(R.string.accessibility_authenticate_hint)); - private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityEnterHint = - new AccessibilityNodeInfo.AccessibilityAction( - AccessibilityNodeInfoCompat.ACTION_CLICK, - getResources().getString(R.string.accessibility_enter_hint)); - public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(v, info); - if (isActionable()) { - if (mShowLockIcon) { - info.addAction(mAccessibilityAuthenticateHint); - } else if (mShowUnlockIcon) { - info.addAction(mAccessibilityEnterHint); - } - } - } - }; - private boolean isLockScreen() { return !mIsDozing && !mIsBouncerShowing @@ -401,18 +421,15 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } private void updateConfiguration() { - WindowManager windowManager = getContext().getSystemService(WindowManager.class); + WindowManager windowManager = mContext.getSystemService(WindowManager.class); Rect bounds = windowManager.getCurrentWindowMetrics().getBounds(); mWidthPixels = bounds.right; mHeightPixels = bounds.bottom; - mBottomPaddingPx = getResources().getDimensionPixelSize(R.dimen.lock_icon_margin_bottom); - mDefaultPaddingPx = - getResources().getDimensionPixelSize(R.dimen.lock_icon_padding); - - mUnlockedLabel = mView.getContext().getResources().getString( + mBottomPaddingPx = mResources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom); + mDefaultPaddingPx = mResources.getDimensionPixelSize(R.dimen.lock_icon_padding); + mUnlockedLabel = mResources.getString( R.string.accessibility_unlock_button); - mLockedLabel = mView.getContext() - .getResources().getString(R.string.accessibility_lock_icon); + mLockedLabel = mResources.getString(R.string.accessibility_lock_icon); updateLockIconLocation(); } @@ -755,7 +772,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } else { mVibrator.vibrate( Process.myUid(), - getContext().getOpPackageName(), + mContext.getOpPackageName(), UdfpsController.EFFECT_CLICK, "lock-icon-down", TOUCH_VIBRATION_ATTRIBUTES); @@ -769,7 +786,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } else { mVibrator.vibrate( Process.myUid(), - getContext().getOpPackageName(), + mContext.getOpPackageName(), UdfpsController.EFFECT_CLICK, "lock-screen-lock-icon-longpress", TOUCH_VIBRATION_ATTRIBUTES); diff --git a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt index 3c74bf484e98..066cba230b76 100644 --- a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt @@ -16,24 +16,69 @@ package com.android.systemui.back.domain.interactor +import android.window.BackEvent +import android.window.OnBackAnimationCallback +import android.window.OnBackInvokedCallback +import android.window.OnBackInvokedDispatcher +import android.window.WindowOnBackInvokedDispatcher +import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor import com.android.systemui.shade.QuickSettingsController import com.android.systemui.shade.ShadeController import com.android.systemui.shade.ShadeViewController +import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch /** Handles requests to go back either from a button or gesture. */ @SysUISingleton class BackActionInteractor @Inject constructor( + @Application private val scope: CoroutineScope, private val statusBarStateController: StatusBarStateController, private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, - private val shadeController: ShadeController -) { + private val shadeController: ShadeController, + private val notificationShadeWindowController: NotificationShadeWindowController, + private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor, + featureFlags: FeatureFlags, +) : CoreStartable { + + private var isCallbackRegistered = false + + private val callback = + if (featureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE)) { + /** + * New callback that handles back gesture invoked, cancel, progress and provides + * feedback via Shade animation. + */ + object : OnBackAnimationCallback { + override fun onBackInvoked() { + onBackRequested() + } + + override fun onBackProgressed(backEvent: BackEvent) { + if (shouldBackBeHandled() && shadeViewController.canBeCollapsed()) { + shadeViewController.onBackProgressed(backEvent.progress) + } + } + } + } else { + OnBackInvokedCallback { onBackRequested() } + } + + private val onBackInvokedDispatcher: WindowOnBackInvokedDispatcher? + get() = + notificationShadeWindowController.windowRootView?.viewRootImpl?.onBackInvokedDispatcher + private lateinit var shadeViewController: ShadeViewController private lateinit var qsController: QuickSettingsController @@ -42,6 +87,19 @@ constructor( this.shadeViewController = svController } + override fun start() { + scope.launch { + windowRootViewVisibilityInteractor.isLockscreenOrShadeVisibleAndInteractive.collect { + visible -> + if (visible) { + registerBackCallback() + } else { + unregisterBackCallback() + } + } + } + } + fun shouldBackBeHandled(): Boolean { return statusBarStateController.state != StatusBarState.KEYGUARD && statusBarStateController.state != StatusBarState.SHADE_LOCKED && @@ -74,4 +132,24 @@ constructor( } return false } + + private fun registerBackCallback() { + if (isCallbackRegistered) { + return + } + onBackInvokedDispatcher?.let { + it.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, callback) + isCallbackRegistered = true + } + } + + private fun unregisterBackCallback() { + if (!isCallbackRegistered) { + return + } + onBackInvokedDispatcher?.let { + it.unregisterOnBackInvokedCallback(callback) + isCallbackRegistered = false + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index b23e08538b5c..a36870346b9a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -586,16 +586,18 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (shouldTryToDismissKeyguard()) { tryDismissingKeyguard(); } - onFingerDown(requestId, - data.getPointerId(), - data.getX(), - data.getY(), - data.getMinor(), - data.getMajor(), - data.getOrientation(), - data.getTime(), - data.getGestureStart(), - mStatusBarStateController.isDozing()); + if (!mOnFingerDown) { + onFingerDown(requestId, + data.getPointerId(), + data.getX(), + data.getY(), + data.getMinor(), + data.getMajor(), + data.getOrientation(), + data.getTime(), + data.getGestureStart(), + mStatusBarStateController.isDozing()); + } // Pilfer if valid overlap, don't allow following events to reach keyguard shouldPilfer = true; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index e8b8f54220aa..be08932cfa64 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -78,17 +78,6 @@ constructor( private val _isOverlayTouched: MutableStateFlow<Boolean> = MutableStateFlow(false) - /** - * If the API caller or the user's personal preferences require explicit confirmation after - * successful authentication. - */ - val isConfirmationRequired: Flow<Boolean> = - combine(_isOverlayTouched, promptSelectorInteractor.isConfirmationRequired) { - isOverlayTouched, - isConfirmationRequired -> - !isOverlayTouched && isConfirmationRequired - } - /** The kind of credential the user has. */ val credentialKind: Flow<PromptKind> = promptSelectorInteractor.credentialKind @@ -137,6 +126,15 @@ constructor( } .distinctUntilChanged() + /** + * If the API caller or the user's personal preferences require explicit confirmation after + * successful authentication. Confirmation always required when in explicit flow. + */ + val isConfirmationRequired: Flow<Boolean> = + combine(_isOverlayTouched, size) { isOverlayTouched, size -> + !isOverlayTouched && size.isNotSmall + } + /** Title for the prompt. */ val title: Flow<String> = promptSelectorInteractor.prompt.map { it?.title ?: "" }.distinctUntilChanged() @@ -170,12 +168,7 @@ constructor( .distinctUntilChanged() /** If the icon can be used as a confirmation button. */ - val isIconConfirmButton: Flow<Boolean> = - combine(size, promptSelectorInteractor.isConfirmationRequired) { - size, - isConfirmationRequired -> - size.isNotSmall && isConfirmationRequired - } + val isIconConfirmButton: Flow<Boolean> = size.map { it.isNotSmall }.distinctUntilChanged() /** If the negative button should be shown. */ val isNegativeButtonVisible: Flow<Boolean> = diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index 1bf3a9ead08e..fc32f4c138d7 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt @@ -22,6 +22,8 @@ import com.android.systemui.authentication.domain.interactor.AuthenticationInter import com.android.systemui.authentication.domain.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.bouncer.data.repository.BouncerRepository +import com.android.systemui.classifier.FalsingClassifier +import com.android.systemui.classifier.domain.interactor.FalsingInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlags @@ -50,6 +52,7 @@ constructor( private val authenticationInteractor: AuthenticationInteractor, private val sceneInteractor: SceneInteractor, featureFlags: FeatureFlags, + private val falsingInteractor: FalsingInteractor, ) { /** The user-facing message to show in the bouncer. */ @@ -103,6 +106,34 @@ constructor( } } + /** Notifies that the user has places down a pointer, not necessarily dragging just yet. */ + fun onDown() { + falsingInteractor.avoidGesture() + } + + /** + * Notifies of "intentional" (i.e. non-false) user interaction with the UI which is very likely + * to be real user interaction with the bouncer and not the result of a false touch in the + * user's pocket or by the user's face while holding their device up to their ear. + */ + fun onIntentionalUserInput() { + falsingInteractor.updateFalseConfidence(FalsingClassifier.Result.passed(0.6)) + } + + /** + * Notifies of false input which is very likely to be the result of a false touch in the user's + * pocket or by the user's face while holding their device up to their ear. + */ + fun onFalseUserInput() { + falsingInteractor.updateFalseConfidence( + FalsingClassifier.Result.falsed( + /* confidence= */ 0.7, + /* context= */ javaClass.simpleName, + /* reason= */ "empty pattern input", + ) + ) + } + /** * Either shows the bouncer or unlocks the device, if the bouncer doesn't need to be shown. * diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt index ca15f4e063a9..80a41ce672ec 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt @@ -34,6 +34,7 @@ class PasswordBouncerViewModel( ) { private val _password = MutableStateFlow("") + /** The password entered so far. */ val password: StateFlow<String> = _password.asStateFlow() @@ -48,6 +49,10 @@ class PasswordBouncerViewModel( interactor.clearMessage() } + if (password.isNotEmpty()) { + interactor.onIntentionalUserInput() + } + _password.value = password } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt index 4425f9ffcb5e..85eaf0b8db5a 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt @@ -46,10 +46,12 @@ class PatternBouncerViewModel( /** The number of columns in the dot grid. */ val columnCount = 3 + /** The number of rows in the dot grid. */ val rowCount = 3 private val _selectedDots = MutableStateFlow<LinkedHashSet<PatternDotViewModel>>(linkedSetOf()) + /** The dots that were selected by the user, in the order of selection. */ val selectedDots: StateFlow<List<PatternDotViewModel>> = _selectedDots @@ -61,10 +63,12 @@ class PatternBouncerViewModel( ) private val _currentDot = MutableStateFlow<PatternDotViewModel?>(null) + /** The most-recently selected dot that the user selected. */ val currentDot: StateFlow<PatternDotViewModel?> = _currentDot.asStateFlow() private val _dots = MutableStateFlow(defaultDots()) + /** All dots on the grid. */ val dots: StateFlow<List<PatternDotViewModel>> = _dots.asStateFlow() @@ -76,6 +80,11 @@ class PatternBouncerViewModel( interactor.resetMessage() } + /** Notifies that the user has placed down a pointer, not necessarily dragging just yet. */ + fun onDown() { + interactor.onDown() + } + /** Notifies that the user has started a drag gesture across the dot grid. */ fun onDragStart() { interactor.clearMessage() @@ -124,11 +133,13 @@ class PatternBouncerViewModel( dot = PatternDotViewModel( x = - if (hitDot.x > dot.x) dot.x + 1 - else if (hitDot.x < dot.x) dot.x - 1 else dot.x, + if (hitDot.x > dot.x) { + dot.x + 1 + } else if (hitDot.x < dot.x) dot.x - 1 else dot.x, y = - if (hitDot.y > dot.y) dot.y + 1 - else if (hitDot.y < dot.y) dot.y - 1 else dot.y, + if (hitDot.y > dot.y) { + dot.y + 1 + } else if (hitDot.y < dot.y) dot.y - 1 else dot.y, ) } } @@ -148,6 +159,12 @@ class PatternBouncerViewModel( /** Notifies that the user has ended the drag gesture across the dot grid. */ fun onDragEnd() { val pattern = _selectedDots.value.map { it.toCoordinate() } + + if (pattern.size == 1) { + // Single dot patterns are treated as erroneous/false taps: + interactor.onFalseUserInput() + } + _dots.value = defaultDots() _currentDot.value = null _selectedDots.value = linkedSetOf() diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt index 844cf024ef71..ebf939b264fa 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt @@ -88,6 +88,11 @@ class PinBouncerViewModel( interactor.resetMessage() } + /** Notifies that the user has placed down a pointer. */ + fun onDown() { + interactor.onDown() + } + /** Notifies that the user clicked on a PIN button with the given digit value. */ fun onPinButtonClicked(input: Int) { val pinInput = mutablePinInput.value @@ -95,6 +100,8 @@ class PinBouncerViewModel( interactor.clearMessage() } + interactor.onIntentionalUserInput() + mutablePinInput.value = pinInput.append(input) tryAuthenticate(useAutoConfirm = true) } @@ -148,8 +155,10 @@ class PinBouncerViewModel( enum class ActionButtonAppearance { /** Button must not be shown. */ Hidden, + /** Button is shown, but with no background to make it less prominent. */ Subtle, + /** Button is shown. */ Shown, } diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt index 4227a7a67330..b2680950d9c5 100644 --- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt @@ -29,12 +29,14 @@ import android.util.Log import android.view.WindowManager import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.ActivityIntentHelper +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserTracker import com.android.systemui.shared.system.ActivityManagerKt.isInForeground import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.phone.CentralSurfaces +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.policy.KeyguardStateController import java.util.concurrent.Executor import javax.inject.Inject @@ -43,10 +45,12 @@ import javax.inject.Inject * Helps with handling camera-related gestures (for example, double-tap the power button to launch * the camera). */ +@SysUISingleton class CameraGestureHelper @Inject constructor( private val context: Context, private val centralSurfaces: CentralSurfaces, private val keyguardStateController: KeyguardStateController, + private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, private val packageManager: PackageManager, private val activityManager: ActivityManager, private val activityStarter: ActivityStarter, @@ -133,7 +137,7 @@ class CameraGestureHelper @Inject constructor( centralSurfaces.startLaunchTransitionTimeout() // Call this to make sure the keyguard is ready to be dismissed once the next intent is // handled by the OS (in our case it is the activity we started right above) - centralSurfaces.readyForKeyguardDone() + statusBarKeyguardViewManager.readyForKeyguardDone() } /** diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java index 08e1e9a9a035..f77f98956cbf 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java @@ -29,78 +29,21 @@ public interface FalsingCollector { void setShowingAod(boolean showingAod); /** */ - void onNotificationStartDraggingDown(); - - /** */ - void onNotificationStopDraggingDown(); - - /** */ - void setNotificationExpanded(); - - /** */ - void onQsDown(); - - /** */ boolean shouldEnforceBouncer(); /** */ - void onTrackingStarted(boolean secure); - - /** */ - void onTrackingStopped(); - - /** */ - void onLeftAffordanceOn(); - - /** */ - void onCameraOn(); - - /** */ - void onAffordanceSwipingStarted(boolean rightCorner); - - /** */ - void onAffordanceSwipingAborted(); - - /** */ - void onStartExpandingFromPulse(); - - /** */ - void onExpansionFromPulseStopped(); - - /** */ void onScreenOnFromTouch(); /** */ boolean isReportingEnabled(); /** */ - void onUnlockHintStarted(); - - /** */ - void onCameraHintStarted(); - - /** */ - void onLeftAffordanceHintStarted(); - - /** */ void onScreenTurningOn(); /** */ void onScreenOff(); /** */ - void onNotificationStopDismissing(); - - /** */ - void onNotificationDismissed(); - - /** */ - void onNotificationStartDismissing(); - - /** */ - void onNotificationDoubleTap(boolean accepted, float dx, float dy); - - /** */ void onBouncerShown(); /** */ diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorActual.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorActual.kt new file mode 100644 index 000000000000..3eaad1fed7bc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorActual.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.classifier + +import javax.inject.Qualifier + +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class FalsingCollectorActual diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java index f335d1d0475f..c0ee71cf0dc8 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java @@ -29,59 +29,11 @@ public class FalsingCollectorFake implements FalsingCollector { } @Override - public void onNotificationStartDraggingDown() { - } - - @Override - public void onNotificationStopDraggingDown() { - } - - @Override - public void setNotificationExpanded() { - } - - @Override - public void onQsDown() { - } - - @Override public boolean shouldEnforceBouncer() { return false; } @Override - public void onTrackingStarted(boolean secure) { - } - - @Override - public void onTrackingStopped() { - } - - @Override - public void onLeftAffordanceOn() { - } - - @Override - public void onCameraOn() { - } - - @Override - public void onAffordanceSwipingStarted(boolean rightCorner) { - } - - @Override - public void onAffordanceSwipingAborted() { - } - - @Override - public void onStartExpandingFromPulse() { - } - - @Override - public void onExpansionFromPulseStopped() { - } - - @Override public void onScreenOnFromTouch() { } @@ -91,18 +43,6 @@ public class FalsingCollectorFake implements FalsingCollector { } @Override - public void onUnlockHintStarted() { - } - - @Override - public void onCameraHintStarted() { - } - - @Override - public void onLeftAffordanceHintStarted() { - } - - @Override public void onScreenTurningOn() { } @@ -111,22 +51,6 @@ public class FalsingCollectorFake implements FalsingCollector { } @Override - public void onNotificationStopDismissing() { - } - - @Override - public void onNotificationDismissed() { - } - - @Override - public void onNotificationStartDismissing() { - } - - @Override - public void onNotificationDoubleTap(boolean accepted, float dx, float dy) { - } - - @Override public void onBouncerShown() { } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java index 6a021f6a82b6..39c01f759654 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java @@ -103,10 +103,6 @@ class FalsingCollectorImpl implements FalsingCollector { private final BatteryStateChangeCallback mBatteryListener = new BatteryStateChangeCallback() { @Override - public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { - } - - @Override public void onWirelessChargingChanged(boolean isWirelessCharging) { if (isWirelessCharging || mDockManager.isDocked()) { mProximitySensor.pause(); @@ -169,34 +165,21 @@ class FalsingCollectorImpl implements FalsingCollector { @Override public void onSuccessfulUnlock() { + logDebug("REAL: onSuccessfulUnlock"); mFalsingManager.onSuccessfulUnlock(); sessionEnd(); } @Override public void setShowingAod(boolean showingAod) { + logDebug("REAL: setShowingAod(" + showingAod + ")"); mShowingAod = showingAod; updateSessionActive(); } - @Override - public void onNotificationStartDraggingDown() { - } - - @Override - public void onNotificationStopDraggingDown() { - } - - @Override - public void setNotificationExpanded() { - } - - @Override - public void onQsDown() { - } - @VisibleForTesting void onQsExpansionChanged(Boolean expanded) { + logDebug("REAL: onQsExpansionChanged(" + expanded + ")"); if (expanded) { unregisterSensors(); } else if (mSessionStarted) { @@ -210,39 +193,8 @@ class FalsingCollectorImpl implements FalsingCollector { } @Override - public void onTrackingStarted(boolean secure) { - } - - @Override - public void onTrackingStopped() { - } - - @Override - public void onLeftAffordanceOn() { - } - - @Override - public void onCameraOn() { - } - - @Override - public void onAffordanceSwipingStarted(boolean rightCorner) { - } - - @Override - public void onAffordanceSwipingAborted() { - } - - @Override - public void onStartExpandingFromPulse() { - } - - @Override - public void onExpansionFromPulseStopped() { - } - - @Override public void onScreenOnFromTouch() { + logDebug("REAL: onScreenOnFromTouch"); onScreenTurningOn(); } @@ -252,52 +204,28 @@ class FalsingCollectorImpl implements FalsingCollector { } @Override - public void onUnlockHintStarted() { - } - - @Override - public void onCameraHintStarted() { - } - - @Override - public void onLeftAffordanceHintStarted() { - } - - @Override public void onScreenTurningOn() { + logDebug("REAL: onScreenTurningOn"); mScreenOn = true; updateSessionActive(); } @Override public void onScreenOff() { + logDebug("REAL: onScreenOff"); mScreenOn = false; updateSessionActive(); } @Override - public void onNotificationStopDismissing() { - } - - @Override - public void onNotificationDismissed() { - } - - @Override - public void onNotificationStartDismissing() { - } - - @Override - public void onNotificationDoubleTap(boolean accepted, float dx, float dy) { - } - - @Override public void onBouncerShown() { + logDebug("REAL: onBouncerShown"); unregisterSensors(); } @Override public void onBouncerHidden() { + logDebug("REAL: onBouncerHidden"); if (mSessionStarted) { registerSensors(); } @@ -305,6 +233,7 @@ class FalsingCollectorImpl implements FalsingCollector { @Override public void onTouchEvent(MotionEvent ev) { + logDebug("REAL: onTouchEvent(" + ev.getActionMasked() + ")"); if (!mKeyguardStateController.isShowing()) { avoidGesture(); return; @@ -334,6 +263,7 @@ class FalsingCollectorImpl implements FalsingCollector { @Override public void onMotionEventComplete() { + logDebug("REAL: onMotionEventComplete"); // We must delay processing the completion because of the way Android handles click events. // It generally delays executing them immediately, instead choosing to give the UI a chance // to respond to touch events before acknowledging the click. As such, we must also delay, @@ -350,6 +280,7 @@ class FalsingCollectorImpl implements FalsingCollector { @Override public void avoidGesture() { + logDebug("REAL: avoidGesture"); mAvoidGesture = true; if (mPendingDownEvent != null) { mPendingDownEvent.recycle(); @@ -359,6 +290,7 @@ class FalsingCollectorImpl implements FalsingCollector { @Override public void cleanup() { + logDebug("REAL: cleanup"); unregisterSensors(); mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateCallback); mStatusBarStateController.removeCallback(mStatusBarStateListener); @@ -368,11 +300,13 @@ class FalsingCollectorImpl implements FalsingCollector { @Override public void updateFalseConfidence(FalsingClassifier.Result result) { + logDebug("REAL: updateFalseConfidence(" + result.isFalse() + ")"); mHistoryTracker.addResults(Collections.singleton(result), mSystemClock.uptimeMillis()); } @Override public void onA11yAction() { + logDebug("REAL: onA11yAction"); if (mPendingDownEvent != null) { mPendingDownEvent.recycle(); mPendingDownEvent = null; @@ -427,17 +361,13 @@ class FalsingCollectorImpl implements FalsingCollector { static void logDebug(String msg) { - logDebug(msg, null); - } - - static void logDebug(String msg, Throwable throwable) { if (DEBUG) { - Log.d(TAG, msg, throwable); + logDebug(msg); } } private static class ProximityEventImpl implements FalsingManager.ProximityEvent { - private ThresholdSensorEvent mThresholdSensorEvent; + private final ThresholdSensorEvent mThresholdSensorEvent; ProximityEventImpl(ThresholdSensorEvent thresholdSensorEvent) { mThresholdSensorEvent = thresholdSensorEvent; diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt new file mode 100644 index 000000000000..e5b404f30889 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.classifier + +import android.view.MotionEvent +import com.android.systemui.classifier.FalsingCollectorImpl.logDebug +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +@SysUISingleton +class FalsingCollectorNoOp @Inject constructor() : FalsingCollector { + override fun onSuccessfulUnlock() { + logDebug("NOOP: onSuccessfulUnlock") + } + + override fun setShowingAod(showingAod: Boolean) { + logDebug("NOOP: setShowingAod($showingAod)") + } + + override fun shouldEnforceBouncer(): Boolean = false + + override fun onScreenOnFromTouch() { + logDebug("NOOP: onScreenOnFromTouch") + } + + override fun isReportingEnabled(): Boolean = false + + override fun onScreenTurningOn() { + logDebug("NOOP: onScreenTurningOn") + } + + override fun onScreenOff() { + logDebug("NOOP: onScreenOff") + } + + override fun onBouncerShown() { + logDebug("NOOP: onBouncerShown") + } + + override fun onBouncerHidden() { + logDebug("NOOP: onBouncerHidden") + } + + override fun onTouchEvent(ev: MotionEvent) { + logDebug("NOOP: onTouchEvent(${ev.actionMasked})") + } + + override fun onMotionEventComplete() { + logDebug("NOOP: onMotionEventComplete") + } + + override fun avoidGesture() { + logDebug("NOOP: avoidGesture") + } + + override fun cleanup() { + logDebug("NOOP: cleanup") + } + + override fun updateFalseConfidence(result: FalsingClassifier.Result) { + logDebug("NOOP: updateFalseConfidence(${result.isFalse})") + } + + override fun onA11yAction() { + logDebug("NOOP: onA11yAction") + } +} diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java index c7f3b2d08efd..3195d093a711 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java @@ -22,19 +22,21 @@ import android.view.ViewConfiguration; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FeatureFlagsClassic; +import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.phone.NotificationTapHelper; +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import dagger.multibindings.ElementsIntoSet; + import java.util.Arrays; import java.util.HashSet; import java.util.Set; import javax.inject.Named; -import dagger.Binds; -import dagger.Module; -import dagger.Provides; -import dagger.multibindings.ElementsIntoSet; - /** Dagger Module for Falsing. */ @Module public interface FalsingModule { @@ -45,10 +47,20 @@ public interface FalsingModule { String DOUBLE_TAP_TIMEOUT_MS = "falsing_double_tap_timeout_ms"; String IS_FOLDABLE_DEVICE = "falsing_foldable_device"; - /** */ - @Binds + /** Provides the actual {@link FalsingCollector} if the scene container feature is off. */ + @Provides @SysUISingleton - FalsingCollector bindsFalsingCollector(FalsingCollectorImpl impl); + static FalsingCollector providesFalsingCollectorLegacy( + FalsingCollectorImpl impl, + FalsingCollectorNoOp noOp, + FeatureFlagsClassic featureFlags) { + return featureFlags.isEnabled(Flags.SCENE_CONTAINER) ? noOp : impl; + } + + /** Provides the actual {@link FalsingCollector}. */ + @Binds + @FalsingCollectorActual + FalsingCollector bindsFalsingCollectorActual(FalsingCollectorImpl impl); /** */ @Provides diff --git a/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt b/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt new file mode 100644 index 000000000000..2e861c399ee9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.classifier.domain.interactor + +import android.view.MotionEvent +import com.android.systemui.classifier.FalsingClassifier +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.classifier.FalsingCollectorActual +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +/** + * Exposes the subset of the [FalsingCollector] API that's required by external callers. + * + * E.g. methods of [FalsingCollector] that are not exposed by this class don't need to be invoked by + * external callers as they're already called by the scene framework. + */ +@SysUISingleton +class FalsingInteractor +@Inject +constructor( + @FalsingCollectorActual private val collector: FalsingCollector, +) { + /** + * Notifies of a [MotionEvent] that passed through the UI. + * + * Must call [onMotionEventComplete] when done with this event. + */ + fun onTouchEvent(event: MotionEvent) = collector.onTouchEvent(event) + + /** + * Notifies that a [MotionEvent] has finished being dispatched through the UI. + * + * Must be called after each call to [onTouchEvent]. + */ + fun onMotionEventComplete() = collector.onMotionEventComplete() + + /** + * Instructs the falsing system to ignore the rest of the current input gesture; automatically + * resets when another gesture is started (with the next down event). + */ + fun avoidGesture() = collector.avoidGesture() + + /** + * Inserts the given [result] into the falsing system, affecting future runs of the classifier + * as if this was a result that had organically happened before. + */ + fun updateFalseConfidence( + result: FalsingClassifier.Result, + ) = collector.updateFalseConfidence(result) +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt index c3369da68821..970b475fe702 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt @@ -16,10 +16,10 @@ package com.android.systemui.communal.ui.view.layout.blueprints -import androidx.constraintlayout.widget.ConstraintSet import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalWidgetSection import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.data.repository.KeyguardBlueprint +import com.android.systemui.keyguard.shared.model.KeyguardBlueprint +import com.android.systemui.keyguard.shared.model.KeyguardSection import javax.inject.Inject /** Blueprint for communal mode. */ @@ -28,13 +28,10 @@ import javax.inject.Inject class DefaultCommunalBlueprint @Inject constructor( - private val defaultCommunalWidgetSection: DefaultCommunalWidgetSection, + defaultCommunalWidgetSection: DefaultCommunalWidgetSection, ) : KeyguardBlueprint { override val id: String = COMMUNAL - - override fun apply(constraintSet: ConstraintSet) { - defaultCommunalWidgetSection.apply(constraintSet) - } + override val sections: Array<KeyguardSection> = arrayOf(defaultCommunalWidgetSection) companion object { const val COMMUNAL = "communal" diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalWidgetSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalWidgetSection.kt index b0e3132a1fc7..4fb9384630a5 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalWidgetSection.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalWidgetSection.kt @@ -17,18 +17,47 @@ package com.android.systemui.communal.ui.view.layout.sections import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.END import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import com.android.systemui.R -import com.android.systemui.keyguard.data.repository.KeyguardSection +import com.android.systemui.communal.ui.adapter.CommunalWidgetViewAdapter +import com.android.systemui.communal.ui.binder.CommunalWidgetViewBinder +import com.android.systemui.communal.ui.viewmodel.CommunalWidgetViewModel +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor +import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.keyguard.ui.view.KeyguardRootView +import dagger.Lazy import javax.inject.Inject -class DefaultCommunalWidgetSection @Inject constructor() : KeyguardSection { +class DefaultCommunalWidgetSection +@Inject +constructor( + private val featureFlags: FeatureFlags, + private val keyguardRootView: KeyguardRootView, + private val communalWidgetViewModel: CommunalWidgetViewModel, + private val communalWidgetViewAdapter: CommunalWidgetViewAdapter, + private val keyguardBlueprintInteractor: Lazy<KeyguardBlueprintInteractor>, +) : KeyguardSection { private val widgetAreaViewId = R.id.communal_widget_wrapper + override fun addViews(constraintLayout: ConstraintLayout) { + if (!featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)) { + return + } + + CommunalWidgetViewBinder.bind( + keyguardRootView, + communalWidgetViewModel, + communalWidgetViewAdapter, + keyguardBlueprintInteractor.get(), + ) + } - override fun apply(constraintSet: ConstraintSet) { + override fun applyConstraints(constraintSet: ConstraintSet) { constraintSet.apply { constrainWidth(widgetAreaViewId, WRAP_CONTENT) constrainHeight(widgetAreaViewId, WRAP_CONTENT) diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt index d3174f11ace5..adb0bf38b51f 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt @@ -71,14 +71,9 @@ open class ControlsServiceInfo( private var resolved: Boolean = false @WorkerThread - fun resolvePanelActivity( - allowAllApps: Boolean = false - ) { + fun resolvePanelActivity() { if (resolved) return resolved = true - val validPackages = context.resources - .getStringArray(R.array.config_controlsPreferredPackages) - if (componentName.packageName !in validPackages && !allowAllApps) return panelActivity = _panelActivity?.let { val resolveInfos = mPm.queryIntentActivitiesAsUser( Intent().setComponent(it), diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt index 83bec664876d..74e1dc0b0fad 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt @@ -33,7 +33,6 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.settings.UserTracker import com.android.systemui.util.ActivityTaskManagerProxy import com.android.systemui.util.asIndenting @@ -125,9 +124,8 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( private fun updateServices(newServices: List<ControlsServiceInfo>) { if (activityTaskManagerProxy.supportsMultiWindow(context)) { - val allowAllApps = featureFlags.isEnabled(Flags.APP_PANELS_ALL_APPS_ALLOWED) newServices.forEach { - it.resolvePanelActivity(allowAllApps) } + it.resolvePanelActivity() } } if (newServices != availableServices) { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index 6fdb4ca7238f..dcacd090fa27 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -22,6 +22,7 @@ import com.android.systemui.Dependency; import com.android.systemui.InitController; import com.android.systemui.SystemUIAppComponentFactoryBase; import com.android.systemui.dagger.qualifiers.PerUser; +import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardSliceProvider; import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli; @@ -140,6 +141,7 @@ public interface SysUIComponent { getMediaMuteAwaitConnectionCli(); getNearbyMediaDevicesManager(); getUnfoldLatencyTracker().init(); + getConnectingDisplayViewModel().init(); getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init); getFoldStateLogger().ifPresent(FoldStateLogger::init); getUnfoldTransitionProgressProvider().ifPresent((progressProvider) -> @@ -229,6 +231,11 @@ public interface SysUIComponent { NearbyMediaDevicesManager getNearbyMediaDevicesManager(); /** + * Creates a ConnectingDisplayViewModel + */ + ConnectingDisplayViewModel getConnectingDisplayViewModel(); + + /** * Returns {@link CoreStartable}s that should be started with the application. */ Map<Class<?>, Provider<CoreStartable>> getStartables(); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index 1b2a9ebc9ca4..7ce7ce94e4c9 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -23,6 +23,7 @@ import com.android.systemui.ScreenDecorations import com.android.systemui.SliceBroadcastRelayHandler import com.android.systemui.accessibility.SystemActions import com.android.systemui.accessibility.WindowMagnification +import com.android.systemui.back.domain.interactor.BackActionInteractor import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.BiometricNotificationService import com.android.systemui.clipboardoverlay.ClipboardListener @@ -346,4 +347,9 @@ abstract class SystemUICoreStartableModule { abstract fun bindStatusBarHeadsUpChangeListener( impl: StatusBarHeadsUpChangeListener ): CoreStartable + + @Binds + @IntoMap + @ClassKey(BackActionInteractor::class) + abstract fun bindBackActionInteractor(impl: BackActionInteractor): CoreStartable } diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt index b18f7bfe7acc..e7bbf97e3b51 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt @@ -22,6 +22,7 @@ import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_ADDED import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_REMOVED import android.os.Handler +import android.util.Log import android.view.Display import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton @@ -34,6 +35,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.stateIn @@ -41,6 +43,13 @@ import kotlinx.coroutines.flow.stateIn interface DisplayRepository { /** Provides a nullable set of displays. */ val displays: Flow<Set<Display>> + + /** + * Pending display id that can be enabled/disabled. + * + * When `null`, it means there is no pending display waiting to be enabled. + */ + val pendingDisplayId: Flow<Int?> } @SysUISingleton @@ -85,8 +94,60 @@ constructor( initialValue = getDisplays() ) - fun getDisplays(): Set<Display> = + private fun getDisplays(): Set<Display> = traceSection("DisplayRepository#getDisplays()") { displayManager.displays?.toSet() ?: emptySet() } + + override val pendingDisplayId: Flow<Int?> = + conflatedCallbackFlow { + val callback = + object : DisplayConnectionListener { + private val pendingIds = mutableSetOf<Int>() + override fun onDisplayConnected(id: Int) { + pendingIds += id + trySend(id) + } + + override fun onDisplayDisconnected(id: Int) { + if (id in pendingIds) { + pendingIds -= id + trySend(null) + } else { + Log.e( + TAG, + "onDisplayDisconnected received for unknown display. " + + "id=$id, knownIds=$pendingIds" + ) + } + } + } + displayManager.registerDisplayListener( + callback, + backgroundHandler, + DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED, + ) + awaitClose { displayManager.unregisterDisplayListener(callback) } + } + .distinctUntilChanged() + .flowOn(backgroundCoroutineDispatcher) + .stateIn( + applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = null + ) + + private companion object { + const val TAG = "DisplayRepository" + } +} + +/** Used to provide default implementations for all methods. */ +private interface DisplayConnectionListener : DisplayListener { + + override fun onDisplayConnected(id: Int) {} + override fun onDisplayDisconnected(id: Int) {} + override fun onDisplayAdded(id: Int) {} + override fun onDisplayRemoved(id: Int) {} + override fun onDisplayChanged(id: Int) {} } diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt index 4b957c7f435c..ef6fa26420a3 100644 --- a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt @@ -16,12 +16,17 @@ package com.android.systemui.display.domain.interactor +import android.hardware.display.DisplayManager import android.view.Display import com.android.systemui.dagger.SysUISingleton import com.android.systemui.display.data.repository.DisplayRepository +import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.util.traceSection import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map @@ -37,18 +42,32 @@ interface ConnectedDisplayInteractor { */ val connectedDisplayState: Flow<State> + /** Pending display that can be enabled to be used by the system. */ + val pendingDisplay: Flow<PendingDisplay?> + /** Possible connected display state. */ enum class State { DISCONNECTED, CONNECTED, CONNECTED_SECURE, } + + /** Represents a connected display that has not been enabled yet. */ + interface PendingDisplay { + /** Enables the display, making it available to the system. */ + fun enable() + + /** Disables the display, making it unavailable to the system. */ + fun disable() + } } @SysUISingleton class ConnectedDisplayInteractorImpl @Inject constructor( + private val displayManager: DisplayManager, + keyguardRepository: KeyguardRepository, displayRepository: DisplayRepository, ) : ConnectedDisplayInteractor { @@ -70,4 +89,31 @@ constructor( } } .distinctUntilChanged() + + // Provides the pending display only if the lockscreen is unlocked + override val pendingDisplay: Flow<PendingDisplay?> = + displayRepository.pendingDisplayId.combine(keyguardRepository.isKeyguardUnlocked) { + pendingDisplayId, + keyguardUnlocked -> + if (pendingDisplayId != null && keyguardUnlocked) { + pendingDisplayId.toPendingDisplay() + } else { + null + } + } + + private fun Int.toPendingDisplay() = + object : PendingDisplay { + val id = this@toPendingDisplay + override fun enable() { + traceSection("DisplayRepository#enable($id)") { + displayManager.enableConnectedDisplay(id) + } + } + override fun disable() { + traceSection("DisplayRepository#enable($id)") { + displayManager.disableConnectedDisplay(id) + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt new file mode 100644 index 000000000000..174c6ff04a7d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.display.ui.view + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.Gravity +import android.view.View +import android.view.WindowManager +import android.widget.TextView +import com.android.systemui.R + +/** Dialog used to decide what to do with a connected display. */ +class MirroringConfirmationDialog( + context: Context, + private val onStartMirroringClickListener: View.OnClickListener, + private val onDismissClickListener: View.OnClickListener, +) : Dialog(context, R.style.Theme_SystemUI_Dialog) { + + private lateinit var mirrorButton: TextView + private lateinit var dismissButton: TextView + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window?.apply { + setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL) + addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS) + setGravity(Gravity.BOTTOM) + } + setContentView(R.layout.connected_display_dialog) + setCanceledOnTouchOutside(true) + mirrorButton = + requireViewById<TextView>(R.id.enable_display).apply { + setOnClickListener(onStartMirroringClickListener) + } + dismissButton = + requireViewById<TextView>(R.id.cancel).apply { + setOnClickListener(onDismissClickListener) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt new file mode 100644 index 000000000000..ece33b7f6032 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.display.ui.viewmodel + +import android.app.Dialog +import android.content.Context +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor +import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay +import com.android.systemui.display.ui.view.MirroringConfirmationDialog +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +/** + * Shows/hides a dialog to allow the user to decide whether to use the external display for + * mirroring. + */ +@SysUISingleton +class ConnectingDisplayViewModel +@Inject +constructor( + private val context: Context, + private val connectedDisplayInteractor: ConnectedDisplayInteractor, + @Application private val scope: CoroutineScope, +) { + + private var dialog: Dialog? = null + + /** Starts listening for pending displays. */ + fun init() { + connectedDisplayInteractor.pendingDisplay + .onEach { pendingDisplay -> + if (pendingDisplay == null) { + hideDialog() + } else { + showDialog(pendingDisplay) + } + } + .launchIn(scope) + } + + private fun showDialog(pendingDisplay: PendingDisplay) { + hideDialog() + dialog = + MirroringConfirmationDialog( + context, + onStartMirroringClickListener = { + pendingDisplay.enable() + hideDialog() + }, + onDismissClickListener = { hideDialog() } + ) + .apply { show() } + } + + private fun hideDialog() { + dialog?.hide() + dialog = null + } +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index e9a539f7ca46..907e106d1151 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -39,6 +39,10 @@ object Flags { @JvmField val TEAMFOOD = unreleasedFlag("teamfood") // 100 - notification + // TODO(b/297792660): Tracking Bug + val ADD_TRANSIENT_HUN_IN_STACK_STATE_ANIMATOR = + unreleasedFlag("add_transient_hun_in_stack_state_animator", teamfood = false) + // TODO(b/254512751): Tracking Bug val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING = unreleasedFlag("notification_pipeline_developer_logging") @@ -204,6 +208,10 @@ object Flags { @JvmField val LOCK_SCREEN_LONG_PRESS_ENABLED = releasedFlag("lock_screen_long_press_enabled") + /** Inflate and bind views upon emitting a blueprint value . */ + // TODO(b/297365780): Tracking Bug + @JvmField val LAZY_INFLATE_KEYGUARD = unreleasedFlag("lazy_inflate_keyguard") + /** Enables UI updates for AI wallpapers in the wallpaper picker. */ // TODO(b/267722622): Tracking Bug @JvmField val WALLPAPER_PICKER_UI_FOR_AIWP = releasedFlag("wallpaper_picker_ui_for_aiwp") @@ -370,6 +378,9 @@ object Flags { // 600- status bar + // TODO(b/291315866): Tracking Bug + @JvmField val SIGNAL_CALLBACK_DEPRECATION = unreleasedFlag("signal_callback_deprecation") + // TODO(b/265892345): Tracking Bug val PLUG_IN_STATUS_BAR_CHIP = releasedFlag("plug_in_status_bar_chip") @@ -391,7 +402,7 @@ object Flags { // TODO(b/290676905): Tracking Bug val NEW_SHADE_CARRIER_GROUP_MOBILE_ICONS = - unreleasedFlag("new_shade_carrier_group_mobile_icons") + unreleasedFlag("new_shade_carrier_group_mobile_icons", teamfood = true) // 700 - dialer/calls // TODO(b/254512734): Tracking Bug @@ -631,9 +642,6 @@ object Flags { // 1900 @JvmField val NOTE_TASKS = releasedFlag("keycode_flag") - // 2000 - device controls - @JvmField val APP_PANELS_ALL_APPS_ALLOWED = releasedFlag("app_panels_all_apps_allowed") - // 2200 - biometrics (udfps, sfps, BiometricPrompt, etc.) // TODO(b/259264861): Tracking Bug @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag("udfps_new_touch_detection") @@ -654,6 +662,10 @@ object Flags { val WARN_ON_BLOCKING_BINDER_TRANSACTIONS = unreleasedFlag("warn_on_blocking_binder_transactions") + @JvmField + val COROUTINE_TRACING = + unreleasedFlag("coroutine_tracing") + // TODO(b/283071711): Tracking bug @JvmField val TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK = diff --git a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt index c41b5e4d319b..285601116d0b 100644 --- a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt +++ b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt @@ -366,6 +366,52 @@ constructor( } } + /** + * Obtains the image size from the image header, without decoding the full image. + * + * @param icon an [Icon] representing the source of the image + * @return the [Size] if it could be determined from the image header, or `null` otherwise + */ + suspend fun loadSize(icon: Icon, context: Context): Size? = + withContext(backgroundDispatcher) { loadSizeSync(icon, context) } + + /** + * Obtains the image size from the image header, without decoding the full image. + * + * @param icon an [Icon] representing the source of the image + * @return the [Size] if it could be determined from the image header, or `null` otherwise + */ + @WorkerThread + fun loadSizeSync(icon: Icon, context: Context): Size? { + return when (icon.type) { + Icon.TYPE_URI, + Icon.TYPE_URI_ADAPTIVE_BITMAP -> { + val source = ImageDecoder.createSource(context.contentResolver, icon.uri) + loadSizeSync(source) + } + else -> null + } + } + + /** + * Obtains the image size from the image header, without decoding the full image. + * + * @param source [ImageDecoder.Source] of the image + * @return the [Size] if it could be determined from the image header, or `null` otherwise + */ + @WorkerThread + fun loadSizeSync(source: ImageDecoder.Source): Size? { + return try { + ImageDecoder.decodeHeader(source).size + } catch (e: IOException) { + Log.w(TAG, "Failed to load source $source", e) + return null + } catch (e: DecodeException) { + Log.w(TAG, "Failed to decode source $source", e) + return null + } + } + companion object { const val TAG = "ImageLoader" @@ -452,7 +498,7 @@ constructor( * originate from other processes so we need to make sure we load them from the right * package source. * - * @return [Resources] to load the icon drawble or null if icon doesn't carry a resource or + * @return [Resources] to load the icon drawable or null if icon doesn't carry a resource or * the resource package couldn't be resolved. */ @WorkerThread diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt new file mode 100644 index 000000000000..629b361064a7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.haptics.slider + +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update + +/** An event producer for a Seekable element such as the Android [SeekBar] */ +class SeekableSliderEventProducer : SliderEventProducer, OnSeekBarChangeListener { + + /** The current event reported by a SeekBar */ + private val _currentEvent = MutableStateFlow(SliderEvent(SliderEventType.NOTHING, 0f)) + + override fun produceEvents(): Flow<SliderEvent> = _currentEvent.asStateFlow() + + override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { + val eventType = + if (fromUser) SliderEventType.PROGRESS_CHANGE_BY_USER + else SliderEventType.PROGRESS_CHANGE_BY_PROGRAM + + _currentEvent.value = SliderEvent(eventType, normalizeProgress(seekBar, progress)) + } + + /** + * Normalize the integer progress of a SeekBar to the range from 0F to 1F. + * + * @param[seekBar] The SeekBar that reports a progress. + * @param[progress] The integer progress of the SeekBar within its min and max values. + * @return The progress in the range from 0F to 1F. + */ + private fun normalizeProgress(seekBar: SeekBar, progress: Int): Float { + if (seekBar.max == seekBar.min) { + return 1.0f + } + val range = seekBar.max - seekBar.min + return (progress - seekBar.min) / range.toFloat() + } + + override fun onStartTrackingTouch(seekBar: SeekBar) { + _currentEvent.update { previousEvent -> + SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, previousEvent.currentProgress) + } + } + + override fun onStopTrackingTouch(seekBar: SeekBar) { + _currentEvent.update { previousEvent -> + SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, previousEvent.currentProgress) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEvent.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEvent.kt new file mode 100644 index 000000000000..1377b29d8d21 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEvent.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.haptics.slider + +import androidx.annotation.FloatRange + +/** + * An event arising from a slider. + * + * @property[type] The type of event. Must be one of [SliderEventType]. + * @property[currentProgress] The current progress of the slider normalized to the range between 0F + * and 1F (inclusive). + */ +data class SliderEvent( + val type: SliderEventType, + @FloatRange(from = 0.0, to = 1.0) val currentProgress: Float +) diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventProducer.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventProducer.kt new file mode 100644 index 000000000000..8b17e86e7cc8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventProducer.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.haptics.slider + +import kotlinx.coroutines.flow.Flow + +/** Defines a producer of [SliderEvent] to be consumed as a [Flow] */ +interface SliderEventProducer { + + /** + * Produce a stream of [SliderEvent] + * + * @return A [Flow] of [SliderEvent] produced from the operation of a slider. + */ + fun produceEvents(): Flow<SliderEvent> +} diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt new file mode 100644 index 000000000000..413e27763ba8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt @@ -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. + */ + +package com.android.systemui.haptics.slider + +/** The type of a [SliderEvent]. */ +enum class SliderEventType { + /* No event. */ + NOTHING, + /* The slider has captured a touch input and is tracking touch events. */ + STARTED_TRACKING_TOUCH, + /* The slider progress is changing due to user touch input. */ + PROGRESS_CHANGE_BY_USER, + /* The slider progress is changing programmatically. */ + PROGRESS_CHANGE_BY_PROGRAM, + /* The slider has stopped tracking touch events. */ + STOPPED_TRACKING_TOUCH, + /* The external (not touch) stimulus that was modifying the slider progress has stopped. */ + EXTERNAL_STIMULUS_RELEASE, +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 9d2771ec4d89..f6add9c66f7c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -298,18 +298,18 @@ public class KeyguardService extends Service { } @Inject - public KeyguardService(KeyguardViewMediator keyguardViewMediator, - KeyguardLifecyclesDispatcher keyguardLifecyclesDispatcher, - ScreenOnCoordinator screenOnCoordinator, - ShellTransitions shellTransitions, - DisplayTracker displayTracker, - WindowManagerLockscreenVisibilityViewModel - wmLockscreenVisibilityViewModel, - WindowManagerLockscreenVisibilityManager wmLockscreenVisibilityManager, - KeyguardSurfaceBehindViewModel keyguardSurfaceBehindViewModel, - KeyguardSurfaceBehindParamsApplier keyguardSurfaceBehindAnimator, - @Application CoroutineScope scope, - FeatureFlags featureFlags) { + public KeyguardService( + KeyguardViewMediator keyguardViewMediator, + KeyguardLifecyclesDispatcher keyguardLifecyclesDispatcher, + ScreenOnCoordinator screenOnCoordinator, + ShellTransitions shellTransitions, + DisplayTracker displayTracker, + WindowManagerLockscreenVisibilityViewModel wmLockscreenVisibilityViewModel, + WindowManagerLockscreenVisibilityManager wmLockscreenVisibilityManager, + KeyguardSurfaceBehindViewModel keyguardSurfaceBehindViewModel, + KeyguardSurfaceBehindParamsApplier keyguardSurfaceBehindAnimator, + @Application CoroutineScope scope, + FeatureFlags featureFlags) { super(); mKeyguardViewMediator = keyguardViewMediator; mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index 1cd87955fd84..6bc9abf13cf7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -17,10 +17,15 @@ package com.android.systemui.keyguard +import android.content.Context import android.content.res.Configuration +import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import com.android.keyguard.KeyguardStatusView import com.android.keyguard.KeyguardStatusViewController +import com.android.keyguard.LockIconView +import com.android.keyguard.LockIconViewController import com.android.keyguard.dagger.KeyguardStatusViewComponent import com.android.systemui.CoreStartable import com.android.systemui.R @@ -37,6 +42,7 @@ import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder +import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.keyguard.ui.view.layout.KeyguardBlueprintCommandListener import com.android.systemui.keyguard.ui.viewmodel.KeyguardAmbientIndicationViewModel @@ -92,6 +98,9 @@ constructor( private val communalWidgetViewModel: CommunalWidgetViewModel, private val communalWidgetViewAdapter: CommunalWidgetViewAdapter, private val notificationStackScrollerLayoutController: NotificationStackScrollLayoutController, + private val context: Context, + private val keyguardIndicationController: KeyguardIndicationController, + private val lockIconViewController: LockIconViewController, ) : CoreStartable { private var rootViewHandle: DisposableHandle? = null @@ -100,22 +109,41 @@ constructor( private var rightShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null private var ambientIndicationAreaHandle: KeyguardAmbientIndicationAreaViewBinder.Binding? = null private var settingsPopupMenuHandle: DisposableHandle? = null - private var keyguardStatusViewController: KeyguardStatusViewController? = null + var keyguardStatusViewController: KeyguardStatusViewController? = null + get() { + if (field == null) { + val statusViewComponent = + keyguardStatusViewComponentFactory.build( + LayoutInflater.from(context).inflate(R.layout.keyguard_status_view, null) + as KeyguardStatusView + ) + val controller = statusViewComponent.keyguardStatusViewController + controller.init() + field = controller + } + + return field + } override fun start() { - bindKeyguardRootView() - val notificationPanel = - notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup - unbindKeyguardBottomArea(notificationPanel) - bindIndicationArea() - bindLockIconView(notificationPanel) - bindKeyguardStatusView(notificationPanel) - setupNotificationStackScrollLayout(notificationPanel) - bindLeftShortcut() - bindRightShortcut() - bindAmbientIndicationArea() - bindSettingsPopupMenu() - bindCommunalWidgetArea() + if (featureFlags.isEnabled(Flags.LAZY_INFLATE_KEYGUARD)) { + keyguardRootView.removeAllViews() + initializeViews() + } else { + bindKeyguardRootView() + val notificationPanel = + notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup + unbindKeyguardBottomArea(notificationPanel) + bindIndicationArea() + bindLockIconView(notificationPanel) + bindKeyguardStatusView(notificationPanel) + setupNotificationStackScrollLayout(notificationPanel) + bindLeftShortcut() + bindRightShortcut() + bindAmbientIndicationArea() + bindSettingsPopupMenu() + bindCommunalWidgetArea() + } KeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel) keyguardBlueprintCommandListener.start() @@ -164,6 +192,14 @@ constructor( ) } + /** Initialize views so that corresponding controllers have a view set. */ + private fun initializeViews() { + val indicationArea = KeyguardIndicationArea(context, null) + keyguardIndicationController.setIndicationArea(indicationArea) + + lockIconViewController.setLockIconView(LockIconView(context, null)) + } + private fun bindKeyguardRootView() { rootViewHandle?.dispose() rootViewHandle = @@ -186,6 +222,9 @@ constructor( keyguardRootView.findViewById<View?>(R.id.lock_icon_view)?.let { keyguardRootView.removeView(it) } + legacyParent.requireViewById<LockIconView>(R.id.lock_icon_view).let { + lockIconViewController.setLockIconView(it) + } } } @@ -307,6 +346,5 @@ constructor( * Temporary, to allow NotificationPanelViewController to use the same instance while code is * migrated: b/288242803 */ - fun getKeyguardStatusViewController() = keyguardStatusViewController fun getKeyguardRootView() = keyguardRootView } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index e983a0550c1f..2b4dc8108418 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -3207,7 +3207,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, * visible */ public void exitKeyguardAndFinishSurfaceBehindRemoteAnimation(boolean showKeyguard) { - Log.d(TAG, "onKeyguardExitRemoteAnimationFinished"); + Log.d(TAG, "exitKeyguardAndFinishSurfaceBehindRemoteAnimation"); if (!mSurfaceBehindRemoteAnimationRunning && !mSurfaceBehindRemoteAnimationRequested) { Log.d(TAG, "skip onKeyguardExitRemoteAnimationFinished showKeyguard=" + showKeyguard + " surfaceAnimationRunning=" + mSurfaceBehindRemoteAnimationRunning @@ -3222,6 +3222,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // Post layout changes to the next frame, so we don't hang at the end of the animation. DejankUtils.postAfterTraversal(() -> { + if (!mPM.isInteractive()) { + Log.e(TAG, "exitKeyguardAndFinishSurfaceBehindRemoteAnimation#postAfterTraversal" + + "Not interactive after traversal. Don't hide the keyguard. This means we " + + "re-locked the device during unlock."); + return; + } + onKeyguardExitFinished(); if (mKeyguardStateController.isDismissingFromSwipe() || wasShowing) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt index 7234757081e2..f91ae743d956 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt @@ -17,13 +17,10 @@ package com.android.systemui.keyguard.data.repository -import android.view.View -import androidx.constraintlayout.widget.ConstraintLayout -import androidx.constraintlayout.widget.ConstraintSet -import androidx.core.view.children import com.android.systemui.common.ui.data.repository.ConfigurationRepository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.shared.model.KeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule import java.io.PrintWriter @@ -95,26 +92,3 @@ constructor( blueprintIdMap.forEach { entry -> pw.println("${entry.key}") } } } - -/** Determines the constraints for the ConstraintSet in the lockscreen root view. */ -interface KeyguardBlueprint { - val id: String - val shouldRemoveUnconstrainedViews: Boolean - get() = true - - fun apply(constraintLayout: ConstraintSet) - fun removeUnConstrainedViews(constraintLayout: ConstraintLayout, constraintSet: ConstraintSet) { - constraintLayout.children - .map { it.id } - .filterNot { constraintSet.knownIds.contains(it) } - .forEach { constraintSet.setVisibility(it, View.GONE) } - } -} - -/** - * Lower level modules that determine constraints for a particular section in the lockscreen root - * view. - */ -interface KeyguardSection { - fun apply(constraintSet: ConstraintSet) -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index 42cd3a576926..d399e4c145fc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -158,7 +158,7 @@ interface KeyguardRepository { val lastDozeTapToWakePosition: StateFlow<Point?> /** Observable for the [StatusBarState] */ - val statusBarState: Flow<StatusBarState> + val statusBarState: StateFlow<StatusBarState> /** Observable for device wake/sleep state */ val wakefulness: StateFlow<WakefulnessModel> @@ -520,23 +520,29 @@ constructor( return keyguardBypassController.bypassEnabled } - override val statusBarState: Flow<StatusBarState> = conflatedCallbackFlow { - val callback = - object : StatusBarStateController.StateListener { - override fun onStateChanged(state: Int) { - trySendWithFailureLogging(statusBarStateIntToObject(state), TAG, "state") - } - } - - statusBarStateController.addCallback(callback) - trySendWithFailureLogging( - statusBarStateIntToObject(statusBarStateController.getState()), - TAG, - "initial state" - ) + // TODO(b/297345631): Expose this at the interactor level instead so that it can be powered by + // [SceneInteractor] when scenes are ready. + override val statusBarState: StateFlow<StatusBarState> = + conflatedCallbackFlow { + val callback = + object : StatusBarStateController.StateListener { + override fun onStateChanged(state: Int) { + trySendWithFailureLogging( + statusBarStateIntToObject(state), + TAG, + "state" + ) + } + } - awaitClose { statusBarStateController.removeCallback(callback) } - } + statusBarStateController.addCallback(callback) + awaitClose { statusBarStateController.removeCallback(callback) } + } + .stateIn( + scope, + SharingStarted.Eagerly, + statusBarStateIntToObject(statusBarStateController.state) + ) override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow { fun dispatchUpdate() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt index ff0db34ca06d..714add461c16 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt @@ -50,14 +50,28 @@ constructor( listenForOccludedToAodOrDozing() listenForOccludedToGone() listenForOccludedToAlternateBouncer() + listenForOccludedToPrimaryBouncer() + } + + private fun listenForOccludedToPrimaryBouncer() { + scope.launch { + keyguardInteractor.primaryBouncerShowing + .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair) + .collect { (isBouncerShowing, lastStartedTransitionStep) -> + if ( + isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.OCCLUDED + ) { + startTransitionTo(KeyguardState.PRIMARY_BOUNCER) + } + } + } } private fun listenForOccludedToDreaming() { scope.launch { keyguardInteractor.isAbleToDream .sample(transitionInteractor.finishedKeyguardState, ::Pair) - .collect { pair -> - val (isAbleToDream, keyguardState) = pair + .collect { (isAbleToDream, keyguardState) -> if (isAbleToDream && keyguardState == KeyguardState.OCCLUDED) { startTransitionTo(KeyguardState.DREAMING) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index e13f6757112e..0c05a0e33871 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -78,28 +78,37 @@ constructor( /** Position information for the shared notification container. */ val sharedNotificationContainerPosition = MutableStateFlow(SharedNotificationContainerPosition()) + /** * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at * all. */ val dozeAmount: Flow<Float> = repository.linearDozeAmount + /** Whether the system is in doze mode. */ val isDozing: Flow<Boolean> = repository.isDozing + /** Receive an event for doze time tick */ val dozeTimeTick: Flow<Long> = repository.dozeTimeTick + /** Whether Always-on Display mode is available. */ val isAodAvailable: Flow<Boolean> = repository.isAodAvailable + /** Doze transition information. */ val dozeTransitionModel: Flow<DozeTransitionModel> = repository.dozeTransitionModel + /** * Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true, * but not vice-versa. */ val isDreaming: Flow<Boolean> = repository.isDreaming + /** Whether the system is dreaming with an overlay active */ val isDreamingWithOverlay: Flow<Boolean> = repository.isDreamingWithOverlay + /** Whether the system is dreaming and the active dream is hosted in lockscreen */ val isActiveDreamLockscreenHosted: StateFlow<Boolean> = repository.isActiveDreamLockscreenHosted + /** Event for when the camera gesture is detected */ val onCameraLaunchDetected: Flow<CameraLaunchSourceModel> = conflatedCallbackFlow { val callback = @@ -148,18 +157,25 @@ constructor( /** Whether the keyguard is showing or not. */ val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing + /** Whether the keyguard is unlocked or not. */ val isKeyguardUnlocked: Flow<Boolean> = repository.isKeyguardUnlocked + /** Whether the keyguard is occluded (covered by an activity). */ val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded + /** Whether the keyguard is going away. */ val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway + /** Whether the primary bouncer is showing or not. */ val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerShow + /** Whether the alternate bouncer is showing or not. */ val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible + /** Observable for the [StatusBarState] */ val statusBarState: Flow<StatusBarState> = repository.statusBarState + /** * Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear, * side, under display) is used to unlock the device. diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt new file mode 100644 index 000000000000..659c5f3007e8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.shared.model + +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet + +/** Determines the constraints for the ConstraintSet in the lockscreen root view. */ +interface KeyguardBlueprint { + val id: String + val sections: Array<KeyguardSection> + + fun addViews(constraintLayout: ConstraintLayout) { + sections.forEach { it.addViews(constraintLayout) } + } + + fun applyConstraints(constraintSet: ConstraintSet) { + sections.forEach { it.applyConstraints(constraintSet) } + } + + fun onDestroy() { + sections.forEach { it.onDestroy() } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSection.kt new file mode 100644 index 000000000000..19f50dec3032 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSection.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.shared.model + +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet + +/** + * Lower level modules that determine constraints for a particular section in the lockscreen root + * view. + */ +interface KeyguardSection { + fun addViews(constraintLayout: ConstraintLayout) + fun applyConstraints(constraintSet: ConstraintSet) + fun onDestroy() {} +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt index fb685dab1797..c8a04fdbca52 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt @@ -19,18 +19,20 @@ package com.android.systemui.keyguard.shared.model import android.os.PowerManager /** The reason we're waking up or going to sleep, such as pressing the power button. */ -enum class WakeSleepReason { +enum class WakeSleepReason( + val isTouch: Boolean, +) { /** The physical power button was pressed to wake up or sleep the device. */ - POWER_BUTTON, + POWER_BUTTON(isTouch = false), /** The user has tapped or double tapped to wake the screen. */ - TAP, + TAP(isTouch = true), /** The user performed some sort of gesture to wake the screen. */ - GESTURE, + GESTURE(isTouch = true), /** Something else happened to wake up or sleep the device. */ - OTHER; + OTHER(isTouch = false); companion object { fun fromPowerManagerWakeReason(reason: Int): WakeSleepReason { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt index e40c2793176e..c340e5df498b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt @@ -21,6 +21,7 @@ import android.os.Trace import android.util.Log import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet +import androidx.core.view.children import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel @@ -36,16 +37,28 @@ class KeyguardBlueprintViewBinder { repeatOnLifecycle(Lifecycle.State.CREATED) { launch { viewModel.blueprint.collect { blueprint -> - Trace.beginSection("KeyguardBlueprintController#applyBlueprint") + Trace.beginSection("KeyguardBlueprint#applyBlueprint") Log.d(TAG, "applying blueprint: $blueprint") - ConstraintSet().apply { - clone(constraintLayout) - val emptyLayout = ConstraintSet.Layout() - knownIds.forEach { getConstraint(it).layout.copyFrom(emptyLayout) } - blueprint?.apply(this) - blueprint?.removeUnConstrainedViews(constraintLayout, this) - applyTo(constraintLayout) + if (blueprint != viewModel.currentBluePrint) { + viewModel.currentBluePrint?.onDestroy() } + val constraintSet = + ConstraintSet().apply { + clone(constraintLayout) + val emptyLayout = ConstraintSet.Layout() + knownIds.forEach { + getConstraint(it).layout.copyFrom(emptyLayout) + } + blueprint.addViews(constraintLayout) + blueprint.applyConstraints(this) + applyTo(constraintLayout) + } + // Remove all unconstrained views. + constraintLayout.children + .filterNot { constraintSet.knownIds.contains(it.id) } + .forEach { constraintLayout.removeView(it) } + + viewModel.currentBluePrint = blueprint Trace.endSection() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index 41c1c9600561..2cfc47845c71 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -34,6 +34,7 @@ import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.widget.FrameLayout +import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isInvisible import com.android.keyguard.ClockEventController import com.android.keyguard.KeyguardClockSwitch @@ -45,12 +46,12 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor -import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder import com.android.systemui.keyguard.ui.view.KeyguardRootView +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel @@ -109,6 +110,7 @@ constructor( private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel, private val chipbarCoordinator: ChipbarCoordinator, private val keyguardStateController: KeyguardStateController, + private val defaultShortcutsSection: DefaultShortcutsSection, ) { val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN) @@ -119,6 +121,7 @@ constructor( KeyguardPreviewConstants.KEY_HIGHLIGHT_QUICK_AFFORDANCES, false, ) + /** [shouldHideClock] here means that we never create and bind the clock views */ private val shouldHideClock: Boolean = bundle.getBoolean(ClockPreviewConstants.KEY_HIDE_CLOCK, false) @@ -176,7 +179,6 @@ constructor( if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { setupKeyguardRootView(rootView) - setupShortcuts(rootView) } else { setUpBottomArea(rootView) } @@ -348,14 +350,14 @@ constructor( FrameLayout.LayoutParams.MATCH_PARENT, ), ) - KeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel) - keyguardBlueprintInteractor.refreshBlueprint() + setupShortcuts(keyguardRootView) } - private fun setupShortcuts(rootView: FrameLayout) { + private fun setupShortcuts(keyguardRootView: ConstraintLayout) { + defaultShortcutsSection.addShortcutViews(keyguardRootView) shortcutsBindings.add( KeyguardQuickAffordanceViewBinder.bind( - rootView.requireViewById(R.id.start_button), + keyguardRootView.requireViewById(R.id.start_button), quickAffordancesCombinedViewModel.startButton, keyguardRootViewModel.alpha, falsingManager, @@ -367,7 +369,7 @@ constructor( shortcutsBindings.add( KeyguardQuickAffordanceViewBinder.bind( - rootView.requireViewById(R.id.end_button), + keyguardRootView.requireViewById(R.id.end_button), quickAffordancesCombinedViewModel.endButton, keyguardRootViewModel.alpha, falsingManager, @@ -516,11 +518,11 @@ constructor( // is dark or a light. // TODO(b/277832214) we can potentially simplify this code by checking for // wallpaperColors being null in the if clause above and removing the many ?. - val wallpaperColorScheme = - wallpaperColors?.let { ColorScheme(it, /* darkTheme= */ false) } + val wallpaperColorScheme = wallpaperColors?.let { ColorScheme(it, darkTheme = false) } val lightClockColor = wallpaperColorScheme?.accent1?.s100 val darkClockColor = wallpaperColorScheme?.accent2?.s600 - /** Note that when [wallpaperColors] is null, isWallpaperDark is true. */ + + // Note that when [wallpaperColors] is null, isWallpaperDark is true. val isWallpaperDark: Boolean = (wallpaperColors?.colorHints?.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) == 0 clock.events.onSeedColorChanged( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt index 518df0719aaa..5a15fc236a0a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt @@ -17,12 +17,15 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints -import androidx.constraintlayout.widget.ConstraintSet +import androidx.constraintlayout.widget.ConstraintLayout import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.data.repository.KeyguardBlueprint +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.model.KeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection @@ -39,24 +42,34 @@ import javax.inject.Inject class DefaultKeyguardBlueprint @Inject constructor( - private val defaultIndicationAreaSection: DefaultIndicationAreaSection, - private val defaultLockIconSection: DefaultLockIconSection, - private val defaultShortcutsSection: DefaultShortcutsSection, - private val defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection, - private val defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection, - private val defaultStatusViewSection: DefaultStatusViewSection, - private val splitShadeGuidelines: SplitShadeGuidelines, + defaultIndicationAreaSection: DefaultIndicationAreaSection, + defaultLockIconSection: DefaultLockIconSection, + defaultShortcutsSection: DefaultShortcutsSection, + defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection, + defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection, + defaultStatusViewSection: DefaultStatusViewSection, + defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection, + splitShadeGuidelines: SplitShadeGuidelines, + private val featureFlags: FeatureFlags, ) : KeyguardBlueprint { override val id: String = DEFAULT - override fun apply(constraintSet: ConstraintSet) { - defaultIndicationAreaSection.apply(constraintSet) - defaultLockIconSection.apply(constraintSet) - defaultShortcutsSection.apply(constraintSet) - defaultAmbientIndicationAreaSection.apply(constraintSet) - defaultSettingsPopupMenuSection.apply(constraintSet) - defaultStatusViewSection.apply(constraintSet) - splitShadeGuidelines.apply(constraintSet) + override val sections = + arrayOf( + defaultIndicationAreaSection, + defaultLockIconSection, + defaultShortcutsSection, + defaultAmbientIndicationAreaSection, + defaultSettingsPopupMenuSection, + defaultStatusViewSection, + defaultNotificationStackScrollLayoutSection, + splitShadeGuidelines, + ) + + override fun addViews(constraintLayout: ConstraintLayout) { + if (featureFlags.isEnabled(Flags.LAZY_INFLATE_KEYGUARD)) { + super.addViews(constraintLayout) + } } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt index 07f316b487bf..fda4c3d5376a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt @@ -18,7 +18,7 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints import com.android.systemui.communal.ui.view.layout.blueprints.DefaultCommunalBlueprint -import com.android.systemui.keyguard.data.repository.KeyguardBlueprint +import com.android.systemui.keyguard.shared.model.KeyguardBlueprint import dagger.Binds import dagger.Module import dagger.multibindings.IntoSet diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt index 54c27960db3c..5ef625e62d00 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt @@ -17,16 +17,14 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints -import androidx.constraintlayout.widget.ConstraintSet -import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.data.repository.KeyguardBlueprint +import com.android.systemui.keyguard.shared.model.KeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.sections.AlignShortcutsToUdfpsSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection -import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines import javax.inject.Inject @@ -36,31 +34,28 @@ import javax.inject.Inject class ShortcutsBesideUdfpsKeyguardBlueprint @Inject constructor( - private val keyguardUpdateMonitor: KeyguardUpdateMonitor, - private val defaultIndicationAreaSection: DefaultIndicationAreaSection, - private val defaultLockIconSection: DefaultLockIconSection, - private val defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection, - private val defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection, - private val alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection, - private val defaultShortcutsSection: DefaultShortcutsSection, - private val defaultStatusViewSection: DefaultStatusViewSection, - private val splitShadeGuidelines: SplitShadeGuidelines, + defaultIndicationAreaSection: DefaultIndicationAreaSection, + defaultLockIconSection: DefaultLockIconSection, + defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection, + defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection, + alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection, + defaultStatusViewSection: DefaultStatusViewSection, + splitShadeGuidelines: SplitShadeGuidelines, + defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection, ) : KeyguardBlueprint { override val id: String = SHORTCUTS_BESIDE_UDFPS - override fun apply(constraintSet: ConstraintSet) { - defaultIndicationAreaSection.apply(constraintSet) - defaultLockIconSection.apply(constraintSet) - defaultAmbientIndicationAreaSection.apply(constraintSet) - defaultSettingsPopupMenuSection.apply(constraintSet) - if (keyguardUpdateMonitor.isUdfpsSupported) { - alignShortcutsToUdfpsSection.apply(constraintSet) - } else { - defaultShortcutsSection.apply(constraintSet) - } - defaultStatusViewSection.apply(constraintSet) - splitShadeGuidelines.apply(constraintSet) - } + override val sections = + arrayOf( + defaultIndicationAreaSection, + defaultLockIconSection, + defaultAmbientIndicationAreaSection, + defaultSettingsPopupMenuSection, + alignShortcutsToUdfpsSection, + defaultStatusViewSection, + defaultNotificationStackScrollLayoutSection, + splitShadeGuidelines, + ) companion object { const val SHORTCUTS_BESIDE_UDFPS = "shortcutsBesideUdfps" diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt index 156b9f3e5f48..587c6b7cb4ae 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.view.layout.sections import android.content.res.Resources +import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.LEFT @@ -26,12 +27,58 @@ import androidx.constraintlayout.widget.ConstraintSet.RIGHT import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.R import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.keyguard.data.repository.KeyguardSection +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder +import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.statusbar.KeyguardIndicationController +import com.android.systemui.statusbar.VibratorHelper import javax.inject.Inject -class AlignShortcutsToUdfpsSection @Inject constructor(@Main private val resources: Resources) : - KeyguardSection { - override fun apply(constraintSet: ConstraintSet) { +class AlignShortcutsToUdfpsSection +@Inject +constructor( + @Main private val resources: Resources, + private val featureFlags: FeatureFlags, + private val keyguardQuickAffordancesCombinedViewModel: + KeyguardQuickAffordancesCombinedViewModel, + private val keyguardRootViewModel: KeyguardRootViewModel, + private val falsingManager: FalsingManager, + private val indicationController: KeyguardIndicationController, + private val vibratorHelper: VibratorHelper, +) : BaseShortcutsSection(), KeyguardSection { + + override fun addViews(constraintLayout: ConstraintLayout) { + if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { + addLeftShortcut(constraintLayout) + addRightShortcut(constraintLayout) + leftShortcutHandle = + KeyguardQuickAffordanceViewBinder.bind( + constraintLayout.requireViewById(R.id.start_button), + keyguardQuickAffordancesCombinedViewModel.startButton, + keyguardRootViewModel.alpha, + falsingManager, + vibratorHelper, + ) { + indicationController.showTransientIndication(it) + } + rightShortcutHandle = + KeyguardQuickAffordanceViewBinder.bind( + constraintLayout.requireViewById(R.id.end_button), + keyguardQuickAffordancesCombinedViewModel.endButton, + keyguardRootViewModel.alpha, + falsingManager, + vibratorHelper, + ) { + indicationController.showTransientIndication(it) + } + } + } + + override fun applyConstraints(constraintSet: ConstraintSet) { val width = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width) val height = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/BaseShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/BaseShortcutsSection.kt new file mode 100644 index 000000000000..db0cf5ad3000 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/BaseShortcutsSection.kt @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.view.layout.sections + +import android.view.View +import android.widget.ImageView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import androidx.core.content.res.ResourcesCompat +import com.android.systemui.R +import com.android.systemui.animation.view.LaunchableImageView +import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder + +/** Base class for sections that add lockscreen shortcuts. */ +abstract class BaseShortcutsSection : KeyguardSection { + protected open var leftShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null + protected open var rightShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null + + override fun addViews(constraintLayout: ConstraintLayout) {} + + override fun applyConstraints(constraintSet: ConstraintSet) {} + + override fun onDestroy() { + leftShortcutHandle?.destroy() + rightShortcutHandle?.destroy() + } + + protected open fun addLeftShortcut(constraintLayout: ConstraintLayout) { + if (constraintLayout.findViewById<View>(R.id.start_button) != null) return + + val padding = + constraintLayout.resources.getDimensionPixelSize( + R.dimen.keyguard_affordance_fixed_padding + ) + val view = + LaunchableImageView(constraintLayout.context, null).apply { + id = R.id.start_button + scaleType = ImageView.ScaleType.FIT_CENTER + background = + ResourcesCompat.getDrawable( + context.resources, + R.drawable.keyguard_bottom_affordance_bg, + context.theme + ) + foreground = + ResourcesCompat.getDrawable( + context.resources, + R.drawable.keyguard_bottom_affordance_selected_border, + context.theme + ) + visibility = View.INVISIBLE + setPadding(padding, padding, padding, padding) + } + constraintLayout.addView(view) + } + + protected open fun addRightShortcut(constraintLayout: ConstraintLayout) { + if (constraintLayout.findViewById<View>(R.id.end_button) != null) return + + val padding = + constraintLayout.resources.getDimensionPixelSize( + R.dimen.keyguard_affordance_fixed_padding + ) + val view = + LaunchableImageView(constraintLayout.context, null).apply { + id = R.id.end_button + scaleType = ImageView.ScaleType.FIT_CENTER + background = + ResourcesCompat.getDrawable( + context.resources, + R.drawable.keyguard_bottom_affordance_bg, + context.theme + ) + foreground = + ResourcesCompat.getDrawable( + context.resources, + R.drawable.keyguard_bottom_affordance_selected_border, + context.theme + ) + visibility = View.INVISIBLE + setPadding(padding, padding, padding, padding) + } + constraintLayout.addView(view) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt index abf25a23439f..f8455c53a51f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt @@ -17,7 +17,10 @@ package com.android.systemui.keyguard.ui.view.layout.sections +import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.END @@ -28,13 +31,44 @@ import androidx.constraintlayout.widget.ConstraintSet.TOP import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.R -import com.android.systemui.keyguard.data.repository.KeyguardSection +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.keyguard.ui.binder.KeyguardAmbientIndicationAreaViewBinder +import com.android.systemui.keyguard.ui.viewmodel.KeyguardAmbientIndicationViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import javax.inject.Inject class DefaultAmbientIndicationAreaSection @Inject -constructor(private val keyguardUpdateMonitor: KeyguardUpdateMonitor) : KeyguardSection { - override fun apply(constraintSet: ConstraintSet) { +constructor( + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val featureFlags: FeatureFlags, + private val keyguardAmbientIndicationViewModel: KeyguardAmbientIndicationViewModel, + private val keyguardRootViewModel: KeyguardRootViewModel, +) : KeyguardSection { + private var ambientIndicationAreaHandle: KeyguardAmbientIndicationAreaViewBinder.Binding? = null + + override fun addViews(constraintLayout: ConstraintLayout) { + if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { + if (constraintLayout.findViewById<View>(R.id.ambient_indication_container) == null) { + val view = + LayoutInflater.from(constraintLayout.context) + .inflate(R.layout.ambient_indication, constraintLayout, false) + + constraintLayout.addView(view) + } + + ambientIndicationAreaHandle = + KeyguardAmbientIndicationAreaViewBinder.bind( + constraintLayout, + keyguardAmbientIndicationViewModel, + keyguardRootViewModel, + ) + } + } + + override fun applyConstraints(constraintSet: ConstraintSet) { constraintSet.apply { constrainWidth(R.id.ambient_indication_container, MATCH_PARENT) @@ -59,4 +93,8 @@ constructor(private val keyguardUpdateMonitor: KeyguardUpdateMonitor) : Keyguard } } } + + override fun onDestroy() { + ambientIndicationAreaHandle?.destroy() + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt index dee7ed570b05..f04bfc675f1f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt @@ -18,17 +18,53 @@ package com.android.systemui.keyguard.ui.view.layout.sections import android.content.Context +import android.view.View import android.view.ViewGroup +import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import com.android.systemui.R -import com.android.systemui.keyguard.data.repository.KeyguardSection +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder +import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea +import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel +import com.android.systemui.statusbar.KeyguardIndicationController import javax.inject.Inject +import kotlinx.coroutines.DisposableHandle -class DefaultIndicationAreaSection @Inject constructor(private val context: Context) : - KeyguardSection { +class DefaultIndicationAreaSection +@Inject +constructor( + private val context: Context, + private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel, + private val keyguardRootViewModel: KeyguardRootViewModel, + private val indicationController: KeyguardIndicationController, + private val featureFlags: FeatureFlags, +) : KeyguardSection { private val indicationAreaViewId = R.id.keyguard_indication_area + private var indicationAreaHandle: DisposableHandle? = null - override fun apply(constraintSet: ConstraintSet) { + override fun addViews(constraintLayout: ConstraintLayout) { + if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { + if (constraintLayout.findViewById<View>(indicationAreaViewId) == null) { + val view = KeyguardIndicationArea(context, null) + constraintLayout.addView(view) + } + + indicationAreaHandle = + KeyguardIndicationAreaBinder.bind( + constraintLayout, + keyguardIndicationAreaViewModel, + keyguardRootViewModel, + indicationController, + featureFlags, + ) + } + } + + override fun applyConstraints(constraintSet: ConstraintSet) { constraintSet.apply { constrainWidth(indicationAreaViewId, ViewGroup.LayoutParams.MATCH_PARENT) constrainHeight(indicationAreaViewId, ViewGroup.LayoutParams.WRAP_CONTENT) @@ -53,4 +89,8 @@ class DefaultIndicationAreaSection @Inject constructor(private val context: Cont ) } } + + override fun onDestroy() { + indicationAreaHandle?.dispose() + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt index 461faec217ca..3d62f3f1f985 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt @@ -21,13 +21,20 @@ import android.content.Context import android.graphics.Point import android.graphics.Rect import android.util.DisplayMetrics +import android.view.View import android.view.WindowManager import androidx.annotation.VisibleForTesting +import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.LockIconView +import com.android.keyguard.LockIconViewController import com.android.systemui.R import com.android.systemui.biometrics.AuthController -import com.android.systemui.keyguard.data.repository.KeyguardSection +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.shade.NotificationPanelView import javax.inject.Inject class DefaultLockIconSection @@ -37,16 +44,30 @@ constructor( private val authController: AuthController, private val windowManager: WindowManager, private val context: Context, + private val notificationPanelView: NotificationPanelView, + private val featureFlags: FeatureFlags, + private val lockIconViewController: LockIconViewController, ) : KeyguardSection { private val lockIconViewId = R.id.lock_icon_view - override fun apply(constraintSet: ConstraintSet) { + override fun addViews(constraintLayout: ConstraintLayout) { + if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) { + notificationPanelView.findViewById<View>(R.id.lock_icon_view).let { + notificationPanelView.removeView(it) + } + if (constraintLayout.findViewById<View>(R.id.lock_icon_view) == null) { + val view = LockIconView(context, null).apply { id = R.id.lock_icon_view } + constraintLayout.addView(view) + lockIconViewController.setLockIconView(view) + } + } + } + + override fun applyConstraints(constraintSet: ConstraintSet) { val isUdfpsSupported = keyguardUpdateMonitor.isUdfpsSupported val scaleFactor: Float = authController.scaleFactor val mBottomPaddingPx = context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom) - val mDefaultPaddingPx = context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) - val scaledPadding: Int = (mDefaultPaddingPx * scaleFactor).toInt() val bounds = windowManager.currentWindowMetrics.bounds val widthPixels = bounds.right.toFloat() val heightPixels = bounds.bottom.toFloat() @@ -57,12 +78,7 @@ constructor( if (isUdfpsSupported) { authController.udfpsLocation?.let { udfpsLocation -> - centerLockIcon( - udfpsLocation, - authController.udfpsRadius, - scaledPadding, - constraintSet - ) + centerLockIcon(udfpsLocation, authController.udfpsRadius, constraintSet) } } else { centerLockIcon( @@ -71,19 +87,13 @@ constructor( (heightPixels - ((mBottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt() ), lockIconRadiusPx * scaleFactor, - scaledPadding, constraintSet, ) } } @VisibleForTesting - internal fun centerLockIcon( - center: Point, - radius: Float, - drawablePadding: Int, - constraintSet: ConstraintSet - ) { + internal fun centerLockIcon(center: Point, radius: Float, constraintSet: ConstraintSet) { val sensorRect = Rect().apply { set( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt new file mode 100644 index 000000000000..a203e41db86e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.view.layout.sections + +import android.view.View +import android.view.ViewGroup +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.R +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.shade.NotificationPanelView +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer +import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel +import javax.inject.Inject + +class DefaultNotificationStackScrollLayoutSection +@Inject +constructor( + private val featureFlags: FeatureFlags, + private val notificationPanelView: NotificationPanelView, + private val sharedNotificationContainer: SharedNotificationContainer, + private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, + private val controller: NotificationStackScrollLayoutController, +) : KeyguardSection { + override fun addViews(constraintLayout: ConstraintLayout) { + if (featureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + // This moves the existing NSSL view to a different parent, as the controller is a + // singleton and recreating it has other bad side effects + notificationPanelView.findViewById<View?>(R.id.notification_stack_scroller)?.let { + (it.parent as ViewGroup).removeView(it) + sharedNotificationContainer.addNotificationStackScrollLayout(it) + SharedNotificationContainerBinder.bind( + sharedNotificationContainer, + sharedNotificationContainerViewModel, + controller, + ) + } + } + } + + override fun applyConstraints(constraintSet: ConstraintSet) {} +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt index ad1e4f8b98b4..660cc96f21e6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt @@ -18,20 +18,65 @@ package com.android.systemui.keyguard.ui.view.layout.sections import android.content.res.Resources +import android.view.LayoutInflater +import android.view.View +import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.END import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT +import androidx.core.view.isVisible import com.android.systemui.R +import com.android.systemui.animation.view.LaunchableLinearLayout import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.keyguard.data.repository.KeyguardSection +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder +import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.statusbar.VibratorHelper import javax.inject.Inject +import kotlinx.coroutines.DisposableHandle -class DefaultSettingsPopupMenuSection @Inject constructor(@Main private val resources: Resources) : - KeyguardSection { - override fun apply(constraintSet: ConstraintSet) { +class DefaultSettingsPopupMenuSection +@Inject +constructor( + @Main private val resources: Resources, + private val featureFlags: FeatureFlags, + private val keyguardSettingsMenuViewModel: KeyguardSettingsMenuViewModel, + private val vibratorHelper: VibratorHelper, + private val activityStarter: ActivityStarter, +) : KeyguardSection { + private var settingsPopupMenuHandle: DisposableHandle? = null + + override fun addViews(constraintLayout: ConstraintLayout) { + if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { + if (constraintLayout.findViewById<View?>(R.id.keyguard_settings_button) == null) { + val view = + LayoutInflater.from(constraintLayout.context) + .inflate(R.layout.keyguard_settings_popup_menu, constraintLayout, false) + .apply { + id = R.id.keyguard_settings_button + isVisible = false + alpha = 0f + } as LaunchableLinearLayout + constraintLayout.addView(view) + } + + settingsPopupMenuHandle = + KeyguardSettingsViewBinder.bind( + constraintLayout.requireViewById<View>(R.id.keyguard_settings_button), + keyguardSettingsMenuViewModel, + vibratorHelper, + activityStarter, + ) + } + } + + override fun applyConstraints(constraintSet: ConstraintSet) { val horizontalOffsetMargin = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_horizontal_offset) @@ -51,6 +96,11 @@ class DefaultSettingsPopupMenuSection @Inject constructor(@Main private val reso BOTTOM, resources.getDimensionPixelSize(R.dimen.keyguard_affordance_vertical_offset) ) + setVisibility(R.id.keyguard_settings_button, View.GONE) } } + + override fun onDestroy() { + settingsPopupMenuHandle?.dispose() + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt index db4653defd34..965910a1be66 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.view.layout.sections import android.content.res.Resources +import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.LEFT @@ -25,12 +26,58 @@ import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.RIGHT import com.android.systemui.R import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.keyguard.data.repository.KeyguardSection +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder +import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.statusbar.KeyguardIndicationController +import com.android.systemui.statusbar.VibratorHelper import javax.inject.Inject -class DefaultShortcutsSection @Inject constructor(@Main private val resources: Resources) : - KeyguardSection { - override fun apply(constraintSet: ConstraintSet) { +class DefaultShortcutsSection +@Inject +constructor( + @Main private val resources: Resources, + private val featureFlags: FeatureFlags, + private val keyguardQuickAffordancesCombinedViewModel: + KeyguardQuickAffordancesCombinedViewModel, + private val keyguardRootViewModel: KeyguardRootViewModel, + private val falsingManager: FalsingManager, + private val indicationController: KeyguardIndicationController, + private val vibratorHelper: VibratorHelper, +) : BaseShortcutsSection(), KeyguardSection { + + override fun addViews(constraintLayout: ConstraintLayout) { + if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { + addLeftShortcut(constraintLayout) + addRightShortcut(constraintLayout) + leftShortcutHandle = + KeyguardQuickAffordanceViewBinder.bind( + constraintLayout.requireViewById(R.id.start_button), + keyguardQuickAffordancesCombinedViewModel.startButton, + keyguardRootViewModel.alpha, + falsingManager, + vibratorHelper, + ) { + indicationController.showTransientIndication(it) + } + rightShortcutHandle = + KeyguardQuickAffordanceViewBinder.bind( + constraintLayout.requireViewById(R.id.end_button), + keyguardQuickAffordancesCombinedViewModel.endButton, + keyguardRootViewModel.alpha, + falsingManager, + vibratorHelper, + ) { + indicationController.showTransientIndication(it) + } + } + } + + override fun applyConstraints(constraintSet: ConstraintSet) { val width = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width) val height = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height) val horizontalOffsetMargin = @@ -50,4 +97,15 @@ class DefaultShortcutsSection @Inject constructor(@Main private val resources: R connect(R.id.end_button, BOTTOM, PARENT_ID, BOTTOM, verticalOffsetMargin) } } + + /** Method to add shortcuts without applying any data binding. */ + fun addShortcutViews(constraintLayout: ConstraintLayout) { + addLeftShortcut(constraintLayout) + addRightShortcut(constraintLayout) + ConstraintSet().apply { + clone(constraintLayout) + applyConstraints(this) + applyTo(constraintLayout) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt index f1f59732b121..321d7a70b087 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt @@ -18,23 +18,76 @@ package com.android.systemui.keyguard.ui.view.layout.sections import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.END import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP +import com.android.keyguard.KeyguardStatusView +import com.android.keyguard.dagger.KeyguardStatusViewComponent import com.android.systemui.R -import com.android.systemui.keyguard.data.repository.KeyguardSection +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.KeyguardViewConfigurator +import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.media.controls.ui.KeyguardMediaController +import com.android.systemui.shade.NotificationPanelView +import com.android.systemui.shade.NotificationPanelViewController import com.android.systemui.util.LargeScreenUtils import com.android.systemui.util.Utils +import dagger.Lazy import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi -class DefaultStatusViewSection @Inject constructor(private val context: Context) : KeyguardSection { +class DefaultStatusViewSection +@Inject +constructor( + private val context: Context, + private val featureFlags: FeatureFlags, + private val notificationPanelView: NotificationPanelView, + private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory, + private val keyguardViewConfigurator: Lazy<KeyguardViewConfigurator>, + private val notificationPanelViewController: Lazy<NotificationPanelViewController>, + private val keyguardMediaController: KeyguardMediaController, +) : KeyguardSection { private val statusViewId = R.id.keyguard_status_view - override fun apply(constraintSet: ConstraintSet) { + @OptIn(ExperimentalCoroutinesApi::class) + override fun addViews(constraintLayout: ConstraintLayout) { + // At startup, 2 views with the ID `R.id.keyguard_status_view` will be available. + // Disable one of them + if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + notificationPanelView.findViewById<View>(statusViewId)?.let { + notificationPanelView.removeView(it) + } + if (constraintLayout.findViewById<View>(statusViewId) == null) { + val keyguardStatusView = + (LayoutInflater.from(context) + .inflate(R.layout.keyguard_status_view, constraintLayout, false) + as KeyguardStatusView) + .apply { clipChildren = false } + + val statusViewComponent = + keyguardStatusViewComponentFactory.build(keyguardStatusView) + val controller = statusViewComponent.keyguardStatusViewController + controller.init() + constraintLayout.addView(keyguardStatusView) + keyguardMediaController.attachSplitShadeContainer( + keyguardStatusView.requireViewById<ViewGroup>(R.id.status_view_media_container) + ) + keyguardViewConfigurator.get().keyguardStatusViewController = controller + notificationPanelViewController.get().updateStatusBarViewController() + } + } + } + + override fun applyConstraints(constraintSet: ConstraintSet) { constraintSet.apply { constrainWidth(statusViewId, MATCH_CONSTRAINT) constrainHeight(statusViewId, WRAP_CONTENT) @@ -52,4 +105,9 @@ class DefaultStatusViewSection @Inject constructor(private val context: Context) setMargin(statusViewId, TOP, margin) } } + + @OptIn(ExperimentalCoroutinesApi::class) + override fun onDestroy() { + keyguardViewConfigurator.get().keyguardStatusViewController = null + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeGuidelines.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeGuidelines.kt index 668b17ffeba0..bd629d512614 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeGuidelines.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeGuidelines.kt @@ -18,23 +18,17 @@ package com.android.systemui.keyguard.ui.view.layout.sections import android.content.Context -import android.view.ViewGroup +import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet +import androidx.constraintlayout.widget.ConstraintSet.VERTICAL import com.android.systemui.R -import com.android.systemui.keyguard.data.repository.KeyguardSection +import com.android.systemui.keyguard.shared.model.KeyguardSection import javax.inject.Inject -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT -import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT -import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID -import androidx.constraintlayout.widget.ConstraintSet.START -import androidx.constraintlayout.widget.ConstraintSet.TOP -import androidx.constraintlayout.widget.ConstraintSet.END -import androidx.constraintlayout.widget.ConstraintSet.VERTICAL -class SplitShadeGuidelines @Inject constructor(private val context: Context) : - KeyguardSection { +class SplitShadeGuidelines @Inject constructor(private val context: Context) : KeyguardSection { + override fun addViews(constraintLayout: ConstraintLayout) {} - override fun apply(constraintSet: ConstraintSet) { + override fun applyConstraints(constraintSet: ConstraintSet) { constraintSet.apply { // For use on large screens, it will provide a guideline vertically in the center to // enable items to be aligned on the left or right sides diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt index 5e9e553616dc..e2bfc36aee37 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt @@ -17,13 +17,13 @@ package com.android.systemui.keyguard.ui.viewmodel -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor +import com.android.systemui.keyguard.shared.model.KeyguardBlueprint import javax.inject.Inject -@SysUISingleton class KeyguardBlueprintViewModel @Inject constructor(keyguardBlueprintInteractor: KeyguardBlueprintInteractor) { + var currentBluePrint: KeyguardBlueprint? = null val blueprint = keyguardBlueprintInteractor.blueprint } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt index 93c4902332c5..05c932372ccf 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt @@ -17,7 +17,6 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor -import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.shared.model.SceneKey import javax.inject.Inject @@ -30,7 +29,7 @@ class LockscreenSceneViewModel @Inject constructor( authenticationInteractor: AuthenticationInteractor, - private val bouncerInteractor: BouncerInteractor, + val longPress: KeyguardLongPressViewModel, ) { /** The key of the scene we should switch to when swiping up. */ val upDestinationSceneKey: Flow<SceneKey> = @@ -41,9 +40,4 @@ constructor( SceneKey.Bouncer } } - - /** Notifies that the lock button on the lock screen was clicked. */ - fun onLockButtonClicked() { - bouncerInteractor.showOrUnlockDevice() - } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt index 8f884d24ad21..053c9b56ef96 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt @@ -206,6 +206,11 @@ class MediaProjectionAppSelectorActivity( override fun bind(recentTasks: List<RecentTask>) { recentsViewController.bind(recentTasks) + if (!hasWorkProfile()) { + // Make sure to refresh the adapter, to show/hide the recents view depending on whether + // there are recents or not. + mMultiProfilePagerAdapter.personalListAdapter.notifyDataSetChanged() + } } override fun returnSelectedApp(launchCookie: IBinder) { @@ -248,9 +253,20 @@ class MediaProjectionAppSelectorActivity( override fun shouldGetOnlyDefaultActivities() = false - override fun shouldShowContentPreview() = true + override fun shouldShowContentPreview() = + if (hasWorkProfile()) { + // When the user has a work profile, we can always set this to true, and the layout is + // adjusted automatically, and hide the recents view. + true + } else { + // When there is no work profile, we should only show the content preview if there are + // recents, otherwise the collapsed app selector will look empty. + recentsViewController.hasRecentTasks + } + + override fun shouldShowContentPreviewWhenEmpty() = shouldShowContentPreview() - override fun shouldShowContentPreviewWhenEmpty(): Boolean = true + private fun hasWorkProfile() = mMultiProfilePagerAdapter.count > 1 override fun createMyUserIdProvider(): MyUserIdProvider = object : MyUserIdProvider() { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt index 398dcf260dff..38a6a8f3470b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt @@ -39,7 +39,6 @@ import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.Dumpable import com.android.systemui.R -import com.android.systemui.classifier.FalsingCollector import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager @@ -59,6 +58,11 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.PageIndicator import com.android.systemui.shared.system.SysUiStatsLog +import com.android.systemui.shared.system.SysUiStatsLog.SMARTSPACE_CARD_REPORTED +import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD +import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY as SSPACE_CARD_REPORTED__DREAM_OVERLAY +import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN as SSPACE_CARD_REPORTED__LOCKSCREEN +import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider import com.android.systemui.statusbar.policy.ConfigurationController @@ -100,7 +104,6 @@ constructor( @Main executor: DelayableExecutor, private val mediaManager: MediaDataManager, configurationController: ConfigurationController, - falsingCollector: FalsingCollector, falsingManager: FalsingManager, dumpManager: DumpManager, private val logger: MediaUiEventLogger, @@ -122,6 +125,7 @@ constructor( /** Is the player currently visible (at the end of the transformation */ private var playersVisible: Boolean = false + /** * The desired location where we'll be at the end of the transformation. Usually this matches * the end location, except when we're still waiting on a state update call. @@ -152,6 +156,7 @@ constructor( @VisibleForTesting var mediaCarousel: MediaScrollView val mediaCarouselScrollHandler: MediaCarouselScrollHandler val mediaFrame: ViewGroup + @VisibleForTesting lateinit var settingsButton: View private set @@ -280,7 +285,6 @@ constructor( this::updatePageIndicatorLocation, this::updateSeekbarListening, this::closeGuts, - falsingCollector, falsingManager, this::logSmartspaceImpression, logger @@ -327,23 +331,18 @@ constructor( if (addOrUpdatePlayer(key, oldKey, data, isSsReactivated)) { // Log card received if a new resumable media card is added MediaPlayerData.getMediaPlayer(key)?.let { - /* ktlint-disable max-line-length */ logSmartspaceCardReported( 759, // SMARTSPACE_CARD_RECEIVED it.mSmartspaceId, it.mUid, surfaces = intArrayOf( - SysUiStatsLog - .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, - SysUiStatsLog - .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN, - SysUiStatsLog - .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY + SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, + SSPACE_CARD_REPORTED__LOCKSCREEN, + SSPACE_CARD_REPORTED__DREAM_OVERLAY, ), rank = MediaPlayerData.getMediaPlayerIndex(key) ) - /* ktlint-disable max-line-length */ } if ( mediaCarouselScrollHandler.visibleToUser && @@ -362,24 +361,20 @@ constructor( it.mUid + systemClock.currentTimeMillis().toInt() ) it.mIsImpressed = false - /* ktlint-disable max-line-length */ + logSmartspaceCardReported( 759, // SMARTSPACE_CARD_RECEIVED it.mSmartspaceId, it.mUid, surfaces = intArrayOf( - SysUiStatsLog - .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, - SysUiStatsLog - .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN, - SysUiStatsLog - .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY + SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, + SSPACE_CARD_REPORTED__LOCKSCREEN, + SSPACE_CARD_REPORTED__DREAM_OVERLAY, ), rank = index, receivedLatencyMillis = receivedSmartspaceCardLatency ) - /* ktlint-disable max-line-length */ } } // If media container area already visible to the user, log impression for @@ -431,19 +426,16 @@ constructor( it.mUid + systemClock.currentTimeMillis().toInt() ) it.mIsImpressed = false - /* ktlint-disable max-line-length */ + logSmartspaceCardReported( 759, // SMARTSPACE_CARD_RECEIVED it.mSmartspaceId, it.mUid, surfaces = intArrayOf( - SysUiStatsLog - .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, - SysUiStatsLog - .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN, - SysUiStatsLog - .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY + SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, + SSPACE_CARD_REPORTED__LOCKSCREEN, + SSPACE_CARD_REPORTED__DREAM_OVERLAY, ), rank = index, receivedLatencyMillis = @@ -451,25 +443,20 @@ constructor( data.headphoneConnectionTimeMillis) .toInt() ) - /* ktlint-disable max-line-length */ } } } addSmartspaceMediaRecommendations(key, data, shouldPrioritize) MediaPlayerData.getMediaPlayer(key)?.let { - /* ktlint-disable max-line-length */ logSmartspaceCardReported( 759, // SMARTSPACE_CARD_RECEIVED it.mSmartspaceId, it.mUid, surfaces = intArrayOf( - SysUiStatsLog - .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, - SysUiStatsLog - .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN, - SysUiStatsLog - .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY + SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, + SSPACE_CARD_REPORTED__LOCKSCREEN, + SSPACE_CARD_REPORTED__DREAM_OVERLAY, ), rank = MediaPlayerData.getMediaPlayerIndex(key), receivedLatencyMillis = @@ -477,7 +464,6 @@ constructor( data.headphoneConnectionTimeMillis) .toInt() ) - /* ktlint-disable max-line-length */ } if ( mediaCarouselScrollHandler.visibleToUser && @@ -560,7 +546,10 @@ constructor( mediaCarouselScrollHandler.onSettingsButtonUpdated(settings) settingsButton.setOnClickListener { logger.logCarouselSettings() - activityStarter.startActivity(settingsIntent, true /* dismissShade */) + activityStarter.startActivity( + settingsIntent, + /* dismissShade= */ true, + ) } } @@ -1108,7 +1097,6 @@ constructor( } } - @JvmOverloads /** * Log Smartspace events * @@ -1127,6 +1115,7 @@ constructor( * between headphone connection to sysUI displays media recommendation card * @param isSwipeToDismiss whether is to log swipe-to-dismiss event */ + @JvmOverloads fun logSmartspaceCardReported( eventId: Int, instanceId: Int, @@ -1154,21 +1143,24 @@ constructor( val cardinality = mediaContent.getChildCount() surfaces.forEach { surface -> - /* ktlint-disable max-line-length */ SysUiStatsLog.write( - SysUiStatsLog.SMARTSPACE_CARD_REPORTED, + SMARTSPACE_CARD_REPORTED, eventId, instanceId, // Deprecated, replaced with AiAi feature type so we don't need to create logging // card type for each new feature. - SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD, + SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD, surface, // Use -1 as rank value to indicate user swipe to dismiss the card if (isSwipeToDismiss) -1 else rank, cardinality, - if (mediaControlKey.isSsMediaRec) 15 // MEDIA_RECOMMENDATION - else if (mediaControlKey.isSsReactivated) 43 // MEDIA_RESUME_SS_ACTIVATED - else 31, // MEDIA_RESUME + if (mediaControlKey.isSsMediaRec) { + 15 // MEDIA_RECOMMENDATION + } else if (mediaControlKey.isSsReactivated) { + 43 // MEDIA_RESUME_SS_ACTIVATED + } else { + 31 + }, // MEDIA_RESUME uid, interactedSubcardRank, interactedSubcardCardinality, @@ -1176,7 +1168,7 @@ constructor( null, // Media cards cannot have subcards. null // Media cards don't have dimensions today. ) - /* ktlint-disable max-line-length */ + if (DEBUG) { Log.d( TAG, @@ -1259,6 +1251,7 @@ internal object MediaPlayerData { instanceId = InstanceId.fakeInstanceId(-1), appUid = -1 ) + // Whether should prioritize Smartspace card. internal var shouldPrioritizeSs: Boolean = false private set @@ -1291,6 +1284,7 @@ internal object MediaPlayerData { private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator) private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf() + // A map that tracks order of visible media players before they get reordered. private val visibleMediaPlayers = LinkedHashMap<String, MediaSortKey>() diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt index ce50a11cd85d..bbb61b4d1745 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt @@ -30,7 +30,6 @@ import com.android.settingslib.Utils import com.android.systemui.Gefingerpoken import com.android.systemui.R import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS -import com.android.systemui.classifier.FalsingCollector import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.PageIndicator @@ -59,7 +58,6 @@ class MediaCarouselScrollHandler( private var translationChangedListener: () -> Unit, private var seekBarUpdateListener: (visibleToUser: Boolean) -> Unit, private val closeGuts: (immediate: Boolean) -> Unit, - private val falsingCollector: FalsingCollector, private val falsingManager: FalsingManager, private val logSmartspaceImpression: (Boolean) -> Unit, private val logger: MediaUiEventLogger @@ -67,8 +65,10 @@ class MediaCarouselScrollHandler( /** Is the view in RTL */ val isRtl: Boolean get() = scrollView.isLayoutRtl + /** Do we need falsing protection? */ var falsingProtectionNeeded: Boolean = false + /** The width of the carousel */ private var carouselWidth: Int = 0 @@ -80,6 +80,7 @@ class MediaCarouselScrollHandler( /** The content where the players are added */ private var mediaContent: ViewGroup + /** The gesture detector to detect touch gestures */ private val gestureDetector: GestureDetectorCompat @@ -140,9 +141,6 @@ class MediaCarouselScrollHandler( ) = onScroll(down!!, lastMotion, distanceX) override fun onDown(e: MotionEvent): Boolean { - if (falsingProtectionNeeded) { - falsingCollector.onNotificationStartDismissing() - } return false } } @@ -263,9 +261,6 @@ class MediaCarouselScrollHandler( private fun onTouch(motionEvent: MotionEvent): Boolean { val isUp = motionEvent.action == MotionEvent.ACTION_UP - if (isUp && falsingProtectionNeeded) { - falsingCollector.onNotificationStopDismissing() - } if (gestureDetector.onTouchEvent(motionEvent)) { if (isUp) { // If this is an up and we're flinging, we don't want to have this touch reach @@ -482,8 +477,11 @@ class MediaCarouselScrollHandler( } val relativeLocation = visibleMediaIndex.toFloat() + - if (playerWidthPlusPadding > 0) scrollInAmount.toFloat() / playerWidthPlusPadding - else 0f + if (playerWidthPlusPadding > 0) { + scrollInAmount.toFloat() / playerWidthPlusPadding + } else { + 0f + } // Fix the location, because PageIndicator does not handle RTL internally val location = if (isRtl) { 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 64de9bd76122..5d732fb8ace9 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 @@ -35,8 +35,8 @@ import com.android.systemui.util.recycler.HorizontalSpacerItemDecoration import javax.inject.Inject /** - * Controller that handles view of the recent apps selector in the media projection activity. - * It is responsible for creating and updating recent apps view. + * Controller that handles view of the recent apps selector in the media projection activity. It is + * responsible for creating and updating recent apps view. */ @MediaProjectionAppSelectorScope class MediaProjectionRecentsViewController @@ -51,15 +51,21 @@ constructor( private var views: Views? = null private var lastBoundData: List<RecentTask>? = null + val hasRecentTasks: Boolean + get() = lastBoundData?.isNotEmpty() ?: false + init { taskViewSizeProvider.addCallback(this) } fun createView(parent: ViewGroup): ViewGroup = - views?.root ?: createRecentViews(parent).also { - views = it - lastBoundData?.let { recents -> bind(recents) } - }.root + views?.root + ?: createRecentViews(parent) + .also { + views = it + lastBoundData?.let { recents -> bind(recents) } + } + .root fun bind(recentTasks: List<RecentTask>) { views?.apply { @@ -88,7 +94,8 @@ constructor( .inflate(R.layout.media_projection_recent_tasks, parent, /* attachToRoot= */ false) as ViewGroup - val container = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_container) + val container = + recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_container) container.setTaskHeightSize() val progress = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_loader) diff --git a/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt b/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt index 69cb6119580b..b2a87197371d 100644 --- a/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt @@ -39,6 +39,9 @@ interface PowerRepository { /** Wakes up the device. */ fun wakeUp(why: String, @PowerManager.WakeReason wakeReason: Int) + + /** Notifies the power repository that a user touch happened. */ + fun userTouch() } @SysUISingleton @@ -83,6 +86,14 @@ constructor( ) } + override fun userTouch() { + manager.userActivity( + systemClock.uptimeMillis(), + PowerManager.USER_ACTIVITY_EVENT_TOUCH, + /* flags= */ 0, + ) + } + companion object { private const val TAG = "PowerRepository" } diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt index 809edc09070a..16885ed4b1d5 100644 --- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt @@ -19,6 +19,7 @@ package com.android.systemui.power.domain.interactor import android.os.PowerManager import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.classifier.FalsingCollectorActual import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -34,7 +35,7 @@ class PowerInteractor constructor( private val repository: PowerRepository, private val keyguardRepository: KeyguardRepository, - private val falsingCollector: FalsingCollector, + @FalsingCollectorActual private val falsingCollector: FalsingCollector, private val screenOffAnimationController: ScreenOffAnimationController, private val statusBarStateController: StatusBarStateController, ) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index 54376fe604df..e9a242847362 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -18,6 +18,8 @@ package com.android.systemui.qs.tiles; import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY; +import static com.android.systemui.flags.Flags.SIGNAL_CALLBACK_DEPRECATION; + import android.annotation.NonNull; import android.app.Dialog; import android.content.Intent; @@ -42,6 +44,7 @@ import com.android.systemui.animation.DialogCuj; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; @@ -54,6 +57,8 @@ import com.android.systemui.statusbar.connectivity.NetworkController; import com.android.systemui.statusbar.connectivity.SignalCallback; import com.android.systemui.statusbar.connectivity.WifiIndicators; import com.android.systemui.statusbar.phone.SystemUIDialog; +import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor; +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.CastController.CastDevice; import com.android.systemui.statusbar.policy.HotspotController; @@ -61,6 +66,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; import javax.inject.Inject; @@ -79,6 +85,9 @@ public class CastTile extends QSTileImpl<BooleanState> { private final NetworkController mNetworkController; private final DialogLaunchAnimator mDialogLaunchAnimator; private final Callback mCallback = new Callback(); + private final WifiInteractor mWifiInteractor; + private final TileJavaAdapter mJavaAdapter; + private final FeatureFlags mFeatureFlags; private boolean mWifiConnected; private boolean mHotspotConnected; @@ -97,7 +106,10 @@ public class CastTile extends QSTileImpl<BooleanState> { KeyguardStateController keyguardStateController, NetworkController networkController, HotspotController hotspotController, - DialogLaunchAnimator dialogLaunchAnimator + DialogLaunchAnimator dialogLaunchAnimator, + WifiInteractor wifiInteractor, + TileJavaAdapter javaAdapter, + FeatureFlags featureFlags ) { super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); @@ -105,9 +117,16 @@ public class CastTile extends QSTileImpl<BooleanState> { mKeyguard = keyguardStateController; mNetworkController = networkController; mDialogLaunchAnimator = dialogLaunchAnimator; + mWifiInteractor = wifiInteractor; + mJavaAdapter = javaAdapter; + mFeatureFlags = featureFlags; mController.observe(this, mCallback); mKeyguard.observe(this, mCallback); - mNetworkController.observe(this, mSignalCallback); + if (!mFeatureFlags.isEnabled(SIGNAL_CALLBACK_DEPRECATION)) { + mNetworkController.observe(this, mSignalCallback); + } else { + mJavaAdapter.bind(this, mWifiInteractor.getWifiNetwork(), mNetworkModelConsumer); + } hotspotController.observe(this, mHotspotCallback); } @@ -293,19 +312,37 @@ public class CastTile extends QSTileImpl<BooleanState> { return mWifiConnected || mHotspotConnected; } + private void setWifiConnected(boolean connected) { + if (connected != mWifiConnected) { + mWifiConnected = connected; + // Hotspot is not connected, so changes here should update + if (!mHotspotConnected) { + refreshState(); + } + } + } + + private void setHotspotConnected(boolean connected) { + if (connected != mHotspotConnected) { + mHotspotConnected = connected; + // Wifi is not connected, so changes here should update + if (!mWifiConnected) { + refreshState(); + } + } + } + + private final Consumer<WifiNetworkModel> mNetworkModelConsumer = (model) -> { + setWifiConnected(model instanceof WifiNetworkModel.Active); + }; + private final SignalCallback mSignalCallback = new SignalCallback() { @Override public void setWifiIndicators(@NonNull WifiIndicators indicators) { // statusIcon.visible has the connected status information boolean enabledAndConnected = indicators.enabled - && (indicators.qsIcon == null ? false : indicators.qsIcon.visible); - if (enabledAndConnected != mWifiConnected) { - mWifiConnected = enabledAndConnected; - // Hotspot is not connected, so changes here should update - if (!mHotspotConnected) { - refreshState(); - } - } + && (indicators.qsIcon != null && indicators.qsIcon.visible); + setWifiConnected(enabledAndConnected); } }; @@ -314,13 +351,7 @@ public class CastTile extends QSTileImpl<BooleanState> { @Override public void onHotspotChanged(boolean enabled, int numDevices) { boolean enabledAndConnected = enabled && numDevices > 0; - if (enabledAndConnected != mHotspotConnected) { - mHotspotConnected = enabledAndConnected; - // Wifi is not connected, so changes here should update - if (!mWifiConnected) { - refreshState(); - } - } + setHotspotConnected(enabledAndConnected); } }; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt new file mode 100644 index 000000000000..3b2f8b772d60 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles + +import android.content.Context +import android.content.Intent +import android.os.Handler +import android.os.Looper +import android.provider.Settings +import android.view.View +import com.android.internal.logging.MetricsLogger +import com.android.systemui.R +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.plugins.qs.QSIconView +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.qs.AlphaControlledSignalTileView +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.qs.tiles.dialog.InternetDialogFactory +import com.android.systemui.statusbar.connectivity.AccessPointController +import com.android.systemui.statusbar.pipeline.shared.ui.binder.InternetTileBinder +import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileModel +import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.InternetTileViewModel +import javax.inject.Inject + +class InternetTileNewImpl +@Inject +constructor( + host: QSHost, + uiEventLogger: QsEventLogger, + @Background backgroundLooper: Looper, + @Main private val mainHandler: Handler, + falsingManager: FalsingManager, + metricsLogger: MetricsLogger, + statusBarStateController: StatusBarStateController, + activityStarter: ActivityStarter, + qsLogger: QSLogger, + viewModel: InternetTileViewModel, + private val internetDialogFactory: InternetDialogFactory, + private val accessPointController: AccessPointController, +) : + QSTileImpl<QSTile.SignalState>( + host, + uiEventLogger, + backgroundLooper, + mainHandler, + falsingManager, + metricsLogger, + statusBarStateController, + activityStarter, + qsLogger + ) { + private var model: InternetTileModel = viewModel.tileModel.value + + init { + InternetTileBinder.bind(lifecycle, viewModel.tileModel) { newModel -> + model = newModel + refreshState() + } + } + + override fun createTileView(context: Context): QSIconView = + AlphaControlledSignalTileView(context) + + override fun getTileLabel(): CharSequence = + mContext.getString(R.string.quick_settings_internet_label) + + override fun newTileState(): QSTile.SignalState { + return QSTile.SignalState().also { it.forceExpandIcon = true } + } + + override fun handleClick(view: View?) { + mainHandler.post { + internetDialogFactory.create( + aboveStatusBar = true, + accessPointController.canConfigMobileData(), + accessPointController.canConfigWifi(), + view, + ) + } + } + + override fun handleUpdateState(state: QSTile.SignalState, arg: Any?) { + state.label = mContext.resources.getString(R.string.quick_settings_internet_label) + + model.applyTo(state, mContext) + } + + override fun getLongClickIntent(): Intent = WIFI_SETTINGS + + companion object { + private val WIFI_SETTINGS = Intent(Settings.ACTION_WIFI_SETTINGS) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/TileJavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/TileJavaAdapter.kt new file mode 100644 index 000000000000..a2430ad45274 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/TileJavaAdapter.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.android.systemui.qs.tiles + +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.dagger.SysUISingleton +import java.util.function.Consumer +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch + +/** + * Utility for binding tiles to kotlin flows. Similar to [JavaAdapter] and usable for QS tiles. We + * use [Lifecycle.State.RESUMED] here to match the implementation of [CallbackController.observe] + */ +@SysUISingleton +class TileJavaAdapter @Inject constructor() { + fun <T> bind( + lifecycleOwner: LifecycleOwner, + flow: Flow<T>, + consumer: Consumer<T>, + ) { + lifecycleOwner.lifecycleScope.launch { + lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { + flow.collect { consumer.accept(it) } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepository.kt new file mode 100644 index 000000000000..d833e56a1795 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepository.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.scene.data.repository + +import android.os.RemoteException +import com.android.internal.statusbar.IStatusBarService +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.UiBackground +import java.util.concurrent.Executor +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** Source of truth for the visibility of various parts of the window root view. */ +@SysUISingleton +class WindowRootViewVisibilityRepository +@Inject +constructor( + private val statusBarService: IStatusBarService, + @UiBackground private val uiBgExecutor: Executor, +) { + private val _isLockscreenOrShadeVisible = MutableStateFlow(false) + val isLockscreenOrShadeVisible: StateFlow<Boolean> = _isLockscreenOrShadeVisible.asStateFlow() + + fun setIsLockscreenOrShadeVisible(visible: Boolean) { + _isLockscreenOrShadeVisible.value = visible + } + + /** + * Called when the lockscreen or shade has been shown and can be interacted with so that SysUI + * can notify external services. + */ + fun onLockscreenOrShadeInteractive( + shouldClearNotificationEffects: Boolean, + notificationCount: Int, + ) { + executeServiceCallOnUiBg { + statusBarService.onPanelRevealed(shouldClearNotificationEffects, notificationCount) + } + } + + /** + * Called when the lockscreen or shade no longer can be interactecd with so that SysUI can + * notify external services. + */ + fun onLockscreenOrShadeNotInteractive() { + executeServiceCallOnUiBg { statusBarService.onPanelHidden() } + } + + private fun executeServiceCallOnUiBg(runnable: () -> Unit) { + uiBgExecutor.execute { + try { + runnable.invoke() + } catch (ex: RemoteException) { + // Won't fail unless the world has ended + } + } + } +} 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 a7434c6ac797..45ee7be35ec3 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 @@ -18,6 +18,7 @@ package com.android.systemui.scene.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.power.data.repository.PowerRepository import com.android.systemui.scene.data.repository.SceneContainerRepository import com.android.systemui.scene.shared.logger.SceneLogger import com.android.systemui.scene.shared.model.ObservableTransitionState @@ -44,6 +45,7 @@ class SceneInteractor constructor( @Application applicationScope: CoroutineScope, private val repository: SceneContainerRepository, + private val powerRepository: PowerRepository, private val logger: SceneLogger, ) { @@ -153,6 +155,11 @@ constructor( repository.setTransitionState(transitionState) } + /** Handles a user input event. */ + fun onUserInput() { + powerRepository.userTouch() + } + /** * Notifies that the UI has transitioned sufficiently to the given scene. * @@ -161,7 +168,7 @@ constructor( * Once a transition between one scene and another passes a threshold, the UI invokes this * method to report it, updating the value in [desiredScene] to match what the UI shows. */ - internal fun onSceneChanged(scene: SceneModel, loggingReason: String) { + fun onSceneChanged(scene: SceneModel, loggingReason: String) { updateDesiredScene(scene, loggingReason, logger::logSceneChangeCommitted) } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt new file mode 100644 index 000000000000..16ffcc27a70f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.scene.domain.interactor + +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository +import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.notification.init.NotificationsController +import com.android.systemui.statusbar.policy.HeadsUpManager +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +/** Business logic about the visibility of various parts of the window root view. */ +@SysUISingleton +class WindowRootViewVisibilityInteractor +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val windowRootViewVisibilityRepository: WindowRootViewVisibilityRepository, + private val keyguardRepository: KeyguardRepository, + private val headsUpManager: HeadsUpManager, +) : CoreStartable { + + private var notificationPresenter: NotificationPresenter? = null + private var notificationsController: NotificationsController? = null + + private val isNotifPresenterFullyCollapsed: Boolean + get() = notificationPresenter?.isPresenterFullyCollapsed ?: true + + /** + * True if lockscreen (including AOD) or the shade is visible and false otherwise. Notably, + * false if the bouncer is visible. + * + * TODO(b/297080059): Use [SceneInteractor] as the source of truth if the scene flag is on. + */ + val isLockscreenOrShadeVisible: StateFlow<Boolean> = + windowRootViewVisibilityRepository.isLockscreenOrShadeVisible + + /** + * True if lockscreen (including AOD) or the shade is visible **and** the user is currently + * interacting with the device, false otherwise. Notably, false if the bouncer is visible and + * false if the device is asleep. + */ + val isLockscreenOrShadeVisibleAndInteractive: StateFlow<Boolean> = + combine( + isLockscreenOrShadeVisible, + keyguardRepository.wakefulness, + ) { isKeyguardAodOrShadeVisible, wakefulness -> + isKeyguardAodOrShadeVisible && wakefulness.isDeviceInteractive() + } + .stateIn(scope, SharingStarted.Eagerly, initialValue = false) + + /** + * Sets classes that aren't easily injectable on this class. + * + * TODO(b/277762009): Inject these directly instead. + */ + fun setUp( + presenter: NotificationPresenter?, + notificationsController: NotificationsController?, + ) { + this.notificationPresenter = presenter + this.notificationsController = notificationsController + } + + override fun start() { + scope.launch { + isLockscreenOrShadeVisibleAndInteractive.collect { interactive -> + if (interactive) { + windowRootViewVisibilityRepository.onLockscreenOrShadeInteractive( + getShouldClearNotificationEffects(keyguardRepository.statusBarState.value), + getNotificationLoad(), + ) + } else { + windowRootViewVisibilityRepository.onLockscreenOrShadeNotInteractive() + } + } + } + } + + fun setIsLockscreenOrShadeVisible(visible: Boolean) { + windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(visible) + } + + private fun getShouldClearNotificationEffects(statusBarState: StatusBarState): Boolean { + return !isNotifPresenterFullyCollapsed && + (statusBarState == StatusBarState.SHADE || + statusBarState == StatusBarState.SHADE_LOCKED) + } + + private fun getNotificationLoad(): Int { + return if (headsUpManager.hasPinnedHeadsUp() && isNotifPresenterFullyCollapsed) { + 1 + } else { + notificationsController?.getActiveNotificationsCount() ?: 0 + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 17470998cf74..7f77acc1789a 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -14,11 +14,15 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.scene.domain.startable import com.android.systemui.CoreStartable import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.domain.model.AuthenticationMethodModel +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.classifier.FalsingCollectorActual import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.DisplayId @@ -40,7 +44,12 @@ import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SE import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.distinctUntilChangedBy +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.launch @@ -61,6 +70,7 @@ constructor( private val sysUiState: SysUiState, @DisplayId private val displayId: Int, private val sceneLogger: SceneLogger, + @FalsingCollectorActual private val falsingCollector: FalsingCollector, ) : CoreStartable { override fun start() { @@ -69,6 +79,7 @@ constructor( hydrateVisibility() automaticallySwitchScenes() hydrateSystemUiState() + collectFalsingSignals() } else { sceneLogger.logFrameworkEnabled(isEnabled = false) } @@ -225,6 +236,66 @@ constructor( } } + /** Collects and reports signals into the falsing system. */ + private fun collectFalsingSignals() { + applicationScope.launch { + authenticationInteractor.isLockscreenDismissed.collect { isLockscreenDismissed -> + if (isLockscreenDismissed) { + falsingCollector.onSuccessfulUnlock() + } + } + } + + applicationScope.launch { + keyguardInteractor.isDozing.distinctUntilChanged().collect { isDozing -> + falsingCollector.setShowingAod(isDozing) + } + } + + applicationScope.launch { + keyguardInteractor.isAodAvailable + .flatMapLatest { isAodAvailable -> + if (!isAodAvailable) { + keyguardInteractor.wakefulnessModel + } else { + emptyFlow() + } + } + .map { wakefulnessModel -> + val wakeChange: Boolean? = + when (wakefulnessModel.state) { + WakefulnessState.STARTING_TO_WAKE -> true + WakefulnessState.ASLEEP -> false + else -> null + } + (wakeChange to wakefulnessModel.lastWakeReason).takeIf { wakeChange != null } + } + .filterNotNull() + .distinctUntilChangedBy { it.first } + .collect { (wakeChange, wakeReason) -> + when { + wakeChange == true && wakeReason.isTouch -> + falsingCollector.onScreenOnFromTouch() + wakeChange == true -> falsingCollector.onScreenTurningOn() + wakeChange == false -> falsingCollector.onScreenOff() + } + } + } + + applicationScope.launch { + sceneInteractor.desiredScene + .map { it.key == SceneKey.Bouncer } + .distinctUntilChanged() + .collect { switchedToBouncerScene -> + if (switchedToBouncerScene) { + falsingCollector.onBouncerShown() + } else { + falsingCollector.onBouncerHidden() + } + } + } + } + private fun switchToScene(targetSceneKey: SceneKey, loggingReason: String) { sceneInteractor.changeScene( scene = SceneModel(targetSceneKey), diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt index 8da1803053f4..fcfdcebba2ea 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt @@ -17,6 +17,7 @@ package com.android.systemui.scene.domain.startable import com.android.systemui.CoreStartable +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor import dagger.Binds import dagger.Module import dagger.multibindings.ClassKey @@ -29,4 +30,11 @@ interface SceneContainerStartableModule { @IntoMap @ClassKey(SceneContainerStartable::class) fun bind(impl: SceneContainerStartable): CoreStartable + + @Binds + @IntoMap + @ClassKey(WindowRootViewVisibilityInteractor::class) + fun bindWindowRootViewVisibilityInteractor( + impl: WindowRootViewVisibilityInteractor + ): CoreStartable } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt index f9324a95c1e5..3e766073f720 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt @@ -110,6 +110,7 @@ object SceneWindowRootViewBinder { // SysUI altogether. private fun createVisibilityToggleView(otherView: View): View { val toggleView = View(otherView.context) + otherView.isVisible = false toggleView.layoutParams = FrameLayout.LayoutParams(200, 200, Gravity.CENTER_HORIZONTAL) toggleView.setOnClickListener { val now = Instant.now() diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt index c9a73e665293..ef688a8e4337 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + package com.android.systemui.scene.ui.view import android.annotation.SuppressLint 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 5c16fb54e3a0..24316601830a 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 @@ -16,6 +16,8 @@ package com.android.systemui.scene.ui.viewmodel +import android.view.MotionEvent +import com.android.systemui.classifier.domain.interactor.FalsingInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.ObservableTransitionState @@ -30,7 +32,8 @@ import kotlinx.coroutines.flow.StateFlow class SceneContainerViewModel @Inject constructor( - private val interactor: SceneInteractor, + private val sceneInteractor: SceneInteractor, + private val falsingInteractor: FalsingInteractor, ) { /** * Keys of all scenes in the container. @@ -38,17 +41,17 @@ constructor( * The scenes will be sorted in z-order such that the last one is the one that should be * rendered on top of all previous ones. */ - val allSceneKeys: List<SceneKey> = interactor.allSceneKeys() + val allSceneKeys: List<SceneKey> = sceneInteractor.allSceneKeys() /** The scene that should be rendered. */ - val currentScene: StateFlow<SceneModel> = interactor.desiredScene + val currentScene: StateFlow<SceneModel> = sceneInteractor.desiredScene /** Whether the container is visible. */ - val isVisible: StateFlow<Boolean> = interactor.isVisible + val isVisible: StateFlow<Boolean> = sceneInteractor.isVisible /** Notifies that the UI has transitioned sufficiently to the given scene. */ fun onSceneChanged(scene: SceneModel) { - interactor.onSceneChanged( + sceneInteractor.onSceneChanged( scene = scene, loggingReason = SCENE_TRANSITION_LOGGING_REASON, ) @@ -60,7 +63,27 @@ constructor( * Note that you must call is with `null` when the UI is done or risk a memory leak. */ fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) { - interactor.setTransitionState(transitionState) + sceneInteractor.setTransitionState(transitionState) + } + + /** + * Notifies that a [MotionEvent] is first seen at the top of the scene container UI. + * + * Call this before the [MotionEvent] starts to propagate through the UI hierarchy. + */ + fun onMotionEvent(event: MotionEvent) { + sceneInteractor.onUserInput() + falsingInteractor.onTouchEvent(event) + } + + /** + * Notifies that a [MotionEvent] that was previously sent to [onMotionEvent] has passed through + * the scene container UI. + * + * Call this after the [MotionEvent] propagates completely through the UI hierarchy. + */ + fun onMotionEventComplete() { + falsingInteractor.onMotionEventComplete() } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index a41d6e8928ab..8db7abf7347f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -1226,6 +1226,38 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private void updateViewControllers( FrameLayout userAvatarView, KeyguardUserSwitcherView keyguardUserSwitcherView) { + updateStatusBarViewController(); + if (mKeyguardUserSwitcherController != null) { + // Try to close the switcher so that callbacks are triggered if necessary. + // Otherwise, NPV can get into a state where some of the views are still hidden + mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(false); + } + + mKeyguardQsUserSwitchController = null; + mKeyguardUserSwitcherController = null; + + // Re-associate the KeyguardUserSwitcherController + if (userAvatarView != null) { + KeyguardQsUserSwitchComponent userSwitcherComponent = + mKeyguardQsUserSwitchComponentFactory.build(userAvatarView); + mKeyguardQsUserSwitchController = + userSwitcherComponent.getKeyguardQsUserSwitchController(); + mKeyguardQsUserSwitchController.init(); + mKeyguardStatusBarViewController.setKeyguardUserSwitcherEnabled(true); + } else if (keyguardUserSwitcherView != null) { + KeyguardUserSwitcherComponent userSwitcherComponent = + mKeyguardUserSwitcherComponentFactory.build(keyguardUserSwitcherView); + mKeyguardUserSwitcherController = + userSwitcherComponent.getKeyguardUserSwitcherController(); + mKeyguardUserSwitcherController.init(); + mKeyguardStatusBarViewController.setKeyguardUserSwitcherEnabled(true); + } else { + mKeyguardStatusBarViewController.setKeyguardUserSwitcherEnabled(false); + } + } + + /** Updates the StatusBarViewController and updates any that depend on it. */ + public void updateStatusBarViewController() { // Re-associate the KeyguardStatusViewController if (mKeyguardStatusViewController != null) { mKeyguardStatusViewController.onDestroy(); @@ -1235,17 +1267,17 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // Need a shared controller until mKeyguardStatusViewController can be removed from // here, due to important state being set in that controller. Rebind in order to pick // up config changes - mKeyguardViewConfigurator.bindKeyguardStatusView(mView); mKeyguardStatusViewController = mKeyguardViewConfigurator.getKeyguardStatusViewController(); } else { KeyguardStatusView keyguardStatusView = mView.getRootView().findViewById( R.id.keyguard_status_view); KeyguardStatusViewComponent statusViewComponent = - mKeyguardStatusViewComponentFactory.build(keyguardStatusView); + mKeyguardStatusViewComponentFactory.build(keyguardStatusView); mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController(); mKeyguardStatusViewController.init(); } + mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled); mKeyguardStatusViewController.getView().addOnLayoutChangeListener( (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { @@ -1256,34 +1288,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump }); updateClockAppearance(); - - if (mKeyguardUserSwitcherController != null) { - // Try to close the switcher so that callbacks are triggered if necessary. - // Otherwise, NPV can get into a state where some of the views are still hidden - mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(false); - } - - mKeyguardQsUserSwitchController = null; - mKeyguardUserSwitcherController = null; - - // Re-associate the KeyguardUserSwitcherController - if (userAvatarView != null) { - KeyguardQsUserSwitchComponent userSwitcherComponent = - mKeyguardQsUserSwitchComponentFactory.build(userAvatarView); - mKeyguardQsUserSwitchController = - userSwitcherComponent.getKeyguardQsUserSwitchController(); - mKeyguardQsUserSwitchController.init(); - mKeyguardStatusBarViewController.setKeyguardUserSwitcherEnabled(true); - } else if (keyguardUserSwitcherView != null) { - KeyguardUserSwitcherComponent userSwitcherComponent = - mKeyguardUserSwitcherComponentFactory.build(keyguardUserSwitcherView); - mKeyguardUserSwitcherController = - userSwitcherComponent.getKeyguardUserSwitcherController(); - mKeyguardUserSwitcherController.init(); - mKeyguardStatusBarViewController.setKeyguardUserSwitcherEnabled(true); - } else { - mKeyguardStatusBarViewController.setKeyguardUserSwitcherEnabled(false); - } } @Override @@ -1403,7 +1407,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump updateViewControllers(userAvatarView, keyguardUserSwitcherView); - if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW) && !mFeatureFlags.isEnabled( + Flags.LAZY_INFLATE_KEYGUARD)) { attachSplitShadeMediaPlayerContainer( mKeyguardViewConfigurator.getKeyguardRootView() .findViewById(R.id.status_view_media_container)); @@ -2818,7 +2823,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private void onTrackingStarted() { - mFalsingCollector.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen()); endClosing(); mTracking = true; mTrackingStartedListener.onTrackingStarted(); @@ -2834,7 +2838,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private void onTrackingStopped(boolean expand) { - mFalsingCollector.onTrackingStopped(); mTracking = false; maybeStopTrackingExpansionFromStatusBar(expand); @@ -2888,7 +2891,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @VisibleForTesting void onUnlockHintStarted() { - mFalsingCollector.onUnlockHintStarted(); mKeyguardIndicationController.showActionToUnlock(); mScrimController.setExpansionAffectsAlpha(false); mNotificationStackScrollLayoutController.setUnlockHintRunning(true); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index ad9df72b0cdf..4a76dd0df47b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -21,7 +21,6 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE; -import static com.android.systemui.DejankUtils.whitelistIpcs; import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; import android.app.IActivityManager; @@ -52,6 +51,7 @@ import com.android.systemui.R; import com.android.systemui.biometrics.AuthController; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpsysTableLogger; import com.android.systemui.keyguard.KeyguardViewMediator; @@ -76,6 +76,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -104,6 +105,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW private final float mKeyguardMaxRefreshRate; private final KeyguardViewMediator mKeyguardViewMediator; private final KeyguardBypassController mKeyguardBypassController; + private final Executor mBackgroundExecutor; private final AuthController mAuthController; private ViewGroup mWindowRootView; private LayoutParams mLp; @@ -141,6 +143,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW ConfigurationController configurationController, KeyguardViewMediator keyguardViewMediator, KeyguardBypassController keyguardBypassController, + @Background Executor backgroundExecutor, SysuiColorExtractor colorExtractor, DumpManager dumpManager, KeyguardStateController keyguardStateController, @@ -159,6 +162,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW mLpChanged = new LayoutParams(); mKeyguardViewMediator = keyguardViewMediator; mKeyguardBypassController = keyguardBypassController; + mBackgroundExecutor = backgroundExecutor; mColorExtractor = colorExtractor; mScreenOffAnimationController = screenOffAnimationController; dumpManager.registerDumpable(this); @@ -520,13 +524,14 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW applyWindowLayoutParams(); if (mHasTopUi != mHasTopUiChanged) { - whitelistIpcs(() -> { + mHasTopUi = mHasTopUiChanged; + mBackgroundExecutor.execute(() -> { try { mActivityManager.setHasTopUi(mHasTopUiChanged); } catch (RemoteException e) { Log.e(TAG, "Failed to call setHasTopUi", e); } - mHasTopUi = mHasTopUiChanged; + }); } notifyStateChangedCallbacks(); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index 3a916cf198f3..0f85c7616071 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -35,6 +35,7 @@ import com.android.keyguard.AuthKeyguardMessageArea; import com.android.keyguard.KeyguardMessageAreaController; import com.android.keyguard.LockIconViewController; import com.android.keyguard.dagger.KeyguardBouncerComponent; +import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.back.domain.interactor.BackActionInteractor; import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor; @@ -43,6 +44,7 @@ import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dock.DockManager; +import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor; @@ -65,6 +67,8 @@ import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.phone.CentralSurfaces; +import com.android.systemui.statusbar.phone.DozeScrimController; +import com.android.systemui.statusbar.phone.DozeServiceHost; import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.window.StatusBarWindowStateController; @@ -81,7 +85,7 @@ import javax.inject.Inject; * Controller for {@link NotificationShadeWindowView}. */ @SysUISingleton -public class NotificationShadeWindowViewController { +public class NotificationShadeWindowViewController implements Dumpable { private static final String TAG = "NotifShadeWindowVC"; private final FalsingCollector mFalsingCollector; private final SysuiStatusBarStateController mStatusBarStateController; @@ -118,6 +122,8 @@ public class NotificationShadeWindowViewController { private NotificationStackScrollLayout mStackScrollLayout; private PhoneStatusBarViewController mStatusBarViewController; private final CentralSurfaces mService; + private final DozeServiceHost mDozeServiceHost; + private final DozeScrimController mDozeScrimController; private final BackActionInteractor mBackActionInteractor; private final PowerInteractor mPowerInteractor; private final NotificationShadeWindowController mNotificationShadeWindowController; @@ -152,6 +158,8 @@ public class NotificationShadeWindowViewController { StatusBarWindowStateController statusBarWindowStateController, LockIconViewController lockIconViewController, CentralSurfaces centralSurfaces, + DozeServiceHost dozeServiceHost, + DozeScrimController dozeScrimController, BackActionInteractor backActionInteractor, PowerInteractor powerInteractor, NotificationShadeWindowController controller, @@ -160,6 +168,7 @@ public class NotificationShadeWindowViewController { NotificationInsetsController notificationInsetsController, AmbientState ambientState, ShadeLogger shadeLogger, + DumpManager dumpManager, PulsingGestureListener pulsingGestureListener, LockscreenHostedDreamGestureListener lockscreenHostedDreamGestureListener, KeyguardBouncerViewModel keyguardBouncerViewModel, @@ -187,8 +196,9 @@ public class NotificationShadeWindowViewController { mLockIconViewController = lockIconViewController; mBackActionInteractor = backActionInteractor; mShadeLogger = shadeLogger; - mLockIconViewController.init(); mService = centralSurfaces; + mDozeServiceHost = dozeServiceHost; + mDozeScrimController = dozeScrimController; mPowerInteractor = powerInteractor; mNotificationShadeWindowController = controller; mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; @@ -226,6 +236,9 @@ public class NotificationShadeWindowViewController { progressProvider -> progressProvider.addCallback( mDisableSubpixelTextTransitionListener)); } + + lockIconViewController.setLockIconView(mView.findViewById(R.id.lock_icon_view)); + dumpManager.registerDumpable(this); } /** @@ -332,7 +345,7 @@ public class NotificationShadeWindowViewController { } if (mStatusBarStateController.isDozing()) { - mService.extendDozePulse(); + mDozeScrimController.extendPulse(); } mLockIconViewController.onTouchEvent( ev, @@ -391,7 +404,7 @@ public class NotificationShadeWindowViewController { @Override public boolean shouldInterceptTouchEvent(MotionEvent ev) { - if (mStatusBarStateController.isDozing() && !mService.isPulsing() + if (mStatusBarStateController.isDozing() && !mDozeServiceHost.isPulsing() && !mDockManager.isDocked()) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { mShadeLogger.d("NSWVC: capture all touch events in always-on"); @@ -445,7 +458,7 @@ public class NotificationShadeWindowViewController { public boolean handleTouchEvent(MotionEvent ev) { boolean handled = false; if (mStatusBarStateController.isDozing()) { - handled = !mService.isPulsing(); + handled = !mDozeServiceHost.isPulsing(); } if (mStatusBarKeyguardViewManager.onTouch(ev)) { @@ -533,6 +546,7 @@ public class NotificationShadeWindowViewController { mAmbientState.setSwipingUp(false); } + @Override public void dump(PrintWriter pw, String[] args) { pw.print(" mExpandAnimationRunning="); pw.println(mExpandAnimationRunning); diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index c9c911b205e1..b2bbffdb5453 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -67,7 +67,6 @@ import com.android.systemui.DejankUtils; import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.classifier.Classifier; -import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; @@ -147,7 +146,6 @@ public class QuickSettingsController implements Dumpable { private final MediaHierarchyManager mMediaHierarchyManager; private final AmbientState mAmbientState; private final RecordingController mRecordingController; - private final FalsingCollector mFalsingCollector; private final LockscreenGestureLogger mLockscreenGestureLogger; private final ShadeLogger mShadeLog; private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor; @@ -335,7 +333,6 @@ public class QuickSettingsController implements Dumpable { AmbientState ambientState, RecordingController recordingController, FalsingManager falsingManager, - FalsingCollector falsingCollector, AccessibilityManager accessibilityManager, LockscreenGestureLogger lockscreenGestureLogger, MetricsLogger metricsLogger, @@ -381,7 +378,6 @@ public class QuickSettingsController implements Dumpable { mAmbientState = ambientState; mRecordingController = recordingController; mFalsingManager = falsingManager; - mFalsingCollector = falsingCollector; mAccessibilityManager = accessibilityManager; mLockscreenGestureLogger = lockscreenGestureLogger; @@ -1660,7 +1656,6 @@ public class QuickSettingsController implements Dumpable { } } if (shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) { - mFalsingCollector.onQsDown(); mShadeLog.logMotionEvent(event, "handleQsDown: down action, QS tracking enabled"); mTracking = true; diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java index d7a339210412..9a3e4e577634 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java @@ -30,6 +30,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.dagger.ShadeTouchLog; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationShadeWindowController; @@ -59,6 +60,7 @@ public final class ShadeControllerImpl implements ShadeController { private final CommandQueue mCommandQueue; private final Executor mMainExecutor; private final LogBuffer mTouchLog; + private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; private final KeyguardStateController mKeyguardStateController; private final NotificationShadeWindowController mNotificationShadeWindowController; private final StatusBarStateController mStatusBarStateController; @@ -83,6 +85,7 @@ public final class ShadeControllerImpl implements ShadeController { CommandQueue commandQueue, @Main Executor mainExecutor, @ShadeTouchLog LogBuffer touchLog, + WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor, KeyguardStateController keyguardStateController, StatusBarStateController statusBarStateController, StatusBarKeyguardViewManager statusBarKeyguardViewManager, @@ -97,6 +100,7 @@ public final class ShadeControllerImpl implements ShadeController { mCommandQueue = commandQueue; mMainExecutor = mainExecutor; mTouchLog = touchLog; + mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor; mShadeViewControllerLazy = shadeViewControllerLazy; mStatusBarStateController = statusBarStateController; mStatusBarWindowController = statusBarWindowController; @@ -391,6 +395,7 @@ public final class ShadeControllerImpl implements ShadeController { private void notifyVisibilityChanged(boolean visible) { mShadeVisibilityListener.visibilityChanged(visible); + mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(visible); } private void notifyExpandedVisibleChanged(boolean expandedVisible) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt index 2532bad1d7a7..b553f0fccd91 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt @@ -99,9 +99,6 @@ interface ShadeSurface : ShadeViewController { /** Sets the view's alpha to max. */ fun resetAlpha() - /** Sets progress of the predictive back animation. */ - fun onBackProgressed(progressFraction: Float) - /** @see com.android.systemui.keyguard.ScreenLifecycle.Observer.onScreenTurningOn */ fun onScreenTurningOn() diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt index 182a676c9841..1121834f196d 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt @@ -155,6 +155,9 @@ interface ShadeViewController { /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */ fun onBackPressed() + /** Sets progress of the predictive back animation. */ + fun onBackProgressed(progressFraction: Float) + /** Sets whether the status bar launch animation is currently running. */ fun setIsLaunchAnimationRunning(running: Boolean) diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt index 09b74b213ebf..6a2bef296b6b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt @@ -58,6 +58,7 @@ class ShadeViewControllerEmptyImpl @Inject constructor() : ShadeViewController { return false } override fun onBackPressed() {} + override fun onBackProgressed(progressFraction: Float) {} override fun setIsLaunchAnimationRunning(running: Boolean) {} override fun setAlpha(alpha: Int, animate: Boolean) {} override fun setAlphaChangeAnimationEndAction(r: Runnable) {} diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt index 05b1ac64a5f6..6585fcb1ae53 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt @@ -22,7 +22,6 @@ import android.os.Handler import android.view.LayoutInflater import android.view.ViewStub import androidx.constraintlayout.motion.widget.MotionLayout -import com.android.keyguard.LockIconView import com.android.systemui.R import com.android.systemui.battery.BatteryMeterView import com.android.systemui.battery.BatteryMeterViewController @@ -96,11 +95,11 @@ abstract class ShadeViewProviderModule { ?: throw IllegalStateException("Window root view could not be properly inflated") } - @Provides - @SysUISingleton // TODO(b/277762009): Do something similar to // {@link StatusBarWindowModule.InternalWindowView} so that only // {@link NotificationShadeWindowViewController} can inject this view. + @Provides + @SysUISingleton fun providesNotificationShadeWindowView( root: WindowRootView, featureFlags: FeatureFlags, @@ -206,21 +205,6 @@ abstract class ShadeViewProviderModule { // TODO(b/277762009): Only allow this view's controller to inject the view. See above. @Provides @SysUISingleton - fun providesLockIconView( - keyguardRootView: KeyguardRootView, - notificationPanelView: NotificationPanelView, - featureFlags: FeatureFlags - ): LockIconView { - if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) { - return keyguardRootView.requireViewById(R.id.lock_icon_view) - } else { - return notificationPanelView.requireViewById(R.id.lock_icon_view) - } - } - - // TODO(b/277762009): Only allow this view's controller to inject the view. See above. - @Provides - @SysUISingleton fun providesTapAgainView( notificationPanelView: NotificationPanelView, ): TapAgainView { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index 25bb204d6f23..f004982413e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -81,6 +81,7 @@ class LockscreenShadeTransitionController @Inject constructor( private val powerInteractor: PowerInteractor, ) : Dumpable { private var pulseHeight: Float = 0f + @get:VisibleForTesting var fractionToShade: Float = 0f private set @@ -255,17 +256,17 @@ class LockscreenShadeTransitionController @Inject constructor( private fun updateResources() { fullTransitionDistance = context.resources.getDimensionPixelSize( - R.dimen.lockscreen_shade_full_transition_distance) + R.dimen.lockscreen_shade_full_transition_distance) fullTransitionDistanceByTap = context.resources.getDimensionPixelSize( R.dimen.lockscreen_shade_transition_by_tap_distance) notificationShelfTransitionDistance = context.resources.getDimensionPixelSize( - R.dimen.lockscreen_shade_notif_shelf_transition_distance) + R.dimen.lockscreen_shade_notif_shelf_transition_distance) depthControllerTransitionDistance = context.resources.getDimensionPixelSize( - R.dimen.lockscreen_shade_depth_controller_transition_distance) + R.dimen.lockscreen_shade_depth_controller_transition_distance) udfpsTransitionDistance = context.resources.getDimensionPixelSize( - R.dimen.lockscreen_shade_udfps_keyguard_transition_distance) + R.dimen.lockscreen_shade_udfps_keyguard_transition_distance) statusBarTransitionDistance = context.resources.getDimensionPixelSize( - R.dimen.lockscreen_shade_status_bar_transition_distance) + R.dimen.lockscreen_shade_status_bar_transition_distance) useSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(context.resources) } @@ -292,8 +293,8 @@ class LockscreenShadeTransitionController @Inject constructor( */ internal fun canDragDown(): Boolean { return (statusBarStateController.state == StatusBarState.KEYGUARD || - nsslController.isInLockedDownShade()) && - (qS.isFullyCollapsed || useSplitShade) + nsslController.isInLockedDownShade()) && + (qS.isFullyCollapsed || useSplitShade) } /** @@ -308,10 +309,15 @@ class LockscreenShadeTransitionController @Inject constructor( if (nsslController.isInLockedDownShade()) { logger.logDraggedDownLockDownShade(startingChild) statusBarStateController.setLeaveOpenOnKeyguardHide(true) - activityStarter.dismissKeyguardThenExecute(OnDismissAction { - nextHideKeyguardNeedsNoAnimation = true - false - }, cancelRunnable, false /* afterKeyguardGone */) + activityStarter.dismissKeyguardThenExecute( + { + nextHideKeyguardNeedsNoAnimation = true + false + }, + cancelRunnable, + /* afterKeyguardGone= */ + false, + ) } else { logger.logDraggedDown(startingChild, dragLengthY) if (!ambientState.isDozing() || startingChild != null) { @@ -320,11 +326,18 @@ class LockscreenShadeTransitionController @Inject constructor( val animationHandler = { delay: Long -> if (startingChild is ExpandableNotificationRow) { startingChild.onExpandedByGesture( - true /* drag down is always an open */) + true /* drag down is always an open */ + ) } shadeViewController.transitionToExpandedShade(delay) - callbacks.forEach { it.setTransitionToFullShadeAmount(0f, - true /* animated */, delay) } + callbacks.forEach { + it.setTransitionToFullShadeAmount( + 0f, + /* animated= */ + true, + delay + ) + } // Let's reset ourselves, ready for the next animation @@ -350,7 +363,12 @@ class LockscreenShadeTransitionController @Inject constructor( */ internal fun onDragDownReset() { logger.logDragDownAborted() - nsslController.setDimmed(true /* dimmed */, true /* animated */) + nsslController.setDimmed( + /* dimmed= */ + true, + /* animate= */ + true, + ) nsslController.resetScrollPosition() nsslController.resetCheckSnoozeLeavebehind() setDragDownAmountAnimated(0f) @@ -361,7 +379,12 @@ class LockscreenShadeTransitionController @Inject constructor( * @param above whether they dragged above it */ internal fun onCrossedThreshold(above: Boolean) { - nsslController.setDimmed(!above /* dimmed */, true /* animate */) + nsslController.setDimmed( + /* dimmed= */ + !above, + /* animate= */ + true, + ) } /** @@ -411,8 +434,8 @@ class LockscreenShadeTransitionController @Inject constructor( */ internal val isDragDownAnywhereEnabled: Boolean get() = (statusBarStateController.getState() == StatusBarState.KEYGUARD && - !keyguardBypassController.bypassEnabled && - (qS.isFullyCollapsed || useSplitShade)) + !keyguardBypassController.bypassEnabled && + (qS.isFullyCollapsed || useSplitShade)) /** * The amount in pixels that the user has dragged down. @@ -429,8 +452,15 @@ class LockscreenShadeTransitionController @Inject constructor( qsTransitionController.dragDownAmount = value - callbacks.forEach { it.setTransitionToFullShadeAmount(field, - false /* animate */, 0 /* delay */) } + callbacks.forEach { + it.setTransitionToFullShadeAmount( + field, + /* animate= */ + false, + /* delay= */ + 0, + ) + } mediaHierarchyManager.setTransitionToFullShadeAmount(field) scrimTransitionController.dragDownAmount = value @@ -538,8 +568,11 @@ class LockscreenShadeTransitionController @Inject constructor( shadeViewController.transitionToExpandedShade(delay) } } - goToLockedShadeInternal(expandedView, animationHandler, - cancelAction = null) + goToLockedShadeInternal( + expandedView, + animationHandler, + cancelAction = null + ) } } @@ -570,14 +603,19 @@ class LockscreenShadeTransitionController @Inject constructor( var entry: NotificationEntry? = null if (expandView is ExpandableNotificationRow) { entry = expandView.entry - entry.setUserExpanded(true /* userExpanded */, true /* allowChildExpansion */) + entry.setUserExpanded( + /* userExpanded= */ + true, + /* allowChildExpansion= */ + true, + ) // Indicate that the group expansion is changing at this time -- this way the group // and children backgrounds / divider animations will look correct. entry.setGroupExpansionChanging(true) userId = entry.sbn.userId } var fullShadeNeedsBouncer = ( - !lockScreenUserManager.shouldShowLockscreenNotifications() || + !lockScreenUserManager.shouldShowLockscreenNotifications() || falsingCollector.shouldEnforceBouncer()) if (keyguardBypassController.bypassEnabled) { fullShadeNeedsBouncer = false @@ -596,7 +634,10 @@ class LockscreenShadeTransitionController @Inject constructor( val cancelHandler = Runnable { draggedDownEntry?.apply { setUserLocked(false) - notifyHeightChanged(false /* needsAnimation */) + notifyHeightChanged( + /* needsAnimation= */ + false, + ) draggedDownEntry = null } cancelAction?.run() @@ -614,7 +655,10 @@ class LockscreenShadeTransitionController @Inject constructor( // This call needs to be after updating the shade state since otherwise // the scrimstate resets too early if (animationHandler != null) { - animationHandler.invoke(0 /* delay */) + animationHandler.invoke( + /* delay= */ + 0, + ) } else { performDefaultGoToFullShadeAnimation(0) } @@ -721,12 +765,13 @@ class LockscreenShadeTransitionController @Inject constructor( it.println("isDragDownAnywhereEnabled: $isDragDownAnywhereEnabled") it.println("isFalsingCheckNeeded: $isFalsingCheckNeeded") it.println("isWakingToShadeLocked: $isWakingToShadeLocked") - it.println("hasPendingHandlerOnKeyguardDismiss: " + - "${animationHandlerOnKeyguardDismiss != null}") + it.println( + "hasPendingHandlerOnKeyguardDismiss: " + + "${animationHandlerOnKeyguardDismiss != null}" + ) } } - fun addCallback(callback: Callback) { if (!callbacks.contains(callback)) { callbacks.add(callback) @@ -791,7 +836,7 @@ class DragDownHelper( fun updateResources(context: Context) { minDragDistance = context.resources.getDimensionPixelSize( - R.dimen.keyguard_drag_down_min_distance) + R.dimen.keyguard_drag_down_min_distance) val configuration = ViewConfiguration.get(context) touchSlop = configuration.scaledTouchSlop.toFloat() slopMultiplier = configuration.scaledAmbiguousGestureMultiplier @@ -808,16 +853,17 @@ class DragDownHelper( initialTouchY = y initialTouchX = x } + MotionEvent.ACTION_MOVE -> { val h = y - initialTouchY // Adjust the touch slop if another gesture may be being performed. val touchSlop = if (event.classification - == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE) + == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE) { touchSlop * slopMultiplier - else + } else { touchSlop + } if (h > touchSlop && h > Math.abs(x - initialTouchX)) { - falsingCollector.onNotificationStartDraggingDown() isDraggingDown = true captureStartingChild(initialTouchX, initialTouchY) initialTouchY = y @@ -858,8 +904,9 @@ class DragDownHelper( } return true } + MotionEvent.ACTION_UP -> if (!falsingManager.isUnlockingDisabled && !isFalseTouch && - dragDownCallback.canDragDown()) { + dragDownCallback.canDragDown()) { dragDownCallback.onDraggedDown(startingChild, (y - initialTouchY).toInt()) if (startingChild != null) { expandCallback.setUserLockedChild(startingChild, false) @@ -870,6 +917,7 @@ class DragDownHelper( stopDragging() return false } + MotionEvent.ACTION_CANCEL -> { stopDragging() return false @@ -913,8 +961,8 @@ class DragDownHelper( @VisibleForTesting fun cancelChildExpansion( - child: ExpandableView, - animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS + child: ExpandableView, + animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS ) { if (child.actualHeight == child.collapsedHeight) { expandCallback.setUserLockedChild(child, false) @@ -936,7 +984,6 @@ class DragDownHelper( } private fun stopDragging() { - falsingCollector.onNotificationStopDraggingDown() if (startingChild != null) { cancelChildExpansion(startingChild!!) startingChild = null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 763400b307fd..5bd40b8ed714 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -65,7 +65,6 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Di import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.phone.BiometricUnlockController; -import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.LockscreenWallpaper; import com.android.systemui.statusbar.phone.ScrimController; @@ -74,6 +73,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.Utils; import com.android.systemui.util.concurrency.DelayableExecutor; +import dagger.Lazy; + import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -87,8 +88,6 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; -import dagger.Lazy; - /** * Handles tasks and state related to media notifications. For example, there is a 'current' media * notification, which this class keeps track of. @@ -133,7 +132,6 @@ public class NotificationMediaManager implements Dumpable { private final Context mContext; private final ArrayList<MediaListener> mMediaListeners; - private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy; private final MediaArtworkProcessor mMediaArtworkProcessor; private final Set<AsyncTask<?, ?, ?>> mProcessArtworkTasks = new ArraySet<>(); @@ -186,7 +184,6 @@ public class NotificationMediaManager implements Dumpable { */ public NotificationMediaManager( Context context, - Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, Lazy<NotificationShadeWindowController> notificationShadeWindowController, NotificationVisibilityProvider visibilityProvider, MediaArtworkProcessor mediaArtworkProcessor, @@ -205,8 +202,6 @@ public class NotificationMediaManager implements Dumpable { mMediaArtworkProcessor = mediaArtworkProcessor; mKeyguardBypassController = keyguardBypassController; mMediaListeners = new ArrayList<>(); - // TODO: use KeyguardStateController#isOccluded to remove this dependency - mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy; mNotificationShadeWindowController = notificationShadeWindowController; mVisibilityProvider = visibilityProvider; mMainExecutor = mainExecutor; @@ -619,9 +614,7 @@ public class NotificationMediaManager implements Dumpable { NotificationShadeWindowController windowController = mNotificationShadeWindowController.get(); - boolean hideBecauseOccluded = - mCentralSurfacesOptionalLazy.get() - .map(CentralSurfaces::isOccluded).orElse(false); + boolean hideBecauseOccluded = mKeyguardStateController.isOccluded(); final boolean hasArtwork = artworkDrawable != null; mColorExtractor.setHasMediaArtwork(hasMediaArtwork); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt index fc6eaa804f86..2c0741ecf986 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt @@ -28,10 +28,10 @@ import android.view.MotionEvent import android.view.VelocityTracker import android.view.ViewConfiguration import androidx.annotation.VisibleForTesting +import com.android.app.animation.Interpolators import com.android.systemui.Dumpable import com.android.systemui.Gefingerpoken import com.android.systemui.R -import com.android.app.animation.Interpolators import com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN import com.android.systemui.classifier.FalsingCollector import com.android.systemui.dagger.SysUISingleton @@ -76,6 +76,7 @@ constructor( companion object { private val SPRING_BACK_ANIMATION_LENGTH_MS = 375 } + private val mPowerManager: PowerManager? private var mInitialTouchX: Float = 0.0f @@ -94,7 +95,10 @@ constructor( pulseExpandAbortListener?.run() } } - headsUpManager.unpinAll(true /* userUnPinned */) + headsUpManager.unpinAll( + /*userUnPinned= */ + true, + ) } } var leavingLockscreen: Boolean = false @@ -168,7 +172,6 @@ constructor( MotionEvent.ACTION_MOVE -> { val h = y - mInitialTouchY if (h > touchSlop && h > Math.abs(x - mInitialTouchX)) { - falsingCollector.onStartExpandingFromPulse() isExpanding = true captureStartingChild(mInitialTouchX, mInitialTouchY) mInitialTouchY = y @@ -200,14 +203,14 @@ constructor( event.action == MotionEvent.ACTION_UP) && isExpanding val isDraggingNotificationOrCanBypass = mStartingChild?.showingPulsing() == true || - bypassController.canBypass() + bypassController.canBypass() if ((!canHandleMotionEvent() || !isDraggingNotificationOrCanBypass) && !finishExpanding) { // We allow cancellations/finishing to still go through here to clean up the state return false } if (velocityTracker == null || !isExpanding || - event.actionMasked == MotionEvent.ACTION_DOWN) { + event.actionMasked == MotionEvent.ACTION_DOWN) { return startExpansion(event) } velocityTracker!!.addMovement(event) @@ -217,9 +220,12 @@ constructor( when (event.actionMasked) { MotionEvent.ACTION_MOVE -> updateExpansionHeight(moveDistance) MotionEvent.ACTION_UP -> { - velocityTracker!!.computeCurrentVelocity(1000 /* units */) + velocityTracker!!.computeCurrentVelocity( + /* units= */ + 1000, + ) val canExpand = moveDistance > 0 && velocityTracker!!.getYVelocity() > -1000 && - statusBarStateController.state != StatusBarState.SHADE + statusBarStateController.state != StatusBarState.SHADE if (!falsingManager.isUnlockingDisabled && !isFalseTouch && canExpand) { finishExpansion() } else { @@ -227,6 +233,7 @@ constructor( } recycleVelocityTracker() } + MotionEvent.ACTION_CANCEL -> { cancelExpansion() recycleVelocityTracker() @@ -243,17 +250,25 @@ constructor( } if (statusBarStateController.isDozing) { wakeUpCoordinator.willWakeUp = true - mPowerManager!!.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE, - "com.android.systemui:PULSEDRAG") + mPowerManager!!.wakeUp( + SystemClock.uptimeMillis(), + PowerManager.WAKE_REASON_GESTURE, + "com.android.systemui:PULSEDRAG" + ) } - lockscreenShadeTransitionController.goToLockedShade(startingChild, - needsQSAnimation = false) + lockscreenShadeTransitionController.goToLockedShade( + startingChild, + needsQSAnimation = false + ) lockscreenShadeTransitionController.finishPulseAnimation(cancelled = false) leavingLockscreen = true isExpanding = false if (mStartingChild is ExpandableNotificationRow) { val row = mStartingChild as ExpandableNotificationRow? - row!!.onExpandedByGesture(true /* userExpanded */) + row!!.onExpandedByGesture( + /*userExpanded= */ + true, + ) } } @@ -261,15 +276,20 @@ constructor( var expansionHeight = max(height, 0.0f) if (mStartingChild != null) { val child = mStartingChild!! - val newHeight = Math.min((child.collapsedHeight + expansionHeight).toInt(), - child.maxContentHeight) + val newHeight = Math.min( + (child.collapsedHeight + expansionHeight).toInt(), + child.maxContentHeight + ) child.actualHeight = newHeight } else { wakeUpCoordinator.setNotificationsVisibleForExpansion( height > lockscreenShadeTransitionController.distanceUntilShowingPulsingNotifications, - true /* animate */, - true /* increaseSpeed */) + /*animate= */ + true, + /*increaseSpeed= */ + true + ) } lockscreenShadeTransitionController.setPulseHeight(expansionHeight, animate = false) } @@ -285,8 +305,8 @@ constructor( @VisibleForTesting fun reset( - child: ExpandableView, - animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS.toLong() + child: ExpandableView, + animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS.toLong() ) { if (child.actualHeight == child.collapsedHeight) { setUserLocked(child, false) @@ -315,15 +335,19 @@ constructor( private fun cancelExpansion() { isExpanding = false - falsingCollector.onExpansionFromPulseStopped() if (mStartingChild != null) { reset(mStartingChild!!) mStartingChild = null } lockscreenShadeTransitionController.finishPulseAnimation(cancelled = true) - wakeUpCoordinator.setNotificationsVisibleForExpansion(false /* visible */, - true /* animate */, - false /* increaseSpeed */) + wakeUpCoordinator.setNotificationsVisibleForExpansion( + /*visible= */ + false, + /*animate= */ + true, + /*increaseSpeed= */ + false + ) } private fun findView(x: Float, y: Float): ExpandableView? { @@ -335,7 +359,9 @@ constructor( val childAtRawPosition = stackScrollerController.getChildAtRawPosition(totalX, totalY) return if (childAtRawPosition != null && childAtRawPosition.isContentExpandable) { childAtRawPosition - } else null + } else { + null + } } fun setUp(stackScrollerController: NotificationStackScrollLayoutController) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt index 92aa9866ef66..b46b525f776c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.connectivity +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags.SIGNAL_CALLBACK_DEPRECATION import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.qs.tiles.AirplaneModeTile import com.android.systemui.qs.tiles.BluetoothTile @@ -23,21 +25,17 @@ import com.android.systemui.qs.tiles.CastTile import com.android.systemui.qs.tiles.DataSaverTile import com.android.systemui.qs.tiles.HotspotTile import com.android.systemui.qs.tiles.InternetTile +import com.android.systemui.qs.tiles.InternetTileNewImpl import com.android.systemui.qs.tiles.NfcTile import dagger.Binds import dagger.Module +import dagger.Provides import dagger.multibindings.IntoMap import dagger.multibindings.StringKey @Module interface ConnectivityModule { - /** Inject InternetTile into tileMap in QSModule */ - @Binds - @IntoMap - @StringKey(InternetTile.TILE_SPEC) - fun bindInternetTile(internetTile: InternetTile): QSTileImpl<*> - /** Inject BluetoothTile into tileMap in QSModule */ @Binds @IntoMap @@ -70,4 +68,21 @@ interface ConnectivityModule { /** Inject NfcTile into tileMap in QSModule */ @Binds @IntoMap @StringKey(NfcTile.TILE_SPEC) fun bindNfcTile(nfcTile: NfcTile): QSTileImpl<*> + + companion object { + /** Inject InternetTile or InternetTileNewImpl into tileMap in QSModule */ + @Provides + @IntoMap + @StringKey(InternetTile.TILE_SPEC) + fun bindInternetTile( + internetTile: InternetTile, + newInternetTile: InternetTileNewImpl, + featureFlags: FeatureFlags, + ): QSTileImpl<*> = + if (featureFlags.isEnabled(SIGNAL_CALLBACK_DEPRECATION)) { + newInternetTile + } else { + internetTile + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index f726c4e8a753..3dfe068e6137 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -64,7 +64,6 @@ import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; -import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.CentralSurfacesImpl; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.ManagedProfileController; @@ -135,7 +134,6 @@ public interface CentralSurfacesDependenciesModule { @Provides static NotificationMediaManager provideNotificationMediaManager( Context context, - Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, Lazy<NotificationShadeWindowController> notificationShadeWindowController, NotificationVisibilityProvider visibilityProvider, MediaArtworkProcessor mediaArtworkProcessor, @@ -152,7 +150,6 @@ public interface CentralSurfacesDependenciesModule { DisplayManager displayManager) { return new NotificationMediaManager( context, - centralSurfacesOptionalLazy, notificationShadeWindowController, visibilityProvider, mediaArtworkProcessor, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index 637637d39957..09be41b56a10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -19,12 +19,13 @@ package com.android.systemui.statusbar.notification.dagger; import android.content.Context; import com.android.internal.jank.InteractionJankMonitor; +import com.android.systemui.CoreStartable; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.shade.ShadeEventsModule; -import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider; @@ -76,10 +77,13 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.Notificati import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter; +import com.android.systemui.util.kotlin.JavaAdapter; import dagger.Binds; import dagger.Module; import dagger.Provides; +import dagger.multibindings.ClassKey; +import dagger.multibindings.IntoMap; import java.util.concurrent.Executor; @@ -110,6 +114,13 @@ public interface NotificationsModule { @Binds NotifGutsViewManager bindNotifGutsViewManager(NotificationGutsManager notificationGutsManager); + /** Binds {@link NotificationGutsManager} as a {@link CoreStartable}. */ + @Binds + @IntoMap + @ClassKey(NotificationGutsManager.class) + CoreStartable bindsNotificationGutsManager(NotificationGutsManager notificationGutsManager); + + /** Provides an instance of {@link VisibilityLocationProvider} */ @Binds VisibilityLocationProvider bindVisibilityLocationProvider( @@ -125,7 +136,8 @@ public interface NotificationsModule { NotificationVisibilityProvider visibilityProvider, NotifPipeline notifPipeline, StatusBarStateController statusBarStateController, - ShadeExpansionStateManager shadeExpansionStateManager, + WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor, + JavaAdapter javaAdapter, NotificationLogger.ExpansionStateLogger expansionStateLogger, NotificationPanelLogger notificationPanelLogger) { return new NotificationLogger( @@ -135,11 +147,18 @@ public interface NotificationsModule { visibilityProvider, notifPipeline, statusBarStateController, - shadeExpansionStateManager, + windowRootViewVisibilityInteractor, + javaAdapter, expansionStateLogger, notificationPanelLogger); } + /** Binds {@link NotificationLogger} as a {@link CoreStartable}. */ + @Binds + @IntoMap + @ClassKey(NotificationLogger.class) + CoreStartable bindsNotificationLogger(NotificationLogger notificationLogger); + /** Provides an instance of {@link NotificationPanelLogger} */ @SysUISingleton @Provides diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java index 26f97de95070..d2034d7a8564 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java @@ -33,10 +33,11 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.CoreStartable; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; -import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore; @@ -48,6 +49,7 @@ import com.android.systemui.statusbar.notification.dagger.NotificationsModule; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.util.Compile; +import com.android.systemui.util.kotlin.JavaAdapter; import java.util.Collection; import java.util.Collections; @@ -62,7 +64,7 @@ import javax.inject.Inject; * Handles notification logging, in particular, logging which notifications are visible and which * are not. */ -public class NotificationLogger implements StateListener { +public class NotificationLogger implements StateListener, CoreStartable { static final String TAG = "NotificationLogger"; private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); @@ -81,6 +83,8 @@ public class NotificationLogger implements StateListener { private final NotifPipeline mNotifPipeline; private final NotificationPanelLogger mNotificationPanelLogger; private final ExpansionStateLogger mExpansionStateLogger; + private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; + private final JavaAdapter mJavaAdapter; protected Handler mHandler = new Handler(); protected IStatusBarService mBarService; @@ -88,10 +92,7 @@ public class NotificationLogger implements StateListener { private NotificationListContainer mListContainer; private final Object mDozingLock = new Object(); @GuardedBy("mDozingLock") - private Boolean mDozing = null; // Use null to indicate state is not yet known - @GuardedBy("mDozingLock") private Boolean mLockscreen = null; // Use null to indicate state is not yet known - private Boolean mPanelExpanded = null; // Use null to indicate state is not yet known private boolean mLogging = false; // Tracks notifications currently visible in mNotificationStackScroller and @@ -202,7 +203,8 @@ public class NotificationLogger implements StateListener { NotificationVisibilityProvider visibilityProvider, NotifPipeline notifPipeline, StatusBarStateController statusBarStateController, - ShadeExpansionStateManager shadeExpansionStateManager, + WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor, + JavaAdapter javaAdapter, ExpansionStateLogger expansionStateLogger, NotificationPanelLogger notificationPanelLogger) { mNotificationListener = notificationListener; @@ -214,9 +216,10 @@ public class NotificationLogger implements StateListener { ServiceManager.getService(Context.STATUS_BAR_SERVICE)); mExpansionStateLogger = expansionStateLogger; mNotificationPanelLogger = notificationPanelLogger; + mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor; + mJavaAdapter = javaAdapter; // Not expected to be destroyed, don't need to unsubscribe statusBarStateController.addCallback(this); - shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged); registerNewPipelineListener(); } @@ -239,6 +242,22 @@ public class NotificationLogger implements StateListener { mListContainer = listContainer; } + @Override + public void start() { + mJavaAdapter.alwaysCollectFlow( + mWindowRootViewVisibilityInteractor.isLockscreenOrShadeVisibleAndInteractive(), + this::onLockscreenOrShadeVisibleAndInteractiveChanged); + } + + private void onLockscreenOrShadeVisibleAndInteractiveChanged(boolean visible) { + if (visible) { + startNotificationLogging(); + } else { + // Ensure we stop notification logging when the device isn't interactive. + stopNotificationLogging(); + } + } + public void stopNotificationLogging() { if (mLogging) { mLogging = false; @@ -257,16 +276,19 @@ public class NotificationLogger implements StateListener { } } + @GuardedBy("mDozingLock") public void startNotificationLogging() { if (!mLogging) { mLogging = true; if (DEBUG) { Log.i(TAG, "startNotificationLogging"); } + boolean lockscreen = mLockscreen != null && mLockscreen; + mNotificationPanelLogger.logPanelShown(lockscreen, getVisibleNotifications()); mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged); - // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't - // cause the scroller to emit child location events. Hence generate - // one ourselves to guarantee that we're reporting visible + // Sometimes, the transition from lockscreenOrShadeVisible=false -> + // lockscreenOrShadeVisible=true doesn't cause the scroller to emit child location + // events. Hence generate one ourselves to guarantee that we're reporting visible // notifications. // (Note that in cases where the scroller does emit events, this // additional event doesn't break anything.) @@ -274,13 +296,6 @@ public class NotificationLogger implements StateListener { } } - private void setDozing(boolean dozing) { - synchronized (mDozingLock) { - mDozing = dozing; - maybeUpdateLoggingStatus(); - } - } - private void logNotificationVisibilityChanges( Collection<NotificationVisibility> newlyVisible, Collection<NotificationVisibility> noLongerVisible) { @@ -362,39 +377,6 @@ public class NotificationLogger implements StateListener { } } - @Override - public void onDozingChanged(boolean isDozing) { - if (DEBUG) { - Log.i(TAG, "onDozingChanged: new=" + isDozing); - } - setDozing(isDozing); - } - - @GuardedBy("mDozingLock") - private void maybeUpdateLoggingStatus() { - if (mPanelExpanded == null || mDozing == null) { - if (DEBUG) { - Log.i(TAG, "Panel status unclear: panelExpandedKnown=" - + (mPanelExpanded == null) + " dozingKnown=" + (mDozing == null)); - } - return; - } - // Once we know panelExpanded and Dozing, turn logging on & off when appropriate - boolean lockscreen = mLockscreen == null ? false : mLockscreen; - if (mPanelExpanded && !mDozing) { - mNotificationPanelLogger.logPanelShown(lockscreen, getVisibleNotifications()); - if (DEBUG) { - Log.i(TAG, "Notification panel shown, lockscreen=" + lockscreen); - } - startNotificationLogging(); - } else { - if (DEBUG) { - Log.i(TAG, "Notification panel hidden, lockscreen=" + lockscreen); - } - stopNotificationLogging(); - } - } - /** * Called when the notification is expanded / collapsed. */ @@ -404,20 +386,6 @@ public class NotificationLogger implements StateListener { } @VisibleForTesting - void onShadeExpansionFullyChanged(Boolean isExpanded) { - // mPanelExpanded is initialized as null - if (mPanelExpanded == null || !mPanelExpanded.equals(isExpanded)) { - if (DEBUG) { - Log.i(TAG, "onPanelExpandedChanged: new=" + isExpanded); - } - mPanelExpanded = isExpanded; - synchronized (mDozingLock) { - maybeUpdateLoggingStatus(); - } - } - } - - @VisibleForTesting void onChildLocationsChanged() { if (mHandler.hasCallbacks(mVisibilityReporter)) { // Visibilities will be reported when the existing diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index d92d11b18d74..c02382dcde94 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -74,7 +74,6 @@ import com.android.internal.util.ContrastColorUtil; import com.android.internal.widget.CachingIconView; import com.android.internal.widget.CallLayout; import com.android.systemui.R; -import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.flags.ViewRefactorFlag; @@ -245,7 +244,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private NotificationEntry mEntry; private String mAppName; private FalsingManager mFalsingManager; - private FalsingCollector mFalsingCollector; /** * Whether or not the notification is using the heads up view and should peek from the top. @@ -1711,7 +1709,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView OnExpandClickListener onExpandClickListener, CoordinateOnClickListener onFeedbackClickListener, FalsingManager falsingManager, - FalsingCollector falsingCollector, StatusBarStateController statusBarStateController, PeopleNotificationIdentifier peopleNotificationIdentifier, OnUserInteractionCallback onUserInteractionCallback, @@ -1743,7 +1740,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mOnExpandClickListener = onExpandClickListener; setOnFeedbackClickListener(onFeedbackClickListener); mFalsingManager = falsingManager; - mFalsingCollector = falsingCollector; mStatusBarStateController = statusBarStateController; mPeopleNotificationIdentifier = peopleNotificationIdentifier; for (NotificationContentView l : mLayouts) { @@ -2471,7 +2467,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * @param allowChildExpansion whether a call to this method allows expanding children */ public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) { - mFalsingCollector.setNotificationExpanded(); if (mIsSummaryWithChildren && !shouldShowPublic() && allowChildExpansion && !mChildrenContainer.showingAsLowPriority()) { final boolean wasExpanded = mGroupExpansionManager.isGroupExpanded(mEntry); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index 80f5d1939ac0..af55f44b785a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -34,7 +34,6 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.IStatusBarService; -import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.plugins.FalsingManager; @@ -102,7 +101,6 @@ public class ExpandableNotificationRowController implements NotifViewController private final NotificationGutsManager mNotificationGutsManager; private final OnUserInteractionCallback mOnUserInteractionCallback; private final FalsingManager mFalsingManager; - private final FalsingCollector mFalsingCollector; private final FeatureFlags mFeatureFlags; private final boolean mAllowLongPress; private final PeopleNotificationIdentifier mPeopleNotificationIdentifier; @@ -222,7 +220,6 @@ public class ExpandableNotificationRowController implements NotifViewController @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress, OnUserInteractionCallback onUserInteractionCallback, FalsingManager falsingManager, - FalsingCollector falsingCollector, FeatureFlags featureFlags, PeopleNotificationIdentifier peopleNotificationIdentifier, Optional<BubblesManager> bubblesManagerOptional, @@ -251,7 +248,6 @@ public class ExpandableNotificationRowController implements NotifViewController mFalsingManager = falsingManager; mOnFeedbackClickListener = mNotificationGutsManager::openGuts; mAllowLongPress = allowLongPress; - mFalsingCollector = falsingCollector; mFeatureFlags = featureFlags; mPeopleNotificationIdentifier = peopleNotificationIdentifier; mBubblesManagerOptional = bubblesManagerOptional; @@ -285,7 +281,6 @@ public class ExpandableNotificationRowController implements NotifViewController mOnExpandClickListener, mOnFeedbackClickListener, mFalsingManager, - mFalsingCollector, mStatusBarStateController, mPeopleNotificationIdentifier, mOnUserInteractionCallback, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 6f79ea8c543b..44ead26de012 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -45,6 +45,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.settingslib.notification.ConversationIconFactory; +import com.android.systemui.CoreStartable; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; @@ -53,6 +54,7 @@ import com.android.systemui.people.widget.PeopleSpaceWidgetManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.settings.UserContextProvider; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -69,6 +71,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationListContain import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.wmshell.BubblesManager; import java.util.Optional; @@ -80,7 +83,7 @@ import javax.inject.Inject; * closing guts, and keeping track of the currently exposed notification guts. */ @SysUISingleton -public class NotificationGutsManager implements NotifGutsViewManager { +public class NotificationGutsManager implements NotifGutsViewManager, CoreStartable { private static final String TAG = "NotificationGutsManager"; // Must match constant in Settings. Used to highlight preferences when linking to Settings. @@ -109,6 +112,7 @@ public class NotificationGutsManager implements NotifGutsViewManager { private final Handler mMainHandler; private final Handler mBgHandler; + private final JavaAdapter mJavaAdapter; private final Optional<BubblesManager> mBubblesManagerOptional; private Runnable mOpenRunnable; private final INotificationManager mNotificationManager; @@ -121,6 +125,7 @@ public class NotificationGutsManager implements NotifGutsViewManager { private final UserContextProvider mContextTracker; private final UiEventLogger mUiEventLogger; private final ShadeController mShadeController; + private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; private NotifGutsViewListener mGutsListener; private final HeadsUpManagerPhone mHeadsUpManagerPhone; private final ActivityStarter mActivityStarter; @@ -129,6 +134,7 @@ public class NotificationGutsManager implements NotifGutsViewManager { public NotificationGutsManager(Context context, @Main Handler mainHandler, @Background Handler bgHandler, + JavaAdapter javaAdapter, AccessibilityManager accessibilityManager, HighPriorityProvider highPriorityProvider, INotificationManager notificationManager, @@ -143,6 +149,7 @@ public class NotificationGutsManager implements NotifGutsViewManager { UiEventLogger uiEventLogger, OnUserInteractionCallback onUserInteractionCallback, ShadeController shadeController, + WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor, NotificationLockscreenUserManager notificationLockscreenUserManager, StatusBarStateController statusBarStateController, DeviceProvisionedController deviceProvisionedController, @@ -152,6 +159,7 @@ public class NotificationGutsManager implements NotifGutsViewManager { mContext = context; mMainHandler = mainHandler; mBgHandler = bgHandler; + mJavaAdapter = javaAdapter; mAccessibilityManager = accessibilityManager; mHighPriorityProvider = highPriorityProvider; mNotificationManager = notificationManager; @@ -166,6 +174,7 @@ public class NotificationGutsManager implements NotifGutsViewManager { mUiEventLogger = uiEventLogger; mOnUserInteractionCallback = onUserInteractionCallback; mShadeController = shadeController; + mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor; mLockscreenUserManager = notificationLockscreenUserManager; mStatusBarStateController = statusBarStateController; mDeviceProvisionedController = deviceProvisionedController; @@ -187,6 +196,25 @@ public class NotificationGutsManager implements NotifGutsViewManager { mNotificationActivityStarter = notificationActivityStarter; } + @Override + public void start() { + mJavaAdapter.alwaysCollectFlow( + mWindowRootViewVisibilityInteractor.isLockscreenOrShadeVisible(), + this::onLockscreenShadeVisibilityChanged); + } + + private void onLockscreenShadeVisibilityChanged(boolean visible) { + if (!visible) { + closeAndSaveGuts( + /* removeLeavebehind= */ true , + /* force= */ true, + /* removeControls= */ true, + /* x= */ -1, + /* y= */ -1, + /* resetMenu= */ true); + } + } + public void onDensityOrFontScaleChanged(NotificationEntry entry) { setExposedGuts(entry.getGuts()); bindGuts(entry.getRow()); @@ -512,7 +540,7 @@ public class NotificationGutsManager implements NotifGutsViewManager { mNotificationGutsExposed.removeCallbacks(mOpenRunnable); mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force); } - if (resetMenu) { + if (resetMenu && mListContainer != null) { mListContainer.resetExposedMenuView(false /* animate */, true /* force */); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 6db8df91edcc..d8f513c493f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -455,7 +455,6 @@ public class NotificationStackScrollLayoutController { @Override public void onDragCancelled(View v) { - mFalsingCollector.onNotificationStopDismissing(); } /** @@ -501,7 +500,6 @@ public class NotificationStackScrollLayoutController { } mView.addSwipedOutView(view); - mFalsingCollector.onNotificationDismissed(); if (mFalsingCollector.shouldEnforceBouncer()) { mActivityStarter.executeRunnableDismissingKeyguard( null, @@ -549,7 +547,6 @@ public class NotificationStackScrollLayoutController { @Override public void onBeginDrag(View v) { - mFalsingCollector.onNotificationStartDismissing(); mView.onSwipeBegin(v); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index d0a093cf905a..cbb39150392c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -200,10 +200,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner { void onKeyguardViewManagerStatesUpdated(); - boolean isPulsing(); - - boolean isOccluded(); - boolean isDeviceInVrMode(); NotificationPresenter getPresenter(); @@ -247,8 +243,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner { @Deprecated float getDisplayHeight(); - void readyForKeyguardDone(); - void showKeyguard(); boolean hideKeyguard(); @@ -370,8 +364,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner { @Deprecated float getDisplayDensity(); - void extendDozePulse(); - public static class KeyboardShortcutsMessage { final int mDeviceId; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt new file mode 100644 index 000000000000..10422e31dfaa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone + +import android.content.Intent +import androidx.lifecycle.LifecycleRegistry +import com.android.keyguard.AuthKeyguardMessageArea +import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.navigationbar.NavigationBarView +import com.android.systemui.plugins.ActivityStarter.OnDismissAction +import com.android.systemui.qs.QSPanelController +import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import java.io.PrintWriter + +/** + * Empty implementation of [CentralSurfaces] for variants only need to override portions of the + * interface. + * + * **Important**: Prefer binding an Optional<CentralSurfaces> to an empty optional instead of + * including this class. + */ +abstract class CentralSurfacesEmptyImpl : CentralSurfaces { + override val lifecycle = LifecycleRegistry(this) + override fun updateIsKeyguard() = false + override fun updateIsKeyguard(forceStateChange: Boolean) = false + override fun getKeyguardMessageArea(): AuthKeyguardMessageArea? = null + override fun isLaunchingActivityOverLockscreen() = false + override fun onKeyguardViewManagerStatesUpdated() {} + override fun isDeviceInVrMode() = false + override fun getPresenter(): NotificationPresenter? = null + override fun onInputFocusTransfer(start: Boolean, cancel: Boolean, velocity: Float) {} + override fun getCommandQueuePanelsEnabled() = false + override fun getBiometricUnlockController(): BiometricUnlockController? = null + override fun showWirelessChargingAnimation(batteryLevel: Int) {} + override fun checkBarModes() {} + override fun updateBubblesVisibility() {} + override fun setInteracting(barWindow: Int, interacting: Boolean) {} + override fun dump(pwOriginal: PrintWriter, args: Array<String>) {} + override fun getDisplayWidth() = 0f + override fun getDisplayHeight() = 0f + override fun showKeyguard() {} + override fun hideKeyguard() = false + override fun showKeyguardImpl() {} + override fun fadeKeyguardAfterLaunchTransition( + beforeFading: Runnable?, + endRunnable: Runnable?, + cancelRunnable: Runnable?, + ) {} + override fun startLaunchTransitionTimeout() {} + override fun hideKeyguardImpl(forceStateChange: Boolean) = false + override fun keyguardGoingAway() {} + override fun setKeyguardFadingAway(startTime: Long, delay: Long, fadeoutDuration: Long) {} + override fun finishKeyguardFadingAway() {} + override fun userActivity() {} + override fun endAffordanceLaunch() {} + override fun shouldKeyguardHideImmediately() = false + override fun showBouncerWithDimissAndCancelIfKeyguard( + performAction: OnDismissAction?, + cancelAction: Runnable?, + ) {} + override fun getNavigationBarView(): NavigationBarView? = null + override fun isOverviewEnabled() = false + override fun showPinningEnterExitToast(entering: Boolean) {} + override fun showPinningEscapeToast() {} + override fun setBouncerShowing(bouncerShowing: Boolean) {} + override fun getWakefulnessState() = 0 + override fun isScreenFullyOff() = false + override fun showScreenPinningRequest(taskId: Int, allowCancel: Boolean) {} + override fun getEmergencyActionIntent(): Intent? = null + override fun isCameraAllowedByAdmin() = false + override fun isGoingToSleep() = false + override fun notifyBiometricAuthModeChanged() {} + override fun setTransitionToFullShadeProgress(transitionToFullShadeProgress: Float) {} + override fun setPrimaryBouncerHiddenFraction(expansion: Float) {} + override fun updateScrimController() {} + override fun isKeyguardShowing() = false + override fun shouldIgnoreTouch() = false + override fun isDeviceInteractive() = false + override fun awakenDreams() {} + override fun clearNotificationEffects() {} + override fun isBouncerShowing() = false + override fun isBouncerShowingScrimmed() = false + override fun isBouncerShowingOverDream() = false + override fun isKeyguardSecure() = false + override fun updateNotificationPanelTouchState() {} + override fun getRotation() = 0 + override fun setBarStateForTest(state: Int) {} + override fun showTransientUnchecked() {} + override fun clearTransient() {} + override fun acquireGestureWakeLock(time: Long) {} + override fun setAppearance(appearance: Int) = false + override fun getBarMode() = 0 + override fun resendMessage(msg: Int) {} + override fun resendMessage(msg: Any?) {} + override fun setLastCameraLaunchSource(source: Int) {} + override fun setLaunchCameraOnFinishedGoingToSleep(launch: Boolean) {} + override fun setLaunchCameraOnFinishedWaking(launch: Boolean) {} + override fun setLaunchEmergencyActionOnFinishedGoingToSleep(launch: Boolean) {} + override fun setLaunchEmergencyActionOnFinishedWaking(launch: Boolean) {} + override fun getQSPanelController(): QSPanelController? = null + override fun getDisplayDensity() = 0f + override fun setIsLaunchingActivityOverLockscreen(isLaunchingActivityOverLockscreen: Boolean) {} + override fun getAnimatorControllerFromNotification( + associatedView: ExpandableNotificationRow?, + ): ActivityLaunchAnimator.Controller? = null +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 6a3ebe63da08..d91f37504c2e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -92,17 +92,12 @@ import android.view.IWindowManager; import android.view.MotionEvent; import android.view.ThreadedRenderer; import android.view.View; -import android.view.ViewRootImpl; import android.view.WindowInsets; import android.view.WindowInsetsController.Appearance; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; import android.widget.DateTimeView; -import android.window.BackEvent; -import android.window.OnBackAnimationCallback; -import android.window.OnBackInvokedCallback; -import android.window.OnBackInvokedDispatcher; import androidx.annotation.NonNull; import androidx.lifecycle.Lifecycle; @@ -176,6 +171,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.qs.QSFragment; import com.android.systemui.qs.QSPanelController; import com.android.systemui.recents.ScreenPinningRequest; +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.scrim.ScrimView; import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.brightness.BrightnessSliderController; @@ -221,7 +217,6 @@ import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; -import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -462,6 +457,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final CentralSurfacesComponent.Factory mCentralSurfacesComponentFactory; private final PluginManager mPluginManager; private final ShadeController mShadeController; + private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; private final InitController mInitController; private final Lazy<CameraLauncher> mCameraLauncherLazy; private final AlternateBouncerInteractor mAlternateBouncerInteractor; @@ -489,13 +485,11 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private View mReportRejectedTouch; private final NotificationGutsManager mGutsManager; - private final NotificationLogger mNotificationLogger; private final ShadeExpansionStateManager mShadeExpansionStateManager; private final KeyguardViewMediator mKeyguardViewMediator; protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider; private final BrightnessSliderController.Factory mBrightnessSliderFactory; private final FeatureFlags mFeatureFlags; - private final boolean mAnimateBack; private final FragmentService mFragmentService; private final ScreenOffAnimationController mScreenOffAnimationController; private final WallpaperController mWallpaperController; @@ -508,13 +502,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private CentralSurfacesComponent mCentralSurfacesComponent; - /** - * This keeps track of whether we have (or haven't) registered the predictive back callback. - * Since we can have visible -> visible transitions, we need to avoid - * double-registering (or double-unregistering) our callback. - */ - private boolean mIsBackCallbackRegistered = false; - /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */ private @Appearance int mAppearance; @@ -640,31 +627,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final InteractionJankMonitor mJankMonitor; - /** Existing callback that handles back gesture invoked for the Shade. */ - private final OnBackInvokedCallback mOnBackInvokedCallback; - - /** - * New callback that handles back gesture invoked, cancel, progress - * and provides feedback via Shade animation. - * (enabled via the WM_SHADE_ANIMATE_BACK_GESTURE flag) - */ - private final OnBackAnimationCallback mOnBackAnimationCallback = new OnBackAnimationCallback() { - @Override - public void onBackInvoked() { - mBackActionInteractor.onBackRequested(); - } - - @Override - public void onBackProgressed(BackEvent event) { - if (mBackActionInteractor.shouldBackBeHandled()) { - if (mShadeSurface.canBeCollapsed()) { - float fraction = event.getProgress(); - mShadeSurface.onBackProgressed(fraction); - } - } - } - }; - /** * Public constructor for CentralSurfaces. * @@ -694,7 +656,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { FalsingCollector falsingCollector, BroadcastDispatcher broadcastDispatcher, NotificationGutsManager notificationGutsManager, - NotificationLogger notificationLogger, NotificationInterruptStateProvider notificationInterruptStateProvider, ShadeExpansionStateManager shadeExpansionStateManager, KeyguardViewMediator keyguardViewMediator, @@ -745,6 +706,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { Lazy<CentralSurfacesCommandQueueCallbacks> commandQueueCallbacksLazy, PluginManager pluginManager, ShadeController shadeController, + WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor, StatusBarKeyguardViewManager statusBarKeyguardViewManager, ViewMediatorCallback viewMediatorCallback, InitController initController, @@ -803,7 +765,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mFalsingManager = falsingManager; mBroadcastDispatcher = broadcastDispatcher; mGutsManager = notificationGutsManager; - mNotificationLogger = notificationLogger; mNotificationInterruptStateProvider = notificationInterruptStateProvider; mShadeExpansionStateManager = shadeExpansionStateManager; mKeyguardViewMediator = keyguardViewMediator; @@ -856,6 +817,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mCommandQueueCallbacksLazy = commandQueueCallbacksLazy; mPluginManager = pluginManager; mShadeController = shadeController; + mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mKeyguardViewMediatorCallback = viewMediatorCallback; mInitController = initController; @@ -926,14 +888,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI)) { mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true); } - // Based on teamfood flag, enable predictive back animation for the Shade. - mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE); - mOnBackInvokedCallback = () -> { - if (DEBUG) { - Log.d(TAG, "mOnBackInvokedCallback() called"); - } - mBackActionInteractor.onBackRequested(); - }; } private void initBubbles(Bubbles bubbles) { @@ -1171,7 +1125,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { new FoldStateListener(mContext, this::onFoldedStateChanged)); } - @VisibleForTesting + /** + * @deprecated use {@link + * WindowRootViewVisibilityInteractor.isLockscreenOrShadeVisible} instead. + */ @VisibleForTesting void initShadeVisibilityListener() { mShadeController.setVisibilityListener(new ShadeController.ShadeVisibilityListener() { @Override @@ -1480,7 +1437,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // - Shade is in QQS over keyguard - swiping up should take us back to keyguard if (!isKeyguardShowing() || mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing() - || isOccluded() + || mKeyguardStateController.isOccluded() || !mKeyguardStateController.canDismissLockScreen() || mKeyguardViewMediator.isAnySimPinSecure() || (mQsController.getExpanded() && trackingTouch) @@ -1563,6 +1520,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mNotifListContainer, mStackScrollerController.getNotifStackController(), mNotificationActivityStarter); + mWindowRootViewVisibilityInteractor.setUp(mPresenter, mNotificationsController); } /** @@ -1709,22 +1667,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } @Override - public boolean isPulsing() { - return mDozeServiceHost.isPulsing(); - } - - /** - * When the keyguard is showing and covered by a "showWhenLocked" activity it - * is occluded. This is controlled by {@link com.android.server.policy.PhoneWindowManager} - * - * @return whether the keyguard is currently occluded - */ - @Override - public boolean isOccluded() { - return mKeyguardStateController.isOccluded(); - } - - @Override public boolean isDeviceInVrMode() { return mPresenter.isDeviceInVrMode(); } @@ -1963,8 +1905,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { pw.print(" mDozing="); pw.println(mDozing); pw.print(" mWallpaperSupported= "); pw.println(mWallpaperSupported); - pw.println(" ShadeWindowView: "); - getNotificationShadeWindowViewController().dump(pw, args); CentralSurfaces.dumpBarTransitions( pw, "PhoneStatusBarTransitions", mStatusBarTransitions); @@ -2077,11 +2017,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return mDisplay.getRotation(); } - @Override - public void readyForKeyguardDone() { - mStatusBarKeyguardViewManager.readyForKeyguardDone(); - } - private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -2170,82 +2105,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { com.android.systemui.R.dimen.physical_power_button_center_screen_location_y)); } - protected void handleVisibleToUserChanged(boolean visibleToUser) { - if (visibleToUser) { - onVisibleToUser(); - mNotificationLogger.startNotificationLogging(); - - if (!mIsBackCallbackRegistered) { - ViewRootImpl viewRootImpl = getViewRootImpl(); - if (viewRootImpl != null) { - viewRootImpl.getOnBackInvokedDispatcher() - .registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, - mAnimateBack ? mOnBackAnimationCallback - : mOnBackInvokedCallback); - mIsBackCallbackRegistered = true; - if (DEBUG) Log.d(TAG, "is now VISIBLE to user AND callback registered"); - } - } else { - if (DEBUG) Log.d(TAG, "is now VISIBLE to user, BUT callback ALREADY unregistered"); - } - } else { - mNotificationLogger.stopNotificationLogging(); - onInvisibleToUser(); - - if (mIsBackCallbackRegistered) { - ViewRootImpl viewRootImpl = getViewRootImpl(); - if (viewRootImpl != null) { - viewRootImpl.getOnBackInvokedDispatcher() - .unregisterOnBackInvokedCallback( - mAnimateBack ? mOnBackAnimationCallback - : mOnBackInvokedCallback); - mIsBackCallbackRegistered = false; - if (DEBUG) Log.d(TAG, "is NOT VISIBLE to user, AND callback unregistered"); - } - } else { - if (DEBUG) { - Log.d(TAG, - "is NOT VISIBLE to user, BUT NO callback (or callback ALREADY " - + "unregistered)"); - } - } - } - } - - void onVisibleToUser() { - /* The LEDs are turned off when the notification panel is shown, even just a little bit. - * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do - * this. - */ - boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp(); - boolean clearNotificationEffects = - !mPresenter.isPresenterFullyCollapsed() && (mState == StatusBarState.SHADE - || mState == StatusBarState.SHADE_LOCKED); - int notificationLoad = mNotificationsController.getActiveNotificationsCount(); - if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) { - notificationLoad = 1; - } - final int finalNotificationLoad = notificationLoad; - mUiBgExecutor.execute(() -> { - try { - mBarService.onPanelRevealed(clearNotificationEffects, - finalNotificationLoad); - } catch (RemoteException ex) { - // Won't fail unless the world has ended. - } - }); - } - - void onInvisibleToUser() { - mUiBgExecutor.execute(() -> { - try { - mBarService.onPanelHidden(); - } catch (RemoteException ex) { - // Won't fail unless the world has ended. - } - }); - } - private void logStateToEventlog() { boolean isShowing = mKeyguardStateController.isShowing(); boolean isOccluded = mKeyguardStateController.isOccluded(); @@ -2325,11 +2184,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // there's no surface we can show to the user. Note that the device goes fully interactive // late in the transition, so we also allow the device to start dozing once the screen has // turned off fully. + boolean keyguardShowingUnOccluded = + mKeyguardStateController.isShowing() && !mKeyguardStateController.isOccluded(); boolean keyguardForDozing = mDozeServiceHost.getDozingRequested() && (!mDeviceInteractive || (isGoingToSleep() - && (isScreenFullyOff() - || (mKeyguardStateController.isShowing() && !isOccluded())))); - boolean isWakingAndOccluded = isOccluded() && isWakingOrAwake(); + && (isScreenFullyOff() || keyguardShowingUnOccluded))); + boolean isWakingAndOccluded = mKeyguardStateController.isOccluded() && isWakingOrAwake(); boolean shouldBeKeyguard = (mStatusBarStateController.isKeyguardRequested() || keyguardForDozing) && !wakeAndUnlocking && !isWakingAndOccluded; if (keyguardForDozing) { @@ -2728,11 +2588,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mNavigationBarController.showPinningEscapeToast(mDisplayId); } - protected ViewRootImpl getViewRootImpl() { - View root = mNotificationShadeWindowController.getWindowRootView(); - if (root != null) return root.getViewRootImpl(); - return null; - } /** * Propagation of the bouncer state, indicating that it's fully visible. */ @@ -2779,7 +2634,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { releaseGestureWakeLock(); mLaunchCameraWhenFinishedWaking = false; mDeviceInteractive = false; - updateVisibleToUser(); updateNotificationPanelTouchState(); getNotificationShadeWindowViewController().cancelCurrentTouch(); @@ -2852,12 +2706,14 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // cancelling a sleep), from the power button, on a device with a power button // FPS, and 'press to unlock' is required. mShouldDelayWakeUpAnimation = - !isPulsing() + !mDozeServiceHost.isPulsing() && mStatusBarStateController.getDozeAmount() == 1f && mWakefulnessLifecycle.getLastWakeReason() == PowerManager.WAKE_REASON_POWER_BUTTON && mFingerprintManager.get().isPowerbuttonFps() - && mFingerprintManager.get().hasEnrolledFingerprints() + && mKeyguardUpdateMonitor + .getCachedIsUnlockWithFingerprintPossible( + mUserTracker.getUserId()) && !touchToUnlockAnytime; if (DEBUG_WAKEUP_DELAY) { Log.d(TAG, "mShouldDelayWakeUpAnimation=" + mShouldDelayWakeUpAnimation); @@ -2876,7 +2732,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { /* wakingUp= */ true, mShouldDelayWakeUpAnimation); - updateVisibleToUser(); updateIsKeyguard(); mShouldDelayLockscreenTransitionFromAod = mDozeParameters.getAlwaysOn() && mFeatureFlags.isEnabled( @@ -3128,7 +2983,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mScrimController.setExpansionAffectsAlpha(!unlocking); if (mAlternateBouncerInteractor.isVisibleState()) { - if ((!isOccluded() || mShadeSurface.isPanelExpanded()) + if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded()) && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED || mTransitionToFullShadeProgress > 0f)) { mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE); @@ -3164,7 +3019,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // This will cancel the keyguardFadingAway animation if it is running. We need to do // this as otherwise it can remain pending and leave keyguard in a weird state. mUnlockScrimCallback.onCancelled(); - } else if (mKeyguardStateController.isShowing() && !isOccluded() && !unlocking) { + } else if (mKeyguardStateController.isShowing() + && !mKeyguardStateController.isOccluded() + && !unlocking) { mScrimController.transitionTo(ScrimState.KEYGUARD); } else if (mKeyguardStateController.isShowing() && mKeyguardUpdateMonitor.isDreaming() && !unlocking) { @@ -3203,9 +3060,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { protected boolean mVisible; - // mScreenOnFromKeyguard && mVisible. - private boolean mVisibleToUser; - protected DevicePolicyManager mDevicePolicyManager; private final PowerManager mPowerManager; protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @@ -3329,21 +3183,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { if (visible) { DejankUtils.notifyRendererOfExpensiveFrame( getNotificationShadeWindowView(), "onShadeVisibilityChanged"); - } else { - mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, - true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */); } } - updateVisibleToUser(); - } - - protected void updateVisibleToUser() { - boolean oldVisibleToUser = mVisibleToUser; - mVisibleToUser = mVisible && mDeviceInteractive; - - if (oldVisibleToUser != mVisibleToUser) { - handleVisibleToUserChanged(mVisibleToUser); - } } /** @@ -3401,11 +3242,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } - @Override - public void extendDozePulse(){ - mDozeScrimController.extendPulse(); - } - private final KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() { @Override @@ -3524,7 +3360,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // If we're visible and switched to SHADE_LOCKED (the user dragged // down on the lockscreen), clear notification LED, vibration, // ringing. - // Other transitions are covered in handleVisibleToUserChanged(). + // Other transitions are covered in WindowRootViewVisibilityInteractor. if (mVisible && (newState == StatusBarState.SHADE_LOCKED || mStatusBarStateController.goingToFullShade())) { clearNotificationEffects(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java index 801cdbf0e83e..4849f64659d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java @@ -185,7 +185,7 @@ public final class DozeServiceHost implements DozeHost { return mDozingRequested; } - boolean isPulsing() { + public boolean isPulsing() { return mPulsing; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 62a8cfde80da..b0f8276e460d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -763,7 +763,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump * Sets the amount of vertical over scroll that should be performed on the notifications scrim. */ public void setNotificationsOverScrollAmount(int overScrollAmount) { - mNotificationsScrim.setTranslationY(overScrollAmount); + if (mNotificationsScrim != null) mNotificationsScrim.setTranslationY(overScrollAmount); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java index d5cb6b6606c0..4878d45cec84 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -81,11 +81,7 @@ public interface StatusBarIconController { /** Removes an icon that had come from an active tile service. */ void removeIconForTile(String slot); - /** - * Adds or updates an icon for the given slot for **internal system icons**. - * - * TODO(b/265307726): Re-name this to `setInternalIcon`. - */ + /** Adds or updates an icon for the given slot for **internal system icons**. */ void setIcon(String slot, int resourceId, CharSequence contentDescription); /** @@ -127,11 +123,6 @@ public interface StatusBarIconController { */ void removeIcon(String slot, int tag); - /** - * TODO(b/265307726): Re-name this to `removeAllIconsForInternalSlot`. - */ - void removeAllIconsForSlot(String slot); - // TODO: See if we can rename this tunable name. String ICON_HIDE_LIST = "icon_blacklist"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java index 553cbc5f0803..0f4d68c68d00 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java @@ -385,13 +385,7 @@ public class StatusBarIconControllerImpl implements Tunable, } private void removeAllIconsForExternalSlot(String slotName) { - removeAllIconsForSlot(createExternalSlotName(slotName)); - } - - /** */ - @Override - public void removeAllIconsForSlot(String slotName) { - removeAllIconsForSlot(slotName, /* fromNewPipeline */ false); + removeAllIconsForSlot(createExternalSlotName(slotName), /* fromNewPipeline= */ false); } private void removeAllIconsForSlot(String slotName, boolean fromNewPipeline) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractor.kt new file mode 100644 index 000000000000..3709e4c06009 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractor.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.ethernet.domain + +import com.android.settingslib.AccessibilityContentDescriptions +import com.android.systemui.R +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** + * Currently we don't do much to interact with ethernet. We simply need a place to map between the + * connectivity state of a default ethernet connection, and an icon representing that connection. + */ +@SysUISingleton +class EthernetInteractor +@Inject +constructor( + connectivityRepository: ConnectivityRepository, +) { + /** Icon representing the current connectivity status of the ethernet connection */ + val icon: Flow<Icon.Resource?> = + connectivityRepository.defaultConnections.map { + if (it.ethernet.isDefault) { + if (it.isValidated) { + Icon.Resource( + R.drawable.stat_sys_ethernet_fully, + ContentDescription.Resource( + AccessibilityContentDescriptions.ETHERNET_CONNECTION_VALUES[1] + ) + ) + } else { + Icon.Resource( + R.drawable.stat_sys_ethernet, + ContentDescription.Resource( + AccessibilityContentDescriptions.ETHERNET_CONNECTION_VALUES[0] + ) + ) + } + } else { + null + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt index 051e88f9264d..02473f276b4b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt @@ -16,9 +16,11 @@ package com.android.systemui.statusbar.pipeline.mobile.data +import android.content.Intent import android.telephony.ServiceState import android.telephony.SignalStrength import android.telephony.TelephonyDisplayInfo +import android.telephony.TelephonyManager import com.android.settingslib.SignalIcon import com.android.settingslib.mobile.MobileMappings import com.android.systemui.dagger.SysUISingleton @@ -162,6 +164,28 @@ constructor( fun logOnSubscriptionsChanged() { buffer.log(TAG, LogLevel.INFO, {}, { "onSubscriptionsChanged" }) } + + fun logServiceProvidersUpdatedBroadcast(intent: Intent) { + val showSpn = intent.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false) + val spn = intent.getStringExtra(TelephonyManager.EXTRA_DATA_SPN) + val showPlmn = intent.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false) + val plmn = intent.getStringExtra(TelephonyManager.EXTRA_PLMN) + + buffer.log( + TAG, + LogLevel.INFO, + { + bool1 = showSpn + str1 = spn + bool2 = showPlmn + str2 = plmn + }, + { + "Intent: ACTION_SERVICE_PROVIDERS_UPDATED." + + " showSpn=$bool1 spn=$str1 showPlmn=$bool2 plmn=$str2" + } + ) + } } private const val TAG = "MobileInputLog" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt index 1f1ac92b3956..cd6862113ee9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt @@ -16,12 +16,16 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod +import android.annotation.SuppressLint +import android.content.BroadcastReceiver +import android.content.Context import android.content.Intent import android.content.IntentFilter import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN import android.telephony.CellSignalStrengthCdma import android.telephony.ServiceState import android.telephony.SignalStrength +import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import android.telephony.TelephonyCallback import android.telephony.TelephonyDisplayInfo @@ -34,6 +38,7 @@ import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID import com.android.settingslib.Utils import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.log.table.TableLogBuffer @@ -81,6 +86,7 @@ import kotlinx.coroutines.flow.stateIn @OptIn(ExperimentalCoroutinesApi::class) class MobileConnectionRepositoryImpl( override val subId: Int, + private val context: Context, subscriptionModel: StateFlow<SubscriptionModel?>, defaultNetworkName: NetworkNameModel, networkNameSeparator: String, @@ -323,16 +329,35 @@ class MobileConnectionRepositoryImpl( } .stateIn(scope, SharingStarted.WhileSubscribed(), telephonyManager.simCarrierId) + /** BroadcastDispatcher does not handle sticky broadcasts, so we can't use it here */ + @SuppressLint("RegisterReceiverViaContext") override val networkName: StateFlow<NetworkNameModel> = - broadcastDispatcher - .broadcastFlow( - filter = IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED), - map = { intent, _ -> intent }, - ) - .filter { intent -> - intent.getIntExtra(EXTRA_SUBSCRIPTION_ID, INVALID_SUBSCRIPTION_ID) == subId + conflatedCallbackFlow { + val receiver = + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if ( + intent.getIntExtra( + EXTRA_SUBSCRIPTION_INDEX, + INVALID_SUBSCRIPTION_ID + ) == subId + ) { + logger.logServiceProvidersUpdatedBroadcast(intent) + trySend( + intent.toNetworkNameModel(networkNameSeparator) + ?: defaultNetworkName + ) + } + } + } + + context.registerReceiver( + receiver, + IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED) + ) + + awaitClose { context.unregisterReceiver(receiver) } } - .map { intent -> intent.toNetworkNameModel(networkNameSeparator) ?: defaultNetworkName } .stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName) override val dataEnabled = run { @@ -349,6 +374,7 @@ class MobileConnectionRepositoryImpl( class Factory @Inject constructor( + private val context: Context, private val broadcastDispatcher: BroadcastDispatcher, private val telephonyManager: TelephonyManager, private val logger: MobileInputLogger, @@ -366,6 +392,7 @@ class MobileConnectionRepositoryImpl( ): MobileConnectionRepository { return MobileConnectionRepositoryImpl( subId, + context, subscriptionModel, defaultNetworkName, networkNameSeparator, 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 4cfde5bd5622..4bf297cd088c 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 @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor import android.content.Context -import android.telephony.CarrierConfigManager import com.android.settingslib.SignalIcon.MobileIconGroup +import com.android.settingslib.graph.SignalDrawable import com.android.settingslib.mobile.MobileIconCarrierIdOverrides import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl import com.android.systemui.dagger.qualifiers.Application @@ -31,6 +31,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConn import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.DefaultIcon import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.OverriddenIcon +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -60,24 +61,17 @@ interface MobileIconInteractor { */ val isDataConnected: StateFlow<Boolean> - /** Only true if mobile is the default transport but is not validated, otherwise false */ - val isDefaultConnectionFailed: StateFlow<Boolean> - /** True if we consider this connection to be in service, i.e. can make calls */ val isInService: StateFlow<Boolean> - // TODO(b/256839546): clarify naming of default vs active - /** True if we want to consider the data connection enabled */ - val isDefaultDataEnabled: StateFlow<Boolean> - /** Observable for the data enabled state of this connection */ val isDataEnabled: StateFlow<Boolean> /** True if the RAT icon should always be displayed and false otherwise. */ val alwaysShowDataRatIcon: StateFlow<Boolean> - /** True if the CDMA level should be preferred over the primary level. */ - val alwaysUseCdmaLevel: StateFlow<Boolean> + /** Canonical representation of the current mobile signal strength as a triangle. */ + val signalLevelIcon: StateFlow<SignalIconModel> /** Observable for RAT type (network type) indicator */ val networkTypeIconGroup: StateFlow<NetworkTypeIconModel> @@ -108,9 +102,6 @@ interface MobileIconInteractor { /** True if there is only one active subscription. */ val isSingleCarrier: StateFlow<Boolean> - /** True if this line of service is emergency-only */ - val isEmergencyOnly: StateFlow<Boolean> - /** * True if this connection is considered roaming. The roaming bit can come from [ServiceState], * or directly from the telephony manager's CDMA ERI number value. Note that we don't consider a @@ -118,12 +109,6 @@ interface MobileIconInteractor { */ val isRoaming: StateFlow<Boolean> - /** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */ - val level: StateFlow<Int> - - /** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */ - val numberOfLevels: StateFlow<Int> - /** See [MobileIconsInteractor.isForceHidden]. */ val isForceHidden: Flow<Boolean> @@ -141,12 +126,12 @@ class MobileIconInteractorImpl( @Application scope: CoroutineScope, defaultSubscriptionHasDataEnabled: StateFlow<Boolean>, override val alwaysShowDataRatIcon: StateFlow<Boolean>, - override val alwaysUseCdmaLevel: StateFlow<Boolean>, + alwaysUseCdmaLevel: StateFlow<Boolean>, override val isSingleCarrier: StateFlow<Boolean>, override val mobileIsDefault: StateFlow<Boolean>, defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>, defaultMobileIconGroup: StateFlow<MobileIconGroup>, - override val isDefaultConnectionFailed: StateFlow<Boolean>, + isDefaultConnectionFailed: StateFlow<Boolean>, override val isForceHidden: Flow<Boolean>, connectionRepository: MobileConnectionRepository, private val context: Context, @@ -170,8 +155,6 @@ class MobileIconInteractorImpl( .distinctUntilChanged() .stateIn(scope, SharingStarted.WhileSubscribed(), false) - override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled - override val networkName = combine(connectionRepository.operatorAlphaShort, connectionRepository.networkName) { operatorAlphaShort, @@ -255,8 +238,6 @@ class MobileIconInteractorImpl( DefaultIcon(defaultMobileIconGroup.value), ) - override val isEmergencyOnly = connectionRepository.isEmergencyOnly - override val isRoaming: StateFlow<Boolean> = combine( connectionRepository.carrierNetworkChangeActive, @@ -274,7 +255,7 @@ class MobileIconInteractorImpl( } .stateIn(scope, SharingStarted.WhileSubscribed(), false) - override val level: StateFlow<Int> = + private val level: StateFlow<Int> = combine( connectionRepository.isGsm, connectionRepository.primaryLevel, @@ -290,7 +271,7 @@ class MobileIconInteractorImpl( } .stateIn(scope, SharingStarted.WhileSubscribed(), 0) - override val numberOfLevels: StateFlow<Int> = + private val numberOfLevels: StateFlow<Int> = connectionRepository.numberOfLevels.stateIn( scope, SharingStarted.WhileSubscribed(), @@ -305,4 +286,54 @@ class MobileIconInteractorImpl( override val isInService = connectionRepository.isInService override val isAllowedDuringAirplaneMode = connectionRepository.isAllowedDuringAirplaneMode + + /** Whether or not to show the error state of [SignalDrawable] */ + private val showExclamationMark: StateFlow<Boolean> = + combine( + defaultSubscriptionHasDataEnabled, + isDefaultConnectionFailed, + isInService, + ) { isDefaultDataEnabled, isDefaultConnectionFailed, isInService -> + !isDefaultDataEnabled || isDefaultConnectionFailed || !isInService + } + .stateIn(scope, SharingStarted.WhileSubscribed(), true) + + private val shownLevel: StateFlow<Int> = + combine( + level, + isInService, + ) { level, isInService -> + if (isInService) level else 0 + } + .stateIn(scope, SharingStarted.WhileSubscribed(), 0) + + override val signalLevelIcon: StateFlow<SignalIconModel> = run { + val initial = + SignalIconModel( + level = shownLevel.value, + numberOfLevels = numberOfLevels.value, + showExclamationMark = showExclamationMark.value, + carrierNetworkChange = carrierNetworkChangeActive.value, + ) + combine( + shownLevel, + numberOfLevels, + showExclamationMark, + carrierNetworkChangeActive, + ) { shownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange -> + SignalIconModel( + shownLevel, + numberOfLevels, + showExclamationMark, + carrierNetworkChange, + ) + } + .distinctUntilChanged() + .logDiffsForTable( + tableLogBuffer, + columnPrefix = "icon", + initialValue = initial, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), initial) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt index d08808b65399..62150e9a1236 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt @@ -33,6 +33,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupR import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository import com.android.systemui.util.CarrierConfigTracker +import java.lang.ref.WeakReference import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -70,6 +71,12 @@ interface MobileIconsInteractor { /** True if the active mobile data subscription has data enabled */ val activeDataConnectionHasDataEnabled: StateFlow<Boolean> + /** + * Flow providing a reference to the Interactor for the active data subId. This represents the + * [MobileConnectionInteractor] responsible for the active data connection, if any. + */ + val activeDataIconInteractor: StateFlow<MobileIconInteractor?> + /** True if the RAT icon should always be displayed and false otherwise. */ val alwaysShowDataRatIcon: StateFlow<Boolean> @@ -96,9 +103,9 @@ interface MobileIconsInteractor { /** * Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given - * subId. Will throw if the ID is invalid + * subId. */ - fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor + fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor } @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @@ -116,6 +123,9 @@ constructor( private val context: Context, ) : MobileIconsInteractor { + // Weak reference lookup for created interactors + private val reuseCache = mutableMapOf<Int, WeakReference<MobileIconInteractor>>() + override val mobileIsDefault = combine( mobileConnectionsRepo.mobileIsDefault, @@ -138,6 +148,17 @@ constructor( .flatMapLatest { it?.dataEnabled ?: flowOf(false) } .stateIn(scope, SharingStarted.WhileSubscribed(), false) + override val activeDataIconInteractor: StateFlow<MobileIconInteractor?> = + mobileConnectionsRepo.activeMobileDataSubscriptionId + .mapLatest { + if (it != null) { + getMobileConnectionInteractorForSubId(it) + } else { + null + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), null) + private val unfilteredSubscriptions: Flow<List<SubscriptionModel>> = mobileConnectionsRepo.subscriptions @@ -306,21 +327,25 @@ constructor( .stateIn(scope, SharingStarted.WhileSubscribed(), false) /** Vends out new [MobileIconInteractor] for a particular subId */ - override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor = + override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor = + reuseCache[subId]?.get() ?: createMobileConnectionInteractorForSubId(subId) + + private fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor = MobileIconInteractorImpl( - scope, - activeDataConnectionHasDataEnabled, - alwaysShowDataRatIcon, - alwaysUseCdmaLevel, - isSingleCarrier, - mobileIsDefault, - defaultMobileIconMapping, - defaultMobileIconGroup, - isDefaultConnectionFailed, - isForceHidden, - mobileConnectionsRepo.getRepoForSubId(subId), - context, - ) + scope, + activeDataConnectionHasDataEnabled, + alwaysShowDataRatIcon, + alwaysUseCdmaLevel, + isSingleCarrier, + mobileIsDefault, + defaultMobileIconMapping, + defaultMobileIconGroup, + isDefaultConnectionFailed, + isForceHidden, + mobileConnectionsRepo.getRepoForSubId(subId), + context, + ) + .also { reuseCache[subId] = WeakReference(it) } companion object { private const val LOGGING_PREFIX = "Intr" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt index 6de3f85027ae..e58f08183157 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.pipeline.mobile.ui.model +package com.android.systemui.statusbar.pipeline.mobile.domain.model import com.android.settingslib.graph.SignalDrawable import com.android.systemui.log.table.Diffable diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt index cffc833eaf88..a1a5370819f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt @@ -22,8 +22,8 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.pipeline.dagger.VerboseMobileViewLog +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger.Companion.getIdForLogging -import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel import javax.inject.Inject /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt index 275cfc58d581..dfabeea96082 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt @@ -17,14 +17,13 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH -import com.android.settingslib.graph.SignalDrawable import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor -import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import kotlinx.coroutines.CoroutineScope @@ -76,26 +75,6 @@ constructor( constants: ConnectivityConstants, scope: CoroutineScope, ) : MobileIconViewModelCommon { - /** Whether or not to show the error state of [SignalDrawable] */ - private val showExclamationMark: StateFlow<Boolean> = - combine( - iconInteractor.isDefaultDataEnabled, - iconInteractor.isDefaultConnectionFailed, - iconInteractor.isInService, - ) { isDefaultDataEnabled, isDefaultConnectionFailed, isInService -> - !isDefaultDataEnabled || isDefaultConnectionFailed || !isInService - } - .stateIn(scope, SharingStarted.WhileSubscribed(), true) - - private val shownLevel: StateFlow<Int> = - combine( - iconInteractor.level, - iconInteractor.isInService, - ) { level, isInService -> - if (isInService) level else 0 - } - .stateIn(scope, SharingStarted.WhileSubscribed(), 0) - override val isVisible: StateFlow<Boolean> = if (!constants.hasDataCapabilities) { flowOf(false) @@ -123,40 +102,12 @@ constructor( ) .stateIn(scope, SharingStarted.WhileSubscribed(), false) - override val icon: Flow<SignalIconModel> = run { - val initial = - SignalIconModel( - level = shownLevel.value, - numberOfLevels = iconInteractor.numberOfLevels.value, - showExclamationMark = showExclamationMark.value, - carrierNetworkChange = iconInteractor.carrierNetworkChangeActive.value, - ) - combine( - shownLevel, - iconInteractor.numberOfLevels, - showExclamationMark, - iconInteractor.carrierNetworkChangeActive, - ) { shownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange -> - SignalIconModel( - shownLevel, - numberOfLevels, - showExclamationMark, - carrierNetworkChange, - ) - } - .distinctUntilChanged() - .logDiffsForTable( - iconInteractor.tableLogBuffer, - columnPrefix = "icon", - initialValue = initial, - ) - .stateIn(scope, SharingStarted.WhileSubscribed(), initial) - } + override val icon: Flow<SignalIconModel> = iconInteractor.signalLevelIcon override val contentDescription: Flow<ContentDescription> = run { val initial = ContentDescription.Resource(PHONE_SIGNAL_STRENGTH[0]) - shownLevel - .map { ContentDescription.Resource(PHONE_SIGNAL_STRENGTH[it]) } + iconInteractor.signalLevelIcon + .map { ContentDescription.Resource(PHONE_SIGNAL_STRENGTH[it.level]) } .stateIn(scope, SharingStarted.WhileSubscribed(), initial) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt index 216afb987f91..a4ec3a36694d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt @@ -101,7 +101,7 @@ constructor( val common = commonViewModelForSub(subId) return LocationBasedMobileViewModel.viewModelForLocation( common, - mobileIconInteractorForSub(subId), + interactor.getMobileConnectionInteractorForSubId(subId), verboseLogger, location, scope, @@ -112,7 +112,7 @@ constructor( return mobileIconSubIdCache[subId] ?: MobileIconViewModel( subId, - mobileIconInteractorForSub(subId), + interactor.getMobileConnectionInteractorForSubId(subId), airplaneModeInteractor, constants, scope, @@ -120,14 +120,6 @@ constructor( .also { mobileIconSubIdCache[subId] = it } } - @VisibleForTesting - fun mobileIconInteractorForSub(subId: Int): MobileIconInteractor { - return mobileIconInteractorSubIdCache[subId] - ?: interactor.createMobileConnectionInteractorForSubId(subId).also { - mobileIconInteractorSubIdCache[subId] = it - } - } - private fun invalidateCaches(subIds: List<Int>) { val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) } subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/InternetTileBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/InternetTileBinder.kt new file mode 100644 index 000000000000..189dc40d275f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/InternetTileBinder.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.shared.ui.binder + +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.coroutineScope +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileModel +import java.util.function.Consumer +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +/** + * Binds an [InternetTileModel] flow to a consumer for the internet tile to apply to its qs state + */ +object InternetTileBinder { + fun bind( + lifecycle: Lifecycle, + tileModelFlow: StateFlow<InternetTileModel>, + consumer: Consumer<InternetTileModel> + ) { + lifecycle.coroutineScope.launch { + lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) { + tileModelFlow.collect { consumer.accept(it) } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt new file mode 100644 index 000000000000..327dd8d442e8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.shared.ui.model + +import android.content.Context +import android.graphics.drawable.Drawable +import android.service.quicksettings.Tile +import com.android.settingslib.graph.SignalDrawable +import com.android.systemui.common.shared.model.Text +import com.android.systemui.common.shared.model.Text.Companion.loadText +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.tileimpl.QSTileImpl + +/** Model describing the state that the QS Internet tile should be in. */ +sealed interface InternetTileModel { + val secondaryTitle: String? + val secondaryLabel: Text? + val iconId: Int? + val icon: QSTile.Icon? + + fun applyTo(state: QSTile.SignalState, context: Context) { + if (secondaryLabel != null) { + state.secondaryLabel = secondaryLabel.loadText(context) + } else { + state.secondaryLabel = secondaryTitle + } + + // inout indicators are unused + state.activityIn = false + state.activityOut = false + + // To support both SignalDrawable and other icons, give priority to icons over IDs + if (icon != null) { + state.icon = icon + } else if (iconId != null) { + state.icon = QSTileImpl.ResourceIcon.get(iconId!!) + } + + state.state = + if (this is Active) { + Tile.STATE_ACTIVE + } else { + Tile.STATE_INACTIVE + } + } + + data class Active( + override val secondaryTitle: String? = null, + override val secondaryLabel: Text? = null, + override val iconId: Int? = null, + override val icon: QSTile.Icon? = null, + ) : InternetTileModel + + data class Inactive( + override val secondaryTitle: String? = null, + override val secondaryLabel: Text? = null, + override val iconId: Int? = null, + override val icon: QSTile.Icon? = null, + ) : InternetTileModel +} + +/** + * [QSTile.Icon]-compatible container class for us to marshal the compacted [SignalDrawable] state + * across to the internet tile. + */ +data class SignalIcon(val state: Int) : QSTile.Icon() { + + override fun getDrawable(context: Context): Drawable { + val d = SignalDrawable(context) + d.setLevel(state) + return d + } + + override fun toString(): String { + return String.format("SignalIcon[mState=0x%08x]", state) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt new file mode 100644 index 000000000000..120ba4eba094 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel + +import android.content.Context +import com.android.systemui.R +import com.android.systemui.common.shared.model.Text +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon +import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository +import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor +import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository +import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileModel +import com.android.systemui.statusbar.pipeline.shared.ui.model.SignalIcon +import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +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.stateIn + +/** + * View model for the quick settings [InternetTile]. This model exposes mainly a single flow of + * InternetTileModel objects, so that updating the tile is as simple as collecting on this state + * flow and then calling [QSTileImpl.refreshState] + */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class InternetTileViewModel +@Inject +constructor( + airplaneModeRepository: AirplaneModeRepository, + connectivityRepository: ConnectivityRepository, + ethernetInteractor: EthernetInteractor, + mobileIconsInteractor: MobileIconsInteractor, + wifiInteractor: WifiInteractor, + private val context: Context, + @Application scope: CoroutineScope, +) { + // Three symmetrical Flows that can be switched upon based on the value of + // [DefaultConnectionModel] + private val wifiIconFlow: Flow<InternetTileModel> = + wifiInteractor.wifiNetwork.flatMapLatest { + val wifiIcon = WifiIcon.fromModel(it, context) + if (it is WifiNetworkModel.Active && wifiIcon is WifiIcon.Visible) { + flowOf( + InternetTileModel.Active( + secondaryTitle = removeDoubleQuotes(it.ssid), + icon = ResourceIcon.get(wifiIcon.icon.res) + ) + ) + } else { + notConnectedFlow + } + } + + private val mobileDataContentName: Flow<CharSequence?> = + mobileIconsInteractor.activeDataIconInteractor.flatMapLatest { + if (it == null) { + flowOf(null) + } else { + combine(it.isRoaming, it.networkTypeIconGroup) { isRoaming, networkTypeIconGroup -> + val cd = loadString(networkTypeIconGroup.contentDescription) + if (isRoaming) { + val roaming = context.getString(R.string.data_connection_roaming) + if (cd != null) { + context.getString(R.string.mobile_data_text_format, roaming, cd) + } else { + roaming + } + } else { + cd + } + } + } + } + + private val mobileIconFlow: Flow<InternetTileModel> = + mobileIconsInteractor.activeDataIconInteractor.flatMapLatest { + if (it == null) { + notConnectedFlow + } else { + combine( + it.networkName, + it.signalLevelIcon, + mobileDataContentName, + ) { networkNameModel, signalIcon, dataContentDescription -> + InternetTileModel.Active( + secondaryTitle = + mobileDataContentConcat(networkNameModel.name, dataContentDescription), + icon = SignalIcon(signalIcon.toSignalDrawableState()), + ) + } + } + } + + private fun mobileDataContentConcat( + networkName: String?, + dataContentDescription: CharSequence? + ): String { + if (dataContentDescription == null) { + return networkName ?: "" + } + if (networkName == null) { + return dataContentDescription.toString() + } + + return context.getString( + R.string.mobile_carrier_text_format, + networkName, + dataContentDescription + ) + } + + private fun loadString(resId: Int): String? = + if (resId > 0) { + context.getString(resId) + } else { + null + } + + private val ethernetIconFlow: Flow<InternetTileModel> = + ethernetInteractor.icon.flatMapLatest { + if (it == null) { + notConnectedFlow + } else { + flowOf( + InternetTileModel.Active( + secondaryTitle = it.contentDescription.toString(), + iconId = it.res + ) + ) + } + } + + private val notConnectedFlow: StateFlow<InternetTileModel> = + combine( + wifiInteractor.areNetworksAvailable, + airplaneModeRepository.isAirplaneMode, + ) { networksAvailable, isAirplaneMode -> + when { + isAirplaneMode -> { + InternetTileModel.Inactive( + secondaryTitle = context.getString(R.string.status_bar_airplane), + icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable) + ) + } + networksAvailable -> { + InternetTileModel.Inactive( + secondaryTitle = + context.getString(R.string.quick_settings_networks_available), + iconId = R.drawable.ic_qs_no_internet_available, + ) + } + else -> { + NOT_CONNECTED_NETWORKS_UNAVAILABLE + } + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), NOT_CONNECTED_NETWORKS_UNAVAILABLE) + + /** + * Strict ordering of which repo is sending its data to the internet tile. Swaps between each of + * the interim providers (wifi, mobile, ethernet, or not-connected) + */ + private val activeModelProvider: Flow<InternetTileModel> = + connectivityRepository.defaultConnections.flatMapLatest { + when { + it.ethernet.isDefault -> ethernetIconFlow + it.mobile.isDefault || it.carrierMerged.isDefault -> mobileIconFlow + it.wifi.isDefault -> wifiIconFlow + else -> notConnectedFlow + } + } + + /** Consumable flow describing the correct state for the InternetTile */ + val tileModel: StateFlow<InternetTileModel> = + activeModelProvider.stateIn(scope, SharingStarted.WhileSubscribed(), notConnectedFlow.value) + + companion object { + val NOT_CONNECTED_NETWORKS_UNAVAILABLE = + InternetTileModel.Inactive( + secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable), + iconId = R.drawable.ic_qs_no_internet_unavailable, + ) + + private fun removeDoubleQuotes(string: String?): String? { + if (string == null) return null + val length = string.length + return if (length > 1 && string[0] == '"' && string[length - 1] == '"') { + string.substring(1, length - 1) + } else string + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt index b5b99a76ded8..b22e09e9ba40 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository import com.android.systemui.CoreStartable import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry import kotlinx.coroutines.flow.StateFlow /** Provides data related to the wifi state. */ @@ -45,6 +46,12 @@ interface WifiRepository { val wifiActivity: StateFlow<DataActivityModel> /** + * The list of known wifi networks, per [WifiManager.scanResults]. This list is passively + * updated and does not trigger a scan. + */ + val wifiScanResults: StateFlow<List<WifiScanEntry>> + + /** * Returns true if the device is currently connected to a wifi network with a valid SSID and * false otherwise. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt index 80091ac7bdba..ca042e26b20b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt @@ -27,6 +27,7 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityMod import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -124,4 +125,9 @@ constructor( activeRepo .flatMapLatest { it.wifiActivity } .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.wifiActivity.value) + + override val wifiScanResults: StateFlow<List<WifiScanEntry>> = + activeRepo + .flatMapLatest { it.wifiScanResults } + .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.wifiScanResults.value) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt index 4b19c3a153ed..152d181bfc16 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt @@ -22,6 +22,7 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActiv import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -55,6 +56,10 @@ constructor( MutableStateFlow(DataActivityModel(hasActivityIn = false, hasActivityOut = false)) override val wifiActivity: StateFlow<DataActivityModel> = _wifiActivity + private val _wifiScanResults: MutableStateFlow<List<WifiScanEntry>> = + MutableStateFlow(emptyList()) + override val wifiScanResults: StateFlow<List<WifiScanEntry>> = _wifiScanResults + fun startProcessingCommands() { demoCommandJob = scope.launch { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt index 36c46a90c731..cfdbe4a9bb6e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt @@ -21,6 +21,7 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityMod import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -49,6 +50,9 @@ class DisabledWifiRepository @Inject constructor() : override val wifiActivity: StateFlow<DataActivityModel> = MutableStateFlow(ACTIVITY).asStateFlow() + override val wifiScanResults: StateFlow<List<WifiScanEntry>> = + MutableStateFlow<List<WifiScanEntry>>(emptyList()).asStateFlow() + companion object { private val NETWORK = WifiNetworkModel.Unavailable private val ACTIVITY = DataActivityModel(hasActivityIn = false, hasActivityOut = false) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt index f1b98b3972e1..67dd32f473e0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt @@ -16,15 +16,21 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod +import android.annotation.SuppressLint +import android.net.wifi.ScanResult import android.net.wifi.WifiManager import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry import java.util.concurrent.Executor +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.asExecutor import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -64,6 +70,34 @@ object WifiRepositoryHelper { ) } + /** + * Creates a flow that listens for new [ScanResult]s from [WifiManager]. Does not request a scan + */ + fun createNetworkScanFlow( + wifiManager: WifiManager, + scope: CoroutineScope, + @Background dispatcher: CoroutineDispatcher, + inputLogger: () -> Unit, + ): StateFlow<List<WifiScanEntry>> { + return conflatedCallbackFlow { + val callback = + object : WifiManager.ScanResultsCallback() { + @SuppressLint("MissingPermission") + override fun onScanResultsAvailable() { + inputLogger.invoke() + trySend(wifiManager.scanResults.toModel()) + } + } + + wifiManager.registerScanResultsCallback(dispatcher.asExecutor(), callback) + + awaitClose { wifiManager.unregisterScanResultsCallback(callback) } + } + .stateIn(scope, SharingStarted.Eagerly, emptyList()) + } + + private fun List<ScanResult>.toModel(): List<WifiScanEntry> = map { WifiScanEntry(it.SSID) } + // TODO(b/292534484): This print should only be done in [MessagePrinter] part of the log buffer. private fun prettyPrintActivity(activity: Int): String { return when (activity) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt index 7c7b58d00e3b..59ef8846e281 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt @@ -48,6 +48,7 @@ import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiReposito import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry import java.util.concurrent.Executor import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -231,6 +232,14 @@ constructor( logger::logActivity, ) + override val wifiScanResults: StateFlow<List<WifiScanEntry>> = + WifiRepositoryHelper.createNetworkScanFlow( + wifiManager, + scope, + bgDispatcher, + logger::logScanResults + ) + companion object { // Start out with no known wifi network. // Note: [WifiStatusTracker] (the old implementation of connectivity logic) does do an diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt index d4f40dd03a32..9b404f187c88 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt @@ -23,6 +23,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags @@ -42,6 +43,7 @@ import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRep import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_STATE_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive.toHotspotDeviceType +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry import com.android.wifitrackerlib.HotspotNetworkEntry import com.android.wifitrackerlib.MergedCarrierEntry import com.android.wifitrackerlib.WifiEntry @@ -51,6 +53,7 @@ import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE import com.android.wifitrackerlib.WifiPickerTracker import java.util.concurrent.Executor import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.SharingStarted @@ -73,6 +76,7 @@ constructor( featureFlags: FeatureFlags, @Application private val scope: CoroutineScope, @Main private val mainExecutor: Executor, + @Background private val bgDispatcher: CoroutineDispatcher, private val wifiPickerTrackerFactory: WifiPickerTrackerFactory, private val wifiManager: WifiManager, @WifiTrackerLibInputLog private val inputLogger: LogBuffer, @@ -300,6 +304,14 @@ constructor( this::logActivity, ) + override val wifiScanResults: StateFlow<List<WifiScanEntry>> = + WifiRepositoryHelper.createNetworkScanFlow( + wifiManager, + scope, + bgDispatcher, + this::logScanResults, + ) + private fun logOnWifiEntriesChanged(connectedEntry: WifiEntry?) { inputLogger.log( TAG, @@ -322,6 +334,9 @@ constructor( inputLogger.log(TAG, LogLevel.DEBUG, { str1 = activity }, { "onActivityChanged: $str1" }) } + private fun logScanResults() = + inputLogger.log(TAG, LogLevel.DEBUG, {}, { "onScanResultsAvailable" }) + /** * Data class storing all the information fetched from [WifiPickerTracker]. * @@ -345,6 +360,7 @@ constructor( private val featureFlags: FeatureFlags, @Application private val scope: CoroutineScope, @Main private val mainExecutor: Executor, + @Background private val bgDispatcher: CoroutineDispatcher, private val wifiPickerTrackerFactory: WifiPickerTrackerFactory, @WifiTrackerLibInputLog private val inputLogger: LogBuffer, @WifiTrackerLibTableLog private val wifiTrackerLibTableLogBuffer: TableLogBuffer, @@ -354,6 +370,7 @@ constructor( featureFlags, scope, mainExecutor, + bgDispatcher, wifiPickerTrackerFactory, wifiManager, inputLogger, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt index 1a41abf031bf..110e3390e722 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt @@ -17,15 +17,21 @@ package com.android.systemui.statusbar.pipeline.wifi.domain.interactor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn /** * The business logic layer for the wifi icon. @@ -54,6 +60,9 @@ interface WifiInteractor { /** True if we're configured to force-hide the wifi icon and false otherwise. */ val isForceHidden: Flow<Boolean> + + /** True if there are networks available other than the currently-connected one */ + val areNetworksAvailable: StateFlow<Boolean> } @SysUISingleton @@ -62,6 +71,7 @@ class WifiInteractorImpl constructor( connectivityRepository: ConnectivityRepository, wifiRepository: WifiRepository, + @Application scope: CoroutineScope, ) : WifiInteractor { override val ssid: Flow<String?> = @@ -91,4 +101,26 @@ constructor( override val isForceHidden: Flow<Boolean> = connectivityRepository.forceHiddenSlots.map { it.contains(ConnectivitySlot.WIFI) } + + override val areNetworksAvailable: StateFlow<Boolean> = + combine( + wifiNetwork, + wifiRepository.wifiScanResults, + ) { currentNetwork, scanResults -> + // We consider networks to be available if the scan results list contains networks + // other than the one that is currently connected + if (scanResults.isEmpty()) { + false + } else if (currentNetwork !is WifiNetworkModel.Active) { + true + } else { + anyNonMatchingNetworkExists(currentNetwork, scanResults) + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + private fun anyNonMatchingNetworkExists( + currentNetwork: WifiNetworkModel.Active, + availableNetworks: List<WifiScanEntry> + ): Boolean = availableNetworks.firstOrNull { it.ssid != currentNetwork.ssid } != null } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt index f244376f5a5a..b76bb51bfcb8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt @@ -59,6 +59,8 @@ constructor( fun logActivity(activity: String) { buffer.log(TAG, LogLevel.DEBUG, { str1 = activity }, { "Activity: $str1" }) } + + fun logScanResults() = buffer.log(TAG, LogLevel.DEBUG, {}, { "onScanResultsAvailable" }) } private const val TAG = "WifiInputLog" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiScanEntry.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiScanEntry.kt new file mode 100644 index 000000000000..d4a5a0e5cca9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiScanEntry.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.wifi.shared.model + +/** + * Represents a single entry in the scan results callback. Use the [ssid] field to check against + * other networks + */ +data class WifiScanEntry(val ssid: String) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt index 094bcf96eef9..8156500528a7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt @@ -17,10 +17,18 @@ package com.android.systemui.statusbar.pipeline.wifi.ui.model import android.annotation.DrawableRes +import android.content.Context +import androidx.annotation.StringRes +import androidx.annotation.VisibleForTesting +import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH +import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION +import com.android.systemui.R import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.log.table.Diffable import com.android.systemui.log.table.TableRowLogger +import com.android.systemui.statusbar.connectivity.WifiIcons +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel /** Represents the various states of the wifi icon. */ sealed interface WifiIcon : Diffable<WifiIcon> { @@ -34,7 +42,7 @@ sealed interface WifiIcon : Diffable<WifiIcon> { * description. */ class Visible( - @DrawableRes res: Int, + @DrawableRes val res: Int, val contentDescription: ContentDescription.Loaded, ) : WifiIcon { val icon = Icon.Resource(res, contentDescription) @@ -51,6 +59,46 @@ sealed interface WifiIcon : Diffable<WifiIcon> { override fun logFull(row: TableRowLogger) { row.logChange(COL_ICON, toString()) } + + companion object { + @StringRes + @VisibleForTesting + internal val NO_INTERNET = R.string.data_connection_no_internet + + /** Mapping from a [WifiNetworkModel] to the appropriate [WifiIcon] */ + fun fromModel(model: WifiNetworkModel, context: Context): WifiIcon = + when (model) { + is WifiNetworkModel.Unavailable -> Hidden + is WifiNetworkModel.Invalid -> Hidden + is WifiNetworkModel.CarrierMerged -> Hidden + is WifiNetworkModel.Inactive -> + Visible( + res = WifiIcons.WIFI_NO_NETWORK, + ContentDescription.Loaded( + "${context.getString(WIFI_NO_CONNECTION)},${context.getString( + NO_INTERNET + )}" + ) + ) + is WifiNetworkModel.Active -> { + val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[model.level]) + when { + model.isValidated -> + Visible( + WifiIcons.WIFI_FULL_ICONS[model.level], + ContentDescription.Loaded(levelDesc), + ) + else -> + Visible( + WifiIcons.WIFI_NO_INTERNET_ICONS[model.level], + ContentDescription.Loaded( + "$levelDesc,${context.getString(NO_INTERNET)}" + ), + ) + } + } + } + } } private const val COL_ICON = "icon" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt index d9c214452ef1..27ac7b9be6e6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt @@ -17,19 +17,10 @@ package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel import android.content.Context -import androidx.annotation.StringRes -import androidx.annotation.VisibleForTesting -import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH -import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION -import com.android.systemui.R -import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable -import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS -import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS -import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule.Companion.FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog @@ -77,35 +68,7 @@ constructor( ) : WifiViewModelCommon { /** Returns the icon to use based on the given network. */ private fun WifiNetworkModel.icon(): WifiIcon { - return when (this) { - is WifiNetworkModel.Unavailable -> WifiIcon.Hidden - is WifiNetworkModel.Invalid -> WifiIcon.Hidden - is WifiNetworkModel.CarrierMerged -> WifiIcon.Hidden - is WifiNetworkModel.Inactive -> - WifiIcon.Visible( - res = WIFI_NO_NETWORK, - ContentDescription.Loaded( - "${context.getString(WIFI_NO_CONNECTION)},${context.getString(NO_INTERNET)}" - ) - ) - is WifiNetworkModel.Active -> { - val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level]) - when { - this.isValidated -> - WifiIcon.Visible( - WIFI_FULL_ICONS[this.level], - ContentDescription.Loaded(levelDesc), - ) - else -> - WifiIcon.Visible( - WIFI_NO_INTERNET_ICONS[this.level], - ContentDescription.Loaded( - "$levelDesc,${context.getString(NO_INTERNET)}" - ), - ) - } - } - } + return WifiIcon.fromModel(this, context) } override val wifiIcon: StateFlow<WifiIcon> = @@ -186,10 +149,4 @@ constructor( airplaneModeViewModel.isAirplaneModeIconVisible override val isSignalSpacerVisible: Flow<Boolean> = shouldShowSignalSpacerProvider.get() - - companion object { - @StringRes - @VisibleForTesting - internal val NO_INTERNET = R.string.data_connection_no_internet - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java index 1212585d9829..feef02913416 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java @@ -16,9 +16,15 @@ package com.android.systemui.statusbar.policy; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.Log; import android.view.View; import com.android.systemui.R; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.util.Compile; /** * A class of utility static methods for heads up notifications. @@ -26,12 +32,18 @@ import com.android.systemui.R; public final class HeadsUpUtil { private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag; + private static final String LOG_TAG = "HeadsUpUtil"; + private static final boolean LOG_DEBUG = Compile.IS_DEBUG && Log.isLoggable(LOG_TAG, Log.DEBUG); + /** * Set the given view as clicked or not-clicked. * @param view The view to be set the flag to. * @param clicked True to set as clicked. False to not-clicked. */ public static void setNeedsHeadsUpDisappearAnimationAfterClick(View view, boolean clicked) { + if (LOG_DEBUG) { + logTagClickedNotificationChanged(view, clicked); + } view.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null); } @@ -44,4 +56,36 @@ public final class HeadsUpUtil { Boolean clicked = (Boolean) view.getTag(TAG_CLICKED_NOTIFICATION); return clicked != null && clicked; } + + private static void logTagClickedNotificationChanged(@Nullable View view, boolean isClicked) { + if (view == null) { + return; + } + + final boolean wasClicked = isClickedHeadsUpNotification(view); + if (isClicked == wasClicked) { + return; + } + + Log.d(LOG_TAG, getViewKey(view) + ": TAG_CLICKED_NOTIFICATION set to " + isClicked); + } + + private static @NonNull String getViewKey(@NonNull View view) { + if (!(view instanceof ExpandableNotificationRow)) { + return "(not a row)"; + } + + final ExpandableNotificationRow row = (ExpandableNotificationRow) view; + final NotificationEntry entry = row.getEntry(); + if (entry == null) { + return "(null entry)"; + } + + final String key = entry.getKey(); + if (key == null) { + return "(null key)"; + } + + return key; + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt index 3a9473085583..e1b608ffb1d6 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt @@ -16,15 +16,16 @@ package com.android.keyguard -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.inputmethod.InputMethodManager import android.widget.EditText import android.widget.ImageView +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.internal.widget.LockPatternUtils import com.android.systemui.R +import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.flags.FakeFeatureFlags @@ -45,7 +46,8 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RoboPilotTest +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class KeyguardPasswordViewControllerTest : SysuiTestCase() { @Mock private lateinit var keyguardPasswordView: KeyguardPasswordView diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt index 1acd676f02cd..93048a5787b2 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt @@ -16,15 +16,16 @@ package com.android.keyguard -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.internal.widget.LockPatternUtils import com.android.systemui.R +import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorFake @@ -52,7 +53,8 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RoboPilotTest +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class KeyguardPatternViewControllerTest : SysuiTestCase() { private lateinit var mKeyguardPatternView: KeyguardPatternView diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java index efe1955595ca..2b90e7c66e8d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java @@ -22,16 +22,17 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.R; +import com.android.systemui.RoboPilotTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.classifier.FalsingCollectorFake; @@ -46,7 +47,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RoboPilotTest +@RunWith(AndroidJUnit4.class) @RunWithLooper public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index 80fd7213778e..61acacdb99a8 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt @@ -16,16 +16,17 @@ package com.android.keyguard -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.internal.widget.LockPatternUtils import com.android.keyguard.KeyguardSecurityModel.SecurityMode import com.android.systemui.R +import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorFake @@ -52,7 +53,8 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RoboPilotTest +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class KeyguardPinViewControllerTest : SysuiTestCase() { @@ -175,7 +177,8 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { private fun getPinTopGuideline(): Float { val cs = ConstraintSet() - val container = objectKeyguardPINView.requireViewById(R.id.pin_container) as ConstraintLayout + val container = + objectKeyguardPINView.requireViewById(R.id.pin_container) as ConstraintLayout cs.clone(container) return cs.getConstraint(R.id.pin_pad_top_guideline).layout.guidePercent } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 80172a10062a..6bff4ce4aad9 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -21,7 +21,6 @@ import android.content.res.Configuration import android.hardware.biometrics.BiometricOverlayConstants import android.media.AudioManager import android.telephony.TelephonyManager -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.testing.TestableResources import android.view.Gravity @@ -30,6 +29,7 @@ import android.view.MotionEvent import android.view.View import android.view.WindowInsetsController import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.internal.logging.UiEventLogger @@ -37,6 +37,7 @@ import com.android.internal.widget.LockPatternUtils import com.android.keyguard.KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback import com.android.keyguard.KeyguardSecurityModel.SecurityMode import com.android.systemui.R +import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate @@ -96,7 +97,8 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RoboPilotTest +@RunWith(AndroidJUnit4::class) @RunWithLooper class KeyguardSecurityContainerControllerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index f6450a4fb563..3e330d65329e 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -44,7 +44,6 @@ import static org.mockito.Mockito.when; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.graphics.Insets; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.MotionEvent; import android.view.View; @@ -54,9 +53,11 @@ import android.window.BackEvent; import android.window.OnBackAnimationCallback; import androidx.constraintlayout.widget.ConstraintSet; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.R; +import com.android.systemui.RoboPilotTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingA11yDelegate; import com.android.systemui.plugins.FalsingManager; @@ -75,7 +76,8 @@ import org.mockito.junit.MockitoRule; import java.util.ArrayList; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RoboPilotTest +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper() public class KeyguardSecurityContainerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java index 64e1458c42d4..68c2f59722ad 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java @@ -25,17 +25,18 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.LayoutInflater; import android.view.ViewGroup; import android.view.WindowInsetsController; import androidx.asynclayoutinflater.view.AsyncLayoutInflater; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.R; +import com.android.systemui.RoboPilotTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.flags.FeatureFlags; @@ -49,7 +50,8 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RoboPilotTest +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper() public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt index 291dda256c4f..4db5f35ca07d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt @@ -18,13 +18,14 @@ package com.android.keyguard import android.telephony.PinResult import android.telephony.TelephonyManager -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.LayoutInflater +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.internal.widget.LockPatternUtils import com.android.systemui.R +import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.flags.FakeFeatureFlags @@ -43,7 +44,8 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RoboPilotTest +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class KeyguardSimPinViewControllerTest : SysuiTestCase() { private lateinit var simPinView: KeyguardSimPinView diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt index 626faa601970..47ff3b90e2e2 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt @@ -18,13 +18,14 @@ package com.android.keyguard import android.telephony.PinResult import android.telephony.TelephonyManager -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.LayoutInflater +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.internal.widget.LockPatternUtils import com.android.systemui.R +import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.flags.FakeFeatureFlags @@ -39,7 +40,8 @@ import org.mockito.Mockito import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RoboPilotTest +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class KeyguardSimPukViewControllerTest : SysuiTestCase() { private lateinit var simPukView: KeyguardSimPukView diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java index b9e3f140a9ab..09ff546120c6 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java @@ -24,7 +24,6 @@ import static com.android.systemui.flags.Flags.MIGRATE_LOCK_ICON; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -148,7 +147,6 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { mFeatureFlags.set(MIGRATE_LOCK_ICON, false); mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false); mUnderTest = new LockIconViewController( - mLockIconView, mStatusBarStateController, mKeyguardUpdateMonitor, mKeyguardViewController, @@ -167,7 +165,8 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { .getKeyguardTransitionInteractor(), KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(), mFeatureFlags, - mPrimaryBouncerInteractor + mPrimaryBouncerInteractor, + mContext ); } @@ -228,9 +227,6 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { protected void init(boolean useMigrationFlag) { mFeatureFlags.set(DOZING_MIGRATION_1, useMigrationFlag); - mUnderTest.init(); - - verify(mLockIconView, atLeast(1)).addOnAttachStateChangeListener(mAttachCaptor.capture()); - mAttachCaptor.getValue().onViewAttachedToWindow(mLockIconView); + mUnderTest.setLockIconView(mLockIconView); } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java index 45021ba1b300..979fc83ec43a 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java @@ -52,6 +52,12 @@ import org.junit.runner.RunWith; @TestableLooper.RunWithLooper public class LockIconViewControllerTest extends LockIconViewControllerBaseTest { + @Override + public void setUp() throws Exception { + super.setUp(); + when(mLockIconView.isAttachedToWindow()).thenReturn(true); + } + @Test public void testUpdateFingerprintLocationOnInit() { // GIVEN fp sensor location is available pre-attached diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt index 88c710aedf93..ddb482f0623c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt @@ -16,18 +16,46 @@ package com.android.systemui.back.domain.interactor +import android.view.ViewRootImpl +import android.window.BackEvent +import android.window.BackEvent.EDGE_LEFT +import android.window.OnBackAnimationCallback +import android.window.OnBackInvokedCallback +import android.window.OnBackInvokedDispatcher +import android.window.WindowOnBackInvokedDispatcher import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.internal.statusbar.IStatusBarService import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.shared.model.WakeSleepReason +import com.android.systemui.keyguard.shared.model.WakefulnessModel +import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor +import com.android.systemui.scene.ui.view.WindowRootView import com.android.systemui.shade.QuickSettingsController import com.android.systemui.shade.ShadeController import com.android.systemui.shade.ShadeViewController +import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager +import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent import org.junit.Before import org.junit.Rule import org.junit.Test @@ -41,7 +69,12 @@ import org.mockito.junit.MockitoJUnit @SmallTest @RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) class BackActionInteractorTest : SysuiTestCase() { + private val testScope = TestScope() + private val featureFlags = FakeFeatureFlags() + private val executor = FakeExecutor(FakeSystemClock()) + @JvmField @Rule var mockitoRule = MockitoJUnit.rule() @Mock private lateinit var statusBarStateController: StatusBarStateController @@ -49,18 +82,42 @@ class BackActionInteractorTest : SysuiTestCase() { @Mock private lateinit var shadeController: ShadeController @Mock private lateinit var qsController: QuickSettingsController @Mock private lateinit var shadeViewController: ShadeViewController + @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController + @Mock private lateinit var windowRootView: WindowRootView + @Mock private lateinit var viewRootImpl: ViewRootImpl + @Mock private lateinit var onBackInvokedDispatcher: WindowOnBackInvokedDispatcher + @Mock private lateinit var iStatusBarService: IStatusBarService + @Mock private lateinit var headsUpManager: HeadsUpManager - private lateinit var backActionInteractor: BackActionInteractor + private val keyguardRepository = FakeKeyguardRepository() + private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy { + WindowRootViewVisibilityInteractor( + testScope.backgroundScope, + WindowRootViewVisibilityRepository(iStatusBarService, executor), + keyguardRepository, + headsUpManager, + ) + } - @Before - fun setup() { - backActionInteractor = - BackActionInteractor( + private val backActionInteractor: BackActionInteractor by lazy { + BackActionInteractor( + testScope.backgroundScope, statusBarStateController, statusBarKeyguardViewManager, shadeController, + notificationShadeWindowController, + windowRootViewVisibilityInteractor, + featureFlags, ) - backActionInteractor.setup(qsController, shadeViewController) + .apply { this.setup(qsController, shadeViewController) } + } + + @Before + fun setUp() { + featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, false) + whenever(notificationShadeWindowController.windowRootView).thenReturn(windowRootView) + whenever(windowRootView.viewRootImpl).thenReturn(viewRootImpl) + whenever(viewRootImpl.onBackInvokedDispatcher).thenReturn(onBackInvokedDispatcher) } @Test @@ -117,4 +174,139 @@ class BackActionInteractorTest : SysuiTestCase() { verify(statusBarKeyguardViewManager, never()).onBackPressed() verify(shadeViewController, never()).animateCollapseQs(anyBoolean()) } + + @Test + fun shadeVisibleAndDeviceAwake_callbackRegistered() { + backActionInteractor.start() + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.AWAKE) + + testScope.runCurrent() + + verify(onBackInvokedDispatcher) + .registerOnBackInvokedCallback(eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), any()) + } + + @Test + fun noWindowRootView_noCrashAttemptingCallbackRegistration() { + whenever(notificationShadeWindowController.windowRootView).thenReturn(null) + + backActionInteractor.start() + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.AWAKE) + + testScope.runCurrent() + // No assert necessary, just testing no crash + } + + @Test + fun shadeNotVisible_callbackUnregistered() { + backActionInteractor.start() + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.AWAKE) + val callback = getBackInvokedCallback() + + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false) + testScope.runCurrent() + + verify(onBackInvokedDispatcher).unregisterOnBackInvokedCallback(callback) + } + + @Test + fun deviceAsleep_callbackUnregistered() { + backActionInteractor.start() + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.AWAKE) + val callback = getBackInvokedCallback() + + setWakefulness(WakefulnessState.ASLEEP) + testScope.runCurrent() + + verify(onBackInvokedDispatcher).unregisterOnBackInvokedCallback(callback) + } + + @Test + fun animationFlagOff_onBackInvoked_keyguardNotified() { + backActionInteractor.start() + featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, false) + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.AWAKE) + val callback = getBackInvokedCallback() + whenever(statusBarKeyguardViewManager.canHandleBackPressed()).thenReturn(true) + + callback.onBackInvoked() + + verify(statusBarKeyguardViewManager).onBackPressed() + } + + @Test + fun animationFlagOn_onBackInvoked_keyguardNotified() { + featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true) + backActionInteractor.start() + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.AWAKE) + val callback = getBackInvokedCallback() + whenever(statusBarKeyguardViewManager.canHandleBackPressed()).thenReturn(true) + + callback.onBackInvoked() + + verify(statusBarKeyguardViewManager).onBackPressed() + } + + @Test + fun animationFlagOn_callbackIsAnimationCallback() { + featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true) + backActionInteractor.start() + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.AWAKE) + + val callback = getBackInvokedCallback() + + assertThat(callback).isInstanceOf(OnBackAnimationCallback::class.java) + } + + @Test + fun onBackProgressed_shadeCannotBeCollapsed_shadeViewControllerNotNotified() { + featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true) + backActionInteractor.start() + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.AWAKE) + val callback = getBackInvokedCallback() as OnBackAnimationCallback + + whenever(shadeViewController.canBeCollapsed()).thenReturn(false) + + callback.onBackProgressed(createBackEvent(0.3f)) + + verify(shadeViewController, never()).onBackProgressed(0.3f) + } + + @Test + fun onBackProgressed_shadeCanBeCollapsed_shadeViewControllerNotified() { + featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true) + backActionInteractor.start() + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.AWAKE) + val callback = getBackInvokedCallback() as OnBackAnimationCallback + + whenever(shadeViewController.canBeCollapsed()).thenReturn(true) + + callback.onBackProgressed(createBackEvent(0.4f)) + + verify(shadeViewController).onBackProgressed(0.4f) + } + + private fun getBackInvokedCallback(): OnBackInvokedCallback { + testScope.runCurrent() + val captor = argumentCaptor<OnBackInvokedCallback>() + verify(onBackInvokedDispatcher).registerOnBackInvokedCallback(any(), captor.capture()) + return captor.value!! + } + + private fun createBackEvent(progress: Float): BackEvent = + BackEvent(/* touchX= */ 0f, /* touchY= */ 0f, progress, /* swipeEdge= */ EDGE_LEFT) + + private fun setWakefulness(state: WakefulnessState) { + val model = WakefulnessModel(state, WakeSleepReason.OTHER, WakeSleepReason.OTHER) + keyguardRepository.setWakefulnessModel(model) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 7ab8e8b229a3..e56b5c7406b6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -1616,4 +1616,43 @@ public class UdfpsControllerTest extends SysuiTestCase { // THEN vibrate is used verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS)); } + + @Test + public void aodInterrupt_withNewTouchDetection() throws RemoteException { + mUdfpsController.cancelAodSendFingerUpAction(); + final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, + 0L); + final TouchProcessorResult processorResultDown = + new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN, + 1 /* pointerId */, touchData); + + // Enable new touch detection. + when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); + + // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. + initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); + + // GIVEN that the overlay is showing and screen is on and fp is running + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, 0, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mScreenObserver.onScreenTurnedOn(); + mFgExecutor.runAllReady(); + + // WHEN fingerprint is requested because of AOD interrupt + mUdfpsController.onAodInterrupt(0, 0, 2f, 3f); + + // Check case where touch driver sends touch to UdfpsView as well + verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultDown); + MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); + + mBiometricExecutor.runAllReady(); + + // THEN only one onPointerDown is sent + verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index 47084c087952..0ed46da27b55 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -20,6 +20,7 @@ import android.hardware.biometrics.PromptInfo import android.hardware.face.FaceSensorPropertiesInternal import android.hardware.fingerprint.FingerprintSensorPropertiesInternal import android.view.HapticFeedbackConstants +import android.view.MotionEvent import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase @@ -500,6 +501,81 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test + fun auto_confirm_authentication_when_finger_down() = runGenericTest { + val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false) + + // No icon button when face only, can't confirm before auth + if (!testCase.isFaceOnly) { + viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_DOWN)) + } + viewModel.showAuthenticated(testCase.authenticatedModality, 0) + + val authenticating by collectLastValue(viewModel.isAuthenticating) + val authenticated by collectLastValue(viewModel.isAuthenticated) + val message by collectLastValue(viewModel.message) + val size by collectLastValue(viewModel.size) + val legacyState by collectLastValue(viewModel.legacyState) + val canTryAgain by collectLastValue(viewModel.canTryAgainNow) + + assertThat(authenticating).isFalse() + assertThat(canTryAgain).isFalse() + assertThat(authenticated?.isAuthenticated).isTrue() + + if (testCase.isFaceOnly && expectConfirmation) { + assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION) + + assertThat(size).isEqualTo(PromptSize.MEDIUM) + assertButtonsVisible( + cancel = true, + confirm = true, + ) + + viewModel.confirmAuthenticated() + assertThat(message).isEqualTo(PromptMessage.Empty) + assertButtonsVisible() + } else { + assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED) + } + } + + @Test + fun cannot_auto_confirm_authentication_when_finger_up() = runGenericTest { + val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false) + + // No icon button when face only, can't confirm before auth + if (!testCase.isFaceOnly) { + viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_DOWN)) + viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_UP)) + } + viewModel.showAuthenticated(testCase.authenticatedModality, 0) + + val authenticating by collectLastValue(viewModel.isAuthenticating) + val authenticated by collectLastValue(viewModel.isAuthenticated) + val message by collectLastValue(viewModel.message) + val size by collectLastValue(viewModel.size) + val legacyState by collectLastValue(viewModel.legacyState) + val canTryAgain by collectLastValue(viewModel.canTryAgainNow) + + assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation) + if (expectConfirmation) { + assertThat(size).isEqualTo(PromptSize.MEDIUM) + assertButtonsVisible( + cancel = true, + confirm = true, + ) + + viewModel.confirmAuthenticated() + assertThat(message).isEqualTo(PromptMessage.Empty) + assertButtonsVisible() + } + + assertThat(authenticating).isFalse() + assertThat(authenticated?.isAuthenticated).isTrue() + assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED) + assertThat(canTryAgain).isFalse() + } + + @Test fun cannot_confirm_unless_authenticated() = runGenericTest { val authenticating by collectLastValue(viewModel.isAuthenticating) val authenticated by collectLastValue(viewModel.isAuthenticated) @@ -679,6 +755,10 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa testScope.runTest { block() } } + /** Obtain a MotionEvent with the specified MotionEvent action constant */ + private fun obtainMotionEvent(action: Int): MotionEvent = + MotionEvent.obtain(0, 0, action, 0f, 0f, 0) + companion object { @JvmStatic @Parameterized.Parameters(name = "{0}") diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt index 937a7a92ec46..037c1badf0ac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.phone.CentralSurfaces +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.KotlinArgumentCaptor import com.android.systemui.util.mockito.eq @@ -55,6 +56,8 @@ class CameraGestureHelperTest : SysuiTestCase() { @Mock lateinit var centralSurfaces: CentralSurfaces @Mock + lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager + @Mock lateinit var keyguardStateController: KeyguardStateController @Mock lateinit var packageManager: PackageManager @@ -91,6 +94,7 @@ class CameraGestureHelperTest : SysuiTestCase() { context = mock(), centralSurfaces = centralSurfaces, keyguardStateController = keyguardStateController, + statusBarKeyguardViewManager = statusBarKeyguardViewManager, packageManager = packageManager, activityManager = activityManager, activityStarter = activityStarter, diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt index e3a75f161318..4ad9549d1d4a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt @@ -2,6 +2,7 @@ package com.android.systemui.communal.ui.view.layout.blueprints import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -28,9 +29,16 @@ class DefaultCommunalBlueprintTest : SysuiTestCase() { } @Test - fun apply() { + fun addView() { + val constraintLayout = ConstraintLayout(context, null) + blueprint.addViews(constraintLayout) + verify(widgetSection).addViews(constraintLayout) + } + + @Test + fun applyConstraints() { val cs = ConstraintSet() - blueprint.apply(cs) - verify(widgetSection).apply(cs) + blueprint.applyConstraints(cs) + verify(widgetSection).applyConstraints(cs) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt index b1061ba76c14..74d0d210b9b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt @@ -36,7 +36,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags.APP_PANELS_ALL_APPS_ALLOWED import com.android.systemui.settings.UserTracker import com.android.systemui.util.ActivityTaskManagerProxy import com.android.systemui.util.concurrency.FakeExecutor @@ -123,9 +122,6 @@ class ControlsListingControllerImplTest : SysuiTestCase() { arrayOf(componentName.packageName) ) - // Return false by default, we'll test the true path - `when`(featureFlags.isEnabled(APP_PANELS_ALL_APPS_ALLOWED)).thenReturn(false) - val wrapper = object : ContextWrapper(mContext) { override fun createContextAsUser(user: UserHandle, flags: Int): Context { return baseContext @@ -469,38 +465,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() { } @Test - fun testPackageNotPreferred_nullPanel() { - mContext.orCreateTestableResources - .addOverride(R.array.config_controlsPreferredPackages, arrayOf<String>()) - - val serviceInfo = ServiceInfo( - componentName, - activityName - ) - - `when`(packageManager.getComponentEnabledSetting(eq(activityName))) - .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED) - - setUpQueryResult(listOf( - ActivityInfo( - activityName, - exported = true, - permission = Manifest.permission.BIND_CONTROLS - ) - )) - - val list = listOf(serviceInfo) - serviceListingCallbackCaptor.value.onServicesReloaded(list) - - executor.runAllReady() - - assertNull(controller.getCurrentServices()[0].panelActivity) - } - - @Test - fun testPackageNotPreferred_allowAllApps_correctPanel() { - `when`(featureFlags.isEnabled(APP_PANELS_ALL_APPS_ALLOWED)).thenReturn(true) - + fun testPackageNotPreferred_correctPanel() { mContext.orCreateTestableResources .addOverride(R.array.config_controlsPreferredPackages, arrayOf<String>()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt index 9be54fbb0a82..db7c003ee545 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt @@ -165,13 +165,78 @@ class DisplayRepositoryTest : SysuiTestCase() { assertThat(value?.ids()).containsExactly(1, 2, 3, 4) } + @Test + fun onDisplayConnected_pendingDisplayReceived() = + testScope.runTest { + val pendingDisplay by latestPendingDisplayFlowValue() + + displayListener.value.onDisplayConnected(1) + + assertThat(pendingDisplay).isEqualTo(1) + } + + @Test + fun onDisplayDisconnected_pendingDisplayNull() = + testScope.runTest { + val pendingDisplay by latestPendingDisplayFlowValue() + displayListener.value.onDisplayConnected(1) + + assertThat(pendingDisplay).isNotNull() + + displayListener.value.onDisplayDisconnected(1) + + assertThat(pendingDisplay).isNull() + } + + @Test + fun onDisplayDisconnected_unknownDisplay_doesNotSendNull() = + testScope.runTest { + val pendingDisplay by latestPendingDisplayFlowValue() + displayListener.value.onDisplayConnected(1) + + assertThat(pendingDisplay).isNotNull() + + displayListener.value.onDisplayDisconnected(2) + + assertThat(pendingDisplay).isNotNull() + } + + @Test + fun onDisplayConnected_multipleTimes_sendsOnlyTheLastOne() = + testScope.runTest { + val pendingDisplay by latestPendingDisplayFlowValue() + displayListener.value.onDisplayConnected(1) + displayListener.value.onDisplayConnected(2) + + assertThat(pendingDisplay).isEqualTo(2) + } + private fun Iterable<Display>.ids(): List<Int> = map { it.displayId } // Wrapper to capture the displayListener. private fun TestScope.latestDisplayFlowValue(): FlowValue<Set<Display>?> { val flowValue = collectLastValue(displayRepository.displays) verify(displayManager) - .registerDisplayListener(displayListener.capture(), eq(testHandler), anyLong()) + .registerDisplayListener( + displayListener.capture(), + eq(testHandler), + eq( + DisplayManager.EVENT_FLAG_DISPLAY_ADDED or + DisplayManager.EVENT_FLAG_DISPLAY_CHANGED or + DisplayManager.EVENT_FLAG_DISPLAY_REMOVED + ) + ) + return flowValue + } + + private fun TestScope.latestPendingDisplayFlowValue(): FlowValue<Int?> { + val flowValue = collectLastValue(displayRepository.pendingDisplayId) + verify(displayManager) + .registerDisplayListener( + displayListener.capture(), + eq(testHandler), + eq(DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) + ) return flowValue } diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt index eb0ad698e34b..50617a16ce0b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.display.domain.interactor +import android.hardware.display.DisplayManager import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.Display @@ -27,14 +28,20 @@ import com.android.systemui.coroutines.FlowValue import com.android.systemui.coroutines.collectLastValue import com.android.systemui.display.data.repository.FakeDisplayRepository import com.android.systemui.display.data.repository.display +import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper @@ -42,11 +49,22 @@ import org.junit.runner.RunWith @SmallTest class ConnectedDisplayInteractorTest : SysuiTestCase() { + private val displayManager = mock<DisplayManager>() private val fakeDisplayRepository = FakeDisplayRepository() + private val fakeKeyguardRepository = FakeKeyguardRepository() private val connectedDisplayStateProvider: ConnectedDisplayInteractor = - ConnectedDisplayInteractorImpl(fakeDisplayRepository) + ConnectedDisplayInteractorImpl( + displayManager, + fakeKeyguardRepository, + fakeDisplayRepository + ) private val testScope = TestScope(UnconfinedTestDispatcher()) + @Before + fun setup() { + fakeKeyguardRepository.setKeyguardUnlocked(true) + } + @Test fun displayState_nullDisplays_disconnected() = testScope.runTest { @@ -126,6 +144,70 @@ class ConnectedDisplayInteractorTest : SysuiTestCase() { assertThat(value).isEqualTo(State.CONNECTED_SECURE) } + @Test + fun pendingDisplay_propagated() = + testScope.runTest { + val value by lastPendingDisplay() + val pendingDisplayId = 4 + + fakeDisplayRepository.emit(pendingDisplayId) + + assertThat(value).isNotNull() + } + + @Test + fun onPendingDisplay_enable_displayEnabled() = + testScope.runTest { + val pendingDisplay by lastPendingDisplay() + + fakeDisplayRepository.emit(1) + pendingDisplay!!.enable() + + Mockito.verify(displayManager).enableConnectedDisplay(eq(1)) + } + + @Test + fun onPendingDisplay_disable_displayDisabled() = + testScope.runTest { + val pendingDisplay by lastPendingDisplay() + + fakeDisplayRepository.emit(1) + pendingDisplay!!.disable() + + Mockito.verify(displayManager).disableConnectedDisplay(eq(1)) + } + + @Test + fun onPendingDisplay_keyguardUnlocked_returnsPendingDisplay() = + testScope.runTest { + fakeKeyguardRepository.setKeyguardUnlocked(false) + val pendingDisplay by lastPendingDisplay() + + fakeDisplayRepository.emit(1) + assertThat(pendingDisplay).isNull() + + fakeKeyguardRepository.setKeyguardUnlocked(true) + + assertThat(pendingDisplay).isNotNull() + } + + @Test + fun onPendingDisplay_keyguardLocked_returnsNull() = + testScope.runTest { + fakeKeyguardRepository.setKeyguardUnlocked(true) + val pendingDisplay by lastPendingDisplay() + + fakeDisplayRepository.emit(1) + assertThat(pendingDisplay).isNotNull() + + fakeKeyguardRepository.setKeyguardUnlocked(false) + + assertThat(pendingDisplay).isNull() + } + private fun TestScope.lastValue(): FlowValue<State?> = collectLastValue(connectedDisplayStateProvider.connectedDisplayState) + + private fun TestScope.lastPendingDisplay(): FlowValue<PendingDisplay?> = + collectLastValue(connectedDisplayStateProvider.pendingDisplay) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt new file mode 100644 index 000000000000..705964736a49 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.display.ui.view + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.View +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +class MirroringConfirmationDialogTest : SysuiTestCase() { + + private lateinit var dialog: MirroringConfirmationDialog + + private val onStartMirroringCallback = mock<View.OnClickListener>() + private val onCancelCallback = mock<View.OnClickListener>() + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + dialog = MirroringConfirmationDialog(context, onStartMirroringCallback, onCancelCallback) + } + + @Test + fun startMirroringButton_clicked_callsCorrectCallback() { + dialog.show() + + dialog.requireViewById<View>(R.id.enable_display).callOnClick() + + verify(onStartMirroringCallback).onClick(any()) + verify(onCancelCallback, never()).onClick(any()) + } + + @Test + fun cancelButton_clicked_callsCorrectCallback() { + dialog.show() + + dialog.requireViewById<View>(R.id.cancel).callOnClick() + + verify(onCancelCallback).onClick(any()) + verify(onStartMirroringCallback, never()).onClick(any()) + } + + @After + fun teardown() { + if (::dialog.isInitialized) { + dialog.dismiss() + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt index ccd631ec37d0..8f6634478617 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt @@ -9,6 +9,7 @@ import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import android.graphics.drawable.VectorDrawable import android.net.Uri +import android.util.Size import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.R @@ -78,12 +79,19 @@ class ImageLoaderTest : SysuiTestCase() { } @Test - fun invalidIcon_returnsNull() = + fun invalidIcon_loadDrawable_returnsNull() = testScope.runTest { assertThat(imageLoader.loadDrawable(Icon.createWithFilePath("this is broken"))).isNull() } @Test + fun invalidIcon_loadSize_returnsNull() = + testScope.runTest { + assertThat(imageLoader.loadSize(Icon.createWithFilePath("this is broken"), context)) + .isNull() + } + + @Test fun invalidIS_returnsNull() = testScope.runTest { assertThat( @@ -172,6 +180,17 @@ class ImageLoaderTest : SysuiTestCase() { } @Test + fun validBitmapIcon_loadSize_returnsNull() = + testScope.runTest { + val bitmap = + BitmapFactory.decodeResource( + context.resources, + R.drawable.dessert_zombiegingerbread + ) + assertThat(imageLoader.loadSize(Icon.createWithBitmap(bitmap), context)).isNull() + } + + @Test fun validUriIcon_returnsBitmapDrawable() = testScope.runTest { val bitmap = @@ -186,6 +205,17 @@ class ImageLoaderTest : SysuiTestCase() { } @Test + fun validUriIcon_returnsSize() = + testScope.runTest { + val drawable = context.resources.getDrawable(R.drawable.dessert_zombiegingerbread) + val uri = + "android.resource://${context.packageName}/${R.drawable.dessert_zombiegingerbread}" + val loadedSize = + imageLoader.loadSize(Icon.createWithContentUri(Uri.parse(uri)), context) + assertSizeEqualToDrawableSize(loadedSize, drawable) + } + + @Test fun validDataIcon_returnsBitmapDrawable() = testScope.runTest { val bitmap = @@ -205,6 +235,54 @@ class ImageLoaderTest : SysuiTestCase() { } @Test + fun validDataIcon_loadSize_returnsNull() = + testScope.runTest { + val bitmap = + BitmapFactory.decodeResource( + context.resources, + R.drawable.dessert_zombiegingerbread + ) + val bos = + ByteArrayOutputStream( + bitmap.byteCount * 2 + ) // Compressed bitmap should be smaller than its source. + bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos) + + val array = bos.toByteArray() + assertThat(imageLoader.loadSize(Icon.createWithData(array, 0, array.size), context)) + .isNull() + } + + @Test + fun validResourceIcon_returnsBitmapDrawable() = + testScope.runTest { + val bitmap = context.resources.getDrawable(R.drawable.dessert_zombiegingerbread) + val loadedDrawable = + imageLoader.loadDrawable( + Icon.createWithResource( + "com.android.systemui.tests", + R.drawable.dessert_zombiegingerbread + ) + ) + assertBitmapEqualToDrawable(loadedDrawable, (bitmap as BitmapDrawable).bitmap) + } + + @Test + fun validResourceIcon_loadSize_returnsNull() = + testScope.runTest { + assertThat( + imageLoader.loadSize( + Icon.createWithResource( + "com.android.systemui.tests", + R.drawable.dessert_zombiegingerbread + ), + context + ) + ) + .isNull() + } + + @Test fun validSystemResourceIcon_returnsBitmapDrawable() = testScope.runTest { val bitmap = @@ -217,6 +295,18 @@ class ImageLoaderTest : SysuiTestCase() { } @Test + fun validSystemResourceIcon_loadSize_returnsNull() = + testScope.runTest { + assertThat( + imageLoader.loadSize( + Icon.createWithResource("android", android.R.drawable.ic_dialog_alert), + context + ) + ) + .isNull() + } + + @Test fun invalidDifferentPackageResourceIcon_returnsNull() = testScope.runTest { val loadedDrawable = @@ -230,6 +320,20 @@ class ImageLoaderTest : SysuiTestCase() { } @Test + fun invalidDifferentPackageResourceIcon_loadSize_returnsNull() = + testScope.runTest { + assertThat( + imageLoader.loadDrawable( + Icon.createWithResource( + "noooope.wrong.package", + R.drawable.dessert_zombiegingerbread + ) + ) + ) + .isNull() + } + + @Test fun validBitmapResource_widthMoreRestricted_downsizesKeepingAspectRatio() = testScope.runTest { val loadedDrawable = @@ -343,4 +447,10 @@ class ImageLoaderTest : SysuiTestCase() { assertThat(actual?.width).isEqualTo(expected.width) assertThat(actual?.height).isEqualTo(expected.height) } + + private fun assertSizeEqualToDrawableSize(actual: Size?, expected: Drawable) { + assertThat(actual).isNotNull() + assertThat(actual?.width).isEqualTo(expected.intrinsicWidth) + assertThat(actual?.height).isEqualTo(expected.intrinsicHeight) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt new file mode 100644 index 000000000000..71a56cd8588c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.haptics.slider + +import android.widget.SeekBar +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import junit.framework.Assert.assertEquals +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class SeekableSliderEventProducerTest : SysuiTestCase() { + + private val seekBar = SeekBar(mContext) + private val eventProducer = SeekableSliderEventProducer() + private val eventFlow = eventProducer.produceEvents() + + @Test + fun onStartTrackingTouch_noProgress_trackingTouchEventProduced() = runTest { + val latest by collectLastValue(eventFlow) + + eventProducer.onStartTrackingTouch(seekBar) + + assertEquals(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, 0F), latest) + } + + @Test + fun onStopTrackingTouch_noProgress_StoppedTrackingTouchEventProduced() = runTest { + val latest by collectLastValue(eventFlow) + + eventProducer.onStopTrackingTouch(seekBar) + + assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, 0F), latest) + } + + @Test + fun onProgressChangeByUser_changeByUserEventProduced_withNormalizedProgress() = runTest { + val progress = 50 + val latest by collectLastValue(eventFlow) + + eventProducer.onProgressChanged(seekBar, progress, true) + + assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_USER, 0.5F), latest) + } + + @Test + fun onProgressChangeByUser_zeroWidthSlider_changeByUserEventProduced_withMaxProgress() = + runTest { + // No-width slider where the min and max values are the same + seekBar.min = 100 + seekBar.max = 100 + val progress = 50 + val latest by collectLastValue(eventFlow) + + eventProducer.onProgressChanged(seekBar, progress, true) + + assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_USER, 1.0F), latest) + } + + @Test + fun onProgressChangeByProgram_changeByProgramEventProduced_withNormalizedProgress() = runTest { + val progress = 50 + val latest by collectLastValue(eventFlow) + + eventProducer.onProgressChanged(seekBar, progress, false) + + assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, 0.5F), latest) + } + + @Test + fun onProgressChangeByProgram_zeroWidthSlider_changeByProgramEventProduced_withMaxProgress() = + runTest { + // No-width slider where the min and max values are the same + seekBar.min = 100 + seekBar.max = 100 + val progress = 50 + val latest by collectLastValue(eventFlow) + + eventProducer.onProgressChanged(seekBar, progress, false) + + assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, 1.0F), latest) + } + + @Test + fun onStartTrackingTouch_afterProgress_trackingTouchEventProduced_withNormalizedProgress() = + runTest { + val progress = 50 + val latest by collectLastValue(eventFlow) + + eventProducer.onProgressChanged(seekBar, progress, true) + eventProducer.onStartTrackingTouch(seekBar) + + assertEquals(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, 0.5F), latest) + } + + @Test + fun onStopTrackingTouch_afterProgress_stopTrackingTouchEventProduced_withNormalizedProgress() = + runTest { + val progress = 50 + val latest by collectLastValue(eventFlow) + + eventProducer.onProgressChanged(seekBar, progress, true) + eventProducer.onStopTrackingTouch(seekBar) + + assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, 0.5F), latest) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index daafba24e3ca..f78d051eca61 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -221,6 +221,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mSystemClock = new FakeSystemClock(); when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager); when(mPowerManager.newWakeLock(anyInt(), any())).thenReturn(mock(WakeLock.class)); + when(mPowerManager.isInteractive()).thenReturn(true); when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true); when(mInteractionJankMonitor.end(anyInt())).thenReturn(true); mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager); @@ -241,6 +242,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mConfigurationController, mViewMediator, mKeyguardBypassController, + mUiBgExecutor, mColorExtractor, mDumpManager, mKeyguardStateController, @@ -868,8 +870,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { when(mKeyguardStateController.isShowing()).thenReturn(true); TestableLooper.get(this).processAllMessages(); - when(mPowerManager.isInteractive()).thenReturn(true); - mViewMediator.onSystemReady(); TestableLooper.get(this).processAllMessages(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index 5ead16bdc10f..2691860bc25b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -46,6 +46,7 @@ import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.withArgCaptor @@ -322,20 +323,20 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { val captor = argumentCaptor<StatusBarStateController.StateListener>() runCurrent() - verify(statusBarStateController).addCallback(captor.capture()) + verify(statusBarStateController, atLeastOnce()).addCallback(captor.capture()) - captor.value.onDozeAmountChanged(0.433f, 0.4f) + captor.allValues.forEach { it.onDozeAmountChanged(0.433f, 0.4f) } runCurrent() - captor.value.onDozeAmountChanged(0.498f, 0.5f) + captor.allValues.forEach { it.onDozeAmountChanged(0.498f, 0.5f) } runCurrent() - captor.value.onDozeAmountChanged(0.661f, 0.65f) + captor.allValues.forEach { it.onDozeAmountChanged(0.661f, 0.65f) } runCurrent() assertThat(values).isEqualTo(listOf(0f, 0.433f, 0.498f, 0.661f)) job.cancel() runCurrent() - verify(statusBarStateController).removeCallback(captor.value) + verify(statusBarStateController).removeCallback(any()) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index ca93246e8d9f..d4576053f9c7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -1049,7 +1049,6 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { @Test fun occludedToAlternateBouncer() = testScope.runTest { - // GIVEN a prior transition has run to OCCLUDED runTransition(KeyguardState.LOCKSCREEN, KeyguardState.OCCLUDED) keyguardRepository.setKeyguardOccluded(true) @@ -1073,6 +1072,31 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + fun occludedToPrimaryBouncer() = + testScope.runTest { + // GIVEN a prior transition has run to OCCLUDED + runTransition(KeyguardState.LOCKSCREEN, KeyguardState.OCCLUDED) + keyguardRepository.setKeyguardOccluded(true) + runCurrent() + + // WHEN primary bouncer shows + bouncerRepository.setPrimaryShow(true) // beverlyt + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture(), anyBoolean()) + } + // THEN a transition to AlternateBouncer should occur + assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED) + assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test fun primaryBouncerToOccluded() = testScope.runTest { // GIVEN device not sleeping diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt index addb1815cead..3b4eab2a5fe1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt @@ -19,13 +19,17 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection @@ -34,6 +38,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -50,7 +55,9 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { private lateinit var defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection @Mock private lateinit var defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection @Mock private lateinit var defaultStatusViewSection: DefaultStatusViewSection + @Mock private lateinit var defaultNSSLSection: DefaultNotificationStackScrollLayoutSection @Mock private lateinit var splitShadeGuidelines: SplitShadeGuidelines + private val featureFlags = FakeFeatureFlags() @Before fun setup() { @@ -64,20 +71,32 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { defaultAmbientIndicationAreaSection, defaultSettingsPopupMenuSection, defaultStatusViewSection, + defaultNSSLSection, splitShadeGuidelines, + featureFlags, ) + featureFlags.set(Flags.LAZY_INFLATE_KEYGUARD, false) } @Test - fun apply() { + fun addViews() { + val constraintLayout = ConstraintLayout(context, null) + underTest.addViews(constraintLayout) + underTest.sections.forEach { verify(it, never()).addViews(constraintLayout) } + } + + @Test + fun addViews_lazyInflateFlagOn() { + featureFlags.set(Flags.LAZY_INFLATE_KEYGUARD, true) + val constraintLayout = ConstraintLayout(context, null) + underTest.addViews(constraintLayout) + underTest.sections.forEach { verify(it).addViews(constraintLayout) } + } + + @Test + fun applyConstraints() { val cs = ConstraintSet() - underTest.apply(cs) - verify(defaultIndicationAreaSection).apply(cs) - verify(defaultLockIconSection).apply(cs) - verify(defaultShortcutsSection).apply(cs) - verify(defaultAmbientIndicationAreaSection).apply(cs) - verify(defaultSettingsPopupMenuSection).apply(cs) - verify(defaultStatusViewSection).apply(cs) - verify(splitShadeGuidelines).apply(cs) + underTest.applyConstraints(cs) + underTest.sections.forEach { verify(it).applyConstraints(cs) } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt index 3dcc03d61fd7..798b23e4a074 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt @@ -22,20 +22,45 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel +import com.android.systemui.statusbar.KeyguardIndicationController import com.google.common.truth.Truth.assertThat +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.MockitoAnnotations @RunWith(JUnit4::class) @SmallTest class DefaultIndicationAreaSectionTest : SysuiTestCase() { - private val underTest = DefaultIndicationAreaSection(context) + @Mock private lateinit var keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel + @Mock private lateinit var keyguardRootViewModel: KeyguardRootViewModel + @Mock private lateinit var indicationController: KeyguardIndicationController + @Mock private lateinit var featureFlags: FeatureFlags + + private lateinit var underTest: DefaultIndicationAreaSection + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + underTest = + DefaultIndicationAreaSection( + context, + keyguardIndicationAreaViewModel, + keyguardRootViewModel, + indicationController, + featureFlags, + ) + } @Test fun apply() { val cs = ConstraintSet() - underTest.apply(cs) + underTest.applyConstraints(cs) val constraint = cs.getConstraint(R.id.keyguard_indication_area) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt index 379c03c4353d..1192a8017dc4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt @@ -22,10 +22,12 @@ import android.view.WindowManager import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.LockIconViewController import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController -import com.android.systemui.keyguard.ui.view.KeyguardRootView +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.shade.NotificationPanelView import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -41,19 +43,30 @@ class DefaultLockIconSectionTest : SysuiTestCase() { @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var authController: AuthController @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager + @Mock private lateinit var notificationPanelView: NotificationPanelView + @Mock private lateinit var featureFlags: FeatureFlags + @Mock private lateinit var lockIconViewController: LockIconViewController private lateinit var underTest: DefaultLockIconSection @Before fun setup() { MockitoAnnotations.initMocks(this) underTest = - DefaultLockIconSection(keyguardUpdateMonitor, authController, windowManager, context) + DefaultLockIconSection( + keyguardUpdateMonitor, + authController, + windowManager, + context, + notificationPanelView, + featureFlags, + lockIconViewController + ) } @Test fun apply() { val cs = ConstraintSet() - underTest.apply(cs) + underTest.applyConstraints(cs) val constraint = cs.getConstraint(R.id.lock_icon_view) @@ -64,7 +77,7 @@ class DefaultLockIconSectionTest : SysuiTestCase() { @Test fun testCenterLockIcon() { val cs = ConstraintSet() - underTest.centerLockIcon(Point(5, 6), 1F, 5, cs) + underTest.centerLockIcon(Point(5, 6), 1F, cs) val constraint = cs.getConstraint(R.id.lock_icon_view) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index a9f288d3575f..b30dc9cc67c4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -23,15 +23,13 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 -@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(JUnit4::class) class LockscreenSceneViewModelTest : SysuiTestCase() { @@ -47,10 +45,9 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { private val underTest = LockscreenSceneViewModel( authenticationInteractor = authenticationInteractor, - bouncerInteractor = - utils.bouncerInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, + longPress = + KeyguardLongPressViewModel( + interactor = mock(), ), ) @@ -76,30 +73,4 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer) } - - @Test - fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setUnlocked(false) - runCurrent() - - underTest.onLockButtonClicked() - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - } - - @Test - fun onLockButtonClicked_deviceUnlocked_switchesToGone() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setUnlocked(true) - runCurrent() - - underTest.onLockButtonClicked() - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt index ef51e474bf71..3961a945414d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt @@ -33,7 +33,6 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.keyguard.TestScopeProvider import com.android.systemui.R import com.android.systemui.SysuiTestCase -import com.android.systemui.classifier.FalsingCollector import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository @@ -105,7 +104,6 @@ class MediaCarouselControllerTest : SysuiTestCase() { @Mock @Main private lateinit var executor: DelayableExecutor @Mock lateinit var mediaDataManager: MediaDataManager @Mock lateinit var configurationController: ConfigurationController - @Mock lateinit var falsingCollector: FalsingCollector @Mock lateinit var falsingManager: FalsingManager @Mock lateinit var dumpManager: DumpManager @Mock lateinit var logger: MediaUiEventLogger @@ -146,7 +144,6 @@ class MediaCarouselControllerTest : SysuiTestCase() { executor, mediaDataManager, configurationController, - falsingCollector, falsingManager, dumpManager, logger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt index a01394f027cf..f566efe6f176 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt @@ -193,6 +193,23 @@ class PowerRepositoryImplTest : SysuiTestCase() { assertThat(reasonCaptor.value).contains(context.applicationContext.packageName) } + @Test + fun userActivity_notifiesPowerManager() { + systemClock.setUptimeMillis(345000) + + underTest.userTouch() + + val flagsCaptor = argumentCaptor<Int>() + verify(manager) + .userActivity( + eq(345000L), + eq(PowerManager.USER_ACTIVITY_EVENT_TOUCH), + capture(flagsCaptor) + ) + assertThat(flagsCaptor.value).isNotEqualTo(PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) + assertThat(flagsCaptor.value).isNotEqualTo(PowerManager.USER_ACTIVITY_FLAG_INDIRECT) + } + private fun verifyRegistered() { // We must verify with all arguments, even those that are optional because they have default // values because Mockito is forcing us to. Once we can use mockito-kotlin, we should be diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java index 93ed99423f0f..e537131bad01 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java @@ -14,6 +14,8 @@ package com.android.systemui.qs.tiles; +import static com.android.systemui.flags.Flags.SIGNAL_CALLBACK_DEPRECATION; + import static junit.framework.Assert.assertTrue; import static junit.framework.TestCase.assertEquals; @@ -37,9 +39,11 @@ import androidx.lifecycle.LifecycleOwner; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; +import com.android.keyguard.TestScopeProvider; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.classifier.FalsingManagerFake; +import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; @@ -49,6 +53,12 @@ import com.android.systemui.statusbar.connectivity.IconState; import com.android.systemui.statusbar.connectivity.NetworkController; import com.android.systemui.statusbar.connectivity.SignalCallback; import com.android.systemui.statusbar.connectivity.WifiIndicators; +import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository; +import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository; +import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor; +import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl; +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel; +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.CastController.CastDevice; import com.android.systemui.statusbar.policy.HotspotController; @@ -65,6 +75,7 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; +import kotlinx.coroutines.test.TestScope; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @@ -98,6 +109,12 @@ public class CastTileTest extends SysuiTestCase { @Mock private QsEventLogger mUiEventLogger; + private WifiInteractor mWifiInteractor; + private final TileJavaAdapter mJavaAdapter = new TileJavaAdapter(); + private final FakeWifiRepository mWifiRepository = new FakeWifiRepository(); + private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); + private final TestScope mTestScope = TestScopeProvider.getTestScope(); + private TestableLooper mTestableLooper; private CastTile mCastTile; @@ -108,42 +125,11 @@ public class CastTileTest extends SysuiTestCase { when(mHost.getContext()).thenReturn(mContext); - mCastTile = new CastTile( - mHost, - mUiEventLogger, - mTestableLooper.getLooper(), - new Handler(mTestableLooper.getLooper()), - new FalsingManagerFake(), - mMetricsLogger, - mStatusBarStateController, - mActivityStarter, - mQSLogger, - mController, - mKeyguard, - mNetworkController, - mHotspotController, - mDialogLaunchAnimator + mWifiInteractor = new WifiInteractorImpl( + new FakeConnectivityRepository(), + mWifiRepository, + mTestScope ); - mCastTile.initialize(); - - // We are not setting the mocks to listening, so we trigger a first refresh state to - // set the initial state - mCastTile.refreshState(); - - mTestableLooper.processAllMessages(); - - mCastTile.handleSetListening(true); - ArgumentCaptor<SignalCallback> signalCallbackArgumentCaptor = - ArgumentCaptor.forClass(SignalCallback.class); - verify(mNetworkController).observe(any(LifecycleOwner.class), - signalCallbackArgumentCaptor.capture()); - mSignalCallback = signalCallbackArgumentCaptor.getValue(); - - ArgumentCaptor<HotspotController.Callback> hotspotCallbackArgumentCaptor = - ArgumentCaptor.forClass(HotspotController.Callback.class); - verify(mHotspotController).observe(any(LifecycleOwner.class), - hotspotCallbackArgumentCaptor.capture()); - mHotspotCallback = hotspotCallbackArgumentCaptor.getValue(); } @After @@ -156,10 +142,11 @@ public class CastTileTest extends SysuiTestCase { // All these tests for enabled/disabled wifi have hotspot not enabled @Test public void testStateUnavailable_wifiDisabled() { + createAndStartTileOldImpl(); IconState qsIcon = new IconState(false, 0, ""); WifiIndicators indicators = new WifiIndicators( false, mock(IconState.class), - qsIcon, false,false, "", + qsIcon, false, false, "", false, ""); mSignalCallback.setWifiIndicators(indicators); mTestableLooper.processAllMessages(); @@ -169,10 +156,11 @@ public class CastTileTest extends SysuiTestCase { @Test public void testStateUnavailable_wifiNotConnected() { + createAndStartTileOldImpl(); IconState qsIcon = new IconState(false, 0, ""); WifiIndicators indicators = new WifiIndicators( true, mock(IconState.class), - qsIcon, false,false, "", + qsIcon, false, false, "", false, ""); mSignalCallback.setWifiIndicators(indicators); mTestableLooper.processAllMessages(); @@ -184,7 +172,7 @@ public class CastTileTest extends SysuiTestCase { IconState qsIcon = new IconState(true, 0, ""); WifiIndicators indicators = new WifiIndicators( true, mock(IconState.class), - qsIcon, false,false, "", + qsIcon, false, false, "", false, ""); mSignalCallback.setWifiIndicators(indicators); mTestableLooper.processAllMessages(); @@ -192,6 +180,7 @@ public class CastTileTest extends SysuiTestCase { @Test public void testStateActive_wifiEnabledAndCasting() { + createAndStartTileOldImpl(); CastController.CastDevice device = new CastController.CastDevice(); device.state = CastController.CastDevice.STATE_CONNECTED; List<CastDevice> devices = new ArrayList<>(); @@ -204,15 +193,87 @@ public class CastTileTest extends SysuiTestCase { @Test public void testStateInactive_wifiEnabledNotCasting() { + createAndStartTileOldImpl(); enableWifiAndProcessMessages(); assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state); } // ------------------------------------------------- // ------------------------------------------------- + // All these tests for enabled/disabled wifi have hotspot not enabled, and have the + // SIGNAL_CALLBACK_DEPRECATION flag set to true + + @Test + public void stateUnavailable_wifiDisabled_newPipeline() { + createAndStartTileNewImpl(); + mWifiRepository.setIsWifiEnabled(false); + mTestableLooper.processAllMessages(); + + assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state); + } + + @Test + public void stateUnavailable_wifiEnabled_notConnected_newPipeline() { + createAndStartTileNewImpl(); + mWifiRepository.setIsWifiEnabled(true); + mWifiRepository.setWifiNetwork(Inactive.INSTANCE); + mTestableLooper.processAllMessages(); + + assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state); + } + + @Test + public void stateActive_wifiConnectedAndCasting_newPipeline() { + createAndStartTileNewImpl(); + CastController.CastDevice device = new CastController.CastDevice(); + device.state = CastDevice.STATE_CONNECTED; + List<CastDevice> devices = new ArrayList<>(); + devices.add(device); + when(mController.getCastDevices()).thenReturn(devices); + + mWifiRepository.setWifiNetwork( + new WifiNetworkModel.Active( + 1 /* networkId */, + true /* isValidated */, + 3 /* level */, + "test" /* ssid */, + WifiNetworkModel.HotspotDeviceType.NONE, + false /* isPasspointAccessPoint */, + false /* isOnlineSignUpforPasspointAccessPoint */, + null /* passpointProviderFriendlyName */ + )); + mTestableLooper.processAllMessages(); + + assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state); + } + + @Test + public void stateInactive_wifiConnectedNotCasting_newPipeline() { + createAndStartTileNewImpl(); + + mWifiRepository.setWifiNetwork( + new WifiNetworkModel.Active( + 1 /* networkId */, + true /* isValidated */, + 3 /* level */, + "test" /* ssid */, + WifiNetworkModel.HotspotDeviceType.NONE, + false /* isPasspointAccessPoint */, + false /* isOnlineSignUpforPasspointAccessPoint */, + null /* passpointProviderFriendlyName */ + )); + mTestableLooper.processAllMessages(); + + assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state); + } + + // ------------------------------------------------- + + // ------------------------------------------------- // All these tests for enabled/disabled hotspot have wifi not enabled @Test public void testStateUnavailable_hotspotDisabled() { + createAndStartTileOldImpl(); mHotspotCallback.onHotspotChanged(false, 0); mTestableLooper.processAllMessages(); @@ -221,6 +282,7 @@ public class CastTileTest extends SysuiTestCase { @Test public void testStateUnavailable_hotspotEnabledNotConnected() { + createAndStartTileOldImpl(); mHotspotCallback.onHotspotChanged(true, 0); mTestableLooper.processAllMessages(); @@ -229,6 +291,7 @@ public class CastTileTest extends SysuiTestCase { @Test public void testStateActive_hotspotEnabledAndConnectedAndCasting() { + createAndStartTileOldImpl(); CastController.CastDevice device = new CastController.CastDevice(); device.state = CastController.CastDevice.STATE_CONNECTED; List<CastDevice> devices = new ArrayList<>(); @@ -242,6 +305,7 @@ public class CastTileTest extends SysuiTestCase { @Test public void testStateInactive_hotspotEnabledAndConnectedAndNotCasting() { + createAndStartTileOldImpl(); mHotspotCallback.onHotspotChanged(true, 1); mTestableLooper.processAllMessages(); assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state); @@ -250,6 +314,7 @@ public class CastTileTest extends SysuiTestCase { @Test public void testHandleClick_castDevicePresent() { + createAndStartTileOldImpl(); CastController.CastDevice device = new CastController.CastDevice(); device.state = CastDevice.STATE_CONNECTED; device.tag = mock(MediaRouter.RouteInfo.class); @@ -267,6 +332,7 @@ public class CastTileTest extends SysuiTestCase { @Test public void testHandleClick_projectionOnly() { + createAndStartTileOldImpl(); CastController.CastDevice device = new CastController.CastDevice(); device.state = CastDevice.STATE_CONNECTED; device.tag = mock(MediaProjectionInfo.class); @@ -283,6 +349,7 @@ public class CastTileTest extends SysuiTestCase { @Test public void testUpdateState_projectionOnly() { + createAndStartTileOldImpl(); CastController.CastDevice device = new CastController.CastDevice(); device.state = CastDevice.STATE_CONNECTED; device.tag = mock(MediaProjectionInfo.class); @@ -298,6 +365,7 @@ public class CastTileTest extends SysuiTestCase { @Test public void testUpdateState_castingAndProjection() { + createAndStartTileOldImpl(); CastController.CastDevice casting = new CastController.CastDevice(); casting.state = CastDevice.STATE_CONNECTED; casting.tag = mock(RouteInfo.class); @@ -322,6 +390,7 @@ public class CastTileTest extends SysuiTestCase { @Test public void testUpdateState_connectedAndConnecting() { + createAndStartTileOldImpl(); CastController.CastDevice connecting = new CastController.CastDevice(); connecting.state = CastDevice.STATE_CONNECTING; connecting.tag = mock(RouteInfo.class); @@ -346,6 +415,7 @@ public class CastTileTest extends SysuiTestCase { @Test public void testExpandView_wifiNotConnected() { + createAndStartTileOldImpl(); mCastTile.refreshState(); mTestableLooper.processAllMessages(); @@ -354,6 +424,7 @@ public class CastTileTest extends SysuiTestCase { @Test public void testExpandView_wifiEnabledNotCasting() { + createAndStartTileOldImpl(); enableWifiAndProcessMessages(); assertTrue(mCastTile.getState().forceExpandIcon); @@ -361,6 +432,7 @@ public class CastTileTest extends SysuiTestCase { @Test public void testExpandView_casting_projection() { + createAndStartTileOldImpl(); CastController.CastDevice device = new CastController.CastDevice(); device.state = CastController.CastDevice.STATE_CONNECTED; List<CastDevice> devices = new ArrayList<>(); @@ -374,6 +446,7 @@ public class CastTileTest extends SysuiTestCase { @Test public void testExpandView_connecting_projection() { + createAndStartTileOldImpl(); CastController.CastDevice connecting = new CastController.CastDevice(); connecting.state = CastDevice.STATE_CONNECTING; connecting.name = "Test Casting Device"; @@ -389,6 +462,7 @@ public class CastTileTest extends SysuiTestCase { @Test public void testExpandView_casting_mediaRoute() { + createAndStartTileOldImpl(); CastController.CastDevice device = new CastController.CastDevice(); device.state = CastDevice.STATE_CONNECTED; device.tag = mock(MediaRouter.RouteInfo.class); @@ -403,6 +477,7 @@ public class CastTileTest extends SysuiTestCase { @Test public void testExpandView_connecting_mediaRoute() { + createAndStartTileOldImpl(); CastController.CastDevice connecting = new CastController.CastDevice(); connecting.state = CastDevice.STATE_CONNECTING; connecting.tag = mock(RouteInfo.class); @@ -416,4 +491,86 @@ public class CastTileTest extends SysuiTestCase { assertTrue(mCastTile.getState().forceExpandIcon); } + + /** + * For simplicity, let this method still set the field even though that's kind of gross + */ + private void createAndStartTileOldImpl() { + mFeatureFlags.set(SIGNAL_CALLBACK_DEPRECATION, false); + mCastTile = new CastTile( + mHost, + mUiEventLogger, + mTestableLooper.getLooper(), + new Handler(mTestableLooper.getLooper()), + new FalsingManagerFake(), + mMetricsLogger, + mStatusBarStateController, + mActivityStarter, + mQSLogger, + mController, + mKeyguard, + mNetworkController, + mHotspotController, + mDialogLaunchAnimator, + mWifiInteractor, + mJavaAdapter, + mFeatureFlags + ); + mCastTile.initialize(); + + // We are not setting the mocks to listening, so we trigger a first refresh state to + // set the initial state + mCastTile.refreshState(); + + mTestableLooper.processAllMessages(); + + mCastTile.handleSetListening(true); + ArgumentCaptor<SignalCallback> signalCallbackArgumentCaptor = + ArgumentCaptor.forClass(SignalCallback.class); + verify(mNetworkController).observe(any(LifecycleOwner.class), + signalCallbackArgumentCaptor.capture()); + mSignalCallback = signalCallbackArgumentCaptor.getValue(); + + ArgumentCaptor<HotspotController.Callback> hotspotCallbackArgumentCaptor = + ArgumentCaptor.forClass(HotspotController.Callback.class); + verify(mHotspotController).observe(any(LifecycleOwner.class), + hotspotCallbackArgumentCaptor.capture()); + mHotspotCallback = hotspotCallbackArgumentCaptor.getValue(); + } + + private void createAndStartTileNewImpl() { + mFeatureFlags.set(SIGNAL_CALLBACK_DEPRECATION, true); + mCastTile = new CastTile( + mHost, + mUiEventLogger, + mTestableLooper.getLooper(), + new Handler(mTestableLooper.getLooper()), + new FalsingManagerFake(), + mMetricsLogger, + mStatusBarStateController, + mActivityStarter, + mQSLogger, + mController, + mKeyguard, + mNetworkController, + mHotspotController, + mDialogLaunchAnimator, + mWifiInteractor, + mJavaAdapter, + mFeatureFlags + ); + mCastTile.initialize(); + + // Since we do not capture the callbacks like in the old impl, set the state to RESUMED + // So that TileJavaAdapter is collecting on flows + mCastTile.setListening(new Object(), true); + + mTestableLooper.processAllMessages(); + + ArgumentCaptor<HotspotController.Callback> hotspotCallbackArgumentCaptor = + ArgumentCaptor.forClass(HotspotController.Callback.class); + verify(mHotspotController).observe(any(LifecycleOwner.class), + hotspotCallbackArgumentCaptor.capture()); + mHotspotCallback = hotspotCallbackArgumentCaptor.getValue(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt new file mode 100644 index 000000000000..b6cf459f579c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles + +import android.os.Handler +import android.service.quicksettings.Tile +import android.testing.TestableLooper +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.internal.logging.MetricsLogger +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingManagerFake +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.qs.tiles.dialog.InternetDialogFactory +import com.android.systemui.statusbar.connectivity.AccessPointController +import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository +import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor +import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy +import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel +import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Wifi +import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.InternetTileViewModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository +import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWithLooper(setAsMainLooper = true) +class InternetTileNewImplTest : SysuiTestCase() { + lateinit var underTest: InternetTileNewImpl + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + private var airplaneModeRepository = FakeAirplaneModeRepository() + private var connectivityRepository = FakeConnectivityRepository() + private var ethernetInteractor = EthernetInteractor(connectivityRepository) + private var mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) + private var wifiRepository = FakeWifiRepository() + private var wifiInteractor = + WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope) + private lateinit var viewModel: InternetTileViewModel + + private lateinit var looper: TestableLooper + + @Mock private lateinit var host: QSHost + @Mock private lateinit var eventLogger: QsEventLogger + @Mock private lateinit var metricsLogger: MetricsLogger + @Mock private lateinit var sbStateController: StatusBarStateController + @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var logger: QSLogger + @Mock private lateinit var dialogFactory: InternetDialogFactory + @Mock private lateinit var accessPointController: AccessPointController + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + looper = TestableLooper.get(this) + + // Allow the tile to load resources + whenever(host.context).thenReturn(context) + whenever(host.userContext).thenReturn(context) + + viewModel = + InternetTileViewModel( + airplaneModeRepository, + connectivityRepository, + ethernetInteractor, + mobileIconsInteractor, + wifiInteractor, + context, + testScope.backgroundScope, + ) + + underTest = + InternetTileNewImpl( + host, + eventLogger, + looper.looper, + Handler(looper.looper), + FalsingManagerFake(), + metricsLogger, + sbStateController, + activityStarter, + logger, + viewModel, + dialogFactory, + accessPointController + ) + + underTest.initialize() + underTest.setListening(Object(), true) + + looper.processAllMessages() + } + + @Test + fun noDefaultConnection_noNetworkAvailable() = + testScope.runTest { + connectivityRepository.defaultConnections.value = DefaultConnectionModel() + wifiRepository.wifiScanResults.value = listOf() + + runCurrent() + looper.processAllMessages() + + assertThat(underTest.state.secondaryLabel.toString()) + .isEqualTo(context.getString(R.string.quick_settings_networks_unavailable)) + assertThat(underTest.state.state).isEqualTo(Tile.STATE_INACTIVE) + } + + @Test + fun noDefaultConnection_networksAvailable() = + testScope.runTest { + connectivityRepository.defaultConnections.value = DefaultConnectionModel() + wifiRepository.wifiScanResults.value = + listOf( + WifiScanEntry(ssid = "ssid 1"), + WifiScanEntry(ssid = "ssid 2"), + ) + + runCurrent() + looper.processAllMessages() + + assertThat(underTest.state.secondaryLabel.toString()) + .isEqualTo(context.getString(R.string.quick_settings_networks_available)) + assertThat(underTest.state.state).isEqualTo(1) + } + + @Test + fun airplaneMode_enabled_wifiDisabled() = + testScope.runTest { + airplaneModeRepository.setIsAirplaneMode(true) + connectivityRepository.defaultConnections.value = DefaultConnectionModel() + wifiRepository.setIsWifiEnabled(false) + + runCurrent() + looper.processAllMessages() + + assertThat(underTest.state.state).isEqualTo(Tile.STATE_INACTIVE) + assertThat(underTest.state.secondaryLabel) + .isEqualTo(context.getString(R.string.status_bar_airplane)) + } + + @Test + fun airplaneMode_enabled_wifiEnabledButNotConnected() = + testScope.runTest { + airplaneModeRepository.setIsAirplaneMode(true) + connectivityRepository.defaultConnections.value = DefaultConnectionModel() + wifiRepository.setIsWifiEnabled(true) + + runCurrent() + looper.processAllMessages() + + assertThat(underTest.state.state).isEqualTo(Tile.STATE_INACTIVE) + assertThat(underTest.state.secondaryLabel) + .isEqualTo(context.getString(R.string.status_bar_airplane)) + } + + @Test + fun airplaneMode_enabled_wifiEnabledAndConnected() = + testScope.runTest { + airplaneModeRepository.setIsAirplaneMode(true) + connectivityRepository.defaultConnections.value = + DefaultConnectionModel( + wifi = Wifi(true), + isValidated = true, + ) + wifiRepository.setIsWifiEnabled(true) + wifiRepository.setWifiNetwork(ACTIVE_WIFI) + + runCurrent() + looper.processAllMessages() + + assertThat(underTest.state.state).isEqualTo(Tile.STATE_ACTIVE) + assertThat(underTest.state.secondaryLabel).isEqualTo(WIFI_SSID) + } + + @Test + fun wifiConnected() = + testScope.runTest { + connectivityRepository.defaultConnections.value = + DefaultConnectionModel( + wifi = Wifi(true), + isValidated = true, + ) + + wifiRepository.setIsWifiEnabled(true) + wifiRepository.setWifiNetwork(ACTIVE_WIFI) + + runCurrent() + looper.processAllMessages() + + assertThat(underTest.state.state).isEqualTo(Tile.STATE_ACTIVE) + assertThat(underTest.state.secondaryLabel).isEqualTo(WIFI_SSID) + } + + companion object { + const val WIFI_SSID = "test ssid" + val ACTIVE_WIFI = + WifiNetworkModel.Active( + networkId = 1, + isValidated = true, + level = 4, + ssid = WIFI_SSID, + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 46cbfacb0044..6006cd42424a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.model.WakefulnessState +import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel import com.android.systemui.model.SysUiState import com.android.systemui.scene.SceneTestUtils.Companion.toDataLayer @@ -99,7 +100,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { ) private val sceneContainerViewModel = SceneContainerViewModel( - interactor = sceneInteractor, + sceneInteractor = sceneInteractor, + falsingInteractor = utils.falsingInteractor(), ) .apply { setTransitionState(transitionState) } @@ -117,7 +119,10 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { private val lockscreenSceneViewModel = LockscreenSceneViewModel( authenticationInteractor = authenticationInteractor, - bouncerInteractor = bouncerInteractor, + longPress = + KeyguardLongPressViewModel( + interactor = mock(), + ), ) private val shadeSceneViewModel = @@ -127,7 +132,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { bouncerInteractor = bouncerInteractor, ) - private val keyguardRepository = utils.keyguardRepository() + private val keyguardRepository = utils.keyguardRepository private val keyguardInteractor = utils.keyguardInteractor( repository = keyguardRepository, @@ -151,6 +156,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { sysUiState = sysUiState, displayId = displayTracker.defaultDisplayId, sceneLogger = mock(), + falsingCollector = utils.falsingCollector(), ) startable.start() @@ -165,9 +171,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @Test fun clickLockButtonAndEnterCorrectPin_unlocksDevice() = testScope.runTest { - lockscreenSceneViewModel.onLockButtonClicked() - assertCurrentScene(SceneKey.Bouncer) - emulateUiSceneTransition() + emulateUserDrivenTransition(SceneKey.Bouncer) enterPin() assertCurrentScene(SceneKey.Gone) @@ -460,10 +464,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { .that(authenticationInteractor.isUnlocked.value) .isFalse() - lockscreenSceneViewModel.onLockButtonClicked() - runCurrent() - emulateUiSceneTransition() - + emulateUserDrivenTransition(SceneKey.Bouncer) enterPin() emulateUiSceneTransition( expectedVisible = false, diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt new file mode 100644 index 000000000000..2187f3657848 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.scene.data.repository + +import androidx.test.filters.SmallTest +import com.android.internal.statusbar.IStatusBarService +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.mockito.Mockito.verify + +@SmallTest +class WindowRootViewVisibilityRepositoryTest : SysuiTestCase() { + private val iStatusBarService = mock<IStatusBarService>() + private val executor = FakeExecutor(FakeSystemClock()) + private val underTest = WindowRootViewVisibilityRepository(iStatusBarService, executor) + + @Test + fun isLockscreenOrShadeVisible_true() { + underTest.setIsLockscreenOrShadeVisible(true) + + assertThat(underTest.isLockscreenOrShadeVisible.value).isTrue() + } + + @Test + fun isLockscreenOrShadeVisible_false() { + underTest.setIsLockscreenOrShadeVisible(false) + + assertThat(underTest.isLockscreenOrShadeVisible.value).isFalse() + } + + @Test + fun onLockscreenOrShadeInteractive_statusBarServiceNotified() { + underTest.onLockscreenOrShadeInteractive( + shouldClearNotificationEffects = true, + notificationCount = 3, + ) + executor.runAllReady() + + verify(iStatusBarService).onPanelRevealed(true, 3) + } + + @Test + fun onLockscreenOrShadeNotInteractive_statusBarServiceNotified() { + underTest.onLockscreenOrShadeNotInteractive() + executor.runAllReady() + + verify(iStatusBarService).onPanelHidden() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index 713c6027d642..8620f6184107 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -147,4 +147,11 @@ class SceneInteractorTest : SysuiTestCase() { underTest.setVisible(true, "reason") assertThat(isVisible).isTrue() } + + @Test + fun userInput() = + testScope.runTest { + underTest.onUserInput() + assertThat(utils.powerRepository.userTouchRegistered).isTrue() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt new file mode 100644 index 000000000000..f304435b7a7a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.scene.domain.interactor + +import androidx.test.filters.SmallTest +import com.android.internal.statusbar.IStatusBarService +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.keyguard.shared.model.WakeSleepReason +import com.android.systemui.keyguard.shared.model.WakefulnessModel +import com.android.systemui.keyguard.shared.model.WakefulnessState +import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository +import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.notification.init.NotificationsController +import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify + +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { + + private val testScope = TestScope() + private val iStatusBarService = mock<IStatusBarService>() + private val executor = FakeExecutor(FakeSystemClock()) + private val windowRootViewVisibilityRepository = + WindowRootViewVisibilityRepository(iStatusBarService, executor) + private val keyguardRepository = FakeKeyguardRepository() + private val headsUpManager = mock<HeadsUpManager>() + private val notificationPresenter = mock<NotificationPresenter>() + private val notificationsController = mock<NotificationsController>() + + private val underTest = + WindowRootViewVisibilityInteractor( + testScope.backgroundScope, + windowRootViewVisibilityRepository, + keyguardRepository, + headsUpManager, + ) + .apply { setUp(notificationPresenter, notificationsController) } + + @Test + fun isLockscreenOrShadeVisible_true() { + underTest.setIsLockscreenOrShadeVisible(true) + + assertThat(underTest.isLockscreenOrShadeVisible.value).isTrue() + } + + @Test + fun isLockscreenOrShadeVisible_false() { + underTest.setIsLockscreenOrShadeVisible(false) + + assertThat(underTest.isLockscreenOrShadeVisible.value).isFalse() + } + + @Test + fun isLockscreenOrShadeVisible_matchesRepo() { + windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(true) + + assertThat(underTest.isLockscreenOrShadeVisible.value).isTrue() + + windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(false) + + assertThat(underTest.isLockscreenOrShadeVisible.value).isFalse() + } + + @Test + fun isLockscreenOrShadeVisibleAndInteractive_notVisible_false() = + testScope.runTest { + val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive) + setWakefulness(WakefulnessState.AWAKE) + + underTest.setIsLockscreenOrShadeVisible(false) + + assertThat(actual).isFalse() + } + + @Test + fun isLockscreenOrShadeVisibleAndInteractive_deviceAsleep_false() = + testScope.runTest { + val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive) + underTest.setIsLockscreenOrShadeVisible(true) + + setWakefulness(WakefulnessState.ASLEEP) + + assertThat(actual).isFalse() + } + + @Test + fun isLockscreenOrShadeVisibleAndInteractive_visibleAndAwake_true() = + testScope.runTest { + val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive) + + underTest.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.AWAKE) + + assertThat(actual).isTrue() + } + + @Test + fun isLockscreenOrShadeVisibleAndInteractive_visibleAndStartingToWake_true() = + testScope.runTest { + val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive) + + underTest.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.STARTING_TO_WAKE) + + assertThat(actual).isTrue() + } + + @Test + fun isLockscreenOrShadeVisibleAndInteractive_visibleAndStartingToSleep_true() = + testScope.runTest { + val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive) + + underTest.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.STARTING_TO_SLEEP) + + assertThat(actual).isTrue() + } + + @Test + fun lockscreenShadeInteractive_statusBarServiceNotified() = + testScope.runTest { + underTest.start() + + makeLockscreenShadeVisible() + testScope.runCurrent() + executor.runAllReady() + + verify(iStatusBarService).onPanelRevealed(any(), any()) + } + + @Test + fun lockscreenShadeNotInteractive_statusBarServiceNotified() = + testScope.runTest { + underTest.start() + + // First, make the shade visible + makeLockscreenShadeVisible() + testScope.runCurrent() + reset(iStatusBarService) + + // WHEN lockscreen or shade is no longer visible + underTest.setIsLockscreenOrShadeVisible(false) + testScope.runCurrent() + executor.runAllReady() + + // THEN status bar service is notified + verify(iStatusBarService).onPanelHidden() + } + + @Test + fun lockscreenShadeInteractive_presenterCollapsed_notifEffectsNotCleared() = + testScope.runTest { + underTest.start() + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true) + + makeLockscreenShadeVisible() + + val shouldClearNotifEffects = argumentCaptor<Boolean>() + verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any()) + assertThat(shouldClearNotifEffects.value).isFalse() + } + + @Test + fun lockscreenShadeInteractive_nullPresenter_notifEffectsNotCleared() = + testScope.runTest { + underTest.start() + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + + underTest.setUp(presenter = null, notificationsController) + + makeLockscreenShadeVisible() + + val shouldClearNotifEffects = argumentCaptor<Boolean>() + verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any()) + assertThat(shouldClearNotifEffects.value).isFalse() + } + + @Test + fun lockscreenShadeInteractive_stateKeyguard_notifEffectsNotCleared() = + testScope.runTest { + underTest.start() + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false) + + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + + makeLockscreenShadeVisible() + + val shouldClearNotifEffects = argumentCaptor<Boolean>() + verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any()) + assertThat(shouldClearNotifEffects.value).isFalse() + } + + @Test + fun lockscreenShadeInteractive_stateShade_presenterNotCollapsed_notifEffectsCleared() = + testScope.runTest { + underTest.start() + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false) + + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + + makeLockscreenShadeVisible() + + val shouldClearNotifEffects = argumentCaptor<Boolean>() + verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any()) + assertThat(shouldClearNotifEffects.value).isTrue() + } + + @Test + fun lockscreenShadeInteractive_stateShadeLocked_presenterNotCollapsed_notifEffectsCleared() = + testScope.runTest { + underTest.start() + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false) + + keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED) + + makeLockscreenShadeVisible() + + val shouldClearNotifEffects = argumentCaptor<Boolean>() + verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any()) + assertThat(shouldClearNotifEffects.value).isTrue() + } + + @Test + fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_notifCountOne() = + testScope.runTest { + underTest.start() + + whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true) + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true) + whenever(notificationsController.getActiveNotificationsCount()).thenReturn(4) + + makeLockscreenShadeVisible() + + val notifCount = argumentCaptor<Int>() + verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture()) + assertThat(notifCount.value).isEqualTo(1) + } + + @Test + fun lockscreenShadeInteractive_hasHeadsUpAndNullPresenter_notifCountOne() = + testScope.runTest { + underTest.start() + + whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true) + underTest.setUp(presenter = null, notificationsController) + + makeLockscreenShadeVisible() + + val notifCount = argumentCaptor<Int>() + verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture()) + assertThat(notifCount.value).isEqualTo(1) + } + + @Test + fun lockscreenShadeInteractive_noHeadsUp_notifCountMatchesNotifController() = + testScope.runTest { + underTest.start() + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true) + + whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false) + whenever(notificationsController.getActiveNotificationsCount()).thenReturn(9) + + makeLockscreenShadeVisible() + + val notifCount = argumentCaptor<Int>() + verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture()) + assertThat(notifCount.value).isEqualTo(9) + } + + @Test + fun lockscreenShadeInteractive_notifPresenterNotCollapsed_notifCountMatchesNotifController() = + testScope.runTest { + underTest.start() + whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true) + + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false) + whenever(notificationsController.getActiveNotificationsCount()).thenReturn(8) + + makeLockscreenShadeVisible() + + val notifCount = argumentCaptor<Int>() + verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture()) + assertThat(notifCount.value).isEqualTo(8) + } + + @Test + fun lockscreenShadeInteractive_noHeadsUp_noNotifController_notifCountZero() = + testScope.runTest { + underTest.start() + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true) + + whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false) + underTest.setUp(notificationPresenter, notificationsController = null) + + makeLockscreenShadeVisible() + + val notifCount = argumentCaptor<Int>() + verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture()) + assertThat(notifCount.value).isEqualTo(0) + } + + private fun makeLockscreenShadeVisible() { + underTest.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.AWAKE) + testScope.runCurrent() + executor.runAllReady() + } + + private fun setWakefulness(state: WakefulnessState) { + val model = WakefulnessModel(state, WakeSleepReason.OTHER, WakeSleepReason.OTHER) + keyguardRepository.setWakefulnessModel(model) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 951cadd7664e..771c3e330e8a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -22,6 +22,7 @@ import android.view.Display import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.domain.model.AuthenticationMethodModel +import com.android.systemui.classifier.FalsingCollector import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.model.WakeSleepReason @@ -45,6 +46,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify @@ -60,13 +62,15 @@ class SceneContainerStartableTest : SysuiTestCase() { private val authenticationInteractor = utils.authenticationInteractor( repository = authenticationRepository, + sceneInteractor = sceneInteractor, ) - private val keyguardRepository = utils.keyguardRepository() + private val keyguardRepository = utils.keyguardRepository private val keyguardInteractor = utils.keyguardInteractor( repository = keyguardRepository, ) private val sysUiState: SysUiState = mock() + private val falsingCollector: FalsingCollector = mock() private val underTest = SceneContainerStartable( @@ -78,6 +82,7 @@ class SceneContainerStartableTest : SysuiTestCase() { sysUiState = sysUiState, displayId = Display.DEFAULT_DISPLAY, sceneLogger = mock(), + falsingCollector = falsingCollector, ) @Test @@ -243,7 +248,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) underTest.start() - keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE) + keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) } @@ -259,7 +264,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) underTest.start() - keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE) + keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) } @@ -275,7 +280,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) underTest.start() - keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE) + keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) } @@ -294,11 +299,217 @@ class SceneContainerStartableTest : SysuiTestCase() { authenticationRepository.setUnlocked(true) runCurrent() - keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE) + keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) } + @Test + fun collectFalsingSignals_onSuccessfulUnlock() = + testScope.runTest { + prepareState( + initialSceneKey = SceneKey.Lockscreen, + authenticationMethod = AuthenticationMethodModel.Pin, + isDeviceUnlocked = false, + ) + underTest.start() + runCurrent() + verify(falsingCollector, never()).onSuccessfulUnlock() + + // Move around scenes without unlocking. + listOf( + SceneKey.Shade, + SceneKey.QuickSettings, + SceneKey.Shade, + SceneKey.Lockscreen, + SceneKey.Bouncer, + ) + .forEach { sceneKey -> + sceneInteractor.changeScene(SceneModel(sceneKey), "reason") + runCurrent() + verify(falsingCollector, never()).onSuccessfulUnlock() + } + + // Changing to the Gone scene should report a successful unlock. + sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason") + runCurrent() + verify(falsingCollector).onSuccessfulUnlock() + + // Move around scenes without changing back to Lockscreen, shouldn't report another + // unlock. + listOf( + SceneKey.Shade, + SceneKey.QuickSettings, + SceneKey.Shade, + SceneKey.Gone, + ) + .forEach { sceneKey -> + sceneInteractor.changeScene(SceneModel(sceneKey), "reason") + runCurrent() + verify(falsingCollector, times(1)).onSuccessfulUnlock() + } + + // Changing to the Lockscreen scene shouldn't report a successful unlock. + sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason") + runCurrent() + verify(falsingCollector, times(1)).onSuccessfulUnlock() + + // Move around scenes without unlocking. + listOf( + SceneKey.Shade, + SceneKey.QuickSettings, + SceneKey.Shade, + SceneKey.Lockscreen, + SceneKey.Bouncer, + ) + .forEach { sceneKey -> + sceneInteractor.changeScene(SceneModel(sceneKey), "reason") + runCurrent() + verify(falsingCollector, times(1)).onSuccessfulUnlock() + } + + // Changing to the Gone scene should report a second successful unlock. + sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason") + runCurrent() + verify(falsingCollector, times(2)).onSuccessfulUnlock() + } + + @Test + fun collectFalsingSignals_setShowingAod() = + testScope.runTest { + prepareState( + initialSceneKey = SceneKey.Lockscreen, + authenticationMethod = AuthenticationMethodModel.Pin, + isDeviceUnlocked = false, + ) + underTest.start() + runCurrent() + verify(falsingCollector).setShowingAod(false) + + keyguardRepository.setIsDozing(true) + runCurrent() + verify(falsingCollector).setShowingAod(true) + + keyguardRepository.setIsDozing(false) + runCurrent() + verify(falsingCollector, times(2)).setShowingAod(false) + } + + @Test + fun collectFalsingSignals_screenOnAndOff_aodUnavailable() = + testScope.runTest { + keyguardRepository.setAodAvailable(false) + runCurrent() + prepareState( + initialSceneKey = SceneKey.Lockscreen, + authenticationMethod = AuthenticationMethodModel.Pin, + isDeviceUnlocked = false, + ) + underTest.start() + runCurrent() + verify(falsingCollector, never()).onScreenTurningOn() + verify(falsingCollector, never()).onScreenOnFromTouch() + verify(falsingCollector, never()).onScreenOff() + + keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) + runCurrent() + verify(falsingCollector, times(1)).onScreenTurningOn() + verify(falsingCollector, never()).onScreenOnFromTouch() + verify(falsingCollector, never()).onScreenOff() + + keyguardRepository.setWakefulnessModel(ASLEEP) + runCurrent() + verify(falsingCollector, times(1)).onScreenTurningOn() + verify(falsingCollector, never()).onScreenOnFromTouch() + verify(falsingCollector, times(1)).onScreenOff() + + keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_TAP) + runCurrent() + verify(falsingCollector, times(1)).onScreenTurningOn() + verify(falsingCollector, times(1)).onScreenOnFromTouch() + verify(falsingCollector, times(1)).onScreenOff() + + keyguardRepository.setWakefulnessModel(ASLEEP) + runCurrent() + verify(falsingCollector, times(1)).onScreenTurningOn() + verify(falsingCollector, times(1)).onScreenOnFromTouch() + verify(falsingCollector, times(2)).onScreenOff() + + keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) + runCurrent() + verify(falsingCollector, times(2)).onScreenTurningOn() + verify(falsingCollector, times(1)).onScreenOnFromTouch() + verify(falsingCollector, times(2)).onScreenOff() + } + + @Test + fun collectFalsingSignals_screenOnAndOff_aodAvailable() = + testScope.runTest { + keyguardRepository.setAodAvailable(true) + runCurrent() + prepareState( + initialSceneKey = SceneKey.Lockscreen, + authenticationMethod = AuthenticationMethodModel.Pin, + isDeviceUnlocked = false, + ) + underTest.start() + runCurrent() + verify(falsingCollector, never()).onScreenTurningOn() + verify(falsingCollector, never()).onScreenOnFromTouch() + verify(falsingCollector, never()).onScreenOff() + + keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) + runCurrent() + verify(falsingCollector, never()).onScreenTurningOn() + verify(falsingCollector, never()).onScreenOnFromTouch() + verify(falsingCollector, never()).onScreenOff() + + keyguardRepository.setWakefulnessModel(ASLEEP) + runCurrent() + verify(falsingCollector, never()).onScreenTurningOn() + verify(falsingCollector, never()).onScreenOnFromTouch() + verify(falsingCollector, never()).onScreenOff() + + keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_TAP) + runCurrent() + verify(falsingCollector, never()).onScreenTurningOn() + verify(falsingCollector, never()).onScreenOnFromTouch() + verify(falsingCollector, never()).onScreenOff() + + keyguardRepository.setWakefulnessModel(ASLEEP) + runCurrent() + verify(falsingCollector, never()).onScreenTurningOn() + verify(falsingCollector, never()).onScreenOnFromTouch() + verify(falsingCollector, never()).onScreenOff() + + keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) + runCurrent() + verify(falsingCollector, never()).onScreenTurningOn() + verify(falsingCollector, never()).onScreenOnFromTouch() + verify(falsingCollector, never()).onScreenOff() + } + + @Test + fun collectFalsingSignals_bouncerVisibility() = + testScope.runTest { + prepareState( + initialSceneKey = SceneKey.Lockscreen, + authenticationMethod = AuthenticationMethodModel.Pin, + isDeviceUnlocked = false, + ) + underTest.start() + runCurrent() + verify(falsingCollector).onBouncerHidden() + + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + runCurrent() + verify(falsingCollector).onBouncerShown() + + sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason") + runCurrent() + verify(falsingCollector, times(2)).onBouncerHidden() + } + private fun prepareState( isDeviceUnlocked: Boolean = false, isBypassEnabled: Boolean = false, @@ -334,11 +545,23 @@ class SceneContainerStartableTest : SysuiTestCase() { lastWakeReason = WakeSleepReason.POWER_BUTTON, lastSleepReason = WakeSleepReason.POWER_BUTTON ) - private val STARTING_TO_WAKE = + private val ASLEEP = + WakefulnessModel( + state = WakefulnessState.ASLEEP, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.POWER_BUTTON + ) + private val STARTING_TO_WAKE_FROM_POWER_BUTTON = WakefulnessModel( state = WakefulnessState.STARTING_TO_WAKE, lastWakeReason = WakeSleepReason.POWER_BUTTON, lastSleepReason = WakeSleepReason.POWER_BUTTON ) + private val STARTING_TO_WAKE_FROM_TAP = + WakefulnessModel( + state = WakefulnessState.STARTING_TO_WAKE, + lastWakeReason = WakeSleepReason.TAP, + lastSleepReason = WakeSleepReason.POWER_BUTTON + ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt index 88abb642f7c1..0b56a59ca467 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt @@ -39,7 +39,8 @@ class SceneContainerViewModelTest : SysuiTestCase() { private val interactor = utils.sceneInteractor() private val underTest = SceneContainerViewModel( - interactor = interactor, + sceneInteractor = interactor, + falsingInteractor = utils.falsingInteractor(), ) @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 209dcc1dd203..c573ac638032 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -716,7 +716,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mAmbientState, mRecordingController, mFalsingManager, - new FalsingCollectorFake(), mAccessibilityManager, mLockscreenGestureLogger, mMetricsLogger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 1738b06cc0b3..dfd782b73a22 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -63,6 +63,8 @@ import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.google.common.util.concurrent.MoreExecutors; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -73,6 +75,7 @@ import org.mockito.MockitoAnnotations; import org.mockito.Spy; import java.util.List; +import java.util.concurrent.Executor; @RunWith(AndroidTestingRunner.class) @RunWithLooper @@ -98,6 +101,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { @Mock private ShadeWindowLogger mShadeWindowLogger; @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener; + private final Executor mBackgroundExecutor = MoreExecutors.directExecutor(); private NotificationShadeWindowControllerImpl mNotificationShadeWindowController; private float mPreferredRefreshRate = -1; @@ -125,6 +129,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController, + mBackgroundExecutor, mColorExtractor, mDumpManager, mKeyguardStateController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 3cce4232ab7a..39fe4989260e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -36,6 +36,7 @@ import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.dock.DockManager +import com.android.systemui.dump.DumpManager import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags @@ -56,6 +57,8 @@ import com.android.systemui.statusbar.notification.data.repository.NotificationE import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.phone.CentralSurfaces +import com.android.systemui.statusbar.phone.DozeScrimController +import com.android.systemui.statusbar.phone.DozeServiceHost import com.android.systemui.statusbar.phone.PhoneStatusBarViewController import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.window.StatusBarWindowStateController @@ -90,6 +93,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Mock private lateinit var view: NotificationShadeWindowView @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController @Mock private lateinit var centralSurfaces: CentralSurfaces + @Mock private lateinit var dozeServiceHost: DozeServiceHost + @Mock private lateinit var dozeScrimController: DozeScrimController @Mock private lateinit var backActionInteractor: BackActionInteractor @Mock private lateinit var powerInteractor: PowerInteractor @Mock private lateinit var dockManager: DockManager @@ -98,6 +103,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController @Mock private lateinit var shadeLogger: ShadeLogger + @Mock private lateinit var dumpManager: DumpManager @Mock private lateinit var ambientState: AmbientState @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel @Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController @@ -155,46 +161,49 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { fakeClock = FakeSystemClock() underTest = NotificationShadeWindowViewController( - lockscreenShadeTransitionController, - FalsingCollectorFake(), - sysuiStatusBarStateController, - dockManager, - notificationShadeDepthController, - view, - notificationPanelViewController, - ShadeExpansionStateManager(), - stackScrollLayoutController, - statusBarKeyguardViewManager, - statusBarWindowStateController, - lockIconViewController, - centralSurfaces, - backActionInteractor, - powerInteractor, - notificationShadeWindowController, - unfoldTransitionProgressProvider, - keyguardUnlockAnimationController, - notificationInsetsController, - ambientState, - shadeLogger, - pulsingGestureListener, - mLockscreenHostedDreamGestureListener, - keyguardBouncerViewModel, - keyguardBouncerComponentFactory, - mock(KeyguardMessageAreaController.Factory::class.java), - keyguardTransitionInteractor, - primaryBouncerToGoneTransitionViewModel, - notificationExpansionRepository, - featureFlags, - fakeClock, - BouncerMessageInteractor( - FakeBouncerMessageRepository(), - mock(BouncerMessageFactory::class.java), - FakeUserRepository(), - CountDownTimerUtil(), - featureFlags - ), - BouncerLogger(logcatLogBuffer("BouncerLog")), - keyEventInteractor, + lockscreenShadeTransitionController, + FalsingCollectorFake(), + sysuiStatusBarStateController, + dockManager, + notificationShadeDepthController, + view, + notificationPanelViewController, + ShadeExpansionStateManager(), + stackScrollLayoutController, + statusBarKeyguardViewManager, + statusBarWindowStateController, + lockIconViewController, + centralSurfaces, + dozeServiceHost, + dozeScrimController, + backActionInteractor, + powerInteractor, + notificationShadeWindowController, + unfoldTransitionProgressProvider, + keyguardUnlockAnimationController, + notificationInsetsController, + ambientState, + shadeLogger, + dumpManager, + pulsingGestureListener, + mLockscreenHostedDreamGestureListener, + keyguardBouncerViewModel, + keyguardBouncerComponentFactory, + mock(KeyguardMessageAreaController.Factory::class.java), + keyguardTransitionInteractor, + primaryBouncerToGoneTransitionViewModel, + notificationExpansionRepository, + featureFlags, + fakeClock, + BouncerMessageInteractor( + FakeBouncerMessageRepository(), + mock(BouncerMessageFactory::class.java), + FakeUserRepository(), + CountDownTimerUtil(), + featureFlags + ), + BouncerLogger(logcatLogBuffer("BouncerLog")), + keyEventInteractor, ) underTest.setupExpandedStatusBar() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index 66d48d64b3a3..3031658d3bcc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -35,6 +35,7 @@ import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.dock.DockManager +import com.android.systemui.dump.DumpManager import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags @@ -56,6 +57,8 @@ import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.phone.CentralSurfaces +import com.android.systemui.statusbar.phone.DozeScrimController +import com.android.systemui.statusbar.phone.DozeServiceHost import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.window.StatusBarWindowStateController import com.android.systemui.unfold.UnfoldTransitionProgressProvider @@ -89,6 +92,8 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController @Mock private lateinit var shadeController: ShadeController @Mock private lateinit var centralSurfaces: CentralSurfaces + @Mock private lateinit var dozeServiceHost: DozeServiceHost + @Mock private lateinit var dozeScrimController: DozeScrimController @Mock private lateinit var backActionInteractor: BackActionInteractor @Mock private lateinit var powerInteractor: PowerInteractor @Mock private lateinit var dockManager: DockManager @@ -107,6 +112,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController @Mock private lateinit var ambientState: AmbientState @Mock private lateinit var shadeLogger: ShadeLogger + @Mock private lateinit var dumpManager: DumpManager @Mock private lateinit var pulsingGestureListener: PulsingGestureListener @Mock private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener @@ -174,6 +180,8 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { statusBarWindowStateController, lockIconViewController, centralSurfaces, + dozeServiceHost, + dozeScrimController, backActionInteractor, powerInteractor, notificationShadeWindowController, @@ -182,6 +190,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { notificationInsetsController, ambientState, shadeLogger, + dumpManager, pulsingGestureListener, mLockscreenHostedDreamGestureListener, keyguardBouncerViewModel, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java index e22e5713d699..ab0ae05b0e83 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -37,7 +37,6 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.TestScopeProvider; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; -import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentHostManager; @@ -132,7 +131,6 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { @Mock protected AmbientState mAmbientState; @Mock protected RecordingController mRecordingController; @Mock protected FalsingManager mFalsingManager; - @Mock protected FalsingCollector mFalsingCollector; @Mock protected AccessibilityManager mAccessibilityManager; @Mock protected LockscreenGestureLogger mLockscreenGestureLogger; @Mock protected MetricsLogger mMetricsLogger; @@ -242,7 +240,6 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { mAmbientState, mRecordingController, mFalsingManager, - mFalsingCollector, mAccessibilityManager, mLockscreenGestureLogger, mMetricsLogger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt index 6a14a005b0f0..bf2d6a6f2f16 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt @@ -20,20 +20,28 @@ import android.testing.AndroidTestingRunner import android.view.Display import android.view.WindowManager import androidx.test.filters.SmallTest +import com.android.internal.statusbar.IStatusBarService +import com.android.keyguard.TestScopeProvider import com.android.systemui.SysuiTestCase import com.android.systemui.assist.AssistManager +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.log.LogBuffer import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.notification.row.NotificationGutsManager import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.window.StatusBarWindowController import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat import dagger.Lazy import org.junit.Before import org.junit.Test @@ -47,6 +55,8 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @SmallTest class ShadeControllerImplTest : SysuiTestCase() { + private val executor = FakeExecutor(FakeSystemClock()) + @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var statusBarStateController: StatusBarStateController @@ -61,6 +71,17 @@ class ShadeControllerImplTest : SysuiTestCase() { @Mock private lateinit var nswvc: NotificationShadeWindowViewController @Mock private lateinit var display: Display @Mock private lateinit var touchLog: LogBuffer + @Mock private lateinit var iStatusBarService: IStatusBarService + @Mock private lateinit var headsUpManager: HeadsUpManager + + private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy { + WindowRootViewVisibilityInteractor( + TestScopeProvider.getTestScope(), + WindowRootViewVisibilityRepository(iStatusBarService, executor), + FakeKeyguardRepository(), + headsUpManager, + ) + } private lateinit var shadeController: ShadeControllerImpl @@ -74,6 +95,7 @@ class ShadeControllerImplTest : SysuiTestCase() { commandQueue, FakeExecutor(FakeSystemClock()), touchLog, + windowRootViewVisibilityInteractor, keyguardStateController, statusBarStateController, statusBarKeyguardViewManager, @@ -86,6 +108,7 @@ class ShadeControllerImplTest : SysuiTestCase() { Lazy { gutsManager }, ) shadeController.setNotificationShadeWindowViewController(nswvc) + shadeController.setVisibilityListener(mock()) } @Test @@ -133,4 +156,24 @@ class ShadeControllerImplTest : SysuiTestCase() { // VERIFY that cancelCurrentTouch is NOT called verify(nswvc, never()).cancelCurrentTouch() } + + @Test + fun visible_changesToTrue_windowInteractorUpdated() { + shadeController.makeExpandedVisible(true) + + assertThat(windowRootViewVisibilityInteractor.isLockscreenOrShadeVisible.value).isTrue() + } + + @Test + fun visible_changesToFalse_windowInteractorUpdated() { + // GIVEN the shade is currently expanded + shadeController.makeExpandedVisible(true) + assertThat(windowRootViewVisibilityInteractor.isLockscreenOrShadeVisible.value).isTrue() + + // WHEN the shade is collapsed + shadeController.collapseShade() + + // THEN the interactor is notified + assertThat(windowRootViewVisibilityInteractor.isLockscreenOrShadeVisible.value).isFalse() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt index 786856b0baa6..66d2465750ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt @@ -20,6 +20,7 @@ import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor +import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State.CONNECTED import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.privacy.PrivacyItemController @@ -105,5 +106,7 @@ class SystemEventCoordinatorTest : SysuiTestCase() { suspend fun emit(value: ConnectedDisplayInteractor.State) = flow.emit(value) override val connectedDisplayState: Flow<ConnectedDisplayInteractor.State> get() = flow + override val pendingDisplay: Flow<PendingDisplay?> + get() = MutableSharedFlow<PendingDisplay>() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java index 7117c233c8c5..bbf0151a90ce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java @@ -40,8 +40,14 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.InstanceId; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; +import com.android.keyguard.TestScopeProvider; import com.android.systemui.SysuiTestCase; -import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; +import com.android.systemui.keyguard.shared.model.WakeSleepReason; +import com.android.systemui.keyguard.shared.model.WakefulnessModel; +import com.android.systemui.keyguard.shared.model.WakefulnessState; +import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository; +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateControllerImpl; @@ -54,7 +60,9 @@ import com.android.systemui.statusbar.notification.collection.render.Notificatio import com.android.systemui.statusbar.notification.logging.nano.Notifications; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; +import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.time.FakeSystemClock; import com.google.android.collect.Lists; @@ -71,6 +79,8 @@ import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; +import kotlinx.coroutines.test.TestScope; + @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -89,7 +99,7 @@ public class NotificationLoggerTest extends SysuiTestCase { @Mock private NotificationVisibilityProvider mVisibilityProvider; @Mock private NotifPipeline mNotifPipeline; @Mock private NotificationListener mListener; - @Mock private ShadeExpansionStateManager mShadeExpansionStateManager; + @Mock private HeadsUpManager mHeadsUpManager; private NotificationEntry mEntry; private TestableNotificationLogger mLogger; @@ -97,12 +107,23 @@ public class NotificationLoggerTest extends SysuiTestCase { private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); private NotificationPanelLoggerFake mNotificationPanelLoggerFake = new NotificationPanelLoggerFake(); + private final TestScope mTestScope = TestScopeProvider.getTestScope(); + private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository(); + private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; + private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope()); @Before public void setUp() { MockitoAnnotations.initMocks(this); when(mNotifLiveDataStore.getActiveNotifList()).thenReturn(mActiveNotifEntries); + mWindowRootViewVisibilityInteractor = new WindowRootViewVisibilityInteractor( + mTestScope.getBackgroundScope(), + new WindowRootViewVisibilityRepository(mBarService, mUiBgExecutor), + mKeyguardRepository, + mHeadsUpManager); + mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true); + mEntry = new NotificationEntryBuilder() .setPkg(TEST_PACKAGE_NAME) .setOpPkg(TEST_PACKAGE_NAME) @@ -120,10 +141,12 @@ public class NotificationLoggerTest extends SysuiTestCase { mVisibilityProvider, mNotifPipeline, mock(StatusBarStateControllerImpl.class), - mShadeExpansionStateManager, + mWindowRootViewVisibilityInteractor, + mJavaAdapter, mBarService, mExpansionStateLogger ); + mLogger.start(); mLogger.setUpWithContainer(mListContainer); verify(mNotifPipeline).addCollectionListener(any()); } @@ -183,31 +206,26 @@ public class NotificationLoggerTest extends SysuiTestCase { Mockito.reset(mBarService); setStateAsleep(); - mLogger.onDozingChanged(false); // Wake to lockscreen - mLogger.onDozingChanged(true); // And go back to sleep, turning off logging + + setStateAwake(); // Wake to lockscreen + + setStateAsleep(); // And go back to sleep, turning off logging mUiBgExecutor.runAllReady(); + // The visibility objects are recycled by NotificationLogger, so we can't use specific // matchers here. verify(mBarService, times(1)).onNotificationVisibilityChanged(any(), any()); } - private void setStateAsleep() { - mLogger.onShadeExpansionFullyChanged(true); - mLogger.onDozingChanged(true); - mLogger.onStateChanged(StatusBarState.KEYGUARD); - } - - private void setStateAwake() { - mLogger.onShadeExpansionFullyChanged(false); - mLogger.onDozingChanged(false); - mLogger.onStateChanged(StatusBarState.SHADE); - } - @Test - public void testLogPanelShownOnWake() { + public void testLogPanelShownOnWakeToLockscreen() { when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry)); setStateAsleep(); - mLogger.onDozingChanged(false); // Wake to lockscreen + + // Wake to lockscreen + mLogger.onStateChanged(StatusBarState.KEYGUARD); + setStateAwake(); + assertEquals(1, mNotificationPanelLoggerFake.getCalls().size()); assertTrue(mNotificationPanelLoggerFake.get(0).isLockscreen); assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length); @@ -222,9 +240,14 @@ public class NotificationLoggerTest extends SysuiTestCase { @Test public void testLogPanelShownOnShadePull() { when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry)); + // Start as awake, but with the panel not visible setStateAwake(); + mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false); + // Now expand panel - mLogger.onShadeExpansionFullyChanged(true); + mLogger.onStateChanged(StatusBarState.SHADE); + mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true); + assertEquals(1, mNotificationPanelLoggerFake.getCalls().size()); assertFalse(mNotificationPanelLoggerFake.get(0).isLockscreen); assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length); @@ -251,13 +274,34 @@ public class NotificationLoggerTest extends SysuiTestCase { when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(entry)); setStateAsleep(); - mLogger.onDozingChanged(false); // Wake to lockscreen + + // Wake to lockscreen + setStateAwake(); + assertEquals(1, mNotificationPanelLoggerFake.getCalls().size()); assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length); Notifications.Notification n = mNotificationPanelLoggerFake.get(0).list.notifications[0]; assertEquals(0, n.instanceId); } + private void setStateAsleep() { + mKeyguardRepository.setWakefulnessModel( + new WakefulnessModel( + WakefulnessState.ASLEEP, + WakeSleepReason.OTHER, + WakeSleepReason.OTHER)); + mTestScope.getTestScheduler().runCurrent(); + } + + private void setStateAwake() { + mKeyguardRepository.setWakefulnessModel( + new WakefulnessModel( + WakefulnessState.AWAKE, + WakeSleepReason.OTHER, + WakeSleepReason.OTHER)); + mTestScope.getTestScheduler().runCurrent(); + } + private class TestableNotificationLogger extends NotificationLogger { TestableNotificationLogger(NotificationListener notificationListener, @@ -266,7 +310,8 @@ public class NotificationLoggerTest extends SysuiTestCase { NotificationVisibilityProvider visibilityProvider, NotifPipeline notifPipeline, StatusBarStateControllerImpl statusBarStateController, - ShadeExpansionStateManager shadeExpansionStateManager, + WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor, + JavaAdapter javaAdapter, IStatusBarService barService, ExpansionStateLogger expansionStateLogger) { super( @@ -276,7 +321,8 @@ public class NotificationLoggerTest extends SysuiTestCase { visibilityProvider, notifPipeline, statusBarStateController, - shadeExpansionStateManager, + windowRootViewVisibilityInteractor, + javaAdapter, expansionStateLogger, mNotificationPanelLoggerFake ); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt index 0cc0b987aa34..7c8199eaa0e5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt @@ -27,7 +27,6 @@ import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.internal.statusbar.IStatusBarService import com.android.systemui.SysuiTestCase -import com.android.systemui.classifier.FalsingCollector import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.FeatureFlags import com.android.systemui.plugins.FalsingManager @@ -57,6 +56,7 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.withArgCaptor import com.android.systemui.util.time.SystemClock import com.android.systemui.wmshell.BubblesManager +import java.util.Optional import junit.framework.Assert import org.junit.After import org.junit.Before @@ -68,7 +68,6 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever -import java.util.Optional @SmallTest @RunWith(AndroidTestingRunner::class) @@ -100,7 +99,6 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { private val gutsManager: NotificationGutsManager = mock() private val onUserInteractionCallback: OnUserInteractionCallback = mock() private val falsingManager: FalsingManager = mock() - private val falsingCollector: FalsingCollector = mock() private val featureFlags: FeatureFlags = mock() private val peopleNotificationIdentifier: PeopleNotificationIdentifier = mock() private val bubblesManager: BubblesManager = mock() @@ -140,7 +138,6 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { /*allowLongPress=*/ false, onUserInteractionCallback, falsingManager, - falsingCollector, featureFlags, peopleNotificationIdentifier, Optional.of(bubblesManager), @@ -226,20 +223,20 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { fun registerSettingsListener_forBubbles() { controller.init(mock(NotificationEntry::class.java)) val viewStateObserver = withArgCaptor { - verify(view).addOnAttachStateChangeListener(capture()); + verify(view).addOnAttachStateChangeListener(capture()) } - viewStateObserver.onViewAttachedToWindow(view); - verify(settingsController).addCallback(any(), any()); + viewStateObserver.onViewAttachedToWindow(view) + verify(settingsController).addCallback(any(), any()) } @Test fun unregisterSettingsListener_forBubbles() { controller.init(mock(NotificationEntry::class.java)) val viewStateObserver = withArgCaptor { - verify(view).addOnAttachStateChangeListener(capture()); + verify(view).addOnAttachStateChangeListener(capture()) } - viewStateObserver.onViewDetachedFromWindow(view); - verify(settingsController).removeCallback(any(), any()); + viewStateObserver.onViewDetachedFromWindow(view) + verify(settingsController).removeCallback(any(), any()) } @Test @@ -263,11 +260,17 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { whenever(view.privateLayout).thenReturn(childView) controller.mSettingsListener.onSettingChanged( - BUBBLES_SETTING_URI, view.entry.sbn.userId, "1") + BUBBLES_SETTING_URI, + view.entry.sbn.userId, + "1" + ) verify(childView).setBubblesEnabledForUser(true) controller.mSettingsListener.onSettingChanged( - BUBBLES_SETTING_URI, view.entry.sbn.userId, "9") + BUBBLES_SETTING_URI, + view.entry.sbn.userId, + "9" + ) verify(childView).setBubblesEnabledForUser(false) } @@ -277,13 +280,12 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { whenever(view.privateLayout).thenReturn(childView) val notification = Notification.Builder(mContext).build() - val sbn = SbnBuilder().setNotification(notification) - .setUser(UserHandle.of(USER_ALL)) - .build() - whenever(view.entry).thenReturn(NotificationEntryBuilder() - .setSbn(sbn) - .setUser(UserHandle.of(USER_ALL)) - .build()) + val sbn = + SbnBuilder().setNotification(notification).setUser(UserHandle.of(USER_ALL)).build() + whenever(view.entry) + .thenReturn( + NotificationEntryBuilder().setSbn(sbn).setUser(UserHandle.of(USER_ALL)).build() + ) controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, 9, "1") verify(childView).setBubblesEnabledForUser(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index 705d52bcf13f..9e0f83c9fc53 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -37,6 +37,7 @@ import static org.mockito.ArgumentMatchers.anySet; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -66,11 +67,16 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.testing.UiEventLoggerFake; +import com.android.internal.statusbar.IStatusBarService; +import com.android.keyguard.TestScopeProvider; import com.android.systemui.SysuiTestCase; +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; import com.android.systemui.people.widget.PeopleSpaceWidgetManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository; +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.settings.UserContextProvider; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -84,6 +90,9 @@ import com.android.systemui.statusbar.notification.row.NotificationGutsManager.O import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.kotlin.JavaAdapter; +import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.wmshell.BubblesManager; import org.junit.Before; @@ -97,6 +106,8 @@ import org.mockito.junit.MockitoRule; import java.util.Optional; +import kotlinx.coroutines.test.TestScope; + /** * Tests for {@link NotificationGutsManager}. */ @@ -108,6 +119,10 @@ public class NotificationGutsManagerTest extends SysuiTestCase { private NotificationChannel mTestNotificationChannel = new NotificationChannel( TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT); + + private TestScope mTestScope = TestScopeProvider.getTestScope(); + private JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope()); + private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); private TestableLooper mTestableLooper; private Handler mHandler; private NotificationTestHelper mHelper; @@ -124,6 +139,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { @Mock private AccessibilityManager mAccessibilityManager; @Mock private HighPriorityProvider mHighPriorityProvider; @Mock private INotificationManager mINotificationManager; + @Mock private IStatusBarService mBarService; @Mock private LauncherApps mLauncherApps; @Mock private ShortcutManager mShortcutManager; @Mock private ChannelEditorDialogController mChannelEditorDialogController; @@ -140,6 +156,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { @Mock private UserManager mUserManager; + private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; + @Before public void setUp() { mTestableLooper = TestableLooper.get(this); @@ -148,21 +166,42 @@ public class NotificationGutsManagerTest extends SysuiTestCase { mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)); when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); - mGutsManager = new NotificationGutsManager(mContext, mHandler, mHandler, + mWindowRootViewVisibilityInteractor = new WindowRootViewVisibilityInteractor( + mTestScope.getBackgroundScope(), + new WindowRootViewVisibilityRepository(mBarService, mExecutor), + new FakeKeyguardRepository(), + mHeadsUpManagerPhone); + + mGutsManager = new NotificationGutsManager( + mContext, + mHandler, + mHandler, + mJavaAdapter, mAccessibilityManager, - mHighPriorityProvider, mINotificationManager, mUserManager, - mPeopleSpaceWidgetManager, mLauncherApps, mShortcutManager, - mChannelEditorDialogController, mContextTracker, mAssistantFeedbackController, - Optional.of(mBubblesManager), new UiEventLoggerFake(), mOnUserInteractionCallback, + mHighPriorityProvider, + mINotificationManager, + mUserManager, + mPeopleSpaceWidgetManager, + mLauncherApps, + mShortcutManager, + mChannelEditorDialogController, + mContextTracker, + mAssistantFeedbackController, + Optional.of(mBubblesManager), + new UiEventLoggerFake(), + mOnUserInteractionCallback, mShadeController, + mWindowRootViewVisibilityInteractor, mNotificationLockscreenUserManager, mStatusBarStateController, mDeviceProvisionedController, mMetricsLogger, - mHeadsUpManagerPhone, mActivityStarter); + mHeadsUpManagerPhone, + mActivityStarter); mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer, mOnSettingsClickListener); mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter); + mGutsManager.start(); } //////////////////////////////////////////////////////////////////////////////////////////////// @@ -210,6 +249,62 @@ public class NotificationGutsManagerTest extends SysuiTestCase { } @Test + public void testLockscreenShadeVisible_visible_gutsNotClosed() { + // First, start out lockscreen or shade as not visible + mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false); + mTestScope.getTestScheduler().runCurrent(); + + NotificationGuts guts = mock(NotificationGuts.class); + mGutsManager.setExposedGuts(guts); + + // WHEN the lockscreen or shade becomes visible + mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true); + mTestScope.getTestScheduler().runCurrent(); + + // THEN the guts are not closed + verify(guts, never()).removeCallbacks(any()); + verify(guts, never()).closeControls( + anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean()); + } + + @Test + public void testLockscreenShadeVisible_notVisible_gutsClosed() { + // First, start out lockscreen or shade as visible + mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true); + mTestScope.getTestScheduler().runCurrent(); + + NotificationGuts guts = mock(NotificationGuts.class); + mGutsManager.setExposedGuts(guts); + + // WHEN the lockscreen or shade is no longer visible + mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false); + mTestScope.getTestScheduler().runCurrent(); + + // THEN the guts are closed + verify(guts).removeCallbacks(any()); + verify(guts).closeControls( + /* leavebehinds= */ eq(true), + /* controls= */ eq(true), + /* x= */ anyInt(), + /* y= */ anyInt(), + /* force= */ eq(true)); + } + + @Test + public void testLockscreenShadeVisible_notVisible_listContainerReset() { + // First, start out lockscreen or shade as visible + mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true); + mTestScope.getTestScheduler().runCurrent(); + + // WHEN the lockscreen or shade is no longer visible + mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false); + mTestScope.getTestScheduler().runCurrent(); + + // THEN the list container is reset + verify(mNotificationListContainer).resetExposedMenuView(anyBoolean(), anyBoolean()); + } + + @Test public void testChangeDensityOrFontScale() { NotificationGuts guts = spy(new NotificationGuts(mContext)); when(guts.post(any())).thenAnswer(invocation -> { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 0425830db8ba..9dfcb3f75eb1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -54,7 +54,6 @@ import androidx.annotation.NonNull; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.TestableDependency; -import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.FeatureFlags; @@ -600,7 +599,6 @@ public class NotificationTestHelper { mock(OnExpandClickListener.class), mock(ExpandableNotificationRow.CoordinateOnClickListener.class), new FalsingManagerFake(), - new FalsingCollectorFake(), mStatusBarStateController, mPeopleNotificationIdentifier, mOnUserInteractionCallback, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index f47efe3f79c4..5a1450f1c57b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -24,7 +24,6 @@ import static com.android.systemui.statusbar.StatusBarState.SHADE; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; -import static junit.framework.TestCase.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -62,7 +61,6 @@ import android.os.IPowerManager; import android.os.IThermalService; import android.os.Looper; import android.os.PowerManager; -import android.os.RemoteException; import android.os.UserHandle; import android.service.dreams.IDreamManager; import android.support.test.metricshelper.MetricsAsserts; @@ -73,13 +71,7 @@ import android.util.DisplayMetrics; import android.util.SparseArray; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; -import android.view.ViewRootImpl; import android.view.WindowManager; -import android.window.BackEvent; -import android.window.OnBackAnimationCallback; -import android.window.OnBackInvokedCallback; -import android.window.OnBackInvokedDispatcher; -import android.window.WindowOnBackInvokedDispatcher; import androidx.test.filters.SmallTest; @@ -125,6 +117,7 @@ import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.recents.ScreenPinningRequest; +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.shade.CameraLauncher; @@ -158,7 +151,6 @@ import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore; -import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; @@ -167,10 +159,7 @@ import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl; -import com.android.systemui.statusbar.notification.logging.NotificationLogger; -import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; -import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; @@ -199,7 +188,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -227,7 +215,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private KeyguardIndicationController mKeyguardIndicationController; @Mock private NotificationStackScrollLayout mStackScroller; @Mock private NotificationStackScrollLayoutController mStackScrollerController; - @Mock private NotificationListContainer mNotificationListContainer; @Mock private HeadsUpManagerPhone mHeadsUpManager; @Mock private NotificationPanelViewController mNotificationPanelViewController; @Mock private ShadeLogger mShadeLogger; @@ -254,7 +241,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private StatusBarNotificationPresenter mNotificationPresenter; @Mock private NotificationActivityStarter mNotificationActivityStarter; @Mock private AmbientDisplayConfiguration mAmbientDisplayConfiguration; - @Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private StatusBarSignalPolicy mStatusBarSignalPolicy; @Mock private BroadcastDispatcher mBroadcastDispatcher; @@ -325,18 +311,11 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private Lazy<CameraLauncher> mCameraLauncherLazy; @Mock private CameraLauncher mCameraLauncher; @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; - /** - * The process of registering/unregistering a predictive back callback requires a - * ViewRootImpl, which is present IRL, but may be missing during a Mockito unit test. - * To prevent an NPE during test execution, we explicitly craft and provide a fake ViewRootImpl. - */ - @Mock private ViewRootImpl mViewRootImpl; - @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher; @Mock private UserTracker mUserTracker; @Mock private FingerprintManager mFingerprintManager; - @Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback; @Mock IPowerManager mPowerManagerService; @Mock ActivityStarter mActivityStarter; + @Mock private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; private ShadeController mShadeController; private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); @@ -388,18 +367,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class)); mMetricsLogger = new FakeMetricsLogger(); - NotificationLogger notificationLogger = new NotificationLogger( - mNotificationListener, - mUiBgExecutor, - mNotifLiveDataStore, - mVisibilityProvider, - mock(NotifPipeline.class), - mStatusBarStateController, - mShadeExpansionStateManager, - mExpansionStateLogger, - new NotificationPanelLoggerFake() - ); - notificationLogger.setVisibilityReporter(mock(Runnable.class)); when(mCommandQueue.asBinder()).thenReturn(new Binder()); @@ -448,6 +415,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mCommandQueue, mMainExecutor, mock(LogBuffer.class), + mock(WindowRootViewVisibilityInteractor.class), mKeyguardStateController, mStatusBarStateController, mStatusBarKeyguardViewManager, @@ -490,7 +458,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { new FalsingCollectorFake(), mBroadcastDispatcher, mNotificationGutsManager, - notificationLogger, mNotificationInterruptStateProvider, new ShadeExpansionStateManager(), mKeyguardViewMediator, @@ -541,6 +508,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { () -> mCentralSurfacesCommandQueueCallbacks, mPluginManager, mShadeController, + mWindowRootViewVisibilityInteractor, mStatusBarKeyguardViewManager, mViewMediatorCallback, mInitController, @@ -578,16 +546,9 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mUserTracker, () -> mFingerprintManager, mActivityStarter - ) { - @Override - protected ViewRootImpl getViewRootImpl() { - return mViewRootImpl; - } - }; + ); mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver); mCentralSurfaces.initShadeVisibilityListener(); - when(mViewRootImpl.getOnBackInvokedDispatcher()) - .thenReturn(mOnBackInvokedDispatcher); when(mKeyguardViewMediator.registerCentralSurfaces( any(CentralSurfacesImpl.class), any(NotificationPanelViewController.class), @@ -609,7 +570,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "sysui:GestureWakeLock"); mCentralSurfaces.startKeyguard(); mInitController.executePostInitTasks(); - notificationLogger.setUpWithContainer(mNotificationListContainer); mCentralSurfaces.registerCallbacks(); } @@ -799,151 +759,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test - public void testLogHidden() { - try { - mCentralSurfaces.handleVisibleToUserChanged(false); - mUiBgExecutor.runAllReady(); - verify(mBarService, times(1)).onPanelHidden(); - verify(mBarService, never()).onPanelRevealed(anyBoolean(), anyInt()); - } catch (RemoteException e) { - fail(); - } - } - - /** - * Do the following: - * 1. verify that a predictive back callback is registered when CSurf becomes visible - * 2. verify that the same callback is unregistered when CSurf becomes invisible - */ - @Test - public void testPredictiveBackCallback_registration() { - mCentralSurfaces.handleVisibleToUserChanged(true); - verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( - eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), - mOnBackInvokedCallback.capture()); - - mCentralSurfaces.handleVisibleToUserChanged(false); - verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback( - eq(mOnBackInvokedCallback.getValue())); - } - - /** - * Do the following: - * 1. capture the predictive back callback during registration - * 2. call the callback directly - * 3. verify that the ShadeController's panel collapse animation is invoked - */ - @Test - public void testPredictiveBackCallback_invocationCollapsesPanel() { - mCentralSurfaces.handleVisibleToUserChanged(true); - verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( - eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), - mOnBackInvokedCallback.capture()); - - when(mBackActionInteractor.shouldBackBeHandled()).thenReturn(true); - mOnBackInvokedCallback.getValue().onBackInvoked(); - verify(mBackActionInteractor).onBackRequested(); - } - - /** - * When back progress is at 100%, the onBackProgressed animation driver inside - * NotificationPanelViewController should be invoked appropriately (with 1.0f passed in). - */ - @Test - public void testPredictiveBackAnimation_progressMaxScalesPanel() { - mCentralSurfaces.handleVisibleToUserChanged(true); - verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( - eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), - mOnBackInvokedCallback.capture()); - - OnBackAnimationCallback onBackAnimationCallback = - (OnBackAnimationCallback) (mOnBackInvokedCallback.getValue()); - when(mBackActionInteractor.shouldBackBeHandled()).thenReturn(true); - when(mNotificationPanelViewController.canBeCollapsed()).thenReturn(true); - - BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 100.0f, 1.0f, BackEvent.EDGE_LEFT); - onBackAnimationCallback.onBackProgressed(fakeSwipeInFromLeftEdge); - verify(mNotificationPanelViewController).onBackProgressed(eq(1.0f)); - } - - /** - * When back progress is at 0%, the onBackProgressed animation driver inside - * NotificationPanelViewController should be invoked appropriately (with 0.0f passed in). - */ - @Test - public void testPredictiveBackAnimation_progressMinScalesPanel() { - mCentralSurfaces.handleVisibleToUserChanged(true); - verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( - eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), - mOnBackInvokedCallback.capture()); - - OnBackAnimationCallback onBackAnimationCallback = - (OnBackAnimationCallback) (mOnBackInvokedCallback.getValue()); - when(mBackActionInteractor.shouldBackBeHandled()).thenReturn(true); - when(mNotificationPanelViewController.canBeCollapsed()).thenReturn(true); - - BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 10.0f, 0.0f, BackEvent.EDGE_LEFT); - onBackAnimationCallback.onBackProgressed(fakeSwipeInFromLeftEdge); - verify(mNotificationPanelViewController).onBackProgressed(eq(0.0f)); - } - - @Test - public void testPanelOpenForHeadsUp() { - when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true); - when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true); - when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5); - when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(true); - mCentralSurfaces.setBarStateForTest(SHADE); - - try { - mCentralSurfaces.handleVisibleToUserChanged(true); - mUiBgExecutor.runAllReady(); - verify(mBarService, never()).onPanelHidden(); - verify(mBarService, times(1)).onPanelRevealed(false, 1); - } catch (RemoteException e) { - fail(); - } - mMainExecutor.runAllReady(); - } - - @Test - public void testPanelOpenAndClear() { - when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false); - when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5); - - when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(false); - mCentralSurfaces.setBarStateForTest(SHADE); - - try { - mCentralSurfaces.handleVisibleToUserChanged(true); - mUiBgExecutor.runAllReady(); - verify(mBarService, never()).onPanelHidden(); - verify(mBarService, times(1)).onPanelRevealed(true, 5); - } catch (RemoteException e) { - fail(); - } - mMainExecutor.runAllReady(); - } - - @Test - public void testPanelOpenAndNoClear() { - when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false); - when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5); - when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(false); - mCentralSurfaces.setBarStateForTest(StatusBarState.KEYGUARD); - - try { - mCentralSurfaces.handleVisibleToUserChanged(true); - mUiBgExecutor.runAllReady(); - verify(mBarService, never()).onPanelHidden(); - verify(mBarService, times(1)).onPanelRevealed(false, 5); - } catch (RemoteException e) { - fail(); - } - mMainExecutor.runAllReady(); - } - - @Test public void testDump_DoesNotCrash() { mCentralSurfaces.dump(new PrintWriter(new ByteArrayOutputStream()), null); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt index 9795b9d3c169..71c27dec74d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt @@ -29,6 +29,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor +import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State import com.android.systemui.privacy.PrivacyItemController import com.android.systemui.privacy.logging.PrivacyLogger @@ -316,5 +317,7 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { suspend fun emit(value: State) = flow.emit(value) override val connectedDisplayState: Flow<State> get() = flow + override val pendingDisplay: Flow<PendingDisplay?> + get() = TODO("Not yet implemented") } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt index 08e89fbef486..6f04f369d52f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt @@ -220,30 +220,6 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { /** Regression test for b/255428281. */ @Test - fun internalAndExternalIconWithSameName_internalRemoved_viaRemoveAll_externalStays() { - val slotName = "mute" - - // Internal - underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription") - - // External - underTest.setIconFromTile(slotName, createExternalIcon()) - - // WHEN the internal icon is removed via #removeAllIconsForSlot - underTest.removeAllIconsForSlot(slotName) - - // THEN the internal icon is removed but the external icon remains - assertThat(iconList.slots).hasSize(2) - assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX) - assertThat(iconList.slots[1].name).isEqualTo(slotName) - assertThat(iconList.slots[0].hasIconsInSlot()).isTrue() - assertThat(iconList.slots[1].hasIconsInSlot()).isFalse() // Indicates removal - - verify(iconGroup).onRemoveIcon(1) - } - - /** Regression test for b/255428281. */ - @Test fun internalAndExternalIconWithSameName_internalUpdatedIndependently() { val slotName = "mute" diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java index 085ec27a4e6b..0ff6f200d402 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java @@ -120,34 +120,6 @@ public class StatusBarIconControllerTest extends LeakCheckedTest { verify(manager, never()).onRemoveIcon(anyInt()); } - @Test - public void testRemoveAllIconsForSlot_ignoredForNewPipeline() { - IconManager manager = mock(IconManager.class); - - // GIVEN the new pipeline is on - StatusBarPipelineFlags flags = mock(StatusBarPipelineFlags.class); - when(flags.isIconControlledByFlags("test_icon")).thenReturn(true); - - StatusBarIconController iconController = new StatusBarIconControllerImpl( - mContext, - mock(CommandQueue.class), - mock(DemoModeController.class), - mock(ConfigurationController.class), - mock(TunerService.class), - mock(DumpManager.class), - mock(StatusBarIconList.class), - flags - ); - - iconController.addIconGroup(manager); - - // WHEN a request to remove a new icon is sent - iconController.removeAllIconsForSlot("test_icon"); - - // THEN it is not removed for those icons - verify(manager, never()).onRemoveIcon(anyInt()); - } - private <T extends IconManager & TestableIconManager> void testCallOnAdd_forManager(T manager) { StatusBarIconHolder holder = holderForType(TYPE_ICON); manager.onIconAdded(0, "test_slot", false, holder); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractorTest.kt new file mode 100644 index 000000000000..5784be34ce25 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractorTest.kt @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.ethernet.domain + +import androidx.test.filters.SmallTest +import com.android.settingslib.AccessibilityContentDescriptions +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test + +@SmallTest +class EthernetInteractorTest : SysuiTestCase() { + private val connectivityRepository = FakeConnectivityRepository() + private val underTest = EthernetInteractor(connectivityRepository) + + private val testScope = TestScope() + + @Test + fun icon_default_validated() = + testScope.runTest { + val latest by collectLastValue(underTest.icon) + + connectivityRepository.setEthernetConnected(default = true, validated = true) + + val expected = + Icon.Resource( + R.drawable.stat_sys_ethernet_fully, + ContentDescription.Resource( + AccessibilityContentDescriptions.ETHERNET_CONNECTION_VALUES[1] + ) + ) + + assertThat(latest).isEqualTo(expected) + } + + @Test + fun icon_default_notValidated() = + testScope.runTest { + val latest by collectLastValue(underTest.icon) + + connectivityRepository.setEthernetConnected(default = true, validated = false) + + val expected = + Icon.Resource( + R.drawable.stat_sys_ethernet, + ContentDescription.Resource( + AccessibilityContentDescriptions.ETHERNET_CONNECTION_VALUES[0] + ) + ) + + assertThat(latest).isEqualTo(expected) + } + + @Test + fun icon_notDefault_validated() = + testScope.runTest { + val latest by collectLastValue(underTest.icon) + + connectivityRepository.setEthernetConnected(default = false, validated = true) + + assertThat(latest).isNull() + } + + @Test + fun icon_notDefault_notValidated() = + testScope.runTest { + val latest by collectLastValue(underTest.icon) + + connectivityRepository.setEthernetConnected(default = false, validated = false) + + assertThat(latest).isNull() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt index ff2875355a6a..812e91beee48 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt @@ -64,6 +64,28 @@ class FakeMobileConnectionRepository( _dataEnabled.value = enabled } + /** + * Set [primaryLevel] and [cdmaLevel]. Convenient when you don't care about the connection type + */ + fun setAllLevels(level: Int) { + cdmaLevel.value = level + primaryLevel.value = level + } + + /** + * Set both [isRoaming] and [cdmaRoaming] properties, in the event that you don't care about the + * connection type + */ + fun setAllRoaming(roaming: Boolean) { + isRoaming.value = roaming + cdmaRoaming.value = roaming + } + + /** Set the correct [resolvedNetworkType] for the given group via its lookup key */ + fun setNetworkTypeKey(key: String) { + resolvedNetworkType.value = ResolvedNetworkType.DefaultNetworkType(key) + } + companion object { const val DEFAULT_NETWORK_NAME = "default name" } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt index 9c0cb17700a6..ede02d1fbc9c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt @@ -674,6 +674,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { val realRepo = MobileConnectionRepositoryImpl( SUB_ID, + context, subscriptionModel, DEFAULT_NAME_MODEL, SEP, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt index e50e5e31a786..3af960b74a5f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt @@ -16,18 +16,22 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod +import android.content.BroadcastReceiver +import android.content.Context import android.content.Intent import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL import android.telephony.NetworkRegistrationInfo import android.telephony.ServiceState import android.telephony.ServiceState.STATE_IN_SERVICE import android.telephony.ServiceState.STATE_OUT_OF_SERVICE +import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX import android.telephony.TelephonyCallback import android.telephony.TelephonyCallback.DataActivityListener import android.telephony.TelephonyCallback.ServiceStateListener import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE import android.telephony.TelephonyManager +import android.telephony.TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED import android.telephony.TelephonyManager.DATA_ACTIVITY_DORMANT import android.telephony.TelephonyManager.DATA_ACTIVITY_IN import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT @@ -75,6 +79,7 @@ import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsPro import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -88,6 +93,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.mockito.Mock +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @@ -100,6 +106,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { @Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var logger: MobileInputLogger @Mock private lateinit var tableLogger: TableLogBuffer + @Mock private lateinit var context: Context private val mobileMappings = FakeMobileMappingsProxy() private val systemUiCarrierConfig = @@ -129,6 +136,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { underTest = MobileConnectionRepositoryImpl( SUB_1_ID, + context, subscriptionModel, DEFAULT_NAME_MODEL, SEP, @@ -706,7 +714,9 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { val job = underTest.networkName.onEach { latest = it }.launchIn(this) val intent = spnIntent() - fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + captor.value!!.onReceive(context, intent) assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP)) @@ -720,13 +730,16 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { val job = underTest.networkName.onEach { latest = it }.launchIn(this) val intent = spnIntent() - fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + captor.value!!.onReceive(context, intent) + assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP)) // WHEN an intent with a different subId is sent val wrongSubIntent = spnIntent(subId = 101) - fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, wrongSubIntent) + captor.value!!.onReceive(context, wrongSubIntent) // THEN the previous intent's name is still used assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP)) @@ -741,7 +754,10 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { val job = underTest.networkName.onEach { latest = it }.launchIn(this) val intent = spnIntent() - fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + captor.value!!.onReceive(context, intent) + assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP)) val intentWithoutInfo = @@ -750,7 +766,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { showPlmn = false, ) - fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intentWithoutInfo) + captor.value!!.onReceive(context, intentWithoutInfo) assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL) @@ -893,7 +909,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { plmn: String = PLMN, ): Intent = Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED).apply { - putExtra(EXTRA_SUBSCRIPTION_ID, subId) + putExtra(EXTRA_SUBSCRIPTION_INDEX, subId) putExtra(EXTRA_SHOW_SPN, showSpn) putExtra(EXTRA_SPN, spn) putExtra(EXTRA_SHOW_PLMN, showPlmn) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt index ea60aa74f12b..852ed2054fcd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt @@ -125,6 +125,7 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() { underTest = MobileConnectionRepositoryImpl( SUB_1_ID, + context, subscriptionModel, DEFAULT_NAME, SEP, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index fd05cc495692..6f9764a907fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -175,6 +175,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { connectionFactory = MobileConnectionRepositoryImpl.Factory( + context, fakeBroadcastDispatcher, telephonyManager = telephonyManager, bgDispatcher = dispatcher, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt index a3df785c5dae..de2b6a850e03 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt @@ -16,12 +16,11 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor -import android.telephony.CellSignalStrength import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel -import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import kotlinx.coroutines.flow.MutableStateFlow @@ -30,8 +29,6 @@ class FakeMobileIconInteractor( ) : MobileIconInteractor { override val alwaysShowDataRatIcon = MutableStateFlow(false) - override val alwaysUseCdmaLevel = MutableStateFlow(false) - override val activity = MutableStateFlow( DataActivityModel( @@ -55,14 +52,8 @@ class FakeMobileIconInteractor( override val carrierName = MutableStateFlow("demo mode") - private val _isEmergencyOnly = MutableStateFlow(false) - override val isEmergencyOnly = _isEmergencyOnly - override val isRoaming = MutableStateFlow(false) - private val _isFailedConnection = MutableStateFlow(false) - override val isDefaultConnectionFailed = _isFailedConnection - override val isDataConnected = MutableStateFlow(true) override val isInService = MutableStateFlow(true) @@ -70,40 +61,21 @@ class FakeMobileIconInteractor( private val _isDataEnabled = MutableStateFlow(true) override val isDataEnabled = _isDataEnabled - private val _isDefaultDataEnabled = MutableStateFlow(true) - override val isDefaultDataEnabled = _isDefaultDataEnabled - - private val _level = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) - override val level = _level - - private val _numberOfLevels = MutableStateFlow(DEFAULT_NUM_LEVELS) - override val numberOfLevels = _numberOfLevels - override val isForceHidden = MutableStateFlow(false) override val isAllowedDuringAirplaneMode = MutableStateFlow(false) - fun setIsEmergencyOnly(emergency: Boolean) { - _isEmergencyOnly.value = emergency - } + override val signalLevelIcon: MutableStateFlow<SignalIconModel> = + MutableStateFlow( + SignalIconModel( + level = 0, + numberOfLevels = 4, + showExclamationMark = false, + carrierNetworkChange = false, + ) + ) fun setIsDataEnabled(enabled: Boolean) { _isDataEnabled.value = enabled } - - fun setIsDefaultDataEnabled(disabled: Boolean) { - _isDefaultDataEnabled.value = disabled - } - - fun setIsFailedConnection(failed: Boolean) { - _isFailedConnection.value = failed - } - - fun setLevel(level: Int) { - _level.value = level - } - - fun setNumberOfLevels(num: Int) { - _numberOfLevels.value = num - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt index 82b7ec41d148..75d1869adc7c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt @@ -58,6 +58,8 @@ class FakeMobileIconsInteractor( private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false) override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled + override val activeDataIconInteractor = MutableStateFlow(null) + override val alwaysShowDataRatIcon = MutableStateFlow(false) override val alwaysUseCdmaLevel = MutableStateFlow(false) @@ -78,7 +80,7 @@ class FakeMobileIconsInteractor( override val isForceHidden = MutableStateFlow(false) /** Always returns a new fake interactor */ - override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor { + override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor { return FakeMobileIconInteractor(tableLogBuffer).also { interactorCache[subId] = it } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt index e3c59adef529..e2f91194cf40 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt @@ -35,6 +35,7 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobi import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FOUR_G import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.THREE_G import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock @@ -75,20 +76,10 @@ class MobileIconInteractorTest : SysuiTestCase() { @Before fun setUp() { underTest = createInteractor() - } - - @Test - fun gsm_level_default_unknown() = - testScope.runTest { - connectionRepository.isGsm.value = true - - var latest: Int? = null - val job = underTest.level.onEach { latest = it }.launchIn(this) - - assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) - job.cancel() - } + mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true + connectionRepository.isInService.value = true + } @Test fun gsm_usesGsmLevel() = @@ -98,7 +89,7 @@ class MobileIconInteractorTest : SysuiTestCase() { connectionRepository.cdmaLevel.value = CDMA_LEVEL var latest: Int? = null - val job = underTest.level.onEach { latest = it }.launchIn(this) + val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this) assertThat(latest).isEqualTo(GSM_LEVEL) @@ -114,7 +105,7 @@ class MobileIconInteractorTest : SysuiTestCase() { mobileIconsInteractor.alwaysUseCdmaLevel.value = true var latest: Int? = null - val job = underTest.level.onEach { latest = it }.launchIn(this) + val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this) assertThat(latest).isEqualTo(GSM_LEVEL) @@ -127,7 +118,7 @@ class MobileIconInteractorTest : SysuiTestCase() { connectionRepository.isGsm.value = false var latest: Int? = null - val job = underTest.level.onEach { latest = it }.launchIn(this) + val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this) assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) job.cancel() @@ -142,7 +133,7 @@ class MobileIconInteractorTest : SysuiTestCase() { mobileIconsInteractor.alwaysUseCdmaLevel.value = true var latest: Int? = null - val job = underTest.level.onEach { latest = it }.launchIn(this) + val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this) assertThat(latest).isEqualTo(CDMA_LEVEL) @@ -158,7 +149,7 @@ class MobileIconInteractorTest : SysuiTestCase() { mobileIconsInteractor.alwaysUseCdmaLevel.value = false var latest: Int? = null - val job = underTest.level.onEach { latest = it }.launchIn(this) + val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this) assertThat(latest).isEqualTo(GSM_LEVEL) @@ -169,7 +160,7 @@ class MobileIconInteractorTest : SysuiTestCase() { fun numberOfLevels_comesFromRepo() = testScope.runTest { var latest: Int? = null - val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this) + val job = underTest.signalLevelIcon.onEach { latest = it.numberOfLevels }.launchIn(this) connectionRepository.numberOfLevels.value = 5 assertThat(latest).isEqualTo(5) @@ -295,50 +286,6 @@ class MobileIconInteractorTest : SysuiTestCase() { } @Test - fun alwaysUseCdmaLevel_matchesParent() = - testScope.runTest { - var latest: Boolean? = null - val job = underTest.alwaysUseCdmaLevel.onEach { latest = it }.launchIn(this) - - mobileIconsInteractor.alwaysUseCdmaLevel.value = true - assertThat(latest).isTrue() - - mobileIconsInteractor.alwaysUseCdmaLevel.value = false - assertThat(latest).isFalse() - - job.cancel() - } - - @Test - fun test_isDefaultDataEnabled_matchesParent() = - testScope.runTest { - var latest: Boolean? = null - val job = underTest.isDefaultDataEnabled.onEach { latest = it }.launchIn(this) - - mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true - assertThat(latest).isTrue() - - mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = false - assertThat(latest).isFalse() - - job.cancel() - } - - @Test - fun test_isDefaultConnectionFailed_matchedParent() = - testScope.runTest { - val job = underTest.isDefaultConnectionFailed.launchIn(this) - - mobileIconsInteractor.isDefaultConnectionFailed.value = false - assertThat(underTest.isDefaultConnectionFailed.value).isFalse() - - mobileIconsInteractor.isDefaultConnectionFailed.value = true - assertThat(underTest.isDefaultConnectionFailed.value).isTrue() - - job.cancel() - } - - @Test fun dataState_connected() = testScope.runTest { var latest: Boolean? = null @@ -541,6 +488,142 @@ class MobileIconInteractorTest : SysuiTestCase() { assertThat(latest).isFalse() } + @Test + fun iconId_correctLevel_notCutout() = + testScope.runTest { + connectionRepository.isInService.value = true + connectionRepository.primaryLevel.value = 1 + connectionRepository.setDataEnabled(false) + + var latest: SignalIconModel? = null + val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this) + + assertThat(latest?.level).isEqualTo(1) + assertThat(latest?.showExclamationMark).isFalse() + + job.cancel() + } + + @Test + fun icon_usesLevelFromInteractor() = + testScope.runTest { + connectionRepository.isInService.value = true + + var latest: SignalIconModel? = null + val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this) + + connectionRepository.primaryLevel.value = 3 + assertThat(latest!!.level).isEqualTo(3) + + connectionRepository.primaryLevel.value = 1 + assertThat(latest!!.level).isEqualTo(1) + + job.cancel() + } + + @Test + fun icon_usesNumberOfLevelsFromInteractor() = + testScope.runTest { + var latest: SignalIconModel? = null + val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this) + + connectionRepository.numberOfLevels.value = 5 + assertThat(latest!!.numberOfLevels).isEqualTo(5) + + connectionRepository.numberOfLevels.value = 2 + assertThat(latest!!.numberOfLevels).isEqualTo(2) + + job.cancel() + } + + @Test + fun icon_defaultDataDisabled_showExclamationTrue() = + testScope.runTest { + mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = false + + var latest: SignalIconModel? = null + val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this) + + assertThat(latest!!.showExclamationMark).isTrue() + + job.cancel() + } + + @Test + fun icon_defaultConnectionFailed_showExclamationTrue() = + testScope.runTest { + mobileIconsInteractor.isDefaultConnectionFailed.value = true + + var latest: SignalIconModel? = null + val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this) + + assertThat(latest!!.showExclamationMark).isTrue() + + job.cancel() + } + + @Test + fun icon_enabledAndNotFailed_showExclamationFalse() = + testScope.runTest { + connectionRepository.isInService.value = true + mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true + mobileIconsInteractor.isDefaultConnectionFailed.value = false + + var latest: SignalIconModel? = null + val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this) + + assertThat(latest!!.showExclamationMark).isFalse() + + job.cancel() + } + + @Test + fun icon_usesEmptyState_whenNotInService() = + testScope.runTest { + var latest: SignalIconModel? = null + val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this) + + connectionRepository.isInService.value = false + + assertThat(latest?.level).isEqualTo(0) + assertThat(latest?.showExclamationMark).isTrue() + + // Changing the level doesn't overwrite the disabled state + connectionRepository.primaryLevel.value = 2 + assertThat(latest?.level).isEqualTo(0) + assertThat(latest?.showExclamationMark).isTrue() + + // Once back in service, the regular icon appears + connectionRepository.isInService.value = true + assertThat(latest?.level).isEqualTo(2) + assertThat(latest?.showExclamationMark).isFalse() + + job.cancel() + } + + @Test + fun icon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() = + testScope.runTest { + var latest: SignalIconModel? = null + val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this) + + connectionRepository.isInService.value = true + connectionRepository.carrierNetworkChangeActive.value = true + connectionRepository.primaryLevel.value = 1 + connectionRepository.cdmaLevel.value = 1 + + assertThat(latest!!.level).isEqualTo(1) + assertThat(latest!!.carrierNetworkChange).isTrue() + + // SignalIconModel respects the current level + connectionRepository.primaryLevel.value = 2 + + assertThat(latest!!.level).isEqualTo(2) + assertThat(latest!!.carrierNetworkChange).isTrue() + + job.cancel() + } + private fun createInteractor( overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl() ) = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt index 3e6f90931b87..b4c7578241e8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt @@ -781,6 +781,16 @@ class MobileIconsInteractorTest : SysuiTestCase() { job.cancel() } + @Test + fun iconInteractor_cachedPerSubId() = + testScope.runTest { + val interactor1 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID) + val interactor2 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID) + + assertThat(interactor1).isNotNull() + assertThat(interactor1).isSameInstanceAs(interactor2) + } + /** * Convenience method for creating a pair of subscriptions to test the filteredSubscriptions * flow. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt index 01c388a523e6..90a894648c9f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.model import androidx.test.filters.SmallTest import com.android.settingslib.graph.SignalDrawable import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt index e59d90f6bb66..187832908e2b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt @@ -17,18 +17,26 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel import androidx.test.filters.SmallTest -import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.SysuiTestCase import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor -import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor -import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel -import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel -import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModelTest.Companion.defaultSignal +import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState +import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorImpl +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel +import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.android.systemui.util.CarrierConfigTracker import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -50,11 +58,18 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() { private lateinit var homeIcon: HomeMobileIconViewModel private lateinit var qsIcon: QsMobileIconViewModel private lateinit var keyguardIcon: KeyguardMobileIconViewModel - private lateinit var interactor: FakeMobileIconInteractor + private lateinit var iconsInteractor: MobileIconsInteractor + private lateinit var interactor: MobileIconInteractor + private lateinit var connectionsRepository: FakeMobileConnectionsRepository + private lateinit var repository: FakeMobileConnectionRepository private lateinit var airplaneModeInteractor: AirplaneModeInteractor + + private val connectivityRepository = FakeConnectivityRepository() + @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags @Mock private lateinit var constants: ConnectivityConstants @Mock private lateinit var tableLogBuffer: TableLogBuffer + @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker private val testDispatcher = UnconfinedTestDispatcher() private val testScope = TestScope(testDispatcher) @@ -67,16 +82,51 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() { FakeAirplaneModeRepository(), FakeConnectivityRepository(), ) - interactor = FakeMobileIconInteractor(tableLogBuffer) - interactor.apply { - setLevel(1) - setIsDefaultDataEnabled(true) - setIsFailedConnection(false) - setIsEmergencyOnly(false) - setNumberOfLevels(4) - networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G) - isDataConnected.value = true - } + connectionsRepository = + FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogBuffer) + repository = + FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer).apply { + isInService.value = true + cdmaLevel.value = 1 + primaryLevel.value = 1 + isEmergencyOnly.value = false + numberOfLevels.value = 4 + resolvedNetworkType.value = ResolvedNetworkType.DefaultNetworkType(lookupKey = "3G") + dataConnectionState.value = DataConnectionState.Connected + } + + connectionsRepository.activeMobileDataRepository.value = repository + + connectivityRepository.apply { setMobileConnected() } + + iconsInteractor = + MobileIconsInteractorImpl( + connectionsRepository, + carrierConfigTracker, + tableLogBuffer, + connectivityRepository, + FakeUserSetupRepository(), + testScope.backgroundScope, + context, + ) + + interactor = + MobileIconInteractorImpl( + testScope.backgroundScope, + iconsInteractor.activeDataConnectionHasDataEnabled, + iconsInteractor.alwaysShowDataRatIcon, + iconsInteractor.alwaysUseCdmaLevel, + iconsInteractor.isSingleCarrier, + iconsInteractor.mobileIsDefault, + iconsInteractor.defaultMobileIconMapping, + iconsInteractor.defaultMobileIconGroup, + iconsInteractor.isDefaultConnectionFailed, + iconsInteractor.isForceHidden, + repository, + context, + MobileIconCarrierIdOverridesFake() + ) + commonImpl = MobileIconViewModel( SUB_1_ID, @@ -109,7 +159,7 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() { assertThat(latestQs).isEqualTo(expected) assertThat(latestKeyguard).isEqualTo(expected) - interactor.setLevel(2) + repository.setAllLevels(2) expected = defaultSignal(level = 2) assertThat(latestHome).isEqualTo(expected) @@ -123,5 +173,16 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() { companion object { private const val SUB_1_ID = 1 + private const val NUM_LEVELS = 4 + + /** Convenience constructor for these tests */ + fun defaultSignal(level: Int = 1): SignalIconModel { + return SignalIconModel( + level, + NUM_LEVELS, + showExclamationMark = false, + carrierNetworkChange = false, + ) + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt index 72feec78a979..796d5ec3dd42 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt @@ -19,20 +19,30 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel import androidx.test.filters.SmallTest import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE +import com.android.settingslib.mobile.MobileMappings +import com.android.settingslib.mobile.TelephonyIcons.G import com.android.settingslib.mobile.TelephonyIcons.THREE_G import com.android.settingslib.mobile.TelephonyIcons.UNKNOWN import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor -import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor -import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel -import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorImpl +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel +import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants +import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.android.systemui.util.CarrierConfigTracker import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -51,12 +61,18 @@ import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest class MobileIconViewModelTest : SysuiTestCase() { + private var connectivityRepository = FakeConnectivityRepository() + private lateinit var underTest: MobileIconViewModel - private lateinit var interactor: FakeMobileIconInteractor + private lateinit var interactor: MobileIconInteractorImpl + private lateinit var iconsInteractor: MobileIconsInteractorImpl + private lateinit var repository: FakeMobileConnectionRepository + private lateinit var connectionsRepository: FakeMobileConnectionsRepository private lateinit var airplaneModeRepository: FakeAirplaneModeRepository private lateinit var airplaneModeInteractor: AirplaneModeInteractor @Mock private lateinit var constants: ConnectivityConstants @Mock private lateinit var tableLogBuffer: TableLogBuffer + @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker private val testDispatcher = UnconfinedTestDispatcher() private val testScope = TestScope(testDispatcher) @@ -66,23 +82,53 @@ class MobileIconViewModelTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) whenever(constants.hasDataCapabilities).thenReturn(true) + connectionsRepository = + FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogBuffer) + + repository = + FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer).apply { + setNetworkTypeKey(connectionsRepository.GSM_KEY) + isInService.value = true + dataConnectionState.value = DataConnectionState.Connected + dataEnabled.value = true + } + connectionsRepository.activeMobileDataRepository.value = repository + connectionsRepository.mobileIsDefault.value = true + airplaneModeRepository = FakeAirplaneModeRepository() airplaneModeInteractor = AirplaneModeInteractor( airplaneModeRepository, - FakeConnectivityRepository(), + connectivityRepository, ) - interactor = FakeMobileIconInteractor(tableLogBuffer) - interactor.apply { - setLevel(1) - setIsDefaultDataEnabled(true) - setIsFailedConnection(false) - setIsEmergencyOnly(false) - setNumberOfLevels(4) - interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) - isDataConnected.value = true - } + iconsInteractor = + MobileIconsInteractorImpl( + connectionsRepository, + carrierConfigTracker, + tableLogBuffer, + connectivityRepository, + FakeUserSetupRepository(), + testScope.backgroundScope, + context, + ) + + interactor = + MobileIconInteractorImpl( + testScope.backgroundScope, + iconsInteractor.activeDataConnectionHasDataEnabled, + iconsInteractor.alwaysShowDataRatIcon, + iconsInteractor.alwaysUseCdmaLevel, + iconsInteractor.isSingleCarrier, + iconsInteractor.mobileIsDefault, + iconsInteractor.defaultMobileIconMapping, + iconsInteractor.defaultMobileIconGroup, + iconsInteractor.isDefaultConnectionFailed, + iconsInteractor.isForceHidden, + repository, + context, + MobileIconCarrierIdOverridesFake() + ) createAndSetViewModel() } @@ -108,7 +154,6 @@ class MobileIconViewModelTest : SysuiTestCase() { val job = underTest.isVisible.onEach { latest = it }.launchIn(this) airplaneModeRepository.setIsAirplaneMode(false) - interactor.isForceHidden.value = false assertThat(latest).isTrue() @@ -122,8 +167,8 @@ class MobileIconViewModelTest : SysuiTestCase() { val job = underTest.isVisible.onEach { latest = it }.launchIn(this) airplaneModeRepository.setIsAirplaneMode(true) - interactor.isAllowedDuringAirplaneMode.value = false - interactor.isForceHidden.value = false + repository.isAllowedDuringAirplaneMode.value = false + connectivityRepository.setForceHiddenIcons(setOf()) assertThat(latest).isFalse() @@ -138,8 +183,8 @@ class MobileIconViewModelTest : SysuiTestCase() { val job = underTest.isVisible.onEach { latest = it }.launchIn(this) airplaneModeRepository.setIsAirplaneMode(true) - interactor.isAllowedDuringAirplaneMode.value = true - interactor.isForceHidden.value = false + repository.isAllowedDuringAirplaneMode.value = true + connectivityRepository.setForceHiddenIcons(setOf()) assertThat(latest).isTrue() @@ -153,7 +198,7 @@ class MobileIconViewModelTest : SysuiTestCase() { val job = underTest.isVisible.onEach { latest = it }.launchIn(this) airplaneModeRepository.setIsAirplaneMode(false) - interactor.isForceHidden.value = true + connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE)) assertThat(latest).isFalse() @@ -167,156 +212,29 @@ class MobileIconViewModelTest : SysuiTestCase() { val job = underTest.isVisible.onEach { latest = it }.launchIn(this) airplaneModeRepository.setIsAirplaneMode(false) - interactor.isForceHidden.value = false + connectivityRepository.setForceHiddenIcons(setOf()) assertThat(latest).isTrue() airplaneModeRepository.setIsAirplaneMode(true) assertThat(latest).isFalse() - interactor.isAllowedDuringAirplaneMode.value = true + repository.isAllowedDuringAirplaneMode.value = true assertThat(latest).isTrue() - interactor.isForceHidden.value = true + connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE)) assertThat(latest).isFalse() job.cancel() } @Test - fun iconId_correctLevel_notCutout() = - testScope.runTest { - var latest: SignalIconModel? = null - val job = underTest.icon.onEach { latest = it }.launchIn(this) - val expected = defaultSignal() - - assertThat(latest).isEqualTo(expected) - - job.cancel() - } - - @Test - fun icon_usesLevelFromInteractor() = - testScope.runTest { - var latest: SignalIconModel? = null - val job = underTest.icon.onEach { latest = it }.launchIn(this) - - interactor.level.value = 3 - assertThat(latest!!.level).isEqualTo(3) - - interactor.level.value = 1 - assertThat(latest!!.level).isEqualTo(1) - - job.cancel() - } - - @Test - fun icon_usesNumberOfLevelsFromInteractor() = - testScope.runTest { - var latest: SignalIconModel? = null - val job = underTest.icon.onEach { latest = it }.launchIn(this) - - interactor.numberOfLevels.value = 5 - assertThat(latest!!.numberOfLevels).isEqualTo(5) - - interactor.numberOfLevels.value = 2 - assertThat(latest!!.numberOfLevels).isEqualTo(2) - - job.cancel() - } - - @Test - fun icon_defaultDataDisabled_showExclamationTrue() = - testScope.runTest { - interactor.setIsDefaultDataEnabled(false) - - var latest: SignalIconModel? = null - val job = underTest.icon.onEach { latest = it }.launchIn(this) - - assertThat(latest!!.showExclamationMark).isTrue() - - job.cancel() - } - - @Test - fun icon_defaultConnectionFailed_showExclamationTrue() = - testScope.runTest { - interactor.isDefaultConnectionFailed.value = true - - var latest: SignalIconModel? = null - val job = underTest.icon.onEach { latest = it }.launchIn(this) - - assertThat(latest!!.showExclamationMark).isTrue() - - job.cancel() - } - - @Test - fun icon_enabledAndNotFailed_showExclamationFalse() = - testScope.runTest { - interactor.setIsDefaultDataEnabled(true) - interactor.isDefaultConnectionFailed.value = false - - var latest: SignalIconModel? = null - val job = underTest.icon.onEach { latest = it }.launchIn(this) - - assertThat(latest!!.showExclamationMark).isFalse() - - job.cancel() - } - - @Test - fun icon_usesEmptyState_whenNotInService() = - testScope.runTest { - var latest: SignalIconModel? = null - val job = underTest.icon.onEach { latest = it }.launchIn(this) - - interactor.isInService.value = false - - var expected = emptySignal() - - assertThat(latest).isEqualTo(expected) - - // Changing the level doesn't overwrite the disabled state - interactor.level.value = 2 - assertThat(latest).isEqualTo(expected) - - // Once back in service, the regular icon appears - interactor.isInService.value = true - expected = defaultSignal(level = 2) - assertThat(latest).isEqualTo(expected) - - job.cancel() - } - - @Test - fun icon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() = - testScope.runTest { - var latest: SignalIconModel? = null - val job = underTest.icon.onEach { latest = it }.launchIn(this) - - interactor.carrierNetworkChangeActive.value = true - interactor.level.value = 1 - - assertThat(latest!!.level).isEqualTo(1) - assertThat(latest!!.carrierNetworkChange).isTrue() - - // SignalIconModel respects the current level - interactor.level.value = 2 - - assertThat(latest!!.level).isEqualTo(2) - assertThat(latest!!.carrierNetworkChange).isTrue() - - job.cancel() - } - - @Test fun contentDescription_notInService_usesNoPhone() = testScope.runTest { var latest: ContentDescription? = null val job = underTest.contentDescription.onEach { latest = it }.launchIn(this) - interactor.isInService.value = false + repository.isInService.value = false assertThat((latest as ContentDescription.Resource).res) .isEqualTo(PHONE_SIGNAL_STRENGTH_NONE) @@ -330,13 +248,11 @@ class MobileIconViewModelTest : SysuiTestCase() { var latest: ContentDescription? = null val job = underTest.contentDescription.onEach { latest = it }.launchIn(this) - interactor.isInService.value = true - - interactor.level.value = 2 + repository.setAllLevels(2) assertThat((latest as ContentDescription.Resource).res) .isEqualTo(PHONE_SIGNAL_STRENGTH[2]) - interactor.level.value = 0 + repository.setAllLevels(0) assertThat((latest as ContentDescription.Resource).res) .isEqualTo(PHONE_SIGNAL_STRENGTH[0]) @@ -351,7 +267,8 @@ class MobileIconViewModelTest : SysuiTestCase() { THREE_G.dataType, ContentDescription.Resource(THREE_G.dataContentDescription) ) - interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) + connectionsRepository.mobileIsDefault.value = true + repository.setNetworkTypeKey(connectionsRepository.GSM_KEY) var latest: Icon? = null val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) @@ -364,9 +281,9 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun networkType_null_whenDisabled() = testScope.runTest { - interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) - interactor.setIsDataEnabled(false) - interactor.mobileIsDefault.value = true + repository.setNetworkTypeKey(connectionsRepository.GSM_KEY) + repository.setDataEnabled(false) + connectionsRepository.mobileIsDefault.value = true var latest: Icon? = null val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) @@ -378,9 +295,9 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun networkType_null_whenCarrierNetworkChangeActive() = testScope.runTest { - interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) - interactor.carrierNetworkChangeActive.value = true - interactor.mobileIsDefault.value = true + repository.setNetworkTypeKey(connectionsRepository.GSM_KEY) + repository.carrierNetworkChangeActive.value = true + connectionsRepository.mobileIsDefault.value = true var latest: Icon? = null val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) @@ -397,10 +314,10 @@ class MobileIconViewModelTest : SysuiTestCase() { THREE_G.dataType, ContentDescription.Resource(THREE_G.dataContentDescription) ) - interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) - interactor.setIsDataEnabled(true) - interactor.isDataConnected.value = true - interactor.mobileIsDefault.value = true + repository.setNetworkTypeKey(connectionsRepository.GSM_KEY) + repository.setDataEnabled(true) + repository.dataConnectionState.value = DataConnectionState.Connected + connectionsRepository.mobileIsDefault.value = true var latest: Icon? = null val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) @@ -418,15 +335,13 @@ class MobileIconViewModelTest : SysuiTestCase() { ContentDescription.Resource(THREE_G.dataContentDescription) ) - interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) + repository.setNetworkTypeKey(connectionsRepository.GSM_KEY) var latest: Icon? = null val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) - interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) assertThat(latest).isEqualTo(initial) - interactor.isDataConnected.value = false - yield() + repository.dataConnectionState.value = DataConnectionState.Disconnected assertThat(latest).isNull() @@ -441,15 +356,13 @@ class MobileIconViewModelTest : SysuiTestCase() { THREE_G.dataType, ContentDescription.Resource(THREE_G.dataContentDescription) ) - interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) - interactor.setIsDataEnabled(true) + repository.dataEnabled.value = true var latest: Icon? = null val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) assertThat(latest).isEqualTo(expected) - interactor.setIsDataEnabled(false) - yield() + repository.dataEnabled.value = false assertThat(latest).isNull() @@ -459,9 +372,10 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun networkType_alwaysShow_shownEvenWhenDisabled() = testScope.runTest { - interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) - interactor.setIsDataEnabled(false) - interactor.alwaysShowDataRatIcon.value = true + repository.dataEnabled.value = false + + connectionsRepository.defaultDataSubRatConfig.value = + MobileMappings.Config().also { it.alwaysShowDataRatIcon = true } var latest: Icon? = null val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) @@ -479,9 +393,11 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun networkType_alwaysShow_shownEvenWhenDisconnected() = testScope.runTest { - interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) - interactor.isDataConnected.value = false - interactor.alwaysShowDataRatIcon.value = true + repository.setNetworkTypeKey(connectionsRepository.GSM_KEY) + repository.dataConnectionState.value = DataConnectionState.Disconnected + + connectionsRepository.defaultDataSubRatConfig.value = + MobileMappings.Config().also { it.alwaysShowDataRatIcon = true } var latest: Icon? = null val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) @@ -499,9 +415,10 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun networkType_alwaysShow_shownEvenWhenFailedConnection() = testScope.runTest { - interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) - interactor.setIsFailedConnection(true) - interactor.alwaysShowDataRatIcon.value = true + repository.setNetworkTypeKey(connectionsRepository.GSM_KEY) + connectionsRepository.mobileIsDefault.value = true + connectionsRepository.defaultDataSubRatConfig.value = + MobileMappings.Config().also { it.alwaysShowDataRatIcon = true } var latest: Icon? = null val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) @@ -517,16 +434,24 @@ class MobileIconViewModelTest : SysuiTestCase() { } @Test - fun networkType_alwaysShow_notShownWhenInvalidDataTypeIcon() = + fun networkType_alwaysShow_usesDefaultIconWhenInvalid() = testScope.runTest { - // The UNKNOWN icon group doesn't have a valid data type icon ID - interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(UNKNOWN) - interactor.alwaysShowDataRatIcon.value = true + // The UNKNOWN icon group doesn't have a valid data type icon ID, and the logic from the + // old pipeline was to use the default icon group if the map doesn't exist + repository.setNetworkTypeKey(UNKNOWN.name) + connectionsRepository.defaultDataSubRatConfig.value = + MobileMappings.Config().also { it.alwaysShowDataRatIcon = true } var latest: Icon? = null val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) - assertThat(latest).isNull() + val expected = + Icon.Resource( + connectionsRepository.defaultMobileIconGroup.value.dataType, + ContentDescription.Resource(G.dataContentDescription) + ) + + assertThat(latest).isEqualTo(expected) job.cancel() } @@ -534,9 +459,10 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun networkType_alwaysShow_shownWhenNotDefault() = testScope.runTest { - interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) - interactor.mobileIsDefault.value = false - interactor.alwaysShowDataRatIcon.value = true + repository.setNetworkTypeKey(connectionsRepository.GSM_KEY) + connectionsRepository.mobileIsDefault.value = false + connectionsRepository.defaultDataSubRatConfig.value = + MobileMappings.Config().also { it.alwaysShowDataRatIcon = true } var latest: Icon? = null val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) @@ -554,9 +480,9 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun networkType_notShownWhenNotDefault() = testScope.runTest { - interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) - interactor.isDataConnected.value = true - interactor.mobileIsDefault.value = false + repository.setNetworkTypeKey(connectionsRepository.GSM_KEY) + repository.dataConnectionState.value = DataConnectionState.Connected + connectionsRepository.mobileIsDefault.value = false var latest: Icon? = null val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) @@ -569,13 +495,14 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun roaming() = testScope.runTest { - interactor.isRoaming.value = true + repository.setAllRoaming(true) + var latest: Boolean? = null val job = underTest.roaming.onEach { latest = it }.launchIn(this) assertThat(latest).isTrue() - interactor.isRoaming.value = false + repository.setAllRoaming(false) assertThat(latest).isFalse() @@ -599,7 +526,7 @@ class MobileIconViewModelTest : SysuiTestCase() { val containerJob = underTest.activityInVisible.onEach { containerVisible = it }.launchIn(this) - interactor.activity.value = + repository.dataActivityDirection.value = DataActivityModel( hasActivityIn = true, hasActivityOut = true, @@ -631,7 +558,7 @@ class MobileIconViewModelTest : SysuiTestCase() { val containerJob = underTest.activityContainerVisible.onEach { containerVisible = it }.launchIn(this) - interactor.activity.value = + repository.dataActivityDirection.value = DataActivityModel( hasActivityIn = true, hasActivityOut = false, @@ -643,7 +570,7 @@ class MobileIconViewModelTest : SysuiTestCase() { assertThat(outVisible).isFalse() assertThat(containerVisible).isTrue() - interactor.activity.value = + repository.dataActivityDirection.value = DataActivityModel( hasActivityIn = false, hasActivityOut = true, @@ -653,7 +580,7 @@ class MobileIconViewModelTest : SysuiTestCase() { assertThat(outVisible).isTrue() assertThat(containerVisible).isTrue() - interactor.activity.value = + repository.dataActivityDirection.value = DataActivityModel( hasActivityIn = false, hasActivityOut = false, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt index 065dfbabf051..e42515e5871d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt @@ -154,33 +154,6 @@ class MobileIconsViewModelTest : SysuiTestCase() { } @Test - fun caching_mobileIconInteractorIsReusedForSameSubId() = - testScope.runTest { - val interactor1 = underTest.mobileIconInteractorForSub(1) - val interactor2 = underTest.mobileIconInteractorForSub(1) - - assertThat(interactor1).isSameInstanceAs(interactor2) - } - - @Test - fun caching_invalidInteractorssAreRemovedFromCacheWhenSubDisappears() = - testScope.runTest { - // Retrieve interactors to trigger caching - val interactor1 = underTest.mobileIconInteractorForSub(1) - val interactor2 = underTest.mobileIconInteractorForSub(2) - - // Both impls are cached - assertThat(underTest.mobileIconInteractorSubIdCache) - .containsExactly(1, interactor1, 2, interactor2) - - // SUB_1 is removed from the list... - interactor.filteredSubscriptions.value = listOf(SUB_2) - - // ... and dropped from the cache - assertThat(underTest.mobileIconInteractorSubIdCache).containsExactly(2, interactor2) - } - - @Test fun firstMobileSubShowingNetworkTypeIcon_noSubs_false() = testScope.runTest { var latest: Boolean? = null diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt index 8f28cc003d67..e44ff8e21270 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt @@ -27,7 +27,7 @@ class FakeConnectivityRepository : ConnectivityRepository { MutableStateFlow(emptySet()) override val forceHiddenSlots: StateFlow<Set<ConnectivitySlot>> = _forceHiddenIcons - override val defaultConnections: StateFlow<DefaultConnectionModel> = + override val defaultConnections: MutableStateFlow<DefaultConnectionModel> = MutableStateFlow(DefaultConnectionModel()) override val vcnSubId: MutableStateFlow<Int?> = MutableStateFlow(null) @@ -35,4 +35,43 @@ class FakeConnectivityRepository : ConnectivityRepository { fun setForceHiddenIcons(hiddenIcons: Set<ConnectivitySlot>) { _forceHiddenIcons.value = hiddenIcons } + + /** + * Convenience for setting mobile data connected, disconnected, or validated. Defaults to + * setting mobile connected && validated, since the default state is disconnected && not + * validated + */ + fun setMobileConnected( + default: Boolean = true, + validated: Boolean = true, + ) { + defaultConnections.value = + DefaultConnectionModel( + mobile = DefaultConnectionModel.Mobile(default), + isValidated = validated, + ) + } + + /** Similar convenience method for ethernet */ + fun setEthernetConnected( + default: Boolean = true, + validated: Boolean = true, + ) { + defaultConnections.value = + DefaultConnectionModel( + ethernet = DefaultConnectionModel.Ethernet(default), + isValidated = validated, + ) + } + + fun setWifiConnected( + default: Boolean = true, + validated: Boolean = true, + ) { + defaultConnections.value = + DefaultConnectionModel( + wifi = DefaultConnectionModel.Wifi(default), + isValidated = validated, + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt new file mode 100644 index 000000000000..8150a313abe6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel + +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.Text +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon +import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository +import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor +import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState +import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl +import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy +import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel +import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.android.systemui.statusbar.pipeline.shared.ui.model.SignalIcon +import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.InternetTileViewModel.Companion.NOT_CONNECTED_NETWORKS_UNAVAILABLE +import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository +import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry +import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon +import com.android.systemui.util.CarrierConfigTracker +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test + +@SmallTest +class InternetTileViewModelTest : SysuiTestCase() { + private lateinit var underTest: InternetTileViewModel + private lateinit var mobileIconsInteractor: MobileIconsInteractor + + private val airplaneModeRepository = FakeAirplaneModeRepository() + private val connectivityRepository = FakeConnectivityRepository() + private val ethernetInteractor = EthernetInteractor(connectivityRepository) + private val wifiRepository = FakeWifiRepository() + private val userSetupRepo = FakeUserSetupRepository() + private val testScope = TestScope() + private val wifiInteractor = + WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope) + + private val tableLogBuffer: TableLogBuffer = mock() + private val carrierConfigTracker: CarrierConfigTracker = mock() + + private val mobileConnectionsRepository = + FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogBuffer) + private val mobileConnectionRepository = + FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer) + + @Before + fun setUp() { + mobileConnectionRepository.apply { + setNetworkTypeKey(mobileConnectionsRepository.GSM_KEY) + isInService.value = true + dataConnectionState.value = DataConnectionState.Connected + dataEnabled.value = true + } + + mobileConnectionsRepository.apply { + activeMobileDataRepository.value = mobileConnectionRepository + activeMobileDataSubscriptionId.value = SUB_1_ID + setMobileConnectionRepositoryMap(mapOf(SUB_1_ID to mobileConnectionRepository)) + } + + mobileIconsInteractor = + MobileIconsInteractorImpl( + mobileConnectionsRepository, + carrierConfigTracker, + tableLogBuffer, + connectivityRepository, + userSetupRepo, + testScope.backgroundScope, + context, + ) + + underTest = + InternetTileViewModel( + airplaneModeRepository, + connectivityRepository, + ethernetInteractor, + mobileIconsInteractor, + wifiInteractor, + context, + testScope.backgroundScope, + ) + } + + @Test + fun noDefault_noNetworksAvailable() = + testScope.runTest { + val latest by collectLastValue(underTest.tileModel) + + connectivityRepository.defaultConnections.value = DefaultConnectionModel() + + assertThat(latest?.secondaryLabel) + .isEqualTo(Text.Resource(R.string.quick_settings_networks_unavailable)) + assertThat(latest?.iconId).isEqualTo(R.drawable.ic_qs_no_internet_unavailable) + } + + @Test + fun noDefault_networksAvailable() = + testScope.runTest { + // TODO: support [WifiInteractor.areNetworksAvailable] + } + + @Test + fun wifiDefaultAndActive() = + testScope.runTest { + val latest by collectLastValue(underTest.tileModel) + + val networkModel = + WifiNetworkModel.Active( + networkId = 1, + level = 4, + ssid = "test ssid", + ) + + connectivityRepository.setWifiConnected() + wifiRepository.setIsWifiDefault(true) + wifiRepository.setWifiNetwork(networkModel) + + // Type is [Visible] since that is the only model that stores a resId + val expectedIcon: WifiIcon.Visible = + WifiIcon.fromModel(networkModel, context) as WifiIcon.Visible + + assertThat(latest?.secondaryTitle).isEqualTo("test ssid") + assertThat(latest?.secondaryLabel).isNull() + assertThat(latest?.icon).isEqualTo(ResourceIcon.get(expectedIcon.icon.res)) + assertThat(latest?.iconId).isNull() + } + + @Test + fun wifiDefaultAndNotActive_noNetworksAvailable() = + testScope.runTest { + val latest by collectLastValue(underTest.tileModel) + + val networkModel = WifiNetworkModel.Inactive + + connectivityRepository.setWifiConnected(validated = false) + wifiRepository.setIsWifiDefault(true) + wifiRepository.setWifiNetwork(networkModel) + wifiRepository.wifiScanResults.value = emptyList() + + assertThat(latest).isEqualTo(NOT_CONNECTED_NETWORKS_UNAVAILABLE) + } + + @Test + fun wifiDefaultAndNotActive_networksAvailable() = + testScope.runTest { + val latest by collectLastValue(underTest.tileModel) + + val networkModel = WifiNetworkModel.Inactive + + connectivityRepository.setWifiConnected(validated = false) + wifiRepository.setIsWifiDefault(true) + wifiRepository.setWifiNetwork(networkModel) + wifiRepository.wifiScanResults.value = listOf(WifiScanEntry("test 1")) + + assertThat(latest?.secondaryLabel).isNull() + assertThat(latest?.secondaryTitle) + .isEqualTo(context.getString(R.string.quick_settings_networks_available)) + assertThat(latest?.icon).isNull() + assertThat(latest?.iconId).isEqualTo(R.drawable.ic_qs_no_internet_available) + } + + @Test + fun mobileDefault_usesNetworkNameAndIcon() = + testScope.runTest { + val latest by collectLastValue(underTest.tileModel) + + connectivityRepository.setMobileConnected() + mobileConnectionsRepository.mobileIsDefault.value = true + mobileConnectionRepository.apply { + setAllLevels(3) + setAllRoaming(false) + networkName.value = NetworkNameModel.Default("test network") + } + + assertThat(latest?.secondaryTitle).contains("test network") + assertThat(latest?.secondaryLabel).isNull() + assertThat(latest?.icon).isInstanceOf(SignalIcon::class.java) + assertThat(latest?.iconId).isNull() + } + + @Test + fun ethernetDefault_validated_matchesInteractor() = + testScope.runTest { + val latest by collectLastValue(underTest.tileModel) + val ethernetIcon by collectLastValue(ethernetInteractor.icon) + + connectivityRepository.setEthernetConnected(default = true, validated = true) + + assertThat(latest?.secondaryLabel).isNull() + assertThat(latest?.secondaryTitle) + .isEqualTo(ethernetIcon!!.contentDescription.toString()) + assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet_fully) + assertThat(latest?.icon).isNull() + } + + @Test + fun ethernetDefault_notValidated_matchesInteractor() = + testScope.runTest { + val latest by collectLastValue(underTest.tileModel) + val ethernetIcon by collectLastValue(ethernetInteractor.icon) + + connectivityRepository.setEthernetConnected(default = true, validated = false) + + assertThat(latest?.secondaryLabel).isNull() + assertThat(latest?.secondaryTitle) + .isEqualTo(ethernetIcon!!.contentDescription.toString()) + assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet) + assertThat(latest?.icon).isNull() + } + + companion object { + const val SUB_1_ID = 1 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt index 4f7bb724aae3..106b54891948 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryHelper.ACTIVITY_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -39,6 +40,9 @@ class FakeWifiRepository : WifiRepository { private val _wifiActivity = MutableStateFlow(ACTIVITY_DEFAULT) override val wifiActivity: StateFlow<DataActivityModel> = _wifiActivity + override val wifiScanResults: MutableStateFlow<List<WifiScanEntry>> = + MutableStateFlow(emptyList()) + fun setIsWifiEnabled(enabled: Boolean) { _isWifiEnabled.value = enabled } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt index bea1154eeb34..c2e75aa85fcb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt @@ -27,6 +27,7 @@ import android.net.NetworkCapabilities.TRANSPORT_WIFI import android.net.TransportInfo import android.net.VpnTransportInfo import android.net.vcn.VcnTransportInfo +import android.net.wifi.ScanResult import android.net.wifi.WifiInfo import android.net.wifi.WifiManager import android.net.wifi.WifiManager.TrafficStateCallback @@ -45,6 +46,7 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.Connectivi import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor @@ -1205,6 +1207,58 @@ class WifiRepositoryImplTest : SysuiTestCase() { .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true)) } + @Test + fun wifiScanResults_containsSsidList() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiScanResults) + + val scanResults = + listOf( + ScanResult().also { it.SSID = "ssid 1" }, + ScanResult().also { it.SSID = "ssid 2" }, + ScanResult().also { it.SSID = "ssid 3" }, + ScanResult().also { it.SSID = "ssid 4" }, + ScanResult().also { it.SSID = "ssid 5" }, + ) + whenever(wifiManager.scanResults).thenReturn(scanResults) + getScanResultsCallback().onScanResultsAvailable() + + val expected = + listOf( + WifiScanEntry(ssid = "ssid 1"), + WifiScanEntry(ssid = "ssid 2"), + WifiScanEntry(ssid = "ssid 3"), + WifiScanEntry(ssid = "ssid 4"), + WifiScanEntry(ssid = "ssid 5"), + ) + + assertThat(latest).isEqualTo(expected) + } + + @Test + fun wifiScanResults_updates() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiScanResults) + + var scanResults = + listOf( + ScanResult().also { it.SSID = "ssid 1" }, + ScanResult().also { it.SSID = "ssid 2" }, + ScanResult().also { it.SSID = "ssid 3" }, + ScanResult().also { it.SSID = "ssid 4" }, + ScanResult().also { it.SSID = "ssid 5" }, + ) + whenever(wifiManager.scanResults).thenReturn(scanResults) + getScanResultsCallback().onScanResultsAvailable() + + // New scan representing no results + scanResults = emptyList() + whenever(wifiManager.scanResults).thenReturn(scanResults) + getScanResultsCallback().onScanResultsAvailable() + + assertThat(latest).isEmpty() + } + private fun createRepo(): WifiRepositoryImpl { return WifiRepositoryImpl( fakeBroadcastDispatcher, @@ -1240,6 +1294,13 @@ class WifiRepositoryImplTest : SysuiTestCase() { return callbackCaptor.value!! } + private fun getScanResultsCallback(): WifiManager.ScanResultsCallback { + testScope.runCurrent() + val callbackCaptor = argumentCaptor<WifiManager.ScanResultsCallback>() + verify(wifiManager).registerScanResultsCallback(any(), callbackCaptor.capture()) + return callbackCaptor.value!! + } + private fun createWifiNetworkCapabilities( transportInfo: TransportInfo, isValidated: Boolean = true, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt index 662e36a55b9b..afab6230df5b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod +import android.net.wifi.ScanResult import android.net.wifi.WifiManager import android.net.wifi.WifiManager.UNKNOWN_SSID import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo @@ -32,6 +33,7 @@ import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor @@ -77,6 +79,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { featureFlags, testScope.backgroundScope, executor, + dispatcher, wifiPickerTrackerFactory, wifiManager, logger, @@ -1137,6 +1140,58 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true)) } + @Test + fun wifiScanResults_containsSsidList() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiScanResults) + + val scanResults = + listOf( + ScanResult().also { it.SSID = "ssid 1" }, + ScanResult().also { it.SSID = "ssid 2" }, + ScanResult().also { it.SSID = "ssid 3" }, + ScanResult().also { it.SSID = "ssid 4" }, + ScanResult().also { it.SSID = "ssid 5" }, + ) + whenever(wifiManager.scanResults).thenReturn(scanResults) + getScanResultsCallback().onScanResultsAvailable() + + val expected = + listOf( + WifiScanEntry(ssid = "ssid 1"), + WifiScanEntry(ssid = "ssid 2"), + WifiScanEntry(ssid = "ssid 3"), + WifiScanEntry(ssid = "ssid 4"), + WifiScanEntry(ssid = "ssid 5"), + ) + + assertThat(latest).isEqualTo(expected) + } + + @Test + fun wifiScanResults_updates() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiScanResults) + + var scanResults = + listOf( + ScanResult().also { it.SSID = "ssid 1" }, + ScanResult().also { it.SSID = "ssid 2" }, + ScanResult().also { it.SSID = "ssid 3" }, + ScanResult().also { it.SSID = "ssid 4" }, + ScanResult().also { it.SSID = "ssid 5" }, + ) + whenever(wifiManager.scanResults).thenReturn(scanResults) + getScanResultsCallback().onScanResultsAvailable() + + // New scan representing no results + scanResults = listOf() + whenever(wifiManager.scanResults).thenReturn(scanResults) + getScanResultsCallback().onScanResultsAvailable() + + assertThat(latest).isEmpty() + } + private fun getCallback(): WifiPickerTracker.WifiPickerTrackerCallback { testScope.runCurrent() return callbackCaptor.value @@ -1156,6 +1211,13 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { } } + private fun getScanResultsCallback(): WifiManager.ScanResultsCallback { + testScope.runCurrent() + val callbackCaptor = argumentCaptor<WifiManager.ScanResultsCallback>() + verify(wifiManager).registerScanResultsCallback(any(), callbackCaptor.capture()) + return callbackCaptor.value!! + } + private companion object { const val TITLE = "AB" } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt index 6fe88c100fdc..1db80651bf9b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt @@ -21,11 +21,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn @@ -56,7 +58,8 @@ class WifiInteractorImplTest : SysuiTestCase() { fun setUp() { connectivityRepository = FakeConnectivityRepository() wifiRepository = FakeWifiRepository() - underTest = WifiInteractorImpl(connectivityRepository, wifiRepository) + underTest = + WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope) } @Test @@ -300,4 +303,76 @@ class WifiInteractorImplTest : SysuiTestCase() { job.cancel() } + + @Test + fun areNetworksAvailable_noneActive_noResults() = + testScope.runTest { + val latest by collectLastValue(underTest.areNetworksAvailable) + + wifiRepository.wifiScanResults.value = emptyList() + wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive) + + assertThat(latest).isFalse() + } + + @Test + fun areNetworksAvailable_noneActive_nonEmptyResults() = + testScope.runTest { + val latest by collectLastValue(underTest.areNetworksAvailable) + + wifiRepository.wifiScanResults.value = + listOf( + WifiScanEntry(ssid = "ssid 1"), + WifiScanEntry(ssid = "ssid 2"), + WifiScanEntry(ssid = "ssid 3"), + ) + + wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive) + + assertThat(latest).isTrue() + } + + @Test + fun areNetworksAvailable_activeNetwork_resultsIncludeOtherNetworks() = + testScope.runTest { + val latest by collectLastValue(underTest.areNetworksAvailable) + + wifiRepository.wifiScanResults.value = + listOf( + WifiScanEntry(ssid = "ssid 1"), + WifiScanEntry(ssid = "ssid 2"), + WifiScanEntry(ssid = "ssid 3"), + ) + + wifiRepository.setWifiNetwork( + WifiNetworkModel.Active( + ssid = "ssid 2", + networkId = 1, + level = 2, + ) + ) + + assertThat(latest).isTrue() + } + + @Test + fun areNetworksAvailable_activeNetwork_onlyResultIsTheActiveNetwork() = + testScope.runTest { + val latest by collectLastValue(underTest.areNetworksAvailable) + + wifiRepository.wifiScanResults.value = + listOf( + WifiScanEntry(ssid = "ssid 2"), + ) + + wifiRepository.setWifiNetwork( + WifiNetworkModel.Active( + ssid = "ssid 2", + networkId = 1, + level = 2, + ) + ) + + assertThat(latest).isFalse() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt index 3f499359cc6a..a0d4d1390b2c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt @@ -82,8 +82,8 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { connectivityRepository = FakeConnectivityRepository() wifiRepository = FakeWifiRepository() wifiRepository.setIsWifiEnabled(true) - interactor = WifiInteractorImpl(connectivityRepository, wifiRepository) scope = CoroutineScope(Dispatchers.Unconfined) + interactor = WifiInteractorImpl(connectivityRepository, wifiRepository, scope) airplaneModeViewModel = AirplaneModeViewModelImpl( AirplaneModeInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt index e6724d86ec55..1d1b84c04d71 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt @@ -40,7 +40,7 @@ import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiIntera import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon -import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel.Companion.NO_INTERNET +import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon.Companion.NO_INTERNET import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -83,8 +83,8 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase connectivityRepository = FakeConnectivityRepository() wifiRepository = FakeWifiRepository() wifiRepository.setIsWifiEnabled(true) - interactor = WifiInteractorImpl(connectivityRepository, wifiRepository) scope = CoroutineScope(IMMEDIATE) + interactor = WifiInteractorImpl(connectivityRepository, wifiRepository, scope) airplaneModeViewModel = AirplaneModeViewModelImpl( AirplaneModeInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt index bdeba2a2f4c8..5aacc6626eb7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt @@ -74,7 +74,8 @@ class WifiViewModelTest : SysuiTestCase() { connectivityRepository = FakeConnectivityRepository() wifiRepository = FakeWifiRepository() wifiRepository.setIsWifiEnabled(true) - interactor = WifiInteractorImpl(connectivityRepository, wifiRepository) + interactor = + WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope) airplaneModeViewModel = AirplaneModeViewModelImpl( AirplaneModeInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index c12df9837a63..525624547528 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -57,6 +57,7 @@ import com.android.internal.jank.InteractionJankMonitor; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.animation.AnimatorTestRule; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.media.dialog.MediaOutputDialogFactory; @@ -71,6 +72,7 @@ import com.android.systemui.statusbar.policy.FakeConfigurationController; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -129,6 +131,9 @@ public class VolumeDialogImplTest extends SysuiTestCase { private FakeFeatureFlags mFeatureFlags; private int mLongestHideShowAnimationDuration = 250; + @Rule + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); + @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); @@ -242,7 +247,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { | AccessibilityManager.FLAG_CONTENT_TEXT); } - @Test public void testComputeTimeout_withHovering() { Mockito.reset(mAccessibilityMgr); @@ -669,11 +673,12 @@ public class VolumeDialogImplTest extends SysuiTestCase { @After public void teardown() { - cleanUp(mDialog); setOrientation(mOriginalOrientation); + mAnimatorTestRule.advanceTimeBy(mLongestHideShowAnimationDuration); mTestableLooper.moveTimeForward(mLongestHideShowAnimationDuration); mTestableLooper.processAllMessages(); reset(mPostureController); + cleanUp(mDialog); } private void cleanUp(VolumeDialogImpl dialog) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 949c4773f039..1b623a3de7bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -341,6 +341,7 @@ public class BubblesTest extends SysuiTestCase { mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController, + syncExecutor, mColorExtractor, mDumpManager, mKeyguardStateController, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt index 715d66191428..6f51d1b60822 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt @@ -33,10 +33,17 @@ fun display(type: Int, flags: Int = 0, id: Int = 0): Display { /** Fake [DisplayRepository] implementation for testing. */ class FakeDisplayRepository : DisplayRepository { private val flow = MutableSharedFlow<Set<Display>>() + private val pendingDisplayFlow = MutableSharedFlow<Int?>() /** Emits [value] as [displays] flow value. */ suspend fun emit(value: Set<Display>) = flow.emit(value) + /** Emits [value] as [pendingDisplayId] flow value. */ + suspend fun emit(value: Int?) = pendingDisplayFlow.emit(value) + override val displays: Flow<Set<Display>> get() = flow + + override val pendingDisplayId: Flow<Int?> + get() = pendingDisplayFlow } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index faebcaac1be3..cc0c943bdbbe 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -81,7 +81,7 @@ class FakeKeyguardRepository : KeyguardRepository { override val linearDozeAmount: Flow<Float> = _dozeAmount private val _statusBarState = MutableStateFlow(StatusBarState.SHADE) - override val statusBarState: Flow<StatusBarState> = _statusBarState + override val statusBarState: StateFlow<StatusBarState> = _statusBarState private val _dozeTransitionModel = MutableStateFlow(DozeTransitionModel()) override val dozeTransitionModel: Flow<DozeTransitionModel> = _dozeTransitionModel diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt index 3334f3e82c59..b92d946b3750 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt @@ -32,6 +32,8 @@ class FakePowerRepository( var lastWakeWhy: String? = null var lastWakeReason: Int? = null + var userTouchRegistered = false + fun setInteractive(value: Boolean) { _isInteractive.value = value } @@ -40,4 +42,8 @@ class FakePowerRepository( lastWakeWhy = why lastWakeReason = wakeReason } + + override fun userTouch() { + userTouchRegistered = true + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index 6b40f8eb2c2f..f7db44e9618a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -27,6 +27,9 @@ import com.android.systemui.bouncer.data.repository.BouncerRepository import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.classifier.FalsingCollectorFake +import com.android.systemui.classifier.domain.interactor.FalsingInteractor import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags @@ -37,6 +40,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.shared.model.WakeSleepReason import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.keyguard.shared.model.WakefulnessState +import com.android.systemui.power.data.repository.FakePowerRepository import com.android.systemui.scene.data.repository.SceneContainerRepository import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.SceneContainerConfig @@ -93,8 +97,13 @@ class SceneTestUtils( } } + val powerRepository: FakePowerRepository by lazy { FakePowerRepository() } + private val context = test.context + private val falsingCollectorFake: FalsingCollector by lazy { FalsingCollectorFake() } + private var falsingInteractor: FalsingInteractor? = null + fun fakeSceneContainerRepository( containerConfig: SceneContainerConfig = fakeSceneContainerConfig(), ): SceneContainerRepository { @@ -126,6 +135,7 @@ class SceneTestUtils( return SceneInteractor( applicationScope = applicationScope(), repository = repository, + powerRepository = powerRepository, logger = mock(), ) } @@ -149,10 +159,6 @@ class SceneTestUtils( ) } - fun keyguardRepository(): FakeKeyguardRepository { - return keyguardRepository - } - fun keyguardInteractor(repository: KeyguardRepository): KeyguardInteractor { return KeyguardInteractor( repository = repository, @@ -175,6 +181,7 @@ class SceneTestUtils( authenticationInteractor = authenticationInteractor, sceneInteractor = sceneInteractor, featureFlags = featureFlags, + falsingInteractor = falsingInteractor(), ) } @@ -191,6 +198,14 @@ class SceneTestUtils( ) } + fun falsingInteractor(collector: FalsingCollector = falsingCollector()): FalsingInteractor { + return falsingInteractor ?: FalsingInteractor(collector).also { falsingInteractor = it } + } + + fun falsingCollector(): FalsingCollector { + return falsingCollectorFake + } + private fun applicationScope(): CoroutineScope { return testScope.backgroundScope } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java index 03e3423a3dd5..3774d1d16e4e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java @@ -84,10 +84,6 @@ public class FakeStatusBarIconController extends BaseLeakChecker<IconManager> } @Override - public void removeAllIconsForSlot(String slot) { - } - - @Override public void setIconAccessibilityLiveRegion(String slot, int mode) { } diff --git a/packages/services/VirtualCamera/OWNERS b/packages/services/VirtualCamera/OWNERS new file mode 100644 index 000000000000..c66443fb8a14 --- /dev/null +++ b/packages/services/VirtualCamera/OWNERS @@ -0,0 +1,3 @@ +include /services/companion/java/com/android/server/companion/virtual/OWNERS +caen@google.com +jsebechlebsky@google.com
\ No newline at end of file diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index f3a540b1c7a5..cd83f8f4d9d8 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -389,11 +389,19 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo @Override public void onMotionEvent(MotionEvent transformedEvent, MotionEvent rawEvent, int policyFlags) { + if (!mInstalled) { + Slog.w(TAG, "onMotionEvent called before input filter installed!"); + return; + } sendInputEvent(transformedEvent, policyFlags); } @Override public void onKeyEvent(KeyEvent event, int policyFlags) { + if (!mInstalled) { + Slog.w(TAG, "onKeyEvent called before input filter installed!"); + return; + } sendInputEvent(event, policyFlags); } diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java index 4688658bf1c3..423b85f9305f 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java @@ -56,7 +56,7 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { private static final long TIMEOUT_IDLE_BIND_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS; private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS; - private final FillServiceCallbacks mCallbacks; + private FillServiceCallbacks mCallbacks; private final Object mLock = new Object(); private CompletableFuture<FillResponse> mPendingFillRequest; private int mPendingFillRequestId = INVALID_REQUEST_ID; @@ -128,9 +128,12 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { */ public int cancelCurrentRequest() { synchronized (mLock) { - return mPendingFillRequest != null && mPendingFillRequest.cancel(false) + int canceledRequestId = mPendingFillRequest != null && mPendingFillRequest.cancel(false) ? mPendingFillRequestId : INVALID_REQUEST_ID; + mPendingFillRequest = null; + mPendingFillRequestId = INVALID_REQUEST_ID; + return canceledRequestId; } } @@ -184,6 +187,10 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { mPendingFillRequest = null; mPendingFillRequestId = INVALID_REQUEST_ID; } + if (mCallbacks == null) { + Slog.w(TAG, "Error calling RemoteFillService - service already unbound"); + return; + } if (err == null) { mCallbacks.onFillRequestSuccess(request.getId(), res, mComponentName.getPackageName(), request.getFlags()); @@ -220,6 +227,10 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { return save; }).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS) .whenComplete((res, err) -> Handler.getMain().post(() -> { + if (mCallbacks == null) { + Slog.w(TAG, "Error calling RemoteFillService - service already unbound"); + return; + } if (err == null) { mCallbacks.onSaveRequestSuccess(mComponentName.getPackageName(), res); } else { @@ -234,6 +245,8 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { } public void destroy() { + cancelCurrentRequest(); unbind(); + mCallbacks = null; } } diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java index 3fd6fe8afba6..9bab261bb0f4 100644 --- a/services/core/java/com/android/server/PinnerService.java +++ b/services/core/java/com/android/server/PinnerService.java @@ -78,11 +78,14 @@ import java.io.InputStream; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +import sun.misc.Unsafe; + /** * <p>PinnerService pins important files for key processes in memory.</p> * <p>Files to pin are specified in the config_defaultPinnerServiceFiles @@ -150,6 +153,11 @@ public final class PinnerService extends SystemService { @GuardedBy("this") private ArraySet<Integer> mPinKeys; + private static final long MAX_ANON_SIZE = 2L * (1L << 30); // 2GB + private long mPinAnonSize; + private long mPinAnonAddress; + private long mCurrentlyPinnedAnonSize; + // Resource-configured pinner flags; private final boolean mConfiguredToPinCamera; private final boolean mConfiguredToPinHome; @@ -550,6 +558,11 @@ public final class PinnerService extends SystemService { pinKeys.add(KEY_ASSISTANT); } + mPinAnonSize = DeviceConfig.getLong(DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT, + "pin_anon_size", + SystemProperties.getLong("pinner.pin_anon_size", 0)); + mPinAnonSize = Math.max(0, Math.min(mPinAnonSize, MAX_ANON_SIZE)); + return pinKeys; } @@ -589,6 +602,7 @@ public final class PinnerService extends SystemService { int key = currentPinKeys.valueAt(i); pinApp(key, userHandle, true /* force */); } + pinAnonRegion(); } /** @@ -673,6 +687,64 @@ public final class PinnerService extends SystemService { } /** + * Pin an empty anonymous region. This should only be used for ablation experiments. + */ + private void pinAnonRegion() { + if (mPinAnonSize == 0) { + return; + } + long alignedPinSize = mPinAnonSize; + if (alignedPinSize % PAGE_SIZE != 0) { + alignedPinSize -= alignedPinSize % PAGE_SIZE; + Slog.e(TAG, "pinAnonRegion: aligning size to " + alignedPinSize); + } + if (mPinAnonAddress != 0 + && mCurrentlyPinnedAnonSize != alignedPinSize) { + unpinAnonRegion(); + } + long address = 0; + try { + address = Os.mmap(0, alignedPinSize, + OsConstants.PROT_READ | OsConstants.PROT_WRITE, + OsConstants.MAP_PRIVATE | OsConstants.MAP_ANONYMOUS, + new FileDescriptor(), /*offset=*/0); + + Unsafe tempUnsafe = null; + Class<sun.misc.Unsafe> clazz = sun.misc.Unsafe.class; + for (java.lang.reflect.Field f : clazz.getDeclaredFields()) { + f.setAccessible(true); + Object obj = f.get(null); + if (clazz.isInstance(obj)) { + tempUnsafe = clazz.cast(obj); + } + } + if (tempUnsafe == null) { + throw new Exception("Couldn't get Unsafe"); + } + Method setMemory = clazz.getMethod("setMemory", long.class, long.class, byte.class); + setMemory.invoke(tempUnsafe, address, alignedPinSize, (byte) 1); + Os.mlock(address, alignedPinSize); + mCurrentlyPinnedAnonSize = alignedPinSize; + mPinAnonAddress = address; + address = -1; + Slog.e(TAG, "pinAnonRegion success, size=" + mCurrentlyPinnedAnonSize); + } catch (Exception ex) { + Slog.e(TAG, "Could not pin anon region of size " + alignedPinSize, ex); + return; + } finally { + if (address >= 0) { + safeMunmap(address, alignedPinSize); + } + } + } + + private void unpinAnonRegion() { + if (mPinAnonAddress != 0) { + safeMunmap(mPinAnonAddress, mCurrentlyPinnedAnonSize); + } + } + + /** * @return The maximum amount of bytes to be pinned for an app of type {@code key}. */ private int getSizeLimitForKey(@AppKey int key) { @@ -1083,6 +1155,9 @@ public final class PinnerService extends SystemService { totalSize += pf.bytesPinned; } } + if (mPinAnonAddress != 0) { + pw.format("Pinned anon region: %s\n", mCurrentlyPinnedAnonSize); + } pw.format("Total size: %s\n", totalSize); pw.println(); if (!mPendingRepin.isEmpty()) { diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 4d249abafdf1..a78764456d8b 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -32,6 +32,7 @@ import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.IInstalld.IFsveritySetupAuthToken; import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; import static android.os.storage.OnObbStateChangeListener.ERROR_ALREADY_MOUNTED; import static android.os.storage.OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT; @@ -678,6 +679,7 @@ class StorageManagerService extends IStorageManager.Stub private static final int H_VOLUME_STATE_CHANGED = 15; private static final int H_CLOUD_MEDIA_PROVIDER_CHANGED = 16; private static final int H_SECURE_KEYGUARD_STATE_CHANGED = 17; + private static final int H_REMOUNT_VOLUMES_ON_MOVE = 18; class StorageManagerServiceHandler extends Handler { public StorageManagerServiceHandler(Looper looper) { @@ -833,6 +835,10 @@ class StorageManagerService extends IStorageManager.Stub } break; } + case H_REMOUNT_VOLUMES_ON_MOVE: { + remountVolumesForRunningUsersOnMove(); + break; + } } } } @@ -1286,6 +1292,44 @@ class StorageManagerService extends IStorageManager.Stub } } + /** + * This method informs vold and storaged that the user has stopped and started whenever move + * storage is performed. This ensures that the correct emulated volumes are mounted for the + * users other than the current user. This solves an edge case wherein the correct emulated + * volumes are not mounted, this will cause the media data to be still stored on internal + * storage whereas the data should be stored in the adopted primary storage. This method stops + * the users at vold first which will remove the old volumes which and starts the users at vold + * which will reattach the correct volumes. This does not performs a full reset as full reset + * clears every state from vold and SMS {@link #resetIfRebootedAndConnected} which is expensive + * and causes instability. + */ + private void remountVolumesForRunningUsersOnMove() { + // Do not want to hold the lock for long + final List<Integer> unlockedUsers = new ArrayList<>(); + synchronized (mLock) { + for (int userId : mSystemUnlockedUsers) { + if (userId == mCurrentUserId) continue; + unlockedUsers.add(userId); + } + } + for (Integer userId : unlockedUsers) { + try { + mVold.onUserStopped(userId); + mStoraged.onUserStopped(userId); + } catch (Exception e) { + Slog.wtf(TAG, e); + } + } + for (Integer userId : unlockedUsers) { + try { + mVold.onUserStarted(userId); + mStoraged.onUserStarted(userId); + } catch (Exception e) { + Slog.wtf(TAG, e); + } + } + } + private boolean supportsBlockCheckpoint() throws RemoteException { enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS); return mVold.supportsBlockCheckpoint(); @@ -1820,6 +1864,7 @@ class StorageManagerService extends IStorageManager.Stub mPrimaryStorageUuid = mMoveTargetUuid; writeSettingsLocked(); + mHandler.obtainMessage(H_REMOUNT_VOLUMES_ON_MOVE).sendToTarget(); } if (PackageManager.isMoveStatusFinished(status)) { @@ -4640,7 +4685,7 @@ class StorageManagerService extends IStorageManager.Stub pw.print(") total size: "); pw.print(pair.second); pw.print(" ("); - pw.print(DataUnit.MEBIBYTES.toBytes(pair.second)); + pw.print(pair.second / DataUnit.MEBIBYTES.toBytes(1L)); pw.println(" MiB)"); } @@ -4950,5 +4995,24 @@ class StorageManagerService extends IStorageManager.Stub } } + @Override + public IFsveritySetupAuthToken createFsveritySetupAuthToken(ParcelFileDescriptor authFd, + int appUid, @UserIdInt int userId) throws IOException { + try { + return mInstaller.createFsveritySetupAuthToken(authFd, appUid, userId); + } catch (Installer.InstallerException e) { + throw new IOException(e); + } + } + + @Override + public int enableFsverity(IFsveritySetupAuthToken authToken, String filePath, + String packageName) throws IOException { + try { + return mInstaller.enableFsverity(authToken, filePath, packageName); + } catch (Installer.InstallerException e) { + throw new IOException(e); + } + } } } diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index 03022b06dfe1..cd0a9d29d6b4 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -74,6 +74,15 @@ { "name": "CtsVcnTestCases", "file_patterns": ["VcnManagementService\\.java"] + }, + { + "name": "FrameworksNetTests", + "options": [ + { + "exclude-annotation": "com.android.testutils.SkipPresubmit" + } + ], + "file_patterns": ["VpnManagerService\\.java"] } ], "presubmit-large": [ @@ -91,6 +100,21 @@ } ], "file_patterns": ["ClipboardService\\.java"] + }, + { + "name": "CtsHostsideNetworkTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "android.platform.test.annotations.FlakyTest" + }, + { + "exclude-annotation": "com.android.testutils.SkipPresubmit" + } + ], + "file_patterns": ["VpnManagerService\\.java"] } ] } diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index 6baae4b39374..e17424bac6b3 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -937,7 +937,7 @@ public class Watchdog implements Dumpable { mActivity.addErrorToDropBox( dropboxTag, null, "system_server", null, null, null, null, report.toString(), stack, null, null, null, - errorId); + errorId, null); } } }; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index c4816fb1dd71..5773e204938e 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2115,6 +2115,34 @@ public class ActivityManagerService extends IActivityManager.Stub mVoiceInteractionManagerProvider = provider; } + /** + * Represents volatile states associated with a Dropbox entry. + * <p> + * These states, such as the process frozen state, can change quickly over time and thus + * should be captured as soon as possible to ensure accurate state. If a state is undefined, + * it means that the state was not read early and a fallback value can be used. + * </p> + */ + static class VolatileDropboxEntryStates { + private final Boolean mIsProcessFrozen; + + private VolatileDropboxEntryStates(Boolean frozenState) { + this.mIsProcessFrozen = frozenState; + } + + public static VolatileDropboxEntryStates withProcessFrozenState(boolean frozenState) { + return new VolatileDropboxEntryStates(frozenState); + } + + public static VolatileDropboxEntryStates emptyVolatileDropboxEnytyStates() { + return new VolatileDropboxEntryStates(null); + } + + public Boolean isProcessFrozen() { + return mIsProcessFrozen; + } + } + static class MemBinder extends Binder { ActivityManagerService mActivityManagerService; private final PriorityDump.PriorityDumper mPriorityDumper = @@ -8941,7 +8969,7 @@ public class ActivityManagerService extends IActivityManager.Stub addErrorToDropBox( eventType, r, processName, null, null, null, null, null, null, crashInfo, - new Float(loadingProgress), incrementalMetrics, null); + new Float(loadingProgress), incrementalMetrics, null, null); // For GWP-ASan recoverable crashes, don't make the app crash (the whole point of // 'recoverable' is that the app doesn't crash). Normally, for nonrecoreable native crashes, @@ -9052,7 +9080,7 @@ public class ActivityManagerService extends IActivityManager.Stub final StringBuilder sb = new StringBuilder(1024); synchronized (sb) { - appendDropBoxProcessHeaders(process, processName, sb); + appendDropBoxProcessHeaders(process, processName, null, sb); sb.append("Build: ").append(Build.FINGERPRINT).append("\n"); sb.append("System-App: ").append(isSystemApp).append("\n"); sb.append("Uptime-Millis: ").append(info.violationUptimeMillis).append("\n"); @@ -9155,7 +9183,7 @@ public class ActivityManagerService extends IActivityManager.Stub callingPid, (r != null) ? r.getProcessClassEnum() : 0); addErrorToDropBox("wtf", r, processName, null, null, null, tag, null, null, crashInfo, - null, null, null); + null, null, null, null); return r; } @@ -9180,7 +9208,7 @@ public class ActivityManagerService extends IActivityManager.Stub for (Pair<String, ApplicationErrorReport.CrashInfo> p = list.poll(); p != null; p = list.poll()) { addErrorToDropBox("wtf", proc, "system_server", null, null, null, p.first, null, null, - p.second, null, null, null); + p.second, null, null, null, null); } } @@ -9203,7 +9231,7 @@ public class ActivityManagerService extends IActivityManager.Stub * to append various headers to the dropbox log text. */ void appendDropBoxProcessHeaders(ProcessRecord process, String processName, - final StringBuilder sb) { + final VolatileDropboxEntryStates volatileStates, final StringBuilder sb) { // Watchdog thread ends up invoking this function (with // a null ProcessRecord) to add the stack file to dropbox. // Do not acquire a lock on this (am) in such cases, as it @@ -9222,7 +9250,12 @@ public class ActivityManagerService extends IActivityManager.Stub sb.append("PID: ").append(process.getPid()).append("\n"); sb.append("UID: ").append(process.uid).append("\n"); if (process.mOptRecord != null) { - sb.append("Frozen: ").append(process.mOptRecord.isFrozen()).append("\n"); + // Use 'isProcessFrozen' from 'volatileStates' if it'snon-null (present), + // otherwise use 'isFrozen' from 'mOptRecord'. + sb.append("Frozen: ").append( + (volatileStates != null && volatileStates.isProcessFrozen() != null) + ? volatileStates.isProcessFrozen() : process.mOptRecord.isFrozen() + ).append("\n"); } int flags = process.info.flags; final IPackageManager pm = AppGlobals.getPackageManager(); @@ -9335,7 +9368,7 @@ public class ActivityManagerService extends IActivityManager.Stub String subject, final String report, final File dataFile, final ApplicationErrorReport.CrashInfo crashInfo, @Nullable Float loadingProgress, @Nullable IncrementalMetrics incrementalMetrics, - @Nullable UUID errorId) { + @Nullable UUID errorId, @Nullable VolatileDropboxEntryStates volatileStates) { // NOTE -- this must never acquire the ActivityManagerService lock, // otherwise the watchdog may be prevented from resetting the system. @@ -9357,7 +9390,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (rateLimitResult.shouldRateLimit()) return; final StringBuilder sb = new StringBuilder(1024); - appendDropBoxProcessHeaders(process, processName, sb); + appendDropBoxProcessHeaders(process, processName, volatileStates, sb); if (process != null) { sb.append("Foreground: ") .append(process.isInterestingToUserLocked() ? "Yes" : "No") diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java index e7f4bf9dc468..46e5523ac9a6 100644 --- a/services/core/java/com/android/server/am/AppProfiler.java +++ b/services/core/java/com/android/server/am/AppProfiler.java @@ -1843,7 +1843,8 @@ public class AppProfiler { dropBuilder.append(catSw.toString()); FrameworkStatsLog.write(FrameworkStatsLog.LOW_MEM_REPORTED); mService.addErrorToDropBox("lowmem", null, "system_server", null, - null, null, tag.toString(), dropBuilder.toString(), null, null, null, null, null); + null, null, tag.toString(), dropBuilder.toString(), null, null, null, null, null, + null); synchronized (mService) { long now = SystemClock.uptimeMillis(); if (mLastMemUsageReportTime < now) { diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java index 09df2770776d..40b1de623336 100644 --- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java @@ -297,6 +297,7 @@ class ProcessErrorStateRecord { ArrayList<Integer> firstPids = new ArrayList<>(5); SparseBooleanArray lastPids = new SparseBooleanArray(20); + ActivityManagerService.VolatileDropboxEntryStates volatileDropboxEntriyStates = null; mApp.getWindowProcessController().appEarlyNotResponding(annotation, () -> { latencyTracker.waitingOnAMSLockStarted(); @@ -343,6 +344,9 @@ class ProcessErrorStateRecord { synchronized (mProcLock) { latencyTracker.waitingOnProcLockEnded(); setNotResponding(true); + volatileDropboxEntriyStates = + ActivityManagerService.VolatileDropboxEntryStates + .withProcessFrozenState(mApp.mOptRecord.isFrozen()); } // Log the ANR to the event log. @@ -620,7 +624,8 @@ class ProcessErrorStateRecord { ? (ProcessRecord) parentProcess.mOwner : null; mService.addErrorToDropBox("anr", mApp, mApp.processName, activityShortComponentName, parentShortComponentName, parentPr, null, report.toString(), tracesFile, - null, new Float(loadingProgress), incrementalMetrics, errorId); + null, new Float(loadingProgress), incrementalMetrics, errorId, + volatileDropboxEntriyStates); if (mApp.getWindowProcessController().appNotResponding(info.toString(), () -> { diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 97fb0e718c2a..3d11c6843338 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -5643,7 +5643,7 @@ public final class ProcessList { if (logToDropbox) { final long now = SystemClock.elapsedRealtime(); final StringBuilder sb = new StringBuilder(); - mService.appendDropBoxProcessHeaders(app, app.processName, sb); + mService.appendDropBoxProcessHeaders(app, app.processName, null, sb); sb.append("Reason: " + reason).append("\n"); sb.append("Requester UID: " + requester).append("\n"); dbox.addText(DROPBOX_TAG_IMPERCEPTIBLE_KILL, sb.toString()); diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING index 68062b566906..65f6af7d0309 100644 --- a/services/core/java/com/android/server/appop/TEST_MAPPING +++ b/services/core/java/com/android/server/appop/TEST_MAPPING @@ -4,7 +4,7 @@ "name": "CtsAppOpsTestCases", "options": [ { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" + "exclude-annotation": "androidx.test.filters.FlakyTest" } ] }, @@ -31,7 +31,7 @@ "name": "CtsPermissionTestCases", "options": [ { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" + "exclude-annotation": "androidx.test.filters.FlakyTest" }, { "include-filter": "android.permission.cts.BackgroundPermissionsTest" diff --git a/services/core/java/com/android/server/audio/UuidUtils.java b/services/core/java/com/android/server/audio/UuidUtils.java index 2003619b583c..035bea32da4f 100644 --- a/services/core/java/com/android/server/audio/UuidUtils.java +++ b/services/core/java/com/android/server/audio/UuidUtils.java @@ -45,26 +45,24 @@ class UuidUtils { * Generate a headtracking UUID from AudioDeviceAttributes */ public static UUID uuidFromAudioDeviceAttributes(AudioDeviceAttributes device) { - switch (device.getInternalType()) { - case AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP: - String address = device.getAddress().replace(":", ""); - if (address.length() != 12) { - return null; - } - address = "0x" + address; - long lsb = LSB_PREFIX_BT; - try { - lsb |= Long.decode(address).longValue(); - } catch (NumberFormatException e) { - return null; - } - if (AudioService.DEBUG_DEVICES) { - Slog.i(TAG, "uuidFromAudioDeviceAttributes lsb: " + Long.toHexString(lsb)); - } - return new UUID(0, lsb); - default: - // Handle other device types here - return null; + if (!AudioSystem.isBluetoothA2dpOutDevice(device.getInternalType()) + && !AudioSystem.isBluetoothLeOutDevice(device.getInternalType())) { + return null; } + String address = device.getAddress().replace(":", ""); + if (address.length() != 12) { + return null; + } + address = "0x" + address; + long lsb = LSB_PREFIX_BT; + try { + lsb |= Long.decode(address).longValue(); + } catch (NumberFormatException e) { + return null; + } + if (AudioService.DEBUG_DEVICES) { + Slog.i(TAG, "uuidFromAudioDeviceAttributes lsb: " + Long.toHexString(lsb)); + } + return new UUID(0, lsb); } } diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStats.java b/services/core/java/com/android/server/biometrics/AuthenticationStats.java index e109cc8011e7..707240bf41f8 100644 --- a/services/core/java/com/android/server/biometrics/AuthenticationStats.java +++ b/services/core/java/com/android/server/biometrics/AuthenticationStats.java @@ -90,6 +90,11 @@ public class AuthenticationStats { mRejectedAttempts = 0; } + /** Update enrollment notification counter after sending a notification. */ + public void updateNotificationCounter() { + mEnrollmentNotifications++; + } + @Override public boolean equals(Object obj) { if (this == obj) { diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java index 3d1b162d508b..e8a20def02cb 100644 --- a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java +++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java @@ -68,8 +68,10 @@ public class AuthenticationStatsCollector { @Override public void onReceive(@NonNull Context context, @NonNull Intent intent) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); + if (userId != UserHandle.USER_NULL && intent.getAction().equals(Intent.ACTION_USER_REMOVED)) { + Slog.d(TAG, "Removing data for user: " + userId); onUserRemoved(userId); } } @@ -84,7 +86,9 @@ public class AuthenticationStatsCollector { mModality = modality; mBiometricNotification = biometricNotification; - context.registerReceiver(mBroadcastReceiver, new IntentFilter(Intent.ACTION_USER_REMOVED)); + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_USER_REMOVED); + context.registerReceiver(mBroadcastReceiver, intentFilter); } private void initializeUserAuthenticationStatsMap() { @@ -121,10 +125,11 @@ public class AuthenticationStatsCollector { authenticationStats.authenticate(authenticated); + sendNotificationIfNeeded(userId); + if (mPersisterInitialized) { persistDataIfNeeded(userId); } - sendNotificationIfNeeded(userId); } /** Check if a notification should be sent after a calculation cycle. */ @@ -164,8 +169,10 @@ public class AuthenticationStatsCollector { } if (hasEnrolledFace && !hasEnrolledFingerprint) { mBiometricNotification.sendFpEnrollNotification(mContext); + authenticationStats.updateNotificationCounter(); } else if (!hasEnrolledFace && hasEnrolledFingerprint) { mBiometricNotification.sendFaceEnrollNotification(mContext); + authenticationStats.updateNotificationCounter(); } } diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java index 74e1410dba00..8122b1d131f8 100644 --- a/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java +++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java @@ -59,7 +59,7 @@ public class AuthenticationStatsPersister { // The package info in the context isn't initialized in the way it is for normal apps, // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we // build the path manually below using the same policy that appears in ContextImpl. - final File prefsFile = new File(Environment.getDataSystemDeDirectory(), FILE_NAME); + final File prefsFile = new File(Environment.getDataSystemDirectory(), FILE_NAME); mSharedPreferences = context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE); } @@ -137,16 +137,19 @@ public class AuthenticationStatsPersister { iterator.remove(); break; } + // Reset frrStatJson when user doesn't exist. + frrStatJson = null; } - // If there's existing frr stats in the file, we want to update the stats for the given - // modality and keep the stats for other modalities. + // Checks if this is a new user and there's no JSON for this user in the storage. if (frrStatJson == null) { frrStatJson = new JSONObject().put(USER_ID, userId); } frrStatsSet.add(buildFrrStats(frrStatJson, totalAttempts, rejectedAttempts, enrollmentNotifications, modality)); + Slog.d(TAG, "frrStatsSet to persist: " + frrStatsSet); + mSharedPreferences.edit().putStringSet(KEY, frrStatsSet).apply(); } catch (JSONException e) { diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java index 2ff695d7b85d..0fc8ababbafb 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java @@ -95,8 +95,6 @@ public class BiometricNotificationUtils { final Intent intent = new Intent(FACE_ENROLL_ACTION); intent.setPackage(SETTINGS_PACKAGE); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */, @@ -120,8 +118,6 @@ public class BiometricNotificationUtils { final Intent intent = new Intent(FINGERPRINT_ENROLL_ACTION); intent.setPackage(SETTINGS_PACKAGE); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */, diff --git a/services/core/java/com/android/server/connectivity/TEST_MAPPING b/services/core/java/com/android/server/connectivity/TEST_MAPPING new file mode 100644 index 000000000000..687d4b06b4c0 --- /dev/null +++ b/services/core/java/com/android/server/connectivity/TEST_MAPPING @@ -0,0 +1,30 @@ +{ + "presubmit": [ + { + "name": "FrameworksNetTests", + "options": [ + { + "exclude-annotation": "com.android.testutils.SkipPresubmit" + } + ], + "file_patterns": ["Vpn\\.java", "VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"] + } + ], + "presubmit-large": [ + { + "name": "CtsHostsideNetworkTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "android.platform.test.annotations.FlakyTest" + }, + { + "exclude-annotation": "com.android.testutils.SkipPresubmit" + } + ], + "file_patterns": ["Vpn\\.java", "VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"] + } + ] +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index cba5039f714d..ff35b192cccd 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -3296,13 +3296,6 @@ public class Vpn { } agentConnect(this::onValidationStatus); return; // Link properties are already sent. - } else { - // Underlying networks also set in agentConnect() - doSetUnderlyingNetworks(networkAgent, Collections.singletonList(network)); - mNetworkCapabilities = - new NetworkCapabilities.Builder(mNetworkCapabilities) - .setUnderlyingNetworks(Collections.singletonList(network)) - .build(); } lp = makeLinkProperties(); // Accesses VPN instance fields; must be locked @@ -3384,8 +3377,6 @@ public class Vpn { final LinkProperties oldLp = makeLinkProperties(); - final boolean underlyingNetworkHasChanged = - !Arrays.equals(mConfig.underlyingNetworks, new Network[]{network}); mConfig.underlyingNetworks = new Network[] {network}; mConfig.mtu = calculateVpnMtu(); @@ -3417,18 +3408,9 @@ public class Vpn { removed.getAddress(), removed.getPrefixLength()); } } else { - // Put below 3 updates into else block is because agentConnect() will do - // those things, so there is no need to do the redundant work. + // Put below update into else block is because agentConnect() will do + // the same things, so there is no need to do the redundant work. if (!newLp.equals(oldLp)) doSendLinkProperties(mNetworkAgent, newLp); - if (underlyingNetworkHasChanged) { - mNetworkCapabilities = - new NetworkCapabilities.Builder(mNetworkCapabilities) - .setUnderlyingNetworks( - Collections.singletonList(network)) - .build(); - doSetUnderlyingNetworks(mNetworkAgent, - Collections.singletonList(network)); - } } } @@ -3554,10 +3536,28 @@ public class Vpn { */ private void startOrMigrateIkeSession(@Nullable Network underlyingNetwork) { if (underlyingNetwork == null) { + // For null underlyingNetwork case, there will not be a NetworkAgent available so + // no underlying network update is necessary here. Note that updating + // mNetworkCapabilities here would also be reasonable, but it will be updated next + // time the VPN connects anyway. Log.d(TAG, "There is no active network for starting an IKE session"); return; } + final List<Network> networks = Collections.singletonList(underlyingNetwork); + // Update network capabilities if underlying network is changed. + if (!networks.equals(mNetworkCapabilities.getUnderlyingNetworks())) { + mNetworkCapabilities = + new NetworkCapabilities.Builder(mNetworkCapabilities) + .setUnderlyingNetworks(networks) + .build(); + // No NetworkAgent case happens when Vpn tries to start a new VPN. The underlying + // network update will be done later with NetworkAgent connected event. + if (mNetworkAgent != null) { + doSetUnderlyingNetworks(mNetworkAgent, networks); + } + } + if (maybeMigrateIkeSessionAndUpdateVpnTransportInfo(underlyingNetwork)) return; startIkeSession(underlyingNetwork); diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java index c04c2793b3c5..39172b8252ed 100644 --- a/services/core/java/com/android/server/display/HighBrightnessModeController.java +++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java @@ -25,6 +25,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.PowerManager; import android.os.SystemClock; +import android.os.Trace; import android.os.UserHandle; import android.provider.Settings; import android.util.MathUtils; @@ -33,6 +34,7 @@ import android.util.TimeUtils; import android.view.SurfaceControlHdrLayerInfoListener; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.util.FrameworkStatsLog; import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData; import com.android.server.display.DisplayManagerService.Clock; @@ -99,6 +101,7 @@ class HighBrightnessModeController { private boolean mIsHdrLayerPresent = false; // mMaxDesiredHdrSdrRatio should only be applied when there is a valid backlight->nits mapping private float mMaxDesiredHdrSdrRatio = DEFAULT_MAX_DESIRED_HDR_SDR_RATIO; + private boolean mForceHbmChangeCallback = false; private boolean mIsBlockedByLowPowerMode = false; private int mWidth; private int mHeight; @@ -484,7 +487,8 @@ class HighBrightnessModeController { private void updateHbmMode() { int newHbmMode = calculateHighBrightnessMode(); updateHbmStats(newHbmMode); - if (mHbmMode != newHbmMode) { + if (mHbmMode != newHbmMode || mForceHbmChangeCallback) { + mForceHbmChangeCallback = false; mHbmMode = newHbmMode; mHbmChangeCallback.run(); } @@ -600,26 +604,32 @@ class HighBrightnessModeController { public void onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers, int maxW, int maxH, int flags, float maxDesiredHdrSdrRatio) { mHandler.post(() -> { + Trace.traceBegin(Trace.TRACE_TAG_POWER, "HBMController#onHdrInfoChanged"); mIsHdrLayerPresent = numberOfHdrLayers > 0 && (float) (maxW * maxH) >= ((float) (mWidth * mHeight) * mHbmData.minimumHdrPercentOfScreen); - final float candidateDesiredHdrSdrRatio = + float candidateDesiredHdrSdrRatio = mIsHdrLayerPresent && mHdrBrightnessCfg != null ? maxDesiredHdrSdrRatio : DEFAULT_MAX_DESIRED_HDR_SDR_RATIO; - if (candidateDesiredHdrSdrRatio >= 1.0f) { - mMaxDesiredHdrSdrRatio = candidateDesiredHdrSdrRatio; - } else { + if (candidateDesiredHdrSdrRatio < 1.0f) { Slog.w(TAG, "Ignoring invalid desired HDR/SDR Ratio: " + candidateDesiredHdrSdrRatio); - mMaxDesiredHdrSdrRatio = DEFAULT_MAX_DESIRED_HDR_SDR_RATIO; + candidateDesiredHdrSdrRatio = DEFAULT_MAX_DESIRED_HDR_SDR_RATIO; + } + + if (!BrightnessSynchronizer.floatEquals( + mMaxDesiredHdrSdrRatio, candidateDesiredHdrSdrRatio)) { + mForceHbmChangeCallback = true; + mMaxDesiredHdrSdrRatio = candidateDesiredHdrSdrRatio; } // Calling the brightness update so that we can recalculate // brightness with HDR in mind. onBrightnessChanged(mBrightness, mUnthrottledBrightness, mThrottlingReason); + Trace.traceEnd(Trace.TRACE_TAG_POWER); }); } } diff --git a/services/core/java/com/android/server/display/WifiDisplayController.java b/services/core/java/com/android/server/display/WifiDisplayController.java index f6d06aa2fec0..955b8d95fd87 100644 --- a/services/core/java/com/android/server/display/WifiDisplayController.java +++ b/services/core/java/com/android/server/display/WifiDisplayController.java @@ -1062,8 +1062,10 @@ final class WifiDisplayController implements DumpUtils.Dump { } private static WifiDisplay createWifiDisplay(WifiP2pDevice device) { + WifiP2pWfdInfo wfdInfo = device.getWfdInfo(); + boolean isSessionAvailable = wfdInfo != null && wfdInfo.isSessionAvailable(); return new WifiDisplay(device.deviceAddress, device.deviceName, null, - true, device.getWfdInfo().isSessionAvailable(), false); + true, isSessionAvailable, false); } private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 6f0a4b48c09e..1afa3ed97463 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -118,7 +118,6 @@ import static android.service.notification.NotificationListenerService.TRIM_FULL import static android.service.notification.NotificationListenerService.TRIM_LIGHT; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; -import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES; @@ -533,6 +532,8 @@ public class NotificationManagerService extends SystemService { @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2) private static final long NOTIFICATION_LOG_ASSISTANT_CANCEL = 195579280L; + private static final Duration POST_WAKE_LOCK_TIMEOUT = Duration.ofSeconds(30); + private IActivityManager mAm; private ActivityTaskManagerInternal mAtm; private ActivityManager mActivityManager; @@ -2328,8 +2329,7 @@ public class NotificationManagerService extends SystemService { mRankingHandler = rankingHandler; mConditionProviders = conditionProviders; mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders, - new SysUiStatsEvent.BuilderFactory(), flagResolver, - new ZenModeEventLogger(mPackageManagerClient)); + flagResolver, new ZenModeEventLogger(mPackageManagerClient)); mZenModeHelper.addCallback(new ZenModeHelper.Callback() { @Override public void onConfigChanged() { @@ -2390,7 +2390,6 @@ public class NotificationManagerService extends SystemService { mNotificationChannelLogger, mAppOps, mUserProfiles, - new SysUiStatsEvent.BuilderFactory(), mShowReviewPermissionsNotification); mRankingHelper = new RankingHelper(getContext(), mRankingHandler, @@ -6678,22 +6677,14 @@ public class NotificationManagerService extends SystemService { } private PostNotificationTracker acquireWakeLockForPost(String pkg, int uid) { - if (mFlagResolver.isEnabled(WAKE_LOCK_FOR_POSTING_NOTIFICATION) - && Binder.withCleanCallingIdentity( - () -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.NOTIFY_WAKELOCK, false))) { - // The package probably doesn't have WAKE_LOCK permission and should not require it. - return Binder.withCleanCallingIdentity(() -> { - WakeLock wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, - "NotificationManagerService:post:" + pkg); - wakeLock.setWorkSource(new WorkSource(uid, pkg)); - // TODO(b/275044361): Adjust to a more reasonable number when we have the data. - wakeLock.acquire(30_000); - return mPostNotificationTrackerFactory.newTracker(wakeLock); - }); - } else { - return mPostNotificationTrackerFactory.newTracker(null); - } + // The package probably doesn't have WAKE_LOCK permission and should not require it. + return Binder.withCleanCallingIdentity(() -> { + WakeLock wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + "NotificationManagerService:post:" + pkg); + wakeLock.setWorkSource(new WorkSource(uid, pkg)); + wakeLock.acquire(POST_WAKE_LOCK_TIMEOUT.toMillis()); + return mPostNotificationTrackerFactory.newTracker(wakeLock); + }); } /** diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 0e37f101ce70..b132a23b575b 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -25,7 +25,6 @@ import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_MAX; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; -import static android.util.StatsLog.ANNOTATION_ID_IS_UID; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES; @@ -78,6 +77,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags; import com.android.internal.logging.MetricsLogger; +import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; @@ -169,7 +169,6 @@ public class PreferencesHelper implements RankingConfig { * fields. */ private static final int DEFAULT_LOCKED_APP_FIELDS = 0; - private final SysUiStatsEvent.BuilderFactory mStatsEventBuilderFactory; /** * All user-lockable fields for a given application. @@ -208,7 +207,6 @@ public class PreferencesHelper implements RankingConfig { ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager, NotificationChannelLogger notificationChannelLogger, AppOpsManager appOpsManager, ManagedServices.UserProfiles userProfiles, - SysUiStatsEvent.BuilderFactory statsEventBuilderFactory, boolean showReviewPermissionsNotification) { mContext = context; mZenModeHelper = zenHelper; @@ -219,7 +217,6 @@ public class PreferencesHelper implements RankingConfig { mNotificationChannelLogger = notificationChannelLogger; mAppOps = appOpsManager; mUserProfiles = userProfiles; - mStatsEventBuilderFactory = statsEventBuilderFactory; mShowReviewPermissionsNotification = showReviewPermissionsNotification; XML_VERSION = 4; @@ -2190,11 +2187,7 @@ public class PreferencesHelper implements RankingConfig { break; } pulledEvents++; - SysUiStatsEvent.Builder event = mStatsEventBuilderFactory.newBuilder() - .setAtomId(PACKAGE_NOTIFICATION_PREFERENCES); final PackagePreferences r = mPackagePreferences.valueAt(i); - event.writeInt(r.uid); - event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true); // collect whether this package's importance info was user-set for later, if needed // before the migration is enabled, this will simply default to false in all cases. @@ -2214,15 +2207,7 @@ public class PreferencesHelper implements RankingConfig { pkgsWithPermissionsToHandle.remove(key); } - event.writeInt(importance); - event.writeInt(r.visibility); - event.writeInt(r.lockedAppFields); - - // optional bool user_set_importance = 5; - event.writeBoolean(importanceIsUserSet); - - // optional FsiState fsi_state = 6; final boolean isStickyHunFlagEnabled = SystemUiSystemPropertiesFlags.getResolver() .isEnabled(NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI); @@ -2232,20 +2217,23 @@ public class PreferencesHelper implements RankingConfig { final int fsiState = getFsiState(r.pkg, r.uid, requestedFSIPermission, isStickyHunFlagEnabled); - event.writeInt(fsiState); - - // optional bool is_fsi_permission_user_set = 7; final int currentPermissionFlags = mPm.getPermissionFlags( android.Manifest.permission.USE_FULL_SCREEN_INTENT, r.pkg, UserHandle.getUserHandleForUid(r.uid)); - final boolean isUserSet = + final boolean fsiIsUserSet = isFsiPermissionUserSet(r.pkg, r.uid, fsiState, currentPermissionFlags, isStickyHunFlagEnabled); - event.writeBoolean(isUserSet); - - events.add(event.build()); + events.add(FrameworkStatsLog.buildStatsEvent( + PACKAGE_NOTIFICATION_PREFERENCES, + /* optional int32 uid = 1 [(is_uid) = true] */ r.uid, + /* optional int32 importance = 2 */ importance, + /* optional int32 visibility = 3 */ r.visibility, + /* optional int32 user_locked_fields = 4 */ r.lockedAppFields, + /* optional bool user_set_importance = 5 */ importanceIsUserSet, + /* optional FsiState fsi_state = 6 */ fsiState, + /* optional bool is_fsi_permission_user_set = 7 */ fsiIsUserSet)); } } @@ -2256,18 +2244,18 @@ public class PreferencesHelper implements RankingConfig { break; } pulledEvents++; - SysUiStatsEvent.Builder event = mStatsEventBuilderFactory.newBuilder() - .setAtomId(PACKAGE_NOTIFICATION_PREFERENCES); - event.writeInt(p.first); - event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true); - event.writeInt(pkgPermissions.get(p).first ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE); - - // fill out the rest of the fields with default values so as not to confuse the - // builder - event.writeInt(DEFAULT_VISIBILITY); - event.writeInt(DEFAULT_LOCKED_APP_FIELDS); - event.writeBoolean(pkgPermissions.get(p).second); // user_set_importance field - events.add(event.build()); + // Because all fields are required in FrameworkStatsLog.buildStatsEvent, we have + // to fill in default values for all the unspecified fields. + events.add(FrameworkStatsLog.buildStatsEvent( + PACKAGE_NOTIFICATION_PREFERENCES, + /* optional int32 uid = 1 [(is_uid) = true] */ p.first, + /* optional int32 importance = 2 */ pkgPermissions.get(p).first + ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE, + /* optional int32 visibility = 3 */ DEFAULT_VISIBILITY, + /* optional int32 user_locked_fields = 4 */ DEFAULT_LOCKED_APP_FIELDS, + /* optional bool user_set_importance = 5 */ pkgPermissions.get(p).second, + /* optional FsiState fsi_state = 6 */ 0, + /* optional bool is_fsi_permission_user_set = 7 */ false)); } } } @@ -2288,20 +2276,21 @@ public class PreferencesHelper implements RankingConfig { if (++totalChannelsPulled > NOTIFICATION_CHANNEL_PULL_LIMIT) { break; } - SysUiStatsEvent.Builder event = mStatsEventBuilderFactory.newBuilder() - .setAtomId(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES); - event.writeInt(r.uid); - event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true); - event.writeString(channel.getId()); - event.writeString(channel.getName().toString()); - event.writeString(channel.getDescription()); - event.writeInt(channel.getImportance()); - event.writeInt(channel.getUserLockedFields()); - event.writeBoolean(channel.isDeleted()); - event.writeBoolean(channel.getConversationId() != null); - event.writeBoolean(channel.isDemoted()); - event.writeBoolean(channel.isImportantConversation()); - events.add(event.build()); + events.add(FrameworkStatsLog.buildStatsEvent( + PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES, + /* optional int32 uid = 1 [(is_uid) = true] */ r.uid, + /* optional string channel_id = 2 */ channel.getId(), + /* optional string channel_name = 3 */ channel.getName().toString(), + /* optional string description = 4 */ channel.getDescription(), + /* optional int32 importance = 5 */ channel.getImportance(), + /* optional int32 user_locked_fields = 6 */ + channel.getUserLockedFields(), + /* optional bool is_deleted = 7 */ channel.isDeleted(), + /* optional bool is_conversation = 8 */ + channel.getConversationId() != null, + /* optional bool is_demoted_conversation = 9 */ channel.isDemoted(), + /* optional bool is_important_conversation = 10 */ + channel.isImportantConversation())); } } } @@ -2323,16 +2312,15 @@ public class PreferencesHelper implements RankingConfig { if (++totalGroupsPulled > NOTIFICATION_CHANNEL_GROUP_PULL_LIMIT) { break; } - SysUiStatsEvent.Builder event = mStatsEventBuilderFactory.newBuilder() - .setAtomId(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES); - event.writeInt(r.uid); - event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true); - event.writeString(groupChannel.getId()); - event.writeString(groupChannel.getName().toString()); - event.writeString(groupChannel.getDescription()); - event.writeBoolean(groupChannel.isBlocked()); - event.writeInt(groupChannel.getUserLockedFields()); - events.add(event.build()); + events.add(FrameworkStatsLog.buildStatsEvent( + PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES, + /* optional int32 uid = 1 [(is_uid) = true] */ r.uid, + /* optional string group_id = 2 */ groupChannel.getId(), + /* optional string group_name = 3 */ groupChannel.getName().toString(), + /* optional string description = 4 */ groupChannel.getDescription(), + /* optional bool is_blocked = 5 */ groupChannel.isBlocked(), + /* optional int32 user_locked_fields = 6 */ + groupChannel.getUserLockedFields())); } } } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 1f5bd3e0cc60..e49074573ed3 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -21,7 +21,6 @@ import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ENABLED; import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_REMOVED; import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY; import static android.service.notification.NotificationServiceProto.ROOT_CONFIG; -import static android.util.StatsLog.ANNOTATION_ID_IS_UID; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; @@ -81,6 +80,7 @@ import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; import com.android.internal.logging.MetricsLogger; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; +import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; @@ -116,7 +116,6 @@ public class ZenModeHelper { private final SettingsObserver mSettingsObserver; private final AppOpsManager mAppOps; private final NotificationManager mNotificationManager; - private final SysUiStatsEvent.BuilderFactory mStatsEventBuilderFactory; private ZenModeConfig mDefaultConfig; private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); private final ZenModeFiltering mFiltering; @@ -152,7 +151,6 @@ public class ZenModeHelper { private String[] mPriorityOnlyDndExemptPackages; public ZenModeHelper(Context context, Looper looper, ConditionProviders conditionProviders, - SysUiStatsEvent.BuilderFactory statsEventBuilderFactory, SystemUiSystemPropertiesFlags.FlagResolver flagResolver, ZenModeEventLogger zenModeEventLogger) { mContext = context; @@ -174,7 +172,6 @@ public class ZenModeHelper { mFiltering = new ZenModeFiltering(mContext); mConditions = new ZenModeConditions(this, conditionProviders); mServiceConfig = conditionProviders.getConfig(); - mStatsEventBuilderFactory = statsEventBuilderFactory; mFlagResolver = flagResolver; mZenModeEventLogger = zenModeEventLogger; } @@ -1314,17 +1311,14 @@ public class ZenModeHelper { for (int i = 0; i < numConfigs; i++) { final int user = mConfigs.keyAt(i); final ZenModeConfig config = mConfigs.valueAt(i); - SysUiStatsEvent.Builder data = mStatsEventBuilderFactory.newBuilder() - .setAtomId(DND_MODE_RULE) - .writeInt(user) - .writeBoolean(config.manualRule != null) // enabled - .writeBoolean(config.areChannelsBypassingDnd) - .writeInt(ROOT_CONFIG) - .writeString("") // name, empty for root config - .writeInt(Process.SYSTEM_UID) // system owns root config - .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) - .writeByteArray(config.toZenPolicy().toProto()); - events.add(data.build()); + events.add(FrameworkStatsLog.buildStatsEvent(DND_MODE_RULE, + /* optional int32 user = 1 */ user, + /* optional bool enabled = 2 */ config.manualRule != null, + /* optional bool channels_bypassing = 3 */ config.areChannelsBypassingDnd, + /* optional LoggedZenMode zen_mode = 4 */ ROOT_CONFIG, + /* optional string id = 5 */ "", // empty for root config + /* optional int32 uid = 6 */ Process.SYSTEM_UID, // system owns root config + /* optional DNDPolicyProto policy = 7 */ config.toZenPolicy().toProto())); if (config.manualRule != null) { ruleToProtoLocked(user, config.manualRule, true, events); } @@ -1355,21 +1349,18 @@ public class ZenModeHelper { } SysUiStatsEvent.Builder data; - data = mStatsEventBuilderFactory.newBuilder() - .setAtomId(DND_MODE_RULE) - .writeInt(user) - .writeBoolean(rule.enabled) - .writeBoolean(false) // channels_bypassing unused for rules - .writeInt(rule.zenMode) - .writeString(id) - .writeInt(getPackageUid(pkg, user)) - .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true); byte[] policyProto = new byte[]{}; if (rule.zenPolicy != null) { policyProto = rule.zenPolicy.toProto(); } - data.writeByteArray(policyProto); - events.add(data.build()); + events.add(FrameworkStatsLog.buildStatsEvent(DND_MODE_RULE, + /* optional int32 user = 1 */ user, + /* optional bool enabled = 2 */ rule.enabled, + /* optional bool channels_bypassing = 3 */ false, // unused for rules + /* optional android.stats.dnd.ZenMode zen_mode = 4 */ rule.zenMode, + /* optional string id = 5 */ id, + /* optional int32 uid = 6 */ getPackageUid(pkg, user), + /* optional DNDPolicyProto policy = 7 */ policyProto)); } private int getPackageUid(String pkg, int user) { diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index 66a170397b84..a988821fe915 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -23,9 +23,6 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; import static android.content.pm.PackageManager.DELETE_SUCCEEDED; import static android.content.pm.PackageManager.MATCH_KNOWN_PACKAGES; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.os.storage.StorageManager.FLAG_STORAGE_CE; -import static android.os.storage.StorageManager.FLAG_STORAGE_DE; -import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL; import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION; @@ -72,14 +69,13 @@ import com.android.server.wm.ActivityTaskManagerInternal; import dalvik.system.VMRuntime; -import java.util.Collections; import java.util.List; /** * Deletes a package. Uninstall if installed, or at least deletes the base directory if it's called * from a failed installation. Fixes user state after deletion. * Handles special treatments to system apps. - * Relies on RemovePackageHelper to clear internal data structures. + * Relies on RemovePackageHelper to clear internal data structures and remove app data. */ final class DeletePackageHelper { private static final boolean DEBUG_CLEAN_APKS = false; @@ -90,24 +86,17 @@ final class DeletePackageHelper { private final UserManagerInternal mUserManagerInternal; private final PermissionManagerServiceInternal mPermissionManager; private final RemovePackageHelper mRemovePackageHelper; - private final AppDataHelper mAppDataHelper; // TODO(b/198166813): remove PMS dependency - DeletePackageHelper(PackageManagerService pm, RemovePackageHelper removePackageHelper, - AppDataHelper appDataHelper) { + DeletePackageHelper(PackageManagerService pm, RemovePackageHelper removePackageHelper) { mPm = pm; mUserManagerInternal = mPm.mInjector.getUserManagerInternal(); mPermissionManager = mPm.mInjector.getPermissionManagerServiceInternal(); mRemovePackageHelper = removePackageHelper; - mAppDataHelper = appDataHelper; } DeletePackageHelper(PackageManagerService pm) { - mPm = pm; - mAppDataHelper = new AppDataHelper(mPm); - mUserManagerInternal = mPm.mInjector.getUserManagerInternal(); - mPermissionManager = mPm.mInjector.getPermissionManagerServiceInternal(); - mRemovePackageHelper = new RemovePackageHelper(mPm, mAppDataHelper); + this(pm, new RemovePackageHelper(pm)); } /** @@ -484,7 +473,7 @@ final class DeletePackageHelper { } } if (clearPackageStateAndReturn) { - clearPackageStateForUserLIF(ps, userId, outInfo, flags); + mRemovePackageHelper.clearPackageStateForUserLIF(ps, userId, outInfo, flags); mPm.scheduleWritePackageRestrictions(user); return; } @@ -531,55 +520,6 @@ final class DeletePackageHelper { } } - private void clearPackageStateForUserLIF(PackageSetting ps, int userId, - PackageRemovedInfo outInfo, int flags) { - final AndroidPackage pkg; - final SharedUserSetting sus; - synchronized (mPm.mLock) { - pkg = mPm.mPackages.get(ps.getPackageName()); - sus = mPm.mSettings.getSharedUserSettingLPr(ps); - } - - mAppDataHelper.destroyAppProfilesLIF(pkg); - - final List<AndroidPackage> sharedUserPkgs = - sus != null ? sus.getPackages() : Collections.emptyList(); - final PreferredActivityHelper preferredActivityHelper = new PreferredActivityHelper(mPm); - final int[] userIds = (userId == UserHandle.USER_ALL) ? mUserManagerInternal.getUserIds() - : new int[] {userId}; - for (int nextUserId : userIds) { - if (DEBUG_REMOVE) { - Slog.d(TAG, "Updating package:" + ps.getPackageName() + " install state for user:" - + nextUserId); - } - if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) { - mAppDataHelper.destroyAppDataLIF(pkg, nextUserId, - FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL); - ps.setCeDataInode(-1, nextUserId); - } - mAppDataHelper.clearKeystoreData(nextUserId, ps.getAppId()); - preferredActivityHelper.clearPackagePreferredActivities(ps.getPackageName(), - nextUserId); - mPm.mDomainVerificationManager.clearPackageForUser(ps.getPackageName(), nextUserId); - } - mPermissionManager.onPackageUninstalled(ps.getPackageName(), ps.getAppId(), ps, pkg, - sharedUserPkgs, userId); - - if (outInfo != null) { - if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) { - outInfo.mDataRemoved = true; - } - outInfo.mRemovedPackage = ps.getPackageName(); - outInfo.mInstallerPackageName = ps.getInstallSource().mInstallerPackageName; - outInfo.mIsStaticSharedLib = pkg != null && pkg.getStaticSharedLibraryName() != null; - outInfo.mRemovedAppId = ps.getAppId(); - outInfo.mRemovedUsers = userIds; - outInfo.mBroadcastUsers = userIds; - outInfo.mIsExternal = ps.isExternalStorage(); - outInfo.mRemovedPackageVersionCode = ps.getVersionCode(); - } - } - @GuardedBy("mPm.mInstallLock") private void deleteInstalledPackageLIF(PackageSetting ps, boolean deleteCodeAndResources, int flags, @NonNull int[] allUserHandles, diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java index 76203ac7650d..8f7b7217d005 100644 --- a/services/core/java/com/android/server/pm/IPackageManagerBase.java +++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java @@ -30,6 +30,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageArchiverService; import android.content.pm.IPackageDeleteObserver; import android.content.pm.IPackageDeleteObserver2; import android.content.pm.IPackageInstaller; @@ -99,6 +100,9 @@ public abstract class IPackageManagerBase extends IPackageManager.Stub { private final PackageInstallerService mInstallerService; @NonNull + private final PackageArchiverService mPackageArchiverService; + + @NonNull private final PackageProperty mPackageProperty; @NonNull @@ -127,7 +131,8 @@ public abstract class IPackageManagerBase extends IPackageManager.Stub { @Nullable ComponentName instantAppResolverSettingsComponent, @NonNull String requiredSupplementalProcessPackage, @Nullable String servicesExtensionPackageName, - @Nullable String sharedSystemSharedLibraryPackageName) { + @Nullable String sharedSystemSharedLibraryPackageName, + @NonNull PackageArchiverService packageArchiverService) { mService = service; mContext = context; mDexOptHelper = dexOptHelper; @@ -143,6 +148,7 @@ public abstract class IPackageManagerBase extends IPackageManager.Stub { mRequiredSupplementalProcessPackage = requiredSupplementalProcessPackage; mServicesExtensionPackageName = servicesExtensionPackageName; mSharedSystemSharedLibraryPackageName = sharedSystemSharedLibraryPackageName; + mPackageArchiverService = packageArchiverService; } protected Computer snapshot() { @@ -616,6 +622,12 @@ public abstract class IPackageManagerBase extends IPackageManager.Stub { @Override @Deprecated + public final IPackageArchiverService getPackageArchiverService() { + return mPackageArchiverService; + } + + @Override + @Deprecated public final void getPackageSizeInfo(final String packageName, int userId, final IPackageStatsObserver observer) { throw new UnsupportedOperationException( diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 9ac983dfebeb..6233c9bc4dad 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -29,6 +29,7 @@ import android.os.CreateAppDataArgs; import android.os.CreateAppDataResult; import android.os.IBinder; import android.os.IInstalld; +import android.os.ParcelFileDescriptor; import android.os.ReconcileSdkDataArgs; import android.os.RemoteException; import android.os.ServiceManager; @@ -1161,6 +1162,55 @@ public class Installer extends SystemService { } } + /** + * Returns an auth token for the provided writable FD. + * + * @param authFd a file descriptor to proof that the caller can write to the file. + * @param appUid uid of the calling app. + * @param userId id of the user whose app file to enable fs-verity. + * + * @return authToken, or null if a remote call shouldn't be continued. See {@link + * #checkBeforeRemote}. + * + * @throws InstallerException if the remote call failed. + */ + public IInstalld.IFsveritySetupAuthToken createFsveritySetupAuthToken( + ParcelFileDescriptor authFd, int appUid, @UserIdInt int userId) + throws InstallerException { + if (!checkBeforeRemote()) { + return null; + } + try { + return mInstalld.createFsveritySetupAuthToken(authFd, appUid, userId); + } catch (Exception e) { + throw InstallerException.from(e); + } + } + + /** + * Enables fs-verity to the given app file. + * + * @param authToken a token previously returned from {@link #createFsveritySetupAuthToken}. + * @param filePath file path of the package to enable fs-verity. + * @param packageName name of the package. + * + * @return 0 if the operation was successful, otherwise {@code errno}. + * + * @throws InstallerException if the remote call failed (e.g. see {@link #checkBeforeRemote}). + */ + public int enableFsverity(IInstalld.IFsveritySetupAuthToken authToken, String filePath, + String packageName) throws InstallerException { + if (!checkBeforeRemote()) { + throw new InstallerException("fs-verity wasn't enabled with an isolated installer"); + } + BlockGuard.getVmPolicy().onPathAccess(filePath); + try { + return mInstalld.enableFsverity(authToken, filePath, packageName); + } catch (Exception e) { + throw InstallerException.from(e); + } + } + public static class InstallerException extends Exception { public InstallerException(String detailMessage) { super(detailMessage); diff --git a/services/core/java/com/android/server/pm/ArchiveManager.java b/services/core/java/com/android/server/pm/PackageArchiverService.java index 54352060cd38..9c31dc9a1215 100644 --- a/services/core/java/com/android/server/pm/ArchiveManager.java +++ b/services/core/java/com/android/server/pm/PackageArchiverService.java @@ -22,11 +22,13 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.IntentSender; +import android.content.pm.IPackageArchiverService; import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; import android.os.Binder; +import android.os.ParcelableException; import android.os.UserHandle; import android.text.TextUtils; @@ -47,7 +49,9 @@ import java.util.Objects; * while the data directory is kept. Archived apps are included in the list of launcher apps where * tapping them re-installs the full app. */ -final class ArchiveManager { +public class PackageArchiverService extends IPackageArchiverService.Stub { + + private static final String TAG = "PackageArchiver"; private final Context mContext; private final PackageManagerService mPm; @@ -55,36 +59,39 @@ final class ArchiveManager { @Nullable private LauncherApps mLauncherApps; - ArchiveManager(Context context, PackageManagerService mPm) { + public PackageArchiverService(Context context, PackageManagerService mPm) { this.mContext = context; this.mPm = mPm; } - void archiveApp( + @Override + public void requestArchive( @NonNull String packageName, @NonNull String callerPackageName, - @NonNull UserHandle user, - @NonNull IntentSender intentSender) throws PackageManager.NameNotFoundException { + @NonNull IntentSender intentSender, + @NonNull UserHandle userHandle) { Objects.requireNonNull(packageName); Objects.requireNonNull(callerPackageName); - Objects.requireNonNull(user); Objects.requireNonNull(intentSender); + Objects.requireNonNull(userHandle); Computer snapshot = mPm.snapshotComputer(); - int callingUid = Binder.getCallingUid(); - int userId = user.getIdentifier(); - String callingPackageName = snapshot.getNameForUid(callingUid); - snapshot.enforceCrossUserPermission(callingUid, userId, true, true, + int userId = userHandle.getIdentifier(); + int binderUid = Binder.getCallingUid(); + int providedUid = snapshot.getPackageUid(callerPackageName, 0, userId); + snapshot.enforceCrossUserPermission(binderUid, userId, true, true, "archiveApp"); - verifyCaller(callerPackageName, callingPackageName); - PackageStateInternal ps = getPackageState(packageName, snapshot, callingUid, user); - verifyInstaller(packageName, ps.getInstallSource()); + verifyCaller(providedUid, binderUid); + PackageStateInternal ps = getPackageState(packageName, snapshot, binderUid, userId); + verifyInstaller(packageName, ps); + // TODO(b/291569242) Verify that this list is not empty and return failure with + // intentsender List<LauncherActivityInfo> mainActivities = getLauncherApps().getActivityList( ps.getPackageName(), new UserHandle(userId)); - // TODO(b/291569242) Verify that this list is not empty and return failure with intentsender + // TODO(b/282952870) Bug: should happen after the uninstall completes successfully storeArchiveState(ps, mainActivities, userId); // TODO(b/278553670) Add special strings for the delete dialog @@ -93,15 +100,25 @@ final class ArchiveManager { callerPackageName, DELETE_KEEP_DATA, intentSender, userId); } + private static void verifyInstaller(String packageName, PackageStateInternal ps) { + if (ps.getInstallSource().mUpdateOwnerPackageName == null + && ps.getInstallSource().mInstallerPackageName == null) { + throw new ParcelableException( + new PackageManager.NameNotFoundException( + TextUtils.formatSimple("No installer found to archive app %s.", + packageName))); + } + } + @NonNull private static PackageStateInternal getPackageState(String packageName, - Computer snapshot, int callingUid, UserHandle user) - throws PackageManager.NameNotFoundException { + Computer snapshot, int callingUid, int userId) { PackageStateInternal ps = snapshot.getPackageStateFiltered(packageName, callingUid, - user.getIdentifier()); + userId); if (ps == null) { - throw new PackageManager.NameNotFoundException( - TextUtils.formatSimple("Package %s not found.", packageName)); + throw new ParcelableException( + new PackageManager.NameNotFoundException( + TextUtils.formatSimple("Package %s not found.", packageName))); } return ps; } @@ -114,8 +131,7 @@ final class ArchiveManager { } private void storeArchiveState(PackageStateInternal ps, - List<LauncherActivityInfo> mainActivities, int userId) - throws PackageManager.NameNotFoundException { + List<LauncherActivityInfo> mainActivities, int userId) { List<ArchiveActivityInfo> activityInfos = new ArrayList<>(); for (int i = 0; i < mainActivities.size(); i++) { // TODO(b/278553670) Extract and store launcher icons @@ -130,41 +146,34 @@ final class ArchiveManager { ? installSource.mUpdateOwnerPackageName : installSource.mInstallerPackageName; synchronized (mPm.mLock) { - getPackageSetting(ps.getPackageName(), userId).modifyUserState(userId).setArchiveState( - new ArchiveState(activityInfos, installerPackageName)); + PackageSetting packageSetting = getPackageSettingLocked(ps.getPackageName(), userId); + packageSetting + .modifyUserState(userId) + .setArchiveState(new ArchiveState(activityInfos, installerPackageName)); } } @NonNull @GuardedBy("mPm.mLock") - private PackageSetting getPackageSetting(String packageName, int userId) - throws PackageManager.NameNotFoundException { + private PackageSetting getPackageSettingLocked(String packageName, int userId) { PackageSetting ps = mPm.mSettings.getPackageLPr(packageName); + // Shouldn't happen, we already verify presence of the package in getPackageState() if (ps == null || !ps.getUserStateOrDefault(userId).isInstalled()) { - throw new PackageManager.NameNotFoundException( - TextUtils.formatSimple("Package %s not found.", packageName)); + throw new ParcelableException( + new PackageManager.NameNotFoundException( + TextUtils.formatSimple("Package %s not found.", packageName))); } return ps; } - private static void verifyCaller(String callerPackageName, String callingPackageName) { - if (!TextUtils.equals(callingPackageName, callerPackageName)) { + private static void verifyCaller(int providedUid, int binderUid) { + if (providedUid != binderUid) { throw new SecurityException( TextUtils.formatSimple( - "The callerPackageName %s set by the caller doesn't match the " - + "caller's own package name %s.", - callerPackageName, - callingPackageName)); - } - } - - private static void verifyInstaller(String packageName, InstallSource installSource) { - // TODO(b/291060290) Verify installer supports unarchiving - if (installSource.mUpdateOwnerPackageName == null - && installSource.mInstallerPackageName == null) { - throw new SecurityException( - TextUtils.formatSimple("No installer found to archive app %s.", - packageName)); + "The UID %s of callerPackageName set by the caller doesn't match the " + + "caller's actual UID %s.", + providedUid, + binderUid)); } } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 7ccf713a7e06..ac7842948639 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -796,6 +796,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService final PackageInstallerService mInstallerService; + final PackageArchiverService mArchiverService; + final ArtManagerService mArtManagerService; // TODO(b/260124949): Remove these. @@ -1624,7 +1626,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService (i, pm) -> new CrossProfileIntentFilterHelper(i.getSettings(), i.getUserManagerService(), i.getLock(), i.getUserManagerInternal(), context), - (i, pm) -> new UpdateOwnershipHelper()); + (i, pm) -> new UpdateOwnershipHelper(), + (i, pm) -> new PackageArchiverService(i.getContext(), pm)); if (Build.VERSION.SDK_INT <= 0) { Slog.w(TAG, "**** ro.build.version.sdk not set!"); @@ -1769,6 +1772,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService mFactoryTest = testParams.factoryTest; mIncrementalManager = testParams.incrementalManager; mInstallerService = testParams.installerService; + mArchiverService = testParams.archiverService; mInstantAppRegistry = testParams.instantAppRegistry; mChangedPackagesTracker = testParams.changedPackagesTracker; mInstantAppResolverConnection = testParams.instantAppResolverConnection; @@ -1967,9 +1971,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService mApexManager = injector.getApexManager(); mAppsFilter = mInjector.getAppsFilter(); - mInstantAppRegistry = new InstantAppRegistry(mContext, mPermissionManager, - mInjector.getUserManagerInternal(), new DeletePackageHelper(this)); - mChangedPackagesTracker = new ChangedPackagesTracker(); mAppInstallDir = new File(Environment.getDataDirectory(), "app"); @@ -1983,8 +1984,11 @@ public class PackageManagerService implements PackageSender, TestUtilityService mAppDataHelper = new AppDataHelper(this); mInstallPackageHelper = new InstallPackageHelper(this, mAppDataHelper); mRemovePackageHelper = new RemovePackageHelper(this, mAppDataHelper); - mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper, - mAppDataHelper); + mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper); + + mInstantAppRegistry = new InstantAppRegistry(mContext, mPermissionManager, + mInjector.getUserManagerInternal(), mDeletePackageHelper); + mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper); mPreferredActivityHelper = new PreferredActivityHelper(this); mResolveIntentHelper = new ResolveIntentHelper(mContext, mPreferredActivityHelper, @@ -2348,6 +2352,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService }); mInstallerService = mInjector.getPackageInstallerService(); + mArchiverService = mInjector.getPackageArchiverService(); final ComponentName instantAppResolverComponent = getInstantAppResolver(computer); if (instantAppResolverComponent != null) { if (DEBUG_INSTANT) { @@ -4608,7 +4613,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService mDomainVerificationConnection, mInstallerService, mPackageProperty, mResolveComponentName, mInstantAppResolverSettingsComponent, mRequiredSdkSandboxPackage, mServicesExtensionPackageName, - mSharedSystemSharedLibraryPackageName); + mSharedSystemSharedLibraryPackageName, mArchiverService); } @Override diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java index 51840e70ed26..9495279c65b8 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java @@ -127,6 +127,8 @@ public class PackageManagerServiceInjector { mPreparingPackageParserProducer; private final Singleton<PackageInstallerService> mPackageInstallerServiceProducer; + private final Singleton<PackageArchiverService> + mPackageArchiverServiceProducer; private final ProducerWithArgument<InstantAppResolverConnection, ComponentName> mInstantAppResolverConnectionProducer; private final Singleton<LegacyPermissionManagerInternal> @@ -185,7 +187,8 @@ public class PackageManagerServiceInjector { Producer<IBackupManager> iBackupManager, Producer<SharedLibrariesImpl> sharedLibrariesProducer, Producer<CrossProfileIntentFilterHelper> crossProfileIntentFilterHelperProducer, - Producer<UpdateOwnershipHelper> updateOwnershipHelperProducer) { + Producer<UpdateOwnershipHelper> updateOwnershipHelperProducer, + Producer<PackageArchiverService> packageArchiverServiceProducer) { mContext = context; mLock = lock; mInstaller = installer; @@ -241,6 +244,7 @@ public class PackageManagerServiceInjector { mCrossProfileIntentFilterHelperProducer = new Singleton<>( crossProfileIntentFilterHelperProducer); mUpdateOwnershipHelperProducer = new Singleton<>(updateOwnershipHelperProducer); + mPackageArchiverServiceProducer = new Singleton<>(packageArchiverServiceProducer); } /** @@ -387,6 +391,10 @@ public class PackageManagerServiceInjector { return mPackageInstallerServiceProducer.get(this, mPackageManager); } + public PackageArchiverService getPackageArchiverService() { + return mPackageArchiverServiceProducer.get(this, mPackageManager); + } + public InstantAppResolverConnection getInstantAppResolverConnection( ComponentName instantAppResolverComponent) { return mInstantAppResolverConnectionProducer.produce( diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java index ca572091486e..b91ce4be8ca1 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java @@ -60,6 +60,7 @@ public final class PackageManagerServiceTestParams { public @Nullable String incidentReportApproverPackage; public IncrementalManager incrementalManager; public PackageInstallerService installerService; + public PackageArchiverService archiverService; public InstantAppRegistry instantAppRegistry; public ChangedPackagesTracker changedPackagesTracker = new ChangedPackagesTracker(); public InstantAppResolverConnection instantAppResolverConnection; diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index 6d3b26cc2fd4..d4f30fed6967 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -253,6 +253,56 @@ final class RemovePackageHelper { } } + public void clearPackageStateForUserLIF(PackageSetting ps, int userId, + PackageRemovedInfo outInfo, int flags) { + final AndroidPackage pkg; + final SharedUserSetting sus; + synchronized (mPm.mLock) { + pkg = mPm.mPackages.get(ps.getPackageName()); + sus = mPm.mSettings.getSharedUserSettingLPr(ps); + } + + mAppDataHelper.destroyAppProfilesLIF(pkg); + + final List<AndroidPackage> sharedUserPkgs = + sus != null ? sus.getPackages() : Collections.emptyList(); + final PreferredActivityHelper preferredActivityHelper = new PreferredActivityHelper(mPm); + final int[] userIds = (userId == UserHandle.USER_ALL) ? mUserManagerInternal.getUserIds() + : new int[] {userId}; + for (int nextUserId : userIds) { + if (DEBUG_REMOVE) { + Slog.d(TAG, "Updating package:" + ps.getPackageName() + " install state for user:" + + nextUserId); + } + if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) { + mAppDataHelper.destroyAppDataLIF(pkg, nextUserId, + FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL); + ps.setCeDataInode(-1, nextUserId); + } + mAppDataHelper.clearKeystoreData(nextUserId, ps.getAppId()); + preferredActivityHelper.clearPackagePreferredActivities(ps.getPackageName(), + nextUserId); + mPm.mDomainVerificationManager.clearPackageForUser(ps.getPackageName(), nextUserId); + } + mPermissionManager.onPackageUninstalled(ps.getPackageName(), ps.getAppId(), ps, pkg, + sharedUserPkgs, userId); + + if (outInfo != null) { + if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) { + outInfo.mDataRemoved = true; + } + outInfo.mRemovedPackage = ps.getPackageName(); + outInfo.mInstallerPackageName = ps.getInstallSource().mInstallerPackageName; + outInfo.mIsStaticSharedLib = pkg != null && pkg.getStaticSharedLibraryName() != null; + outInfo.mRemovedAppId = ps.getAppId(); + outInfo.mRemovedUsers = userIds; + outInfo.mBroadcastUsers = userIds; + outInfo.mIsExternal = ps.isExternalStorage(); + outInfo.mRemovedPackageVersionCode = ps.getVersionCode(); + } + } + + // Called to clean up disabled system packages public void removePackageData(final PackageSetting deletedPs, @NonNull int[] allUserHandles, PackageRemovedInfo outInfo, int flags, boolean writeSettings) { synchronized (mPm.mInstallLock) { @@ -314,7 +364,6 @@ final class RemovePackageHelper { int removedAppId = -1; // writer - boolean installedStateChanged = false; if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) { final SparseBooleanArray changedUsers = new SparseBooleanArray(); synchronized (mPm.mLock) { @@ -354,9 +403,10 @@ final class RemovePackageHelper { mPm.postPreferredActivityChangedBroadcast(UserHandle.USER_ALL); } } - // make sure to preserve per-user disabled state if this removal was just + // make sure to preserve per-user installed state if this removal was just // a downgrade of a system app to the factory package - if (outInfo != null && outInfo.mOrigUsers != null) { + boolean installedStateChanged = false; + if (outInfo != null && outInfo.mOrigUsers != null && deletedPs.isSystem()) { if (DEBUG_REMOVE) { Slog.d(TAG, "Propagating install state across downgrade"); } diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index dd434fbeecb4..3e4dd1637387 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -3611,8 +3611,8 @@ public class ShortcutService extends IShortcutService.Stub { // Otherwise check persisted shortcuts getShortcutInfoAsync(launcherUserId, packageName, shortcutId, userId, si -> { - cb.complete(getShortcutIconUriInternal(launcherUserId, launcherPackage, - packageName, si, userId)); + cb.complete(si == null ? null : getShortcutIconUriInternal(launcherUserId, + launcherPackage, packageName, si, userId)); }); } diff --git a/services/core/java/com/android/server/pm/permission/TEST_MAPPING b/services/core/java/com/android/server/pm/permission/TEST_MAPPING index b2dcf379fe7d..24323c8bfbde 100644 --- a/services/core/java/com/android/server/pm/permission/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/permission/TEST_MAPPING @@ -4,7 +4,7 @@ "name": "CtsPermissionTestCases", "options": [ { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" + "exclude-annotation": "androidx.test.filters.FlakyTest" }, { "include-filter": "android.permission.cts.BackgroundPermissionsTest" @@ -32,7 +32,7 @@ "name": "CtsPermissionPolicyTestCases", "options": [ { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" + "exclude-annotation": "androidx.test.filters.FlakyTest" }, { "include-filter": "android.permissionpolicy.cts.RestrictedPermissionsTest" diff --git a/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java b/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java index a2177e87bcdb..0bb969f488fe 100644 --- a/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java +++ b/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java @@ -17,13 +17,13 @@ package com.android.server.pm.split; import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.PackageLite; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils.ParseFlags; import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.os.Build; import com.android.internal.util.ArrayUtils; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils.ParseFlags; import libcore.io.IoUtils; @@ -82,8 +82,8 @@ public class DefaultSplitAssetLoader implements SplitAssetLoader { } AssetManager assets = new AssetManager(); - assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Build.VERSION.RESOURCES_SDK_INT); + assets.setConfiguration(0, 0, null, new String[0], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, Build.VERSION.RESOURCES_SDK_INT); assets.setApkAssets(apkAssets, false /*invalidateCaches*/); mCachedAssetManager = assets; diff --git a/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java b/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java index 1a8c199608df..56d92fbc95a2 100644 --- a/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java +++ b/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java @@ -80,8 +80,8 @@ public class SplitAssetDependencyLoader extends SplitDependencyLoader<IllegalArg private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) { final AssetManager assets = new AssetManager(); - assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Build.VERSION.RESOURCES_SDK_INT); + assets.setConfiguration(0, 0, null, new String[0], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, Build.VERSION.RESOURCES_SDK_INT); assets.setApkAssets(apkAssets, false /*invalidateCaches*/); return assets; } diff --git a/services/core/java/com/android/server/recoverysystem/hal/BootControlHIDL.java b/services/core/java/com/android/server/recoverysystem/hal/BootControlHIDL.java index 65325c297719..7c4d7875b76f 100644 --- a/services/core/java/com/android/server/recoverysystem/hal/BootControlHIDL.java +++ b/services/core/java/com/android/server/recoverysystem/hal/BootControlHIDL.java @@ -22,6 +22,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import java.util.NoSuchElementException; + public class BootControlHIDL implements IBootControl { private static final String TAG = "BootControlHIDL"; @@ -32,7 +34,7 @@ public class BootControlHIDL implements IBootControl { public static boolean isServicePresent() { try { android.hardware.boot.V1_0.IBootControl.getService(true); - } catch (RemoteException e) { + } catch (RemoteException | NoSuchElementException e) { return false; } return true; @@ -41,7 +43,7 @@ public class BootControlHIDL implements IBootControl { public static boolean isV1_2ServicePresent() { try { android.hardware.boot.V1_2.IBootControl.getService(true); - } catch (RemoteException e) { + } catch (RemoteException | NoSuchElementException e) { return false; } return true; diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java index 95296210be80..3aed6e30cf87 100644 --- a/services/core/java/com/android/server/security/FileIntegrityService.java +++ b/services/core/java/com/android/server/security/FileIntegrityService.java @@ -27,10 +27,12 @@ import android.os.Build; import android.os.Environment; import android.os.IBinder; import android.os.ParcelFileDescriptor; +import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.ShellCommand; import android.os.UserHandle; +import android.os.storage.StorageManagerInternal; import android.security.IFileIntegrityService; import android.util.Slog; @@ -54,6 +56,7 @@ import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; +import java.util.Objects; /** * A {@link SystemService} that provides file integrity related operations. @@ -112,7 +115,7 @@ public class FileIntegrityService extends SystemService { .exec(this, in, out, err, args, callback, resultReceiver); } - private void checkCallerPermission(String packageName) { + private void checkCallerPackageName(String packageName) { final int callingUid = Binder.getCallingUid(); final int callingUserId = UserHandle.getUserId(callingUid); final PackageManagerInternal packageManager = @@ -123,7 +126,10 @@ public class FileIntegrityService extends SystemService { throw new SecurityException( "Calling uid " + callingUid + " does not own package " + packageName); } + } + private void checkCallerPermission(String packageName) { + checkCallerPackageName(packageName); if (getContext().checkCallingPermission(android.Manifest.permission.INSTALL_PACKAGES) == PackageManager.PERMISSION_GRANTED) { return; @@ -131,12 +137,43 @@ public class FileIntegrityService extends SystemService { final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class); final int mode = appOpsManager.checkOpNoThrow( - AppOpsManager.OP_REQUEST_INSTALL_PACKAGES, callingUid, packageName); + AppOpsManager.OP_REQUEST_INSTALL_PACKAGES, Binder.getCallingUid(), packageName); if (mode != AppOpsManager.MODE_ALLOWED) { throw new SecurityException( "Caller should have INSTALL_PACKAGES or REQUEST_INSTALL_PACKAGES"); } } + + @Override + public android.os.IInstalld.IFsveritySetupAuthToken createAuthToken( + ParcelFileDescriptor authFd) throws RemoteException { + Objects.requireNonNull(authFd); + try { + var authToken = getStorageManagerInternal().createFsveritySetupAuthToken(authFd, + Binder.getCallingUid(), Binder.getCallingUserHandle().getIdentifier()); + // fs-verity setup requires no writable fd to the file. Release the dup now that + // it's passed. + authFd.close(); + return authToken; + } catch (IOException e) { + throw new RemoteException(e); + } + } + + @Override + public int setupFsverity(android.os.IInstalld.IFsveritySetupAuthToken authToken, + String filePath, String packageName) throws RemoteException { + Objects.requireNonNull(authToken); + Objects.requireNonNull(filePath); + Objects.requireNonNull(packageName); + checkCallerPackageName(packageName); + + try { + return getStorageManagerInternal().enableFsverity(authToken, filePath, packageName); + } catch (IOException e) { + throw new RemoteException(e); + } + } }; public FileIntegrityService(final Context context) { @@ -146,9 +183,19 @@ public class FileIntegrityService extends SystemService { } catch (CertificateException e) { Slog.wtf(TAG, "Cannot get an instance of X.509 certificate factory"); } + LocalServices.addService(FileIntegrityService.class, this); } + /** + * Returns StorageManagerInternal as a proxy to fs-verity related calls. This is to plumb + * the call through the canonical Installer instance in StorageManagerService, since the + * Installer instance isn't directly accessible. + */ + private StorageManagerInternal getStorageManagerInternal() { + return LocalServices.getService(StorageManagerInternal.class); + } + @Override public void onStart() { loadAllCertificates(); diff --git a/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java b/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java index f744d00b2066..565eb6e85559 100644 --- a/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java +++ b/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java @@ -18,7 +18,6 @@ package com.android.server.sensorprivacy; import static android.hardware.SensorManager.SENSOR_DELAY_NORMAL; -import android.annotation.ColorInt; import android.app.AppOpsManager; import android.content.Context; import android.hardware.Sensor; @@ -39,6 +38,7 @@ import android.util.Pair; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; import com.android.server.FgThread; import java.util.ArrayDeque; @@ -48,12 +48,10 @@ import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; - class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedListener, SensorEventListener { - @VisibleForTesting - static final double LIGHT_VALUE_MULTIPLIER = 1 / Math.log(1.1); + private static final double LIGHT_VALUE_MULTIPLIER = 1 / Math.log(1.1); private final Handler mHandler; private final Executor mExecutor; @@ -69,11 +67,6 @@ class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedLis private LightsManager.LightsSession mLightsSession = null; - @ColorInt - private final int mDayColor; - @ColorInt - private final int mNightColor; - private final Sensor mLightSensor; private boolean mIsAmbientLightListenerRegistered = false; @@ -81,7 +74,9 @@ class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedLis /** When average of the time integral over the past {@link #mMovingAverageIntervalMillis} * milliseconds of the log_1.1(lux(t)) is greater than this value, use the daytime brightness * else use nighttime brightness. */ - private final long mNightThreshold; + private final long[] mThresholds; + + private final int[] mColors; private final ArrayDeque<Pair<Long, Integer>> mAmbientLightValues = new ArrayDeque<>(); /** Tracks the Riemann sum of {@link #mAmbientLightValues} to avoid O(n) operations when sum is * needed */ @@ -101,6 +96,20 @@ class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedLis @VisibleForTesting CameraPrivacyLightController(Context context, Looper looper) { + mColors = context.getResources().getIntArray(R.array.config_cameraPrivacyLightColors); + if (ArrayUtils.isEmpty(mColors)) { + mHandler = null; + mExecutor = null; + mContext = null; + mAppOpsManager = null; + mLightsManager = null; + mSensorManager = null; + mLightSensor = null; + mMovingAverageIntervalMillis = 0; + mThresholds = null; + // Return here before this class starts interacting with other services. + return; + } mContext = context; mHandler = new Handler(looper); @@ -109,14 +118,20 @@ class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedLis mAppOpsManager = mContext.getSystemService(AppOpsManager.class); mLightsManager = mContext.getSystemService(LightsManager.class); mSensorManager = mContext.getSystemService(SensorManager.class); - - mDayColor = mContext.getColor(R.color.camera_privacy_light_day); - mNightColor = mContext.getColor(R.color.camera_privacy_light_night); mMovingAverageIntervalMillis = mContext.getResources() .getInteger(R.integer.config_cameraPrivacyLightAlsAveragingIntervalMillis); - mNightThreshold = (long) (Math.log(mContext.getResources() - .getInteger(R.integer.config_cameraPrivacyLightAlsNightThreshold)) - * LIGHT_VALUE_MULTIPLIER); + int[] thresholdsLux = mContext.getResources().getIntArray( + R.array.config_cameraPrivacyLightAlsLuxThresholds); + if (thresholdsLux.length != mColors.length - 1) { + throw new IllegalStateException("There must be exactly one more color than thresholds." + + " Found " + mColors.length + " colors and " + thresholdsLux.length + + " thresholds."); + } + mThresholds = new long[thresholdsLux.length]; + for (int i = 0; i < thresholdsLux.length; i++) { + int luxValue = thresholdsLux[i]; + mThresholds[i] = (long) (Math.log(luxValue) * LIGHT_VALUE_MULTIPLIER); + } List<Light> lights = mLightsManager.getLights(); for (int i = 0; i < lights.size(); i++) { @@ -223,13 +238,8 @@ class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedLis mLightsSession.close(); mLightsSession = null; } else { - int lightColor; - if (mLightSensor != null && getLiveAmbientLightTotal() - < getCurrentIntervalMillis() * mNightThreshold) { - lightColor = mNightColor; - } else { - lightColor = mDayColor; - } + int lightColor = + mLightSensor == null ? mColors[mColors.length - 1] : computeCurrentLightColor(); if (mLastLightColor == lightColor && mLightsSession != null) { return; @@ -252,6 +262,18 @@ class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedLis } } + private int computeCurrentLightColor() { + long liveAmbientLightTotal = getLiveAmbientLightTotal(); + long currentInterval = getCurrentIntervalMillis(); + + for (int i = 0; i < mThresholds.length; i++) { + if (liveAmbientLightTotal < currentInterval * mThresholds[i]) { + return mColors[i]; + } + } + return mColors[mColors.length - 1]; + } + private void updateSensorListener(boolean shouldSessionEnd) { if (shouldSessionEnd && mIsAmbientLightListenerRegistered) { mSensorManager.unregisterListener(this); diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 7ea9870b41c5..234e3f4c4b54 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -41,6 +41,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; +import android.content.res.Resources; import android.graphics.Rect; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; @@ -94,6 +95,7 @@ import android.util.SparseArray; import android.view.InputChannel; import android.view.Surface; +import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; @@ -177,6 +179,11 @@ public final class TvInputManagerService extends SystemService { private final ActivityManager mActivityManager; + private boolean mExternalInputLoggingDisplayNameFilterEnabled = false; + private final HashSet<String> mExternalInputLoggingDeviceOnScreenDisplayNames = + new HashSet<String>(); + private final List<String> mExternalInputLoggingDeviceBrandNames = new ArrayList<String>(); + public TvInputManagerService(Context context) { super(context); @@ -192,6 +199,8 @@ public final class TvInputManagerService extends SystemService { synchronized (mLock) { getOrCreateUserStateLocked(mCurrentUserId); } + + initExternalInputLoggingConfigs(); } @Override @@ -224,6 +233,21 @@ public final class TvInputManagerService extends SystemService { } } + private void initExternalInputLoggingConfigs() { + mExternalInputLoggingDisplayNameFilterEnabled = mContext.getResources().getBoolean( + R.bool.config_tvExternalInputLoggingDisplayNameFilterEnabled); + if (!mExternalInputLoggingDisplayNameFilterEnabled) { + return; + } + final String[] deviceOnScreenDisplayNames = mContext.getResources().getStringArray( + R.array.config_tvExternalInputLoggingDeviceOnScreenDisplayNames); + final String[] deviceBrandNames = mContext.getResources().getStringArray( + R.array.config_tvExternalInputLoggingDeviceBrandNames); + mExternalInputLoggingDeviceOnScreenDisplayNames.addAll( + Arrays.asList(deviceOnScreenDisplayNames)); + mExternalInputLoggingDeviceBrandNames.addAll(Arrays.asList(deviceBrandNames)); + } + private void registerBroadcastReceivers() { PackageMonitor monitor = new PackageMonitor() { private void buildTvInputList(String[] packages) { @@ -3073,13 +3097,32 @@ public final class TvInputManagerService extends SystemService { hdmiPort = hdmiDeviceInfo.getPortId(); if (hdmiDeviceInfo.isCecDevice()) { displayName = hdmiDeviceInfo.getDisplayName(); + if (mExternalInputLoggingDisplayNameFilterEnabled) { + displayName = filterExternalInputLoggingDisplayName(displayName); + } vendorId = hdmiDeviceInfo.getVendorId(); } } } FrameworkStatsLog.write(FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT, eventType, inputState, - inputType, displayName, vendorId, hdmiPort, tifSessionId); + inputType, vendorId, hdmiPort, tifSessionId, displayName); + } + + private String filterExternalInputLoggingDisplayName(String displayName) { + String nullDisplayName = "NULL_DISPLAY_NAME", filteredDisplayName = "FILTERED_DISPLAY_NAME"; + if (displayName == null) { + return nullDisplayName; + } + if (mExternalInputLoggingDeviceOnScreenDisplayNames.contains(displayName)) { + return displayName; + } + for (String brandName : mExternalInputLoggingDeviceBrandNames) { + if (displayName.toUpperCase().contains(brandName.toUpperCase())) { + return brandName; + } + } + return filteredDisplayName; } private static final class UserState { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 9eec5f8c4bb6..60854885d5bb 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -225,7 +225,6 @@ import static com.android.server.wm.IdentifierProto.USER_ID; import static com.android.server.wm.LetterboxConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW; import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO; import static com.android.server.wm.StartingData.AFTER_TRANSACTION_COPY_TO_CLIENT; -import static com.android.server.wm.StartingData.AFTER_TRANSACTION_IDLE; import static com.android.server.wm.StartingData.AFTER_TRANSACTION_REMOVE_DIRECTLY; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK; @@ -2698,7 +2697,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private void requestCopySplashScreen() { mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_COPYING; - if (!mAtmService.mTaskOrganizerController.copySplashScreenView(getTask())) { + if (mStartingSurface == null || !mAtmService.mTaskOrganizerController.copySplashScreenView( + getTask(), mStartingSurface.mTaskOrganizer)) { mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH; removeStartingWindow(); } @@ -2711,12 +2711,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A */ void onCopySplashScreenFinish(SplashScreenViewParcelable parcelable) { removeTransferSplashScreenTimeout(); - // unable to copy from shell, maybe it's not a splash screen. or something went wrong. - // either way, abort and reset the sequence. - if (parcelable == null + final SurfaceControl windowAnimationLeash = (parcelable == null || mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING || mStartingWindow == null || mStartingWindow.mRemoved - || finishing) { + || finishing) ? null + : TaskOrganizerController.applyStartingWindowAnimation(mStartingWindow); + if (windowAnimationLeash == null) { + // Unable to copy from shell, maybe it's not a splash screen, or something went wrong. + // Either way, abort and reset the sequence. if (parcelable != null) { parcelable.clearIfNeeded(); } @@ -2724,9 +2726,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A removeStartingWindow(); return; } - // schedule attach splashScreen to client - final SurfaceControl windowAnimationLeash = TaskOrganizerController - .applyStartingWindowAnimation(mStartingWindow); try { mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT; mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token, @@ -2766,7 +2765,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A && (mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_IDLE)) { ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Cleaning splash screen token=%s", this); - mAtmService.mTaskOrganizerController.onAppSplashScreenViewRemoved(getTask()); + mAtmService.mTaskOrganizerController.onAppSplashScreenViewRemoved(getTask(), + mStartingSurface != null ? mStartingSurface.mTaskOrganizer : null); } } @@ -2854,7 +2854,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else if (lastData.mRemoveAfterTransaction == AFTER_TRANSACTION_COPY_TO_CLIENT) { removeStartingWindow(); } - lastData.mRemoveAfterTransaction = AFTER_TRANSACTION_IDLE; } void removeStartingWindowAnimation(boolean prepareAnimation) { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 59159bb1e393..42c363085017 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -4048,10 +4048,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mRecentTasks.notifyTaskPersisterLocked(task, flush); } - boolean isKeyguardLocked(int displayId) { - return mKeyguardController.isKeyguardLocked(displayId); - } - /** * Clears launch params for the given package. * diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index eb15b3107e73..6eb9ed695133 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -794,6 +794,14 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { return false; } + // Try pausing the existing resumed activity in the same TaskFragment if any. + final TaskFragment taskFragment = r.getTaskFragment(); + if (taskFragment != null && taskFragment.getResumedActivity() != null) { + if (taskFragment.startPausing(mUserLeaving, false /* uiSleeping */, r, "realStart")) { + return false; + } + } + final Task task = r.getTask(); final Task rootTask = task.getRootTask(); diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 8c1d8fa93d88..1a319ad61116 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -423,7 +423,7 @@ class KeyguardController { final TransitionController tc = mRootWindowContainer.mTransitionController; - final boolean occluded = isDisplayOccluded(displayId); + final boolean occluded = getDisplayState(displayId).mOccluded; final boolean performTransition = isKeyguardLocked(displayId); final boolean executeTransition = performTransition && !tc.isCollecting(); @@ -500,15 +500,6 @@ class KeyguardController { } } - /** - * Returns {@code true} if the top activity on the display can occlude keyguard or the device - * is dreaming. Note that this method may return {@code true} even if the keyguard is disabled - * or not showing. - */ - boolean isDisplayOccluded(int displayId) { - return getDisplayState(displayId).mOccluded; - } - ActivityRecord getTopOccludingActivity(int displayId) { return getDisplayState(displayId).mTopOccludesActivity; } @@ -601,6 +592,11 @@ class KeyguardController { private boolean mAodShowing; private boolean mKeyguardGoingAway; private boolean mDismissalRequested; + + /** + * True if the top activity on the display can occlude keyguard or the device is dreaming. + * Note that this can be true even if the keyguard is disabled or not showing. + */ private boolean mOccluded; private boolean mShowingDream; diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 6b4cc25e62ca..57f8268b2fdc 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2699,7 +2699,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // transition exists, so this affects only when no lock screen is set. Otherwise // keyguard going away animation will be played. // See also AppTransitionController#getTransitCompatType for more details. - if ((!mTaskSupervisor.getKeyguardController().isDisplayOccluded(display.mDisplayId) + if ((!mTaskSupervisor.getKeyguardController().isKeyguardOccluded(display.mDisplayId) && token.mTag.equals(KEYGUARD_SLEEP_TOKEN_TAG)) || token.mTag.equals(DISPLAY_OFF_SLEEP_TOKEN_TAG)) { display.mSkipAppTransitionAnimation = true; diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java index a23547ef1d5b..2d281c476ec2 100644 --- a/services/core/java/com/android/server/wm/StartingData.java +++ b/services/core/java/com/android/server/wm/StartingData.java @@ -76,7 +76,7 @@ public abstract class StartingData { * This starting window should be removed after applying the start transaction of transition, * which ensures the app window has shown. */ - @AfterTransaction int mRemoveAfterTransaction; + @AfterTransaction int mRemoveAfterTransaction = AFTER_TRANSACTION_IDLE; /** Whether to prepare the removal animation. */ boolean mPrepareRemoveAnimation; diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java index 0bb773ae5e41..a55c232990cf 100644 --- a/services/core/java/com/android/server/wm/StartingSurfaceController.java +++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java @@ -40,6 +40,7 @@ import android.compat.annotation.EnabledSince; import android.content.pm.ApplicationInfo; import android.os.UserHandle; import android.util.Slog; +import android.window.ITaskOrganizer; import android.window.SplashScreenView; import android.window.TaskSnapshot; @@ -79,12 +80,13 @@ public class StartingSurfaceController { } StartingSurface createSplashScreenStartingSurface(ActivityRecord activity, int theme) { - synchronized (mService.mGlobalLock) { final Task task = activity.getTask(); - if (task != null && mService.mAtmService.mTaskOrganizerController.addStartingWindow( - task, activity, theme, null /* taskSnapshot */)) { - return new StartingSurface(task); + final TaskOrganizerController controller = + mService.mAtmService.mTaskOrganizerController; + if (task != null && controller.addStartingWindow(task, activity, theme, + null /* taskSnapshot */)) { + return new StartingSurface(task, controller.getTaskOrganizer()); } } return null; @@ -166,9 +168,12 @@ public class StartingSurfaceController { activity.mDisplayContent.handleTopActivityLaunchingInDifferentOrientation( activity, false /* checkOpening */); } - mService.mAtmService.mTaskOrganizerController.addStartingWindow(task, - activity, 0 /* launchTheme */, taskSnapshot); - return new StartingSurface(task); + final TaskOrganizerController controller = + mService.mAtmService.mTaskOrganizerController; + if (controller.addStartingWindow(task, activity, 0 /* launchTheme */, taskSnapshot)) { + return new StartingSurface(task, controller.getTaskOrganizer()); + } + return null; } } @@ -256,9 +261,12 @@ public class StartingSurfaceController { final class StartingSurface { private final Task mTask; + // The task organizer which hold the client side reference of this surface. + final ITaskOrganizer mTaskOrganizer; - StartingSurface(Task task) { + StartingSurface(Task task, ITaskOrganizer taskOrganizer) { mTask = task; + mTaskOrganizer = taskOrganizer; } /** @@ -268,7 +276,8 @@ public class StartingSurfaceController { */ public void remove(boolean animate) { synchronized (mService.mGlobalLock) { - mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask, animate); + mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask, + mTaskOrganizer, animate); } } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index f9bbc6810835..387a8767ced3 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3579,7 +3579,7 @@ class Task extends TaskFragment { && activity.info != info.taskInfo.topActivityInfo ? activity.info : null; info.isKeyguardOccluded = - mAtmService.mKeyguardController.isDisplayOccluded(DEFAULT_DISPLAY); + mAtmService.mKeyguardController.isKeyguardOccluded(info.taskInfo.displayId); info.startingWindowTypeParameter = activity.mStartingData != null ? activity.mStartingData.mTypeParams diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 3d01001a9f37..41e49b9f0466 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -652,7 +652,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { if (rootTask == null || activity.mStartingData == null) { return false; } - final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast(); + final ITaskOrganizer lastOrganizer = getTaskOrganizer(); if (lastOrganizer == null) { return false; } @@ -672,12 +672,13 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { return true; } - void removeStartingWindow(Task task, boolean prepareAnimation) { + void removeStartingWindow(Task task, ITaskOrganizer taskOrganizer, boolean prepareAnimation) { final Task rootTask = task.getRootTask(); if (rootTask == null) { return; } - final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast(); + final ITaskOrganizer lastOrganizer = taskOrganizer != null ? taskOrganizer + : getTaskOrganizer(); if (lastOrganizer == null) { return; } @@ -771,12 +772,13 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } } - boolean copySplashScreenView(Task task) { + boolean copySplashScreenView(Task task, ITaskOrganizer taskOrganizer) { final Task rootTask = task.getRootTask(); if (rootTask == null) { return false; } - final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast(); + final ITaskOrganizer lastOrganizer = taskOrganizer != null ? taskOrganizer + : getTaskOrganizer(); if (lastOrganizer == null) { return false; } @@ -799,12 +801,12 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { * @see com.android.wm.shell.ShellTaskOrganizer#onAppSplashScreenViewRemoved(int) * @see SplashScreenView#remove() */ - public void onAppSplashScreenViewRemoved(Task task) { + public void onAppSplashScreenViewRemoved(Task task, ITaskOrganizer organizer) { final Task rootTask = task.getRootTask(); if (rootTask == null) { return; } - final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast(); + final ITaskOrganizer lastOrganizer = organizer != null ? organizer : getTaskOrganizer(); if (lastOrganizer == null) { return; } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index e9af42bb7b90..81f91c739bc3 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -1127,6 +1127,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // though, notify the controller to prevent degenerate cases. if (!r.isVisibleRequested()) { mController.mValidateCommitVis.add(r); + } else { + // Make sure onAppTransitionFinished can be notified. + mParticipants.add(r); } return; } diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 31afcbf26220..4d7335873074 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -519,7 +519,8 @@ class WindowToken extends WindowContainer<WindowState> { for (int i = mFixedRotationTransformState.mAssociatedTokens.size() - 1; i >= 0; i--) { final ActivityRecord r = mFixedRotationTransformState.mAssociatedTokens.get(i).asActivityRecord(); - if (r != null && r.isInTransition()) { + // Only care about the transition at Activity/Task level. + if (r != null && r.inTransitionSelfOrParent() && !r.mDisplayContent.inTransition()) { return true; } } diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 788299cba059..6e0d98cab8e6 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -733,7 +733,13 @@ std::shared_ptr<PointerControllerInterface> NativeInputManager::obtainPointerCon if (controller == nullptr) { ensureSpriteControllerLocked(); - controller = PointerController::create(this, mLooper, *mLocked.spriteController); + static const bool ENABLE_POINTER_CHOREOGRAPHER = + sysprop::InputProperties::enable_pointer_choreographer().value_or(false); + + // Disable the functionality of the legacy PointerController if PointerChoreographer is + // enabled. + controller = PointerController::create(this, mLooper, *mLocked.spriteController, + /*enabled=*/!ENABLE_POINTER_CHOREOGRAPHER); mLocked.legacyPointerController = controller; updateInactivityTimeoutLocked(); } @@ -745,7 +751,7 @@ std::shared_ptr<PointerControllerInterface> NativeInputManager::createPointerCon std::scoped_lock _l(mLock); ensureSpriteControllerLocked(); std::shared_ptr<PointerController> pc = - PointerController::create(this, mLooper, *mLocked.spriteController); + PointerController::create(this, mLooper, *mLocked.spriteController, /*enabled=*/true); mLocked.pointerControllers.emplace_back(pc); return pc; } diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java index ea5ce65f6f79..49962eabbff3 100644 --- a/services/net/java/android/net/util/NetworkConstants.java +++ b/services/net/java/android/net/util/NetworkConstants.java @@ -52,7 +52,6 @@ public final class NetworkConstants { public static final int IPV6_ADDR_BITS = 128; public static final int IPV6_ADDR_LEN = 16; public static final int IPV6_MIN_MTU = 1280; - public static final int RFC7421_PREFIX_LENGTH = 64; /** * ICMP common (v4/v6) constants. diff --git a/services/permission/TEST_MAPPING b/services/permission/TEST_MAPPING index b2dcf379fe7d..24323c8bfbde 100644 --- a/services/permission/TEST_MAPPING +++ b/services/permission/TEST_MAPPING @@ -4,7 +4,7 @@ "name": "CtsPermissionTestCases", "options": [ { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" + "exclude-annotation": "androidx.test.filters.FlakyTest" }, { "include-filter": "android.permission.cts.BackgroundPermissionsTest" @@ -32,7 +32,7 @@ "name": "CtsPermissionPolicyTestCases", "options": [ { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" + "exclude-annotation": "androidx.test.filters.FlakyTest" }, { "include-filter": "android.permissionpolicy.cts.RestrictedPermissionsTest" diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt index 5f26d6f846aa..cd37674f4f8c 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt @@ -31,6 +31,7 @@ import org.junit.Test import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mockito.doReturn import kotlin.test.assertFailsWith class PackageManagerLocalSnapshotTest { @@ -154,7 +155,7 @@ class PackageManagerLocalSnapshotTest { put(packageStateUser0.packageName, packageStateUser0) put(packageStateUser10.packageName, packageStateUser10) } - whenever(this.packageStates) { packageStates } + doReturn(packageStates).whenever(this).packageStates whenever(getPackageStateFiltered(anyString(), anyInt(), anyInt())) { packageStates[arguments[0]]?.takeUnless { shouldFilterApplication(it, arguments[1] as Int, arguments[2] as Int) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt index 55645d7f80b1..9fbf86e36df4 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt @@ -48,6 +48,7 @@ import org.mockito.Mockito.any import org.mockito.Mockito.anyInt import org.mockito.Mockito.anyLong import org.mockito.Mockito.anyString +import org.mockito.Mockito.doReturn import org.mockito.Mockito.eq import org.mockito.Mockito.mock import org.mockito.Mockito.verifyNoMoreInteractions @@ -351,12 +352,12 @@ class DomainVerificationEnforcerTest { whenever(this.domainSetId) { domainSetId } whenever(getUserStateOrDefault(0)) { PackageUserStateInternal.DEFAULT } whenever(getUserStateOrDefault(1)) { PackageUserStateInternal.DEFAULT } - whenever(userStates) { + doReturn( SparseArray<PackageUserStateInternal>().apply { this[0] = PackageUserStateInternal.DEFAULT this[1] = PackageUserStateInternal.DEFAULT } - } + ).whenever(this).userStates whenever(isSystem) { false } whenever(signingDetails) { SigningDetails.UNKNOWN } } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt index 86c4335ecfe9..47d9196b502b 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt @@ -44,6 +44,7 @@ import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyLong import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mockito.doReturn import java.util.UUID import java.util.concurrent.atomic.AtomicBoolean import kotlin.test.assertFailsWith @@ -555,12 +556,12 @@ class DomainVerificationManagerApiTest { whenever(this.domainSetId) { domainSetId } whenever(getUserStateOrDefault(0)) { pkgUserState0() } whenever(getUserStateOrDefault(1)) { pkgUserState1() } - whenever(userStates) { + doReturn( SparseArray<PackageUserStateInternal>().apply { this[0] = pkgUserState0() this[1] = pkgUserState1() } - } + ).whenever(this).userStates whenever(isSystem) { false } } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt index e55ff3b7c229..98d780143ff7 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt @@ -1084,12 +1084,12 @@ class DomainVerificationPackageTest { whenever(this.domainSetId) { domainSetId } whenever(getUserStateOrDefault(0)) { PackageUserStateInternal.DEFAULT } whenever(getUserStateOrDefault(10)) { PackageUserStateInternal.DEFAULT } - whenever(userStates) { + doReturn( SparseArray<PackageUserStateInternal>().apply { this[0] = PackageUserStateInternal.DEFAULT this[1] = PackageUserStateInternal.DEFAULT } - } + ).whenever(this).userStates whenever(isSystem) { isSystemApp } val mockSigningDetails = SigningDetails(arrayOf(spy(Signature(signature)) { diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt index 427b5b3494b2..4a211dfeb91e 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt @@ -41,6 +41,7 @@ import org.mockito.Mockito.any import org.mockito.Mockito.anyInt import org.mockito.Mockito.anyLong import org.mockito.Mockito.anyString +import org.mockito.Mockito.doReturn import org.mockito.Mockito.eq import org.mockito.Mockito.verify import java.util.UUID @@ -218,12 +219,12 @@ class DomainVerificationSettingsMutationTest { whenever(domainSetId) { TEST_UUID } whenever(getUserStateOrDefault(0)) { PackageUserStateInternal.DEFAULT } whenever(getUserStateOrDefault(10)) { PackageUserStateInternal.DEFAULT } - whenever(userStates) { + doReturn( SparseArray<PackageUserStateInternal>().apply { this[0] = PackageUserStateInternal.DEFAULT this[1] = PackageUserStateInternal.DEFAULT } - } + ).whenever(this).userStates whenever(isSystem) { false } } } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt index 6bb5f393f0d2..d54d608e48c2 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt @@ -41,6 +41,8 @@ import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyLong import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mockito.doReturn + import java.util.UUID class DomainVerificationUserStateOverrideTest { @@ -155,12 +157,12 @@ class DomainVerificationUserStateOverrideTest { whenever(this.domainSetId) { domainSetId } whenever(getUserStateOrDefault(0)) { PackageUserStateInternal.DEFAULT } whenever(getUserStateOrDefault(1)) { PackageUserStateInternal.DEFAULT } - whenever(userStates) { + doReturn( SparseArray<PackageUserStateInternal>().apply { this[0] = PackageUserStateInternal.DEFAULT this[1] = PackageUserStateInternal.DEFAULT } - } + ).whenever(this).userStates whenever(isSystem) { false } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java index 76e6ec7f6780..8e01a11cb23f 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java @@ -32,8 +32,10 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.anyFloat; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -390,6 +392,35 @@ public class HighBrightnessModeControllerTest { assertEquals(Float.POSITIVE_INFINITY, hbmc.getHdrBrightnessValue(), 0.0); } + @Test + public void testHdrRespectsChangingDesiredHdrSdrRatio() { + final Runnable hbmChangedCallback = mock(Runnable.class); + final HighBrightnessModeController hbmc = new TestHbmBuilder() + .setClock(new OffsettableClock()) + .setHdrBrightnessConfig(mHdrBrightnessDeviceConfigMock) + .setHbmChangedCallback(hbmChangedCallback) + .build(); + + // Passthrough return the max desired hdr/sdr ratio + when(mHdrBrightnessDeviceConfigMock.getHdrBrightnessFromSdr(anyFloat(), anyFloat())) + .thenAnswer(i -> i.getArgument(1)); + + hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/, + DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 2.0f /*maxDesiredHdrSdrRatio*/); + advanceTime(0); + assertEquals(2.0f, hbmc.getHdrBrightnessValue(), EPSILON); + verify(hbmChangedCallback, times(1)).run(); + + // Verify that a change in only the desired hdrSdrRatio still results in the changed + // callback being invoked + hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/, + DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, + 3.0f /*maxDesiredHdrSdrRatio*/); + advanceTime(0); + assertEquals(3.0f, hbmc.getHdrBrightnessValue(), 0.0); + verify(hbmChangedCallback, times(2)).run(); + } + @Test public void testHdrTrumpsSunlight() { @@ -698,6 +729,7 @@ public class HighBrightnessModeControllerTest { private class TestHbmBuilder { OffsettableClock mClock; HighBrightnessModeController.HdrBrightnessDeviceConfig mHdrBrightnessCfg; + Runnable mHdrChangedCallback = () -> {}; TestHbmBuilder setClock(OffsettableClock clock) { mClock = clock; @@ -711,6 +743,11 @@ public class HighBrightnessModeControllerTest { return this; } + TestHbmBuilder setHbmChangedCallback(Runnable runnable) { + mHdrChangedCallback = runnable; + return this; + } + HighBrightnessModeController build() { initHandler(mClock); if (mHighBrightnessModeMetadata == null) { @@ -718,8 +755,8 @@ public class HighBrightnessModeControllerTest { } return new HighBrightnessModeController(mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, - DEFAULT_HBM_DATA, mHdrBrightnessCfg, () -> {}, mHighBrightnessModeMetadata, - mContextSpy); + DEFAULT_HBM_DATA, mHdrBrightnessCfg, mHdrChangedCallback, + mHighBrightnessModeMetadata, mContextSpy); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index 3c753326fb7d..e578ea35518d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -567,9 +567,8 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { @Throws(Exception::class) private fun stageInstantAppResolverScan() { - whenever(mocks.resources.getStringArray(R.array.config_ephemeralResolverPackage)) { - arrayOf("com.android.test.ephemeral.resolver") - } + doReturn(arrayOf("com.android.test.ephemeral.resolver")) + .whenever(mocks.resources).getStringArray(R.array.config_ephemeralResolverPackage) stageScanNewPackage("com.android.test.ephemeral.resolver", 1L, getPartitionFromFlag(PackageManagerService.SCAN_AS_PRODUCT).privAppFolder, withPackage = { pkg: PackageImpl -> diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java index a8b0a7b5633d..c7e1bda2e1b8 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java @@ -37,10 +37,9 @@ import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; import android.os.Binder; import android.os.Build; -import android.os.Process; +import android.os.ParcelableException; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; -import android.text.TextUtils; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -62,7 +61,7 @@ import java.util.List; @SmallTest @Presubmit @RunWith(AndroidJUnit4.class) -public class ArchiveManagerTest { +public class PackageArchiverServiceTest { private static final String PACKAGE = "com.example"; private static final String CALLER_PACKAGE = "com.vending"; @@ -95,10 +94,11 @@ public class ArchiveManagerTest { private final List<LauncherActivityInfo> mLauncherActivityInfos = createLauncherActivities(); + private final int mUserId = UserHandle.CURRENT.getIdentifier(); + private PackageSetting mPackageSetting; - private PackageManagerService mPm; - private ArchiveManager mArchiveManager; + private PackageArchiverService mArchiveService; @Before public void setUp() throws Exception { @@ -106,7 +106,7 @@ public class ArchiveManagerTest { mMockSystem.system().stageNominalSystemState(); when(mMockSystem.mocks().getInjector().getPackageInstallerService()).thenReturn( mInstallerService); - mPm = spy(new PackageManagerService(mMockSystem.mocks().getInjector(), + PackageManagerService pm = spy(new PackageManagerService(mMockSystem.mocks().getInjector(), /* factoryTest= */false, MockSystem.Companion.getDEFAULT_VERSION_INFO().fingerprint, /* isEngBuild= */ false, @@ -124,37 +124,42 @@ public class ArchiveManagerTest { when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps); when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn( mLauncherActivityInfos); - doReturn(mComputer).when(mPm).snapshotComputer(); - when(mComputer.getNameForUid(eq(Binder.getCallingUid()))).thenReturn(CALLER_PACKAGE); - mArchiveManager = new ArchiveManager(mContext, mPm); + doReturn(mComputer).when(pm).snapshotComputer(); + when(mComputer.getPackageUid(eq(CALLER_PACKAGE), eq(0L), eq(mUserId))).thenReturn( + Binder.getCallingUid()); + mArchiveService = new PackageArchiverService(mContext, pm); } @Test public void archiveApp_callerPackageNameIncorrect() { Exception e = assertThrows( SecurityException.class, - () -> mArchiveManager.archiveApp(PACKAGE, "different", UserHandle.CURRENT, - mIntentSender) + () -> mArchiveService.requestArchive(PACKAGE, "different", mIntentSender, + UserHandle.CURRENT + ) ); assertThat(e).hasMessageThat().isEqualTo( String.format( - "The callerPackageName %s set by the caller doesn't match the " - + "caller's own package name %s.", - "different", - CALLER_PACKAGE)); + "The UID %s of callerPackageName set by the caller doesn't match the " + + "caller's actual UID %s.", + 0, + Binder.getCallingUid())); } @Test public void archiveApp_packageNotInstalled() { - when(mMockSystem.mocks().getSettings().getPackageLPr(eq(PACKAGE))).thenReturn( + when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn( null); Exception e = assertThrows( - PackageManager.NameNotFoundException.class, - () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT, - mIntentSender) + ParcelableException.class, + () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, + UserHandle.CURRENT + ) ); - assertThat(e).hasMessageThat().isEqualTo(String.format("Package %s not found.", PACKAGE)); + assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); + assertThat(e.getCause()).hasMessageThat().isEqualTo( + String.format("Package %s not found.", PACKAGE)); } @Test @@ -162,11 +167,14 @@ public class ArchiveManagerTest { mPackageSetting.modifyUserState(UserHandle.CURRENT.getIdentifier()).setInstalled(false); Exception e = assertThrows( - PackageManager.NameNotFoundException.class, - () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT, - mIntentSender) + ParcelableException.class, + () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, + UserHandle.CURRENT + ) ); - assertThat(e).hasMessageThat().isEqualTo(String.format("Package %s not found.", PACKAGE)); + assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); + assertThat(e.getCause()).hasMessageThat().isEqualTo( + String.format("Package %s not found.", PACKAGE)); } @Test @@ -183,17 +191,18 @@ public class ArchiveManagerTest { when(mPackageState.getInstallSource()).thenReturn(otherInstallSource); Exception e = assertThrows( - SecurityException.class, - () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, Process.myUserHandle(), - mIntentSender) + ParcelableException.class, + () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, + UserHandle.CURRENT + ) ); - assertThat(e).hasMessageThat().isEqualTo( - TextUtils.formatSimple("No installer found to archive app %s.", - PACKAGE)); + assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); + assertThat(e.getCause()).hasMessageThat().isEqualTo( + String.format("No installer found to archive app %s.", PACKAGE)); } @Test - public void archiveApp_success() throws PackageManager.NameNotFoundException { + public void archiveApp_success() { List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>(); for (LauncherActivityInfo mainActivity : createLauncherActivities()) { // TODO(b/278553670) Extract and store launcher icons @@ -203,7 +212,8 @@ public class ArchiveManagerTest { activityInfos.add(activityInfo); } - mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT, mIntentSender); + mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT); + verify(mInstallerService).uninstall( eq(new VersionedPackage(PACKAGE, PackageManager.VERSION_CODE_HIGHEST)), eq(CALLER_PACKAGE), eq(DELETE_KEEP_DATA), eq(mIntentSender), diff --git a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java index 20cfd59973c3..dc04b6aea318 100644 --- a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java @@ -24,10 +24,12 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verifyZeroInteractions; import android.app.AppOpsManager; import android.content.Context; @@ -43,14 +45,13 @@ import android.hardware.lights.LightsRequest; import android.os.Handler; import android.os.Looper; import android.permission.PermissionManager; -import android.util.ArraySet; +import android.testing.TestableLooper; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.R; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; @@ -62,24 +63,19 @@ import java.util.Collections; import java.util.List; import java.util.Random; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; public class CameraPrivacyLightControllerTest { + private int[] mDefaultColors = {0, 1, 2}; + private int[] mDefaultAlsThresholdsLux = {10, 50}; + private int mDefaultAlsAveragingIntervalMillis = 5000; - private int mDayColor = 1; - private int mNightColor = 0; - private int mCameraPrivacyLightAlsAveragingIntervalMillis = 5000; - private int mCameraPrivacyLightAlsNightThreshold = (int) getLightSensorValue(15); + private TestableLooper mTestableLooper; private MockitoSession mMockitoSession; @Mock - private Context mContext; - - @Mock - private Resources mResources; - - @Mock private LightsManager mLightsManager; @Mock @@ -103,61 +99,98 @@ public class CameraPrivacyLightControllerTest { private ArgumentCaptor<SensorEventListener> mLightSensorListenerCaptor = ArgumentCaptor.forClass(SensorEventListener.class); - private Set<String> mExemptedPackages = new ArraySet<>(); - private List<Light> mLights = new ArrayList<>(); + private Set<String> mExemptedPackages; + private List<Light> mLights; private int mNextLightId = 1; - @Before - public void setUp() { - mMockitoSession = ExtendedMockito.mockitoSession() - .initMocks(this) - .strictness(Strictness.WARN) - .spyStatic(PermissionManager.class) - .startMocking(); + public CameraPrivacyLightController prepareDefaultCameraPrivacyLightController() { + return prepareDefaultCameraPrivacyLightController(List.of(getNextLight(true))); + } - doReturn(mDayColor).when(mContext).getColor(R.color.camera_privacy_light_day); - doReturn(mNightColor).when(mContext).getColor(R.color.camera_privacy_light_night); + public CameraPrivacyLightController prepareDefaultCameraPrivacyLightController( + List<Light> lights) { + return prepareCameraPrivacyLightController(lights, Set.of(), true, mDefaultColors, + mDefaultAlsThresholdsLux, mDefaultAlsAveragingIntervalMillis); + } + + public CameraPrivacyLightController prepareCameraPrivacyLightController(List<Light> lights, + Set<String> exemptedPackages, boolean hasLightSensor, int[] lightColors, + int[] alsThresholds, int averagingInterval) { + Looper looper = Looper.myLooper(); + if (looper == null) { + Looper.prepare(); + looper = Looper.myLooper(); + } + if (mTestableLooper == null) { + try { + mTestableLooper = new TestableLooper(looper); + } catch (Exception e) { + throw new RuntimeException(e); + } + } - doReturn(mResources).when(mContext).getResources(); - doReturn(mCameraPrivacyLightAlsAveragingIntervalMillis).when(mResources) + Context context = mock(Context.class); + Resources resources = mock(Resources.class); + doReturn(resources).when(context).getResources(); + doReturn(lightColors).when(resources).getIntArray(R.array.config_cameraPrivacyLightColors); + doReturn(alsThresholds).when(resources) + .getIntArray(R.array.config_cameraPrivacyLightAlsLuxThresholds); + doReturn(averagingInterval).when(resources) .getInteger(R.integer.config_cameraPrivacyLightAlsAveragingIntervalMillis); - doReturn(mCameraPrivacyLightAlsNightThreshold).when(mResources) - .getInteger(R.integer.config_cameraPrivacyLightAlsNightThreshold); - doReturn(mLightsManager).when(mContext).getSystemService(LightsManager.class); - doReturn(mAppOpsManager).when(mContext).getSystemService(AppOpsManager.class); - doReturn(mSensorManager).when(mContext).getSystemService(SensorManager.class); + doReturn(mLightsManager).when(context).getSystemService(LightsManager.class); + doReturn(mAppOpsManager).when(context).getSystemService(AppOpsManager.class); + doReturn(mSensorManager).when(context).getSystemService(SensorManager.class); + mLights = lights; + mExemptedPackages = exemptedPackages; doReturn(mLights).when(mLightsManager).getLights(); doReturn(mLightsSession).when(mLightsManager).openSession(anyInt()); + if (!hasLightSensor) { + mLightSensor = null; + } doReturn(mLightSensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT); - - doReturn(mExemptedPackages) + doReturn(exemptedPackages) .when(() -> PermissionManager.getIndicatorExemptedPackages(any())); + + return new CameraPrivacyLightController(context, looper); + } + + @Before + public void setUp() { + mMockitoSession = ExtendedMockito.mockitoSession() + .initMocks(this) + .strictness(Strictness.WARN) + .spyStatic(PermissionManager.class) + .startMocking(); } @After public void tearDown() { - mExemptedPackages.clear(); - mLights.clear(); - mMockitoSession.finishMocking(); } @Test + public void testNoInteractionsWithServicesIfNoColorsSpecified() { + prepareCameraPrivacyLightController(List.of(getNextLight(true)), Collections.EMPTY_SET, + true, new int[0], mDefaultAlsThresholdsLux, mDefaultAlsAveragingIntervalMillis); + + verifyZeroInteractions(mLightsManager); + verifyZeroInteractions(mAppOpsManager); + verifyZeroInteractions(mSensorManager); + } + + @Test public void testAppsOpsListenerNotRegisteredWithoutCameraLights() { - mLights.add(getNextLight(false)); - createCameraPrivacyLightController(); + prepareDefaultCameraPrivacyLightController(List.of(getNextLight(false))); verify(mAppOpsManager, times(0)).startWatchingActive(any(), any(), any()); } @Test public void testAppsOpsListenerRegisteredWithCameraLight() { - mLights.add(getNextLight(true)); - - createCameraPrivacyLightController(); + prepareDefaultCameraPrivacyLightController(); verify(mAppOpsManager, times(1)).startWatchingActive(any(), any(), any()); } @@ -165,11 +198,12 @@ public class CameraPrivacyLightControllerTest { @Test public void testAllCameraLightsAreRequestedOnOpActive() { Random r = new Random(0); + List<Light> lights = new ArrayList<>(); for (int i = 0; i < 30; i++) { - mLights.add(getNextLight(r.nextBoolean())); + lights.add(getNextLight(r.nextBoolean())); } - createCameraPrivacyLightController(); + prepareDefaultCameraPrivacyLightController(lights); // Verify no session has been opened at this point. verify(mLightsManager, times(0)).openSession(anyInt()); @@ -181,8 +215,6 @@ public class CameraPrivacyLightControllerTest { verify(mLightsManager, times(1)).openSession(anyInt()); verify(mLightsSession).requestLights(mLightsRequestCaptor.capture()); - assertEquals("requestLights() not invoked exactly once", - 1, mLightsRequestCaptor.getAllValues().size()); List<Integer> expectedCameraLightIds = mLights.stream() .filter(l -> l.getType() == Light.LIGHT_TYPE_CAMERA) @@ -199,40 +231,25 @@ public class CameraPrivacyLightControllerTest { @Test public void testWillOnlyOpenOnceWhenTwoPackagesStartOp() { - mLights.add(getNextLight(true)); - - createCameraPrivacyLightController(); - - verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture()); - - AppOpsManager.OnOpActiveChangedListener listener = mAppOpsListenerCaptor.getValue(); - listener.onOpActiveChanged(OPSTR_CAMERA, 10101, "pkg1", true); + prepareDefaultCameraPrivacyLightController(); + notifyCamOpChanged(10101, "pkg1", true); verify(mLightsManager, times(1)).openSession(anyInt()); - listener.onOpActiveChanged(OPSTR_CAMERA, 10102, "pkg2", true); + notifyCamOpChanged(10102, "pkg2", true); verify(mLightsManager, times(1)).openSession(anyInt()); } @Test public void testWillCloseOnFinishOp() { - mLights.add(getNextLight(true)); - - createCameraPrivacyLightController(); - - verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture()); - - AppOpsManager.OnOpActiveChangedListener listener = mAppOpsListenerCaptor.getValue(); - listener.onOpActiveChanged(OPSTR_CAMERA, 10101, "pkg1", true); - + prepareDefaultCameraPrivacyLightController(); + notifyCamOpChanged(10101, "pkg1", true); verify(mLightsSession, times(0)).close(); - listener.onOpActiveChanged(OPSTR_CAMERA, 10101, "pkg1", false); + notifyCamOpChanged(10101, "pkg1", false); verify(mLightsSession, times(1)).close(); } @Test public void testWillCloseOnFinishOpForAllPackages() { - mLights.add(getNextLight(true)); - - createCameraPrivacyLightController(); + prepareDefaultCameraPrivacyLightController(); int numUids = 100; List<Integer> uids = new ArrayList<>(numUids); @@ -240,64 +257,52 @@ public class CameraPrivacyLightControllerTest { uids.add(10001 + i); } - verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture()); - - AppOpsManager.OnOpActiveChangedListener listener = mAppOpsListenerCaptor.getValue(); - for (int i = 0; i < numUids; i++) { - listener.onOpActiveChanged(OPSTR_CAMERA, uids.get(i), "pkg" + (int) uids.get(i), true); + notifyCamOpChanged(uids.get(i), "pkg" + (int) uids.get(i), true); } // Change the order which their ops are finished Collections.shuffle(uids, new Random(0)); for (int i = 0; i < numUids - 1; i++) { - listener.onOpActiveChanged(OPSTR_CAMERA, uids.get(i), "pkg" + (int) uids.get(i), false); + notifyCamOpChanged(uids.get(i), "pkg" + (int) uids.get(i), false); } verify(mLightsSession, times(0)).close(); int lastUid = uids.get(numUids - 1); - listener.onOpActiveChanged(OPSTR_CAMERA, lastUid, "pkg" + lastUid, false); + notifyCamOpChanged(lastUid, "pkg" + lastUid, false); verify(mLightsSession, times(1)).close(); } @Test public void testWontOpenForExemptedPackage() { - mLights.add(getNextLight(true)); - mExemptedPackages.add("pkg1"); - - createCameraPrivacyLightController(); + String exemptPackage = "pkg1"; + prepareCameraPrivacyLightController(List.of(getNextLight(true)), + Set.of(exemptPackage), true, mDefaultColors, mDefaultAlsThresholdsLux, + mDefaultAlsAveragingIntervalMillis); - verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture()); - - AppOpsManager.OnOpActiveChangedListener listener = mAppOpsListenerCaptor.getValue(); - listener.onOpActiveChanged(OPSTR_CAMERA, 10101, "pkg1", true); + notifyCamOpChanged(10101, exemptPackage, true); verify(mLightsManager, times(0)).openSession(anyInt()); } @Test public void testNoLightSensor() { - mLights.add(getNextLight(true)); - doReturn(null).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT); - - createCameraPrivacyLightController(); + prepareCameraPrivacyLightController(List.of(getNextLight(true)), + Set.of(), true, mDefaultColors, mDefaultAlsThresholdsLux, + mDefaultAlsAveragingIntervalMillis); openCamera(); verify(mLightsSession).requestLights(mLightsRequestCaptor.capture()); LightsRequest lightsRequest = mLightsRequestCaptor.getValue(); for (LightState lightState : lightsRequest.getLightStates()) { - assertEquals(mDayColor, lightState.getColor()); + assertEquals(mDefaultColors[mDefaultColors.length - 1], lightState.getColor()); } } @Test public void testALSListenerNotRegisteredUntilCameraIsOpened() { - mLights.add(getNextLight(true)); - Sensor sensor = mock(Sensor.class); - doReturn(sensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT); - - CameraPrivacyLightController cplc = createCameraPrivacyLightController(); + prepareDefaultCameraPrivacyLightController(); verify(mSensorManager, never()).registerListener(any(SensorEventListener.class), any(Sensor.class), anyInt(), any(Handler.class)); @@ -307,113 +312,44 @@ public class CameraPrivacyLightControllerTest { verify(mSensorManager, times(1)).registerListener(mLightSensorListenerCaptor.capture(), any(Sensor.class), anyInt(), any(Handler.class)); - mAppOpsListenerCaptor.getValue().onOpActiveChanged(OPSTR_CAMERA, 10001, "pkg", false); + notifyCamOpChanged(10001, "pkg", false); verify(mSensorManager, times(1)).unregisterListener(mLightSensorListenerCaptor.getValue()); } - @Ignore - @Test - public void testDayColor() { - testBrightnessToColor(20, mDayColor); - } - - @Ignore @Test - public void testNightColor() { - testBrightnessToColor(10, mNightColor); - } - - private void testBrightnessToColor(int brightnessValue, int color) { - mLights.add(getNextLight(true)); - Sensor sensor = mock(Sensor.class); - doReturn(sensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT); - - CameraPrivacyLightController cplc = createCameraPrivacyLightController(); + public void testAlsThresholds() { + CameraPrivacyLightController cplc = prepareDefaultCameraPrivacyLightController(); + long elapsedTime = 0; cplc.setElapsedRealTime(0); - openCamera(); - - verify(mSensorManager).registerListener(mLightSensorListenerCaptor.capture(), - any(Sensor.class), anyInt(), any(Handler.class)); - SensorEventListener sensorListener = mLightSensorListenerCaptor.getValue(); - float[] sensorEventValues = new float[1]; - SensorEvent sensorEvent = new SensorEvent(sensor, 0, 0, sensorEventValues); - - sensorEventValues[0] = getLightSensorValue(brightnessValue); - sensorListener.onSensorChanged(sensorEvent); - - verify(mLightsSession, atLeastOnce()).requestLights(mLightsRequestCaptor.capture()); - for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) { - assertEquals(color, lightState.getColor()); + for (int i = 0; i < mDefaultColors.length; i++) { + int expectedColor = mDefaultColors[i]; + int alsLuxValue = i + == mDefaultAlsThresholdsLux.length + ? mDefaultAlsThresholdsLux[i - 1] : mDefaultAlsThresholdsLux[i] - 1; + + notifySensorEvent(cplc, elapsedTime, alsLuxValue); + elapsedTime += mDefaultAlsAveragingIntervalMillis + 1; + notifySensorEvent(cplc, elapsedTime, alsLuxValue); + + verify(mLightsSession, atLeastOnce()).requestLights(mLightsRequestCaptor.capture()); + for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) { + assertEquals(expectedColor, lightState.getColor()); + } } } - @Ignore - @Test - public void testDayToNightTransistion() { - mLights.add(getNextLight(true)); - Sensor sensor = mock(Sensor.class); - doReturn(sensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT); - - CameraPrivacyLightController cplc = createCameraPrivacyLightController(); - cplc.setElapsedRealTime(0); - - openCamera(); - // There will be an initial call at brightness 0 - verify(mLightsSession, times(1)).requestLights(any(LightsRequest.class)); - - verify(mSensorManager).registerListener(mLightSensorListenerCaptor.capture(), - any(Sensor.class), anyInt(), any(Handler.class)); - SensorEventListener sensorListener = mLightSensorListenerCaptor.getValue(); - - onSensorEvent(cplc, sensorListener, sensor, 0, 20); - - // 5 sec avg = 20 - onSensorEvent(cplc, sensorListener, sensor, 5000, 30); - - verify(mLightsSession, times(2)).requestLights(mLightsRequestCaptor.capture()); - for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) { - assertEquals(mDayColor, lightState.getColor()); - } - - // 5 sec avg = 22 - - onSensorEvent(cplc, sensorListener, sensor, 6000, 10); - - // 5 sec avg = 18 - - onSensorEvent(cplc, sensorListener, sensor, 8000, 5); - - // Should have always been day - verify(mLightsSession, times(2)).requestLights(mLightsRequestCaptor.capture()); - for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) { - assertEquals(mDayColor, lightState.getColor()); - } - - // 5 sec avg = 12 - - onSensorEvent(cplc, sensorListener, sensor, 10000, 5); - - // Should now be night - verify(mLightsSession, times(3)).requestLights(mLightsRequestCaptor.capture()); - for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) { - assertEquals(mNightColor, lightState.getColor()); - } + private void notifyCamOpChanged(int uid, String pkg, boolean active) { + verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture()); + mAppOpsListenerCaptor.getValue().onOpActiveChanged(OPSTR_CAMERA, uid, pkg, active); } - private void onSensorEvent(CameraPrivacyLightController cplc, - SensorEventListener sensorListener, Sensor sensor, long timestamp, int value) { + private void notifySensorEvent(CameraPrivacyLightController cplc, long timestamp, int value) { cplc.setElapsedRealTime(timestamp); - sensorListener.onSensorChanged(new SensorEvent(sensor, 0, timestamp, - new float[] {getLightSensorValue(value)})); - } - - // Use the test thread so that the test is deterministic - private CameraPrivacyLightController createCameraPrivacyLightController() { - if (Looper.myLooper() == null) { - Looper.prepare(); - } - return new CameraPrivacyLightController(mContext, Looper.myLooper()); + verify(mSensorManager, atLeastOnce()).registerListener(mLightSensorListenerCaptor.capture(), + eq(mLightSensor), anyInt(), any()); + mLightSensorListenerCaptor.getValue().onSensorChanged(new SensorEvent(mLightSensor, 0, + TimeUnit.MILLISECONDS.toNanos(timestamp), new float[] {value})); } private Light getNextLight(boolean cameraType) { @@ -427,10 +363,6 @@ public class CameraPrivacyLightControllerTest { return light; } - private float getLightSensorValue(int i) { - return (float) Math.exp(i / CameraPrivacyLightController.LIGHT_VALUE_MULTIPLIER); - } - private void openCamera() { verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture()); mAppOpsListenerCaptor.getValue().onOpActiveChanged(OPSTR_CAMERA, 10001, "pkg", true); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index b79c7bee90b1..349a597a37fa 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -303,6 +303,7 @@ public class FullScreenMagnificationGestureHandlerTest { }); } + @FlakyTest(bugId = 297879316) @Test public void testStates_areMutuallyExclusive() { forEachState(state1 -> { @@ -523,6 +524,7 @@ public class FullScreenMagnificationGestureHandlerTest { }); } + @FlakyTest(bugId = 297879316) @Test public void testTwoFingersOneTap_activatedState_dispatchMotionEvents() { goFromStateIdleTo(STATE_ACTIVATED); @@ -583,6 +585,7 @@ public class FullScreenMagnificationGestureHandlerTest { returnToNormalFrom(STATE_ACTIVATED); } + @FlakyTest(bugId = 297879316) @Test public void testFirstFingerSwipe_twoPointerDownAndActivatedState_panningState() { goFromStateIdleTo(STATE_ACTIVATED); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java index bbbab2129d22..a0bca3b6b9fe 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java @@ -50,6 +50,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; +import android.platform.test.annotations.FlakyTest; import android.provider.Settings; import android.test.mock.MockContentResolver; import android.view.InputDevice; @@ -307,6 +308,7 @@ public class WindowMagnificationManagerTest { MagnificationScaleProvider.MAX_SCALE); } + @FlakyTest(bugId = 297879435) @Test public void logTrackingTypingFocus_processScroll_logDuration() { WindowMagnificationManager spyWindowMagnificationManager = spy(mWindowMagnificationManager); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java index 64e776e35f00..a621c0c01067 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java @@ -181,6 +181,7 @@ public class AuthenticationStatsCollectorTest { .getAuthenticationStatsForUser(USER_ID_1); assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0); assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0); + assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(0); assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f); } @@ -203,6 +204,8 @@ public class AuthenticationStatsCollectorTest { .getAuthenticationStatsForUser(USER_ID_1); assertThat(authenticationStats.getTotalAttempts()).isEqualTo(500); assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(400); + assertThat(authenticationStats.getEnrollmentNotifications()) + .isEqualTo(MAXIMUM_ENROLLMENT_NOTIFICATIONS); assertThat(authenticationStats.getFrr()).isWithin(0f).of(0.8f); } @@ -230,6 +233,7 @@ public class AuthenticationStatsCollectorTest { .getAuthenticationStatsForUser(USER_ID_1); assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0); assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0); + assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(0); assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f); } @@ -256,6 +260,7 @@ public class AuthenticationStatsCollectorTest { .getAuthenticationStatsForUser(USER_ID_1); assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0); assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0); + assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(0); assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f); } @@ -284,6 +289,8 @@ public class AuthenticationStatsCollectorTest { assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0); assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0); assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f); + // Assert that notification count has been updated. + assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(1); } @Test @@ -311,5 +318,7 @@ public class AuthenticationStatsCollectorTest { assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0); assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0); assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f); + // Assert that notification count has been updated. + assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(1); } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java index dde2a3c2cfd7..0c0d47a6b165 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java @@ -217,6 +217,31 @@ public class AuthenticationStatsPersisterTest { } @Test + public void persistFrrStats_multiUser_newUser_shouldUpdateRecord() throws JSONException { + AuthenticationStats authenticationStats1 = new AuthenticationStats(USER_ID_1, + 300 /* totalAttempts */, 10 /* rejectedAttempts */, + 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE); + AuthenticationStats authenticationStats2 = new AuthenticationStats(USER_ID_2, + 100 /* totalAttempts */, 5 /* rejectedAttempts */, + 1 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FINGERPRINT); + + // Sets up the shared preference with user 1 only. + when(mSharedPreferences.getStringSet(eq(KEY), anySet())).thenReturn( + Set.of(buildFrrStats(authenticationStats1))); + + // Add data for user 2. + mAuthenticationStatsPersister.persistFrrStats(authenticationStats2.getUserId(), + authenticationStats2.getTotalAttempts(), + authenticationStats2.getRejectedAttempts(), + authenticationStats2.getEnrollmentNotifications(), + authenticationStats2.getModality()); + + verify(mEditor).putStringSet(eq(KEY), mStringSetArgumentCaptor.capture()); + assertThat(mStringSetArgumentCaptor.getValue()) + .contains(buildFrrStats(authenticationStats2)); + } + + @Test public void removeFrrStats_existingUser_shouldUpdateRecord() throws JSONException { AuthenticationStats authenticationStats = new AuthenticationStats(USER_ID_1, 300 /* totalAttempts */, 10 /* rejectedAttempts */, diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp index 4b658958cf18..2dacda0af7f4 100644 --- a/services/tests/uiservicestests/Android.bp +++ b/services/tests/uiservicestests/Android.bp @@ -34,6 +34,7 @@ android_test { "platform-test-annotations", "platformprotosnano", "statsdprotolite", + "StatsdTestUtils", "hamcrest-library", "servicestests-utils", "testables", diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 37f49838a61b..70e5c2e1b198 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -83,7 +83,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI; -import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER; @@ -128,7 +127,6 @@ import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static java.util.Collections.emptyList; @@ -596,9 +594,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mAcquiredWakeLocks.add(wl); return wl; }); - mTestFlagResolver.setFlagOverride(WAKE_LOCK_FOR_POSTING_NOTIFICATION, true); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.NOTIFY_WAKELOCK, "true", false); // apps allowed as convos mService.setStringArrayResourceValue(PKG_O); @@ -1964,34 +1959,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void enqueueNotification_wakeLockSystemPropertyOff_noWakeLock() throws Exception { - mTestFlagResolver.setFlagOverride(WAKE_LOCK_FOR_POSTING_NOTIFICATION, false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.NOTIFY_WAKELOCK, "true", false); - - mBinderService.enqueueNotificationWithTag(PKG, PKG, - "enqueueNotification_setsWakeLockWorkSource", 0, - generateNotificationRecord(null).getNotification(), 0); - waitForIdle(); - - verifyZeroInteractions(mPowerManager); - } - - @Test - public void enqueueNotification_wakeLockDeviceConfigOff_noWakeLock() throws Exception { - mTestFlagResolver.setFlagOverride(WAKE_LOCK_FOR_POSTING_NOTIFICATION, true); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.NOTIFY_WAKELOCK, "false", false); - - mBinderService.enqueueNotificationWithTag(PKG, PKG, - "enqueueNotification_setsWakeLockWorkSource", 0, - generateNotificationRecord(null).getNotification(), 0); - waitForIdle(); - - verifyZeroInteractions(mPowerManager); - } - - @Test public void testCancelNonexistentNotification() throws Exception { mBinderService.cancelNotificationWithTag(PKG, PKG, "testCancelNonexistentNotification", 0, 0); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 9b745f5aaf4f..020afdbce987 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -44,22 +44,11 @@ import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE; import static android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION; import static android.media.AudioAttributes.USAGE_NOTIFICATION; import static android.os.UserHandle.USER_SYSTEM; -import static android.util.StatsLog.ANNOTATION_ID_IS_UID; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS; -import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES; -import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED; -import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.CHANNEL_ID_FIELD_NUMBER; -import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.CHANNEL_NAME_FIELD_NUMBER; -import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IMPORTANCE_FIELD_NUMBER; -import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IS_CONVERSATION_FIELD_NUMBER; -import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IS_DELETED_FIELD_NUMBER; -import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IS_DEMOTED_CONVERSATION_FIELD_NUMBER; -import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IS_IMPORTANT_CONVERSATION_FIELD_NUMBER; -import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.UID_FIELD_NUMBER; import static com.android.server.notification.NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER; import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE; import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT; @@ -134,6 +123,7 @@ import android.util.ArraySet; import android.util.IntArray; import android.util.Pair; import android.util.StatsEvent; +import android.util.StatsEventTestUtils; import android.util.Xml; import android.util.proto.ProtoOutputStream; @@ -144,11 +134,14 @@ import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; import com.android.internal.config.sysui.TestableFlagResolver; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; +import com.android.os.AtomsProto; +import com.android.os.AtomsProto.PackageNotificationChannelPreferences; import com.android.os.AtomsProto.PackageNotificationPreferences; import com.android.server.UiServiceTestCase; import com.android.server.notification.PermissionHelper.PackagePermission; import com.google.common.collect.ImmutableList; +import com.google.protobuf.InvalidProtocolBufferException; import org.json.JSONArray; import org.json.JSONObject; @@ -218,7 +211,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { private PreferencesHelper mHelper; private AudioAttributes mAudioAttributes; private NotificationChannelLoggerFake mLogger = new NotificationChannelLoggerFake(); - private WrappedSysUiStatsEvent.WrappedBuilderFactory mStatsEventBuilderFactory; @Before public void setUp() throws Exception { @@ -331,11 +323,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { when(mUserProfiles.getCurrentProfileIds()).thenReturn(IntArray.wrap(new int[] {0})); - mStatsEventBuilderFactory = new WrappedSysUiStatsEvent.WrappedBuilderFactory(); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, - mStatsEventBuilderFactory, false); + false); resetZenModeHelper(); mAudioAttributes = new AudioAttributes.Builder() @@ -683,7 +673,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testReadXml_oldXml_migrates() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, - mStatsEventBuilderFactory, /* showReviewPermissionsNotification= */ true); + /* showReviewPermissionsNotification= */ true); String xml = "<ranking version=\"2\">\n" + "<package name=\"" + PKG_N_MR1 + "\" uid=\"" + UID_N_MR1 @@ -825,7 +815,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, - mStatsEventBuilderFactory, /* showReviewPermissionsNotification= */ true); + /* showReviewPermissionsNotification= */ true); String xml = "<ranking version=\"3\">\n" + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" @@ -883,7 +873,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testReadXml_newXml_permissionNotificationOff() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, - mStatsEventBuilderFactory, /* showReviewPermissionsNotification= */ false); + /* showReviewPermissionsNotification= */ false); String xml = "<ranking version=\"3\">\n" + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" @@ -941,7 +931,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, - mStatsEventBuilderFactory, /* showReviewPermissionsNotification= */ true); + /* showReviewPermissionsNotification= */ true); String xml = "<ranking version=\"4\">\n" + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" @@ -1521,7 +1511,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, - mStatsEventBuilderFactory, false); + false); NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_LOW); @@ -5392,7 +5382,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testPullPackageChannelPreferencesStats() { + public void testPullPackageChannelPreferencesStats() throws InvalidProtocolBufferException { String channelId = "parent"; String name = "messages"; NotificationChannel fodderA = new NotificationChannel("a", "a", IMPORTANCE_LOW); @@ -5406,25 +5396,40 @@ public class PreferencesHelperTest extends UiServiceTestCase { ArrayList<StatsEvent> events = new ArrayList<>(); mHelper.pullPackageChannelPreferencesStats(events); - int found = 0; - for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) { - if (builder.getAtomId() == PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES - && channelId.equals(builder.getValue(CHANNEL_ID_FIELD_NUMBER))) { - ++found; - assertEquals("uid", UID_O, builder.getValue(UID_FIELD_NUMBER)); - assertTrue("uid annotation", builder.getBooleanAnnotation( - UID_FIELD_NUMBER, ANNOTATION_ID_IS_UID)); - assertEquals("importance", IMPORTANCE_DEFAULT, builder.getValue( - IMPORTANCE_FIELD_NUMBER)); - assertEquals("name", name, builder.getValue(CHANNEL_NAME_FIELD_NUMBER)); - assertFalse("isconv", builder.getBoolean(IS_CONVERSATION_FIELD_NUMBER)); - assertFalse("deleted", builder.getBoolean(IS_DELETED_FIELD_NUMBER)); + // number of channels with preferences should be 3 total + assertEquals("expected number of events", 3, events.size()); + for (StatsEvent ev : events) { + // all of these events should be of PackageNotificationChannelPreferences type, + // and therefore we expect the atom to have this field. + AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev); + assertTrue(atom.hasPackageNotificationChannelPreferences()); + PackageNotificationChannelPreferences p = + atom.getPackageNotificationChannelPreferences(); + + // uid is shared across all channels; conversation & deleted are not set in any of + // these channels; beyond that check individual channel properties + assertEquals("uid", UID_O, p.getUid()); + assertFalse("is conversation", p.getIsConversation()); + assertFalse("is deleted", p.getIsDeleted()); + + String eventChannelId = p.getChannelId(); + if (eventChannelId.equals(channelId)) { + assertEquals("channel name", name, p.getChannelName()); + assertEquals("importance", IMPORTANCE_DEFAULT, p.getImportance()); + assertFalse("is conversation", p.getIsConversation()); + } else if (eventChannelId.equals("a")) { + assertEquals("channel name", "a", p.getChannelName()); + assertEquals("importance", IMPORTANCE_LOW, p.getImportance()); + } else { // b + assertEquals("channel name", "b", p.getChannelName()); + assertEquals("importance", IMPORTANCE_HIGH, p.getImportance()); } } } @Test - public void testPullPackageChannelPreferencesStats_one_to_one() { + public void testPullPackageChannelPreferencesStats_one_to_one() + throws InvalidProtocolBufferException { NotificationChannel channelA = new NotificationChannel("a", "a", IMPORTANCE_LOW); mHelper.createNotificationChannel(PKG_O, UID_O, channelA, true, false, UID_O, false); NotificationChannel channelB = new NotificationChannel("b", "b", IMPORTANCE_LOW); @@ -5437,19 +5442,22 @@ public class PreferencesHelperTest extends UiServiceTestCase { ArrayList<StatsEvent> events = new ArrayList<>(); mHelper.pullPackageChannelPreferencesStats(events); - int found = 0; - for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) { - if (builder.getAtomId() == PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES) { - Object id = builder.getValue(CHANNEL_ID_FIELD_NUMBER); - assertTrue("missing channel in the output", channels.contains(id)); - channels.remove(id); - } + assertEquals("total events", 3, events.size()); + for (StatsEvent ev : events) { + AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev); + assertTrue(atom.hasPackageNotificationChannelPreferences()); + PackageNotificationChannelPreferences p = + atom.getPackageNotificationChannelPreferences(); + String id = p.getChannelId(); + assertTrue("missing channel in the output", channels.contains(id)); + channels.remove(id); } assertTrue("unexpected channel in output", channels.isEmpty()); } @Test - public void testPullPackageChannelPreferencesStats_conversation() { + public void testPullPackageChannelPreferencesStats_conversation() + throws InvalidProtocolBufferException { String conversationId = "friend"; NotificationChannel parent = @@ -5467,21 +5475,25 @@ public class PreferencesHelperTest extends UiServiceTestCase { ArrayList<StatsEvent> events = new ArrayList<>(); mHelper.pullPackageChannelPreferencesStats(events); - for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) { - if (builder.getAtomId() == PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES - && channelId.equals(builder.getValue(CHANNEL_ID_FIELD_NUMBER))) { - assertTrue("isConveration should be true", builder.getBoolean( - IS_CONVERSATION_FIELD_NUMBER)); - assertFalse("not demoted", builder.getBoolean( - IS_DEMOTED_CONVERSATION_FIELD_NUMBER)); - assertFalse("not important", builder.getBoolean( - IS_IMPORTANT_CONVERSATION_FIELD_NUMBER)); + // In this case, we want to check the properties of the conversation channel (not parent) + assertEquals("total events", 2, events.size()); + for (StatsEvent ev : events) { + AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev); + assertTrue(atom.hasPackageNotificationChannelPreferences()); + PackageNotificationChannelPreferences p = + atom.getPackageNotificationChannelPreferences(); + + if (channelId.equals(p.getChannelId())) { + assertTrue("isConversation should be true", p.getIsConversation()); + assertFalse("not demoted", p.getIsDemotedConversation()); + assertFalse("not important", p.getIsImportantConversation()); } } } @Test - public void testPullPackageChannelPreferencesStats_conversation_demoted() { + public void testPullPackageChannelPreferencesStats_conversation_demoted() + throws InvalidProtocolBufferException { NotificationChannel parent = new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT); mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false, UID_O, false); @@ -5496,21 +5508,23 @@ public class PreferencesHelperTest extends UiServiceTestCase { ArrayList<StatsEvent> events = new ArrayList<>(); mHelper.pullPackageChannelPreferencesStats(events); - for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) { - if (builder.getAtomId() == PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES - && channelId.equals(builder.getValue(CHANNEL_ID_FIELD_NUMBER))) { - assertTrue("isConveration should be true", builder.getBoolean( - IS_CONVERSATION_FIELD_NUMBER)); - assertTrue("is demoted", builder.getBoolean( - IS_DEMOTED_CONVERSATION_FIELD_NUMBER)); - assertFalse("not important", builder.getBoolean( - IS_IMPORTANT_CONVERSATION_FIELD_NUMBER)); + assertEquals("total events", 2, events.size()); + for (StatsEvent ev : events) { + AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev); + assertTrue(atom.hasPackageNotificationChannelPreferences()); + PackageNotificationChannelPreferences p = + atom.getPackageNotificationChannelPreferences(); + if (channelId.equals(p.getChannelId())) { + assertTrue("isConversation should be true", p.getIsConversation()); + assertTrue("is demoted", p.getIsDemotedConversation()); + assertFalse("not important", p.getIsImportantConversation()); } } } @Test - public void testPullPackageChannelPreferencesStats_conversation_priority() { + public void testPullPackageChannelPreferencesStats_conversation_priority() + throws InvalidProtocolBufferException { NotificationChannel parent = new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT); mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false, UID_O, false); @@ -5525,21 +5539,23 @@ public class PreferencesHelperTest extends UiServiceTestCase { ArrayList<StatsEvent> events = new ArrayList<>(); mHelper.pullPackageChannelPreferencesStats(events); - for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) { - if (builder.getAtomId() == PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES - && channelId.equals(builder.getValue(CHANNEL_ID_FIELD_NUMBER))) { - assertTrue("isConveration should be true", builder.getBoolean( - IS_CONVERSATION_FIELD_NUMBER)); - assertFalse("not demoted", builder.getBoolean( - IS_DEMOTED_CONVERSATION_FIELD_NUMBER)); - assertTrue("is important", builder.getBoolean( - IS_IMPORTANT_CONVERSATION_FIELD_NUMBER)); + assertEquals("total events", 2, events.size()); + for (StatsEvent ev : events) { + AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev); + assertTrue(atom.hasPackageNotificationChannelPreferences()); + PackageNotificationChannelPreferences p = + atom.getPackageNotificationChannelPreferences(); + if (channelId.equals(p.getChannelId())) { + assertTrue("isConversation should be true", p.getIsConversation()); + assertFalse("not demoted", p.getIsDemotedConversation()); + assertTrue("is important", p.getIsImportantConversation()); } } } @Test - public void testPullPackagePreferencesStats_postPermissionMigration() { + public void testPullPackagePreferencesStats_postPermissionMigration() + throws InvalidProtocolBufferException { // make sure there's at least one channel for each package we want to test NotificationChannel channelA = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT); mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channelA, true, false, @@ -5568,23 +5584,18 @@ public class PreferencesHelperTest extends UiServiceTestCase { ArrayList<StatsEvent> events = new ArrayList<>(); mHelper.pullPackagePreferencesStats(events, appPermissions); - int found = 0; - for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) { - if (builder.getAtomId() == PACKAGE_NOTIFICATION_PREFERENCES) { - ++found; - int uid = builder.getInt(PackageNotificationPreferences.UID_FIELD_NUMBER); - boolean userSet = builder.getBoolean( - PackageNotificationPreferences.USER_SET_IMPORTANCE_FIELD_NUMBER); - - // if it's one of the expected ids, then make sure the importance matches - assertTrue(expected.containsKey(uid)); - assertThat(expected.get(uid).first).isEqualTo( - builder.getInt(PackageNotificationPreferences.IMPORTANCE_FIELD_NUMBER)); - assertThat(expected.get(uid).second).isEqualTo(userSet); - } + assertEquals("total number of packages", 3, events.size()); + for (StatsEvent ev : events) { + AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev); + assertTrue(atom.hasPackageNotificationPreferences()); + PackageNotificationPreferences p = atom.getPackageNotificationPreferences(); + int uid = p.getUid(); + + // if it's one of the expected ids, then make sure the importance matches + assertTrue(expected.containsKey(uid)); + assertThat(expected.get(uid).first).isEqualTo(p.getImportance()); + assertThat(expected.get(uid).second).isEqualTo(p.getUserSetImportance()); } - // should have at least one entry for each of the packages we expected to see - assertThat(found).isAtLeast(3); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/WrappedSysUiStatsEvent.java b/services/tests/uiservicestests/src/com/android/server/notification/WrappedSysUiStatsEvent.java deleted file mode 100644 index 89adc724f600..000000000000 --- a/services/tests/uiservicestests/src/com/android/server/notification/WrappedSysUiStatsEvent.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.notification; - -import android.util.StatsEvent; - -import com.android.server.notification.SysUiStatsEvent.Builder; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -/** - * Wrapper for SysUiStatsEvent that implements validation. - */ -public class WrappedSysUiStatsEvent { - - static class WrappedBuilder extends Builder { - private ArrayList<Object> mValues; - private HashMap<Integer, HashMap<Byte, Object>> mAnnotations; - private int mAtomId; - private int mLastIndex; - private boolean mBuilt; - - WrappedBuilder(StatsEvent.Builder builder) { - super(builder); - mValues = new ArrayList<>(); - mAnnotations = new HashMap<>(); - mValues.add(0); // proto fields are 1-based - } - - @Override - public Builder setAtomId(int atomId) { - mAtomId = atomId; - super.setAtomId(atomId); - return this; - } - - @Override - public Builder writeInt(int value) { - addValue(Integer.valueOf(value)); - super.writeInt(value); - return this; - } - - @Override - public Builder addBooleanAnnotation(byte annotation, boolean value) { - addAnnotation(annotation, Boolean.valueOf(value)); - super.addBooleanAnnotation(annotation, value); - return this; - } - - @Override - public Builder writeString(String value) { - addValue(value); - super.writeString(value); - return this; - } - - @Override - public Builder writeBoolean(boolean value) { - addValue(Boolean.valueOf(value)); - super.writeBoolean(value); - return this; - } - - @Override - public StatsEvent build() { - mBuilt = true; - return super.build(); - } - - public Object getValue(int index) { - return index < mValues.size() ? mValues.get(index) : null; - } - - /** useful to make assertTrue() statements more readable. */ - public boolean getBoolean(int index) { - return (Boolean) mValues.get(index); - } - - /** useful to make assertTrue() statements more readable. */ - public int getInt(int index) { - return (Integer) mValues.get(index); - } - - /** useful to make assertTrue() statements more readable. */ - public String getString(int index) { - return (String) mValues.get(index); - } - - private void addValue(Object value) { - mLastIndex = mValues.size(); - mValues.add(value); - } - - private void addAnnotation(byte annotation, Object value) { - Integer key = Integer.valueOf(mLastIndex); - if (!mAnnotations.containsKey(key)) { - mAnnotations.put(key, new HashMap<>()); - } - mAnnotations.get(key).put(Byte.valueOf(annotation), value); - } - - public boolean getBooleanAnnotation(int i, byte a) { - return ((Boolean) mAnnotations.get(Integer.valueOf(i)).get(Byte.valueOf(a))) - .booleanValue(); - } - - public int getAtomId() { - return mAtomId; - } - } - - static class WrappedBuilderFactory extends SysUiStatsEvent.BuilderFactory { - public List<WrappedBuilder> builders; - - WrappedBuilderFactory() { - builders = new ArrayList<>(); - } - - @Override - Builder newBuilder() { - WrappedBuilder b = new WrappedBuilder(StatsEvent.newBuilder()); - builders.add(b); - return b; - } - } -} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 3ee75de23fdb..e540068e6bc6 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -33,18 +33,12 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; +import static android.provider.Settings.Global.ZEN_MODE_ALARMS; import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; import static android.service.notification.Condition.STATE_FALSE; import static android.service.notification.Condition.STATE_TRUE; -import static android.util.StatsLog.ANNOTATION_ID_IS_UID; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.LOG_DND_STATE_EVENTS; -import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; -import static com.android.os.dnd.DNDModeProto.CHANNELS_BYPASSING_FIELD_NUMBER; -import static com.android.os.dnd.DNDModeProto.ENABLED_FIELD_NUMBER; -import static com.android.os.dnd.DNDModeProto.ID_FIELD_NUMBER; -import static com.android.os.dnd.DNDModeProto.UID_FIELD_NUMBER; -import static com.android.os.dnd.DNDModeProto.ZEN_MODE_FIELD_NUMBER; import static com.android.os.dnd.DNDProtoEnums.PEOPLE_STARRED; import static com.android.os.dnd.DNDProtoEnums.ROOT_CONFIG; import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW; @@ -73,6 +67,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.SuppressLint; import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.AutomaticZenRule; @@ -106,6 +101,7 @@ import android.testing.TestableLooper; import android.util.ArrayMap; import android.util.Log; import android.util.StatsEvent; +import android.util.StatsEventTestUtils; import android.util.Xml; import com.android.internal.R; @@ -113,12 +109,15 @@ import com.android.internal.config.sysui.TestableFlagResolver; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; +import com.android.os.AtomsProto; +import com.android.os.dnd.DNDModeProto; import com.android.os.dnd.DNDPolicyProto; import com.android.os.dnd.DNDProtoEnums; import com.android.server.UiServiceTestCase; import com.android.server.notification.ManagedServices.UserProfiles; import com.google.common.collect.ImmutableList; +import com.google.protobuf.InvalidProtocolBufferException; import org.junit.Before; import org.junit.Test; @@ -140,13 +139,13 @@ import java.util.List; import java.util.Objects; @SmallTest +@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service. @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper public class ZenModeHelperTest extends UiServiceTestCase { private static final String EVENTS_DEFAULT_RULE_ID = "EVENTS_DEFAULT_RULE"; private static final String SCHEDULE_DEFAULT_RULE_ID = "EVERY_NIGHT_DEFAULT_RULE"; - private static final int ZEN_MODE_FOR_TESTING = 99; private static final String CUSTOM_PKG_NAME = "not.android"; private static final int CUSTOM_PKG_UID = 1; private static final String CUSTOM_RULE_ID = "custom_rule"; @@ -159,7 +158,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { private ZenModeHelper mZenModeHelper; private ContentResolver mContentResolver; @Mock AppOpsManager mAppOps; - private WrappedSysUiStatsEvent.WrappedBuilderFactory mStatsEventBuilderFactory; TestableFlagResolver mTestFlagResolver = new TestableFlagResolver(); ZenModeEventLoggerFake mZenModeEventLogger; @@ -178,7 +176,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { Log.d("ZenModeHelperTest", "Couldn't mock default zen mode config xml file err=" + e.toString()); } - mStatsEventBuilderFactory = new WrappedSysUiStatsEvent.WrappedBuilderFactory(); when(mContext.getSystemService(AppOpsManager.class)).thenReturn(mAppOps); when(mContext.getSystemService(NotificationManager.class)).thenReturn(mNotificationManager); @@ -188,8 +185,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mConditionProviders.addSystemProvider(new ScheduleConditionProvider()); mZenModeEventLogger = new ZenModeEventLoggerFake(mPackageManager); mZenModeHelper = new ZenModeHelper(mContext, mTestableLooper.getLooper(), - mConditionProviders, mStatsEventBuilderFactory, mTestFlagResolver, - mZenModeEventLogger); + mConditionProviders, mTestFlagResolver, mZenModeEventLogger); ResolveInfo ri = new ResolveInfo(); ri.activityInfo = new ActivityInfo(); @@ -911,37 +907,35 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - public void testProto() { + public void testProto() throws InvalidProtocolBufferException { mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; // existence of manual rule means it should be in output mZenModeHelper.mConfig.manualRule = new ZenModeConfig.ZenRule(); mZenModeHelper.mConfig.manualRule.pkg = "android"; // system + mZenModeHelper.mConfig.automaticRules = new ArrayMap<>(); // no automatic rules - int n = mZenModeHelper.mConfig.automaticRules.size(); - List<String> ids = new ArrayList<>(n); - for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) { - ids.add(rule.id); - } + List<String> ids = new ArrayList<>(); ids.add(ZenModeConfig.MANUAL_RULE_ID); ids.add(""); // for ROOT_CONFIG, logged with empty string as id List<StatsEvent> events = new LinkedList<>(); mZenModeHelper.pullRules(events); - assertEquals(n + 2, events.size()); // automatic rules + manual rule + root config - for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) { - if (builder.getAtomId() == DND_MODE_RULE) { - if (builder.getInt(ZEN_MODE_FIELD_NUMBER) == ROOT_CONFIG) { - assertTrue(builder.getBoolean(ENABLED_FIELD_NUMBER)); - assertFalse(builder.getBoolean(CHANNELS_BYPASSING_FIELD_NUMBER)); - } - assertEquals(Process.SYSTEM_UID, builder.getInt(UID_FIELD_NUMBER)); - assertTrue(builder.getBooleanAnnotation(UID_FIELD_NUMBER, ANNOTATION_ID_IS_UID)); - String name = (String) builder.getValue(ID_FIELD_NUMBER); - assertTrue("unexpected rule id", ids.contains(name)); - ids.remove(name); - } else { - fail("unexpected atom id: " + builder.getAtomId()); + assertEquals(2, events.size()); // manual rule + root config + for (StatsEvent ev : events) { + AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev); + assertTrue(atom.hasDndModeRule()); + DNDModeProto cfg = atom.getDndModeRule(); + // Additional check for ID to clearly identify the root config because there's some + // odd behavior in the test util around enum value of 0 (the usual default, but not in + // this case). + if (cfg.getZenMode().getNumber() == ROOT_CONFIG && cfg.getId().equals("")) { + assertTrue(cfg.getEnabled()); + assertFalse(cfg.getChannelsBypassing()); } + assertEquals(Process.SYSTEM_UID, cfg.getUid()); + String name = cfg.getId(); + assertTrue("unexpected rule id", ids.contains(name)); + ids.remove(name); } assertEquals("extra rule in output", 0, ids.size()); } @@ -949,28 +943,61 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testProtoWithAutoRule() throws Exception { setupZenConfig(); - // one enabled automatic rule - mZenModeHelper.mConfig.automaticRules = getCustomAutomaticRules(ZEN_MODE_FOR_TESTING); + // one enabled automatic rule. we use a non-usual zen mode value (though it has to be + // a real one in the enum because non-valid enum values are reverted to default). + mZenModeHelper.mConfig.automaticRules = getCustomAutomaticRules(ZEN_MODE_ALARMS); List<StatsEvent> events = new LinkedList<>(); mZenModeHelper.pullRules(events); boolean foundCustomEvent = false; - for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) { - if (builder.getAtomId() == DND_MODE_RULE) { - if (ZEN_MODE_FOR_TESTING == builder.getInt(ZEN_MODE_FIELD_NUMBER)) { - foundCustomEvent = true; - assertEquals(CUSTOM_PKG_UID, builder.getInt(UID_FIELD_NUMBER)); - assertTrue(builder.getBoolean(ENABLED_FIELD_NUMBER)); - } - } else { - fail("unexpected atom id: " + builder.getAtomId()); + for (StatsEvent ev : events) { + AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev); + assertTrue(atom.hasDndModeRule()); + DNDModeProto cfg = atom.getDndModeRule(); + if (cfg.getZenMode().getNumber() == ZEN_MODE_ALARMS) { + foundCustomEvent = true; + assertEquals(CUSTOM_PKG_UID, cfg.getUid()); + assertTrue(cfg.getEnabled()); } } assertTrue("couldn't find custom rule", foundCustomEvent); } @Test + public void testProtoWithDefaultAutoRules() throws Exception { + setupZenConfig(); + // clear the automatic rules so we can reset to only the default rules + mZenModeHelper.mConfig.automaticRules = new ArrayMap<>(); + + // read in XML to restore the default rules + ByteArrayOutputStream baos = writeXmlAndPurge(5); + TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(baos.toByteArray())), null); + parser.nextTag(); + mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL); + List<StatsEvent> events = new LinkedList<>(); + mZenModeHelper.pullRules(events); + + // list for tracking which ids we've seen in the pulled atom output + List<String> ids = new ArrayList<>(); + ids.addAll(ZenModeConfig.DEFAULT_RULE_IDS); + ids.add(""); // empty string for root config + + for (StatsEvent ev : events) { + AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev); + assertTrue(atom.hasDndModeRule()); + DNDModeProto cfg = atom.getDndModeRule(); + if (!ids.contains(cfg.getId())) { + fail("unexpected ID found: " + cfg.getId()); + } + ids.remove(cfg.getId()); + } + assertEquals("default ID(s) not found", 0, ids.size()); + } + + @Test public void ruleUidsCached() throws Exception { setupZenConfig(); // one enabled automatic rule @@ -1019,10 +1046,11 @@ public class ZenModeHelperTest extends UiServiceTestCase { List<StatsEvent> events = new LinkedList<>(); mZenModeHelper.pullRules(events); - boolean foundCustomEvent = false; - for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) { - if (builder.getAtomId() == DND_MODE_RULE - && "customRule".equals(builder.getString(ID_FIELD_NUMBER))) { + for (StatsEvent ev : events) { + AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev); + assertTrue(atom.hasDndModeRule()); + DNDModeProto cfg = atom.getDndModeRule(); + if ("customRule".equals(cfg.getId())) { fail("non-default IDs should be redacted"); } } @@ -1039,14 +1067,17 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.pullRules(events); boolean foundManualRule = false; - for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) { - if (builder.getAtomId() == DND_MODE_RULE - && ZenModeConfig.MANUAL_RULE_ID.equals(builder.getString(ID_FIELD_NUMBER))) { - assertEquals(0, builder.getInt(UID_FIELD_NUMBER)); + for (StatsEvent ev : events) { + AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev); + assertTrue(atom.hasDndModeRule()); + DNDModeProto cfg = atom.getDndModeRule(); + if (ZenModeConfig.MANUAL_RULE_ID.equals(cfg.getId())) { + assertEquals(0, cfg.getUid()); foundManualRule = true; } } - assertTrue("couldn't find manual rule", foundManualRule); } + assertTrue("couldn't find manual rule", foundManualRule); + } @Test public void testWriteXml_onlyBackupsTargetUser() throws Exception { diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 302ad7f33b7c..31682bc9e879 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -1541,7 +1541,8 @@ public class ActivityRecordTests extends WindowTestsBase { // Make keyguard locked and set the top activity show-when-locked. KeyguardController keyguardController = activity.mTaskSupervisor.getKeyguardController(); int displayId = activity.getDisplayId(); - doReturn(true).when(keyguardController).isKeyguardLocked(eq(displayId)); + keyguardController.setKeyguardShown(displayId, true /* keyguardShowing */, + false /* aodShowing */); final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build(); topActivity.setVisibleRequested(true); topActivity.nowVisible = true; @@ -1553,7 +1554,7 @@ public class ActivityRecordTests extends WindowTestsBase { // Verify the stack-top activity is occluded keyguard. assertEquals(topActivity, task.topRunningActivity()); - assertTrue(keyguardController.isDisplayOccluded(DEFAULT_DISPLAY)); + assertTrue(keyguardController.isKeyguardOccluded(displayId)); // Finish the top activity topActivity.setState(PAUSED, "true"); @@ -1562,7 +1563,7 @@ public class ActivityRecordTests extends WindowTestsBase { // Verify new top activity does not occlude keyguard. assertEquals(activity, task.topRunningActivity()); - assertFalse(keyguardController.isDisplayOccluded(DEFAULT_DISPLAY)); + assertFalse(keyguardController.isKeyguardOccluded(displayId)); } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java index d169a5854699..5341588c3992 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java @@ -24,7 +24,6 @@ import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_FIRST_ORDERED_ID; import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_FIRST_ORDERED_ID; @@ -41,11 +40,9 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.doCallRealMethod; -import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.annotation.Nullable; @@ -60,7 +57,6 @@ import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; -import android.os.IBinder; import android.os.LocaleList; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; @@ -76,7 +72,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; -import org.mockito.MockitoSession; import java.util.ArrayList; import java.util.List; @@ -304,18 +299,11 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { */ @Test public void testEnterPipModeWhenRecordParentChangesToNull() { - MockitoSession mockSession = mockitoSession() - .initMocks(this) - .mockStatic(ActivityRecord.class) - .startMocking(); - - ActivityRecord record = mock(ActivityRecord.class); - IBinder token = mock(IBinder.class); + final ActivityRecord record = new ActivityBuilder(mAtm).setCreateTask(true).build(); PictureInPictureParams params = mock(PictureInPictureParams.class); record.pictureInPictureArgs = params; //mock operations in private method ensureValidPictureInPictureActivityParamsLocked() - when(ActivityRecord.forTokenLocked(token)).thenReturn(record); doReturn(true).when(record).supportsPictureInPicture(); doReturn(false).when(params).hasSetAspectRatio(); @@ -323,15 +311,13 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { doReturn(true).when(record) .checkEnterPictureInPictureState("enterPictureInPictureMode", false); doReturn(false).when(record).inPinnedWindowingMode(); - doReturn(false).when(mAtm).isKeyguardLocked(anyInt()); + doReturn(false).when(record).isKeyguardLocked(); //to simulate NPE doReturn(null).when(record).getParent(); - mAtm.mActivityClientController.enterPictureInPictureMode(token, params); + mAtm.mActivityClientController.enterPictureInPictureMode(record.token, params); //if record's null parent is not handled gracefully, test will fail with NPE - - mockSession.finishMocking(); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java index 4473a31f0513..c84fe0871d3f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java @@ -123,7 +123,7 @@ public class ContentRecordingControllerTests extends WindowTestsBase { controller.setContentRecordingSessionLocked(mWaitingDisplaySession, mWm); verify(mVirtualDisplayContent, atLeastOnce()).setContentRecordingSession( mWaitingDisplaySession); - verify(mVirtualDisplayContent).updateRecording(); + verify(mVirtualDisplayContent, atLeastOnce()).updateRecording(); // WHEN updating the session on the same display, so no longer waiting to record. ContentRecordingSession sessionUpdate = ContentRecordingSession.createTaskSession( diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index a2b7da379eea..ae4ebc1223b2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -1698,14 +1698,17 @@ public class DisplayContentTests extends WindowTestsBase { final Task task = app.getTask(); final ActivityRecord app2 = new ActivityBuilder(mWm.mAtmService).setTask(task).build(); mDisplayContent.setFixedRotationLaunchingApp(app2, (mDisplayContent.getRotation() + 1) % 4); - doReturn(true).when(app).isInTransition(); + doReturn(true).when(app).inTransitionSelfOrParent(); // If the task contains a transition, this should be no-op. mDisplayContent.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(app.token); assertTrue(app2.hasFixedRotationTransform()); assertTrue(mDisplayContent.hasTopFixedRotationLaunchingApp()); - doReturn(false).when(app).isInTransition(); + // The display should be unlikely to be in transition, but if it happens, the fixed + // rotation should proceed to finish because the activity/task level transition is finished. + doReturn(true).when(mDisplayContent).inTransition(); + doReturn(false).when(app).inTransitionSelfOrParent(); // Although this notifies app instead of app2 that uses the fixed rotation, app2 should // still finish the transform because there is no more transition event. mDisplayContent.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(app.token); diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java index c3db241f28ae..41659113b1b2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java @@ -26,7 +26,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -35,9 +34,14 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; +import static java.util.Objects.requireNonNull; + import android.app.ActivityThread; import android.app.IApplicationThread; +import android.app.WindowConfiguration; +import android.content.ComponentCallbacks; import android.content.Context; +import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; import android.hardware.display.DisplayManagerGlobal; @@ -50,6 +54,8 @@ import android.view.WindowManagerGlobal; import android.window.WindowContextInfo; import android.window.WindowTokenClient; +import androidx.annotation.NonNull; + import com.android.server.inputmethod.InputMethodDialogWindowContext; import org.junit.After; @@ -58,6 +64,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + // TODO(b/157888351): Move the test to inputmethod package once we find the way to test the // scenario there. /** @@ -138,44 +147,96 @@ public class InputMethodDialogWindowContextTest extends WindowTestsBase { @Test public void testGetSettingsContextOnDualDisplayContent() { final Context context = mWindowContext.get(mSecondaryDisplay.getDisplayId()); + final MaxBoundsVerifier maxBoundsVerifier = new MaxBoundsVerifier(); + context.registerComponentCallbacks(maxBoundsVerifier); + final WindowTokenClient tokenClient = (WindowTokenClient) context.getWindowContextToken(); - assertNotNull(tokenClient); - spyOn(tokenClient); + spyOn(requireNonNull(tokenClient)); final DisplayArea.Tokens imeContainer = mSecondaryDisplay.getImeContainer(); spyOn(imeContainer); assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay); - mSecondaryDisplay.mFirstRoot.placeImeContainer(imeContainer); + final DisplayAreaGroup firstDaGroup = mSecondaryDisplay.mFirstRoot; + maxBoundsVerifier.setMaxBounds(firstDaGroup.getMaxBounds()); + + firstDaGroup.placeImeContainer(imeContainer); verify(imeContainer, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged( - eq(mSecondaryDisplay.mFirstRoot.getConfiguration())); + eq(firstDaGroup.getConfiguration())); verify(tokenClient, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged( - eq(mSecondaryDisplay.mFirstRoot.getConfiguration()), + eq(firstDaGroup.getConfiguration()), eq(mSecondaryDisplay.mDisplayId)); - assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mFirstRoot); + assertThat(imeContainer.getRootDisplayArea()).isEqualTo(firstDaGroup); + maxBoundsVerifier.waitAndAssertMaxMetricsMatches(); assertImeSwitchContextMetricsValidity(context, mSecondaryDisplay); // Clear the previous invocation histories in case we may count the previous // onConfigurationChanged invocation into the next verification. clearInvocations(tokenClient, imeContainer); - mSecondaryDisplay.mSecondRoot.placeImeContainer(imeContainer); + final DisplayAreaGroup secondDaGroup = mSecondaryDisplay.mSecondRoot; + maxBoundsVerifier.setMaxBounds(secondDaGroup.getMaxBounds()); + + secondDaGroup.placeImeContainer(imeContainer); verify(imeContainer, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged( - eq(mSecondaryDisplay.mSecondRoot.getConfiguration())); + eq(secondDaGroup.getConfiguration())); verify(tokenClient, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged( - eq(mSecondaryDisplay.mSecondRoot.getConfiguration()), + eq(secondDaGroup.getConfiguration()), eq(mSecondaryDisplay.mDisplayId)); - assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mSecondRoot); + assertThat(imeContainer.getRootDisplayArea()).isEqualTo(secondDaGroup); + maxBoundsVerifier.waitAndAssertMaxMetricsMatches(); assertImeSwitchContextMetricsValidity(context, mSecondaryDisplay); } private void assertImeSwitchContextMetricsValidity(Context context, DisplayContent dc) { assertThat(context.getDisplayId()).isEqualTo(dc.getDisplayId()); + final Rect imeContainerBounds = dc.getImeContainer().getBounds(); final Rect contextBounds = context.getSystemService(WindowManager.class) .getMaximumWindowMetrics().getBounds(); - final Rect imeContainerBounds = dc.getImeContainer().getBounds(); assertThat(contextBounds).isEqualTo(imeContainerBounds); } + + private static final class MaxBoundsVerifier implements ComponentCallbacks { + + private CountDownLatch mLatch; + + private Rect mMaxBounds; + + /** + * Sets max bounds to verify whether it matches the + * {@link WindowConfiguration#getMaxBounds()} reported from + * {@link #onConfigurationChanged(Configuration)} callback, and also resets the count down + * latch. + * + * @param maxBounds max bounds to verify + */ + private void setMaxBounds(@NonNull Rect maxBounds) { + mMaxBounds = maxBounds; + mLatch = new CountDownLatch(1); + } + + /** + * Waits for the {@link #onConfigurationChanged(Configuration)} callback whose the reported + * {@link WindowConfiguration#getMaxBounds()} matches {@link #mMaxBounds}. + */ + private void waitAndAssertMaxMetricsMatches() { + try { + assertThat(mLatch.await(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); + } catch (InterruptedException e) { + throw new RuntimeException("Test failed because of " + e); + } + } + + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + if (newConfig.windowConfiguration.getMaxBounds().equals(mMaxBounds)) { + mLatch.countDown(); + } + } + + @Override + public void onLowMemory() {} + } } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 13e5ff15f1d8..0bb75d87ffc1 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1999,7 +1999,7 @@ public class CarrierConfigManager { "lte_plus_threshold_bandwidth_khz_int"; /** - * The combined channel bandwidth threshold (non-inclusive) in KHz required to display the + * The combined channel bandwidth threshold (inclusive) in KHz required to display the * NR advanced (i.e. 5G+) data icon. It is 0 by default, meaning minimum bandwidth check is * not enabled. Other factors like bands or frequency can also determine whether the NR * advanced data icon is shown or not. diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp index b94d14fd4b75..0fc2617bb9f2 100644 --- a/tools/aapt/AaptAssets.cpp +++ b/tools/aapt/AaptAssets.cpp @@ -1540,7 +1540,7 @@ status_t AaptAssets::buildIncludedResources(Bundle* bundle) } const String8& featureOfBase = bundle->getFeatureOfPackage(); - if (!featureOfBase.isEmpty()) { + if (!featureOfBase.empty()) { if (bundle->getVerbose()) { printf("Including base feature resources from package: %s\n", featureOfBase.c_str()); diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index 5a06b102592a..60f3f2715395 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -1133,7 +1133,7 @@ int doDump(Bundle* bundle) if (code == ResXMLTree::END_TAG) { depth--; if (depth < 2) { - if (withinSupportsInput && !supportedInput.isEmpty()) { + if (withinSupportsInput && !supportedInput.empty()) { printf("supports-input: '"); const size_t N = supportedInput.size(); for (size_t i=0; i<N; i++) { @@ -1300,7 +1300,7 @@ int doDump(Bundle* bundle) ResTable::normalizeForOutput(versionName.c_str()).c_str()); String8 splitName = AaptXml::getAttribute(tree, NULL, "split"); - if (!splitName.isEmpty()) { + if (!splitName.empty()) { printf(" split='%s'", ResTable::normalizeForOutput( splitName.c_str()).c_str()); } diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index 9c944e0de075..4a360ed1c80e 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -1285,7 +1285,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil packageType = ResourceTable::SharedLibrary; } else if (bundle->getExtending()) { packageType = ResourceTable::System; - } else if (!bundle->getFeatureOfPackage().isEmpty()) { + } else if (!bundle->getFeatureOfPackage().empty()) { packageType = ResourceTable::AppFeature; } @@ -3144,7 +3144,7 @@ writeProguardForXml(ProguardKeepSet* keep, const sp<AaptFile>& layoutFile, tree.restart(); - if (!startTags.isEmpty()) { + if (!startTags.empty()) { bool haveStart = false; while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { if (code != ResXMLTree::START_TAG) { diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index 449e0808806e..bc252cfff239 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -1813,7 +1813,7 @@ status_t ResourceTable::addIncludedResources(Bundle* bundle, const sp<AaptAssets mTypeIdOffset = findLargestTypeIdForPackage(assets->getIncludedResources(), mAssetsPackage); const String8& featureAfter = bundle->getFeatureAfterPackage(); - if (!featureAfter.isEmpty()) { + if (!featureAfter.empty()) { AssetManager featureAssetManager; if (!featureAssetManager.addAssetPath(featureAfter, NULL)) { fprintf(stderr, "ERROR: Feature package '%s' not found.\n", @@ -1823,7 +1823,7 @@ status_t ResourceTable::addIncludedResources(Bundle* bundle, const sp<AaptAssets const ResTable& featureTable = featureAssetManager.getResources(false); mTypeIdOffset = std::max(mTypeIdOffset, - findLargestTypeIdForPackage(featureTable, mAssetsPackage)); + findLargestTypeIdForPackage(featureTable, mAssetsPackage)); } return NO_ERROR; @@ -3252,7 +3252,7 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& // If we're building splits, then each invocation of the flattening // step will have 'missing' entries. Don't warn/error for this case. - if (bundle->getSplitConfigurations().isEmpty()) { + if (bundle->getSplitConfigurations().empty()) { bool missing_entry = false; const char* log_prefix = bundle->getErrorOnMissingConfigEntry() ? "error" : "warning"; @@ -4858,7 +4858,7 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle, Vector<sp<XMLNode> > nodesToVisit; nodesToVisit.push(root); - while (!nodesToVisit.isEmpty()) { + while (!nodesToVisit.empty()) { sp<XMLNode> node = nodesToVisit.top(); nodesToVisit.pop(); diff --git a/tools/aapt/SourcePos.cpp b/tools/aapt/SourcePos.cpp index e13028684414..354a65c14772 100644 --- a/tools/aapt/SourcePos.cpp +++ b/tools/aapt/SourcePos.cpp @@ -78,7 +78,7 @@ ErrorPos::print(FILE* to) const break; } - if (!this->file.isEmpty()) { + if (!this->file.empty()) { if (this->line >= 0) { fprintf(to, "%s:%d: %s%s\n", this->file.c_str(), this->line, type, this->error.c_str()); } else { diff --git a/tools/aapt2/DominatorTree_test.cpp b/tools/aapt2/DominatorTree_test.cpp index 52949da1b64f..a0679a65b9fd 100644 --- a/tools/aapt2/DominatorTree_test.cpp +++ b/tools/aapt2/DominatorTree_test.cpp @@ -50,8 +50,7 @@ class PrettyPrinter : public DominatorTree::Visitor { private: void VisitConfig(const DominatorTree::Node* node, const int indent) { auto config_string = node->value()->config.toString(); - buffer_ << std::string(indent, ' ') - << (config_string.isEmpty() ? "<default>" : config_string) + buffer_ << std::string(indent, ' ') << (config_string.empty() ? "<default>" : config_string) << std::endl; } diff --git a/tools/split-select/SplitDescription.cpp b/tools/split-select/SplitDescription.cpp index 4e2b48e1f450..71500080193f 100644 --- a/tools/split-select/SplitDescription.cpp +++ b/tools/split-select/SplitDescription.cpp @@ -70,7 +70,7 @@ bool SplitDescription::match(const SplitDescription& o) const { String8 SplitDescription::toString() const { String8 extension; if (abi != abi::Variant_none) { - if (extension.isEmpty()) { + if (extension.empty()) { extension.append(":"); } else { extension.append("-"); |