diff options
181 files changed, 4372 insertions, 1566 deletions
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/core/api/current.txt b/core/api/current.txt index d26cf2e60121..e00362407fc7 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -39351,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/system-current.txt b/core/api/system-current.txt index 427516735f95..adbd06c10f47 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -13262,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/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/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/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/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/symbols.xml b/core/res/res/values/symbols.xml index 889901af8fd4..e2fc837b9a64 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" /> 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/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/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/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/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/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 ffb20d87a7f8..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 @@ -25,6 +27,9 @@ 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 @@ -82,7 +87,18 @@ fun SceneContainer( onChangeScene = viewModel::onSceneChanged, transitions = SceneContainerTransitions, state = state, - modifier = modifier.fillMaxSize().motionEventSpy { viewModel.onUserInput() }, + 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/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/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/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 6b578baddcd2..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") @@ -394,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 @@ -634,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") 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/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/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/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt index 342c218c41e5..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 @@ -168,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/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/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/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt index c5b605ad7877..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,12 +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) } - /** Handles an event representing user input. */ - fun onUserInput() { - interactor.onUserInput() + /** + * 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..54b806dd8d19 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -65,6 +65,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; @@ -118,6 +120,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 +156,8 @@ public class NotificationShadeWindowViewController { StatusBarWindowStateController statusBarWindowStateController, LockIconViewController lockIconViewController, CentralSurfaces centralSurfaces, + DozeServiceHost dozeServiceHost, + DozeScrimController dozeScrimController, BackActionInteractor backActionInteractor, PowerInteractor powerInteractor, NotificationShadeWindowController controller, @@ -187,8 +193,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 +233,8 @@ public class NotificationShadeWindowViewController { progressProvider -> progressProvider.addCallback( mDisableSubpixelTextTransitionListener)); } + + lockIconViewController.setLockIconView(mView.findViewById(R.id.lock_icon_view)); } /** @@ -332,7 +341,7 @@ public class NotificationShadeWindowViewController { } if (mStatusBarStateController.isDozing()) { - mService.extendDozePulse(); + mDozeScrimController.extendPulse(); } mLockIconViewController.onTouchEvent( ev, @@ -391,7 +400,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 +454,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)) { 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/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/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/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/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 index 72a5cfeaf052..10422e31dfaa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt @@ -41,8 +41,6 @@ abstract class CentralSurfacesEmptyImpl : CentralSurfaces { override fun getKeyguardMessageArea(): AuthKeyguardMessageArea? = null override fun isLaunchingActivityOverLockscreen() = false override fun onKeyguardViewManagerStatesUpdated() {} - override fun isPulsing() = false - override fun isOccluded() = false override fun isDeviceInVrMode() = false override fun getPresenter(): NotificationPresenter? = null override fun onInputFocusTransfer(start: Boolean, cancel: Boolean, velocity: Float) {} @@ -55,7 +53,6 @@ abstract class CentralSurfacesEmptyImpl : CentralSurfaces { override fun dump(pwOriginal: PrintWriter, args: Array<String>) {} override fun getDisplayWidth() = 0f override fun getDisplayHeight() = 0f - override fun readyForKeyguardDone() {} override fun showKeyguard() {} override fun hideKeyguard() = false override fun showKeyguardImpl() {} @@ -117,7 +114,6 @@ abstract class CentralSurfacesEmptyImpl : CentralSurfaces { override fun setLaunchEmergencyActionOnFinishedWaking(launch: Boolean) {} override fun getQSPanelController(): QSPanelController? = null override fun getDisplayDensity() = 0f - override fun extendDozePulse() {} override fun setIsLaunchingActivityOverLockscreen(isLaunchingActivityOverLockscreen: Boolean) {} override fun getAnimatorControllerFromNotification( associatedView: ExpandableNotificationRow?, 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 4aa8b6bddb09..605b3d23be2d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -1480,7 +1480,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) @@ -1709,22 +1709,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(); } @@ -2077,11 +2061,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) { @@ -2325,11 +2304,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) { @@ -2852,7 +2832,7 @@ 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 @@ -3130,7 +3110,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); @@ -3166,7 +3146,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) { @@ -3403,11 +3385,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } - @Override - public void extendDozePulse(){ - mDozeScrimController.extendPulse(); - } - private final KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() { @Override 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/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/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/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/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/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 0ecbcdebc68c..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 = @@ -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/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index d60d99430860..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,6 +62,7 @@ class SceneContainerStartableTest : SysuiTestCase() { private val authenticationInteractor = utils.authenticationInteractor( repository = authenticationRepository, + sceneInteractor = sceneInteractor, ) private val keyguardRepository = utils.keyguardRepository private val keyguardInteractor = @@ -67,6 +70,7 @@ class SceneContainerStartableTest : SysuiTestCase() { 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..2aea1f934fd0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -56,6 +56,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 +92,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 @@ -168,6 +172,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { statusBarWindowStateController, lockIconViewController, centralSurfaces, + dozeServiceHost, + dozeScrimController, backActionInteractor, powerInteractor, notificationShadeWindowController, 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..1ab613485c9c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -56,6 +56,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 +91,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 @@ -174,6 +178,8 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { statusBarWindowStateController, lockIconViewController, centralSurfaces, + dozeServiceHost, + dozeScrimController, backActionInteractor, powerInteractor, notificationShadeWindowController, 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/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/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/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 7ebf6c81d2d2..ac9cfb3cfb23 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/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index 4f33a97139b2..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 @@ -98,6 +101,9 @@ class SceneTestUtils( private val context = test.context + private val falsingCollectorFake: FalsingCollector by lazy { FalsingCollectorFake() } + private var falsingInteractor: FalsingInteractor? = null + fun fakeSceneContainerRepository( containerConfig: SceneContainerConfig = fakeSceneContainerConfig(), ): SceneContainerRepository { @@ -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/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/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/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/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/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index be6133b5b6ae..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; @@ -6676,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/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/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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 823c9af27de6..ac7842948639 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1971,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"); @@ -1987,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, 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/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/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 9eec5f8c4bb6..ead7f1bf923a 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -2698,7 +2698,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 +2712,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 +2727,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 +2766,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); } } 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/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/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/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/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/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/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/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 |