Merge "Replace onTaskMovedToFront with a transition observer in bubbles" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 7005349..fd92979b 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -18,6 +18,7 @@
// Add java_aconfig_libraries to here to add them to the core framework
srcs: [
+ ":android.os.flags-aconfig-java{.generated_srcjars}",
":android.security.flags-aconfig-java{.generated_srcjars}",
":camera_platform_flags_core_java_lib{.generated_srcjars}",
":com.android.window.flags.window-aconfig-java{.generated_srcjars}",
@@ -78,3 +79,16 @@
aconfig_declarations: "android.security.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// OS
+aconfig_declarations {
+ name: "android.os.flags-aconfig",
+ package: "android.os",
+ srcs: ["core/java/android/os/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.os.flags-aconfig-java",
+ aconfig_declarations: "android.os.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp
index 28db61f..6624812 100644
--- a/cmds/app_process/app_main.cpp
+++ b/cmds/app_process/app_main.cpp
@@ -49,7 +49,7 @@
virtual void onVmCreated(JNIEnv* env)
{
- if (mClassName.isEmpty()) {
+ if (mClassName.empty()) {
return; // Zygote. Nothing to do here.
}
@@ -98,7 +98,7 @@
virtual void onExit(int code)
{
- if (mClassName.isEmpty()) {
+ if (mClassName.empty()) {
// if zygote
IPCThreadState::self()->stopProcess();
hardware::IPCThreadState::self()->stopProcess();
@@ -282,7 +282,7 @@
}
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 @@
}
}
- if (!niceName.isEmpty()) {
+ if (!niceName.empty()) {
runtime.setArgv0(niceName.string(), true /* setProcName */);
}
if (zygote) {
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
- } else if (!className.isEmpty()) {
+ } else if (!className.empty()) {
runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
} else {
fprintf(stderr, "Error: no class name or --zygote supplied.\n");
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index b56dceb..2cbd665 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -723,7 +723,7 @@
bool BootAnimation::preloadAnimation() {
ATRACE_CALL();
findBootAnimationFile();
- if (!mZipFileName.isEmpty()) {
+ if (!mZipFileName.empty()) {
mAnimation = loadAnimation(mZipFileName);
return (mAnimation != nullptr);
}
@@ -842,7 +842,7 @@
// We have no bootanimation file, so we use the stock android logo
// animation.
- if (mZipFileName.isEmpty()) {
+ if (mZipFileName.empty()) {
ALOGD("No animation file");
result = android();
} else {
diff --git a/core/api/current.txt b/core/api/current.txt
index bcb21e5..e003624 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -10560,6 +10560,7 @@
public final class ContextParams {
method @Nullable public String getAttributionTag();
method @Nullable public android.content.AttributionSource getNextAttributionSource();
+ method @NonNull public boolean shouldRegisterAttributionSource();
}
public static final class ContextParams.Builder {
@@ -10568,6 +10569,7 @@
method @NonNull public android.content.ContextParams build();
method @NonNull public android.content.ContextParams.Builder setAttributionTag(@Nullable String);
method @NonNull public android.content.ContextParams.Builder setNextAttributionSource(@Nullable android.content.AttributionSource);
+ method @NonNull public android.content.ContextParams.Builder setShouldRegisterAttributionSource(boolean);
}
public class ContextWrapper extends android.content.Context {
@@ -39349,8 +39351,10 @@
}
public final class FileIntegrityManager {
+ method @FlaggedApi(Flags.FLAG_FSVERITY_API) @Nullable public byte[] getFsverityDigest(@NonNull java.io.File) throws java.io.IOException;
method public boolean isApkVeritySupported();
method @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public boolean isAppSourceCertificateTrusted(@NonNull java.security.cert.X509Certificate) throws java.security.cert.CertificateEncodingException;
+ method @FlaggedApi(Flags.FLAG_FSVERITY_API) public void setupFsverity(@NonNull java.io.File) throws java.io.IOException;
}
public final class KeyChain {
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index b1feb41..7f1e289 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -6,7 +6,9 @@
field public static final String CONTROL_AUTOMOTIVE_GNSS = "android.permission.CONTROL_AUTOMOTIVE_GNSS";
field public static final String GET_INTENT_SENDER_INTENT = "android.permission.GET_INTENT_SENDER_INTENT";
field public static final String MAKE_UID_VISIBLE = "android.permission.MAKE_UID_VISIBLE";
+ field public static final String MANAGE_REMOTE_AUTH = "android.permission.MANAGE_REMOTE_AUTH";
field public static final String USE_COMPANION_TRANSPORTS = "android.permission.USE_COMPANION_TRANSPORTS";
+ field public static final String USE_REMOTE_AUTH = "android.permission.USE_REMOTE_AUTH";
}
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 2137f47..adbd06c 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3793,6 +3793,10 @@
field @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS) public static final int FLAG_GET_PERSONS_DATA = 2048; // 0x800
}
+ public class PackageArchiver {
+ method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
+ }
+
public class PackageInstaller {
method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException;
method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException;
@@ -3890,6 +3894,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public abstract java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
method @Deprecated @NonNull public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(@NonNull String);
method @Deprecated @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract int getIntentVerificationStatusAsUser(@NonNull String, int);
+ method @NonNull public android.content.pm.PackageArchiver getPackageArchiver();
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public int getPackageUidAsUser(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @android.content.pm.PackageManager.PermissionFlags @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
method @NonNull @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] getUnsuspendablePackages(@NonNull String[]);
@@ -13257,6 +13262,48 @@
method @NonNull public android.service.voice.HotwordRejectedResult.Builder setConfidenceLevel(int);
}
+ public final class HotwordTrainingAudio implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.media.AudioFormat getAudioFormat();
+ method @NonNull public int getAudioType();
+ method @NonNull public byte[] getHotwordAudio();
+ method public int getHotwordOffsetMillis();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordTrainingAudio> CREATOR;
+ field public static final int HOTWORD_OFFSET_UNSET = -1; // 0xffffffff
+ }
+
+ public static final class HotwordTrainingAudio.Builder {
+ ctor public HotwordTrainingAudio.Builder(@NonNull byte[], @NonNull android.media.AudioFormat);
+ method @NonNull public android.service.voice.HotwordTrainingAudio build();
+ method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setAudioFormat(@NonNull android.media.AudioFormat);
+ method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setAudioType(@NonNull int);
+ method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setHotwordAudio(@NonNull byte...);
+ method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setHotwordOffsetMillis(int);
+ }
+
+ public final class HotwordTrainingData implements android.os.Parcelable {
+ method public int describeContents();
+ method public static int getMaxTrainingDataSize();
+ method public int getTimeoutStage();
+ method @NonNull public java.util.List<android.service.voice.HotwordTrainingAudio> getTrainingAudios();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordTrainingData> CREATOR;
+ field public static final int TIMEOUT_STAGE_EARLY = 2; // 0x2
+ field public static final int TIMEOUT_STAGE_LATE = 4; // 0x4
+ field public static final int TIMEOUT_STAGE_MIDDLE = 3; // 0x3
+ field public static final int TIMEOUT_STAGE_UNKNOWN = 0; // 0x0
+ field public static final int TIMEOUT_STAGE_VERY_EARLY = 1; // 0x1
+ }
+
+ public static final class HotwordTrainingData.Builder {
+ ctor public HotwordTrainingData.Builder();
+ method @NonNull public android.service.voice.HotwordTrainingData.Builder addTrainingAudio(@NonNull android.service.voice.HotwordTrainingAudio);
+ method @NonNull public android.service.voice.HotwordTrainingData build();
+ method @NonNull public android.service.voice.HotwordTrainingData.Builder setTimeoutStage(int);
+ method @NonNull public android.service.voice.HotwordTrainingData.Builder setTrainingAudios(@NonNull java.util.List<android.service.voice.HotwordTrainingAudio>);
+ }
+
public interface SandboxedDetectionInitializer {
method public static int getMaxCustomInitializationStatus();
method public void onUpdateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, long, @Nullable java.util.function.IntConsumer);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index cff5e40..22d2999 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -28,6 +28,7 @@
field public static final String MANAGE_APP_OPS_MODES = "android.permission.MANAGE_APP_OPS_MODES";
field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES";
field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS";
+ field public static final String MANAGE_REMOTE_AUTH = "android.permission.MANAGE_REMOTE_AUTH";
field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
field public static final String MANAGE_TOAST_RATE_LIMITING = "android.permission.MANAGE_TOAST_RATE_LIMITING";
field public static final String MODIFY_HDR_CONVERSION_MODE = "android.permission.MODIFY_HDR_CONVERSION_MODE";
@@ -54,6 +55,7 @@
field public static final String TEST_INPUT_METHOD = "android.permission.TEST_INPUT_METHOD";
field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS";
field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
+ field public static final String USE_REMOTE_AUTH = "android.permission.USE_REMOTE_AUTH";
field public static final String WRITE_ALLOWLISTED_DEVICE_CONFIG = "android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG";
field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
@@ -497,14 +499,14 @@
method public int describeContents();
method public int getActivityType();
method @Nullable public android.graphics.Rect getAppBounds();
- method @NonNull public android.graphics.Rect getBounds();
+ method public android.graphics.Rect getBounds();
method @NonNull public android.graphics.Rect getMaxBounds();
method public int getRotation();
method public int getWindowingMode();
method public static boolean isFloating(int);
method public void setActivityType(int);
method public void setAppBounds(@Nullable android.graphics.Rect);
- method public void setBounds(@Nullable android.graphics.Rect);
+ method public void setBounds(android.graphics.Rect);
method public void setMaxBounds(@Nullable android.graphics.Rect);
method public void setRotation(int);
method public void setTo(android.app.WindowConfiguration);
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 0255860..fcd13b8 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -64,6 +64,7 @@
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.KeySet;
import android.content.pm.ModuleInfo;
+import android.content.pm.PackageArchiver;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageItemInfo;
@@ -172,6 +173,7 @@
private volatile UserManager mUserManager;
private volatile PermissionManager mPermissionManager;
private volatile PackageInstaller mInstaller;
+ private volatile PackageArchiver mPackageArchiver;
private volatile ArtManager mArtManager;
private volatile DevicePolicyManager mDevicePolicyManager;
private volatile String mPermissionsControllerPackageName;
@@ -3282,6 +3284,18 @@
}
@Override
+ public PackageArchiver getPackageArchiver() {
+ if (mPackageArchiver == null) {
+ try {
+ mPackageArchiver = new PackageArchiver(mContext, mPM.getPackageArchiverService());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return mPackageArchiver;
+ }
+
+ @Override
public boolean isPackageAvailable(String packageName) {
try {
return mPM.isPackageAvailable(packageName, getUserId());
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 5feafbe..a538247 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3455,20 +3455,20 @@
mOpPackageName = overrideOpPackageName != null ? overrideOpPackageName : opPackageName;
mParams = Objects.requireNonNull(params);
mAttributionSource = createAttributionSource(attributionTag, nextAttributionSource,
- params.getRenouncedPermissions());
+ params.getRenouncedPermissions(), params.shouldRegisterAttributionSource());
mContentResolver = new ApplicationContentResolver(this, mainThread);
}
private @NonNull AttributionSource createAttributionSource(@Nullable String attributionTag,
@Nullable AttributionSource nextAttributionSource,
- @Nullable Set<String> renouncedPermissions) {
+ @Nullable Set<String> renouncedPermissions, boolean shouldRegister) {
AttributionSource attributionSource = new AttributionSource(Process.myUid(),
Process.myPid(), mOpPackageName, attributionTag,
(renouncedPermissions != null) ? renouncedPermissions.toArray(new String[0]) : null,
getDeviceId(), nextAttributionSource);
// If we want to access protected data on behalf of another app we need to
// tell the OS that we opt in to participate in the attribution chain.
- if (nextAttributionSource != null) {
+ if (nextAttributionSource != null || shouldRegister) {
attributionSource = getSystemService(PermissionManager.class)
.registerAttributionSource(attributionSource);
}
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index 729e555..0857c96 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -40,7 +40,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -195,7 +195,8 @@
XmlUtils.beginDocument(parser, TAG_LOCALE_CONFIG);
int outerDepth = parser.getDepth();
AttributeSet attrs = Xml.asAttributeSet(parser);
- Set<String> localeNames = new HashSet<String>();
+ // LinkedHashSet to preserve insertion order
+ Set<String> localeNames = new LinkedHashSet<>();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (TAG_LOCALE.equals(parser.getName())) {
final TypedArray attributes = res.obtainAttributes(
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index e3055e5..bf238c3 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -267,11 +267,12 @@
}
};
+ // TODO(b/297672475): make this take @Nullable
/**
* Sets the bounds to the provided {@link Rect}.
* @param rect the new bounds value.
*/
- public void setBounds(@Nullable Rect rect) {
+ public void setBounds(Rect rect) {
if (rect == null) {
mBounds.setEmpty();
return;
@@ -363,8 +364,8 @@
return mAppBounds;
}
+ // TODO(b/297672475): make this return @NonNull
/** @see #setBounds(Rect) */
- @NonNull
public Rect getBounds() {
return mBounds;
}
diff --git a/core/java/android/content/ContextParams.java b/core/java/android/content/ContextParams.java
index 5cc3a24..988a9c0 100644
--- a/core/java/android/content/ContextParams.java
+++ b/core/java/android/content/ContextParams.java
@@ -50,17 +50,20 @@
private final @Nullable String mAttributionTag;
private final @Nullable AttributionSource mNext;
private final @NonNull Set<String> mRenouncedPermissions;
+ private final boolean mShouldRegisterAttributionSource;
/** {@hide} */
public static final ContextParams EMPTY = new ContextParams.Builder().build();
private ContextParams(@Nullable String attributionTag,
@Nullable AttributionSource next,
- @Nullable Set<String> renouncedPermissions) {
+ @Nullable Set<String> renouncedPermissions,
+ boolean shouldRegister) {
mAttributionTag = attributionTag;
mNext = next;
mRenouncedPermissions = (renouncedPermissions != null)
? renouncedPermissions : Collections.emptySet();
+ mShouldRegisterAttributionSource = shouldRegister;
}
/**
@@ -95,12 +98,22 @@
}
/**
+ * @return Whether the attribution source associated with the Context being created should be
+ * registered.
+ */
+ @NonNull
+ public boolean shouldRegisterAttributionSource() {
+ return mShouldRegisterAttributionSource;
+ }
+
+ /**
* Builder for creating a {@link ContextParams}.
*/
public static final class Builder {
private @Nullable String mAttributionTag;
private @NonNull Set<String> mRenouncedPermissions = Collections.emptySet();
private @Nullable AttributionSource mNext;
+ private boolean mShouldRegisterAttributionSource;
/**
* Create a new builder.
@@ -159,6 +172,19 @@
}
/**
+ * Sets whether the attribution source associated with the context created from these params
+ * should be registered.
+ *
+ * @param shouldRegister Whether the attribution source associated with the Context being
+ * created should be registered.
+ */
+ @NonNull
+ public Builder setShouldRegisterAttributionSource(boolean shouldRegister) {
+ mShouldRegisterAttributionSource = shouldRegister;
+ return this;
+ }
+
+ /**
* Sets permissions which have been voluntarily "renounced" by the
* calling app.
* <p>
@@ -205,7 +231,7 @@
@NonNull
public ContextParams build() {
return new ContextParams(mAttributionTag, mNext,
- mRenouncedPermissions);
+ mRenouncedPermissions, mShouldRegisterAttributionSource);
}
}
}
diff --git a/core/java/android/content/pm/IPackageArchiverService.aidl b/core/java/android/content/pm/IPackageArchiverService.aidl
new file mode 100644
index 0000000..fc471c4
--- /dev/null
+++ b/core/java/android/content/pm/IPackageArchiverService.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm;
+
+import android.content.IntentSender;
+import android.os.UserHandle;
+
+/** {@hide} */
+interface IPackageArchiverService {
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES,android.Manifest.permission.REQUEST_DELETE_PACKAGES})")
+ void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index ea0f5ff..4ed4dd3 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -25,6 +25,7 @@
import android.content.pm.ChangedPackages;
import android.content.pm.InstantAppInfo;
import android.content.pm.FeatureInfo;
+import android.content.pm.IPackageArchiverService;
import android.content.pm.IDexModuleRegisterCallback;
import android.content.pm.InstallSourceInfo;
import android.content.pm.IOnChecksumsReadyListener;
@@ -650,6 +651,8 @@
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
IPackageInstaller getPackageInstaller();
+ IPackageArchiverService getPackageArchiverService();
+
@EnforcePermission("DELETE_PACKAGES")
boolean setBlockUninstallForUser(String packageName, boolean blockUninstall, int userId);
@UnsupportedAppUsage
diff --git a/core/java/android/content/pm/PackageArchiver.java b/core/java/android/content/pm/PackageArchiver.java
new file mode 100644
index 0000000..d739d50
--- /dev/null
+++ b/core/java/android/content/pm/PackageArchiver.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.content.IntentSender;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.ParcelableException;
+import android.os.RemoteException;
+
+/**
+ * {@code ArchiveManager} is used to archive apps. During the archival process, the apps APKs and
+ * cache are removed from the device while the user data is kept. Through the
+ * {@code requestUnarchive()} call, apps can be restored again through their responsible app store.
+ *
+ * <p> Archived apps are returned as displayable apps through the {@link LauncherApps} APIs and
+ * will be displayed to users with UI treatment to highlight that said apps are archived. If
+ * a user taps on an archived app, the app will be unarchived and the restoration process is
+ * communicated.
+ *
+ * @hide
+ */
+// TODO(b/278560219) Improve public documentation.
+@SystemApi
+public class PackageArchiver {
+
+ private final Context mContext;
+ private final IPackageArchiverService mService;
+
+ /**
+ * @hide
+ */
+ public PackageArchiver(Context context, IPackageArchiverService service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Requests to archive a package which is currently installed.
+ *
+ * @param statusReceiver Callback used to notify when the operation is completed.
+ * @throws NameNotFoundException If {@code packageName} isn't found or not available to the
+ * caller.
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.REQUEST_DELETE_PACKAGES})
+ @SystemApi
+ public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver)
+ throws NameNotFoundException {
+ try {
+ mService.requestArchive(packageName, mContext.getPackageName(), statusReceiver,
+ mContext.getUser());
+ } catch (ParcelableException e) {
+ e.maybeRethrow(NameNotFoundException.class);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 885e67e1..d2173a6 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -9934,6 +9934,16 @@
public abstract @NonNull PackageInstaller getPackageInstaller();
/**
+ * {@link PackageArchiver} can be used to archive and restore archived packages.
+ *
+ * @hide
+ */
+ @SystemApi
+ public @NonNull PackageArchiver getPackageArchiver() {
+ throw new UnsupportedOperationException(
+ "getPackageArchiver not implemented in subclass");
+ }
+ /**
* Adds a {@code CrossProfileIntentFilter}. After calling this method all
* intents sent from the user with id sourceUserId can also be be resolved
* by activities in the user with id targetUserId if they match the
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 9387ae1..41ba1dc 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1456,8 +1456,8 @@
private static AssetManager newConfiguredAssetManager() {
AssetManager assetManager = new AssetManager();
- assetManager.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- Build.VERSION.RESOURCES_SDK_INT);
+ assetManager.setConfiguration(0, 0, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT);
return assetManager;
}
@@ -9011,8 +9011,8 @@
}
AssetManager assets = new AssetManager();
- assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- Build.VERSION.RESOURCES_SDK_INT);
+ assets.setConfiguration(0, 0, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT);
assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
mCachedAssetManager = assets;
@@ -9086,8 +9086,8 @@
private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) {
final AssetManager assets = new AssetManager();
- assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- Build.VERSION.RESOURCES_SDK_INT);
+ assets.setConfiguration(0, 0, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT);
assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
return assets;
}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index b225de4..23b9d0b 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -1480,9 +1480,13 @@
int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp,
int screenHeightDp, int screenLayout, int uiMode, int colorMode, int grammaticalGender,
int majorVersion) {
- synchronized (this) {
- ensureValidLocked();
- nativeSetConfiguration(mObject, mcc, mnc, locale, orientation, touchscreen, density,
+ if (locale != null) {
+ setConfiguration(mcc, mnc, null, new String[]{locale}, orientation, touchscreen,
+ density, keyboard, keyboardHidden, navigation, screenWidth, screenHeight,
+ smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode,
+ colorMode, grammaticalGender, majorVersion);
+ } else {
+ setConfiguration(mcc, mnc, null, null, orientation, touchscreen, density,
keyboard, keyboardHidden, navigation, screenWidth, screenHeight,
smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode,
colorMode, grammaticalGender, majorVersion);
@@ -1490,6 +1494,25 @@
}
/**
+ * Change the configuration used when retrieving resources. Not for use by
+ * applications.
+ * @hide
+ */
+ public void setConfiguration(int mcc, int mnc, String defaultLocale, String[] locales,
+ int orientation, int touchscreen, int density, int keyboard, int keyboardHidden,
+ int navigation, int screenWidth, int screenHeight, int smallestScreenWidthDp,
+ int screenWidthDp, int screenHeightDp, int screenLayout, int uiMode, int colorMode,
+ int grammaticalGender, int majorVersion) {
+ synchronized (this) {
+ ensureValidLocked();
+ nativeSetConfiguration(mObject, mcc, mnc, defaultLocale, locales, orientation,
+ touchscreen, density, keyboard, keyboardHidden, navigation, screenWidth,
+ screenHeight, smallestScreenWidthDp, screenWidthDp, screenHeightDp,
+ screenLayout, uiMode, colorMode, grammaticalGender, majorVersion);
+ }
+ }
+
+ /**
* @hide
*/
@UnsupportedAppUsage
@@ -1572,10 +1595,11 @@
private static native void nativeSetApkAssets(long ptr, @NonNull ApkAssets[] apkAssets,
boolean invalidateCaches);
private static native void nativeSetConfiguration(long ptr, int mcc, int mnc,
- @Nullable String locale, int orientation, int touchscreen, int density, int keyboard,
- int keyboardHidden, int navigation, int screenWidth, int screenHeight,
- int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, int screenLayout,
- int uiMode, int colorMode, int grammaticalGender, int majorVersion);
+ @Nullable String defaultLocale, @NonNull String[] locales, int orientation,
+ int touchscreen, int density, int keyboard, int keyboardHidden, int navigation,
+ int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp,
+ int screenHeightDp, int screenLayout, int uiMode, int colorMode, int grammaticalGender,
+ int majorVersion);
private static native @NonNull SparseArray<String> nativeGetAssignedPackageIdentifiers(
long ptr, boolean includeOverlays, boolean includeLoaders);
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 395fef2..76b29e6 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -425,14 +425,12 @@
mConfiguration.setLocales(locales);
}
+ String[] selectedLocales = null;
+ String defaultLocale = null;
if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
if (locales.size() > 1) {
String[] availableLocales;
-
- LocaleList localeList = ResourcesManager.getInstance().getLocaleList();
- if (!localeList.isEmpty()) {
- availableLocales = localeList.toLanguageTags().split(",");
- } else {
+ if (ResourcesManager.getInstance().getLocaleList().isEmpty()) {
// The LocaleList has changed. We must query the AssetManager's
// available Locales and figure out the best matching Locale in the new
// LocaleList.
@@ -444,13 +442,35 @@
availableLocales = null;
}
}
- }
- if (availableLocales != null) {
- final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
- availableLocales);
- if (bestLocale != null && bestLocale != locales.get(0)) {
- mConfiguration.setLocales(new LocaleList(bestLocale, locales));
+
+ if (availableLocales != null) {
+ final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
+ availableLocales);
+ if (bestLocale != null) {
+ selectedLocales = new String[]{
+ adjustLanguageTag(bestLocale.toLanguageTag())};
+ if (!bestLocale.equals(locales.get(0))) {
+ mConfiguration.setLocales(
+ new LocaleList(bestLocale, locales));
+ }
+ }
}
+ } else {
+ selectedLocales = locales.getIntersection(
+ ResourcesManager.getInstance().getLocaleList());
+ defaultLocale = ResourcesManager.getInstance()
+ .getLocaleList().get(0).toLanguageTag();
+ }
+ }
+ }
+ if (selectedLocales == null) {
+ if (ResourcesManager.getInstance().getLocaleList().isEmpty()) {
+ selectedLocales = new String[]{
+ adjustLanguageTag(locales.get(0).toLanguageTag())};
+ } else {
+ selectedLocales = new String[locales.size()];
+ for (int i = 0; i < locales.size(); i++) {
+ selectedLocales[i] = adjustLanguageTag(locales.get(i).toLanguageTag());
}
}
}
@@ -488,7 +508,8 @@
}
mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
- adjustLanguageTag(mConfiguration.getLocales().get(0).toLanguageTag()),
+ defaultLocale,
+ selectedLocales,
mConfiguration.orientation,
mConfiguration.touchscreen,
mConfiguration.densityDpi, mConfiguration.keyboard,
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 5940819..703f165 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1468,6 +1468,13 @@
* <p>Only constrains auto-exposure (AE) algorithm, not
* manual control of {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime} and
* {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}.</p>
+ * <p>To start a CaptureSession with a target FPS range different from the
+ * capture request template's default value, the application
+ * is strongly recommended to call
+ * {@link SessionConfiguration#setSessionParameters }
+ * with the target fps range before creating the capture session. The aeTargetFpsRange is
+ * typically a session parameter. Specifying it at session creation time helps avoid
+ * session reconfiguration delays in cases like 60fps or high speed recording.</p>
* <p><b>Units</b>: Frames per second (FPS)</p>
* <p><b>Range of valid values:</b><br>
* Any of the entries in {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES android.control.aeAvailableTargetFpsRanges}</p>
@@ -2140,6 +2147,12 @@
* {@link CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE android.control.videoStabilizationMode} field will return
* OFF if the recording output is not stabilized, or if there are no output
* Surface types that can be stabilized.</p>
+ * <p>The application is strongly recommended to call
+ * {@link SessionConfiguration#setSessionParameters }
+ * with the desired video stabilization mode before creating the capture session.
+ * Video stabilization mode is a session parameter on many devices. Specifying
+ * it at session creation time helps avoid reconfiguration delay caused by difference
+ * between the default value and the first CaptureRequest.</p>
* <p>If a camera device supports both this mode and OIS
* ({@link CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE android.lens.opticalStabilizationMode}), turning both modes on may
* produce undesirable interaction, so it is recommended not to enable
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 905f98d..746648b 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -887,6 +887,13 @@
* <p>Only constrains auto-exposure (AE) algorithm, not
* manual control of {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime} and
* {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}.</p>
+ * <p>To start a CaptureSession with a target FPS range different from the
+ * capture request template's default value, the application
+ * is strongly recommended to call
+ * {@link SessionConfiguration#setSessionParameters }
+ * with the target fps range before creating the capture session. The aeTargetFpsRange is
+ * typically a session parameter. Specifying it at session creation time helps avoid
+ * session reconfiguration delays in cases like 60fps or high speed recording.</p>
* <p><b>Units</b>: Frames per second (FPS)</p>
* <p><b>Range of valid values:</b><br>
* Any of the entries in {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES android.control.aeAvailableTargetFpsRanges}</p>
@@ -2365,6 +2372,12 @@
* {@link CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE android.control.videoStabilizationMode} field will return
* OFF if the recording output is not stabilized, or if there are no output
* Surface types that can be stabilized.</p>
+ * <p>The application is strongly recommended to call
+ * {@link SessionConfiguration#setSessionParameters }
+ * with the desired video stabilization mode before creating the capture session.
+ * Video stabilization mode is a session parameter on many devices. Specifying
+ * it at session creation time helps avoid reconfiguration delay caused by difference
+ * between the default value and the first CaptureRequest.</p>
* <p>If a camera device supports both this mode and OIS
* ({@link CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE android.lens.opticalStabilizationMode}), turning both modes on may
* produce undesirable interaction, so it is recommended not to enable
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 8f653b3..c0a44b1 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -650,8 +650,6 @@
private InlineSuggestionSessionController mInlineSuggestionSessionController;
- private boolean mHideNavBarForKeyboard;
- private boolean mIsAutomotive;
private @NonNull OptionalInt mHandwritingRequestId = OptionalInt.empty();
private InputEventReceiver mHandwritingEventReceiver;
private Handler mHandler;
@@ -1622,7 +1620,7 @@
// shown the first time (cold start).
mSettingsObserver.shouldShowImeWithHardKeyboard();
- mHideNavBarForKeyboard = getApplicationContext().getResources().getBoolean(
+ final boolean hideNavBarForKeyboard = getApplicationContext().getResources().getBoolean(
com.android.internal.R.bool.config_hideNavBarForKeyboard);
initConfigurationTracker();
@@ -1668,7 +1666,7 @@
// screen real estate. When this happens, the IME window should animate from the
// bottom of the screen to reduce the jank that happens from the lack of synchronization
// between the bottom system window and the IME window.
- if (mHideNavBarForKeyboard) {
+ if (hideNavBarForKeyboard) {
window.setDecorFitsSystemWindows(false);
}
}
@@ -2366,9 +2364,7 @@
public void setExtractView(View view) {
mExtractFrame.removeAllViews();
- mExtractFrame.addView(view, new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT));
+ mExtractFrame.addView(view, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mExtractView = view;
if (view != null) {
mExtractEditText = view.findViewById(
@@ -2387,7 +2383,7 @@
mExtractAction = null;
}
}
-
+
/**
* Replaces the current candidates view with a new one. You only need to
* call this when dynamically changing the view; normally, you should
@@ -2396,11 +2392,9 @@
*/
public void setCandidatesView(View view) {
mCandidatesFrame.removeAllViews();
- mCandidatesFrame.addView(view, new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
+ mCandidatesFrame.addView(view, new FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
}
-
+
/**
* Replaces the current input view with a new one. You only need to
* call this when dynamically changing the view; normally, you should
@@ -2409,12 +2403,10 @@
*/
public void setInputView(View view) {
mInputFrame.removeAllViews();
- mInputFrame.addView(view, new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
+ mInputFrame.addView(view, new FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
mInputView = view;
}
-
+
/**
* Called by the framework to create the layout for showing extracted text.
* Only called when in fullscreen mode. The returned view hierarchy must
@@ -3448,9 +3440,12 @@
return false;
}
+ /**
+ * Not implemented in this class.
+ */
public void onAppPrivateCommand(String action, Bundle data) {
}
-
+
/**
* Handle a request by the system to toggle the soft input area.
*/
@@ -4092,11 +4087,6 @@
| (isInputViewShown() ? IME_VISIBLE : 0);
}
- private boolean isAutomotive() {
- return getApplicationContext().getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_AUTOMOTIVE);
- }
-
/**
* Performs a dump of the InputMethodService's internal state. Override
* to add your own information to the dump.
diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java
index b74bb33..82cdd28 100644
--- a/core/java/android/os/LocaleList.java
+++ b/core/java/android/os/LocaleList.java
@@ -30,6 +30,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
+import java.util.List;
import java.util.Locale;
/**
@@ -151,6 +152,25 @@
}
/**
+ * Find the intersection between this LocaleList and another
+ * @return a String array of the Locales in both LocaleLists
+ * {@hide}
+ */
+ @NonNull
+ public String[] getIntersection(@NonNull LocaleList other) {
+ List<String> intersection = new ArrayList<>();
+ for (Locale l1 : mList) {
+ for (Locale l2 : other.mList) {
+ if (matchesLanguageAndScript(l2, l1)) {
+ intersection.add(l1.toLanguageTag());
+ break;
+ }
+ }
+ }
+ return intersection.toArray(new String[0]);
+ }
+
+ /**
* Creates a new {@link LocaleList}.
*
* If two or more same locales are passed, the repeated locales will be dropped.
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
new file mode 100644
index 0000000..851aa6d
--- /dev/null
+++ b/core/java/android/os/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.os"
+
+flag {
+ name: "disallow_cellular_null_ciphers_restriction"
+ namespace: "cellular_security"
+ description: "Guards a new UserManager user restriction that admins can use to require cellular encryption on their managed devices."
+ bug: "276752881"
+}
diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java
index 22e8251..8961846 100644
--- a/core/java/android/os/storage/StorageManagerInternal.java
+++ b/core/java/android/os/storage/StorageManagerInternal.java
@@ -20,8 +20,11 @@
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 void prepareUserStorageForMove(String fromVolumeUuid, String toVolumeUuid,
List<UserInfo> users);
+ /**
+ * A proxy call to the corresponding method in Installer.
+ * @see com.android.server.pm.Installer#createFsveritySetupAuthToken()
+ */
+ public abstract IInstalld.IFsveritySetupAuthToken createFsveritySetupAuthToken(
+ ParcelFileDescriptor authFd, int appUid, @UserIdInt int userId) throws IOException;
+
+ /**
+ * A proxy call to the corresponding method in Installer.
+ * @see com.android.server.pm.Installer#enableFsverity()
+ */
+ public abstract int enableFsverity(IInstalld.IFsveritySetupAuthToken authToken, String filePath,
+ String packageName) throws IOException;
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 45b4935..522caac 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11656,6 +11656,21 @@
"accessibility_floating_menu_migration_tooltip_prompt";
/**
+ * For the force dark theme feature which inverts any apps that don't already support dark
+ * theme.
+ *
+ * If true, it will automatically invert any app that is mainly light.
+ *
+ * This is related to the force dark override setting, however it will always force the apps
+ * colors and will ignore any developer hints or opt-out APIs.
+ *
+ * @hide
+ */
+ @Readable
+ public static final String ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED =
+ "accessibility_force_invert_color_enabled";
+
+ /**
* Whether the Adaptive connectivity option is enabled.
*
* @hide
diff --git a/core/java/android/security/FileIntegrityManager.java b/core/java/android/security/FileIntegrityManager.java
index 266046e..7869404 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 @@
}
/**
+ * 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 dff347e..1a6cf88 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 @@
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 55133ae..9b1132a 100644
--- a/core/java/android/service/credentials/Action.java
+++ b/core/java/android/service/credentials/Action.java
@@ -46,7 +46,14 @@
* <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 6a9f09f..2495c7d 100644
--- a/core/java/android/service/credentials/CreateEntry.java
+++ b/core/java/android/service/credentials/CreateEntry.java
@@ -66,7 +66,14 @@
/**
* 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 512d833..53094e8 100644
--- a/core/java/android/service/credentials/CredentialEntry.java
+++ b/core/java/android/service/credentials/CredentialEntry.java
@@ -69,8 +69,14 @@
* 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 5b3218a..5fd9925 100644
--- a/core/java/android/service/credentials/RemoteEntry.java
+++ b/core/java/android/service/credentials/RemoteEntry.java
@@ -73,7 +73,14 @@
/**
* 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 0000000..4dd2289
--- /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 0000000..895b0c0
--- /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 0000000..03cc841
--- /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 0000000..9dca77e
--- /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 0a69ea8..048912c 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 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 9233050..8de448b 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 static final Flag LOG_DND_STATE_EVENTS =
releasedFlag("persist.sysui.notification.log_dnd_state_events");
- /** Gating the holding of WakeLocks until NLSes are told about a new notification. */
- public static final Flag WAKE_LOCK_FOR_POSTING_NOTIFICATION =
- releasedFlag("persist.sysui.notification.wake_lock_for_posting_notification");
-
/** Gating storing NotificationRankingUpdate ranking map in shared memory. */
public static final Flag RANKING_UPDATE_ASHMEM = devFlag(
"persist.sysui.notification.ranking_update_ashmem");
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 116c301c..3e9458d 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -342,7 +342,11 @@
synchronized (mLock) {
int samplingInterval = properties.getInt(SETTINGS_SAMPLING_INTERVAL_KEY,
DEFAULT_SAMPLING_INTERVAL);
+ boolean wasEnabled = mEnabled;
mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED);
+ if (wasEnabled != mEnabled) {
+ Log.d(TAG, "Latency tracker " + (mEnabled ? "enabled" : "disabled") + ".");
+ }
for (int action : ACTIONS_ALL) {
String actionName = getNameOfAction(STATSD_ACTION[action]).toLowerCase(Locale.ROOT);
int legacyActionTraceThreshold = properties.getInt(
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 1c59742..eb49f41 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -874,7 +874,7 @@
if (camera == 0) return 0;
String8 params8 = camera->getParameters();
- if (params8.isEmpty()) {
+ if (params8.empty()) {
jniThrowRuntimeException(env, "getParameters failed (empty parameters)");
return 0;
}
diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp
index 5293c58..3e4c7c7 100644
--- a/core/jni/android_hardware_camera2_CameraMetadata.cpp
+++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp
@@ -526,7 +526,7 @@
"Failed to read from fd (errno = %#x, message = '%s')",
errno, strerror(errno));
//return;
- } else if (!logLine.isEmpty()) {
+ } else if (!logLine.empty()) {
ALOGD("%s", logLine.string());
}
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index f04b901..3ee15ab 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -347,14 +347,23 @@
}
static void NativeSetConfiguration(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint mcc, jint mnc,
- jstring locale, jint orientation, jint touchscreen, jint density,
- jint keyboard, jint keyboard_hidden, jint navigation,
- jint screen_width, jint screen_height,
- jint smallest_screen_width_dp, jint screen_width_dp,
- jint screen_height_dp, jint screen_layout, jint ui_mode,
- jint color_mode, jint grammatical_gender, jint major_version) {
+ jstring default_locale, jobjectArray locales, jint orientation,
+ jint touchscreen, jint density, jint keyboard,
+ jint keyboard_hidden, jint navigation, jint screen_width,
+ jint screen_height, jint smallest_screen_width_dp,
+ jint screen_width_dp, jint screen_height_dp, jint screen_layout,
+ jint ui_mode, jint color_mode, jint grammatical_gender,
+ jint major_version) {
ATRACE_NAME("AssetManager::SetConfiguration");
+ const jsize locale_count = (locales == NULL) ? 0 : env->GetArrayLength(locales);
+
+ // Constants duplicated from Java class android.content.res.Configuration.
+ static const jint kScreenLayoutRoundMask = 0x300;
+ static const jint kScreenLayoutRoundShift = 8;
+
+ std::vector<ResTable_config> configs;
+
ResTable_config configuration;
memset(&configuration, 0, sizeof(configuration));
configuration.mcc = static_cast<uint16_t>(mcc);
@@ -375,25 +384,37 @@
configuration.colorMode = static_cast<uint8_t>(color_mode);
configuration.grammaticalInflection = static_cast<uint8_t>(grammatical_gender);
configuration.sdkVersion = static_cast<uint16_t>(major_version);
-
- if (locale != nullptr) {
- ScopedUtfChars locale_utf8(env, locale);
- CHECK(locale_utf8.c_str() != nullptr);
- configuration.setBcp47Locale(locale_utf8.c_str());
- }
-
- // Constants duplicated from Java class android.content.res.Configuration.
- static const jint kScreenLayoutRoundMask = 0x300;
- static const jint kScreenLayoutRoundShift = 8;
-
// In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer
// in C++. We must extract the round qualifier out of the Java screenLayout and put it
// into screenLayout2.
configuration.screenLayout2 =
- static_cast<uint8_t>((screen_layout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
+ static_cast<uint8_t>((screen_layout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
+
+ if (locale_count > 0) {
+ configs.resize(locale_count, configuration);
+ for (int i = 0; i < locale_count; i++) {
+ jstring locale = (jstring)(env->GetObjectArrayElement(locales, i));
+ ScopedUtfChars locale_utf8(env, locale);
+ CHECK(locale_utf8.c_str() != nullptr);
+ configs[i].setBcp47Locale(locale_utf8.c_str());
+ }
+ } else {
+ configs.push_back(configuration);
+ }
+
+ uint32_t default_locale_int = 0;
+ if (default_locale != nullptr) {
+ ResTable_config config;
+ static_assert(std::is_same_v<decltype(config.locale), decltype(default_locale_int)>);
+ ScopedUtfChars locale_utf8(env, default_locale);
+ CHECK(locale_utf8.c_str() != nullptr);
+ config.setBcp47Locale(locale_utf8.c_str());
+ default_locale_int = config.locale;
+ }
auto assetmanager = LockAndStartAssetManager(ptr);
- assetmanager->SetConfiguration(configuration);
+ assetmanager->SetConfigurations(configs);
+ assetmanager->SetDefaultLocale(default_locale_int);
}
static jobject NativeGetAssignedPackageIdentifiers(JNIEnv* env, jclass /*clazz*/, jlong ptr,
@@ -1498,94 +1519,97 @@
// JNI registration.
static const JNINativeMethod gAssetManagerMethods[] = {
- // AssetManager setup methods.
- {"nativeCreate", "()J", (void*)NativeCreate},
- {"nativeDestroy", "(J)V", (void*)NativeDestroy},
- {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets},
- {"nativeSetConfiguration", "(JIILjava/lang/String;IIIIIIIIIIIIIIII)V",
- (void*)NativeSetConfiguration},
- {"nativeGetAssignedPackageIdentifiers", "(JZZ)Landroid/util/SparseArray;",
- (void*)NativeGetAssignedPackageIdentifiers},
+ // AssetManager setup methods.
+ {"nativeCreate", "()J", (void*)NativeCreate},
+ {"nativeDestroy", "(J)V", (void*)NativeDestroy},
+ {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets},
+ {"nativeSetConfiguration", "(JIILjava/lang/String;[Ljava/lang/String;IIIIIIIIIIIIIIII)V",
+ (void*)NativeSetConfiguration},
+ {"nativeGetAssignedPackageIdentifiers", "(JZZ)Landroid/util/SparseArray;",
+ (void*)NativeGetAssignedPackageIdentifiers},
- // AssetManager file methods.
- {"nativeContainsAllocatedTable", "(J)Z", (void*)ContainsAllocatedTable},
- {"nativeList", "(JLjava/lang/String;)[Ljava/lang/String;", (void*)NativeList},
- {"nativeOpenAsset", "(JLjava/lang/String;I)J", (void*)NativeOpenAsset},
- {"nativeOpenAssetFd", "(JLjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
- (void*)NativeOpenAssetFd},
- {"nativeOpenNonAsset", "(JILjava/lang/String;I)J", (void*)NativeOpenNonAsset},
- {"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
- (void*)NativeOpenNonAssetFd},
- {"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset},
- {"nativeOpenXmlAssetFd", "(JILjava/io/FileDescriptor;)J", (void*)NativeOpenXmlAssetFd},
+ // AssetManager file methods.
+ {"nativeContainsAllocatedTable", "(J)Z", (void*)ContainsAllocatedTable},
+ {"nativeList", "(JLjava/lang/String;)[Ljava/lang/String;", (void*)NativeList},
+ {"nativeOpenAsset", "(JLjava/lang/String;I)J", (void*)NativeOpenAsset},
+ {"nativeOpenAssetFd", "(JLjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
+ (void*)NativeOpenAssetFd},
+ {"nativeOpenNonAsset", "(JILjava/lang/String;I)J", (void*)NativeOpenNonAsset},
+ {"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
+ (void*)NativeOpenNonAssetFd},
+ {"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset},
+ {"nativeOpenXmlAssetFd", "(JILjava/io/FileDescriptor;)J", (void*)NativeOpenXmlAssetFd},
- // AssetManager resource methods.
- {"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I", (void*)NativeGetResourceValue},
- {"nativeGetResourceBagValue", "(JIILandroid/util/TypedValue;)I",
- (void*)NativeGetResourceBagValue},
- {"nativeGetStyleAttributes", "(JI)[I", (void*)NativeGetStyleAttributes},
- {"nativeGetResourceStringArray", "(JI)[Ljava/lang/String;",
- (void*)NativeGetResourceStringArray},
- {"nativeGetResourceStringArrayInfo", "(JI)[I", (void*)NativeGetResourceStringArrayInfo},
- {"nativeGetResourceIntArray", "(JI)[I", (void*)NativeGetResourceIntArray},
- {"nativeGetResourceArraySize", "(JI)I", (void*)NativeGetResourceArraySize},
- {"nativeGetResourceArray", "(JI[I)I", (void*)NativeGetResourceArray},
- {"nativeGetParentThemeIdentifier", "(JI)I",
- (void*)NativeGetParentThemeIdentifier},
+ // AssetManager resource methods.
+ {"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I",
+ (void*)NativeGetResourceValue},
+ {"nativeGetResourceBagValue", "(JIILandroid/util/TypedValue;)I",
+ (void*)NativeGetResourceBagValue},
+ {"nativeGetStyleAttributes", "(JI)[I", (void*)NativeGetStyleAttributes},
+ {"nativeGetResourceStringArray", "(JI)[Ljava/lang/String;",
+ (void*)NativeGetResourceStringArray},
+ {"nativeGetResourceStringArrayInfo", "(JI)[I", (void*)NativeGetResourceStringArrayInfo},
+ {"nativeGetResourceIntArray", "(JI)[I", (void*)NativeGetResourceIntArray},
+ {"nativeGetResourceArraySize", "(JI)I", (void*)NativeGetResourceArraySize},
+ {"nativeGetResourceArray", "(JI[I)I", (void*)NativeGetResourceArray},
+ {"nativeGetParentThemeIdentifier", "(JI)I", (void*)NativeGetParentThemeIdentifier},
- // AssetManager resource name/ID methods.
- {"nativeGetResourceIdentifier", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
- (void*)NativeGetResourceIdentifier},
- {"nativeGetResourceName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceName},
- {"nativeGetResourcePackageName", "(JI)Ljava/lang/String;", (void*)NativeGetResourcePackageName},
- {"nativeGetResourceTypeName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceTypeName},
- {"nativeGetResourceEntryName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceEntryName},
- {"nativeSetResourceResolutionLoggingEnabled", "(JZ)V",
- (void*) NativeSetResourceResolutionLoggingEnabled},
- {"nativeGetLastResourceResolution", "(J)Ljava/lang/String;",
- (void*) NativeGetLastResourceResolution},
- {"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales},
- {"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;",
- (void*)NativeGetSizeConfigurations},
- {"nativeGetSizeAndUiModeConfigurations", "(J)[Landroid/content/res/Configuration;",
- (void*)NativeGetSizeAndUiModeConfigurations},
+ // AssetManager resource name/ID methods.
+ {"nativeGetResourceIdentifier",
+ "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
+ (void*)NativeGetResourceIdentifier},
+ {"nativeGetResourceName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceName},
+ {"nativeGetResourcePackageName", "(JI)Ljava/lang/String;",
+ (void*)NativeGetResourcePackageName},
+ {"nativeGetResourceTypeName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceTypeName},
+ {"nativeGetResourceEntryName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceEntryName},
+ {"nativeSetResourceResolutionLoggingEnabled", "(JZ)V",
+ (void*)NativeSetResourceResolutionLoggingEnabled},
+ {"nativeGetLastResourceResolution", "(J)Ljava/lang/String;",
+ (void*)NativeGetLastResourceResolution},
+ {"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales},
+ {"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;",
+ (void*)NativeGetSizeConfigurations},
+ {"nativeGetSizeAndUiModeConfigurations", "(J)[Landroid/content/res/Configuration;",
+ (void*)NativeGetSizeAndUiModeConfigurations},
- // Style attribute related methods.
- {"nativeAttributeResolutionStack", "(JJIII)[I", (void*)NativeAttributeResolutionStack},
- {"nativeApplyStyle", "(JJIIJ[IJJ)V", (void*)NativeApplyStyle},
- {"nativeResolveAttrs", "(JJII[I[I[I[I)Z", (void*)NativeResolveAttrs},
- {"nativeRetrieveAttributes", "(JJ[I[I[I)Z", (void*)NativeRetrieveAttributes},
+ // Style attribute related methods.
+ {"nativeAttributeResolutionStack", "(JJIII)[I", (void*)NativeAttributeResolutionStack},
+ {"nativeApplyStyle", "(JJIIJ[IJJ)V", (void*)NativeApplyStyle},
+ {"nativeResolveAttrs", "(JJII[I[I[I[I)Z", (void*)NativeResolveAttrs},
+ {"nativeRetrieveAttributes", "(JJ[I[I[I)Z", (void*)NativeRetrieveAttributes},
- // Theme related methods.
- {"nativeThemeCreate", "(J)J", (void*)NativeThemeCreate},
- {"nativeGetThemeFreeFunction", "()J", (void*)NativeGetThemeFreeFunction},
- {"nativeThemeApplyStyle", "(JJIZ)V", (void*)NativeThemeApplyStyle},
- {"nativeThemeRebase", "(JJ[I[ZI)V", (void*)NativeThemeRebase},
+ // Theme related methods.
+ {"nativeThemeCreate", "(J)J", (void*)NativeThemeCreate},
+ {"nativeGetThemeFreeFunction", "()J", (void*)NativeGetThemeFreeFunction},
+ {"nativeThemeApplyStyle", "(JJIZ)V", (void*)NativeThemeApplyStyle},
+ {"nativeThemeRebase", "(JJ[I[ZI)V", (void*)NativeThemeRebase},
- {"nativeThemeCopy", "(JJJJ)V", (void*)NativeThemeCopy},
- {"nativeThemeGetAttributeValue", "(JJILandroid/util/TypedValue;Z)I",
- (void*)NativeThemeGetAttributeValue},
- {"nativeThemeDump", "(JJILjava/lang/String;Ljava/lang/String;)V", (void*)NativeThemeDump},
- {"nativeThemeGetChangingConfigurations", "(J)I", (void*)NativeThemeGetChangingConfigurations},
+ {"nativeThemeCopy", "(JJJJ)V", (void*)NativeThemeCopy},
+ {"nativeThemeGetAttributeValue", "(JJILandroid/util/TypedValue;Z)I",
+ (void*)NativeThemeGetAttributeValue},
+ {"nativeThemeDump", "(JJILjava/lang/String;Ljava/lang/String;)V", (void*)NativeThemeDump},
+ {"nativeThemeGetChangingConfigurations", "(J)I",
+ (void*)NativeThemeGetChangingConfigurations},
- // AssetInputStream methods.
- {"nativeAssetDestroy", "(J)V", (void*)NativeAssetDestroy},
- {"nativeAssetReadChar", "(J)I", (void*)NativeAssetReadChar},
- {"nativeAssetRead", "(J[BII)I", (void*)NativeAssetRead},
- {"nativeAssetSeek", "(JJI)J", (void*)NativeAssetSeek},
- {"nativeAssetGetLength", "(J)J", (void*)NativeAssetGetLength},
- {"nativeAssetGetRemainingLength", "(J)J", (void*)NativeAssetGetRemainingLength},
+ // AssetInputStream methods.
+ {"nativeAssetDestroy", "(J)V", (void*)NativeAssetDestroy},
+ {"nativeAssetReadChar", "(J)I", (void*)NativeAssetReadChar},
+ {"nativeAssetRead", "(J[BII)I", (void*)NativeAssetRead},
+ {"nativeAssetSeek", "(JJI)J", (void*)NativeAssetSeek},
+ {"nativeAssetGetLength", "(J)J", (void*)NativeAssetGetLength},
+ {"nativeAssetGetRemainingLength", "(J)J", (void*)NativeAssetGetRemainingLength},
- // System/idmap related methods.
- {"nativeGetOverlayableMap", "(JLjava/lang/String;)Ljava/util/Map;",
- (void*)NativeGetOverlayableMap},
- {"nativeGetOverlayablesToString", "(JLjava/lang/String;)Ljava/lang/String;",
- (void*)NativeGetOverlayablesToString},
+ // System/idmap related methods.
+ {"nativeGetOverlayableMap", "(JLjava/lang/String;)Ljava/util/Map;",
+ (void*)NativeGetOverlayableMap},
+ {"nativeGetOverlayablesToString", "(JLjava/lang/String;)Ljava/lang/String;",
+ (void*)NativeGetOverlayablesToString},
- // Global management/debug methods.
- {"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount},
- {"getAssetAllocations", "()Ljava/lang/String;", (void*)NativeGetAssetAllocations},
- {"getGlobalAssetManagerCount", "()I", (void*)NativeGetGlobalAssetManagerCount},
+ // Global management/debug methods.
+ {"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount},
+ {"getAssetAllocations", "()Ljava/lang/String;", (void*)NativeGetAssetAllocations},
+ {"getGlobalAssetManagerCount", "()I", (void*)NativeGetGlobalAssetManagerCount},
};
int register_android_content_AssetManager(JNIEnv* env) {
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 4f2bf4a..b1d3d92 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -583,7 +583,7 @@
env->ReleaseStringCritical(name, str);
}
- if (!name8.isEmpty()) {
+ if (!name8.empty()) {
AndroidRuntime::getRuntime()->setArgv0(name8.string(), true /* setProcName */);
}
}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index ed0081c..ad88092 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -97,6 +97,7 @@
optional SettingProto accessibility_magnification_joystick_enabled = 50 [ (android.privacy).dest = DEST_AUTOMATIC ];
// Settings for font scaling
optional SettingProto accessibility_font_scaling_has_been_changed = 51 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto accessibility_force_invert_color_enabled = 52 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Accessibility accessibility = 2;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index dd1a499..d898a23 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2094,6 +2094,16 @@
<permission android:name="android.permission.MANAGE_TEST_NETWORKS"
android:protectionLevel="signature" />
+ <!-- Allows direct access to the <RemoteAuth>Service interfaces.
+ @hide @TestApi @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) -->
+ <permission android:name="android.permission.MANAGE_REMOTE_AUTH"
+ android:protectionLevel="signature" />
+
+ <!-- Allows direct access to the <RemoteAuth>Service authentication methods.
+ @hide @TestApi @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) -->
+ <permission android:name="android.permission.USE_REMOTE_AUTH"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi @hide Allows applications to read Wi-Fi credential.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.READ_WIFI_CREDENTIAL"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index f45499a..e54347f 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3537,11 +3537,11 @@
<attr name="preferKeepClear" format="boolean" />
<!-- <p>Whether or not the auto handwriting initiation is enabled in this View.
- <p>For a view with active {@link android.view.inputmethod.InputConnection},
- if auto handwriting initiation is enabled stylus movement within its view boundary
+ <p>For a view with an active {@link android.view.inputmethod.InputConnection},
+ if auto handwriting initiation is enabled, stylus movement within its view boundary
will automatically trigger the handwriting mode.
- <p>This is true by default.
- See {@link android.view.View#setAutoHandwritingEnabled}. -->
+ See {@link android.view.View#setAutoHandwritingEnabled}.
+ <p>The default value of this flag is configurable by the device manufacturer. -->
<attr name="autoHandwritingEnabled" format="boolean" />
<!-- <p>The amount of offset that is applied to the left edge of the view's stylus
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index e67ea82..a6830a6 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 0ea5b8a..9e24a73 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -6534,8 +6534,19 @@
<!-- Interval in milliseconds to average light sensor values for camera light brightness -->
<integer name="config_cameraPrivacyLightAlsAveragingIntervalMillis">3000</integer>
- <!-- Light sensor's lux value to use as the threshold between using day or night brightness -->
- <integer name="config_cameraPrivacyLightAlsNightThreshold">4</integer>
+ <!-- Ambient Light sensor's lux values to use as the threshold between brightness colors defined
+ by config_cameraPrivacyLightColors. If the ambient brightness less than the first element
+ in this array then lights of type "camera" will be set to the color in position 0 of
+ config_cameraPrivacyLightColors. This array must be strictly increasing and have a length
+ of zero means there is only one brightness -->
+ <integer-array name="config_cameraPrivacyLightAlsLuxThresholds">
+ </integer-array>
+ <!-- Colors to configure the camera privacy light at different brightnesses. This array must
+ have exactly one more entry than config_cameraPrivacyLightAlsLuxThresholds,
+ or a length of zero if the feature isn't supported. If nonempty and the device doesn't have
+ an ambient light sensor the last element in this array will be the only one used -->
+ <array name="config_cameraPrivacyLightColors">
+ </array>
<!-- List of system components which are allowed to receive ServiceState entries in an
un-sanitized form, even if the location toggle is off. This is intended ONLY for system
diff --git a/core/res/res/values/config_tv_external_input_logging.xml b/core/res/res/values/config_tv_external_input_logging.xml
new file mode 100644
index 0000000..72e30be
--- /dev/null
+++ b/core/res/res/values/config_tv_external_input_logging.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright (C) 2023 The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+ for different hardware and product builds. Do not translate.
+
+ NOTE: The naming convention is "config_camelCaseValue". Some legacy
+ entries do not follow the convention, but all new entries should. -->
+
+<resources>
+ <bool name="config_tvExternalInputLoggingDisplayNameFilterEnabled">false</bool>
+
+ <string-array name="config_tvExternalInputLoggingDeviceOnScreenDisplayNames">
+ <item>Chromecast</item>
+ <item>Chromecast HD</item>
+ <item>SHIELD</item>
+ <item>Roku</item>
+ <item>Roku Express 4</item>
+ <item>Home Theater</item>
+ <item>Fire TV Stick</item>
+ <item>PlayStation 5</item>
+ <item>NintendoSwitch</item>
+ </string-array>
+
+ <string-array name="config_tvExternalInputLoggingDeviceBrandNames">
+ <item>Chromecast</item>
+ <item>SHIELD</item>
+ <item>Roku</item>
+ <item>Apple</item>
+ <item>Fire TV</item>
+ <item>PlayStation</item>
+ <item>Nintendo</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 889901a..ee0563b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5009,10 +5009,9 @@
<java-symbol type="string" name="vdm_camera_access_denied" />
<java-symbol type="string" name="vdm_secure_window" />
- <java-symbol type="color" name="camera_privacy_light_day"/>
- <java-symbol type="color" name="camera_privacy_light_night"/>
<java-symbol type="integer" name="config_cameraPrivacyLightAlsAveragingIntervalMillis"/>
- <java-symbol type="integer" name="config_cameraPrivacyLightAlsNightThreshold"/>
+ <java-symbol type="array" name="config_cameraPrivacyLightAlsLuxThresholds"/>
+ <java-symbol type="array" name="config_cameraPrivacyLightColors"/>
<java-symbol type="bool" name="config_bg_current_drain_monitor_enabled" />
<java-symbol type="array" name="config_bg_current_drain_threshold_to_restricted_bucket" />
@@ -5221,4 +5220,9 @@
<!-- Whether we order unlocking and waking -->
<java-symbol type="bool" name="config_orderUnlockAndWake" />
+
+ <!-- External TV Input Logging Configs -->
+ <java-symbol type="bool" name="config_tvExternalInputLoggingDisplayNameFilterEnabled" />
+ <java-symbol type="array" name="config_tvExternalInputLoggingDeviceOnScreenDisplayNames" />
+ <java-symbol type="array" name="config_tvExternalInputLoggingDeviceBrandNames" />
</resources>
diff --git a/core/tests/coretests/src/android/provider/NameValueCacheTest.java b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
index 54a3817..87e4a42 100644
--- a/core/tests/coretests/src/android/provider/NameValueCacheTest.java
+++ b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
@@ -39,6 +39,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -54,6 +55,7 @@
* Due to how the classes are structured, we have to test it in a somewhat roundabout way. We're
* mocking out the contentProvider and are handcrafting very specific Bundles to answer the queries.
*/
+@Ignore("b/297724333")
@Presubmit
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -233,6 +235,7 @@
mConfigsCacheGenerationStore.close();
}
+ @Ignore("b/297724333")
@Test
public void testCaching_singleNamespace() throws Exception {
HashMap<String, String> keyValues = new HashMap<>();
@@ -270,6 +273,7 @@
assertThat(cachedKeyValues2).containsExactlyEntriesIn(keyValues);
}
+ @Ignore("b/297724333")
@Test
public void testCaching_multipleNamespaces() throws Exception {
HashMap<String, String> keyValues = new HashMap<>();
@@ -309,6 +313,7 @@
assertThat(cachedKeyValues2).containsExactlyEntriesIn(keyValues2);
}
+ @Ignore("b/297724333")
@Test
public void testCaching_emptyNamespace() throws Exception {
Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
@@ -325,6 +330,7 @@
assertThat(cachedKeyValues).isEmpty();
}
+ @Ignore("b/297724333")
@Test
public void testCaching_singleSetting() throws Exception {
Settings.Secure.putString(mMockContentResolver, SETTING, "a");
@@ -355,6 +361,7 @@
assertThat(cachedValue2).isEqualTo("b");
}
+ @Ignore("b/297724333")
@Test
public void testCaching_multipleSettings() throws Exception {
Settings.Secure.putString(mMockContentResolver, SETTING, "a");
@@ -385,6 +392,7 @@
assertThat(cachedValue2).isEqualTo("b");
}
+ @Ignore("b/297724333")
@Test
public void testCaching_unsetSetting() throws Exception {
String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
index 246a1e7..a0e9947 100644
--- a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
+++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
@@ -48,7 +48,9 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
@Presubmit
@RunWith(AndroidJUnit4.class)
@@ -102,6 +104,25 @@
return partitionOrder.toString();
}
+ // configIndex should come from real time partition order cause partitions could get
+ // reordered by /product/overlay/partition_order.xml
+ private Map<String, Integer> createConfigIndexes(OverlayConfig overlayConfig,
+ String... configPartitions) {
+ Map<String, Integer> configIndexes = new HashMap<>();
+ for (int i = 0; i < configPartitions.length; i++) {
+ configIndexes.put(configPartitions[i], -1);
+ }
+
+ String[] partitions = overlayConfig.getPartitionOrder().split(", ");
+ int index = 0;
+ for (int i = 0; i < partitions.length; i++) {
+ if (configIndexes.containsKey(partitions[i])) {
+ configIndexes.put(partitions[i], index++);
+ }
+ }
+ return configIndexes;
+ }
+
@Test
public void testImmutableAfterNonImmutableFails() throws IOException {
mExpectedException.expect(IllegalStateException.class);
@@ -292,11 +313,13 @@
mScannerRule.addOverlay(createFile("/system_ext/overlay/five.apk"), "five");
final OverlayConfig overlayConfig = createConfigImpl();
- assertConfig(overlayConfig, "one", true, true, 0);
- assertConfig(overlayConfig, "two", true, true, 1);
- assertConfig(overlayConfig, "three", true, true, 2);
- assertConfig(overlayConfig, "four", true, true, 3);
- assertConfig(overlayConfig, "five", true, true, 4);
+ Map<String, Integer> configIndexes = createConfigIndexes(overlayConfig,
+ "vendor", "odm", "oem", "product", "system_ext");
+ assertConfig(overlayConfig, "one", true, true, configIndexes.get("vendor"));
+ assertConfig(overlayConfig, "two", true, true, configIndexes.get("odm"));
+ assertConfig(overlayConfig, "three", true, true, configIndexes.get("oem"));
+ assertConfig(overlayConfig, "four", true, true, configIndexes.get("product"));
+ assertConfig(overlayConfig, "five", true, true, configIndexes.get("system_ext"));
}
@Test
@@ -313,9 +336,11 @@
true, 0);
final OverlayConfig overlayConfig = createConfigImpl();
- assertConfig(overlayConfig, "one", false, true, 0);
- assertConfig(overlayConfig, "two", true, true, 1);
- assertConfig(overlayConfig, "three", false, true, 2);
+ Map<String, Integer> configIndexes = createConfigIndexes(overlayConfig,
+ "vendor", "odm", "product");
+ assertConfig(overlayConfig, "one", false, true, configIndexes.get("vendor"));
+ assertConfig(overlayConfig, "two", true, true, configIndexes.get("odm"));
+ assertConfig(overlayConfig, "three", false, true, configIndexes.get("product"));
}
@Test
@@ -327,9 +352,11 @@
true, 0);
final OverlayConfig overlayConfig = createConfigImpl();
- assertConfig(overlayConfig, "one", false, true, 0);
- assertConfig(overlayConfig, "two", false, true, 1);
- assertConfig(overlayConfig, "three", false, true, 2);
+ Map<String, Integer> configIndexes = createConfigIndexes(overlayConfig,
+ "vendor", "odm", "product");
+ assertConfig(overlayConfig, "one", false, true, configIndexes.get("vendor"));
+ assertConfig(overlayConfig, "two", false, true, configIndexes.get("odm"));
+ assertConfig(overlayConfig, "three", false, true, configIndexes.get("product"));
}
@Test
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index d0c0c02..7edf2fc 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -450,6 +450,10 @@
<dimen name="freeform_resize_corner">44dp</dimen>
+ <!-- The width of the area at the sides of the screen where a freeform task will transition to
+ split select if dragged until the touch input is within the range. -->
+ <dimen name="desktop_mode_transition_area_width">32dp</dimen>
+
<!-- The height of the area at the top of the screen where a freeform task will transition to
fullscreen if dragged until the top bound of the task is within the area. -->
<dimen name="desktop_mode_transition_area_height">16dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt
index fd000ee..a8743fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt
@@ -21,7 +21,6 @@
import android.graphics.PointF
import android.util.Size
import com.android.wm.shell.R
-import com.android.wm.shell.pip.PipDisplayLayoutState
class LegacySizeSpecSource(
private val context: Context,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhonePipKeepClearAlgorithm.java
similarity index 96%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhonePipKeepClearAlgorithm.java
index 2d34035..133242d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhonePipKeepClearAlgorithm.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip.phone;
+package com.android.wm.shell.common.pip;
import android.content.Context;
import android.content.res.Resources;
@@ -24,9 +24,6 @@
import android.view.Gravity;
import com.android.wm.shell.R;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
import java.util.Set;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt
index c563068..18c7bdd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt
@@ -21,7 +21,6 @@
import android.os.SystemProperties
import android.util.Size
import com.android.wm.shell.R
-import com.android.wm.shell.pip.PipDisplayLayoutState
import java.io.PrintWriter
class PhoneSizeSpecSource(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
similarity index 97%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
index 4fef672..a9f687f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -28,8 +28,6 @@
import android.view.Gravity;
import com.android.wm.shell.R;
-import com.android.wm.shell.common.pip.PipUtils;
-import com.android.wm.shell.common.pip.SizeSpecSource;
import java.io.PrintWriter;
@@ -202,7 +200,8 @@
*
* @return {@code false} if the given source is too small to use for the entering animation.
*/
- static boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint, Rect destinationBounds) {
+ public static boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint,
+ Rect destinationBounds) {
return sourceRectHint != null
&& sourceRectHint.width() > destinationBounds.width()
&& sourceRectHint.height() > destinationBounds.height();
@@ -224,7 +223,7 @@
}
/**
- * @return whether the given {@param aspectRatio} is valid.
+ * @return whether the given aspectRatio is valid.
*/
public boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
return Float.compare(mMinAspectRatio, aspectRatio) <= 0
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
similarity index 97%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
index 279ffc5..3b32b6c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -36,7 +36,6 @@
import com.android.internal.util.function.TriConsumer;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.io.PrintWriter;
@@ -314,8 +313,11 @@
return mPipDisplayLayoutState.getDisplayLayout();
}
+ /**
+ * Clears the PiP re-entry state.
+ */
@VisibleForTesting
- void clearReentryState() {
+ public void clearReentryState() {
mPipReentryState = null;
}
@@ -400,11 +402,18 @@
mNamedUnrestrictedKeepClearAreas.remove(name);
}
+
+ /**
+ * @return restricted keep clear areas.
+ */
@NonNull
public Set<Rect> getRestrictedKeepClearAreas() {
return mRestrictedKeepClearAreas;
}
+ /**
+ * @return unrestricted keep clear areas.
+ */
@NonNull
public Set<Rect> getUnrestrictedKeepClearAreas() {
if (mNamedUnrestrictedKeepClearAreas.isEmpty()) return mUnrestrictedKeepClearAreas;
@@ -561,7 +570,11 @@
}
}
- static final class PipReentryState {
+ /**
+ * Represents the state of pip to potentially restore upon reentry.
+ */
+ @VisibleForTesting
+ public static final class PipReentryState {
private static final String TAG = PipReentryState.class.getSimpleName();
private final @Nullable Size mSize;
@@ -573,11 +586,11 @@
}
@Nullable
- Size getSize() {
+ public Size getSize() {
return mSize;
}
- float getSnapFraction() {
+ public float getSnapFraction() {
return mSnapFraction;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDisplayLayoutState.java
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDisplayLayoutState.java
index 4aa260b..ed42117 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDisplayLayoutState.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
import static com.android.wm.shell.common.pip.PipUtils.dpToPx;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipKeepClearAlgorithmInterface.java
similarity index 95%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipKeepClearAlgorithmInterface.java
index 5045cf9..954233c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipKeepClearAlgorithmInterface.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
import android.graphics.Rect;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPinchResizingAlgorithm.java
similarity index 96%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPinchResizingAlgorithm.java
index 23153be72..02b3a88 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPinchResizingAlgorithm.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.wm.shell.pip.phone;
+package com.android.wm.shell.common.pip;
import android.graphics.Point;
import android.graphics.PointF;
@@ -35,7 +35,7 @@
private final PointF mTmpLastCentroid = new PointF();
/**
- * Updates {@param resizeBoundsOut} with the new bounds of the PIP, and returns the angle in
+ * Updates resizeBoundsOut with the new bounds of the PIP, and returns the angle in
* degrees that the PIP should be rotated.
*/
public float calculateBoundsAndAngle(PointF downPoint, PointF downSecondPoint,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipSnapAlgorithm.java
similarity index 84%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipSnapAlgorithm.java
index dd30137..007052e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipSnapAlgorithm.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
import android.graphics.Rect;
@@ -39,14 +39,14 @@
}
/**
- * @return returns a fraction that describes where along the {@param movementBounds} the
- * {@param stackBounds} are. If the {@param stackBounds} are not currently on the
- * {@param movementBounds} exactly, then they will be snapped to the movement bounds.
+ * @return returns a fraction that describes where along the movementBounds the
+ * stackBounds are. If the stackBounds are not currently on the
+ * movementBounds exactly, then they will be snapped to the movement bounds.
* stashType dictates whether the PiP is stashed (off-screen) or not. If
* that's the case, we will have to do some math to calculate the snap fraction
* correctly.
*
- * The fraction is defined in a clockwise fashion against the {@param movementBounds}:
+ * The fraction is defined in a clockwise fashion against the movementBounds:
*
* 0 1
* 4 +---+ 1
@@ -58,10 +58,10 @@
@PipBoundsState.StashType int stashType) {
final Rect tmpBounds = new Rect();
snapRectToClosestEdge(stackBounds, movementBounds, tmpBounds, stashType);
- final float widthFraction = (float) (tmpBounds.left - movementBounds.left) /
- movementBounds.width();
- final float heightFraction = (float) (tmpBounds.top - movementBounds.top) /
- movementBounds.height();
+ final float widthFraction = (float) (tmpBounds.left - movementBounds.left)
+ / movementBounds.width();
+ final float heightFraction = (float) (tmpBounds.top - movementBounds.top)
+ / movementBounds.height();
if (tmpBounds.top == movementBounds.top) {
return widthFraction;
} else if (tmpBounds.left == movementBounds.right) {
@@ -74,10 +74,10 @@
}
/**
- * Moves the {@param stackBounds} along the {@param movementBounds} to the given snap fraction.
+ * Moves the stackBounds along the movementBounds to the given snap fraction.
* See {@link #getSnapFraction(Rect, Rect)}.
*
- * The fraction is define in a clockwise fashion against the {@param movementBounds}:
+ * The fraction is define in a clockwise fashion against the movementBounds:
*
* 0 1
* 4 +---+ 1
@@ -122,11 +122,11 @@
}
/**
- * Snaps the {@param stackBounds} to the closest edge of the {@param movementBounds} and writes
- * the new bounds out to {@param boundsOut}.
+ * Snaps the stackBounds to the closest edge of the movementBounds and writes
+ * the new bounds out to boundsOut.
*/
@VisibleForTesting
- void snapRectToClosestEdge(Rect stackBounds, Rect movementBounds, Rect boundsOut,
+ public void snapRectToClosestEdge(Rect stackBounds, Rect movementBounds, Rect boundsOut,
@PipBoundsState.StashType int stashType) {
int leftEdge = stackBounds.left;
if (stashType == STASH_TYPE_LEFT) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index e28b8d7..1c2cee5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -59,8 +59,15 @@
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
+import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.compatui.CompatUIConfiguration;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
@@ -354,6 +361,42 @@
return new PipMediaController(context, mainHandler);
}
+ @WMSingleton
+ @Provides
+ static SizeSpecSource provideSizeSpecSource(Context context,
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ return new PhoneSizeSpecSource(context, pipDisplayLayoutState);
+ }
+
+ @WMSingleton
+ @Provides
+ static PipBoundsState providePipBoundsState(Context context,
+ SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) {
+ return new PipBoundsState(context, sizeSpecSource, pipDisplayLayoutState);
+ }
+
+
+ @WMSingleton
+ @Provides
+ static PipSnapAlgorithm providePipSnapAlgorithm() {
+ return new PipSnapAlgorithm();
+ }
+
+ @WMSingleton
+ @Provides
+ static PhonePipKeepClearAlgorithm providePhonePipKeepClearAlgorithm(Context context) {
+ return new PhonePipKeepClearAlgorithm(context);
+ }
+
+ @WMSingleton
+ @Provides
+ static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
+ PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
+ PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
+ PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) {
+ return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm,
+ pipKeepClearAlgorithm, pipDisplayLayoutState, sizeSpecSource);
+ }
//
// Bubbles (optional feature)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 4e92ca1..ba882c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -30,9 +30,13 @@
import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.common.pip.SizeSpecSource;
@@ -41,17 +45,12 @@
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransition;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.pip.phone.PhonePipKeepClearAlgorithm;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.pip.phone.PipController;
import com.android.wm.shell.pip.phone.PipMotionHelper;
@@ -119,35 +118,6 @@
}
}
- @WMSingleton
- @Provides
- static PipBoundsState providePipBoundsState(Context context,
- SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) {
- return new PipBoundsState(context, sizeSpecSource, pipDisplayLayoutState);
- }
-
- @WMSingleton
- @Provides
- static PipSnapAlgorithm providePipSnapAlgorithm() {
- return new PipSnapAlgorithm();
- }
-
- @WMSingleton
- @Provides
- static PhonePipKeepClearAlgorithm providePhonePipKeepClearAlgorithm(Context context) {
- return new PhonePipKeepClearAlgorithm(context);
- }
-
- @WMSingleton
- @Provides
- static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
- PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
- PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
- PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) {
- return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm,
- pipKeepClearAlgorithm, pipDisplayLayoutState, sizeSpecSource);
- }
-
// Handler is used by Icon.loadDrawableAsync
@WMSingleton
@Provides
@@ -213,13 +183,6 @@
@WMSingleton
@Provides
- static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper
- pipSurfaceTransactionHelper) {
- return new PipAnimationController(pipSurfaceTransactionHelper);
- }
-
- @WMSingleton
- @Provides
static PipTransition providePipTransition(Context context,
ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions,
PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
@@ -235,13 +198,6 @@
@WMSingleton
@Provides
- static SizeSpecSource provideSizeSpecSource(Context context,
- PipDisplayLayoutState pipDisplayLayoutState) {
- return new PhoneSizeSpecSource(context, pipDisplayLayoutState);
- }
-
- @WMSingleton
- @Provides
static PipAppOpsListener providePipAppOpsListener(Context context,
PipTouchHandler pipTouchHandler,
@ShellMainThread ShellExecutor mainExecutor) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
index c4ca501..b42372b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
@@ -19,6 +19,7 @@
import android.content.Context;
import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import dagger.Module;
@@ -35,4 +36,11 @@
static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) {
return new PipSurfaceTransactionHelper(context);
}
+
+ @WMSingleton
+ @Provides
+ static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper
+ pipSurfaceTransactionHelper) {
+ return new PipAnimationController(pipSurfaceTransactionHelper);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 8dec4ea..af97cf6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -16,11 +16,16 @@
package com.android.wm.shell.dagger.pip;
-import android.annotation.Nullable;
+import android.annotation.NonNull;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.pip2.PipTransition;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
import dagger.Module;
import dagger.Provides;
@@ -33,8 +38,12 @@
public abstract class Pip2Module {
@WMSingleton
@Provides
- @Nullable
- static PipTransition providePipTransition() {
- return null;
+ static PipTransition providePipTransition(@NonNull ShellInit shellInit,
+ @NonNull ShellTaskOrganizer shellTaskOrganizer,
+ @NonNull Transitions transitions,
+ PipBoundsState pipBoundsState,
+ PipBoundsAlgorithm pipBoundsAlgorithm) {
+ return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null,
+ pipBoundsAlgorithm);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
index 9c9364e..570f0a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
@@ -16,8 +16,6 @@
package com.android.wm.shell.dagger.pip;
-import android.annotation.Nullable;
-
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.pip.PipTransitionController;
@@ -38,8 +36,8 @@
@Provides
static PipTransitionController providePipTransitionController(
com.android.wm.shell.pip.PipTransition legacyPipTransition,
- @Nullable com.android.wm.shell.pip2.PipTransition newPipTransition) {
- if (PipUtils.isPip2ExperimentEnabled() && newPipTransition != null) {
+ com.android.wm.shell.pip2.PipTransition newPipTransition) {
+ if (PipUtils.isPip2ExperimentEnabled()) {
return newPipTransition;
} else {
return legacyPipTransition;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
index a6ff9ec..a9675f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
@@ -30,16 +30,15 @@
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.pip.LegacySizeSpecSource;
import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
-import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
@@ -131,15 +130,9 @@
@WMSingleton
@Provides
- static PipSnapAlgorithm providePipSnapAlgorithm() {
- return new PipSnapAlgorithm();
- }
-
- @WMSingleton
- @Provides
static TvPipBoundsAlgorithm provideTvPipBoundsAlgorithm(Context context,
TvPipBoundsState tvPipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
- PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) {
+ PipDisplayLayoutState pipDisplayLayoutState, LegacySizeSpecSource sizeSpecSource) {
return new TvPipBoundsAlgorithm(context, tvPipBoundsState, pipSnapAlgorithm,
pipDisplayLayoutState, sizeSpecSource);
}
@@ -147,13 +140,13 @@
@WMSingleton
@Provides
static TvPipBoundsState provideTvPipBoundsState(Context context,
- SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) {
+ LegacySizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) {
return new TvPipBoundsState(context, sizeSpecSource, pipDisplayLayoutState);
}
@WMSingleton
@Provides
- static SizeSpecSource provideSizeSpecSource(Context context,
+ static LegacySizeSpecSource provideSizeSpecSource(Context context,
PipDisplayLayoutState pipDisplayLayoutState) {
return new LegacySizeSpecSource(context, pipDisplayLayoutState);
}
@@ -200,13 +193,6 @@
@WMSingleton
@Provides
- static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper
- pipSurfaceTransactionHelper) {
- return new PipAnimationController(pipSurfaceTransactionHelper);
- }
-
- @WMSingleton
- @Provides
static PipTransitionState providePipTransitionState() {
return new PipTransitionState();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 0f0d572..a587bed 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.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 @@
* 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
/**
- * Create fullscreen indicator and fades it in.
+ * Create an indicator. Animator fades it in while expanding the bounds outwards.
*/
- 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.
- */
- 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 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 void transitionFreeformIndicatorToFullscreen() {
mIsFullscreen = true;
+ mType = TO_FULLSCREEN_INDICATOR;
final VisualIndicatorAnimator animator =
VisualIndicatorAnimator.toFullscreenAnimatorWithAnimatedBounds(
mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
@@ -160,6 +236,14 @@
}
/**
+ * 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,23 +294,6 @@
* @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);
- final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
- view, bounds, bounds);
- 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) {
final int padding = displayLayout.stableInsets().top;
@@ -235,7 +302,37 @@
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;
+ }
+
+ public static VisualIndicatorAnimator animateBounds(
+ @NonNull View view, int type, @NonNull DisplayLayout displayLayout) {
+ final int padding = displayLayout.stableInsets().top;
+ 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(startBounds));
animator.setInterpolator(new DecelerateInterpolator());
setupIndicatorAnimation(animator);
return animator;
@@ -252,12 +349,13 @@
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 @@
}
/**
- * 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 4740a9d..236dec0 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.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.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 @@
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 @@
) {
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 @@
/**
* 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 @@
*
* @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 @@
if (visualIndicator == null) {
visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
displayController, context, taskSurface, shellTaskOrganizer,
- rootTaskDisplayAreaOrganizer)
- visualIndicator?.createFullscreenIndicator()
+ rootTaskDisplayAreaOrganizer, TO_DESKTOP_INDICATOR)
+ visualIndicator?.createIndicatorWithAnimatedBounds()
}
val indicator = visualIndicator ?: return
if (y >= getFreeformTransitionStatusBarDragThreshold(taskInfo)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index ed9ff1c..9e8f9c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -82,6 +82,9 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.phone.PipMotionHelper;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 83e03dc..e3922d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -64,6 +64,9 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -89,6 +92,7 @@
private final int mEnterExitAnimationDuration;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private final Optional<SplitScreenController> mSplitScreenOptional;
+ private final PipAnimationController mPipAnimationController;
private @PipAnimationController.AnimationType int mEnterAnimationType = ANIM_TYPE_BOUNDS;
private Transitions.TransitionFinishCallback mFinishCallback;
private SurfaceControl.Transaction mFinishTransaction;
@@ -137,10 +141,11 @@
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional<SplitScreenController> splitScreenOptional) {
super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
- pipBoundsAlgorithm, pipAnimationController);
+ pipBoundsAlgorithm);
mContext = context;
mPipTransitionState = pipTransitionState;
mPipDisplayLayoutState = pipDisplayLayoutState;
+ mPipAnimationController = pipAnimationController;
mEnterExitAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipResizeAnimationDuration);
mSurfaceTransactionHelper = pipSurfaceTransactionHelper;
@@ -148,6 +153,13 @@
}
@Override
+ protected void onInit() {
+ if (!PipUtils.isPip2ExperimentEnabled()) {
+ mTransitions.addHandler(this);
+ }
+ }
+
+ @Override
public void startExitTransition(int type, WindowContainerTransaction out,
@Nullable Rect destinationBounds) {
if (destinationBounds != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 64bba67..20c57fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -38,7 +38,8 @@
import androidx.annotation.NonNull;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -52,7 +53,6 @@
*/
public abstract class PipTransitionController implements Transitions.TransitionHandler {
- protected final PipAnimationController mPipAnimationController;
protected final PipBoundsAlgorithm mPipBoundsAlgorithm;
protected final PipBoundsState mPipBoundsState;
protected final ShellTaskOrganizer mShellTaskOrganizer;
@@ -135,22 +135,18 @@
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
PipBoundsState pipBoundsState,
- PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipAnimationController pipAnimationController) {
+ PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm) {
mPipBoundsState = pipBoundsState;
mPipMenuController = pipMenuController;
mShellTaskOrganizer = shellTaskOrganizer;
mPipBoundsAlgorithm = pipBoundsAlgorithm;
- mPipAnimationController = pipAnimationController;
mTransitions = transitions;
- if (!PipUtils.isPip2ExperimentEnabled()) {
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- shellInit.addInitCallback(this::onInit, this);
- }
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ shellInit.addInitCallback(this::onInit, this);
}
}
- private void onInit() {
+ protected void onInit() {
mTransitions.addHandler(this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index cc182ba..7606526 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -38,10 +38,10 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipMediaController;
import com.android.wm.shell.common.pip.PipMediaController.ActionListener;
import com.android.wm.shell.common.pip.PipUiEventLogger;
-import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMenuController;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
index 8c2879e..118ad9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
@@ -15,7 +15,7 @@
*/
package com.android.wm.shell.pip.phone;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
import android.annotation.NonNull;
import android.content.Context;
@@ -36,8 +36,8 @@
import com.android.wm.shell.R;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index ddea574..1064867 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -76,7 +76,12 @@
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -85,12 +90,7 @@
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
index d7d335b..1b1ebc3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
@@ -20,7 +20,7 @@
import android.annotation.NonNull;
import android.graphics.Rect;
-import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipBoundsState;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 8f0a8e1..c708b86 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -20,10 +20,10 @@
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT;
import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_DISMISS;
import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE;
@@ -41,8 +41,8 @@
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import com.android.wm.shell.common.pip.PipAppOpsListener;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index 4e687dd..e5f9fdc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -46,10 +46,11 @@
import com.android.internal.policy.TaskResizingAlgorithm;
import com.android.wm.shell.R;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipPinchResizingAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipTaskOrganizer;
import java.io.PrintWriter;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index cf54a71..2ce4fb9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -18,10 +18,10 @@
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASH_MINIMUM_VELOCITY_THRESHOLD;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE;
import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE;
@@ -50,12 +50,12 @@
import com.android.wm.shell.R;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index cd58ff4..a48e969f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -36,11 +36,11 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.SizeSpecSource;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
index 8d4a384..8a215b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
@@ -16,7 +16,7 @@
package com.android.wm.shell.pip.tv;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index d11f4d5..2b3a93e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -29,10 +29,10 @@
import android.view.Gravity;
import android.view.View;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.SizeSpecSource;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 5f5d8ad..72115fd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -48,11 +48,11 @@
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
index a94bd6e..93f6826 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
@@ -21,12 +21,12 @@
import android.graphics.Rect
import android.util.Size
import android.view.Gravity
-import com.android.wm.shell.pip.PipBoundsState
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_BOTTOM
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_TOP
+import com.android.wm.shell.common.pip.PipBoundsState
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_BOTTOM
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_TOP
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
@@ -54,11 +54,11 @@
* the unstash timeout if already stashed.
*/
data class Placement(
- val bounds: Rect,
- val anchorBounds: Rect,
- @PipBoundsState.StashType val stashType: Int = STASH_TYPE_NONE,
- val unstashDestinationBounds: Rect? = null,
- val triggerStash: Boolean = false
+ val bounds: Rect,
+ val anchorBounds: Rect,
+ @PipBoundsState.StashType val stashType: Int = STASH_TYPE_NONE,
+ val unstashDestinationBounds: Rect? = null,
+ val triggerStash: Boolean = false
) {
/** Bounds to use if the PiP should not be stashed. */
fun getUnstashedBounds() = unstashDestinationBounds ?: bounds
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index 6720804..f315afb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -25,12 +25,12 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipMenuController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index d3253a5..f24b2b3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -21,8 +21,8 @@
import androidx.annotation.NonNull;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTransition;
import com.android.wm.shell.pip.PipTransitionState;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java
index 8ab85d0..b8e4c04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java
@@ -17,27 +17,65 @@
package com.android.wm.shell.pip2;
import android.annotation.NonNull;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.Nullable;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.PipMenuController;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
/** Placeholder, for demonstrate purpose only. */
-public abstract class PipTransition extends PipTransitionController {
+public class PipTransition extends PipTransitionController {
public PipTransition(
@NonNull ShellInit shellInit,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
PipBoundsState pipBoundsState,
PipMenuController pipMenuController,
- PipBoundsAlgorithm pipBoundsAlgorithm,
- PipAnimationController pipAnimationController) {
+ PipBoundsAlgorithm pipBoundsAlgorithm) {
super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
- pipBoundsAlgorithm, pipAnimationController);
+ pipBoundsAlgorithm);
}
+
+ @Override
+ protected void onInit() {
+ if (PipUtils.isPip2ExperimentEnabled()) {
+ mTransitions.addHandler(this);
+ }
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ return null;
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ return false;
+ }
+
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {}
+
+ @Override
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishT) {}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl
index 7171da5..a25f391 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 @@
/**
* 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 f20fe0b..ad40493 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 @@
/** 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 210bf68..f90ee58 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 @@
* 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 @@
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 6970068..842b1bf 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 @@
}
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 026e973..92b44d4 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.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 @@
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 @@
final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
- position, newTaskBounds.top, mWindowDecorByTaskId.get(mTaskId)));
+ position,
+ new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
+ newTaskBounds, mWindowDecorByTaskId.get(mTaskId)));
mIsDragging = false;
return true;
}
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index dfbadae..434b008 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -69,10 +69,22 @@
}
filegroup {
+ name: "WMShellFlickerServicePlatinumTests-src",
+ srcs: [
+ "src/com/android/wm/shell/flicker/service/*/platinum/**/*.kt",
+ "src/com/android/wm/shell/flicker/service/*/scenarios/**/*.kt",
+ "src/com/android/wm/shell/flicker/service/common/**/*.kt",
+ ],
+}
+
+filegroup {
name: "WMShellFlickerServiceTests-src",
srcs: [
"src/com/android/wm/shell/flicker/service/**/*.kt",
],
+ exclude_srcs: [
+ "src/com/android/wm/shell/flicker/service/*/platinum/**/*.kt",
+ ],
}
java_library {
@@ -143,6 +155,7 @@
":WMShellFlickerTestsSplitScreenGroup2-src",
":WMShellFlickerTestsSplitScreenBase-src",
":WMShellFlickerServiceTests-src",
+ ":WMShellFlickerServicePlatinumTests-src",
],
}
@@ -210,3 +223,15 @@
":WMShellFlickerServiceTests-src",
],
}
+
+android_test {
+ name: "WMShellFlickerServicePlatinumTests",
+ defaults: ["WMShellFlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestService.xml"],
+ package_name: "com.android.wm.shell.flicker.service",
+ instrumentation_target_package: "com.android.wm.shell.flicker.service",
+ srcs: [
+ ":WMShellFlickerTestsBase-src",
+ ":WMShellFlickerServicePlatinumTests-src",
+ ],
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/common/Utils.kt
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/common/Utils.kt
index fa723e3..5f15785 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/common/Utils.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service
+package com.android.wm.shell.flicker.service.common
import android.app.Instrumentation
import android.platform.test.rule.NavigationModeRule
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
index e530f63..245184c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
index e9fc437..1f2f1ec 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
index 416692c..ebbf7c5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
index 494a246..71e701c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
index 9b43816..c433b21 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Assume
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
index 50151f1..3f087a5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Assume
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
index 76fbf60..767e7b5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Assume
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
index f8e43f1..2592fd4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Assume
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
index c2100f6..f2cbf24 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
index 70f3bed..538ed96 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
@@ -25,7 +25,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
index 86f394d..0dab5ad 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
index d7b611e..ad3a2d4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
index 3cc5df0..b780a16 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
index 4a9c32f..329d61d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
index 383a6b3..a9933bbe 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index bf1b7f9..46259a8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -33,6 +33,11 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.SizeSpecSource;
import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index 4341c4c..d34e27b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -36,6 +36,8 @@
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.SizeSpecSource;
import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java
index b9226d2..ac13d7f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java
@@ -25,6 +25,8 @@
import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import org.junit.Before;
import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 248d665..4e2b7f6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -53,6 +53,11 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java
index cc9e26b..8c7b47e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java
@@ -29,8 +29,9 @@
import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
import org.junit.Before;
import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
index 024cba3..3d5cd69 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
@@ -33,8 +33,8 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.SizeSpecSource;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
import org.junit.After;
import org.junit.Assert;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 911f5e1..4eb5193 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -55,15 +55,16 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
index 8ce3ca4..0f8db85 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
@@ -29,7 +29,7 @@
import android.testing.AndroidTestingRunner;
import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipBoundsState;
import org.junit.Assert;
import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 12b4f3e..6777a5b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -37,13 +37,13 @@
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 314f195d..9aaabd1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -33,13 +33,13 @@
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.sysui.ShellInit;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt
index 7370ed7..94f2b91 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt
@@ -25,7 +25,7 @@
import com.android.wm.shell.R
import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT
import com.android.wm.shell.pip.tv.TvPipBoundsController.POSITION_DEBOUNCE_TIMEOUT_MILLIS
import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
index 256610b..974539f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
@@ -27,9 +27,9 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.pip.LegacySizeSpecSource;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.SizeSpecSource;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
import org.junit.Before;
import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt
index aedf65d..998060f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt
@@ -22,10 +22,10 @@
import android.util.Size
import android.view.Gravity
import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_BOTTOM
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_TOP
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_BOTTOM
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_TOP
import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index b1ef4e5..6523469 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -91,12 +91,17 @@
StringPoolRef entry_string_ref;
};
-AssetManager2::AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration)
- : configuration_(configuration) {
+AssetManager2::AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration) {
+ configurations_.push_back(configuration);
+
// Don't invalidate caches here as there's nothing cached yet.
SetApkAssets(apk_assets, false);
}
+AssetManager2::AssetManager2() {
+ configurations_.resize(1);
+}
+
bool AssetManager2::SetApkAssets(ApkAssetsList apk_assets, bool invalidate_caches) {
BuildDynamicRefTable(apk_assets);
RebuildFilterList();
@@ -421,9 +426,16 @@
return false;
}
-void AssetManager2::SetConfiguration(const ResTable_config& configuration) {
- const int diff = configuration_.diff(configuration);
- configuration_ = configuration;
+void AssetManager2::SetConfigurations(std::vector<ResTable_config> configurations) {
+ int diff = 0;
+ if (configurations_.size() != configurations.size()) {
+ diff = -1;
+ } else {
+ for (int i = 0; i < configurations_.size(); i++) {
+ diff |= configurations_[i].diff(configurations[i]);
+ }
+ }
+ configurations_ = std::move(configurations);
if (diff) {
RebuildFilterList();
@@ -620,16 +632,6 @@
auto op = StartOperation();
- // Might use this if density_override != 0.
- ResTable_config density_override_config;
-
- // Select our configuration or generate a density override configuration.
- const ResTable_config* desired_config = &configuration_;
- if (density_override != 0 && density_override != configuration_.density) {
- density_override_config = configuration_;
- density_override_config.density = density_override;
- desired_config = &density_override_config;
- }
// Retrieve the package group from the package id of the resource id.
if (UNLIKELY(!is_valid_resid(resid))) {
@@ -648,119 +650,160 @@
}
const PackageGroup& package_group = package_groups_[package_idx];
- auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config,
- stop_at_first_match, ignore_configuration);
- if (UNLIKELY(!result.has_value())) {
- return base::unexpected(result.error());
- }
+ std::optional<FindEntryResult> final_result;
+ bool final_has_locale = false;
+ bool final_overlaid = false;
+ for (auto & config : configurations_) {
+ // Might use this if density_override != 0.
+ ResTable_config density_override_config;
- bool overlaid = false;
- if (!stop_at_first_match && !ignore_configuration) {
- const auto& assets = GetApkAssets(result->cookie);
- if (!assets) {
- ALOGE("Found expired ApkAssets #%d for resource ID 0x%08x.", result->cookie, resid);
- return base::unexpected(std::nullopt);
+ // Select our configuration or generate a density override configuration.
+ const ResTable_config* desired_config = &config;
+ if (density_override != 0 && density_override != config.density) {
+ density_override_config = config;
+ density_override_config.density = density_override;
+ desired_config = &density_override_config;
}
- if (!assets->IsLoader()) {
- for (const auto& id_map : package_group.overlays_) {
- auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid);
- if (!overlay_entry) {
- // No id map entry exists for this target resource.
- continue;
- }
- if (overlay_entry.IsInlineValue()) {
- // The target resource is overlaid by an inline value not represented by a resource.
- ConfigDescription best_frro_config;
- Res_value best_frro_value;
- bool frro_found = false;
- for( const auto& [config, value] : overlay_entry.GetInlineValue()) {
- if ((!frro_found || config.isBetterThan(best_frro_config, desired_config))
- && config.match(*desired_config)) {
- frro_found = true;
- best_frro_config = config;
- best_frro_value = value;
- }
- }
- if (!frro_found) {
+
+ auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config,
+ stop_at_first_match, ignore_configuration);
+ if (UNLIKELY(!result.has_value())) {
+ return base::unexpected(result.error());
+ }
+ bool overlaid = false;
+ if (!stop_at_first_match && !ignore_configuration) {
+ const auto& assets = GetApkAssets(result->cookie);
+ if (!assets) {
+ ALOGE("Found expired ApkAssets #%d for resource ID 0x%08x.", result->cookie, resid);
+ return base::unexpected(std::nullopt);
+ }
+ if (!assets->IsLoader()) {
+ for (const auto& id_map : package_group.overlays_) {
+ auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid);
+ if (!overlay_entry) {
+ // No id map entry exists for this target resource.
continue;
}
- result->entry = best_frro_value;
+ if (overlay_entry.IsInlineValue()) {
+ // The target resource is overlaid by an inline value not represented by a resource.
+ ConfigDescription best_frro_config;
+ Res_value best_frro_value;
+ bool frro_found = false;
+ for( const auto& [config, value] : overlay_entry.GetInlineValue()) {
+ if ((!frro_found || config.isBetterThan(best_frro_config, desired_config))
+ && config.match(*desired_config)) {
+ frro_found = true;
+ best_frro_config = config;
+ best_frro_value = value;
+ }
+ }
+ if (!frro_found) {
+ continue;
+ }
+ result->entry = best_frro_value;
+ result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
+ result->cookie = id_map.cookie;
+
+ if (UNLIKELY(logging_enabled)) {
+ last_resolution_.steps.push_back(Resolution::Step{
+ Resolution::Step::Type::OVERLAID_INLINE, result->cookie, String8()});
+ if (auto path = assets->GetPath()) {
+ const std::string overlay_path = path->data();
+ if (IsFabricatedOverlay(overlay_path)) {
+ // FRRO don't have package name so we use the creating package here.
+ String8 frro_name = String8("FRRO");
+ // Get the first part of it since the expected one should be like
+ // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro
+ // under /data/resource-cache/.
+ const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1);
+ const size_t end = name.find('-');
+ if (frro_name.size() != overlay_path.size() && end != std::string::npos) {
+ frro_name.append(base::StringPrintf(" created by %s",
+ name.substr(0 /* pos */,
+ end).c_str()).c_str());
+ }
+ last_resolution_.best_package_name = frro_name;
+ } else {
+ last_resolution_.best_package_name = result->package_name->c_str();
+ }
+ }
+ overlaid = true;
+ }
+ continue;
+ }
+
+ auto overlay_result = FindEntry(overlay_entry.GetResourceId(), density_override,
+ false /* stop_at_first_match */,
+ false /* ignore_configuration */);
+ if (UNLIKELY(IsIOError(overlay_result))) {
+ return base::unexpected(overlay_result.error());
+ }
+ if (!overlay_result.has_value()) {
+ continue;
+ }
+
+ if (!overlay_result->config.isBetterThan(result->config, desired_config)
+ && overlay_result->config.compare(result->config) != 0) {
+ // The configuration of the entry for the overlay must be equal to or better than the
+ // target configuration to be chosen as the better value.
+ continue;
+ }
+
+ result->cookie = overlay_result->cookie;
+ result->entry = overlay_result->entry;
+ result->config = overlay_result->config;
result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
- result->cookie = id_map.cookie;
if (UNLIKELY(logging_enabled)) {
last_resolution_.steps.push_back(
- Resolution::Step{Resolution::Step::Type::OVERLAID_INLINE, result->cookie, String8()});
- if (auto path = assets->GetPath()) {
- const std::string overlay_path = path->data();
- if (IsFabricatedOverlay(overlay_path)) {
- // FRRO don't have package name so we use the creating package here.
- String8 frro_name = String8("FRRO");
- // Get the first part of it since the expected one should be like
- // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro
- // under /data/resource-cache/.
- const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1);
- const size_t end = name.find('-');
- if (frro_name.size() != overlay_path.size() && end != std::string::npos) {
- frro_name.append(base::StringPrintf(" created by %s",
- name.substr(0 /* pos */,
- end).c_str()).c_str());
- }
- last_resolution_.best_package_name = frro_name;
- } else {
- last_resolution_.best_package_name = result->package_name->c_str();
- }
- }
+ Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->cookie,
+ overlay_result->config.toString()});
+ last_resolution_.best_package_name =
+ overlay_result->package_name->c_str();
overlaid = true;
}
- continue;
- }
-
- auto overlay_result = FindEntry(overlay_entry.GetResourceId(), density_override,
- false /* stop_at_first_match */,
- false /* ignore_configuration */);
- if (UNLIKELY(IsIOError(overlay_result))) {
- return base::unexpected(overlay_result.error());
- }
- if (!overlay_result.has_value()) {
- continue;
- }
-
- if (!overlay_result->config.isBetterThan(result->config, desired_config)
- && overlay_result->config.compare(result->config) != 0) {
- // The configuration of the entry for the overlay must be equal to or better than the target
- // configuration to be chosen as the better value.
- continue;
- }
-
- result->cookie = overlay_result->cookie;
- result->entry = overlay_result->entry;
- result->config = overlay_result->config;
- result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
-
- if (UNLIKELY(logging_enabled)) {
- last_resolution_.steps.push_back(
- Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->cookie,
- overlay_result->config.toString()});
- last_resolution_.best_package_name =
- overlay_result->package_name->c_str();
- overlaid = true;
}
}
}
+
+ bool has_locale = false;
+ if (result->config.locale == 0) {
+ if (default_locale_ != 0) {
+ ResTable_config conf;
+ conf.locale = default_locale_;
+ // Since we know conf has a locale and only a locale, match will tell us if that locale
+ // matches
+ has_locale = conf.match(config);
+ }
+ } else {
+ has_locale = true;
+ }
+
+ // if we don't have a result yet
+ if (!final_result ||
+ // or this config is better before the locale than the existing result
+ result->config.isBetterThanBeforeLocale(final_result->config, desired_config) ||
+ // or the existing config isn't better before locale and this one specifies a locale
+ // whereas the existing one doesn't
+ (!final_result->config.isBetterThanBeforeLocale(result->config, desired_config)
+ && has_locale && !final_has_locale)) {
+ final_result = result.value();
+ final_overlaid = overlaid;
+ final_has_locale = has_locale;
+ }
}
if (UNLIKELY(logging_enabled)) {
- last_resolution_.cookie = result->cookie;
- last_resolution_.type_string_ref = result->type_string_ref;
- last_resolution_.entry_string_ref = result->entry_string_ref;
- last_resolution_.best_config_name = result->config.toString();
- if (!overlaid) {
- last_resolution_.best_package_name = result->package_name->c_str();
+ last_resolution_.cookie = final_result->cookie;
+ last_resolution_.type_string_ref = final_result->type_string_ref;
+ last_resolution_.entry_string_ref = final_result->entry_string_ref;
+ last_resolution_.best_config_name = final_result->config.toString();
+ if (!final_overlaid) {
+ last_resolution_.best_package_name = final_result->package_name->c_str();
}
}
- return result;
+ return *final_result;
}
base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
@@ -778,8 +821,10 @@
// If `desired_config` is not the same as the set configuration or the caller will accept a value
// from any configuration, then we cannot use our filtered list of types since it only it contains
// types matched to the set configuration.
- const bool use_filtered = !ignore_configuration && &desired_config == &configuration_;
-
+ const bool use_filtered = !ignore_configuration && std::find_if(
+ configurations_.begin(), configurations_.end(),
+ [&desired_config](auto& value) { return &desired_config == &value; })
+ != configurations_.end();
const size_t package_count = package_group.packages_.size();
for (size_t pi = 0; pi < package_count; pi++) {
const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi];
@@ -934,10 +979,22 @@
}
std::stringstream log_stream;
- log_stream << base::StringPrintf("Resolution for 0x%08x %s\n"
- "\tFor config - %s", resid, resource_name_string.c_str(),
- configuration_.toString().c_str());
-
+ if (configurations_.size() == 1) {
+ log_stream << base::StringPrintf("Resolution for 0x%08x %s\n"
+ "\tFor config - %s", resid, resource_name_string.c_str(),
+ configurations_[0].toString().c_str());
+ } else {
+ ResTable_config conf = configurations_[0];
+ conf.clearLocale();
+ log_stream << base::StringPrintf("Resolution for 0x%08x %s\n\tFor config - %s and locales",
+ resid, resource_name_string.c_str(), conf.toString().c_str());
+ char str[40];
+ str[0] = '\0';
+ for(auto iter = configurations_.begin(); iter < configurations_.end(); iter++) {
+ iter->getBcp47Locale(str);
+ log_stream << base::StringPrintf(" %s%s", str, iter < configurations_.end() ? "," : "");
+ }
+ }
for (const Resolution::Step& step : last_resolution_.steps) {
constexpr static std::array kStepStrings = {
"Found initial",
@@ -956,13 +1013,13 @@
const auto& assets = GetApkAssets(step.cookie);
log_stream << "\n\t" << prefix << ": " << (assets ? assets->GetDebugName() : "<null>")
<< " #" << step.cookie;
- if (!step.config_name.isEmpty()) {
+ if (!step.config_name.empty()) {
log_stream << " - " << step.config_name;
}
}
log_stream << "\nBest matching is from "
- << (last_resolution_.best_config_name.isEmpty() ? "default"
+ << (last_resolution_.best_config_name.empty() ? "default"
: last_resolution_.best_config_name)
<< " configuration of " << last_resolution_.best_package_name;
return log_stream.str();
@@ -1427,11 +1484,14 @@
package.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) {
FilteredConfigGroup* group = nullptr;
for (const auto& type_entry : type_spec.type_entries) {
- if (type_entry.config.match(configuration_)) {
- if (!group) {
- group = &package.filtered_configs_.editItemAt(type_id - 1);
+ for (auto & config : configurations_) {
+ if (type_entry.config.match(config)) {
+ if (!group) {
+ group = &package.filtered_configs_.editItemAt(type_id - 1);
+ }
+ group->type_entries.push_back(&type_entry);
+ break;
}
- group->type_entries.push_back(&type_entry);
}
}
});
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 112058f..ec14316 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -2568,6 +2568,22 @@
return false;
}
+bool ResTable_config::isBetterThanBeforeLocale(const ResTable_config& o,
+ const ResTable_config* requested) const {
+ if (requested) {
+ if (imsi || o.imsi) {
+ if ((mcc != o.mcc) && requested->mcc) {
+ return (mcc);
+ }
+
+ if ((mnc != o.mnc) && requested->mnc) {
+ return (mnc);
+ }
+ }
+ }
+ return false;
+}
+
bool ResTable_config::isBetterThan(const ResTable_config& o,
const ResTable_config* requested) const {
if (requested) {
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index f611d0d..d9ff35b 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -100,7 +100,7 @@
using ApkAssetsWPtr = wp<const ApkAssets>;
using ApkAssetsList = std::span<const ApkAssetsPtr>;
- AssetManager2() = default;
+ AssetManager2();
explicit AssetManager2(AssetManager2&& other) = default;
AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration);
@@ -156,10 +156,14 @@
// Sets/resets the configuration for this AssetManager. This will cause all
// caches that are related to the configuration change to be invalidated.
- void SetConfiguration(const ResTable_config& configuration);
+ void SetConfigurations(std::vector<ResTable_config> configurations);
- inline const ResTable_config& GetConfiguration() const {
- return configuration_;
+ inline const std::vector<ResTable_config>& GetConfigurations() const {
+ return configurations_;
+ }
+
+ inline void SetDefaultLocale(uint32_t default_locale) {
+ default_locale_ = default_locale;
}
// Returns all configurations for which there are resources defined, or an I/O error if reading
@@ -465,9 +469,11 @@
// without taking too much memory.
std::array<uint8_t, std::numeric_limits<uint8_t>::max() + 1> package_ids_;
- // The current configuration set for this AssetManager. When this changes, cached resources
+ uint32_t default_locale_;
+
+ // The current configurations set for this AssetManager. When this changes, cached resources
// may need to be purged.
- ResTable_config configuration_ = {};
+ std::vector<ResTable_config> configurations_;
// Cached set of bags. These are cached because they can inherit keys from parent bags,
// which involves some calculation.
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 6de1d1e..fdb3551 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1375,6 +1375,8 @@
// match the requested configuration at all.
bool isLocaleBetterThan(const ResTable_config& o, const ResTable_config* requested) const;
+ bool isBetterThanBeforeLocale(const ResTable_config& o, const ResTable_config* requested) const;
+
String8 toString() const;
};
diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp
index 6fae72a..2caa98c 100644
--- a/libs/androidfw/tests/AssetManager2_bench.cpp
+++ b/libs/androidfw/tests/AssetManager2_bench.cpp
@@ -228,10 +228,12 @@
ResTable_config config;
memset(&config, 0, sizeof(config));
+ std::vector<ResTable_config> configs;
+ configs.push_back(config);
while (state.KeepRunning()) {
- config.sdkVersion = ~config.sdkVersion;
- assets.SetConfiguration(config);
+ configs[0].sdkVersion = ~configs[0].sdkVersion;
+ assets.SetConfigurations(configs);
}
}
BENCHMARK(BM_AssetManagerSetConfigurationFramework);
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index df3fa02..c62f095 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -113,7 +113,7 @@
desired_config.language[1] = 'e';
AssetManager2 assetmanager;
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -137,7 +137,7 @@
desired_config.language[1] = 'e';
AssetManager2 assetmanager;
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -466,10 +466,10 @@
TEST_F(AssetManager2Test, DensityOverride) {
AssetManager2 assetmanager;
assetmanager.SetApkAssets({basic_assets_, basic_xhdpi_assets_, basic_xxhdpi_assets_});
- assetmanager.SetConfiguration({
+ assetmanager.SetConfigurations({{
.density = ResTable_config::DENSITY_XHIGH,
.sdkVersion = 21,
- });
+ }});
auto value = assetmanager.GetResource(basic::R::string::density, false /*may_be_bag*/);
ASSERT_TRUE(value.has_value());
@@ -721,7 +721,7 @@
ResTable_config desired_config;
AssetManager2 assetmanager;
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_});
assetmanager.SetResourceResolutionLoggingEnabled(false);
@@ -736,7 +736,7 @@
ResTable_config desired_config;
AssetManager2 assetmanager;
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_});
auto result = assetmanager.GetLastResourceResolution();
@@ -751,7 +751,7 @@
AssetManager2 assetmanager;
assetmanager.SetResourceResolutionLoggingEnabled(true);
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -774,7 +774,7 @@
AssetManager2 assetmanager;
assetmanager.SetResourceResolutionLoggingEnabled(true);
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -796,7 +796,7 @@
AssetManager2 assetmanager;
assetmanager.SetResourceResolutionLoggingEnabled(true);
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -817,7 +817,7 @@
AssetManager2 assetmanager;
assetmanager.SetResourceResolutionLoggingEnabled(true);
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({overlayable_assets_});
const auto map = assetmanager.GetOverlayableMapForPackage(0x7f);
diff --git a/libs/androidfw/tests/BenchmarkHelpers.cpp b/libs/androidfw/tests/BenchmarkHelpers.cpp
index b97dd96..8b883f4 100644
--- a/libs/androidfw/tests/BenchmarkHelpers.cpp
+++ b/libs/androidfw/tests/BenchmarkHelpers.cpp
@@ -66,7 +66,7 @@
AssetManager2 assetmanager;
assetmanager.SetApkAssets(apk_assets);
if (config != nullptr) {
- assetmanager.SetConfiguration(*config);
+ assetmanager.SetConfigurations({*config});
}
while (state.KeepRunning()) {
diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp
index e08a6a7..181d141 100644
--- a/libs/androidfw/tests/Theme_test.cpp
+++ b/libs/androidfw/tests/Theme_test.cpp
@@ -260,7 +260,7 @@
ResTable_config night{};
night.uiMode = ResTable_config::UI_MODE_NIGHT_YES;
night.version = 8u;
- am_night.SetConfiguration(night);
+ am_night.SetConfigurations({night});
auto theme = am.NewTheme();
{
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index a17f2f7..7aef7a5 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -203,10 +203,9 @@
if (advances) {
advancesArray.reset(new jfloat[count]);
}
- minikin::MinikinRect bounds;
const float advance = MinikinUtils::measureText(
paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, start, count,
- contextCount, advancesArray.get(), &bounds);
+ contextCount, advancesArray.get(), nullptr);
if (advances) {
env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray.get());
}
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 120d812..d54c5f5 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -162,7 +162,6 @@
destroyHardwareResources();
mAnimationContext->destroy();
mRenderThread.cacheManager().onContextStopped(this);
- mHintSessionWrapper.destroy();
}
static void setBufferCount(ANativeWindow* window) {
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 9695e6f..c41cd04 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -63,10 +63,10 @@
std::shared_ptr<PointerController> PointerController::create(
const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
- SpriteController& spriteController) {
+ SpriteController& spriteController, bool enabled) {
// using 'new' to access non-public constructor
std::shared_ptr<PointerController> controller = std::shared_ptr<PointerController>(
- new PointerController(policy, looper, spriteController));
+ new PointerController(policy, looper, spriteController, enabled));
/*
* Now we need to hook up the constructed PointerController object to its callbacks.
@@ -85,9 +85,10 @@
}
PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
- const sp<Looper>& looper, SpriteController& spriteController)
+ const sp<Looper>& looper, SpriteController& spriteController,
+ bool enabled)
: PointerController(
- policy, looper, spriteController,
+ policy, looper, spriteController, enabled,
[](const sp<android::gui::WindowInfosListener>& listener) {
SurfaceComposerClient::getDefault()->addWindowInfosListener(listener);
},
@@ -97,9 +98,10 @@
PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
const sp<Looper>& looper, SpriteController& spriteController,
- WindowListenerConsumer registerListener,
+ bool enabled, WindowListenerConsumer registerListener,
WindowListenerConsumer unregisterListener)
- : mContext(policy, looper, spriteController, *this),
+ : mEnabled(enabled),
+ mContext(policy, looper, spriteController, *this),
mCursorController(mContext),
mDisplayInfoListener(sp<DisplayInfoListener>::make(this)),
mUnregisterWindowInfosListener(std::move(unregisterListener)) {
@@ -119,10 +121,14 @@
}
std::optional<FloatRect> PointerController::getBounds() const {
+ if (!mEnabled) return {};
+
return mCursorController.getBounds();
}
void PointerController::move(float deltaX, float deltaY) {
+ if (!mEnabled) return;
+
const int32_t displayId = mCursorController.getDisplayId();
vec2 transformed;
{
@@ -134,6 +140,8 @@
}
void PointerController::setPosition(float x, float y) {
+ if (!mEnabled) return;
+
const int32_t displayId = mCursorController.getDisplayId();
vec2 transformed;
{
@@ -145,6 +153,11 @@
}
FloatPoint PointerController::getPosition() const {
+ if (!mEnabled) {
+ return FloatPoint{AMOTION_EVENT_INVALID_CURSOR_POSITION,
+ AMOTION_EVENT_INVALID_CURSOR_POSITION};
+ }
+
const int32_t displayId = mCursorController.getDisplayId();
const auto p = mCursorController.getPosition();
{
@@ -155,20 +168,28 @@
}
int32_t PointerController::getDisplayId() const {
+ if (!mEnabled) return ADISPLAY_ID_NONE;
+
return mCursorController.getDisplayId();
}
void PointerController::fade(Transition transition) {
+ if (!mEnabled) return;
+
std::scoped_lock lock(getLock());
mCursorController.fade(transition);
}
void PointerController::unfade(Transition transition) {
+ if (!mEnabled) return;
+
std::scoped_lock lock(getLock());
mCursorController.unfade(transition);
}
void PointerController::setPresentation(Presentation presentation) {
+ if (!mEnabled) return;
+
std::scoped_lock lock(getLock());
if (mLocked.presentation == presentation) {
@@ -193,6 +214,8 @@
void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
BitSet32 spotIdBits, int32_t displayId) {
+ if (!mEnabled) return;
+
std::scoped_lock lock(getLock());
std::array<PointerCoords, MAX_POINTERS> outSpotCoords{};
const ui::Transform& transform = getTransformForDisplayLocked(displayId);
@@ -216,6 +239,8 @@
}
void PointerController::clearSpots() {
+ if (!mEnabled) return;
+
std::scoped_lock lock(getLock());
clearSpotsLocked();
}
@@ -277,11 +302,15 @@
}
void PointerController::updatePointerIcon(PointerIconStyle iconId) {
+ if (!mEnabled) return;
+
std::scoped_lock lock(getLock());
mCursorController.updatePointerIcon(iconId);
}
void PointerController::setCustomPointerIcon(const SpriteIcon& icon) {
+ if (!mEnabled) return;
+
std::scoped_lock lock(getLock());
mCursorController.setCustomPointerIcon(icon);
}
@@ -326,6 +355,11 @@
}
void PointerController::dump(std::string& dump) {
+ if (!mEnabled) {
+ dump += INDENT "PointerController: DISABLED due to ongoing PointerChoreographer refactor\n";
+ return;
+ }
+
dump += INDENT "PointerController:\n";
std::scoped_lock lock(getLock());
dump += StringPrintf(INDENT2 "Presentation: %s\n",
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 01748a8..de39eda 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -47,7 +47,7 @@
public:
static std::shared_ptr<PointerController> create(
const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
- SpriteController& spriteController);
+ SpriteController& spriteController, bool enabled);
~PointerController() override;
@@ -83,12 +83,13 @@
// Constructor used to test WindowInfosListener registration.
PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
- SpriteController& spriteController, WindowListenerConsumer registerListener,
+ SpriteController& spriteController, bool enabled,
+ WindowListenerConsumer registerListener,
WindowListenerConsumer unregisterListener);
private:
PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
- SpriteController& spriteController);
+ SpriteController& spriteController, bool enabled);
friend PointerControllerContext::LooperCallback;
friend PointerControllerContext::MessageHandler;
@@ -99,6 +100,8 @@
// we use the DisplayInfoListener's lock in PointerController.
std::mutex& getLock() const;
+ const bool mEnabled;
+
PointerControllerContext mContext;
MouseCursorController mCursorController;
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index 3e2e43f..94faf4a 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -181,7 +181,8 @@
EXPECT_CALL(*mSpriteController, createSprite())
.WillOnce(Return(mPointerSprite));
- mPointerController = PointerController::create(mPolicy, mLooper, *mSpriteController);
+ mPointerController =
+ PointerController::create(mPolicy, mLooper, *mSpriteController, /*enabled=*/true);
}
PointerControllerTest::~PointerControllerTest() {
@@ -321,6 +322,7 @@
const sp<Looper>& looper, SpriteController& spriteController)
: PointerController(
new MockPointerControllerPolicyInterface(), looper, spriteController,
+ /*enabled=*/true,
[®isteredListener](const sp<android::gui::WindowInfosListener>& listener) {
// Register listener
registeredListener = listener;
diff --git a/native/android/configuration.cpp b/native/android/configuration.cpp
index b50514d..283445f 100644
--- a/native/android/configuration.cpp
+++ b/native/android/configuration.cpp
@@ -36,7 +36,7 @@
void AConfiguration_fromAssetManager(AConfiguration* out, AAssetManager* am) {
ScopedLock<AssetManager2> locked_mgr(*AssetManagerForNdkAssetManager(am));
- ResTable_config config = locked_mgr->GetConfiguration();
+ ResTable_config config = locked_mgr->GetConfigurations()[0];
// AConfiguration is not a virtual subclass, so we can memcpy.
memcpy(out, &config, sizeof(config));
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
index 5e66972..7669e79b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
@@ -3,7 +3,10 @@
hughchen@google.com
timhypeng@google.com
robertluo@google.com
-changbetty@google.com
songferngwang@google.com
+yqian@google.com
+chelseahao@google.com
+yiyishen@google.com
+hahong@google.com
# Emergency approvers in case the above are not available
diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.java
index 5c48c54..6b855c0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.java
+++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.java
@@ -31,6 +31,7 @@
import java.util.Map;
public final class QrCodeGenerator {
+ private static final int DEFAULT_MARGIN = -1;
/**
* Generates a barcode image with {@code contents}.
*
@@ -40,7 +41,20 @@
*/
public static Bitmap encodeQrCode(String contents, int size)
throws WriterException, IllegalArgumentException {
- return encodeQrCode(contents, size, /*invert=*/false);
+ return encodeQrCode(contents, size, DEFAULT_MARGIN, /*invert=*/false);
+ }
+
+ /**
+ * Generates a barcode image with {@code contents}.
+ *
+ * @param contents The contents to encode in the barcode
+ * @param size The preferred image size in pixels
+ * @param margin The margin around the actual barcode
+ * @return Barcode bitmap
+ */
+ public static Bitmap encodeQrCode(String contents, int size, int margin)
+ throws WriterException, IllegalArgumentException {
+ return encodeQrCode(contents, size, margin, /*invert=*/false);
}
/**
@@ -53,10 +67,27 @@
*/
public static Bitmap encodeQrCode(String contents, int size, boolean invert)
throws WriterException, IllegalArgumentException {
+ return encodeQrCode(contents, size, DEFAULT_MARGIN, /*invert=*/invert);
+ }
+
+ /**
+ * Generates a barcode image with {@code contents}.
+ *
+ * @param contents The contents to encode in the barcode
+ * @param size The preferred image size in pixels
+ * @param margin The margin around the actual barcode
+ * @param invert Whether to invert the black/white pixels (e.g. for dark mode)
+ * @return Barcode bitmap
+ */
+ public static Bitmap encodeQrCode(String contents, int size, int margin, boolean invert)
+ throws WriterException, IllegalArgumentException {
final Map<EncodeHintType, Object> hints = new HashMap<>();
if (!isIso88591(contents)) {
hints.put(EncodeHintType.CHARACTER_SET, StandardCharsets.UTF_8.name());
}
+ if (margin != DEFAULT_MARGIN) {
+ hints.put(EncodeHintType.MARGIN, margin);
+ }
final BitMatrix qrBits = new MultiFormatWriter().encode(contents, BarcodeFormat.QR_CODE,
size, size, hints);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index fa2d677..1d25ac7 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -106,5 +106,10 @@
Settings.Global.Wearable.RTL_SWIPE_TO_DISMISS_ENABLED_DEV,
Settings.Global.Wearable.REDUCE_MOTION,
Settings.Global.Wearable.WEAR_LAUNCHER_UI_MODE,
+ Settings.Global.Wearable.USER_HFP_CLIENT_SETTING,
+ Settings.Global.Wearable.RSB_WAKE_ENABLED,
+ Settings.Global.Wearable.SCREENSHOT_ENABLED,
+ Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED,
+ Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 2d62e2a..8787c25 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -217,6 +217,7 @@
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_ICON_TYPE,
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY,
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED,
Settings.Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 4494765..dfc3cef 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -312,6 +312,7 @@
VALIDATORS.put(
Secure.ACCESSIBILITY_BUTTON_TARGETS,
ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR);
+ VALIDATORS.put(Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ONE_HANDED_MODE_ACTIVATED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ONE_HANDED_MODE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ONE_HANDED_MODE_TIMEOUT, ANY_INTEGER_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index d2b444b..7186aba 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1842,6 +1842,10 @@
SecureSettingsProto.Accessibility
.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED);
dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
+ SecureSettingsProto.Accessibility
+ .ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED);
+ dumpSetting(s, p,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED,
SecureSettingsProto.Accessibility
.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED);
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 203efbf..92f65d6 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -658,7 +658,6 @@
Settings.Global.Wearable.COMPANION_BLE_ROLE,
Settings.Global.Wearable.COMPANION_NAME,
Settings.Global.Wearable.COMPANION_APP_NAME,
- Settings.Global.Wearable.USER_HFP_CLIENT_SETTING,
Settings.Global.Wearable.COMPANION_OS_VERSION,
Settings.Global.Wearable.ENABLE_ALL_LANGUAGES,
Settings.Global.Wearable.SETUP_LOCALE,
@@ -673,16 +672,12 @@
Settings.Global.Wearable.CLOCKWORK_LONG_PRESS_TO_ASSISTANT_ENABLED,
Settings.Global.Wearable.WET_MODE_ON,
Settings.Global.Wearable.COOLDOWN_MODE_ON,
- Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED,
- Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED,
Settings.Global.Wearable.BEDTIME_MODE,
Settings.Global.Wearable.BEDTIME_HARD_MODE,
- Settings.Global.Wearable.RSB_WAKE_ENABLED,
Settings.Global.Wearable.LOCK_SCREEN_STATE,
Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_ENABLED,
Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_TYPE,
Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_SPEED,
- Settings.Global.Wearable.SCREENSHOT_ENABLED,
Settings.Global.Wearable.DISABLE_AOD_WHILE_PLUGGED,
Settings.Global.Wearable.NETWORK_LOCATION_OPT_IN,
Settings.Global.Wearable.CUSTOM_COLOR_FOREGROUND,
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 2077af8..c92fe22 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -326,6 +326,17 @@
"tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt",
"tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt",
"tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt",
+
+ /* Bouncer UI tests */
+ "tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt",
+ "tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt",
+ "tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java",
+ "tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt",
+ "tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java",
+ "tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt",
+ "tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java",
+ "tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt",
+ "tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt",
],
path: "tests/src",
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 6778d5a..b5b873c 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -349,6 +349,9 @@
<uses-permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT" />
+ <!-- Listen to (dis-)connection of external displays and enable / disable them. -->
+ <uses-permission android:name="android.permission.MANAGE_DISPLAYS" />
+
<protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
index 7545ff4..8a8557a 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.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 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 64227b8..03efbe0 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.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 @@
.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) }
- }
- }
- viewModel.onDragEnd()
- },
- ) { change, _ ->
- inputPosition = change.position
- viewModel.onDrag(
- xPx = change.position.x,
- yPx = change.position.y,
- containerSizePx = containerSize.width,
- verticalOffsetPx = verticalOffset,
- )
+ 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,
+ )
+ }
+ }
}
) {
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 ec6e5ed..e5c6977 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.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 @@
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 0a100ba..b3b44cb 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 @@
modifier: Modifier,
) {
LockscreenScene(
- viewModel = viewModel,
viewProvider = viewProvider,
+ longPressViewModel = viewModel.longPress,
modifier = modifier,
)
}
@@ -85,23 +98,70 @@
@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 774c409..40b0b4a 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 @@
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 c865070..31cbcb9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalComposeUiApi::class)
+
package com.android.systemui.scene.ui.composable
import androidx.compose.foundation.layout.fillMaxSize
@@ -22,7 +24,12 @@
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.motionEventSpy
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.motionEventSpy
+import androidx.compose.ui.input.pointer.pointerInput
import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.ObservableTransitionState as SceneTransitionObservableTransitionState
import com.android.compose.animation.scene.SceneKey as SceneTransitionSceneKey
@@ -56,6 +63,7 @@
* must have entries in this map.
* @param modifier A modifier.
*/
+@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun SceneContainer(
viewModel: SceneContainerViewModel,
@@ -79,7 +87,18 @@
onChangeScene = viewModel::onSceneChanged,
transitions = SceneContainerTransitions,
state = state,
- modifier = modifier.fillMaxSize(),
+ modifier =
+ modifier
+ .fillMaxSize()
+ .motionEventSpy { event -> viewModel.onMotionEvent(event) }
+ .pointerInput(Unit) {
+ awaitPointerEventScope {
+ while (true) {
+ awaitPointerEvent(PointerEventPass.Final)
+ viewModel.onMotionEventComplete()
+ }
+ }
+ }
) {
sceneByKey.forEach { (sceneKey, composableScene) ->
scene(
diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml
new file mode 100644
index 0000000..569dd4c
--- /dev/null
+++ b/packages/SystemUI/res/layout/connected_display_dialog.xml
@@ -0,0 +1,69 @@
+<!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ android:paddingHorizontal="@dimen/dialog_side_padding"
+ android:paddingTop="@dimen/dialog_top_padding"
+ android:background="@*android:drawable/bottomsheet_background"
+ android:paddingBottom="@dimen/dialog_bottom_padding">
+
+ <ImageView
+ android:id="@+id/connected_display_dialog_icon"
+ android:layout_width="@dimen/screenrecord_logo_size"
+ android:layout_height="@dimen/screenrecord_logo_size"
+ android:importantForAccessibility="no"
+ android:src="@drawable/stat_sys_connected_display"
+ android:tint="?androidprv:attr/materialColorPrimary" />
+
+ <TextView
+ android:id="@+id/connected_display_dialog_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/screenrecord_title_margin_top"
+ android:gravity="center"
+ android:text="@string/connected_display_dialog_start_mirroring"
+ android:textAppearance="@style/TextAppearance.Dialog.Title" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/screenrecord_buttons_margin_top"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/cancel"
+ style="@style/Widget.Dialog.Button.BorderButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/cancel" />
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1" />
+
+ <Button
+ android:id="@+id/enable_display"
+ style="@style/Widget.Dialog.Button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/enable_display" />
+ </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer.xml b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
index 909048e..b00908f 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_footer.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
@@ -47,7 +47,7 @@
android:layout_height="48dp"
android:layout_marginTop="12dp"
android:layout_marginStart="16dp"
- app:layout_constraintVertical_bias="0.0"
+ app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
@@ -65,7 +65,6 @@
android:layout_height="48dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="16dp"
- app:layout_constraintVertical_bias="1.0"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@id/manage_text"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b37aeee..6840108 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3184,6 +3184,12 @@
<!-- Label for a button that, when clicked, sends the user to the app store to install an app. [CHAR LIMIT=64]. -->
<string name="install_app">Install app</string>
+ <!--- Title of the dialog appearing when an external display is connected, asking whether to start mirroring [CHAR LIMIT=NONE]-->
+ <string name="connected_display_dialog_start_mirroring">Mirror to external display?</string>
+
+ <!--- Label of the "enable display" button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
+ <string name="enable_display">Enable display</string>
+
<!-- Title of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=30] -->
<string name="privacy_dialog_title">Microphone & Camera</string>
<!-- Subtitle of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 06b6692..1703b30 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -48,6 +48,7 @@
private static final long STATUS_AREA_MOVE_UP_MILLIS = 967;
private static final long STATUS_AREA_MOVE_DOWN_MILLIS = 467;
private static final float SMARTSPACE_TRANSLATION_CENTER_MULTIPLIER = 1.4f;
+ private static final float SMARTSPACE_TOP_PADDING_MULTIPLIER = 2.625f;
@IntDef({LARGE, SMALL})
@Retention(RetentionPolicy.SOURCE)
@@ -96,6 +97,14 @@
private KeyguardClockFrame mLargeClockFrame;
private ClockController mClock;
+ // It's bc_smartspace_view, assigned by KeyguardClockSwitchController
+ // to get the top padding for translating smartspace for weather clock
+ private View mSmartspace;
+
+ // Smartspace in weather clock is translated by this value
+ // to compensate for the position invisible dateWeatherView
+ private int mSmartspaceTop = -1;
+
private KeyguardStatusAreaView mStatusArea;
private int mSmartspaceTopOffset;
private float mWeatherClockSmartspaceScaling = 1f;
@@ -134,8 +143,11 @@
public void onConfigChanged() {
mClockSwitchYAmount = mContext.getResources().getDimensionPixelSize(
R.dimen.keyguard_clock_switch_y_shift);
- mSmartspaceTopOffset = mContext.getResources().getDimensionPixelSize(
- R.dimen.keyguard_smartspace_top_offset);
+ mSmartspaceTopOffset = (int) (mContext.getResources().getDimensionPixelSize(
+ R.dimen.keyguard_smartspace_top_offset)
+ * mContext.getResources().getConfiguration().fontScale
+ / mContext.getResources().getDisplayMetrics().density
+ * SMARTSPACE_TOP_PADDING_MULTIPLIER);
mWeatherClockSmartspaceScaling = ResourcesCompat.getFloat(
mContext.getResources(), R.dimen.weather_clock_smartspace_scale);
mWeatherClockSmartspaceTranslateX = mContext.getResources().getDimensionPixelSize(
@@ -145,6 +157,12 @@
updateStatusArea(/* animate= */false);
}
+ /** Get bc_smartspace_view from KeyguardClockSwitchController
+ * Use its top to decide the translation value */
+ public void setSmartspace(View smartspace) {
+ mSmartspace = smartspace;
+ }
+
/** Sets whether the large clock is being shown on a connected display. */
public void setLargeClockOnSecondaryDisplay(boolean onSecondaryDisplay) {
if (mClock != null) {
@@ -295,7 +313,7 @@
&& mClock.getLargeClock().getConfig().getHasCustomWeatherDataDisplay()) {
statusAreaClockScale = mWeatherClockSmartspaceScaling;
statusAreaClockTranslateX = mWeatherClockSmartspaceTranslateX;
- statusAreaClockTranslateY = mWeatherClockSmartspaceTranslateY;
+ statusAreaClockTranslateY = mWeatherClockSmartspaceTranslateY - mSmartspaceTop;
if (mSplitShadeCentered) {
statusAreaClockTranslateX *= SMARTSPACE_TRANSLATION_CENTER_MULTIPLIER;
}
@@ -418,10 +436,14 @@
post(() -> updateClockTargetRegions());
}
- if (mDisplayedClockSize != null && !mChildrenAreLaidOut) {
+ if (mSmartspace != null && mSmartspaceTop != mSmartspace.getTop()) {
+ mSmartspaceTop = mSmartspace.getTop();
post(() -> updateClockViews(mDisplayedClockSize == LARGE, mAnimateOnLayout));
}
+ if (mDisplayedClockSize != null && !mChildrenAreLaidOut) {
+ post(() -> updateClockViews(mDisplayedClockSize == LARGE, mAnimateOnLayout));
+ }
mChildrenAreLaidOut = true;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 6d2880e..d897960 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -372,6 +372,7 @@
mSmartspaceView.setPaddingRelative(startPadding, 0, endPadding, 0);
mKeyguardUnlockAnimationController.setLockscreenSmartspace(mSmartspaceView);
+ mView.setSmartspace(mSmartspaceView);
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index 8611dbbb..1d37809 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -142,11 +142,13 @@
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 951a6ae..ab9b647 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.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.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 @@
* 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 @@
@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 @@
@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 @@
private boolean mDownDetected;
private final Rect mSensorTouchLocation = new Rect();
+ private LockIconView mView;
@VisibleForTesting
final Consumer<TransitionStep> mDozeTransitionCallback = (TransitionStep step) -> {
@@ -178,7 +182,6 @@
@Inject
public LockIconViewController(
- @Nullable LockIconView view,
@NonNull StatusBarStateController statusBarStateController,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
@NonNull KeyguardViewController keyguardViewController,
@@ -195,9 +198,9 @@
@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 @@
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 @@
collectFlow(mView, mKeyguardInteractor.isActiveDreamLockscreenHosted(),
mIsActiveDreamLockscreenHostedCallback);
}
- }
- @Override
- protected void onViewAttached() {
updateIsUdfpsEnrolled();
updateConfiguration();
updateKeyguardShowing();
@@ -256,19 +280,49 @@
mStatusBarState = mStatusBarStateController.getState();
updateColors();
- mConfigurationController.addCallback(mConfigurationListener);
-
- mAuthController.addCallback(mAuthControllerCallback);
- mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
- mStatusBarStateController.addCallback(mStatusBarStateListener);
- mKeyguardStateController.addCallback(mKeyguardStateCallback);
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);
mAccessibilityManager.addAccessibilityStateChangeListener(
mAccessibilityStateChangeListener);
- updateAccessibility();
+
+ }
+
+ private void unregisterCallbacks() {
+ mAuthController.removeCallback(mAuthControllerCallback);
+ mConfigurationController.removeCallback(mConfigurationListener);
+ mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
+ mStatusBarStateController.removeCallback(mStatusBarStateListener);
+ mKeyguardStateController.removeCallback(mKeyguardStateCallback);
+ mAccessibilityManager.removeAccessibilityStateChangeListener(
+ mAccessibilityStateChangeListener);
+
}
private void updateAccessibility() {
@@ -279,18 +333,6 @@
}
}
- @Override
- protected void onViewDetached() {
- mAuthController.removeCallback(mAuthControllerCallback);
- mConfigurationController.removeCallback(mConfigurationListener);
- mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
- mStatusBarStateController.removeCallback(mStatusBarStateListener);
- mKeyguardStateController.removeCallback(mKeyguardStateCallback);
-
- mAccessibilityManager.removeAccessibilityStateChangeListener(
- mAccessibilityStateChangeListener);
- }
-
public float getTop() {
return mView.getLocationTop();
}
@@ -363,28 +405,6 @@
}
}
- 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 @@
}
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 @@
} else {
mVibrator.vibrate(
Process.myUid(),
- getContext().getOpPackageName(),
+ mContext.getOpPackageName(),
UdfpsController.EFFECT_CLICK,
"lock-icon-down",
TOUCH_VIBRATION_ATTRIBUTES);
@@ -769,7 +786,7 @@
} else {
mVibrator.vibrate(
Process.myUid(),
- getContext().getOpPackageName(),
+ mContext.getOpPackageName(),
UdfpsController.EFFECT_CLICK,
"lock-screen-lock-icon-longpress",
TOUCH_VIBRATION_ATTRIBUTES);
diff --git a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
index 3c74bf4..066cba23 100644
--- a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
@@ -16,24 +16,69 @@
package com.android.systemui.back.domain.interactor
+import android.window.BackEvent
+import android.window.OnBackAnimationCallback
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
+import android.window.WindowOnBackInvokedDispatcher
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.shade.QuickSettingsController
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
/** Handles requests to go back either from a button or gesture. */
@SysUISingleton
class BackActionInteractor
@Inject
constructor(
+ @Application private val scope: CoroutineScope,
private val statusBarStateController: StatusBarStateController,
private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
- private val shadeController: ShadeController
-) {
+ private val shadeController: ShadeController,
+ private val notificationShadeWindowController: NotificationShadeWindowController,
+ private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor,
+ featureFlags: FeatureFlags,
+) : CoreStartable {
+
+ private var isCallbackRegistered = false
+
+ private val callback =
+ if (featureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE)) {
+ /**
+ * New callback that handles back gesture invoked, cancel, progress and provides
+ * feedback via Shade animation.
+ */
+ object : OnBackAnimationCallback {
+ override fun onBackInvoked() {
+ onBackRequested()
+ }
+
+ override fun onBackProgressed(backEvent: BackEvent) {
+ if (shouldBackBeHandled() && shadeViewController.canBeCollapsed()) {
+ shadeViewController.onBackProgressed(backEvent.progress)
+ }
+ }
+ }
+ } else {
+ OnBackInvokedCallback { onBackRequested() }
+ }
+
+ private val onBackInvokedDispatcher: WindowOnBackInvokedDispatcher?
+ get() =
+ notificationShadeWindowController.windowRootView?.viewRootImpl?.onBackInvokedDispatcher
+
private lateinit var shadeViewController: ShadeViewController
private lateinit var qsController: QuickSettingsController
@@ -42,6 +87,19 @@
this.shadeViewController = svController
}
+ override fun start() {
+ scope.launch {
+ windowRootViewVisibilityInteractor.isLockscreenOrShadeVisibleAndInteractive.collect {
+ visible ->
+ if (visible) {
+ registerBackCallback()
+ } else {
+ unregisterBackCallback()
+ }
+ }
+ }
+ }
+
fun shouldBackBeHandled(): Boolean {
return statusBarStateController.state != StatusBarState.KEYGUARD &&
statusBarStateController.state != StatusBarState.SHADE_LOCKED &&
@@ -74,4 +132,24 @@
}
return false
}
+
+ private fun registerBackCallback() {
+ if (isCallbackRegistered) {
+ return
+ }
+ onBackInvokedDispatcher?.let {
+ it.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, callback)
+ isCallbackRegistered = true
+ }
+ }
+
+ private fun unregisterBackCallback() {
+ if (!isCallbackRegistered) {
+ return
+ }
+ onBackInvokedDispatcher?.let {
+ it.unregisterOnBackInvokedCallback(callback)
+ isCallbackRegistered = false
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index b23e085..a368703 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -586,16 +586,18 @@
if (shouldTryToDismissKeyguard()) {
tryDismissingKeyguard();
}
- onFingerDown(requestId,
- data.getPointerId(),
- data.getX(),
- data.getY(),
- data.getMinor(),
- data.getMajor(),
- data.getOrientation(),
- data.getTime(),
- data.getGestureStart(),
- mStatusBarStateController.isDozing());
+ if (!mOnFingerDown) {
+ onFingerDown(requestId,
+ data.getPointerId(),
+ data.getX(),
+ data.getY(),
+ data.getMinor(),
+ data.getMajor(),
+ data.getOrientation(),
+ data.getTime(),
+ data.getGestureStart(),
+ mStatusBarStateController.isDozing());
+ }
// Pilfer if valid overlap, don't allow following events to reach keyguard
shouldPilfer = true;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index e8b8f54..be08932 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -78,17 +78,6 @@
private val _isOverlayTouched: MutableStateFlow<Boolean> = MutableStateFlow(false)
- /**
- * If the API caller or the user's personal preferences require explicit confirmation after
- * successful authentication.
- */
- val isConfirmationRequired: Flow<Boolean> =
- combine(_isOverlayTouched, promptSelectorInteractor.isConfirmationRequired) {
- isOverlayTouched,
- isConfirmationRequired ->
- !isOverlayTouched && isConfirmationRequired
- }
-
/** The kind of credential the user has. */
val credentialKind: Flow<PromptKind> = promptSelectorInteractor.credentialKind
@@ -137,6 +126,15 @@
}
.distinctUntilChanged()
+ /**
+ * If the API caller or the user's personal preferences require explicit confirmation after
+ * successful authentication. Confirmation always required when in explicit flow.
+ */
+ val isConfirmationRequired: Flow<Boolean> =
+ combine(_isOverlayTouched, size) { isOverlayTouched, size ->
+ !isOverlayTouched && size.isNotSmall
+ }
+
/** Title for the prompt. */
val title: Flow<String> =
promptSelectorInteractor.prompt.map { it?.title ?: "" }.distinctUntilChanged()
@@ -170,12 +168,7 @@
.distinctUntilChanged()
/** If the icon can be used as a confirmation button. */
- val isIconConfirmButton: Flow<Boolean> =
- combine(size, promptSelectorInteractor.isConfirmationRequired) {
- size,
- isConfirmationRequired ->
- size.isNotSmall && isConfirmationRequired
- }
+ val isIconConfirmButton: Flow<Boolean> = size.map { it.isNotSmall }.distinctUntilChanged()
/** If the negative button should be shown. */
val isNegativeButtonVisible: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 1bf3a9e..fc32f4c 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.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 @@
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 @@
}
}
+ /** 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 ca15f4e..80a41ce 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 @@
) {
private val _password = MutableStateFlow("")
+
/** The password entered so far. */
val password: StateFlow<String> = _password.asStateFlow()
@@ -48,6 +49,10 @@
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 4425f9f..85eaf0b 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 @@
/** 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 @@
)
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 @@
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 @@
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 @@
/** 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 844cf02..ebf939b 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 @@
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 @@
interactor.clearMessage()
}
+ interactor.onIntentionalUserInput()
+
mutablePinInput.value = pinInput.append(input)
tryAuthenticate(useAutoConfirm = true)
}
@@ -148,8 +155,10 @@
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 4227a7a..b268095 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.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 @@
* 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 @@
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 08e1e9a..f77f989 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 @@
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 0000000..3eaad1f
--- /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 f335d1d..c0ee71c 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 @@
}
@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 @@
}
@Override
- public void onUnlockHintStarted() {
- }
-
- @Override
- public void onCameraHintStarted() {
- }
-
- @Override
- public void onLeftAffordanceHintStarted() {
- }
-
- @Override
public void onScreenTurningOn() {
}
@@ -111,22 +51,6 @@
}
@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 6a021f6..39c01f7 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 @@
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 @@
@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 @@
}
@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 @@
}
@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 @@
@Override
public void onTouchEvent(MotionEvent ev) {
+ logDebug("REAL: onTouchEvent(" + ev.getActionMasked() + ")");
if (!mKeyguardStateController.isShowing()) {
avoidGesture();
return;
@@ -334,6 +263,7 @@
@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 @@
@Override
public void avoidGesture() {
+ logDebug("REAL: avoidGesture");
mAvoidGesture = true;
if (mPendingDownEvent != null) {
mPendingDownEvent.recycle();
@@ -359,6 +290,7 @@
@Override
public void cleanup() {
+ logDebug("REAL: cleanup");
unregisterSensors();
mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
mStatusBarStateController.removeCallback(mStatusBarStateListener);
@@ -368,11 +300,13 @@
@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 @@
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 0000000..e5b404f
--- /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 c7f3b2d..3195d09 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 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 @@
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 0000000..2e861c3
--- /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 c3369da..970b475 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 @@
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 b0e3132..4fb9384 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
+ }
- override fun apply(constraintSet: ConstraintSet) {
+ CommunalWidgetViewBinder.bind(
+ keyguardRootView,
+ communalWidgetViewModel,
+ communalWidgetViewAdapter,
+ keyguardBlueprintInteractor.get(),
+ )
+ }
+
+ 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 d3174f1..adb0bf3 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 @@
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 83bec66..74e1dc0 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.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 @@
private fun updateServices(newServices: List<ControlsServiceInfo>) {
if (activityTaskManagerProxy.supportsMultiWindow(context)) {
- val allowAllApps = featureFlags.isEnabled(Flags.APP_PANELS_ALL_APPS_ALLOWED)
newServices.forEach {
- it.resolvePanelActivity(allowAllApps) }
+ it.resolvePanelActivity() }
}
if (newServices != availableServices) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 6fdb4ca..dcacd09 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -22,6 +22,7 @@
import com.android.systemui.InitController;
import com.android.systemui.SystemUIAppComponentFactoryBase;
import com.android.systemui.dagger.qualifiers.PerUser;
+import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardSliceProvider;
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
@@ -140,6 +141,7 @@
getMediaMuteAwaitConnectionCli();
getNearbyMediaDevicesManager();
getUnfoldLatencyTracker().init();
+ getConnectingDisplayViewModel().init();
getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
getFoldStateLogger().ifPresent(FoldStateLogger::init);
getUnfoldTransitionProgressProvider().ifPresent((progressProvider) ->
@@ -229,6 +231,11 @@
NearbyMediaDevicesManager getNearbyMediaDevicesManager();
/**
+ * Creates a ConnectingDisplayViewModel
+ */
+ ConnectingDisplayViewModel getConnectingDisplayViewModel();
+
+ /**
* Returns {@link CoreStartable}s that should be started with the application.
*/
Map<Class<?>, Provider<CoreStartable>> getStartables();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 1b2a9eb..7ce7ce94 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -23,6 +23,7 @@
import com.android.systemui.SliceBroadcastRelayHandler
import com.android.systemui.accessibility.SystemActions
import com.android.systemui.accessibility.WindowMagnification
+import com.android.systemui.back.domain.interactor.BackActionInteractor
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.BiometricNotificationService
import com.android.systemui.clipboardoverlay.ClipboardListener
@@ -346,4 +347,9 @@
abstract fun bindStatusBarHeadsUpChangeListener(
impl: StatusBarHeadsUpChangeListener
): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(BackActionInteractor::class)
+ abstract fun bindBackActionInteractor(impl: BackActionInteractor): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index b18f7bf..e7bbf97 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -22,6 +22,7 @@
import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_REMOVED
import android.os.Handler
+import android.util.Log
import android.view.Display
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -34,6 +35,7 @@
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.stateIn
@@ -41,6 +43,13 @@
interface DisplayRepository {
/** Provides a nullable set of displays. */
val displays: Flow<Set<Display>>
+
+ /**
+ * Pending display id that can be enabled/disabled.
+ *
+ * When `null`, it means there is no pending display waiting to be enabled.
+ */
+ val pendingDisplayId: Flow<Int?>
}
@SysUISingleton
@@ -85,8 +94,60 @@
initialValue = getDisplays()
)
- fun getDisplays(): Set<Display> =
+ private fun getDisplays(): Set<Display> =
traceSection("DisplayRepository#getDisplays()") {
displayManager.displays?.toSet() ?: emptySet()
}
+
+ override val pendingDisplayId: Flow<Int?> =
+ conflatedCallbackFlow {
+ val callback =
+ object : DisplayConnectionListener {
+ private val pendingIds = mutableSetOf<Int>()
+ override fun onDisplayConnected(id: Int) {
+ pendingIds += id
+ trySend(id)
+ }
+
+ override fun onDisplayDisconnected(id: Int) {
+ if (id in pendingIds) {
+ pendingIds -= id
+ trySend(null)
+ } else {
+ Log.e(
+ TAG,
+ "onDisplayDisconnected received for unknown display. " +
+ "id=$id, knownIds=$pendingIds"
+ )
+ }
+ }
+ }
+ displayManager.registerDisplayListener(
+ callback,
+ backgroundHandler,
+ DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED,
+ )
+ awaitClose { displayManager.unregisterDisplayListener(callback) }
+ }
+ .distinctUntilChanged()
+ .flowOn(backgroundCoroutineDispatcher)
+ .stateIn(
+ applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = null
+ )
+
+ private companion object {
+ const val TAG = "DisplayRepository"
+ }
+}
+
+/** Used to provide default implementations for all methods. */
+private interface DisplayConnectionListener : DisplayListener {
+
+ override fun onDisplayConnected(id: Int) {}
+ override fun onDisplayDisconnected(id: Int) {}
+ override fun onDisplayAdded(id: Int) {}
+ override fun onDisplayRemoved(id: Int) {}
+ override fun onDisplayChanged(id: Int) {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
index 4b957c7..ef6fa26 100644
--- a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
@@ -16,12 +16,17 @@
package com.android.systemui.display.domain.interactor
+import android.hardware.display.DisplayManager
import android.view.Display
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.util.traceSection
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
@@ -37,18 +42,32 @@
*/
val connectedDisplayState: Flow<State>
+ /** Pending display that can be enabled to be used by the system. */
+ val pendingDisplay: Flow<PendingDisplay?>
+
/** Possible connected display state. */
enum class State {
DISCONNECTED,
CONNECTED,
CONNECTED_SECURE,
}
+
+ /** Represents a connected display that has not been enabled yet. */
+ interface PendingDisplay {
+ /** Enables the display, making it available to the system. */
+ fun enable()
+
+ /** Disables the display, making it unavailable to the system. */
+ fun disable()
+ }
}
@SysUISingleton
class ConnectedDisplayInteractorImpl
@Inject
constructor(
+ private val displayManager: DisplayManager,
+ keyguardRepository: KeyguardRepository,
displayRepository: DisplayRepository,
) : ConnectedDisplayInteractor {
@@ -70,4 +89,31 @@
}
}
.distinctUntilChanged()
+
+ // Provides the pending display only if the lockscreen is unlocked
+ override val pendingDisplay: Flow<PendingDisplay?> =
+ displayRepository.pendingDisplayId.combine(keyguardRepository.isKeyguardUnlocked) {
+ pendingDisplayId,
+ keyguardUnlocked ->
+ if (pendingDisplayId != null && keyguardUnlocked) {
+ pendingDisplayId.toPendingDisplay()
+ } else {
+ null
+ }
+ }
+
+ private fun Int.toPendingDisplay() =
+ object : PendingDisplay {
+ val id = this@toPendingDisplay
+ override fun enable() {
+ traceSection("DisplayRepository#enable($id)") {
+ displayManager.enableConnectedDisplay(id)
+ }
+ }
+ override fun disable() {
+ traceSection("DisplayRepository#enable($id)") {
+ displayManager.disableConnectedDisplay(id)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
new file mode 100644
index 0000000..174c6ff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.display.ui.view
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import android.view.Gravity
+import android.view.View
+import android.view.WindowManager
+import android.widget.TextView
+import com.android.systemui.R
+
+/** Dialog used to decide what to do with a connected display. */
+class MirroringConfirmationDialog(
+ context: Context,
+ private val onStartMirroringClickListener: View.OnClickListener,
+ private val onDismissClickListener: View.OnClickListener,
+) : Dialog(context, R.style.Theme_SystemUI_Dialog) {
+
+ private lateinit var mirrorButton: TextView
+ private lateinit var dismissButton: TextView
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ window?.apply {
+ setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
+ addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+ setGravity(Gravity.BOTTOM)
+ }
+ setContentView(R.layout.connected_display_dialog)
+ setCanceledOnTouchOutside(true)
+ mirrorButton =
+ requireViewById<TextView>(R.id.enable_display).apply {
+ setOnClickListener(onStartMirroringClickListener)
+ }
+ dismissButton =
+ requireViewById<TextView>(R.id.cancel).apply {
+ setOnClickListener(onDismissClickListener)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
new file mode 100644
index 0000000..ece33b7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.display.ui.viewmodel
+
+import android.app.Dialog
+import android.content.Context
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
+import com.android.systemui.display.ui.view.MirroringConfirmationDialog
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+/**
+ * Shows/hides a dialog to allow the user to decide whether to use the external display for
+ * mirroring.
+ */
+@SysUISingleton
+class ConnectingDisplayViewModel
+@Inject
+constructor(
+ private val context: Context,
+ private val connectedDisplayInteractor: ConnectedDisplayInteractor,
+ @Application private val scope: CoroutineScope,
+) {
+
+ private var dialog: Dialog? = null
+
+ /** Starts listening for pending displays. */
+ fun init() {
+ connectedDisplayInteractor.pendingDisplay
+ .onEach { pendingDisplay ->
+ if (pendingDisplay == null) {
+ hideDialog()
+ } else {
+ showDialog(pendingDisplay)
+ }
+ }
+ .launchIn(scope)
+ }
+
+ private fun showDialog(pendingDisplay: PendingDisplay) {
+ hideDialog()
+ dialog =
+ MirroringConfirmationDialog(
+ context,
+ onStartMirroringClickListener = {
+ pendingDisplay.enable()
+ hideDialog()
+ },
+ onDismissClickListener = { hideDialog() }
+ )
+ .apply { show() }
+ }
+
+ private fun hideDialog() {
+ dialog?.hide()
+ dialog = null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index e9a539f..907e106 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 @@
@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 @@
@JvmField
val LOCK_SCREEN_LONG_PRESS_ENABLED = releasedFlag("lock_screen_long_press_enabled")
+ /** Inflate and bind views upon emitting a blueprint value . */
+ // TODO(b/297365780): Tracking Bug
+ @JvmField val LAZY_INFLATE_KEYGUARD = unreleasedFlag("lazy_inflate_keyguard")
+
/** Enables UI updates for AI wallpapers in the wallpaper picker. */
// TODO(b/267722622): Tracking Bug
@JvmField val WALLPAPER_PICKER_UI_FOR_AIWP = releasedFlag("wallpaper_picker_ui_for_aiwp")
@@ -370,6 +378,9 @@
// 600- status bar
+ // TODO(b/291315866): Tracking Bug
+ @JvmField val SIGNAL_CALLBACK_DEPRECATION = unreleasedFlag("signal_callback_deprecation")
+
// TODO(b/265892345): Tracking Bug
val PLUG_IN_STATUS_BAR_CHIP = releasedFlag("plug_in_status_bar_chip")
@@ -391,7 +402,7 @@
// TODO(b/290676905): Tracking Bug
val NEW_SHADE_CARRIER_GROUP_MOBILE_ICONS =
- unreleasedFlag("new_shade_carrier_group_mobile_icons")
+ unreleasedFlag("new_shade_carrier_group_mobile_icons", teamfood = true)
// 700 - dialer/calls
// TODO(b/254512734): Tracking Bug
@@ -631,9 +642,6 @@
// 1900
@JvmField val NOTE_TASKS = releasedFlag("keycode_flag")
- // 2000 - device controls
- @JvmField val APP_PANELS_ALL_APPS_ALLOWED = releasedFlag("app_panels_all_apps_allowed")
-
// 2200 - biometrics (udfps, sfps, BiometricPrompt, etc.)
// TODO(b/259264861): Tracking Bug
@JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag("udfps_new_touch_detection")
@@ -654,6 +662,10 @@
val WARN_ON_BLOCKING_BINDER_TRANSACTIONS =
unreleasedFlag("warn_on_blocking_binder_transactions")
+ @JvmField
+ val COROUTINE_TRACING =
+ unreleasedFlag("coroutine_tracing")
+
// TODO(b/283071711): Tracking bug
@JvmField
val TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK =
diff --git a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
index c41b5e4..2856011 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 @@
}
}
+ /**
+ * 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 @@
* 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 0000000..629b361
--- /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 0000000..1377b29
--- /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 0000000..8b17e86
--- /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 0000000..413e277
--- /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 9d2771e..f6add9c 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 @@
}
@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 1cd8795..6bc9abf 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.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 @@
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 @@
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 @@
)
}
+ /** 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 @@
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 @@
* 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 e983a05..2b4dc81 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 @@
* 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 @@
// 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 7234757..f91ae74 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 @@
blueprintIdMap.forEach { entry -> pw.println("${entry.key}") }
}
}
-
-/** Determines the constraints for the ConstraintSet in the lockscreen root view. */
-interface KeyguardBlueprint {
- val id: String
- val shouldRemoveUnconstrainedViews: Boolean
- get() = true
-
- fun apply(constraintLayout: ConstraintSet)
- fun removeUnConstrainedViews(constraintLayout: ConstraintLayout, constraintSet: ConstraintSet) {
- constraintLayout.children
- .map { it.id }
- .filterNot { constraintSet.knownIds.contains(it) }
- .forEach { constraintSet.setVisibility(it, View.GONE) }
- }
-}
-
-/**
- * Lower level modules that determine constraints for a particular section in the lockscreen root
- * view.
- */
-interface KeyguardSection {
- fun apply(constraintSet: ConstraintSet)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 42cd3a5..d399e4c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -158,7 +158,7 @@
val lastDozeTapToWakePosition: StateFlow<Point?>
/** Observable for the [StatusBarState] */
- val statusBarState: Flow<StatusBarState>
+ val statusBarState: StateFlow<StatusBarState>
/** Observable for device wake/sleep state */
val wakefulness: StateFlow<WakefulnessModel>
@@ -520,23 +520,29 @@
return keyguardBypassController.bypassEnabled
}
- override val statusBarState: Flow<StatusBarState> = conflatedCallbackFlow {
- val callback =
- object : StatusBarStateController.StateListener {
- override fun onStateChanged(state: Int) {
- trySendWithFailureLogging(statusBarStateIntToObject(state), TAG, "state")
- }
+ // TODO(b/297345631): Expose this at the interactor level instead so that it can be powered by
+ // [SceneInteractor] when scenes are ready.
+ override val statusBarState: StateFlow<StatusBarState> =
+ conflatedCallbackFlow {
+ val callback =
+ object : StatusBarStateController.StateListener {
+ override fun onStateChanged(state: Int) {
+ trySendWithFailureLogging(
+ statusBarStateIntToObject(state),
+ TAG,
+ "state"
+ )
+ }
+ }
+
+ statusBarStateController.addCallback(callback)
+ awaitClose { statusBarStateController.removeCallback(callback) }
}
-
- statusBarStateController.addCallback(callback)
- trySendWithFailureLogging(
- statusBarStateIntToObject(statusBarStateController.getState()),
- TAG,
- "initial state"
- )
-
- awaitClose { statusBarStateController.removeCallback(callback) }
- }
+ .stateIn(
+ scope,
+ SharingStarted.Eagerly,
+ statusBarStateIntToObject(statusBarStateController.state)
+ )
override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow {
fun dispatchUpdate() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index ff0db34..714add4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -50,14 +50,28 @@
listenForOccludedToAodOrDozing()
listenForOccludedToGone()
listenForOccludedToAlternateBouncer()
+ listenForOccludedToPrimaryBouncer()
+ }
+
+ private fun listenForOccludedToPrimaryBouncer() {
+ scope.launch {
+ keyguardInteractor.primaryBouncerShowing
+ .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .collect { (isBouncerShowing, lastStartedTransitionStep) ->
+ if (
+ isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.OCCLUDED
+ ) {
+ startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
+ }
+ }
+ }
}
private fun listenForOccludedToDreaming() {
scope.launch {
keyguardInteractor.isAbleToDream
.sample(transitionInteractor.finishedKeyguardState, ::Pair)
- .collect { pair ->
- val (isAbleToDream, keyguardState) = pair
+ .collect { (isAbleToDream, keyguardState) ->
if (isAbleToDream && keyguardState == KeyguardState.OCCLUDED) {
startTransitionTo(KeyguardState.DREAMING)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index e13f675..0c05a0e 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 @@
/** 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 @@
/** 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 0000000..659c5f3
--- /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 0000000..19f50de
--- /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 fb685da..c8a04fd 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 @@
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 e40c279..c340e5d 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.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 @@
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 41c1c96..2cfc478 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.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.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 @@
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 @@
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 @@
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
setupKeyguardRootView(rootView)
- setupShortcuts(rootView)
} else {
setUpBottomArea(rootView)
}
@@ -348,14 +350,14 @@
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 @@
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 @@
// 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 518df07..5a15fc2 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 @@
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 07f316b..fda4c3d 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 54c2796..5ef625e 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 @@
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 156b9f3..587c6b7 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.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 0000000..db0cf5a
--- /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 abf25a2..f8455c5 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.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 @@
}
}
}
+
+ 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 dee7ed5..f04bfc6 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 @@
)
}
}
+
+ 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 461faec..3d62f3f 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.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 @@
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 @@
if (isUdfpsSupported) {
authController.udfpsLocation?.let { udfpsLocation ->
- centerLockIcon(
- udfpsLocation,
- authController.udfpsRadius,
- scaledPadding,
- constraintSet
- )
+ centerLockIcon(udfpsLocation, authController.udfpsRadius, constraintSet)
}
} else {
centerLockIcon(
@@ -71,19 +87,13 @@
(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 0000000..a203e41d
--- /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 ad1e4f8..660cc96 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 @@
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 db4653d..965910a 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.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 @@
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 f1f5973..321d7a7 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 @@
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 668b17f..bd629d5 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 com.android.systemui.R
-import com.android.systemui.keyguard.data.repository.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
+import com.android.systemui.R
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import javax.inject.Inject
-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 5e9e553..e2bfc36 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 93c4902..05c9323 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 @@
@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 @@
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 8f884d24..053c9b5 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 @@
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 @@
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(): Boolean = true
+ override fun shouldShowContentPreviewWhenEmpty() = shouldShowContentPreview()
+
+ 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 398dcf2..38a6a8f 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.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.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 @@
@Main executor: DelayableExecutor,
private val mediaManager: MediaDataManager,
configurationController: ConfigurationController,
- falsingCollector: FalsingCollector,
falsingManager: FalsingManager,
dumpManager: DumpManager,
private val logger: MediaUiEventLogger,
@@ -122,6 +125,7 @@
/** 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 @@
@VisibleForTesting var mediaCarousel: MediaScrollView
val mediaCarouselScrollHandler: MediaCarouselScrollHandler
val mediaFrame: ViewGroup
+
@VisibleForTesting
lateinit var settingsButton: View
private set
@@ -280,7 +285,6 @@
this::updatePageIndicatorLocation,
this::updateSeekbarListening,
this::closeGuts,
- falsingCollector,
falsingManager,
this::logSmartspaceImpression,
logger
@@ -327,23 +331,18 @@
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 @@
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 @@
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 @@
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 @@
data.headphoneConnectionTimeMillis)
.toInt()
)
- /* ktlint-disable max-line-length */
}
if (
mediaCarouselScrollHandler.visibleToUser &&
@@ -560,7 +546,10 @@
mediaCarouselScrollHandler.onSettingsButtonUpdated(settings)
settingsButton.setOnClickListener {
logger.logCarouselSettings()
- activityStarter.startActivity(settingsIntent, true /* dismissShade */)
+ activityStarter.startActivity(
+ settingsIntent,
+ /* dismissShade= */ true,
+ )
}
}
@@ -1108,7 +1097,6 @@
}
}
- @JvmOverloads
/**
* Log Smartspace events
*
@@ -1127,6 +1115,7 @@
* 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 @@
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 @@
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 @@
instanceId = InstanceId.fakeInstanceId(-1),
appUid = -1
)
+
// Whether should prioritize Smartspace card.
internal var shouldPrioritizeSs: Boolean = false
private set
@@ -1291,6 +1284,7 @@
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 ce50a11..bbb61b4 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.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 @@
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 @@
/** 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 @@
/** 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 @@
) = onScroll(down!!, lastMotion, distanceX)
override fun onDown(e: MotionEvent): Boolean {
- if (falsingProtectionNeeded) {
- falsingCollector.onNotificationStartDismissing()
- }
return false
}
}
@@ -263,9 +261,6 @@
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 @@
}
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 64de9bd..5d732fb 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 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 @@
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 @@
.inflate(R.layout.media_projection_recent_tasks, parent, /* attachToRoot= */ false)
as ViewGroup
- val container = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_container)
+ val container =
+ recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_container)
container.setTaskHeightSize()
val progress = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_loader)
diff --git a/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt b/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
index 69cb611..b2a8719 100644
--- a/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
@@ -39,6 +39,9 @@
/** Wakes up the device. */
fun wakeUp(why: String, @PowerManager.WakeReason wakeReason: Int)
+
+ /** Notifies the power repository that a user touch happened. */
+ fun userTouch()
}
@SysUISingleton
@@ -83,6 +86,14 @@
)
}
+ override fun userTouch() {
+ manager.userActivity(
+ systemClock.uptimeMillis(),
+ PowerManager.USER_ACTIVITY_EVENT_TOUCH,
+ /* flags= */ 0,
+ )
+ }
+
companion object {
private const val TAG = "PowerRepository"
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index 809edc0..16885ed 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 @@
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 @@
constructor(
private val repository: PowerRepository,
private val keyguardRepository: KeyguardRepository,
- private val falsingCollector: FalsingCollector,
+ @FalsingCollectorActual private val falsingCollector: FalsingCollector,
private val screenOffAnimationController: ScreenOffAnimationController,
private val statusBarStateController: StatusBarStateController,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 54376fe..e9a2428 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -18,6 +18,8 @@
import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
+import static com.android.systemui.flags.Flags.SIGNAL_CALLBACK_DEPRECATION;
+
import android.annotation.NonNull;
import android.app.Dialog;
import android.content.Intent;
@@ -42,6 +44,7 @@
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
@@ -54,6 +57,8 @@
import com.android.systemui.statusbar.connectivity.SignalCallback;
import com.android.systemui.statusbar.connectivity.WifiIndicators;
import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor;
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
import com.android.systemui.statusbar.policy.HotspotController;
@@ -61,6 +66,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
import javax.inject.Inject;
@@ -79,6 +85,9 @@
private final NetworkController mNetworkController;
private final DialogLaunchAnimator mDialogLaunchAnimator;
private final Callback mCallback = new Callback();
+ private final WifiInteractor mWifiInteractor;
+ private final TileJavaAdapter mJavaAdapter;
+ private final FeatureFlags mFeatureFlags;
private boolean mWifiConnected;
private boolean mHotspotConnected;
@@ -97,7 +106,10 @@
KeyguardStateController keyguardStateController,
NetworkController networkController,
HotspotController hotspotController,
- DialogLaunchAnimator dialogLaunchAnimator
+ DialogLaunchAnimator dialogLaunchAnimator,
+ WifiInteractor wifiInteractor,
+ TileJavaAdapter javaAdapter,
+ FeatureFlags featureFlags
) {
super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
@@ -105,9 +117,16 @@
mKeyguard = keyguardStateController;
mNetworkController = networkController;
mDialogLaunchAnimator = dialogLaunchAnimator;
+ mWifiInteractor = wifiInteractor;
+ mJavaAdapter = javaAdapter;
+ mFeatureFlags = featureFlags;
mController.observe(this, mCallback);
mKeyguard.observe(this, mCallback);
- mNetworkController.observe(this, mSignalCallback);
+ if (!mFeatureFlags.isEnabled(SIGNAL_CALLBACK_DEPRECATION)) {
+ mNetworkController.observe(this, mSignalCallback);
+ } else {
+ mJavaAdapter.bind(this, mWifiInteractor.getWifiNetwork(), mNetworkModelConsumer);
+ }
hotspotController.observe(this, mHotspotCallback);
}
@@ -293,19 +312,37 @@
return mWifiConnected || mHotspotConnected;
}
+ private void setWifiConnected(boolean connected) {
+ if (connected != mWifiConnected) {
+ mWifiConnected = connected;
+ // Hotspot is not connected, so changes here should update
+ if (!mHotspotConnected) {
+ refreshState();
+ }
+ }
+ }
+
+ private void setHotspotConnected(boolean connected) {
+ if (connected != mHotspotConnected) {
+ mHotspotConnected = connected;
+ // Wifi is not connected, so changes here should update
+ if (!mWifiConnected) {
+ refreshState();
+ }
+ }
+ }
+
+ private final Consumer<WifiNetworkModel> mNetworkModelConsumer = (model) -> {
+ setWifiConnected(model instanceof WifiNetworkModel.Active);
+ };
+
private final SignalCallback mSignalCallback = new SignalCallback() {
@Override
public void setWifiIndicators(@NonNull WifiIndicators indicators) {
// statusIcon.visible has the connected status information
boolean enabledAndConnected = indicators.enabled
- && (indicators.qsIcon == null ? false : indicators.qsIcon.visible);
- if (enabledAndConnected != mWifiConnected) {
- mWifiConnected = enabledAndConnected;
- // Hotspot is not connected, so changes here should update
- if (!mHotspotConnected) {
- refreshState();
- }
- }
+ && (indicators.qsIcon != null && indicators.qsIcon.visible);
+ setWifiConnected(enabledAndConnected);
}
};
@@ -314,13 +351,7 @@
@Override
public void onHotspotChanged(boolean enabled, int numDevices) {
boolean enabledAndConnected = enabled && numDevices > 0;
- if (enabledAndConnected != mHotspotConnected) {
- mHotspotConnected = enabledAndConnected;
- // Wifi is not connected, so changes here should update
- if (!mWifiConnected) {
- refreshState();
- }
- }
+ setHotspotConnected(enabledAndConnected);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
new file mode 100644
index 0000000..3b2f8b7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles
+
+import android.content.Context
+import android.content.Intent
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings
+import android.view.View
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.qs.QSIconView
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.AlphaControlledSignalTileView
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tiles.dialog.InternetDialogFactory
+import com.android.systemui.statusbar.connectivity.AccessPointController
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.InternetTileBinder
+import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileModel
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.InternetTileViewModel
+import javax.inject.Inject
+
+class InternetTileNewImpl
+@Inject
+constructor(
+ host: QSHost,
+ uiEventLogger: QsEventLogger,
+ @Background backgroundLooper: Looper,
+ @Main private val mainHandler: Handler,
+ falsingManager: FalsingManager,
+ metricsLogger: MetricsLogger,
+ statusBarStateController: StatusBarStateController,
+ activityStarter: ActivityStarter,
+ qsLogger: QSLogger,
+ viewModel: InternetTileViewModel,
+ private val internetDialogFactory: InternetDialogFactory,
+ private val accessPointController: AccessPointController,
+) :
+ QSTileImpl<QSTile.SignalState>(
+ host,
+ uiEventLogger,
+ backgroundLooper,
+ mainHandler,
+ falsingManager,
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger
+ ) {
+ private var model: InternetTileModel = viewModel.tileModel.value
+
+ init {
+ InternetTileBinder.bind(lifecycle, viewModel.tileModel) { newModel ->
+ model = newModel
+ refreshState()
+ }
+ }
+
+ override fun createTileView(context: Context): QSIconView =
+ AlphaControlledSignalTileView(context)
+
+ override fun getTileLabel(): CharSequence =
+ mContext.getString(R.string.quick_settings_internet_label)
+
+ override fun newTileState(): QSTile.SignalState {
+ return QSTile.SignalState().also { it.forceExpandIcon = true }
+ }
+
+ override fun handleClick(view: View?) {
+ mainHandler.post {
+ internetDialogFactory.create(
+ aboveStatusBar = true,
+ accessPointController.canConfigMobileData(),
+ accessPointController.canConfigWifi(),
+ view,
+ )
+ }
+ }
+
+ override fun handleUpdateState(state: QSTile.SignalState, arg: Any?) {
+ state.label = mContext.resources.getString(R.string.quick_settings_internet_label)
+
+ model.applyTo(state, mContext)
+ }
+
+ override fun getLongClickIntent(): Intent = WIFI_SETTINGS
+
+ companion object {
+ private val WIFI_SETTINGS = Intent(Settings.ACTION_WIFI_SETTINGS)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/TileJavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/TileJavaAdapter.kt
new file mode 100644
index 0000000..a2430ad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/TileJavaAdapter.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.android.systemui.qs.tiles
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.dagger.SysUISingleton
+import java.util.function.Consumer
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/**
+ * Utility for binding tiles to kotlin flows. Similar to [JavaAdapter] and usable for QS tiles. We
+ * use [Lifecycle.State.RESUMED] here to match the implementation of [CallbackController.observe]
+ */
+@SysUISingleton
+class TileJavaAdapter @Inject constructor() {
+ fun <T> bind(
+ lifecycleOwner: LifecycleOwner,
+ flow: Flow<T>,
+ consumer: Consumer<T>,
+ ) {
+ lifecycleOwner.lifecycleScope.launch {
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ flow.collect { consumer.accept(it) }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepository.kt
new file mode 100644
index 0000000..d833e56
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepository.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.data.repository
+
+import android.os.RemoteException
+import com.android.internal.statusbar.IStatusBarService
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.UiBackground
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Source of truth for the visibility of various parts of the window root view. */
+@SysUISingleton
+class WindowRootViewVisibilityRepository
+@Inject
+constructor(
+ private val statusBarService: IStatusBarService,
+ @UiBackground private val uiBgExecutor: Executor,
+) {
+ private val _isLockscreenOrShadeVisible = MutableStateFlow(false)
+ val isLockscreenOrShadeVisible: StateFlow<Boolean> = _isLockscreenOrShadeVisible.asStateFlow()
+
+ fun setIsLockscreenOrShadeVisible(visible: Boolean) {
+ _isLockscreenOrShadeVisible.value = visible
+ }
+
+ /**
+ * Called when the lockscreen or shade has been shown and can be interacted with so that SysUI
+ * can notify external services.
+ */
+ fun onLockscreenOrShadeInteractive(
+ shouldClearNotificationEffects: Boolean,
+ notificationCount: Int,
+ ) {
+ executeServiceCallOnUiBg {
+ statusBarService.onPanelRevealed(shouldClearNotificationEffects, notificationCount)
+ }
+ }
+
+ /**
+ * Called when the lockscreen or shade no longer can be interactecd with so that SysUI can
+ * notify external services.
+ */
+ fun onLockscreenOrShadeNotInteractive() {
+ executeServiceCallOnUiBg { statusBarService.onPanelHidden() }
+ }
+
+ private fun executeServiceCallOnUiBg(runnable: () -> Unit) {
+ uiBgExecutor.execute {
+ try {
+ runnable.invoke()
+ } catch (ex: RemoteException) {
+ // Won't fail unless the world has ended
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index a7434c6..45ee7be 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -18,6 +18,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.power.data.repository.PowerRepository
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.shared.logger.SceneLogger
import com.android.systemui.scene.shared.model.ObservableTransitionState
@@ -44,6 +45,7 @@
constructor(
@Application applicationScope: CoroutineScope,
private val repository: SceneContainerRepository,
+ private val powerRepository: PowerRepository,
private val logger: SceneLogger,
) {
@@ -153,6 +155,11 @@
repository.setTransitionState(transitionState)
}
+ /** Handles a user input event. */
+ fun onUserInput() {
+ powerRepository.userTouch()
+ }
+
/**
* Notifies that the UI has transitioned sufficiently to the given scene.
*
@@ -161,7 +168,7 @@
* Once a transition between one scene and another passes a threshold, the UI invokes this
* method to report it, updating the value in [desiredScene] to match what the UI shows.
*/
- internal fun onSceneChanged(scene: SceneModel, loggingReason: String) {
+ fun onSceneChanged(scene: SceneModel, loggingReason: String) {
updateDesiredScene(scene, loggingReason, logger::logSceneChangeCommitted)
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
new file mode 100644
index 0000000..16ffcc2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.scene.domain.interactor
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.init.NotificationsController
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Business logic about the visibility of various parts of the window root view. */
+@SysUISingleton
+class WindowRootViewVisibilityInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val windowRootViewVisibilityRepository: WindowRootViewVisibilityRepository,
+ private val keyguardRepository: KeyguardRepository,
+ private val headsUpManager: HeadsUpManager,
+) : CoreStartable {
+
+ private var notificationPresenter: NotificationPresenter? = null
+ private var notificationsController: NotificationsController? = null
+
+ private val isNotifPresenterFullyCollapsed: Boolean
+ get() = notificationPresenter?.isPresenterFullyCollapsed ?: true
+
+ /**
+ * True if lockscreen (including AOD) or the shade is visible and false otherwise. Notably,
+ * false if the bouncer is visible.
+ *
+ * TODO(b/297080059): Use [SceneInteractor] as the source of truth if the scene flag is on.
+ */
+ val isLockscreenOrShadeVisible: StateFlow<Boolean> =
+ windowRootViewVisibilityRepository.isLockscreenOrShadeVisible
+
+ /**
+ * True if lockscreen (including AOD) or the shade is visible **and** the user is currently
+ * interacting with the device, false otherwise. Notably, false if the bouncer is visible and
+ * false if the device is asleep.
+ */
+ val isLockscreenOrShadeVisibleAndInteractive: StateFlow<Boolean> =
+ combine(
+ isLockscreenOrShadeVisible,
+ keyguardRepository.wakefulness,
+ ) { isKeyguardAodOrShadeVisible, wakefulness ->
+ isKeyguardAodOrShadeVisible && wakefulness.isDeviceInteractive()
+ }
+ .stateIn(scope, SharingStarted.Eagerly, initialValue = false)
+
+ /**
+ * Sets classes that aren't easily injectable on this class.
+ *
+ * TODO(b/277762009): Inject these directly instead.
+ */
+ fun setUp(
+ presenter: NotificationPresenter?,
+ notificationsController: NotificationsController?,
+ ) {
+ this.notificationPresenter = presenter
+ this.notificationsController = notificationsController
+ }
+
+ override fun start() {
+ scope.launch {
+ isLockscreenOrShadeVisibleAndInteractive.collect { interactive ->
+ if (interactive) {
+ windowRootViewVisibilityRepository.onLockscreenOrShadeInteractive(
+ getShouldClearNotificationEffects(keyguardRepository.statusBarState.value),
+ getNotificationLoad(),
+ )
+ } else {
+ windowRootViewVisibilityRepository.onLockscreenOrShadeNotInteractive()
+ }
+ }
+ }
+ }
+
+ fun setIsLockscreenOrShadeVisible(visible: Boolean) {
+ windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(visible)
+ }
+
+ private fun getShouldClearNotificationEffects(statusBarState: StatusBarState): Boolean {
+ return !isNotifPresenterFullyCollapsed &&
+ (statusBarState == StatusBarState.SHADE ||
+ statusBarState == StatusBarState.SHADE_LOCKED)
+ }
+
+ private fun getNotificationLoad(): Int {
+ return if (headsUpManager.hasPinnedHeadsUp() && isNotifPresenterFullyCollapsed) {
+ 1
+ } else {
+ notificationsController?.getActiveNotificationsCount() ?: 0
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 1747099..7f77acc 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_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 @@
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 @@
hydrateVisibility()
automaticallySwitchScenes()
hydrateSystemUiState()
+ collectFalsingSignals()
} else {
sceneLogger.logFrameworkEnabled(isEnabled = false)
}
@@ -225,6 +236,66 @@
}
}
+ /** Collects and reports signals into the falsing system. */
+ private fun collectFalsingSignals() {
+ applicationScope.launch {
+ authenticationInteractor.isLockscreenDismissed.collect { isLockscreenDismissed ->
+ if (isLockscreenDismissed) {
+ falsingCollector.onSuccessfulUnlock()
+ }
+ }
+ }
+
+ applicationScope.launch {
+ keyguardInteractor.isDozing.distinctUntilChanged().collect { isDozing ->
+ falsingCollector.setShowingAod(isDozing)
+ }
+ }
+
+ applicationScope.launch {
+ keyguardInteractor.isAodAvailable
+ .flatMapLatest { isAodAvailable ->
+ if (!isAodAvailable) {
+ keyguardInteractor.wakefulnessModel
+ } else {
+ emptyFlow()
+ }
+ }
+ .map { wakefulnessModel ->
+ val wakeChange: Boolean? =
+ when (wakefulnessModel.state) {
+ WakefulnessState.STARTING_TO_WAKE -> true
+ WakefulnessState.ASLEEP -> false
+ else -> null
+ }
+ (wakeChange to wakefulnessModel.lastWakeReason).takeIf { wakeChange != null }
+ }
+ .filterNotNull()
+ .distinctUntilChangedBy { it.first }
+ .collect { (wakeChange, wakeReason) ->
+ when {
+ wakeChange == true && wakeReason.isTouch ->
+ falsingCollector.onScreenOnFromTouch()
+ wakeChange == true -> falsingCollector.onScreenTurningOn()
+ wakeChange == false -> falsingCollector.onScreenOff()
+ }
+ }
+ }
+
+ applicationScope.launch {
+ sceneInteractor.desiredScene
+ .map { it.key == SceneKey.Bouncer }
+ .distinctUntilChanged()
+ .collect { switchedToBouncerScene ->
+ if (switchedToBouncerScene) {
+ falsingCollector.onBouncerShown()
+ } else {
+ falsingCollector.onBouncerHidden()
+ }
+ }
+ }
+ }
+
private fun switchToScene(targetSceneKey: SceneKey, loggingReason: String) {
sceneInteractor.changeScene(
scene = SceneModel(targetSceneKey),
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt
index 8da1803..fcfdceb 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt
@@ -17,6 +17,7 @@
package com.android.systemui.scene.domain.startable
import com.android.systemui.CoreStartable
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
@@ -29,4 +30,11 @@
@IntoMap
@ClassKey(SceneContainerStartable::class)
fun bind(impl: SceneContainerStartable): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(WindowRootViewVisibilityInteractor::class)
+ fun bindWindowRootViewVisibilityInteractor(
+ impl: WindowRootViewVisibilityInteractor
+ ): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index f9324a9..3e76607 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 @@
// SysUI altogether.
private fun createVisibilityToggleView(otherView: View): View {
val toggleView = View(otherView.context)
+ otherView.isVisible = false
toggleView.layoutParams = FrameLayout.LayoutParams(200, 200, Gravity.CENTER_HORIZONTAL)
toggleView.setOnClickListener {
val now = Instant.now()
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
index c9a73e6..ef688a8 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
package com.android.systemui.scene.ui.view
import android.annotation.SuppressLint
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index 5c16fb5..2431660 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 @@
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 @@
* The scenes will be sorted in z-order such that the last one is the one that should be
* rendered on top of all previous ones.
*/
- val allSceneKeys: List<SceneKey> = interactor.allSceneKeys()
+ val allSceneKeys: List<SceneKey> = sceneInteractor.allSceneKeys()
/** The scene that should be rendered. */
- val currentScene: StateFlow<SceneModel> = interactor.desiredScene
+ val currentScene: StateFlow<SceneModel> = sceneInteractor.desiredScene
/** Whether the container is visible. */
- val isVisible: StateFlow<Boolean> = interactor.isVisible
+ val isVisible: StateFlow<Boolean> = sceneInteractor.isVisible
/** Notifies that the UI has transitioned sufficiently to the given scene. */
fun onSceneChanged(scene: SceneModel) {
- interactor.onSceneChanged(
+ sceneInteractor.onSceneChanged(
scene = scene,
loggingReason = SCENE_TRANSITION_LOGGING_REASON,
)
@@ -60,7 +63,27 @@
* Note that you must call is with `null` when the UI is done or risk a memory leak.
*/
fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
- interactor.setTransitionState(transitionState)
+ sceneInteractor.setTransitionState(transitionState)
+ }
+
+ /**
+ * Notifies that a [MotionEvent] is first seen at the top of the scene container UI.
+ *
+ * Call this before the [MotionEvent] starts to propagate through the UI hierarchy.
+ */
+ fun onMotionEvent(event: MotionEvent) {
+ sceneInteractor.onUserInput()
+ falsingInteractor.onTouchEvent(event)
+ }
+
+ /**
+ * Notifies that a [MotionEvent] that was previously sent to [onMotionEvent] has passed through
+ * the scene container UI.
+ *
+ * Call this after the [MotionEvent] propagates completely through the UI hierarchy.
+ */
+ fun onMotionEventComplete() {
+ falsingInteractor.onMotionEventComplete()
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index a41d6e8..8db7abf 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1226,37 +1226,7 @@
private void updateViewControllers(
FrameLayout userAvatarView,
KeyguardUserSwitcherView keyguardUserSwitcherView) {
- // Re-associate the KeyguardStatusViewController
- if (mKeyguardStatusViewController != null) {
- mKeyguardStatusViewController.onDestroy();
- }
-
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
- // 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);
- mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
- mKeyguardStatusViewController.init();
- }
- mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
- mKeyguardStatusViewController.getView().addOnLayoutChangeListener(
- (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
- int oldHeight = oldBottom - oldTop;
- if (v.getHeight() != oldHeight) {
- mNotificationStackScrollLayoutController.animateNextTopPaddingChange();
- }
- });
-
- updateClockAppearance();
-
+ 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
@@ -1286,6 +1256,40 @@
}
}
+ /** Updates the StatusBarViewController and updates any that depend on it. */
+ public void updateStatusBarViewController() {
+ // Re-associate the KeyguardStatusViewController
+ if (mKeyguardStatusViewController != null) {
+ mKeyguardStatusViewController.onDestroy();
+ }
+
+ if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ // 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
+ mKeyguardStatusViewController =
+ mKeyguardViewConfigurator.getKeyguardStatusViewController();
+ } else {
+ KeyguardStatusView keyguardStatusView = mView.getRootView().findViewById(
+ R.id.keyguard_status_view);
+ KeyguardStatusViewComponent statusViewComponent =
+ mKeyguardStatusViewComponentFactory.build(keyguardStatusView);
+ mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
+ mKeyguardStatusViewController.init();
+ }
+
+ mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
+ mKeyguardStatusViewController.getView().addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ int oldHeight = oldBottom - oldTop;
+ if (v.getHeight() != oldHeight) {
+ mNotificationStackScrollLayoutController.animateNextTopPaddingChange();
+ }
+ });
+
+ updateClockAppearance();
+ }
+
@Override
public void updateResources() {
final boolean newSplitShadeEnabled =
@@ -1403,7 +1407,8 @@
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 @@
}
private void onTrackingStarted() {
- mFalsingCollector.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen());
endClosing();
mTracking = true;
mTrackingStartedListener.onTrackingStarted();
@@ -2834,7 +2838,6 @@
}
private void onTrackingStopped(boolean expand) {
- mFalsingCollector.onTrackingStopped();
mTracking = false;
maybeStopTrackingExpansionFromStatusBar(expand);
@@ -2888,7 +2891,6 @@
@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 ad9df72..4a76dd0 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.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.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.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 @@
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 @@
ConfigurationController configurationController,
KeyguardViewMediator keyguardViewMediator,
KeyguardBypassController keyguardBypassController,
+ @Background Executor backgroundExecutor,
SysuiColorExtractor colorExtractor,
DumpManager dumpManager,
KeyguardStateController keyguardStateController,
@@ -159,6 +162,7 @@
mLpChanged = new LayoutParams();
mKeyguardViewMediator = keyguardViewMediator;
mKeyguardBypassController = keyguardBypassController;
+ mBackgroundExecutor = backgroundExecutor;
mColorExtractor = colorExtractor;
mScreenOffAnimationController = screenOffAnimationController;
dumpManager.registerDumpable(this);
@@ -520,13 +524,14 @@
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 3a916cf..0f85c76 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -35,6 +35,7 @@
import com.android.keyguard.KeyguardMessageAreaController;
import com.android.keyguard.LockIconViewController;
import com.android.keyguard.dagger.KeyguardBouncerComponent;
+import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.back.domain.interactor.BackActionInteractor;
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
@@ -43,6 +44,7 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dock.DockManager;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor;
@@ -65,6 +67,8 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.DozeScrimController;
+import com.android.systemui.statusbar.phone.DozeServiceHost;
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
@@ -81,7 +85,7 @@
* Controller for {@link NotificationShadeWindowView}.
*/
@SysUISingleton
-public class NotificationShadeWindowViewController {
+public class NotificationShadeWindowViewController implements Dumpable {
private static final String TAG = "NotifShadeWindowVC";
private final FalsingCollector mFalsingCollector;
private final SysuiStatusBarStateController mStatusBarStateController;
@@ -118,6 +122,8 @@
private NotificationStackScrollLayout mStackScrollLayout;
private PhoneStatusBarViewController mStatusBarViewController;
private final CentralSurfaces mService;
+ private final DozeServiceHost mDozeServiceHost;
+ private final DozeScrimController mDozeScrimController;
private final BackActionInteractor mBackActionInteractor;
private final PowerInteractor mPowerInteractor;
private final NotificationShadeWindowController mNotificationShadeWindowController;
@@ -152,6 +158,8 @@
StatusBarWindowStateController statusBarWindowStateController,
LockIconViewController lockIconViewController,
CentralSurfaces centralSurfaces,
+ DozeServiceHost dozeServiceHost,
+ DozeScrimController dozeScrimController,
BackActionInteractor backActionInteractor,
PowerInteractor powerInteractor,
NotificationShadeWindowController controller,
@@ -160,6 +168,7 @@
NotificationInsetsController notificationInsetsController,
AmbientState ambientState,
ShadeLogger shadeLogger,
+ DumpManager dumpManager,
PulsingGestureListener pulsingGestureListener,
LockscreenHostedDreamGestureListener lockscreenHostedDreamGestureListener,
KeyguardBouncerViewModel keyguardBouncerViewModel,
@@ -187,8 +196,9 @@
mLockIconViewController = lockIconViewController;
mBackActionInteractor = backActionInteractor;
mShadeLogger = shadeLogger;
- mLockIconViewController.init();
mService = centralSurfaces;
+ mDozeServiceHost = dozeServiceHost;
+ mDozeScrimController = dozeScrimController;
mPowerInteractor = powerInteractor;
mNotificationShadeWindowController = controller;
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
@@ -226,6 +236,9 @@
progressProvider -> progressProvider.addCallback(
mDisableSubpixelTextTransitionListener));
}
+
+ lockIconViewController.setLockIconView(mView.findViewById(R.id.lock_icon_view));
+ dumpManager.registerDumpable(this);
}
/**
@@ -332,7 +345,7 @@
}
if (mStatusBarStateController.isDozing()) {
- mService.extendDozePulse();
+ mDozeScrimController.extendPulse();
}
mLockIconViewController.onTouchEvent(
ev,
@@ -391,7 +404,7 @@
@Override
public boolean shouldInterceptTouchEvent(MotionEvent ev) {
- if (mStatusBarStateController.isDozing() && !mService.isPulsing()
+ if (mStatusBarStateController.isDozing() && !mDozeServiceHost.isPulsing()
&& !mDockManager.isDocked()) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mShadeLogger.d("NSWVC: capture all touch events in always-on");
@@ -445,7 +458,7 @@
public boolean handleTouchEvent(MotionEvent ev) {
boolean handled = false;
if (mStatusBarStateController.isDozing()) {
- handled = !mService.isPulsing();
+ handled = !mDozeServiceHost.isPulsing();
}
if (mStatusBarKeyguardViewManager.onTouch(ev)) {
@@ -533,6 +546,7 @@
mAmbientState.setSwipingUp(false);
}
+ @Override
public void dump(PrintWriter pw, String[] args) {
pw.print(" mExpandAnimationRunning=");
pw.println(mExpandAnimationRunning);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index c9c911b..b2bbffd 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.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 @@
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 @@
AmbientState ambientState,
RecordingController recordingController,
FalsingManager falsingManager,
- FalsingCollector falsingCollector,
AccessibilityManager accessibilityManager,
LockscreenGestureLogger lockscreenGestureLogger,
MetricsLogger metricsLogger,
@@ -381,7 +378,6 @@
mAmbientState = ambientState;
mRecordingController = recordingController;
mFalsingManager = falsingManager;
- mFalsingCollector = falsingCollector;
mAccessibilityManager = accessibilityManager;
mLockscreenGestureLogger = lockscreenGestureLogger;
@@ -1660,7 +1656,6 @@
}
}
if (shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
- mFalsingCollector.onQsDown();
mShadeLog.logMotionEvent(event,
"handleQsDown: down action, QS tracking enabled");
mTracking = true;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index d7a3392..9a3e4e5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -30,6 +30,7 @@
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.dagger.ShadeTouchLog;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -59,6 +60,7 @@
private final CommandQueue mCommandQueue;
private final Executor mMainExecutor;
private final LogBuffer mTouchLog;
+ private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
private final KeyguardStateController mKeyguardStateController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final StatusBarStateController mStatusBarStateController;
@@ -83,6 +85,7 @@
CommandQueue commandQueue,
@Main Executor mainExecutor,
@ShadeTouchLog LogBuffer touchLog,
+ WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
KeyguardStateController keyguardStateController,
StatusBarStateController statusBarStateController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@@ -97,6 +100,7 @@
mCommandQueue = commandQueue;
mMainExecutor = mainExecutor;
mTouchLog = touchLog;
+ mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
mShadeViewControllerLazy = shadeViewControllerLazy;
mStatusBarStateController = statusBarStateController;
mStatusBarWindowController = statusBarWindowController;
@@ -391,6 +395,7 @@
private void notifyVisibilityChanged(boolean visible) {
mShadeVisibilityListener.visibilityChanged(visible);
+ mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(visible);
}
private void notifyExpandedVisibleChanged(boolean expandedVisible) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
index 2532bad..b553f0f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -99,9 +99,6 @@
/** Sets the view's alpha to max. */
fun resetAlpha()
- /** Sets progress of the predictive back animation. */
- fun onBackProgressed(progressFraction: Float)
-
/** @see com.android.systemui.keyguard.ScreenLifecycle.Observer.onScreenTurningOn */
fun onScreenTurningOn()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 182a676..1121834 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -155,6 +155,9 @@
/** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */
fun onBackPressed()
+ /** Sets progress of the predictive back animation. */
+ fun onBackProgressed(progressFraction: Float)
+
/** Sets whether the status bar launch animation is currently running. */
fun setIsLaunchAnimationRunning(running: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index 09b74b2..6a2bef2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -58,6 +58,7 @@
return false
}
override fun onBackPressed() {}
+ override fun onBackProgressed(progressFraction: Float) {}
override fun setIsLaunchAnimationRunning(running: Boolean) {}
override fun setAlpha(alpha: Int, animate: Boolean) {}
override fun setAlphaChangeAnimationEndAction(r: Runnable) {}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 05b1ac6..6585fcb 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.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 @@
?: 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 @@
// 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 25bb204..f004982 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 @@
private val powerInteractor: PowerInteractor,
) : Dumpable {
private var pulseHeight: Float = 0f
+
@get:VisibleForTesting
var fractionToShade: Float = 0f
private set
@@ -255,17 +256,17 @@
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 @@
*/
internal fun canDragDown(): Boolean {
return (statusBarStateController.state == StatusBarState.KEYGUARD ||
- nsslController.isInLockedDownShade()) &&
- (qS.isFullyCollapsed || useSplitShade)
+ nsslController.isInLockedDownShade()) &&
+ (qS.isFullyCollapsed || useSplitShade)
}
/**
@@ -308,10 +309,15 @@
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 @@
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 @@
*/
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 @@
* @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 @@
*/
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 @@
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 @@
shadeViewController.transitionToExpandedShade(delay)
}
}
- goToLockedShadeInternal(expandedView, animationHandler,
- cancelAction = null)
+ goToLockedShadeInternal(
+ expandedView,
+ animationHandler,
+ cancelAction = null
+ )
}
}
@@ -570,14 +603,19 @@
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 @@
val cancelHandler = Runnable {
draggedDownEntry?.apply {
setUserLocked(false)
- notifyHeightChanged(false /* needsAnimation */)
+ notifyHeightChanged(
+ /* needsAnimation= */
+ false,
+ )
draggedDownEntry = null
}
cancelAction?.run()
@@ -614,7 +655,10 @@
// 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 @@
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 @@
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 @@
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 @@
}
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 @@
stopDragging()
return false
}
+
MotionEvent.ACTION_CANCEL -> {
stopDragging()
return false
@@ -913,8 +961,8 @@
@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 @@
}
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 763400b..5bd40b8 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.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.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.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 @@
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 NotificationMediaManager(
Context context,
- Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
Lazy<NotificationShadeWindowController> notificationShadeWindowController,
NotificationVisibilityProvider visibilityProvider,
MediaArtworkProcessor mediaArtworkProcessor,
@@ -205,8 +202,6 @@
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 @@
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 fc6eaa8..2c0741e 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.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 @@
companion object {
private val SPRING_BACK_ANIMATION_LENGTH_MS = 375
}
+
private val mPowerManager: PowerManager?
private var mInitialTouchX: Float = 0.0f
@@ -94,7 +95,10 @@
pulseExpandAbortListener?.run()
}
}
- headsUpManager.unpinAll(true /* userUnPinned */)
+ headsUpManager.unpinAll(
+ /*userUnPinned= */
+ true,
+ )
}
}
var leavingLockscreen: Boolean = false
@@ -168,7 +172,6 @@
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 @@
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 @@
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 @@
}
recycleVelocityTracker()
}
+
MotionEvent.ACTION_CANCEL -> {
cancelExpansion()
recycleVelocityTracker()
@@ -243,17 +250,25 @@
}
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 @@
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 @@
@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 @@
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 @@
val childAtRawPosition = stackScrollerController.getChildAtRawPosition(totalX, totalY)
return if (childAtRawPosition != null && childAtRawPosition.isContentExpandable) {
childAtRawPosition
- } else null
+ } else {
+ null
+ }
}
fun setUp(stackScrollerController: NotificationStackScrollLayoutController) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
index 92aa986..b46b525 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.connectivity
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.SIGNAL_CALLBACK_DEPRECATION
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.tiles.AirplaneModeTile
import com.android.systemui.qs.tiles.BluetoothTile
@@ -23,21 +25,17 @@
import com.android.systemui.qs.tiles.DataSaverTile
import com.android.systemui.qs.tiles.HotspotTile
import com.android.systemui.qs.tiles.InternetTile
+import com.android.systemui.qs.tiles.InternetTileNewImpl
import com.android.systemui.qs.tiles.NfcTile
import dagger.Binds
import dagger.Module
+import dagger.Provides
import dagger.multibindings.IntoMap
import dagger.multibindings.StringKey
@Module
interface ConnectivityModule {
- /** Inject InternetTile into tileMap in QSModule */
- @Binds
- @IntoMap
- @StringKey(InternetTile.TILE_SPEC)
- fun bindInternetTile(internetTile: InternetTile): QSTileImpl<*>
-
/** Inject BluetoothTile into tileMap in QSModule */
@Binds
@IntoMap
@@ -70,4 +68,21 @@
/** Inject NfcTile into tileMap in QSModule */
@Binds @IntoMap @StringKey(NfcTile.TILE_SPEC) fun bindNfcTile(nfcTile: NfcTile): QSTileImpl<*>
+
+ companion object {
+ /** Inject InternetTile or InternetTileNewImpl into tileMap in QSModule */
+ @Provides
+ @IntoMap
+ @StringKey(InternetTile.TILE_SPEC)
+ fun bindInternetTile(
+ internetTile: InternetTile,
+ newInternetTile: InternetTileNewImpl,
+ featureFlags: FeatureFlags,
+ ): QSTileImpl<*> =
+ if (featureFlags.isEnabled(SIGNAL_CALLBACK_DEPRECATION)) {
+ newInternetTile
+ } else {
+ internetTile
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index f726c4e..3dfe068 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.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 @@
@Provides
static NotificationMediaManager provideNotificationMediaManager(
Context context,
- Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
Lazy<NotificationShadeWindowController> notificationShadeWindowController,
NotificationVisibilityProvider visibilityProvider,
MediaArtworkProcessor mediaArtworkProcessor,
@@ -152,7 +150,6 @@
DisplayManager displayManager) {
return new NotificationMediaManager(
context,
- centralSurfacesOptionalLazy,
notificationShadeWindowController,
visibilityProvider,
mediaArtworkProcessor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 637637d..09be41b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -19,12 +19,13 @@
import android.content.Context;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.systemui.CoreStartable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.shade.ShadeEventsModule;
-import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
@@ -76,10 +77,13 @@
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
+import com.android.systemui.util.kotlin.JavaAdapter;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
import java.util.concurrent.Executor;
@@ -110,6 +114,13 @@
@Binds
NotifGutsViewManager bindNotifGutsViewManager(NotificationGutsManager notificationGutsManager);
+ /** Binds {@link NotificationGutsManager} as a {@link CoreStartable}. */
+ @Binds
+ @IntoMap
+ @ClassKey(NotificationGutsManager.class)
+ CoreStartable bindsNotificationGutsManager(NotificationGutsManager notificationGutsManager);
+
+
/** Provides an instance of {@link VisibilityLocationProvider} */
@Binds
VisibilityLocationProvider bindVisibilityLocationProvider(
@@ -125,7 +136,8 @@
NotificationVisibilityProvider visibilityProvider,
NotifPipeline notifPipeline,
StatusBarStateController statusBarStateController,
- ShadeExpansionStateManager shadeExpansionStateManager,
+ WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
+ JavaAdapter javaAdapter,
NotificationLogger.ExpansionStateLogger expansionStateLogger,
NotificationPanelLogger notificationPanelLogger) {
return new NotificationLogger(
@@ -135,11 +147,18 @@
visibilityProvider,
notifPipeline,
statusBarStateController,
- shadeExpansionStateManager,
+ windowRootViewVisibilityInteractor,
+ javaAdapter,
expansionStateLogger,
notificationPanelLogger);
}
+ /** Binds {@link NotificationLogger} as a {@link CoreStartable}. */
+ @Binds
+ @IntoMap
+ @ClassKey(NotificationLogger.class)
+ CoreStartable bindsNotificationLogger(NotificationLogger notificationLogger);
+
/** Provides an instance of {@link NotificationPanelLogger} */
@SysUISingleton
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 26f97de..d2034d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -33,10 +33,11 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
@@ -48,6 +49,7 @@
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.util.Compile;
+import com.android.systemui.util.kotlin.JavaAdapter;
import java.util.Collection;
import java.util.Collections;
@@ -62,7 +64,7 @@
* Handles notification logging, in particular, logging which notifications are visible and which
* are not.
*/
-public class NotificationLogger implements StateListener {
+public class NotificationLogger implements StateListener, CoreStartable {
static final String TAG = "NotificationLogger";
private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
@@ -81,6 +83,8 @@
private final NotifPipeline mNotifPipeline;
private final NotificationPanelLogger mNotificationPanelLogger;
private final ExpansionStateLogger mExpansionStateLogger;
+ private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
+ private final JavaAdapter mJavaAdapter;
protected Handler mHandler = new Handler();
protected IStatusBarService mBarService;
@@ -88,10 +92,7 @@
private NotificationListContainer mListContainer;
private final Object mDozingLock = new Object();
@GuardedBy("mDozingLock")
- private Boolean mDozing = null; // Use null to indicate state is not yet known
- @GuardedBy("mDozingLock")
private Boolean mLockscreen = null; // Use null to indicate state is not yet known
- private Boolean mPanelExpanded = null; // Use null to indicate state is not yet known
private boolean mLogging = false;
// Tracks notifications currently visible in mNotificationStackScroller and
@@ -202,7 +203,8 @@
NotificationVisibilityProvider visibilityProvider,
NotifPipeline notifPipeline,
StatusBarStateController statusBarStateController,
- ShadeExpansionStateManager shadeExpansionStateManager,
+ WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
+ JavaAdapter javaAdapter,
ExpansionStateLogger expansionStateLogger,
NotificationPanelLogger notificationPanelLogger) {
mNotificationListener = notificationListener;
@@ -214,9 +216,10 @@
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
mExpansionStateLogger = expansionStateLogger;
mNotificationPanelLogger = notificationPanelLogger;
+ mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
+ mJavaAdapter = javaAdapter;
// Not expected to be destroyed, don't need to unsubscribe
statusBarStateController.addCallback(this);
- shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
registerNewPipelineListener();
}
@@ -239,6 +242,22 @@
mListContainer = listContainer;
}
+ @Override
+ public void start() {
+ mJavaAdapter.alwaysCollectFlow(
+ mWindowRootViewVisibilityInteractor.isLockscreenOrShadeVisibleAndInteractive(),
+ this::onLockscreenOrShadeVisibleAndInteractiveChanged);
+ }
+
+ private void onLockscreenOrShadeVisibleAndInteractiveChanged(boolean visible) {
+ if (visible) {
+ startNotificationLogging();
+ } else {
+ // Ensure we stop notification logging when the device isn't interactive.
+ stopNotificationLogging();
+ }
+ }
+
public void stopNotificationLogging() {
if (mLogging) {
mLogging = false;
@@ -257,16 +276,19 @@
}
}
+ @GuardedBy("mDozingLock")
public void startNotificationLogging() {
if (!mLogging) {
mLogging = true;
if (DEBUG) {
Log.i(TAG, "startNotificationLogging");
}
+ boolean lockscreen = mLockscreen != null && mLockscreen;
+ mNotificationPanelLogger.logPanelShown(lockscreen, getVisibleNotifications());
mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged);
- // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't
- // cause the scroller to emit child location events. Hence generate
- // one ourselves to guarantee that we're reporting visible
+ // Sometimes, the transition from lockscreenOrShadeVisible=false ->
+ // lockscreenOrShadeVisible=true doesn't cause the scroller to emit child location
+ // events. Hence generate one ourselves to guarantee that we're reporting visible
// notifications.
// (Note that in cases where the scroller does emit events, this
// additional event doesn't break anything.)
@@ -274,13 +296,6 @@
}
}
- private void setDozing(boolean dozing) {
- synchronized (mDozingLock) {
- mDozing = dozing;
- maybeUpdateLoggingStatus();
- }
- }
-
private void logNotificationVisibilityChanges(
Collection<NotificationVisibility> newlyVisible,
Collection<NotificationVisibility> noLongerVisible) {
@@ -362,39 +377,6 @@
}
}
- @Override
- public void onDozingChanged(boolean isDozing) {
- if (DEBUG) {
- Log.i(TAG, "onDozingChanged: new=" + isDozing);
- }
- setDozing(isDozing);
- }
-
- @GuardedBy("mDozingLock")
- private void maybeUpdateLoggingStatus() {
- if (mPanelExpanded == null || mDozing == null) {
- if (DEBUG) {
- Log.i(TAG, "Panel status unclear: panelExpandedKnown="
- + (mPanelExpanded == null) + " dozingKnown=" + (mDozing == null));
- }
- return;
- }
- // Once we know panelExpanded and Dozing, turn logging on & off when appropriate
- boolean lockscreen = mLockscreen == null ? false : mLockscreen;
- if (mPanelExpanded && !mDozing) {
- mNotificationPanelLogger.logPanelShown(lockscreen, getVisibleNotifications());
- if (DEBUG) {
- Log.i(TAG, "Notification panel shown, lockscreen=" + lockscreen);
- }
- startNotificationLogging();
- } else {
- if (DEBUG) {
- Log.i(TAG, "Notification panel hidden, lockscreen=" + lockscreen);
- }
- stopNotificationLogging();
- }
- }
-
/**
* Called when the notification is expanded / collapsed.
*/
@@ -404,20 +386,6 @@
}
@VisibleForTesting
- void onShadeExpansionFullyChanged(Boolean isExpanded) {
- // mPanelExpanded is initialized as null
- if (mPanelExpanded == null || !mPanelExpanded.equals(isExpanded)) {
- if (DEBUG) {
- Log.i(TAG, "onPanelExpandedChanged: new=" + isExpanded);
- }
- mPanelExpanded = isExpanded;
- synchronized (mDozingLock) {
- maybeUpdateLoggingStatus();
- }
- }
- }
-
- @VisibleForTesting
void onChildLocationsChanged() {
if (mHandler.hasCallbacks(mVisibilityReporter)) {
// Visibilities will be reported when the existing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index d92d11b..c02382d 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.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 @@
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 @@
OnExpandClickListener onExpandClickListener,
CoordinateOnClickListener onFeedbackClickListener,
FalsingManager falsingManager,
- FalsingCollector falsingCollector,
StatusBarStateController statusBarStateController,
PeopleNotificationIdentifier peopleNotificationIdentifier,
OnUserInteractionCallback onUserInteractionCallback,
@@ -1743,7 +1740,6 @@
mOnExpandClickListener = onExpandClickListener;
setOnFeedbackClickListener(onFeedbackClickListener);
mFalsingManager = falsingManager;
- mFalsingCollector = falsingCollector;
mStatusBarStateController = statusBarStateController;
mPeopleNotificationIdentifier = peopleNotificationIdentifier;
for (NotificationContentView l : mLayouts) {
@@ -2471,7 +2467,6 @@
* @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 80f5d19..af55f44 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 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 @@
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 @@
@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 @@
mFalsingManager = falsingManager;
mOnFeedbackClickListener = mNotificationGutsManager::openGuts;
mAllowLongPress = allowLongPress;
- mFalsingCollector = falsingCollector;
mFeatureFlags = featureFlags;
mPeopleNotificationIdentifier = peopleNotificationIdentifier;
mBubblesManagerOptional = bubblesManagerOptional;
@@ -285,7 +281,6 @@
mOnExpandClickListener,
mOnFeedbackClickListener,
mFalsingManager,
- mFalsingCollector,
mStatusBarStateController,
mPeopleNotificationIdentifier,
mOnUserInteractionCallback,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 6f79ea8..44ead26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -45,6 +45,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settingslib.notification.ConversationIconFactory;
+import com.android.systemui.CoreStartable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -53,6 +54,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -69,6 +71,7 @@
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.wmshell.BubblesManager;
import java.util.Optional;
@@ -80,7 +83,7 @@
* closing guts, and keeping track of the currently exposed notification guts.
*/
@SysUISingleton
-public class NotificationGutsManager implements NotifGutsViewManager {
+public class NotificationGutsManager implements NotifGutsViewManager, CoreStartable {
private static final String TAG = "NotificationGutsManager";
// Must match constant in Settings. Used to highlight preferences when linking to Settings.
@@ -109,6 +112,7 @@
private final Handler mMainHandler;
private final Handler mBgHandler;
+ private final JavaAdapter mJavaAdapter;
private final Optional<BubblesManager> mBubblesManagerOptional;
private Runnable mOpenRunnable;
private final INotificationManager mNotificationManager;
@@ -121,6 +125,7 @@
private final UserContextProvider mContextTracker;
private final UiEventLogger mUiEventLogger;
private final ShadeController mShadeController;
+ private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
private NotifGutsViewListener mGutsListener;
private final HeadsUpManagerPhone mHeadsUpManagerPhone;
private final ActivityStarter mActivityStarter;
@@ -129,6 +134,7 @@
public NotificationGutsManager(Context context,
@Main Handler mainHandler,
@Background Handler bgHandler,
+ JavaAdapter javaAdapter,
AccessibilityManager accessibilityManager,
HighPriorityProvider highPriorityProvider,
INotificationManager notificationManager,
@@ -143,6 +149,7 @@
UiEventLogger uiEventLogger,
OnUserInteractionCallback onUserInteractionCallback,
ShadeController shadeController,
+ WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
NotificationLockscreenUserManager notificationLockscreenUserManager,
StatusBarStateController statusBarStateController,
DeviceProvisionedController deviceProvisionedController,
@@ -152,6 +159,7 @@
mContext = context;
mMainHandler = mainHandler;
mBgHandler = bgHandler;
+ mJavaAdapter = javaAdapter;
mAccessibilityManager = accessibilityManager;
mHighPriorityProvider = highPriorityProvider;
mNotificationManager = notificationManager;
@@ -166,6 +174,7 @@
mUiEventLogger = uiEventLogger;
mOnUserInteractionCallback = onUserInteractionCallback;
mShadeController = shadeController;
+ mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
mLockscreenUserManager = notificationLockscreenUserManager;
mStatusBarStateController = statusBarStateController;
mDeviceProvisionedController = deviceProvisionedController;
@@ -187,6 +196,25 @@
mNotificationActivityStarter = notificationActivityStarter;
}
+ @Override
+ public void start() {
+ mJavaAdapter.alwaysCollectFlow(
+ mWindowRootViewVisibilityInteractor.isLockscreenOrShadeVisible(),
+ this::onLockscreenShadeVisibilityChanged);
+ }
+
+ private void onLockscreenShadeVisibilityChanged(boolean visible) {
+ if (!visible) {
+ closeAndSaveGuts(
+ /* removeLeavebehind= */ true ,
+ /* force= */ true,
+ /* removeControls= */ true,
+ /* x= */ -1,
+ /* y= */ -1,
+ /* resetMenu= */ true);
+ }
+ }
+
public void onDensityOrFontScaleChanged(NotificationEntry entry) {
setExposedGuts(entry.getGuts());
bindGuts(entry.getRow());
@@ -512,7 +540,7 @@
mNotificationGutsExposed.removeCallbacks(mOpenRunnable);
mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
}
- if (resetMenu) {
+ if (resetMenu && mListContainer != null) {
mListContainer.resetExposedMenuView(false /* animate */, true /* force */);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 6db8df9..d8f513c 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 @@
@Override
public void onDragCancelled(View v) {
- mFalsingCollector.onNotificationStopDismissing();
}
/**
@@ -501,7 +500,6 @@
}
mView.addSwipedOutView(view);
- mFalsingCollector.onNotificationDismissed();
if (mFalsingCollector.shouldEnforceBouncer()) {
mActivityStarter.executeRunnableDismissingKeyguard(
null,
@@ -549,7 +547,6 @@
@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 d0a093c..cbb3915 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 @@
void onKeyguardViewManagerStatesUpdated();
- boolean isPulsing();
-
- boolean isOccluded();
-
boolean isDeviceInVrMode();
NotificationPresenter getPresenter();
@@ -247,8 +243,6 @@
@Deprecated
float getDisplayHeight();
- void readyForKeyguardDone();
-
void showKeyguard();
boolean hideKeyguard();
@@ -370,8 +364,6 @@
@Deprecated
float getDisplayDensity();
- void extendDozePulse();
-
public static class KeyboardShortcutsMessage {
final int mDeviceId;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
new file mode 100644
index 0000000..10422e3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.content.Intent
+import androidx.lifecycle.LifecycleRegistry
+import com.android.keyguard.AuthKeyguardMessageArea
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.navigationbar.NavigationBarView
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.qs.QSPanelController
+import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import java.io.PrintWriter
+
+/**
+ * Empty implementation of [CentralSurfaces] for variants only need to override portions of the
+ * interface.
+ *
+ * **Important**: Prefer binding an Optional<CentralSurfaces> to an empty optional instead of
+ * including this class.
+ */
+abstract class CentralSurfacesEmptyImpl : CentralSurfaces {
+ override val lifecycle = LifecycleRegistry(this)
+ override fun updateIsKeyguard() = false
+ override fun updateIsKeyguard(forceStateChange: Boolean) = false
+ override fun getKeyguardMessageArea(): AuthKeyguardMessageArea? = null
+ override fun isLaunchingActivityOverLockscreen() = false
+ override fun onKeyguardViewManagerStatesUpdated() {}
+ override fun isDeviceInVrMode() = false
+ override fun getPresenter(): NotificationPresenter? = null
+ override fun onInputFocusTransfer(start: Boolean, cancel: Boolean, velocity: Float) {}
+ override fun getCommandQueuePanelsEnabled() = false
+ override fun getBiometricUnlockController(): BiometricUnlockController? = null
+ override fun showWirelessChargingAnimation(batteryLevel: Int) {}
+ override fun checkBarModes() {}
+ override fun updateBubblesVisibility() {}
+ override fun setInteracting(barWindow: Int, interacting: Boolean) {}
+ override fun dump(pwOriginal: PrintWriter, args: Array<String>) {}
+ override fun getDisplayWidth() = 0f
+ override fun getDisplayHeight() = 0f
+ override fun showKeyguard() {}
+ override fun hideKeyguard() = false
+ override fun showKeyguardImpl() {}
+ override fun fadeKeyguardAfterLaunchTransition(
+ beforeFading: Runnable?,
+ endRunnable: Runnable?,
+ cancelRunnable: Runnable?,
+ ) {}
+ override fun startLaunchTransitionTimeout() {}
+ override fun hideKeyguardImpl(forceStateChange: Boolean) = false
+ override fun keyguardGoingAway() {}
+ override fun setKeyguardFadingAway(startTime: Long, delay: Long, fadeoutDuration: Long) {}
+ override fun finishKeyguardFadingAway() {}
+ override fun userActivity() {}
+ override fun endAffordanceLaunch() {}
+ override fun shouldKeyguardHideImmediately() = false
+ override fun showBouncerWithDimissAndCancelIfKeyguard(
+ performAction: OnDismissAction?,
+ cancelAction: Runnable?,
+ ) {}
+ override fun getNavigationBarView(): NavigationBarView? = null
+ override fun isOverviewEnabled() = false
+ override fun showPinningEnterExitToast(entering: Boolean) {}
+ override fun showPinningEscapeToast() {}
+ override fun setBouncerShowing(bouncerShowing: Boolean) {}
+ override fun getWakefulnessState() = 0
+ override fun isScreenFullyOff() = false
+ override fun showScreenPinningRequest(taskId: Int, allowCancel: Boolean) {}
+ override fun getEmergencyActionIntent(): Intent? = null
+ override fun isCameraAllowedByAdmin() = false
+ override fun isGoingToSleep() = false
+ override fun notifyBiometricAuthModeChanged() {}
+ override fun setTransitionToFullShadeProgress(transitionToFullShadeProgress: Float) {}
+ override fun setPrimaryBouncerHiddenFraction(expansion: Float) {}
+ override fun updateScrimController() {}
+ override fun isKeyguardShowing() = false
+ override fun shouldIgnoreTouch() = false
+ override fun isDeviceInteractive() = false
+ override fun awakenDreams() {}
+ override fun clearNotificationEffects() {}
+ override fun isBouncerShowing() = false
+ override fun isBouncerShowingScrimmed() = false
+ override fun isBouncerShowingOverDream() = false
+ override fun isKeyguardSecure() = false
+ override fun updateNotificationPanelTouchState() {}
+ override fun getRotation() = 0
+ override fun setBarStateForTest(state: Int) {}
+ override fun showTransientUnchecked() {}
+ override fun clearTransient() {}
+ override fun acquireGestureWakeLock(time: Long) {}
+ override fun setAppearance(appearance: Int) = false
+ override fun getBarMode() = 0
+ override fun resendMessage(msg: Int) {}
+ override fun resendMessage(msg: Any?) {}
+ override fun setLastCameraLaunchSource(source: Int) {}
+ override fun setLaunchCameraOnFinishedGoingToSleep(launch: Boolean) {}
+ override fun setLaunchCameraOnFinishedWaking(launch: Boolean) {}
+ override fun setLaunchEmergencyActionOnFinishedGoingToSleep(launch: Boolean) {}
+ override fun setLaunchEmergencyActionOnFinishedWaking(launch: Boolean) {}
+ override fun getQSPanelController(): QSPanelController? = null
+ override fun getDisplayDensity() = 0f
+ override fun setIsLaunchingActivityOverLockscreen(isLaunchingActivityOverLockscreen: Boolean) {}
+ override fun getAnimatorControllerFromNotification(
+ associatedView: ExpandableNotificationRow?,
+ ): ActivityLaunchAnimator.Controller? = null
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 6a3ebe6..d91f375 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -92,17 +92,12 @@
import android.view.MotionEvent;
import android.view.ThreadedRenderer;
import android.view.View;
-import android.view.ViewRootImpl;
import android.view.WindowInsets;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
import android.widget.DateTimeView;
-import android.window.BackEvent;
-import android.window.OnBackAnimationCallback;
-import android.window.OnBackInvokedCallback;
-import android.window.OnBackInvokedDispatcher;
import androidx.annotation.NonNull;
import androidx.lifecycle.Lifecycle;
@@ -176,6 +171,7 @@
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.recents.ScreenPinningRequest;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.settings.brightness.BrightnessSliderController;
@@ -221,7 +217,6 @@
import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -462,6 +457,7 @@
private final CentralSurfacesComponent.Factory mCentralSurfacesComponentFactory;
private final PluginManager mPluginManager;
private final ShadeController mShadeController;
+ private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
private final InitController mInitController;
private final Lazy<CameraLauncher> mCameraLauncherLazy;
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
@@ -489,13 +485,11 @@
private View mReportRejectedTouch;
private final NotificationGutsManager mGutsManager;
- private final NotificationLogger mNotificationLogger;
private final ShadeExpansionStateManager mShadeExpansionStateManager;
private final KeyguardViewMediator mKeyguardViewMediator;
protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
private final BrightnessSliderController.Factory mBrightnessSliderFactory;
private final FeatureFlags mFeatureFlags;
- private final boolean mAnimateBack;
private final FragmentService mFragmentService;
private final ScreenOffAnimationController mScreenOffAnimationController;
private final WallpaperController mWallpaperController;
@@ -508,13 +502,6 @@
private CentralSurfacesComponent mCentralSurfacesComponent;
- /**
- * This keeps track of whether we have (or haven't) registered the predictive back callback.
- * Since we can have visible -> visible transitions, we need to avoid
- * double-registering (or double-unregistering) our callback.
- */
- private boolean mIsBackCallbackRegistered = false;
-
/** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */
private @Appearance int mAppearance;
@@ -640,31 +627,6 @@
private final InteractionJankMonitor mJankMonitor;
- /** Existing callback that handles back gesture invoked for the Shade. */
- private final OnBackInvokedCallback mOnBackInvokedCallback;
-
- /**
- * New callback that handles back gesture invoked, cancel, progress
- * and provides feedback via Shade animation.
- * (enabled via the WM_SHADE_ANIMATE_BACK_GESTURE flag)
- */
- private final OnBackAnimationCallback mOnBackAnimationCallback = new OnBackAnimationCallback() {
- @Override
- public void onBackInvoked() {
- mBackActionInteractor.onBackRequested();
- }
-
- @Override
- public void onBackProgressed(BackEvent event) {
- if (mBackActionInteractor.shouldBackBeHandled()) {
- if (mShadeSurface.canBeCollapsed()) {
- float fraction = event.getProgress();
- mShadeSurface.onBackProgressed(fraction);
- }
- }
- }
- };
-
/**
* Public constructor for CentralSurfaces.
*
@@ -694,7 +656,6 @@
FalsingCollector falsingCollector,
BroadcastDispatcher broadcastDispatcher,
NotificationGutsManager notificationGutsManager,
- NotificationLogger notificationLogger,
NotificationInterruptStateProvider notificationInterruptStateProvider,
ShadeExpansionStateManager shadeExpansionStateManager,
KeyguardViewMediator keyguardViewMediator,
@@ -745,6 +706,7 @@
Lazy<CentralSurfacesCommandQueueCallbacks> commandQueueCallbacksLazy,
PluginManager pluginManager,
ShadeController shadeController,
+ WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
ViewMediatorCallback viewMediatorCallback,
InitController initController,
@@ -803,7 +765,6 @@
mFalsingManager = falsingManager;
mBroadcastDispatcher = broadcastDispatcher;
mGutsManager = notificationGutsManager;
- mNotificationLogger = notificationLogger;
mNotificationInterruptStateProvider = notificationInterruptStateProvider;
mShadeExpansionStateManager = shadeExpansionStateManager;
mKeyguardViewMediator = keyguardViewMediator;
@@ -856,6 +817,7 @@
mCommandQueueCallbacksLazy = commandQueueCallbacksLazy;
mPluginManager = pluginManager;
mShadeController = shadeController;
+ mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardViewMediatorCallback = viewMediatorCallback;
mInitController = initController;
@@ -926,14 +888,6 @@
if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI)) {
mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
}
- // Based on teamfood flag, enable predictive back animation for the Shade.
- mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
- mOnBackInvokedCallback = () -> {
- if (DEBUG) {
- Log.d(TAG, "mOnBackInvokedCallback() called");
- }
- mBackActionInteractor.onBackRequested();
- };
}
private void initBubbles(Bubbles bubbles) {
@@ -1171,7 +1125,10 @@
new FoldStateListener(mContext, this::onFoldedStateChanged));
}
- @VisibleForTesting
+ /**
+ * @deprecated use {@link
+ * WindowRootViewVisibilityInteractor.isLockscreenOrShadeVisible} instead.
+ */ @VisibleForTesting
void initShadeVisibilityListener() {
mShadeController.setVisibilityListener(new ShadeController.ShadeVisibilityListener() {
@Override
@@ -1480,7 +1437,7 @@
// - Shade is in QQS over keyguard - swiping up should take us back to keyguard
if (!isKeyguardShowing()
|| mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()
- || isOccluded()
+ || mKeyguardStateController.isOccluded()
|| !mKeyguardStateController.canDismissLockScreen()
|| mKeyguardViewMediator.isAnySimPinSecure()
|| (mQsController.getExpanded() && trackingTouch)
@@ -1563,6 +1520,7 @@
mNotifListContainer,
mStackScrollerController.getNotifStackController(),
mNotificationActivityStarter);
+ mWindowRootViewVisibilityInteractor.setUp(mPresenter, mNotificationsController);
}
/**
@@ -1709,22 +1667,6 @@
}
@Override
- public boolean isPulsing() {
- return mDozeServiceHost.isPulsing();
- }
-
- /**
- * When the keyguard is showing and covered by a "showWhenLocked" activity it
- * is occluded. This is controlled by {@link com.android.server.policy.PhoneWindowManager}
- *
- * @return whether the keyguard is currently occluded
- */
- @Override
- public boolean isOccluded() {
- return mKeyguardStateController.isOccluded();
- }
-
- @Override
public boolean isDeviceInVrMode() {
return mPresenter.isDeviceInVrMode();
}
@@ -1963,8 +1905,6 @@
pw.print(" mDozing="); pw.println(mDozing);
pw.print(" mWallpaperSupported= "); pw.println(mWallpaperSupported);
- pw.println(" ShadeWindowView: ");
- getNotificationShadeWindowViewController().dump(pw, args);
CentralSurfaces.dumpBarTransitions(
pw, "PhoneStatusBarTransitions", mStatusBarTransitions);
@@ -2077,11 +2017,6 @@
return mDisplay.getRotation();
}
- @Override
- public void readyForKeyguardDone() {
- mStatusBarKeyguardViewManager.readyForKeyguardDone();
- }
-
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -2170,82 +2105,6 @@
com.android.systemui.R.dimen.physical_power_button_center_screen_location_y));
}
- protected void handleVisibleToUserChanged(boolean visibleToUser) {
- if (visibleToUser) {
- onVisibleToUser();
- mNotificationLogger.startNotificationLogging();
-
- if (!mIsBackCallbackRegistered) {
- ViewRootImpl viewRootImpl = getViewRootImpl();
- if (viewRootImpl != null) {
- viewRootImpl.getOnBackInvokedDispatcher()
- .registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- mAnimateBack ? mOnBackAnimationCallback
- : mOnBackInvokedCallback);
- mIsBackCallbackRegistered = true;
- if (DEBUG) Log.d(TAG, "is now VISIBLE to user AND callback registered");
- }
- } else {
- if (DEBUG) Log.d(TAG, "is now VISIBLE to user, BUT callback ALREADY unregistered");
- }
- } else {
- mNotificationLogger.stopNotificationLogging();
- onInvisibleToUser();
-
- if (mIsBackCallbackRegistered) {
- ViewRootImpl viewRootImpl = getViewRootImpl();
- if (viewRootImpl != null) {
- viewRootImpl.getOnBackInvokedDispatcher()
- .unregisterOnBackInvokedCallback(
- mAnimateBack ? mOnBackAnimationCallback
- : mOnBackInvokedCallback);
- mIsBackCallbackRegistered = false;
- if (DEBUG) Log.d(TAG, "is NOT VISIBLE to user, AND callback unregistered");
- }
- } else {
- if (DEBUG) {
- Log.d(TAG,
- "is NOT VISIBLE to user, BUT NO callback (or callback ALREADY "
- + "unregistered)");
- }
- }
- }
- }
-
- void onVisibleToUser() {
- /* The LEDs are turned off when the notification panel is shown, even just a little bit.
- * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do
- * this.
- */
- boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
- boolean clearNotificationEffects =
- !mPresenter.isPresenterFullyCollapsed() && (mState == StatusBarState.SHADE
- || mState == StatusBarState.SHADE_LOCKED);
- int notificationLoad = mNotificationsController.getActiveNotificationsCount();
- if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) {
- notificationLoad = 1;
- }
- final int finalNotificationLoad = notificationLoad;
- mUiBgExecutor.execute(() -> {
- try {
- mBarService.onPanelRevealed(clearNotificationEffects,
- finalNotificationLoad);
- } catch (RemoteException ex) {
- // Won't fail unless the world has ended.
- }
- });
- }
-
- void onInvisibleToUser() {
- mUiBgExecutor.execute(() -> {
- try {
- mBarService.onPanelHidden();
- } catch (RemoteException ex) {
- // Won't fail unless the world has ended.
- }
- });
- }
-
private void logStateToEventlog() {
boolean isShowing = mKeyguardStateController.isShowing();
boolean isOccluded = mKeyguardStateController.isOccluded();
@@ -2325,11 +2184,12 @@
// there's no surface we can show to the user. Note that the device goes fully interactive
// late in the transition, so we also allow the device to start dozing once the screen has
// turned off fully.
+ boolean keyguardShowingUnOccluded =
+ mKeyguardStateController.isShowing() && !mKeyguardStateController.isOccluded();
boolean keyguardForDozing = mDozeServiceHost.getDozingRequested()
&& (!mDeviceInteractive || (isGoingToSleep()
- && (isScreenFullyOff()
- || (mKeyguardStateController.isShowing() && !isOccluded()))));
- boolean isWakingAndOccluded = isOccluded() && isWakingOrAwake();
+ && (isScreenFullyOff() || keyguardShowingUnOccluded)));
+ boolean isWakingAndOccluded = mKeyguardStateController.isOccluded() && isWakingOrAwake();
boolean shouldBeKeyguard = (mStatusBarStateController.isKeyguardRequested()
|| keyguardForDozing) && !wakeAndUnlocking && !isWakingAndOccluded;
if (keyguardForDozing) {
@@ -2728,11 +2588,6 @@
mNavigationBarController.showPinningEscapeToast(mDisplayId);
}
- protected ViewRootImpl getViewRootImpl() {
- View root = mNotificationShadeWindowController.getWindowRootView();
- if (root != null) return root.getViewRootImpl();
- return null;
- }
/**
* Propagation of the bouncer state, indicating that it's fully visible.
*/
@@ -2779,7 +2634,6 @@
releaseGestureWakeLock();
mLaunchCameraWhenFinishedWaking = false;
mDeviceInteractive = false;
- updateVisibleToUser();
updateNotificationPanelTouchState();
getNotificationShadeWindowViewController().cancelCurrentTouch();
@@ -2852,12 +2706,14 @@
// cancelling a sleep), from the power button, on a device with a power button
// FPS, and 'press to unlock' is required.
mShouldDelayWakeUpAnimation =
- !isPulsing()
+ !mDozeServiceHost.isPulsing()
&& mStatusBarStateController.getDozeAmount() == 1f
&& mWakefulnessLifecycle.getLastWakeReason()
== PowerManager.WAKE_REASON_POWER_BUTTON
&& mFingerprintManager.get().isPowerbuttonFps()
- && mFingerprintManager.get().hasEnrolledFingerprints()
+ && mKeyguardUpdateMonitor
+ .getCachedIsUnlockWithFingerprintPossible(
+ mUserTracker.getUserId())
&& !touchToUnlockAnytime;
if (DEBUG_WAKEUP_DELAY) {
Log.d(TAG, "mShouldDelayWakeUpAnimation=" + mShouldDelayWakeUpAnimation);
@@ -2876,7 +2732,6 @@
/* wakingUp= */ true,
mShouldDelayWakeUpAnimation);
- updateVisibleToUser();
updateIsKeyguard();
mShouldDelayLockscreenTransitionFromAod = mDozeParameters.getAlwaysOn()
&& mFeatureFlags.isEnabled(
@@ -3128,7 +2983,7 @@
mScrimController.setExpansionAffectsAlpha(!unlocking);
if (mAlternateBouncerInteractor.isVisibleState()) {
- if ((!isOccluded() || mShadeSurface.isPanelExpanded())
+ if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded())
&& (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED
|| mTransitionToFullShadeProgress > 0f)) {
mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
@@ -3164,7 +3019,9 @@
// This will cancel the keyguardFadingAway animation if it is running. We need to do
// this as otherwise it can remain pending and leave keyguard in a weird state.
mUnlockScrimCallback.onCancelled();
- } else if (mKeyguardStateController.isShowing() && !isOccluded() && !unlocking) {
+ } else if (mKeyguardStateController.isShowing()
+ && !mKeyguardStateController.isOccluded()
+ && !unlocking) {
mScrimController.transitionTo(ScrimState.KEYGUARD);
} else if (mKeyguardStateController.isShowing() && mKeyguardUpdateMonitor.isDreaming()
&& !unlocking) {
@@ -3203,9 +3060,6 @@
protected boolean mVisible;
- // mScreenOnFromKeyguard && mVisible.
- private boolean mVisibleToUser;
-
protected DevicePolicyManager mDevicePolicyManager;
private final PowerManager mPowerManager;
protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -3329,21 +3183,8 @@
if (visible) {
DejankUtils.notifyRendererOfExpensiveFrame(
getNotificationShadeWindowView(), "onShadeVisibilityChanged");
- } else {
- mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
- true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
}
}
- updateVisibleToUser();
- }
-
- protected void updateVisibleToUser() {
- boolean oldVisibleToUser = mVisibleToUser;
- mVisibleToUser = mVisible && mDeviceInteractive;
-
- if (oldVisibleToUser != mVisibleToUser) {
- handleVisibleToUserChanged(mVisibleToUser);
- }
}
/**
@@ -3401,11 +3242,6 @@
}
}
- @Override
- public void extendDozePulse(){
- mDozeScrimController.extendPulse();
- }
-
private final KeyguardUpdateMonitorCallback mUpdateCallback =
new KeyguardUpdateMonitorCallback() {
@Override
@@ -3524,7 +3360,7 @@
// If we're visible and switched to SHADE_LOCKED (the user dragged
// down on the lockscreen), clear notification LED, vibration,
// ringing.
- // Other transitions are covered in handleVisibleToUserChanged().
+ // Other transitions are covered in WindowRootViewVisibilityInteractor.
if (mVisible && (newState == StatusBarState.SHADE_LOCKED
|| mStatusBarStateController.goingToFullShade())) {
clearNotificationEffects();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 801cdbf..4849f64 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 @@
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 62a8cfd..b0f8276 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 @@
* Sets the amount of vertical over scroll that should be performed on the notifications scrim.
*/
public void setNotificationsOverScrollAmount(int overScrollAmount) {
- mNotificationsScrim.setTranslationY(overScrollAmount);
+ if (mNotificationsScrim != null) mNotificationsScrim.setTranslationY(overScrollAmount);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index d5cb6b6..4878d45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -81,11 +81,7 @@
/** Removes an icon that had come from an active tile service. */
void removeIconForTile(String slot);
- /**
- * Adds or updates an icon for the given slot for **internal system icons**.
- *
- * TODO(b/265307726): Re-name this to `setInternalIcon`.
- */
+ /** Adds or updates an icon for the given slot for **internal system icons**. */
void setIcon(String slot, int resourceId, CharSequence contentDescription);
/**
@@ -127,11 +123,6 @@
*/
void removeIcon(String slot, int tag);
- /**
- * TODO(b/265307726): Re-name this to `removeAllIconsForInternalSlot`.
- */
- void removeAllIconsForSlot(String slot);
-
// TODO: See if we can rename this tunable name.
String ICON_HIDE_LIST = "icon_blacklist";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 553cbc5..0f4d68c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -385,13 +385,7 @@
}
private void removeAllIconsForExternalSlot(String slotName) {
- removeAllIconsForSlot(createExternalSlotName(slotName));
- }
-
- /** */
- @Override
- public void removeAllIconsForSlot(String slotName) {
- removeAllIconsForSlot(slotName, /* fromNewPipeline */ false);
+ removeAllIconsForSlot(createExternalSlotName(slotName), /* fromNewPipeline= */ false);
}
private void removeAllIconsForSlot(String slotName, boolean fromNewPipeline) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractor.kt
new file mode 100644
index 0000000..3709e4c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractor.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.ethernet.domain
+
+import com.android.settingslib.AccessibilityContentDescriptions
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/**
+ * Currently we don't do much to interact with ethernet. We simply need a place to map between the
+ * connectivity state of a default ethernet connection, and an icon representing that connection.
+ */
+@SysUISingleton
+class EthernetInteractor
+@Inject
+constructor(
+ connectivityRepository: ConnectivityRepository,
+) {
+ /** Icon representing the current connectivity status of the ethernet connection */
+ val icon: Flow<Icon.Resource?> =
+ connectivityRepository.defaultConnections.map {
+ if (it.ethernet.isDefault) {
+ if (it.isValidated) {
+ Icon.Resource(
+ R.drawable.stat_sys_ethernet_fully,
+ ContentDescription.Resource(
+ AccessibilityContentDescriptions.ETHERNET_CONNECTION_VALUES[1]
+ )
+ )
+ } else {
+ Icon.Resource(
+ R.drawable.stat_sys_ethernet,
+ ContentDescription.Resource(
+ AccessibilityContentDescriptions.ETHERNET_CONNECTION_VALUES[0]
+ )
+ )
+ }
+ } else {
+ null
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index 051e88f..02473f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -16,9 +16,11 @@
package com.android.systemui.statusbar.pipeline.mobile.data
+import android.content.Intent
import android.telephony.ServiceState
import android.telephony.SignalStrength
import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.dagger.SysUISingleton
@@ -162,6 +164,28 @@
fun logOnSubscriptionsChanged() {
buffer.log(TAG, LogLevel.INFO, {}, { "onSubscriptionsChanged" })
}
+
+ fun logServiceProvidersUpdatedBroadcast(intent: Intent) {
+ val showSpn = intent.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false)
+ val spn = intent.getStringExtra(TelephonyManager.EXTRA_DATA_SPN)
+ val showPlmn = intent.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)
+ val plmn = intent.getStringExtra(TelephonyManager.EXTRA_PLMN)
+
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ bool1 = showSpn
+ str1 = spn
+ bool2 = showPlmn
+ str2 = plmn
+ },
+ {
+ "Intent: ACTION_SERVICE_PROVIDERS_UPDATED." +
+ " showSpn=$bool1 spn=$str1 showPlmn=$bool2 plmn=$str2"
+ }
+ )
+ }
}
private const val TAG = "MobileInputLog"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 1f1ac92..cd68621 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -16,12 +16,16 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+import android.annotation.SuppressLint
+import android.content.BroadcastReceiver
+import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
import android.telephony.CellSignalStrengthCdma
import android.telephony.ServiceState
import android.telephony.SignalStrength
+import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.telephony.TelephonyCallback
import android.telephony.TelephonyDisplayInfo
@@ -34,6 +38,7 @@
import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID
import com.android.settingslib.Utils
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.table.TableLogBuffer
@@ -81,6 +86,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
class MobileConnectionRepositoryImpl(
override val subId: Int,
+ private val context: Context,
subscriptionModel: StateFlow<SubscriptionModel?>,
defaultNetworkName: NetworkNameModel,
networkNameSeparator: String,
@@ -323,16 +329,35 @@
}
.stateIn(scope, SharingStarted.WhileSubscribed(), telephonyManager.simCarrierId)
+ /** BroadcastDispatcher does not handle sticky broadcasts, so we can't use it here */
+ @SuppressLint("RegisterReceiverViaContext")
override val networkName: StateFlow<NetworkNameModel> =
- broadcastDispatcher
- .broadcastFlow(
- filter = IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED),
- map = { intent, _ -> intent },
- )
- .filter { intent ->
- intent.getIntExtra(EXTRA_SUBSCRIPTION_ID, INVALID_SUBSCRIPTION_ID) == subId
+ conflatedCallbackFlow {
+ val receiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (
+ intent.getIntExtra(
+ EXTRA_SUBSCRIPTION_INDEX,
+ INVALID_SUBSCRIPTION_ID
+ ) == subId
+ ) {
+ logger.logServiceProvidersUpdatedBroadcast(intent)
+ trySend(
+ intent.toNetworkNameModel(networkNameSeparator)
+ ?: defaultNetworkName
+ )
+ }
+ }
+ }
+
+ context.registerReceiver(
+ receiver,
+ IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED)
+ )
+
+ awaitClose { context.unregisterReceiver(receiver) }
}
- .map { intent -> intent.toNetworkNameModel(networkNameSeparator) ?: defaultNetworkName }
.stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName)
override val dataEnabled = run {
@@ -349,6 +374,7 @@
class Factory
@Inject
constructor(
+ private val context: Context,
private val broadcastDispatcher: BroadcastDispatcher,
private val telephonyManager: TelephonyManager,
private val logger: MobileInputLogger,
@@ -366,6 +392,7 @@
): MobileConnectionRepository {
return MobileConnectionRepositoryImpl(
subId,
+ context,
subscriptionModel,
defaultNetworkName,
networkNameSeparator,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 4cfde5b..4bf297c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.content.Context
-import android.telephony.CarrierConfigManager
import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.graph.SignalDrawable
import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl
import com.android.systemui.dagger.qualifiers.Application
@@ -31,6 +31,7 @@
import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.DefaultIcon
import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.OverriddenIcon
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -60,24 +61,17 @@
*/
val isDataConnected: StateFlow<Boolean>
- /** Only true if mobile is the default transport but is not validated, otherwise false */
- val isDefaultConnectionFailed: StateFlow<Boolean>
-
/** True if we consider this connection to be in service, i.e. can make calls */
val isInService: StateFlow<Boolean>
- // TODO(b/256839546): clarify naming of default vs active
- /** True if we want to consider the data connection enabled */
- val isDefaultDataEnabled: StateFlow<Boolean>
-
/** Observable for the data enabled state of this connection */
val isDataEnabled: StateFlow<Boolean>
/** True if the RAT icon should always be displayed and false otherwise. */
val alwaysShowDataRatIcon: StateFlow<Boolean>
- /** True if the CDMA level should be preferred over the primary level. */
- val alwaysUseCdmaLevel: StateFlow<Boolean>
+ /** Canonical representation of the current mobile signal strength as a triangle. */
+ val signalLevelIcon: StateFlow<SignalIconModel>
/** Observable for RAT type (network type) indicator */
val networkTypeIconGroup: StateFlow<NetworkTypeIconModel>
@@ -108,9 +102,6 @@
/** True if there is only one active subscription. */
val isSingleCarrier: StateFlow<Boolean>
- /** True if this line of service is emergency-only */
- val isEmergencyOnly: StateFlow<Boolean>
-
/**
* True if this connection is considered roaming. The roaming bit can come from [ServiceState],
* or directly from the telephony manager's CDMA ERI number value. Note that we don't consider a
@@ -118,12 +109,6 @@
*/
val isRoaming: StateFlow<Boolean>
- /** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
- val level: StateFlow<Int>
-
- /** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
- val numberOfLevels: StateFlow<Int>
-
/** See [MobileIconsInteractor.isForceHidden]. */
val isForceHidden: Flow<Boolean>
@@ -141,12 +126,12 @@
@Application scope: CoroutineScope,
defaultSubscriptionHasDataEnabled: StateFlow<Boolean>,
override val alwaysShowDataRatIcon: StateFlow<Boolean>,
- override val alwaysUseCdmaLevel: StateFlow<Boolean>,
+ alwaysUseCdmaLevel: StateFlow<Boolean>,
override val isSingleCarrier: StateFlow<Boolean>,
override val mobileIsDefault: StateFlow<Boolean>,
defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>,
defaultMobileIconGroup: StateFlow<MobileIconGroup>,
- override val isDefaultConnectionFailed: StateFlow<Boolean>,
+ isDefaultConnectionFailed: StateFlow<Boolean>,
override val isForceHidden: Flow<Boolean>,
connectionRepository: MobileConnectionRepository,
private val context: Context,
@@ -170,8 +155,6 @@
.distinctUntilChanged()
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
- override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled
-
override val networkName =
combine(connectionRepository.operatorAlphaShort, connectionRepository.networkName) {
operatorAlphaShort,
@@ -255,8 +238,6 @@
DefaultIcon(defaultMobileIconGroup.value),
)
- override val isEmergencyOnly = connectionRepository.isEmergencyOnly
-
override val isRoaming: StateFlow<Boolean> =
combine(
connectionRepository.carrierNetworkChangeActive,
@@ -274,7 +255,7 @@
}
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
- override val level: StateFlow<Int> =
+ private val level: StateFlow<Int> =
combine(
connectionRepository.isGsm,
connectionRepository.primaryLevel,
@@ -290,7 +271,7 @@
}
.stateIn(scope, SharingStarted.WhileSubscribed(), 0)
- override val numberOfLevels: StateFlow<Int> =
+ private val numberOfLevels: StateFlow<Int> =
connectionRepository.numberOfLevels.stateIn(
scope,
SharingStarted.WhileSubscribed(),
@@ -305,4 +286,54 @@
override val isInService = connectionRepository.isInService
override val isAllowedDuringAirplaneMode = connectionRepository.isAllowedDuringAirplaneMode
+
+ /** Whether or not to show the error state of [SignalDrawable] */
+ private val showExclamationMark: StateFlow<Boolean> =
+ combine(
+ defaultSubscriptionHasDataEnabled,
+ isDefaultConnectionFailed,
+ isInService,
+ ) { isDefaultDataEnabled, isDefaultConnectionFailed, isInService ->
+ !isDefaultDataEnabled || isDefaultConnectionFailed || !isInService
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), true)
+
+ private val shownLevel: StateFlow<Int> =
+ combine(
+ level,
+ isInService,
+ ) { level, isInService ->
+ if (isInService) level else 0
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
+
+ override val signalLevelIcon: StateFlow<SignalIconModel> = run {
+ val initial =
+ SignalIconModel(
+ level = shownLevel.value,
+ numberOfLevels = numberOfLevels.value,
+ showExclamationMark = showExclamationMark.value,
+ carrierNetworkChange = carrierNetworkChangeActive.value,
+ )
+ combine(
+ shownLevel,
+ numberOfLevels,
+ showExclamationMark,
+ carrierNetworkChangeActive,
+ ) { shownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange ->
+ SignalIconModel(
+ shownLevel,
+ numberOfLevels,
+ showExclamationMark,
+ carrierNetworkChange,
+ )
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "icon",
+ initialValue = initial,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index d08808b..62150e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -33,6 +33,7 @@
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.util.CarrierConfigTracker
+import java.lang.ref.WeakReference
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -70,6 +71,12 @@
/** True if the active mobile data subscription has data enabled */
val activeDataConnectionHasDataEnabled: StateFlow<Boolean>
+ /**
+ * Flow providing a reference to the Interactor for the active data subId. This represents the
+ * [MobileConnectionInteractor] responsible for the active data connection, if any.
+ */
+ val activeDataIconInteractor: StateFlow<MobileIconInteractor?>
+
/** True if the RAT icon should always be displayed and false otherwise. */
val alwaysShowDataRatIcon: StateFlow<Boolean>
@@ -96,9 +103,9 @@
/**
* Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given
- * subId. Will throw if the ID is invalid
+ * subId.
*/
- fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor
+ fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor
}
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@@ -116,6 +123,9 @@
private val context: Context,
) : MobileIconsInteractor {
+ // Weak reference lookup for created interactors
+ private val reuseCache = mutableMapOf<Int, WeakReference<MobileIconInteractor>>()
+
override val mobileIsDefault =
combine(
mobileConnectionsRepo.mobileIsDefault,
@@ -138,6 +148,17 @@
.flatMapLatest { it?.dataEnabled ?: flowOf(false) }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ override val activeDataIconInteractor: StateFlow<MobileIconInteractor?> =
+ mobileConnectionsRepo.activeMobileDataSubscriptionId
+ .mapLatest {
+ if (it != null) {
+ getMobileConnectionInteractorForSubId(it)
+ } else {
+ null
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
private val unfilteredSubscriptions: Flow<List<SubscriptionModel>> =
mobileConnectionsRepo.subscriptions
@@ -306,21 +327,25 @@
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
/** Vends out new [MobileIconInteractor] for a particular subId */
- override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
+ override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
+ reuseCache[subId]?.get() ?: createMobileConnectionInteractorForSubId(subId)
+
+ private fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
MobileIconInteractorImpl(
- scope,
- activeDataConnectionHasDataEnabled,
- alwaysShowDataRatIcon,
- alwaysUseCdmaLevel,
- isSingleCarrier,
- mobileIsDefault,
- defaultMobileIconMapping,
- defaultMobileIconGroup,
- isDefaultConnectionFailed,
- isForceHidden,
- mobileConnectionsRepo.getRepoForSubId(subId),
- context,
- )
+ scope,
+ activeDataConnectionHasDataEnabled,
+ alwaysShowDataRatIcon,
+ alwaysUseCdmaLevel,
+ isSingleCarrier,
+ mobileIsDefault,
+ defaultMobileIconMapping,
+ defaultMobileIconGroup,
+ isDefaultConnectionFailed,
+ isForceHidden,
+ mobileConnectionsRepo.getRepoForSubId(subId),
+ context,
+ )
+ .also { reuseCache[subId] = WeakReference(it) }
companion object {
private const val LOGGING_PREFIX = "Intr"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt
index 6de3f85..e58f081 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.mobile.ui.model
+package com.android.systemui.statusbar.pipeline.mobile.domain.model
import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.log.table.Diffable
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
index cffc833..a1a5370 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
@@ -22,8 +22,8 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.statusbar.pipeline.dagger.VerboseMobileViewLog
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger.Companion.getIdForLogging
-import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
import javax.inject.Inject
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 275cfc5..dfabeea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -17,14 +17,13 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
-import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
-import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.CoroutineScope
@@ -76,26 +75,6 @@
constants: ConnectivityConstants,
scope: CoroutineScope,
) : MobileIconViewModelCommon {
- /** Whether or not to show the error state of [SignalDrawable] */
- private val showExclamationMark: StateFlow<Boolean> =
- combine(
- iconInteractor.isDefaultDataEnabled,
- iconInteractor.isDefaultConnectionFailed,
- iconInteractor.isInService,
- ) { isDefaultDataEnabled, isDefaultConnectionFailed, isInService ->
- !isDefaultDataEnabled || isDefaultConnectionFailed || !isInService
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), true)
-
- private val shownLevel: StateFlow<Int> =
- combine(
- iconInteractor.level,
- iconInteractor.isInService,
- ) { level, isInService ->
- if (isInService) level else 0
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
-
override val isVisible: StateFlow<Boolean> =
if (!constants.hasDataCapabilities) {
flowOf(false)
@@ -123,40 +102,12 @@
)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
- override val icon: Flow<SignalIconModel> = run {
- val initial =
- SignalIconModel(
- level = shownLevel.value,
- numberOfLevels = iconInteractor.numberOfLevels.value,
- showExclamationMark = showExclamationMark.value,
- carrierNetworkChange = iconInteractor.carrierNetworkChangeActive.value,
- )
- combine(
- shownLevel,
- iconInteractor.numberOfLevels,
- showExclamationMark,
- iconInteractor.carrierNetworkChangeActive,
- ) { shownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange ->
- SignalIconModel(
- shownLevel,
- numberOfLevels,
- showExclamationMark,
- carrierNetworkChange,
- )
- }
- .distinctUntilChanged()
- .logDiffsForTable(
- iconInteractor.tableLogBuffer,
- columnPrefix = "icon",
- initialValue = initial,
- )
- .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
- }
+ override val icon: Flow<SignalIconModel> = iconInteractor.signalLevelIcon
override val contentDescription: Flow<ContentDescription> = run {
val initial = ContentDescription.Resource(PHONE_SIGNAL_STRENGTH[0])
- shownLevel
- .map { ContentDescription.Resource(PHONE_SIGNAL_STRENGTH[it]) }
+ iconInteractor.signalLevelIcon
+ .map { ContentDescription.Resource(PHONE_SIGNAL_STRENGTH[it.level]) }
.stateIn(scope, SharingStarted.WhileSubscribed(), initial)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 216afb9..a4ec3a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -101,7 +101,7 @@
val common = commonViewModelForSub(subId)
return LocationBasedMobileViewModel.viewModelForLocation(
common,
- mobileIconInteractorForSub(subId),
+ interactor.getMobileConnectionInteractorForSubId(subId),
verboseLogger,
location,
scope,
@@ -112,7 +112,7 @@
return mobileIconSubIdCache[subId]
?: MobileIconViewModel(
subId,
- mobileIconInteractorForSub(subId),
+ interactor.getMobileConnectionInteractorForSubId(subId),
airplaneModeInteractor,
constants,
scope,
@@ -120,14 +120,6 @@
.also { mobileIconSubIdCache[subId] = it }
}
- @VisibleForTesting
- fun mobileIconInteractorForSub(subId: Int): MobileIconInteractor {
- return mobileIconInteractorSubIdCache[subId]
- ?: interactor.createMobileConnectionInteractorForSubId(subId).also {
- mobileIconInteractorSubIdCache[subId] = it
- }
- }
-
private fun invalidateCaches(subIds: List<Int>) {
val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) }
subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/InternetTileBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/InternetTileBinder.kt
new file mode 100644
index 0000000..189dc40
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/InternetTileBinder.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared.ui.binder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.coroutineScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileModel
+import java.util.function.Consumer
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+/**
+ * Binds an [InternetTileModel] flow to a consumer for the internet tile to apply to its qs state
+ */
+object InternetTileBinder {
+ fun bind(
+ lifecycle: Lifecycle,
+ tileModelFlow: StateFlow<InternetTileModel>,
+ consumer: Consumer<InternetTileModel>
+ ) {
+ lifecycle.coroutineScope.launch {
+ lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ tileModelFlow.collect { consumer.accept(it) }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt
new file mode 100644
index 0000000..327dd8d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared.ui.model
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.service.quicksettings.Tile
+import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.shared.model.Text.Companion.loadText
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.tileimpl.QSTileImpl
+
+/** Model describing the state that the QS Internet tile should be in. */
+sealed interface InternetTileModel {
+ val secondaryTitle: String?
+ val secondaryLabel: Text?
+ val iconId: Int?
+ val icon: QSTile.Icon?
+
+ fun applyTo(state: QSTile.SignalState, context: Context) {
+ if (secondaryLabel != null) {
+ state.secondaryLabel = secondaryLabel.loadText(context)
+ } else {
+ state.secondaryLabel = secondaryTitle
+ }
+
+ // inout indicators are unused
+ state.activityIn = false
+ state.activityOut = false
+
+ // To support both SignalDrawable and other icons, give priority to icons over IDs
+ if (icon != null) {
+ state.icon = icon
+ } else if (iconId != null) {
+ state.icon = QSTileImpl.ResourceIcon.get(iconId!!)
+ }
+
+ state.state =
+ if (this is Active) {
+ Tile.STATE_ACTIVE
+ } else {
+ Tile.STATE_INACTIVE
+ }
+ }
+
+ data class Active(
+ override val secondaryTitle: String? = null,
+ override val secondaryLabel: Text? = null,
+ override val iconId: Int? = null,
+ override val icon: QSTile.Icon? = null,
+ ) : InternetTileModel
+
+ data class Inactive(
+ override val secondaryTitle: String? = null,
+ override val secondaryLabel: Text? = null,
+ override val iconId: Int? = null,
+ override val icon: QSTile.Icon? = null,
+ ) : InternetTileModel
+}
+
+/**
+ * [QSTile.Icon]-compatible container class for us to marshal the compacted [SignalDrawable] state
+ * across to the internet tile.
+ */
+data class SignalIcon(val state: Int) : QSTile.Icon() {
+
+ override fun getDrawable(context: Context): Drawable {
+ val d = SignalDrawable(context)
+ d.setLevel(state)
+ return d
+ }
+
+ override fun toString(): String {
+ return String.format("SignalIcon[mState=0x%08x]", state)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
new file mode 100644
index 0000000..120ba4e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
+import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileModel
+import com.android.systemui.statusbar.pipeline.shared.ui.model.SignalIcon
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * View model for the quick settings [InternetTile]. This model exposes mainly a single flow of
+ * InternetTileModel objects, so that updating the tile is as simple as collecting on this state
+ * flow and then calling [QSTileImpl.refreshState]
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class InternetTileViewModel
+@Inject
+constructor(
+ airplaneModeRepository: AirplaneModeRepository,
+ connectivityRepository: ConnectivityRepository,
+ ethernetInteractor: EthernetInteractor,
+ mobileIconsInteractor: MobileIconsInteractor,
+ wifiInteractor: WifiInteractor,
+ private val context: Context,
+ @Application scope: CoroutineScope,
+) {
+ // Three symmetrical Flows that can be switched upon based on the value of
+ // [DefaultConnectionModel]
+ private val wifiIconFlow: Flow<InternetTileModel> =
+ wifiInteractor.wifiNetwork.flatMapLatest {
+ val wifiIcon = WifiIcon.fromModel(it, context)
+ if (it is WifiNetworkModel.Active && wifiIcon is WifiIcon.Visible) {
+ flowOf(
+ InternetTileModel.Active(
+ secondaryTitle = removeDoubleQuotes(it.ssid),
+ icon = ResourceIcon.get(wifiIcon.icon.res)
+ )
+ )
+ } else {
+ notConnectedFlow
+ }
+ }
+
+ private val mobileDataContentName: Flow<CharSequence?> =
+ mobileIconsInteractor.activeDataIconInteractor.flatMapLatest {
+ if (it == null) {
+ flowOf(null)
+ } else {
+ combine(it.isRoaming, it.networkTypeIconGroup) { isRoaming, networkTypeIconGroup ->
+ val cd = loadString(networkTypeIconGroup.contentDescription)
+ if (isRoaming) {
+ val roaming = context.getString(R.string.data_connection_roaming)
+ if (cd != null) {
+ context.getString(R.string.mobile_data_text_format, roaming, cd)
+ } else {
+ roaming
+ }
+ } else {
+ cd
+ }
+ }
+ }
+ }
+
+ private val mobileIconFlow: Flow<InternetTileModel> =
+ mobileIconsInteractor.activeDataIconInteractor.flatMapLatest {
+ if (it == null) {
+ notConnectedFlow
+ } else {
+ combine(
+ it.networkName,
+ it.signalLevelIcon,
+ mobileDataContentName,
+ ) { networkNameModel, signalIcon, dataContentDescription ->
+ InternetTileModel.Active(
+ secondaryTitle =
+ mobileDataContentConcat(networkNameModel.name, dataContentDescription),
+ icon = SignalIcon(signalIcon.toSignalDrawableState()),
+ )
+ }
+ }
+ }
+
+ private fun mobileDataContentConcat(
+ networkName: String?,
+ dataContentDescription: CharSequence?
+ ): String {
+ if (dataContentDescription == null) {
+ return networkName ?: ""
+ }
+ if (networkName == null) {
+ return dataContentDescription.toString()
+ }
+
+ return context.getString(
+ R.string.mobile_carrier_text_format,
+ networkName,
+ dataContentDescription
+ )
+ }
+
+ private fun loadString(resId: Int): String? =
+ if (resId > 0) {
+ context.getString(resId)
+ } else {
+ null
+ }
+
+ private val ethernetIconFlow: Flow<InternetTileModel> =
+ ethernetInteractor.icon.flatMapLatest {
+ if (it == null) {
+ notConnectedFlow
+ } else {
+ flowOf(
+ InternetTileModel.Active(
+ secondaryTitle = it.contentDescription.toString(),
+ iconId = it.res
+ )
+ )
+ }
+ }
+
+ private val notConnectedFlow: StateFlow<InternetTileModel> =
+ combine(
+ wifiInteractor.areNetworksAvailable,
+ airplaneModeRepository.isAirplaneMode,
+ ) { networksAvailable, isAirplaneMode ->
+ when {
+ isAirplaneMode -> {
+ InternetTileModel.Inactive(
+ secondaryTitle = context.getString(R.string.status_bar_airplane),
+ icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable)
+ )
+ }
+ networksAvailable -> {
+ InternetTileModel.Inactive(
+ secondaryTitle =
+ context.getString(R.string.quick_settings_networks_available),
+ iconId = R.drawable.ic_qs_no_internet_available,
+ )
+ }
+ else -> {
+ NOT_CONNECTED_NETWORKS_UNAVAILABLE
+ }
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), NOT_CONNECTED_NETWORKS_UNAVAILABLE)
+
+ /**
+ * Strict ordering of which repo is sending its data to the internet tile. Swaps between each of
+ * the interim providers (wifi, mobile, ethernet, or not-connected)
+ */
+ private val activeModelProvider: Flow<InternetTileModel> =
+ connectivityRepository.defaultConnections.flatMapLatest {
+ when {
+ it.ethernet.isDefault -> ethernetIconFlow
+ it.mobile.isDefault || it.carrierMerged.isDefault -> mobileIconFlow
+ it.wifi.isDefault -> wifiIconFlow
+ else -> notConnectedFlow
+ }
+ }
+
+ /** Consumable flow describing the correct state for the InternetTile */
+ val tileModel: StateFlow<InternetTileModel> =
+ activeModelProvider.stateIn(scope, SharingStarted.WhileSubscribed(), notConnectedFlow.value)
+
+ companion object {
+ val NOT_CONNECTED_NETWORKS_UNAVAILABLE =
+ InternetTileModel.Inactive(
+ secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable),
+ iconId = R.drawable.ic_qs_no_internet_unavailable,
+ )
+
+ private fun removeDoubleQuotes(string: String?): String? {
+ if (string == null) return null
+ val length = string.length
+ return if (length > 1 && string[0] == '"' && string[length - 1] == '"') {
+ string.substring(1, length - 1)
+ } else string
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index b5b99a7..b22e09e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -19,6 +19,7 @@
import com.android.systemui.CoreStartable
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import kotlinx.coroutines.flow.StateFlow
/** Provides data related to the wifi state. */
@@ -45,6 +46,12 @@
val wifiActivity: StateFlow<DataActivityModel>
/**
+ * The list of known wifi networks, per [WifiManager.scanResults]. This list is passively
+ * updated and does not trigger a scan.
+ */
+ val wifiScanResults: StateFlow<List<WifiScanEntry>>
+
+ /**
* Returns true if the device is currently connected to a wifi network with a valid SSID and
* false otherwise.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
index 80091ac..ca042e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
@@ -27,6 +27,7 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -124,4 +125,9 @@
activeRepo
.flatMapLatest { it.wifiActivity }
.stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.wifiActivity.value)
+
+ override val wifiScanResults: StateFlow<List<WifiScanEntry>> =
+ activeRepo
+ .flatMapLatest { it.wifiScanResults }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.wifiScanResults.value)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
index 4b19c3a..152d181 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
@@ -22,6 +22,7 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -55,6 +56,10 @@
MutableStateFlow(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
override val wifiActivity: StateFlow<DataActivityModel> = _wifiActivity
+ private val _wifiScanResults: MutableStateFlow<List<WifiScanEntry>> =
+ MutableStateFlow(emptyList())
+ override val wifiScanResults: StateFlow<List<WifiScanEntry>> = _wifiScanResults
+
fun startProcessingCommands() {
demoCommandJob =
scope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
index 36c46a9..cfdbe4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
@@ -21,6 +21,7 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -49,6 +50,9 @@
override val wifiActivity: StateFlow<DataActivityModel> =
MutableStateFlow(ACTIVITY).asStateFlow()
+ override val wifiScanResults: StateFlow<List<WifiScanEntry>> =
+ MutableStateFlow<List<WifiScanEntry>>(emptyList()).asStateFlow()
+
companion object {
private val NETWORK = WifiNetworkModel.Unavailable
private val ACTIVITY = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt
index f1b98b3..67dd32f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt
@@ -16,15 +16,21 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
+import android.annotation.SuppressLint
+import android.net.wifi.ScanResult
import android.net.wifi.WifiManager
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import java.util.concurrent.Executor
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -64,6 +70,34 @@
)
}
+ /**
+ * Creates a flow that listens for new [ScanResult]s from [WifiManager]. Does not request a scan
+ */
+ fun createNetworkScanFlow(
+ wifiManager: WifiManager,
+ scope: CoroutineScope,
+ @Background dispatcher: CoroutineDispatcher,
+ inputLogger: () -> Unit,
+ ): StateFlow<List<WifiScanEntry>> {
+ return conflatedCallbackFlow {
+ val callback =
+ object : WifiManager.ScanResultsCallback() {
+ @SuppressLint("MissingPermission")
+ override fun onScanResultsAvailable() {
+ inputLogger.invoke()
+ trySend(wifiManager.scanResults.toModel())
+ }
+ }
+
+ wifiManager.registerScanResultsCallback(dispatcher.asExecutor(), callback)
+
+ awaitClose { wifiManager.unregisterScanResultsCallback(callback) }
+ }
+ .stateIn(scope, SharingStarted.Eagerly, emptyList())
+ }
+
+ private fun List<ScanResult>.toModel(): List<WifiScanEntry> = map { WifiScanEntry(it.SSID) }
+
// TODO(b/292534484): This print should only be done in [MessagePrinter] part of the log buffer.
private fun prettyPrintActivity(activity: Int): String {
return when (activity) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index 7c7b58d..59ef884 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -48,6 +48,7 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -231,6 +232,14 @@
logger::logActivity,
)
+ override val wifiScanResults: StateFlow<List<WifiScanEntry>> =
+ WifiRepositoryHelper.createNetworkScanFlow(
+ wifiManager,
+ scope,
+ bgDispatcher,
+ logger::logScanResults
+ )
+
companion object {
// Start out with no known wifi network.
// Note: [WifiStatusTracker] (the old implementation of connectivity logic) does do an
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
index d4f40dd..9b404f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
@@ -23,6 +23,7 @@
import androidx.lifecycle.LifecycleRegistry
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -42,6 +43,7 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_STATE_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive.toHotspotDeviceType
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import com.android.wifitrackerlib.HotspotNetworkEntry
import com.android.wifitrackerlib.MergedCarrierEntry
import com.android.wifitrackerlib.WifiEntry
@@ -51,6 +53,7 @@
import com.android.wifitrackerlib.WifiPickerTracker
import java.util.concurrent.Executor
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
@@ -73,6 +76,7 @@
featureFlags: FeatureFlags,
@Application private val scope: CoroutineScope,
@Main private val mainExecutor: Executor,
+ @Background private val bgDispatcher: CoroutineDispatcher,
private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
private val wifiManager: WifiManager,
@WifiTrackerLibInputLog private val inputLogger: LogBuffer,
@@ -300,6 +304,14 @@
this::logActivity,
)
+ override val wifiScanResults: StateFlow<List<WifiScanEntry>> =
+ WifiRepositoryHelper.createNetworkScanFlow(
+ wifiManager,
+ scope,
+ bgDispatcher,
+ this::logScanResults,
+ )
+
private fun logOnWifiEntriesChanged(connectedEntry: WifiEntry?) {
inputLogger.log(
TAG,
@@ -322,6 +334,9 @@
inputLogger.log(TAG, LogLevel.DEBUG, { str1 = activity }, { "onActivityChanged: $str1" })
}
+ private fun logScanResults() =
+ inputLogger.log(TAG, LogLevel.DEBUG, {}, { "onScanResultsAvailable" })
+
/**
* Data class storing all the information fetched from [WifiPickerTracker].
*
@@ -345,6 +360,7 @@
private val featureFlags: FeatureFlags,
@Application private val scope: CoroutineScope,
@Main private val mainExecutor: Executor,
+ @Background private val bgDispatcher: CoroutineDispatcher,
private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
@WifiTrackerLibInputLog private val inputLogger: LogBuffer,
@WifiTrackerLibTableLog private val wifiTrackerLibTableLogBuffer: TableLogBuffer,
@@ -354,6 +370,7 @@
featureFlags,
scope,
mainExecutor,
+ bgDispatcher,
wifiPickerTrackerFactory,
wifiManager,
inputLogger,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
index 1a41abf..110e339 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
@@ -17,15 +17,21 @@
package com.android.systemui.statusbar.pipeline.wifi.domain.interactor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/**
* The business logic layer for the wifi icon.
@@ -54,6 +60,9 @@
/** True if we're configured to force-hide the wifi icon and false otherwise. */
val isForceHidden: Flow<Boolean>
+
+ /** True if there are networks available other than the currently-connected one */
+ val areNetworksAvailable: StateFlow<Boolean>
}
@SysUISingleton
@@ -62,6 +71,7 @@
constructor(
connectivityRepository: ConnectivityRepository,
wifiRepository: WifiRepository,
+ @Application scope: CoroutineScope,
) : WifiInteractor {
override val ssid: Flow<String?> =
@@ -91,4 +101,26 @@
override val isForceHidden: Flow<Boolean> =
connectivityRepository.forceHiddenSlots.map { it.contains(ConnectivitySlot.WIFI) }
+
+ override val areNetworksAvailable: StateFlow<Boolean> =
+ combine(
+ wifiNetwork,
+ wifiRepository.wifiScanResults,
+ ) { currentNetwork, scanResults ->
+ // We consider networks to be available if the scan results list contains networks
+ // other than the one that is currently connected
+ if (scanResults.isEmpty()) {
+ false
+ } else if (currentNetwork !is WifiNetworkModel.Active) {
+ true
+ } else {
+ anyNonMatchingNetworkExists(currentNetwork, scanResults)
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ private fun anyNonMatchingNetworkExists(
+ currentNetwork: WifiNetworkModel.Active,
+ availableNetworks: List<WifiScanEntry>
+ ): Boolean = availableNetworks.firstOrNull { it.ssid != currentNetwork.ssid } != null
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
index f244376..b76bb51 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
@@ -59,6 +59,8 @@
fun logActivity(activity: String) {
buffer.log(TAG, LogLevel.DEBUG, { str1 = activity }, { "Activity: $str1" })
}
+
+ fun logScanResults() = buffer.log(TAG, LogLevel.DEBUG, {}, { "onScanResultsAvailable" })
}
private const val TAG = "WifiInputLog"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiScanEntry.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiScanEntry.kt
new file mode 100644
index 0000000..d4a5a0e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiScanEntry.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.shared.model
+
+/**
+ * Represents a single entry in the scan results callback. Use the [ssid] field to check against
+ * other networks
+ */
+data class WifiScanEntry(val ssid: String)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
index 094bcf9..8156500 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
@@ -17,10 +17,18 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.model
import android.annotation.DrawableRes
+import android.content.Context
+import androidx.annotation.StringRes
+import androidx.annotation.VisibleForTesting
+import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH
+import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
+import com.android.systemui.R
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.log.table.Diffable
import com.android.systemui.log.table.TableRowLogger
+import com.android.systemui.statusbar.connectivity.WifiIcons
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
/** Represents the various states of the wifi icon. */
sealed interface WifiIcon : Diffable<WifiIcon> {
@@ -34,7 +42,7 @@
* description.
*/
class Visible(
- @DrawableRes res: Int,
+ @DrawableRes val res: Int,
val contentDescription: ContentDescription.Loaded,
) : WifiIcon {
val icon = Icon.Resource(res, contentDescription)
@@ -51,6 +59,46 @@
override fun logFull(row: TableRowLogger) {
row.logChange(COL_ICON, toString())
}
+
+ companion object {
+ @StringRes
+ @VisibleForTesting
+ internal val NO_INTERNET = R.string.data_connection_no_internet
+
+ /** Mapping from a [WifiNetworkModel] to the appropriate [WifiIcon] */
+ fun fromModel(model: WifiNetworkModel, context: Context): WifiIcon =
+ when (model) {
+ is WifiNetworkModel.Unavailable -> Hidden
+ is WifiNetworkModel.Invalid -> Hidden
+ is WifiNetworkModel.CarrierMerged -> Hidden
+ is WifiNetworkModel.Inactive ->
+ Visible(
+ res = WifiIcons.WIFI_NO_NETWORK,
+ ContentDescription.Loaded(
+ "${context.getString(WIFI_NO_CONNECTION)},${context.getString(
+ NO_INTERNET
+ )}"
+ )
+ )
+ is WifiNetworkModel.Active -> {
+ val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[model.level])
+ when {
+ model.isValidated ->
+ Visible(
+ WifiIcons.WIFI_FULL_ICONS[model.level],
+ ContentDescription.Loaded(levelDesc),
+ )
+ else ->
+ Visible(
+ WifiIcons.WIFI_NO_INTERNET_ICONS[model.level],
+ ContentDescription.Loaded(
+ "$levelDesc,${context.getString(NO_INTERNET)}"
+ ),
+ )
+ }
+ }
+ }
+ }
}
private const val COL_ICON = "icon"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index d9c2144..27ac7b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -17,19 +17,10 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
import android.content.Context
-import androidx.annotation.StringRes
-import androidx.annotation.VisibleForTesting
-import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH
-import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
-import com.android.systemui.R
-import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
-import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
-import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
-import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule.Companion.FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON
import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
@@ -77,35 +68,7 @@
) : WifiViewModelCommon {
/** Returns the icon to use based on the given network. */
private fun WifiNetworkModel.icon(): WifiIcon {
- return when (this) {
- is WifiNetworkModel.Unavailable -> WifiIcon.Hidden
- is WifiNetworkModel.Invalid -> WifiIcon.Hidden
- is WifiNetworkModel.CarrierMerged -> WifiIcon.Hidden
- is WifiNetworkModel.Inactive ->
- WifiIcon.Visible(
- res = WIFI_NO_NETWORK,
- ContentDescription.Loaded(
- "${context.getString(WIFI_NO_CONNECTION)},${context.getString(NO_INTERNET)}"
- )
- )
- is WifiNetworkModel.Active -> {
- val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level])
- when {
- this.isValidated ->
- WifiIcon.Visible(
- WIFI_FULL_ICONS[this.level],
- ContentDescription.Loaded(levelDesc),
- )
- else ->
- WifiIcon.Visible(
- WIFI_NO_INTERNET_ICONS[this.level],
- ContentDescription.Loaded(
- "$levelDesc,${context.getString(NO_INTERNET)}"
- ),
- )
- }
- }
- }
+ return WifiIcon.fromModel(this, context)
}
override val wifiIcon: StateFlow<WifiIcon> =
@@ -186,10 +149,4 @@
airplaneModeViewModel.isAirplaneModeIconVisible
override val isSignalSpacerVisible: Flow<Boolean> = shouldShowSignalSpacerProvider.get()
-
- companion object {
- @StringRes
- @VisibleForTesting
- internal val NO_INTERNET = R.string.data_connection_no_internet
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
index 1212585..feef029 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
@@ -16,9 +16,15 @@
package com.android.systemui.statusbar.policy;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
import android.view.View;
import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.util.Compile;
/**
* A class of utility static methods for heads up notifications.
@@ -26,12 +32,18 @@
public final class HeadsUpUtil {
private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag;
+ private static final String LOG_TAG = "HeadsUpUtil";
+ private static final boolean LOG_DEBUG = Compile.IS_DEBUG && Log.isLoggable(LOG_TAG, Log.DEBUG);
+
/**
* Set the given view as clicked or not-clicked.
* @param view The view to be set the flag to.
* @param clicked True to set as clicked. False to not-clicked.
*/
public static void setNeedsHeadsUpDisappearAnimationAfterClick(View view, boolean clicked) {
+ if (LOG_DEBUG) {
+ logTagClickedNotificationChanged(view, clicked);
+ }
view.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null);
}
@@ -44,4 +56,36 @@
Boolean clicked = (Boolean) view.getTag(TAG_CLICKED_NOTIFICATION);
return clicked != null && clicked;
}
+
+ private static void logTagClickedNotificationChanged(@Nullable View view, boolean isClicked) {
+ if (view == null) {
+ return;
+ }
+
+ final boolean wasClicked = isClickedHeadsUpNotification(view);
+ if (isClicked == wasClicked) {
+ return;
+ }
+
+ Log.d(LOG_TAG, getViewKey(view) + ": TAG_CLICKED_NOTIFICATION set to " + isClicked);
+ }
+
+ private static @NonNull String getViewKey(@NonNull View view) {
+ if (!(view instanceof ExpandableNotificationRow)) {
+ return "(not a row)";
+ }
+
+ final ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ final NotificationEntry entry = row.getEntry();
+ if (entry == null) {
+ return "(null entry)";
+ }
+
+ final String key = entry.getKey();
+ if (key == null) {
+ return "(null key)";
+ }
+
+ return key;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index 3a94730..e1b608f 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.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 1acd676..93048a5 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.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 efe1955..2b90e7c 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.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.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 80fd721..61acacd 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.MockitoAnnotations
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class KeyguardPinViewControllerTest : SysuiTestCase() {
@@ -175,7 +177,8 @@
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 80172a1..6bff4ce 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.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.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.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.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 f6450a4..3e330d65 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 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.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 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 64e1458..68c2f59 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.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.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 291dda25..4db5f35 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 @@
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.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 626faa6..47ff3b9 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 @@
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.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 b9e3f14..09ff546 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 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 @@
mFeatureFlags.set(MIGRATE_LOCK_ICON, false);
mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false);
mUnderTest = new LockIconViewController(
- mLockIconView,
mStatusBarStateController,
mKeyguardUpdateMonitor,
mKeyguardViewController,
@@ -167,7 +165,8 @@
.getKeyguardTransitionInteractor(),
KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(),
mFeatureFlags,
- mPrimaryBouncerInteractor
+ mPrimaryBouncerInteractor,
+ mContext
);
}
@@ -228,9 +227,6 @@
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 45021ba..979fc83 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 @@
@TestableLooper.RunWithLooper
public class LockIconViewControllerTest extends LockIconViewControllerBaseTest {
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ when(mLockIconView.isAttachedToWindow()).thenReturn(true);
+ }
+
@Test
public void testUpdateFingerprintLocationOnInit() {
// GIVEN fp sensor location is available pre-attached
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index 88c710a..ddb482f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -16,18 +16,46 @@
package com.android.systemui.back.domain.interactor
+import android.view.ViewRootImpl
+import android.window.BackEvent
+import android.window.BackEvent.EDGE_LEFT
+import android.window.OnBackAnimationCallback
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
+import android.window.WindowOnBackInvokedDispatcher
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.QuickSettingsController
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -41,7 +69,12 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
class BackActionInteractorTest : SysuiTestCase() {
+ private val testScope = TestScope()
+ private val featureFlags = FakeFeatureFlags()
+ private val executor = FakeExecutor(FakeSystemClock())
+
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
@Mock private lateinit var statusBarStateController: StatusBarStateController
@@ -49,18 +82,42 @@
@Mock private lateinit var shadeController: ShadeController
@Mock private lateinit var qsController: QuickSettingsController
@Mock private lateinit var shadeViewController: ShadeViewController
+ @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
+ @Mock private lateinit var windowRootView: WindowRootView
+ @Mock private lateinit var viewRootImpl: ViewRootImpl
+ @Mock private lateinit var onBackInvokedDispatcher: WindowOnBackInvokedDispatcher
+ @Mock private lateinit var iStatusBarService: IStatusBarService
+ @Mock private lateinit var headsUpManager: HeadsUpManager
- private lateinit var backActionInteractor: BackActionInteractor
+ private val keyguardRepository = FakeKeyguardRepository()
+ private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy {
+ WindowRootViewVisibilityInteractor(
+ testScope.backgroundScope,
+ WindowRootViewVisibilityRepository(iStatusBarService, executor),
+ keyguardRepository,
+ headsUpManager,
+ )
+ }
- @Before
- fun setup() {
- backActionInteractor =
- BackActionInteractor(
+ private val backActionInteractor: BackActionInteractor by lazy {
+ BackActionInteractor(
+ testScope.backgroundScope,
statusBarStateController,
statusBarKeyguardViewManager,
shadeController,
+ notificationShadeWindowController,
+ windowRootViewVisibilityInteractor,
+ featureFlags,
)
- backActionInteractor.setup(qsController, shadeViewController)
+ .apply { this.setup(qsController, shadeViewController) }
+ }
+
+ @Before
+ fun setUp() {
+ featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, false)
+ whenever(notificationShadeWindowController.windowRootView).thenReturn(windowRootView)
+ whenever(windowRootView.viewRootImpl).thenReturn(viewRootImpl)
+ whenever(viewRootImpl.onBackInvokedDispatcher).thenReturn(onBackInvokedDispatcher)
}
@Test
@@ -117,4 +174,139 @@
verify(statusBarKeyguardViewManager, never()).onBackPressed()
verify(shadeViewController, never()).animateCollapseQs(anyBoolean())
}
+
+ @Test
+ fun shadeVisibleAndDeviceAwake_callbackRegistered() {
+ backActionInteractor.start()
+ windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.AWAKE)
+
+ testScope.runCurrent()
+
+ verify(onBackInvokedDispatcher)
+ .registerOnBackInvokedCallback(eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), any())
+ }
+
+ @Test
+ fun noWindowRootView_noCrashAttemptingCallbackRegistration() {
+ whenever(notificationShadeWindowController.windowRootView).thenReturn(null)
+
+ backActionInteractor.start()
+ windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.AWAKE)
+
+ testScope.runCurrent()
+ // No assert necessary, just testing no crash
+ }
+
+ @Test
+ fun shadeNotVisible_callbackUnregistered() {
+ backActionInteractor.start()
+ windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.AWAKE)
+ val callback = getBackInvokedCallback()
+
+ windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false)
+ testScope.runCurrent()
+
+ verify(onBackInvokedDispatcher).unregisterOnBackInvokedCallback(callback)
+ }
+
+ @Test
+ fun deviceAsleep_callbackUnregistered() {
+ backActionInteractor.start()
+ windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.AWAKE)
+ val callback = getBackInvokedCallback()
+
+ setWakefulness(WakefulnessState.ASLEEP)
+ testScope.runCurrent()
+
+ verify(onBackInvokedDispatcher).unregisterOnBackInvokedCallback(callback)
+ }
+
+ @Test
+ fun animationFlagOff_onBackInvoked_keyguardNotified() {
+ backActionInteractor.start()
+ featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, false)
+ windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.AWAKE)
+ val callback = getBackInvokedCallback()
+ whenever(statusBarKeyguardViewManager.canHandleBackPressed()).thenReturn(true)
+
+ callback.onBackInvoked()
+
+ verify(statusBarKeyguardViewManager).onBackPressed()
+ }
+
+ @Test
+ fun animationFlagOn_onBackInvoked_keyguardNotified() {
+ featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true)
+ backActionInteractor.start()
+ windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.AWAKE)
+ val callback = getBackInvokedCallback()
+ whenever(statusBarKeyguardViewManager.canHandleBackPressed()).thenReturn(true)
+
+ callback.onBackInvoked()
+
+ verify(statusBarKeyguardViewManager).onBackPressed()
+ }
+
+ @Test
+ fun animationFlagOn_callbackIsAnimationCallback() {
+ featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true)
+ backActionInteractor.start()
+ windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.AWAKE)
+
+ val callback = getBackInvokedCallback()
+
+ assertThat(callback).isInstanceOf(OnBackAnimationCallback::class.java)
+ }
+
+ @Test
+ fun onBackProgressed_shadeCannotBeCollapsed_shadeViewControllerNotNotified() {
+ featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true)
+ backActionInteractor.start()
+ windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.AWAKE)
+ val callback = getBackInvokedCallback() as OnBackAnimationCallback
+
+ whenever(shadeViewController.canBeCollapsed()).thenReturn(false)
+
+ callback.onBackProgressed(createBackEvent(0.3f))
+
+ verify(shadeViewController, never()).onBackProgressed(0.3f)
+ }
+
+ @Test
+ fun onBackProgressed_shadeCanBeCollapsed_shadeViewControllerNotified() {
+ featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true)
+ backActionInteractor.start()
+ windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.AWAKE)
+ val callback = getBackInvokedCallback() as OnBackAnimationCallback
+
+ whenever(shadeViewController.canBeCollapsed()).thenReturn(true)
+
+ callback.onBackProgressed(createBackEvent(0.4f))
+
+ verify(shadeViewController).onBackProgressed(0.4f)
+ }
+
+ private fun getBackInvokedCallback(): OnBackInvokedCallback {
+ testScope.runCurrent()
+ val captor = argumentCaptor<OnBackInvokedCallback>()
+ verify(onBackInvokedDispatcher).registerOnBackInvokedCallback(any(), captor.capture())
+ return captor.value!!
+ }
+
+ private fun createBackEvent(progress: Float): BackEvent =
+ BackEvent(/* touchX= */ 0f, /* touchY= */ 0f, progress, /* swipeEdge= */ EDGE_LEFT)
+
+ private fun setWakefulness(state: WakefulnessState) {
+ val model = WakefulnessModel(state, WakeSleepReason.OTHER, WakeSleepReason.OTHER)
+ keyguardRepository.setWakefulnessModel(model)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 7ab8e8b..e56b5c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -1616,4 +1616,43 @@
// THEN vibrate is used
verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS));
}
+
+ @Test
+ public void aodInterrupt_withNewTouchDetection() throws RemoteException {
+ mUdfpsController.cancelAodSendFingerUpAction();
+ final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
+ 0L);
+ final TouchProcessorResult processorResultDown =
+ new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN,
+ 1 /* pointerId */, touchData);
+
+ // Enable new touch detection.
+ when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
+
+ // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
+ initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
+
+ // GIVEN that the overlay is showing and screen is on and fp is running
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, 0,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mScreenObserver.onScreenTurnedOn();
+ mFgExecutor.runAllReady();
+
+ // WHEN fingerprint is requested because of AOD interrupt
+ mUdfpsController.onAodInterrupt(0, 0, 2f, 3f);
+
+ // Check case where touch driver sends touch to UdfpsView as well
+ verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+ when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+ when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+ processorResultDown);
+ MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+
+ mBiometricExecutor.runAllReady();
+
+ // THEN only one onPointerDown is sent
+ verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(),
+ anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 47084c0..0ed46da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -20,6 +20,7 @@
import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.view.HapticFeedbackConstants
+import android.view.MotionEvent
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
@@ -500,6 +501,81 @@
}
@Test
+ fun auto_confirm_authentication_when_finger_down() = runGenericTest {
+ val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+
+ // No icon button when face only, can't confirm before auth
+ if (!testCase.isFaceOnly) {
+ viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_DOWN))
+ }
+ viewModel.showAuthenticated(testCase.authenticatedModality, 0)
+
+ val authenticating by collectLastValue(viewModel.isAuthenticating)
+ val authenticated by collectLastValue(viewModel.isAuthenticated)
+ val message by collectLastValue(viewModel.message)
+ val size by collectLastValue(viewModel.size)
+ val legacyState by collectLastValue(viewModel.legacyState)
+ val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
+
+ assertThat(authenticating).isFalse()
+ assertThat(canTryAgain).isFalse()
+ assertThat(authenticated?.isAuthenticated).isTrue()
+
+ if (testCase.isFaceOnly && expectConfirmation) {
+ assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION)
+
+ assertThat(size).isEqualTo(PromptSize.MEDIUM)
+ assertButtonsVisible(
+ cancel = true,
+ confirm = true,
+ )
+
+ viewModel.confirmAuthenticated()
+ assertThat(message).isEqualTo(PromptMessage.Empty)
+ assertButtonsVisible()
+ } else {
+ assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
+ }
+ }
+
+ @Test
+ fun cannot_auto_confirm_authentication_when_finger_up() = runGenericTest {
+ val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+
+ // No icon button when face only, can't confirm before auth
+ if (!testCase.isFaceOnly) {
+ viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_DOWN))
+ viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_UP))
+ }
+ viewModel.showAuthenticated(testCase.authenticatedModality, 0)
+
+ val authenticating by collectLastValue(viewModel.isAuthenticating)
+ val authenticated by collectLastValue(viewModel.isAuthenticated)
+ val message by collectLastValue(viewModel.message)
+ val size by collectLastValue(viewModel.size)
+ val legacyState by collectLastValue(viewModel.legacyState)
+ val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
+
+ assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
+ if (expectConfirmation) {
+ assertThat(size).isEqualTo(PromptSize.MEDIUM)
+ assertButtonsVisible(
+ cancel = true,
+ confirm = true,
+ )
+
+ viewModel.confirmAuthenticated()
+ assertThat(message).isEqualTo(PromptMessage.Empty)
+ assertButtonsVisible()
+ }
+
+ assertThat(authenticating).isFalse()
+ assertThat(authenticated?.isAuthenticated).isTrue()
+ assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
+ assertThat(canTryAgain).isFalse()
+ }
+
+ @Test
fun cannot_confirm_unless_authenticated() = runGenericTest {
val authenticating by collectLastValue(viewModel.isAuthenticating)
val authenticated by collectLastValue(viewModel.isAuthenticated)
@@ -679,6 +755,10 @@
testScope.runTest { block() }
}
+ /** Obtain a MotionEvent with the specified MotionEvent action constant */
+ private fun obtainMotionEvent(action: Int): MotionEvent =
+ MotionEvent.obtain(0, 0, action, 0f, 0f, 0)
+
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{0}")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
index 937a7a9..037c1ba 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.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 @@
@Mock
lateinit var centralSurfaces: CentralSurfaces
@Mock
+ lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+ @Mock
lateinit var keyguardStateController: KeyguardStateController
@Mock
lateinit var packageManager: PackageManager
@@ -91,6 +94,7 @@
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 e3a75f1..4ad9549 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 @@
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 @@
}
@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 b1061ba..74d0d21 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.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 @@
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 @@
}
@Test
- fun testPackageNotPreferred_nullPanel() {
- mContext.orCreateTestableResources
- .addOverride(R.array.config_controlsPreferredPackages, arrayOf<String>())
-
- val serviceInfo = ServiceInfo(
- componentName,
- activityName
- )
-
- `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
- .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
-
- setUpQueryResult(listOf(
- ActivityInfo(
- activityName,
- exported = true,
- permission = Manifest.permission.BIND_CONTROLS
- )
- ))
-
- val list = listOf(serviceInfo)
- serviceListingCallbackCaptor.value.onServicesReloaded(list)
-
- executor.runAllReady()
-
- assertNull(controller.getCurrentServices()[0].panelActivity)
- }
-
- @Test
- fun testPackageNotPreferred_allowAllApps_correctPanel() {
- `when`(featureFlags.isEnabled(APP_PANELS_ALL_APPS_ALLOWED)).thenReturn(true)
-
+ fun testPackageNotPreferred_correctPanel() {
mContext.orCreateTestableResources
.addOverride(R.array.config_controlsPreferredPackages, arrayOf<String>())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index 9be54fb..db7c003 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -165,13 +165,78 @@
assertThat(value?.ids()).containsExactly(1, 2, 3, 4)
}
+ @Test
+ fun onDisplayConnected_pendingDisplayReceived() =
+ testScope.runTest {
+ val pendingDisplay by latestPendingDisplayFlowValue()
+
+ displayListener.value.onDisplayConnected(1)
+
+ assertThat(pendingDisplay).isEqualTo(1)
+ }
+
+ @Test
+ fun onDisplayDisconnected_pendingDisplayNull() =
+ testScope.runTest {
+ val pendingDisplay by latestPendingDisplayFlowValue()
+ displayListener.value.onDisplayConnected(1)
+
+ assertThat(pendingDisplay).isNotNull()
+
+ displayListener.value.onDisplayDisconnected(1)
+
+ assertThat(pendingDisplay).isNull()
+ }
+
+ @Test
+ fun onDisplayDisconnected_unknownDisplay_doesNotSendNull() =
+ testScope.runTest {
+ val pendingDisplay by latestPendingDisplayFlowValue()
+ displayListener.value.onDisplayConnected(1)
+
+ assertThat(pendingDisplay).isNotNull()
+
+ displayListener.value.onDisplayDisconnected(2)
+
+ assertThat(pendingDisplay).isNotNull()
+ }
+
+ @Test
+ fun onDisplayConnected_multipleTimes_sendsOnlyTheLastOne() =
+ testScope.runTest {
+ val pendingDisplay by latestPendingDisplayFlowValue()
+ displayListener.value.onDisplayConnected(1)
+ displayListener.value.onDisplayConnected(2)
+
+ assertThat(pendingDisplay).isEqualTo(2)
+ }
+
private fun Iterable<Display>.ids(): List<Int> = map { it.displayId }
// Wrapper to capture the displayListener.
private fun TestScope.latestDisplayFlowValue(): FlowValue<Set<Display>?> {
val flowValue = collectLastValue(displayRepository.displays)
verify(displayManager)
- .registerDisplayListener(displayListener.capture(), eq(testHandler), anyLong())
+ .registerDisplayListener(
+ displayListener.capture(),
+ eq(testHandler),
+ eq(
+ DisplayManager.EVENT_FLAG_DISPLAY_ADDED or
+ DisplayManager.EVENT_FLAG_DISPLAY_CHANGED or
+ DisplayManager.EVENT_FLAG_DISPLAY_REMOVED
+ )
+ )
+ return flowValue
+ }
+
+ private fun TestScope.latestPendingDisplayFlowValue(): FlowValue<Int?> {
+ val flowValue = collectLastValue(displayRepository.pendingDisplayId)
+ verify(displayManager)
+ .registerDisplayListener(
+ displayListener.capture(),
+ eq(testHandler),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED)
+ )
return flowValue
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
index eb0ad69..50617a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.display.domain.interactor
+import android.hardware.display.DisplayManager
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.Display
@@ -27,14 +28,20 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.display.data.repository.display
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
@@ -42,11 +49,22 @@
@SmallTest
class ConnectedDisplayInteractorTest : SysuiTestCase() {
+ private val displayManager = mock<DisplayManager>()
private val fakeDisplayRepository = FakeDisplayRepository()
+ private val fakeKeyguardRepository = FakeKeyguardRepository()
private val connectedDisplayStateProvider: ConnectedDisplayInteractor =
- ConnectedDisplayInteractorImpl(fakeDisplayRepository)
+ ConnectedDisplayInteractorImpl(
+ displayManager,
+ fakeKeyguardRepository,
+ fakeDisplayRepository
+ )
private val testScope = TestScope(UnconfinedTestDispatcher())
+ @Before
+ fun setup() {
+ fakeKeyguardRepository.setKeyguardUnlocked(true)
+ }
+
@Test
fun displayState_nullDisplays_disconnected() =
testScope.runTest {
@@ -126,6 +144,70 @@
assertThat(value).isEqualTo(State.CONNECTED_SECURE)
}
+ @Test
+ fun pendingDisplay_propagated() =
+ testScope.runTest {
+ val value by lastPendingDisplay()
+ val pendingDisplayId = 4
+
+ fakeDisplayRepository.emit(pendingDisplayId)
+
+ assertThat(value).isNotNull()
+ }
+
+ @Test
+ fun onPendingDisplay_enable_displayEnabled() =
+ testScope.runTest {
+ val pendingDisplay by lastPendingDisplay()
+
+ fakeDisplayRepository.emit(1)
+ pendingDisplay!!.enable()
+
+ Mockito.verify(displayManager).enableConnectedDisplay(eq(1))
+ }
+
+ @Test
+ fun onPendingDisplay_disable_displayDisabled() =
+ testScope.runTest {
+ val pendingDisplay by lastPendingDisplay()
+
+ fakeDisplayRepository.emit(1)
+ pendingDisplay!!.disable()
+
+ Mockito.verify(displayManager).disableConnectedDisplay(eq(1))
+ }
+
+ @Test
+ fun onPendingDisplay_keyguardUnlocked_returnsPendingDisplay() =
+ testScope.runTest {
+ fakeKeyguardRepository.setKeyguardUnlocked(false)
+ val pendingDisplay by lastPendingDisplay()
+
+ fakeDisplayRepository.emit(1)
+ assertThat(pendingDisplay).isNull()
+
+ fakeKeyguardRepository.setKeyguardUnlocked(true)
+
+ assertThat(pendingDisplay).isNotNull()
+ }
+
+ @Test
+ fun onPendingDisplay_keyguardLocked_returnsNull() =
+ testScope.runTest {
+ fakeKeyguardRepository.setKeyguardUnlocked(true)
+ val pendingDisplay by lastPendingDisplay()
+
+ fakeDisplayRepository.emit(1)
+ assertThat(pendingDisplay).isNotNull()
+
+ fakeKeyguardRepository.setKeyguardUnlocked(false)
+
+ assertThat(pendingDisplay).isNull()
+ }
+
private fun TestScope.lastValue(): FlowValue<State?> =
collectLastValue(connectedDisplayStateProvider.connectedDisplayState)
+
+ private fun TestScope.lastPendingDisplay(): FlowValue<PendingDisplay?> =
+ collectLastValue(connectedDisplayStateProvider.pendingDisplay)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
new file mode 100644
index 0000000..7059647
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.ui.view
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class MirroringConfirmationDialogTest : SysuiTestCase() {
+
+ private lateinit var dialog: MirroringConfirmationDialog
+
+ private val onStartMirroringCallback = mock<View.OnClickListener>()
+ private val onCancelCallback = mock<View.OnClickListener>()
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ dialog = MirroringConfirmationDialog(context, onStartMirroringCallback, onCancelCallback)
+ }
+
+ @Test
+ fun startMirroringButton_clicked_callsCorrectCallback() {
+ dialog.show()
+
+ dialog.requireViewById<View>(R.id.enable_display).callOnClick()
+
+ verify(onStartMirroringCallback).onClick(any())
+ verify(onCancelCallback, never()).onClick(any())
+ }
+
+ @Test
+ fun cancelButton_clicked_callsCorrectCallback() {
+ dialog.show()
+
+ dialog.requireViewById<View>(R.id.cancel).callOnClick()
+
+ verify(onCancelCallback).onClick(any())
+ verify(onStartMirroringCallback, never()).onClick(any())
+ }
+
+ @After
+ fun teardown() {
+ if (::dialog.isInitialized) {
+ dialog.dismiss()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
index ccd631e..8f66344 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.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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
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 0000000..71a56cd
--- /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 daafba2..f78d051 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 @@
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 @@
mConfigurationController,
mViewMediator,
mKeyguardBypassController,
+ mUiBgExecutor,
mColorExtractor,
mDumpManager,
mKeyguardStateController,
@@ -868,8 +870,6 @@
when(mKeyguardStateController.isShowing()).thenReturn(true);
TestableLooper.get(this).processAllMessages();
- when(mPowerManager.isInteractive()).thenReturn(true);
-
mViewMediator.onSystemReady();
TestableLooper.get(this).processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 5ead16b..2691860 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -46,6 +46,7 @@
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
@@ -322,20 +323,20 @@
val captor = argumentCaptor<StatusBarStateController.StateListener>()
runCurrent()
- verify(statusBarStateController).addCallback(captor.capture())
+ verify(statusBarStateController, atLeastOnce()).addCallback(captor.capture())
- captor.value.onDozeAmountChanged(0.433f, 0.4f)
+ captor.allValues.forEach { it.onDozeAmountChanged(0.433f, 0.4f) }
runCurrent()
- captor.value.onDozeAmountChanged(0.498f, 0.5f)
+ captor.allValues.forEach { it.onDozeAmountChanged(0.498f, 0.5f) }
runCurrent()
- captor.value.onDozeAmountChanged(0.661f, 0.65f)
+ captor.allValues.forEach { it.onDozeAmountChanged(0.661f, 0.65f) }
runCurrent()
assertThat(values).isEqualTo(listOf(0f, 0.433f, 0.498f, 0.661f))
job.cancel()
runCurrent()
- verify(statusBarStateController).removeCallback(captor.value)
+ verify(statusBarStateController).removeCallback(any())
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index ca93246..d457605 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -1049,7 +1049,6 @@
@Test
fun occludedToAlternateBouncer() =
testScope.runTest {
-
// GIVEN a prior transition has run to OCCLUDED
runTransition(KeyguardState.LOCKSCREEN, KeyguardState.OCCLUDED)
keyguardRepository.setKeyguardOccluded(true)
@@ -1073,6 +1072,31 @@
}
@Test
+ fun occludedToPrimaryBouncer() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to OCCLUDED
+ runTransition(KeyguardState.LOCKSCREEN, KeyguardState.OCCLUDED)
+ keyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+
+ // WHEN primary bouncer shows
+ bouncerRepository.setPrimaryShow(true) // beverlyt
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture(), anyBoolean())
+ }
+ // THEN a transition to AlternateBouncer should occur
+ assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
+ assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun primaryBouncerToOccluded() =
testScope.runTest {
// GIVEN device not sleeping
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
index addb181..3b4eab2 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 @@
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.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 @@
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 @@
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 3dcc03d..798b23e 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.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 379c03c..1192a80 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 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 @@
@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 @@
@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 a9f288d..b30dc9c 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.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 @@
private val underTest =
LockscreenSceneViewModel(
authenticationInteractor = authenticationInteractor,
- bouncerInteractor =
- utils.bouncerInteractor(
- authenticationInteractor = authenticationInteractor,
- sceneInteractor = sceneInteractor,
+ longPress =
+ KeyguardLongPressViewModel(
+ interactor = mock(),
),
)
@@ -76,30 +73,4 @@
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 ef51e47..3961a94 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.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 @@
@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 @@
executor,
mediaDataManager,
configurationController,
- falsingCollector,
falsingManager,
dumpManager,
logger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt
index a01394f..f566efe6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt
@@ -193,6 +193,23 @@
assertThat(reasonCaptor.value).contains(context.applicationContext.packageName)
}
+ @Test
+ fun userActivity_notifiesPowerManager() {
+ systemClock.setUptimeMillis(345000)
+
+ underTest.userTouch()
+
+ val flagsCaptor = argumentCaptor<Int>()
+ verify(manager)
+ .userActivity(
+ eq(345000L),
+ eq(PowerManager.USER_ACTIVITY_EVENT_TOUCH),
+ capture(flagsCaptor)
+ )
+ assertThat(flagsCaptor.value).isNotEqualTo(PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS)
+ assertThat(flagsCaptor.value).isNotEqualTo(PowerManager.USER_ACTIVITY_FLAG_INDIRECT)
+ }
+
private fun verifyRegistered() {
// We must verify with all arguments, even those that are optional because they have default
// values because Mockito is forcing us to. Once we can use mockito-kotlin, we should be
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index 93ed994..e537131 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -14,6 +14,8 @@
package com.android.systemui.qs.tiles;
+import static com.android.systemui.flags.Flags.SIGNAL_CALLBACK_DEPRECATION;
+
import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertEquals;
@@ -37,9 +39,11 @@
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
+import com.android.keyguard.TestScopeProvider;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
@@ -49,6 +53,12 @@
import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.connectivity.SignalCallback;
import com.android.systemui.statusbar.connectivity.WifiIndicators;
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository;
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository;
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor;
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl;
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel;
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
import com.android.systemui.statusbar.policy.HotspotController;
@@ -65,6 +75,7 @@
import java.util.ArrayList;
import java.util.List;
+import kotlinx.coroutines.test.TestScope;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -98,6 +109,12 @@
@Mock
private QsEventLogger mUiEventLogger;
+ private WifiInteractor mWifiInteractor;
+ private final TileJavaAdapter mJavaAdapter = new TileJavaAdapter();
+ private final FakeWifiRepository mWifiRepository = new FakeWifiRepository();
+ private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+ private final TestScope mTestScope = TestScopeProvider.getTestScope();
+
private TestableLooper mTestableLooper;
private CastTile mCastTile;
@@ -108,42 +125,11 @@
when(mHost.getContext()).thenReturn(mContext);
- mCastTile = new CastTile(
- mHost,
- mUiEventLogger,
- mTestableLooper.getLooper(),
- new Handler(mTestableLooper.getLooper()),
- new FalsingManagerFake(),
- mMetricsLogger,
- mStatusBarStateController,
- mActivityStarter,
- mQSLogger,
- mController,
- mKeyguard,
- mNetworkController,
- mHotspotController,
- mDialogLaunchAnimator
+ mWifiInteractor = new WifiInteractorImpl(
+ new FakeConnectivityRepository(),
+ mWifiRepository,
+ mTestScope
);
- mCastTile.initialize();
-
- // We are not setting the mocks to listening, so we trigger a first refresh state to
- // set the initial state
- mCastTile.refreshState();
-
- mTestableLooper.processAllMessages();
-
- mCastTile.handleSetListening(true);
- ArgumentCaptor<SignalCallback> signalCallbackArgumentCaptor =
- ArgumentCaptor.forClass(SignalCallback.class);
- verify(mNetworkController).observe(any(LifecycleOwner.class),
- signalCallbackArgumentCaptor.capture());
- mSignalCallback = signalCallbackArgumentCaptor.getValue();
-
- ArgumentCaptor<HotspotController.Callback> hotspotCallbackArgumentCaptor =
- ArgumentCaptor.forClass(HotspotController.Callback.class);
- verify(mHotspotController).observe(any(LifecycleOwner.class),
- hotspotCallbackArgumentCaptor.capture());
- mHotspotCallback = hotspotCallbackArgumentCaptor.getValue();
}
@After
@@ -156,10 +142,11 @@
// All these tests for enabled/disabled wifi have hotspot not enabled
@Test
public void testStateUnavailable_wifiDisabled() {
+ createAndStartTileOldImpl();
IconState qsIcon = new IconState(false, 0, "");
WifiIndicators indicators = new WifiIndicators(
false, mock(IconState.class),
- qsIcon, false,false, "",
+ qsIcon, false, false, "",
false, "");
mSignalCallback.setWifiIndicators(indicators);
mTestableLooper.processAllMessages();
@@ -169,10 +156,11 @@
@Test
public void testStateUnavailable_wifiNotConnected() {
+ createAndStartTileOldImpl();
IconState qsIcon = new IconState(false, 0, "");
WifiIndicators indicators = new WifiIndicators(
true, mock(IconState.class),
- qsIcon, false,false, "",
+ qsIcon, false, false, "",
false, "");
mSignalCallback.setWifiIndicators(indicators);
mTestableLooper.processAllMessages();
@@ -184,7 +172,7 @@
IconState qsIcon = new IconState(true, 0, "");
WifiIndicators indicators = new WifiIndicators(
true, mock(IconState.class),
- qsIcon, false,false, "",
+ qsIcon, false, false, "",
false, "");
mSignalCallback.setWifiIndicators(indicators);
mTestableLooper.processAllMessages();
@@ -192,6 +180,7 @@
@Test
public void testStateActive_wifiEnabledAndCasting() {
+ createAndStartTileOldImpl();
CastController.CastDevice device = new CastController.CastDevice();
device.state = CastController.CastDevice.STATE_CONNECTED;
List<CastDevice> devices = new ArrayList<>();
@@ -204,15 +193,87 @@
@Test
public void testStateInactive_wifiEnabledNotCasting() {
+ createAndStartTileOldImpl();
enableWifiAndProcessMessages();
assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
}
// -------------------------------------------------
// -------------------------------------------------
+ // All these tests for enabled/disabled wifi have hotspot not enabled, and have the
+ // SIGNAL_CALLBACK_DEPRECATION flag set to true
+
+ @Test
+ public void stateUnavailable_wifiDisabled_newPipeline() {
+ createAndStartTileNewImpl();
+ mWifiRepository.setIsWifiEnabled(false);
+ mTestableLooper.processAllMessages();
+
+ assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);
+ }
+
+ @Test
+ public void stateUnavailable_wifiEnabled_notConnected_newPipeline() {
+ createAndStartTileNewImpl();
+ mWifiRepository.setIsWifiEnabled(true);
+ mWifiRepository.setWifiNetwork(Inactive.INSTANCE);
+ mTestableLooper.processAllMessages();
+
+ assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);
+ }
+
+ @Test
+ public void stateActive_wifiConnectedAndCasting_newPipeline() {
+ createAndStartTileNewImpl();
+ CastController.CastDevice device = new CastController.CastDevice();
+ device.state = CastDevice.STATE_CONNECTED;
+ List<CastDevice> devices = new ArrayList<>();
+ devices.add(device);
+ when(mController.getCastDevices()).thenReturn(devices);
+
+ mWifiRepository.setWifiNetwork(
+ new WifiNetworkModel.Active(
+ 1 /* networkId */,
+ true /* isValidated */,
+ 3 /* level */,
+ "test" /* ssid */,
+ WifiNetworkModel.HotspotDeviceType.NONE,
+ false /* isPasspointAccessPoint */,
+ false /* isOnlineSignUpforPasspointAccessPoint */,
+ null /* passpointProviderFriendlyName */
+ ));
+ mTestableLooper.processAllMessages();
+
+ assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
+ }
+
+ @Test
+ public void stateInactive_wifiConnectedNotCasting_newPipeline() {
+ createAndStartTileNewImpl();
+
+ mWifiRepository.setWifiNetwork(
+ new WifiNetworkModel.Active(
+ 1 /* networkId */,
+ true /* isValidated */,
+ 3 /* level */,
+ "test" /* ssid */,
+ WifiNetworkModel.HotspotDeviceType.NONE,
+ false /* isPasspointAccessPoint */,
+ false /* isOnlineSignUpforPasspointAccessPoint */,
+ null /* passpointProviderFriendlyName */
+ ));
+ mTestableLooper.processAllMessages();
+
+ assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
+ }
+
+ // -------------------------------------------------
+
+ // -------------------------------------------------
// All these tests for enabled/disabled hotspot have wifi not enabled
@Test
public void testStateUnavailable_hotspotDisabled() {
+ createAndStartTileOldImpl();
mHotspotCallback.onHotspotChanged(false, 0);
mTestableLooper.processAllMessages();
@@ -221,6 +282,7 @@
@Test
public void testStateUnavailable_hotspotEnabledNotConnected() {
+ createAndStartTileOldImpl();
mHotspotCallback.onHotspotChanged(true, 0);
mTestableLooper.processAllMessages();
@@ -229,6 +291,7 @@
@Test
public void testStateActive_hotspotEnabledAndConnectedAndCasting() {
+ createAndStartTileOldImpl();
CastController.CastDevice device = new CastController.CastDevice();
device.state = CastController.CastDevice.STATE_CONNECTED;
List<CastDevice> devices = new ArrayList<>();
@@ -242,6 +305,7 @@
@Test
public void testStateInactive_hotspotEnabledAndConnectedAndNotCasting() {
+ createAndStartTileOldImpl();
mHotspotCallback.onHotspotChanged(true, 1);
mTestableLooper.processAllMessages();
assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
@@ -250,6 +314,7 @@
@Test
public void testHandleClick_castDevicePresent() {
+ createAndStartTileOldImpl();
CastController.CastDevice device = new CastController.CastDevice();
device.state = CastDevice.STATE_CONNECTED;
device.tag = mock(MediaRouter.RouteInfo.class);
@@ -267,6 +332,7 @@
@Test
public void testHandleClick_projectionOnly() {
+ createAndStartTileOldImpl();
CastController.CastDevice device = new CastController.CastDevice();
device.state = CastDevice.STATE_CONNECTED;
device.tag = mock(MediaProjectionInfo.class);
@@ -283,6 +349,7 @@
@Test
public void testUpdateState_projectionOnly() {
+ createAndStartTileOldImpl();
CastController.CastDevice device = new CastController.CastDevice();
device.state = CastDevice.STATE_CONNECTED;
device.tag = mock(MediaProjectionInfo.class);
@@ -298,6 +365,7 @@
@Test
public void testUpdateState_castingAndProjection() {
+ createAndStartTileOldImpl();
CastController.CastDevice casting = new CastController.CastDevice();
casting.state = CastDevice.STATE_CONNECTED;
casting.tag = mock(RouteInfo.class);
@@ -322,6 +390,7 @@
@Test
public void testUpdateState_connectedAndConnecting() {
+ createAndStartTileOldImpl();
CastController.CastDevice connecting = new CastController.CastDevice();
connecting.state = CastDevice.STATE_CONNECTING;
connecting.tag = mock(RouteInfo.class);
@@ -346,6 +415,7 @@
@Test
public void testExpandView_wifiNotConnected() {
+ createAndStartTileOldImpl();
mCastTile.refreshState();
mTestableLooper.processAllMessages();
@@ -354,6 +424,7 @@
@Test
public void testExpandView_wifiEnabledNotCasting() {
+ createAndStartTileOldImpl();
enableWifiAndProcessMessages();
assertTrue(mCastTile.getState().forceExpandIcon);
@@ -361,6 +432,7 @@
@Test
public void testExpandView_casting_projection() {
+ createAndStartTileOldImpl();
CastController.CastDevice device = new CastController.CastDevice();
device.state = CastController.CastDevice.STATE_CONNECTED;
List<CastDevice> devices = new ArrayList<>();
@@ -374,6 +446,7 @@
@Test
public void testExpandView_connecting_projection() {
+ createAndStartTileOldImpl();
CastController.CastDevice connecting = new CastController.CastDevice();
connecting.state = CastDevice.STATE_CONNECTING;
connecting.name = "Test Casting Device";
@@ -389,6 +462,7 @@
@Test
public void testExpandView_casting_mediaRoute() {
+ createAndStartTileOldImpl();
CastController.CastDevice device = new CastController.CastDevice();
device.state = CastDevice.STATE_CONNECTED;
device.tag = mock(MediaRouter.RouteInfo.class);
@@ -403,6 +477,7 @@
@Test
public void testExpandView_connecting_mediaRoute() {
+ createAndStartTileOldImpl();
CastController.CastDevice connecting = new CastController.CastDevice();
connecting.state = CastDevice.STATE_CONNECTING;
connecting.tag = mock(RouteInfo.class);
@@ -416,4 +491,86 @@
assertTrue(mCastTile.getState().forceExpandIcon);
}
+
+ /**
+ * For simplicity, let this method still set the field even though that's kind of gross
+ */
+ private void createAndStartTileOldImpl() {
+ mFeatureFlags.set(SIGNAL_CALLBACK_DEPRECATION, false);
+ mCastTile = new CastTile(
+ mHost,
+ mUiEventLogger,
+ mTestableLooper.getLooper(),
+ new Handler(mTestableLooper.getLooper()),
+ new FalsingManagerFake(),
+ mMetricsLogger,
+ mStatusBarStateController,
+ mActivityStarter,
+ mQSLogger,
+ mController,
+ mKeyguard,
+ mNetworkController,
+ mHotspotController,
+ mDialogLaunchAnimator,
+ mWifiInteractor,
+ mJavaAdapter,
+ mFeatureFlags
+ );
+ mCastTile.initialize();
+
+ // We are not setting the mocks to listening, so we trigger a first refresh state to
+ // set the initial state
+ mCastTile.refreshState();
+
+ mTestableLooper.processAllMessages();
+
+ mCastTile.handleSetListening(true);
+ ArgumentCaptor<SignalCallback> signalCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(SignalCallback.class);
+ verify(mNetworkController).observe(any(LifecycleOwner.class),
+ signalCallbackArgumentCaptor.capture());
+ mSignalCallback = signalCallbackArgumentCaptor.getValue();
+
+ ArgumentCaptor<HotspotController.Callback> hotspotCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(HotspotController.Callback.class);
+ verify(mHotspotController).observe(any(LifecycleOwner.class),
+ hotspotCallbackArgumentCaptor.capture());
+ mHotspotCallback = hotspotCallbackArgumentCaptor.getValue();
+ }
+
+ private void createAndStartTileNewImpl() {
+ mFeatureFlags.set(SIGNAL_CALLBACK_DEPRECATION, true);
+ mCastTile = new CastTile(
+ mHost,
+ mUiEventLogger,
+ mTestableLooper.getLooper(),
+ new Handler(mTestableLooper.getLooper()),
+ new FalsingManagerFake(),
+ mMetricsLogger,
+ mStatusBarStateController,
+ mActivityStarter,
+ mQSLogger,
+ mController,
+ mKeyguard,
+ mNetworkController,
+ mHotspotController,
+ mDialogLaunchAnimator,
+ mWifiInteractor,
+ mJavaAdapter,
+ mFeatureFlags
+ );
+ mCastTile.initialize();
+
+ // Since we do not capture the callbacks like in the old impl, set the state to RESUMED
+ // So that TileJavaAdapter is collecting on flows
+ mCastTile.setListening(new Object(), true);
+
+ mTestableLooper.processAllMessages();
+
+ ArgumentCaptor<HotspotController.Callback> hotspotCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(HotspotController.Callback.class);
+ verify(mHotspotController).observe(any(LifecycleOwner.class),
+ hotspotCallbackArgumentCaptor.capture());
+ mHotspotCallback = hotspotCallbackArgumentCaptor.getValue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
new file mode 100644
index 0000000..b6cf459
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles
+
+import android.os.Handler
+import android.service.quicksettings.Tile
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tiles.dialog.InternetDialogFactory
+import com.android.systemui.statusbar.connectivity.AccessPointController
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Wifi
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.InternetTileViewModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWithLooper(setAsMainLooper = true)
+class InternetTileNewImplTest : SysuiTestCase() {
+ lateinit var underTest: InternetTileNewImpl
+
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private var airplaneModeRepository = FakeAirplaneModeRepository()
+ private var connectivityRepository = FakeConnectivityRepository()
+ private var ethernetInteractor = EthernetInteractor(connectivityRepository)
+ private var mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+ private var wifiRepository = FakeWifiRepository()
+ private var wifiInteractor =
+ WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope)
+ private lateinit var viewModel: InternetTileViewModel
+
+ private lateinit var looper: TestableLooper
+
+ @Mock private lateinit var host: QSHost
+ @Mock private lateinit var eventLogger: QsEventLogger
+ @Mock private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var sbStateController: StatusBarStateController
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var logger: QSLogger
+ @Mock private lateinit var dialogFactory: InternetDialogFactory
+ @Mock private lateinit var accessPointController: AccessPointController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ looper = TestableLooper.get(this)
+
+ // Allow the tile to load resources
+ whenever(host.context).thenReturn(context)
+ whenever(host.userContext).thenReturn(context)
+
+ viewModel =
+ InternetTileViewModel(
+ airplaneModeRepository,
+ connectivityRepository,
+ ethernetInteractor,
+ mobileIconsInteractor,
+ wifiInteractor,
+ context,
+ testScope.backgroundScope,
+ )
+
+ underTest =
+ InternetTileNewImpl(
+ host,
+ eventLogger,
+ looper.looper,
+ Handler(looper.looper),
+ FalsingManagerFake(),
+ metricsLogger,
+ sbStateController,
+ activityStarter,
+ logger,
+ viewModel,
+ dialogFactory,
+ accessPointController
+ )
+
+ underTest.initialize()
+ underTest.setListening(Object(), true)
+
+ looper.processAllMessages()
+ }
+
+ @Test
+ fun noDefaultConnection_noNetworkAvailable() =
+ testScope.runTest {
+ connectivityRepository.defaultConnections.value = DefaultConnectionModel()
+ wifiRepository.wifiScanResults.value = listOf()
+
+ runCurrent()
+ looper.processAllMessages()
+
+ assertThat(underTest.state.secondaryLabel.toString())
+ .isEqualTo(context.getString(R.string.quick_settings_networks_unavailable))
+ assertThat(underTest.state.state).isEqualTo(Tile.STATE_INACTIVE)
+ }
+
+ @Test
+ fun noDefaultConnection_networksAvailable() =
+ testScope.runTest {
+ connectivityRepository.defaultConnections.value = DefaultConnectionModel()
+ wifiRepository.wifiScanResults.value =
+ listOf(
+ WifiScanEntry(ssid = "ssid 1"),
+ WifiScanEntry(ssid = "ssid 2"),
+ )
+
+ runCurrent()
+ looper.processAllMessages()
+
+ assertThat(underTest.state.secondaryLabel.toString())
+ .isEqualTo(context.getString(R.string.quick_settings_networks_available))
+ assertThat(underTest.state.state).isEqualTo(1)
+ }
+
+ @Test
+ fun airplaneMode_enabled_wifiDisabled() =
+ testScope.runTest {
+ airplaneModeRepository.setIsAirplaneMode(true)
+ connectivityRepository.defaultConnections.value = DefaultConnectionModel()
+ wifiRepository.setIsWifiEnabled(false)
+
+ runCurrent()
+ looper.processAllMessages()
+
+ assertThat(underTest.state.state).isEqualTo(Tile.STATE_INACTIVE)
+ assertThat(underTest.state.secondaryLabel)
+ .isEqualTo(context.getString(R.string.status_bar_airplane))
+ }
+
+ @Test
+ fun airplaneMode_enabled_wifiEnabledButNotConnected() =
+ testScope.runTest {
+ airplaneModeRepository.setIsAirplaneMode(true)
+ connectivityRepository.defaultConnections.value = DefaultConnectionModel()
+ wifiRepository.setIsWifiEnabled(true)
+
+ runCurrent()
+ looper.processAllMessages()
+
+ assertThat(underTest.state.state).isEqualTo(Tile.STATE_INACTIVE)
+ assertThat(underTest.state.secondaryLabel)
+ .isEqualTo(context.getString(R.string.status_bar_airplane))
+ }
+
+ @Test
+ fun airplaneMode_enabled_wifiEnabledAndConnected() =
+ testScope.runTest {
+ airplaneModeRepository.setIsAirplaneMode(true)
+ connectivityRepository.defaultConnections.value =
+ DefaultConnectionModel(
+ wifi = Wifi(true),
+ isValidated = true,
+ )
+ wifiRepository.setIsWifiEnabled(true)
+ wifiRepository.setWifiNetwork(ACTIVE_WIFI)
+
+ runCurrent()
+ looper.processAllMessages()
+
+ assertThat(underTest.state.state).isEqualTo(Tile.STATE_ACTIVE)
+ assertThat(underTest.state.secondaryLabel).isEqualTo(WIFI_SSID)
+ }
+
+ @Test
+ fun wifiConnected() =
+ testScope.runTest {
+ connectivityRepository.defaultConnections.value =
+ DefaultConnectionModel(
+ wifi = Wifi(true),
+ isValidated = true,
+ )
+
+ wifiRepository.setIsWifiEnabled(true)
+ wifiRepository.setWifiNetwork(ACTIVE_WIFI)
+
+ runCurrent()
+ looper.processAllMessages()
+
+ assertThat(underTest.state.state).isEqualTo(Tile.STATE_ACTIVE)
+ assertThat(underTest.state.secondaryLabel).isEqualTo(WIFI_SSID)
+ }
+
+ companion object {
+ const val WIFI_SSID = "test ssid"
+ val ACTIVE_WIFI =
+ WifiNetworkModel.Active(
+ networkId = 1,
+ isValidated = true,
+ level = 4,
+ ssid = WIFI_SSID,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 46cbfac..6006cd4 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.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 @@
)
private val sceneContainerViewModel =
SceneContainerViewModel(
- interactor = sceneInteractor,
+ sceneInteractor = sceneInteractor,
+ falsingInteractor = utils.falsingInteractor(),
)
.apply { setTransitionState(transitionState) }
@@ -117,7 +119,10 @@
private val lockscreenSceneViewModel =
LockscreenSceneViewModel(
authenticationInteractor = authenticationInteractor,
- bouncerInteractor = bouncerInteractor,
+ longPress =
+ KeyguardLongPressViewModel(
+ interactor = mock(),
+ ),
)
private val shadeSceneViewModel =
@@ -127,7 +132,7 @@
bouncerInteractor = bouncerInteractor,
)
- private val keyguardRepository = utils.keyguardRepository()
+ private val keyguardRepository = utils.keyguardRepository
private val keyguardInteractor =
utils.keyguardInteractor(
repository = keyguardRepository,
@@ -151,6 +156,7 @@
sysUiState = sysUiState,
displayId = displayTracker.defaultDisplayId,
sceneLogger = mock(),
+ falsingCollector = utils.falsingCollector(),
)
startable.start()
@@ -165,9 +171,7 @@
@Test
fun clickLockButtonAndEnterCorrectPin_unlocksDevice() =
testScope.runTest {
- lockscreenSceneViewModel.onLockButtonClicked()
- assertCurrentScene(SceneKey.Bouncer)
- emulateUiSceneTransition()
+ emulateUserDrivenTransition(SceneKey.Bouncer)
enterPin()
assertCurrentScene(SceneKey.Gone)
@@ -460,10 +464,7 @@
.that(authenticationInteractor.isUnlocked.value)
.isFalse()
- lockscreenSceneViewModel.onLockButtonClicked()
- runCurrent()
- emulateUiSceneTransition()
-
+ emulateUserDrivenTransition(SceneKey.Bouncer)
enterPin()
emulateUiSceneTransition(
expectedVisible = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt
new file mode 100644
index 0000000..2187f36
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.IStatusBarService
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.Mockito.verify
+
+@SmallTest
+class WindowRootViewVisibilityRepositoryTest : SysuiTestCase() {
+ private val iStatusBarService = mock<IStatusBarService>()
+ private val executor = FakeExecutor(FakeSystemClock())
+ private val underTest = WindowRootViewVisibilityRepository(iStatusBarService, executor)
+
+ @Test
+ fun isLockscreenOrShadeVisible_true() {
+ underTest.setIsLockscreenOrShadeVisible(true)
+
+ assertThat(underTest.isLockscreenOrShadeVisible.value).isTrue()
+ }
+
+ @Test
+ fun isLockscreenOrShadeVisible_false() {
+ underTest.setIsLockscreenOrShadeVisible(false)
+
+ assertThat(underTest.isLockscreenOrShadeVisible.value).isFalse()
+ }
+
+ @Test
+ fun onLockscreenOrShadeInteractive_statusBarServiceNotified() {
+ underTest.onLockscreenOrShadeInteractive(
+ shouldClearNotificationEffects = true,
+ notificationCount = 3,
+ )
+ executor.runAllReady()
+
+ verify(iStatusBarService).onPanelRevealed(true, 3)
+ }
+
+ @Test
+ fun onLockscreenOrShadeNotInteractive_statusBarServiceNotified() {
+ underTest.onLockscreenOrShadeNotInteractive()
+ executor.runAllReady()
+
+ verify(iStatusBarService).onPanelHidden()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 713c602..8620f61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -147,4 +147,11 @@
underTest.setVisible(true, "reason")
assertThat(isVisible).isTrue()
}
+
+ @Test
+ fun userInput() =
+ testScope.runTest {
+ underTest.onUserInput()
+ assertThat(utils.powerRepository.userTouchRegistered).isTrue()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
new file mode 100644
index 0000000..f304435
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.IStatusBarService
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.init.NotificationsController
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class WindowRootViewVisibilityInteractorTest : SysuiTestCase() {
+
+ private val testScope = TestScope()
+ private val iStatusBarService = mock<IStatusBarService>()
+ private val executor = FakeExecutor(FakeSystemClock())
+ private val windowRootViewVisibilityRepository =
+ WindowRootViewVisibilityRepository(iStatusBarService, executor)
+ private val keyguardRepository = FakeKeyguardRepository()
+ private val headsUpManager = mock<HeadsUpManager>()
+ private val notificationPresenter = mock<NotificationPresenter>()
+ private val notificationsController = mock<NotificationsController>()
+
+ private val underTest =
+ WindowRootViewVisibilityInteractor(
+ testScope.backgroundScope,
+ windowRootViewVisibilityRepository,
+ keyguardRepository,
+ headsUpManager,
+ )
+ .apply { setUp(notificationPresenter, notificationsController) }
+
+ @Test
+ fun isLockscreenOrShadeVisible_true() {
+ underTest.setIsLockscreenOrShadeVisible(true)
+
+ assertThat(underTest.isLockscreenOrShadeVisible.value).isTrue()
+ }
+
+ @Test
+ fun isLockscreenOrShadeVisible_false() {
+ underTest.setIsLockscreenOrShadeVisible(false)
+
+ assertThat(underTest.isLockscreenOrShadeVisible.value).isFalse()
+ }
+
+ @Test
+ fun isLockscreenOrShadeVisible_matchesRepo() {
+ windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(true)
+
+ assertThat(underTest.isLockscreenOrShadeVisible.value).isTrue()
+
+ windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(false)
+
+ assertThat(underTest.isLockscreenOrShadeVisible.value).isFalse()
+ }
+
+ @Test
+ fun isLockscreenOrShadeVisibleAndInteractive_notVisible_false() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive)
+ setWakefulness(WakefulnessState.AWAKE)
+
+ underTest.setIsLockscreenOrShadeVisible(false)
+
+ assertThat(actual).isFalse()
+ }
+
+ @Test
+ fun isLockscreenOrShadeVisibleAndInteractive_deviceAsleep_false() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive)
+ underTest.setIsLockscreenOrShadeVisible(true)
+
+ setWakefulness(WakefulnessState.ASLEEP)
+
+ assertThat(actual).isFalse()
+ }
+
+ @Test
+ fun isLockscreenOrShadeVisibleAndInteractive_visibleAndAwake_true() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive)
+
+ underTest.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.AWAKE)
+
+ assertThat(actual).isTrue()
+ }
+
+ @Test
+ fun isLockscreenOrShadeVisibleAndInteractive_visibleAndStartingToWake_true() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive)
+
+ underTest.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.STARTING_TO_WAKE)
+
+ assertThat(actual).isTrue()
+ }
+
+ @Test
+ fun isLockscreenOrShadeVisibleAndInteractive_visibleAndStartingToSleep_true() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive)
+
+ underTest.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.STARTING_TO_SLEEP)
+
+ assertThat(actual).isTrue()
+ }
+
+ @Test
+ fun lockscreenShadeInteractive_statusBarServiceNotified() =
+ testScope.runTest {
+ underTest.start()
+
+ makeLockscreenShadeVisible()
+ testScope.runCurrent()
+ executor.runAllReady()
+
+ verify(iStatusBarService).onPanelRevealed(any(), any())
+ }
+
+ @Test
+ fun lockscreenShadeNotInteractive_statusBarServiceNotified() =
+ testScope.runTest {
+ underTest.start()
+
+ // First, make the shade visible
+ makeLockscreenShadeVisible()
+ testScope.runCurrent()
+ reset(iStatusBarService)
+
+ // WHEN lockscreen or shade is no longer visible
+ underTest.setIsLockscreenOrShadeVisible(false)
+ testScope.runCurrent()
+ executor.runAllReady()
+
+ // THEN status bar service is notified
+ verify(iStatusBarService).onPanelHidden()
+ }
+
+ @Test
+ fun lockscreenShadeInteractive_presenterCollapsed_notifEffectsNotCleared() =
+ testScope.runTest {
+ underTest.start()
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
+
+ makeLockscreenShadeVisible()
+
+ val shouldClearNotifEffects = argumentCaptor<Boolean>()
+ verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any())
+ assertThat(shouldClearNotifEffects.value).isFalse()
+ }
+
+ @Test
+ fun lockscreenShadeInteractive_nullPresenter_notifEffectsNotCleared() =
+ testScope.runTest {
+ underTest.start()
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+
+ underTest.setUp(presenter = null, notificationsController)
+
+ makeLockscreenShadeVisible()
+
+ val shouldClearNotifEffects = argumentCaptor<Boolean>()
+ verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any())
+ assertThat(shouldClearNotifEffects.value).isFalse()
+ }
+
+ @Test
+ fun lockscreenShadeInteractive_stateKeyguard_notifEffectsNotCleared() =
+ testScope.runTest {
+ underTest.start()
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false)
+
+ keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+
+ makeLockscreenShadeVisible()
+
+ val shouldClearNotifEffects = argumentCaptor<Boolean>()
+ verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any())
+ assertThat(shouldClearNotifEffects.value).isFalse()
+ }
+
+ @Test
+ fun lockscreenShadeInteractive_stateShade_presenterNotCollapsed_notifEffectsCleared() =
+ testScope.runTest {
+ underTest.start()
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false)
+
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+
+ makeLockscreenShadeVisible()
+
+ val shouldClearNotifEffects = argumentCaptor<Boolean>()
+ verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any())
+ assertThat(shouldClearNotifEffects.value).isTrue()
+ }
+
+ @Test
+ fun lockscreenShadeInteractive_stateShadeLocked_presenterNotCollapsed_notifEffectsCleared() =
+ testScope.runTest {
+ underTest.start()
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false)
+
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
+
+ makeLockscreenShadeVisible()
+
+ val shouldClearNotifEffects = argumentCaptor<Boolean>()
+ verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any())
+ assertThat(shouldClearNotifEffects.value).isTrue()
+ }
+
+ @Test
+ fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_notifCountOne() =
+ testScope.runTest {
+ underTest.start()
+
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
+ whenever(notificationsController.getActiveNotificationsCount()).thenReturn(4)
+
+ makeLockscreenShadeVisible()
+
+ val notifCount = argumentCaptor<Int>()
+ verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+ assertThat(notifCount.value).isEqualTo(1)
+ }
+
+ @Test
+ fun lockscreenShadeInteractive_hasHeadsUpAndNullPresenter_notifCountOne() =
+ testScope.runTest {
+ underTest.start()
+
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
+ underTest.setUp(presenter = null, notificationsController)
+
+ makeLockscreenShadeVisible()
+
+ val notifCount = argumentCaptor<Int>()
+ verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+ assertThat(notifCount.value).isEqualTo(1)
+ }
+
+ @Test
+ fun lockscreenShadeInteractive_noHeadsUp_notifCountMatchesNotifController() =
+ testScope.runTest {
+ underTest.start()
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
+
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false)
+ whenever(notificationsController.getActiveNotificationsCount()).thenReturn(9)
+
+ makeLockscreenShadeVisible()
+
+ val notifCount = argumentCaptor<Int>()
+ verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+ assertThat(notifCount.value).isEqualTo(9)
+ }
+
+ @Test
+ fun lockscreenShadeInteractive_notifPresenterNotCollapsed_notifCountMatchesNotifController() =
+ testScope.runTest {
+ underTest.start()
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
+
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false)
+ whenever(notificationsController.getActiveNotificationsCount()).thenReturn(8)
+
+ makeLockscreenShadeVisible()
+
+ val notifCount = argumentCaptor<Int>()
+ verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+ assertThat(notifCount.value).isEqualTo(8)
+ }
+
+ @Test
+ fun lockscreenShadeInteractive_noHeadsUp_noNotifController_notifCountZero() =
+ testScope.runTest {
+ underTest.start()
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
+
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false)
+ underTest.setUp(notificationPresenter, notificationsController = null)
+
+ makeLockscreenShadeVisible()
+
+ val notifCount = argumentCaptor<Int>()
+ verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+ assertThat(notifCount.value).isEqualTo(0)
+ }
+
+ private fun makeLockscreenShadeVisible() {
+ underTest.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.AWAKE)
+ testScope.runCurrent()
+ executor.runAllReady()
+ }
+
+ private fun setWakefulness(state: WakefulnessState) {
+ val model = WakefulnessModel(state, WakeSleepReason.OTHER, WakeSleepReason.OTHER)
+ keyguardRepository.setWakefulnessModel(model)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 951cadd..771c3e3 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 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.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
@@ -60,13 +62,15 @@
private val authenticationInteractor =
utils.authenticationInteractor(
repository = authenticationRepository,
+ sceneInteractor = sceneInteractor,
)
- private val keyguardRepository = utils.keyguardRepository()
+ private val keyguardRepository = utils.keyguardRepository
private val keyguardInteractor =
utils.keyguardInteractor(
repository = keyguardRepository,
)
private val sysUiState: SysUiState = mock()
+ private val falsingCollector: FalsingCollector = mock()
private val underTest =
SceneContainerStartable(
@@ -78,6 +82,7 @@
sysUiState = sysUiState,
displayId = Display.DEFAULT_DISPLAY,
sceneLogger = mock(),
+ falsingCollector = falsingCollector,
)
@Test
@@ -243,7 +248,7 @@
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 @@
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 @@
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 @@
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 @@
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 88abb642..0b56a59 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 @@
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 209dcc1d..c573ac63 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 @@
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 1738b06..dfd782b 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.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.Spy;
import java.util.List;
+import java.util.concurrent.Executor;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -98,6 +101,7 @@
@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 @@
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 3cce423..39fe498 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -36,6 +36,7 @@
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.dock.DockManager
+import com.android.systemui.dump.DumpManager
import com.android.systemui.dump.logcatLogBuffer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
@@ -56,6 +57,8 @@
import com.android.systemui.statusbar.notification.stack.AmbientState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.DozeScrimController
+import com.android.systemui.statusbar.phone.DozeServiceHost
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.window.StatusBarWindowStateController
@@ -90,6 +93,8 @@
@Mock private lateinit var view: NotificationShadeWindowView
@Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
@Mock private lateinit var centralSurfaces: CentralSurfaces
+ @Mock private lateinit var dozeServiceHost: DozeServiceHost
+ @Mock private lateinit var dozeScrimController: DozeScrimController
@Mock private lateinit var backActionInteractor: BackActionInteractor
@Mock private lateinit var powerInteractor: PowerInteractor
@Mock private lateinit var dockManager: DockManager
@@ -98,6 +103,7 @@
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
@Mock private lateinit var shadeLogger: ShadeLogger
+ @Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var ambientState: AmbientState
@Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
@Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
@@ -155,46 +161,49 @@
fakeClock = FakeSystemClock()
underTest =
NotificationShadeWindowViewController(
- lockscreenShadeTransitionController,
- FalsingCollectorFake(),
- sysuiStatusBarStateController,
- dockManager,
- notificationShadeDepthController,
- view,
- notificationPanelViewController,
- ShadeExpansionStateManager(),
- stackScrollLayoutController,
- statusBarKeyguardViewManager,
- statusBarWindowStateController,
- lockIconViewController,
- centralSurfaces,
- backActionInteractor,
- powerInteractor,
- notificationShadeWindowController,
- unfoldTransitionProgressProvider,
- keyguardUnlockAnimationController,
- notificationInsetsController,
- ambientState,
- shadeLogger,
- pulsingGestureListener,
- mLockscreenHostedDreamGestureListener,
- keyguardBouncerViewModel,
- keyguardBouncerComponentFactory,
- mock(KeyguardMessageAreaController.Factory::class.java),
- keyguardTransitionInteractor,
- primaryBouncerToGoneTransitionViewModel,
- notificationExpansionRepository,
- featureFlags,
- fakeClock,
- BouncerMessageInteractor(
- FakeBouncerMessageRepository(),
- mock(BouncerMessageFactory::class.java),
- FakeUserRepository(),
- CountDownTimerUtil(),
- featureFlags
- ),
- BouncerLogger(logcatLogBuffer("BouncerLog")),
- keyEventInteractor,
+ lockscreenShadeTransitionController,
+ FalsingCollectorFake(),
+ sysuiStatusBarStateController,
+ dockManager,
+ notificationShadeDepthController,
+ view,
+ notificationPanelViewController,
+ ShadeExpansionStateManager(),
+ stackScrollLayoutController,
+ statusBarKeyguardViewManager,
+ statusBarWindowStateController,
+ lockIconViewController,
+ centralSurfaces,
+ dozeServiceHost,
+ dozeScrimController,
+ backActionInteractor,
+ powerInteractor,
+ notificationShadeWindowController,
+ unfoldTransitionProgressProvider,
+ keyguardUnlockAnimationController,
+ notificationInsetsController,
+ ambientState,
+ shadeLogger,
+ dumpManager,
+ pulsingGestureListener,
+ mLockscreenHostedDreamGestureListener,
+ keyguardBouncerViewModel,
+ keyguardBouncerComponentFactory,
+ mock(KeyguardMessageAreaController.Factory::class.java),
+ keyguardTransitionInteractor,
+ primaryBouncerToGoneTransitionViewModel,
+ notificationExpansionRepository,
+ featureFlags,
+ fakeClock,
+ BouncerMessageInteractor(
+ FakeBouncerMessageRepository(),
+ mock(BouncerMessageFactory::class.java),
+ FakeUserRepository(),
+ CountDownTimerUtil(),
+ featureFlags
+ ),
+ BouncerLogger(logcatLogBuffer("BouncerLog")),
+ keyEventInteractor,
)
underTest.setupExpandedStatusBar()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 66d48d6..3031658 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -35,6 +35,7 @@
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.dock.DockManager
+import com.android.systemui.dump.DumpManager
import com.android.systemui.dump.logcatLogBuffer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
@@ -56,6 +57,8 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.DozeScrimController
+import com.android.systemui.statusbar.phone.DozeServiceHost
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.window.StatusBarWindowStateController
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
@@ -89,6 +92,8 @@
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
@Mock private lateinit var shadeController: ShadeController
@Mock private lateinit var centralSurfaces: CentralSurfaces
+ @Mock private lateinit var dozeServiceHost: DozeServiceHost
+ @Mock private lateinit var dozeScrimController: DozeScrimController
@Mock private lateinit var backActionInteractor: BackActionInteractor
@Mock private lateinit var powerInteractor: PowerInteractor
@Mock private lateinit var dockManager: DockManager
@@ -107,6 +112,7 @@
@Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
@Mock private lateinit var ambientState: AmbientState
@Mock private lateinit var shadeLogger: ShadeLogger
+ @Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var pulsingGestureListener: PulsingGestureListener
@Mock
private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
@@ -174,6 +180,8 @@
statusBarWindowStateController,
lockIconViewController,
centralSurfaces,
+ dozeServiceHost,
+ dozeScrimController,
backActionInteractor,
powerInteractor,
notificationShadeWindowController,
@@ -182,6 +190,7 @@
notificationInsetsController,
ambientState,
shadeLogger,
+ dumpManager,
pulsingGestureListener,
mLockscreenHostedDreamGestureListener,
keyguardBouncerViewModel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index e22e571..ab0ae05 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.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 @@
@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 @@
mAmbientState,
mRecordingController,
mFalsingManager,
- mFalsingCollector,
mAccessibilityManager,
mLockscreenGestureLogger,
mMetricsLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 6a14a00..bf2d6a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -20,20 +20,28 @@
import android.view.Display
import android.view.WindowManager
import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.IStatusBarService
+import com.android.keyguard.TestScopeProvider
import com.android.systemui.SysuiTestCase
import com.android.systemui.assist.AssistManager
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.log.LogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.notification.row.NotificationGutsManager
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
import dagger.Lazy
import org.junit.Before
import org.junit.Test
@@ -47,6 +55,8 @@
@RunWith(AndroidTestingRunner::class)
@SmallTest
class ShadeControllerImplTest : SysuiTestCase() {
+ private val executor = FakeExecutor(FakeSystemClock())
+
@Mock private lateinit var commandQueue: CommandQueue
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var statusBarStateController: StatusBarStateController
@@ -61,6 +71,17 @@
@Mock private lateinit var nswvc: NotificationShadeWindowViewController
@Mock private lateinit var display: Display
@Mock private lateinit var touchLog: LogBuffer
+ @Mock private lateinit var iStatusBarService: IStatusBarService
+ @Mock private lateinit var headsUpManager: HeadsUpManager
+
+ private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy {
+ WindowRootViewVisibilityInteractor(
+ TestScopeProvider.getTestScope(),
+ WindowRootViewVisibilityRepository(iStatusBarService, executor),
+ FakeKeyguardRepository(),
+ headsUpManager,
+ )
+ }
private lateinit var shadeController: ShadeControllerImpl
@@ -74,6 +95,7 @@
commandQueue,
FakeExecutor(FakeSystemClock()),
touchLog,
+ windowRootViewVisibilityInteractor,
keyguardStateController,
statusBarStateController,
statusBarKeyguardViewManager,
@@ -86,6 +108,7 @@
Lazy { gutsManager },
)
shadeController.setNotificationShadeWindowViewController(nswvc)
+ shadeController.setVisibilityListener(mock())
}
@Test
@@ -133,4 +156,24 @@
// VERIFY that cancelCurrentTouch is NOT called
verify(nswvc, never()).cancelCurrentTouch()
}
+
+ @Test
+ fun visible_changesToTrue_windowInteractorUpdated() {
+ shadeController.makeExpandedVisible(true)
+
+ assertThat(windowRootViewVisibilityInteractor.isLockscreenOrShadeVisible.value).isTrue()
+ }
+
+ @Test
+ fun visible_changesToFalse_windowInteractorUpdated() {
+ // GIVEN the shade is currently expanded
+ shadeController.makeExpandedVisible(true)
+ assertThat(windowRootViewVisibilityInteractor.isLockscreenOrShadeVisible.value).isTrue()
+
+ // WHEN the shade is collapsed
+ shadeController.collapseShade()
+
+ // THEN the interactor is notified
+ assertThat(windowRootViewVisibilityInteractor.isLockscreenOrShadeVisible.value).isFalse()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
index 786856b..66d2465 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
@@ -20,6 +20,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State.CONNECTED
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.privacy.PrivacyItemController
@@ -105,5 +106,7 @@
suspend fun emit(value: ConnectedDisplayInteractor.State) = flow.emit(value)
override val connectedDisplayState: Flow<ConnectedDisplayInteractor.State>
get() = flow
+ override val pendingDisplay: Flow<PendingDisplay?>
+ get() = MutableSharedFlow<PendingDisplay>()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index 7117c23..bbf0151 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -40,8 +40,14 @@
import com.android.internal.logging.InstanceId;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.keyguard.TestScopeProvider;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
+import com.android.systemui.keyguard.shared.model.WakeSleepReason;
+import com.android.systemui.keyguard.shared.model.WakefulnessModel;
+import com.android.systemui.keyguard.shared.model.WakefulnessState;
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
@@ -54,7 +60,9 @@
import com.android.systemui.statusbar.notification.logging.nano.Notifications;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.FakeSystemClock;
import com.google.android.collect.Lists;
@@ -71,6 +79,8 @@
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
+import kotlinx.coroutines.test.TestScope;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -89,7 +99,7 @@
@Mock private NotificationVisibilityProvider mVisibilityProvider;
@Mock private NotifPipeline mNotifPipeline;
@Mock private NotificationListener mListener;
- @Mock private ShadeExpansionStateManager mShadeExpansionStateManager;
+ @Mock private HeadsUpManager mHeadsUpManager;
private NotificationEntry mEntry;
private TestableNotificationLogger mLogger;
@@ -97,12 +107,23 @@
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
private NotificationPanelLoggerFake mNotificationPanelLoggerFake =
new NotificationPanelLoggerFake();
+ private final TestScope mTestScope = TestScopeProvider.getTestScope();
+ private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
+ private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
+ private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mNotifLiveDataStore.getActiveNotifList()).thenReturn(mActiveNotifEntries);
+ mWindowRootViewVisibilityInteractor = new WindowRootViewVisibilityInteractor(
+ mTestScope.getBackgroundScope(),
+ new WindowRootViewVisibilityRepository(mBarService, mUiBgExecutor),
+ mKeyguardRepository,
+ mHeadsUpManager);
+ mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
+
mEntry = new NotificationEntryBuilder()
.setPkg(TEST_PACKAGE_NAME)
.setOpPkg(TEST_PACKAGE_NAME)
@@ -120,10 +141,12 @@
mVisibilityProvider,
mNotifPipeline,
mock(StatusBarStateControllerImpl.class),
- mShadeExpansionStateManager,
+ mWindowRootViewVisibilityInteractor,
+ mJavaAdapter,
mBarService,
mExpansionStateLogger
);
+ mLogger.start();
mLogger.setUpWithContainer(mListContainer);
verify(mNotifPipeline).addCollectionListener(any());
}
@@ -183,31 +206,26 @@
Mockito.reset(mBarService);
setStateAsleep();
- mLogger.onDozingChanged(false); // Wake to lockscreen
- mLogger.onDozingChanged(true); // And go back to sleep, turning off logging
+
+ setStateAwake(); // Wake to lockscreen
+
+ setStateAsleep(); // And go back to sleep, turning off logging
mUiBgExecutor.runAllReady();
+
// The visibility objects are recycled by NotificationLogger, so we can't use specific
// matchers here.
verify(mBarService, times(1)).onNotificationVisibilityChanged(any(), any());
}
- private void setStateAsleep() {
- mLogger.onShadeExpansionFullyChanged(true);
- mLogger.onDozingChanged(true);
- mLogger.onStateChanged(StatusBarState.KEYGUARD);
- }
-
- private void setStateAwake() {
- mLogger.onShadeExpansionFullyChanged(false);
- mLogger.onDozingChanged(false);
- mLogger.onStateChanged(StatusBarState.SHADE);
- }
-
@Test
- public void testLogPanelShownOnWake() {
+ public void testLogPanelShownOnWakeToLockscreen() {
when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry));
setStateAsleep();
- mLogger.onDozingChanged(false); // Wake to lockscreen
+
+ // Wake to lockscreen
+ mLogger.onStateChanged(StatusBarState.KEYGUARD);
+ setStateAwake();
+
assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
assertTrue(mNotificationPanelLoggerFake.get(0).isLockscreen);
assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length);
@@ -222,9 +240,14 @@
@Test
public void testLogPanelShownOnShadePull() {
when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry));
+ // Start as awake, but with the panel not visible
setStateAwake();
+ mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false);
+
// Now expand panel
- mLogger.onShadeExpansionFullyChanged(true);
+ mLogger.onStateChanged(StatusBarState.SHADE);
+ mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
+
assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
assertFalse(mNotificationPanelLoggerFake.get(0).isLockscreen);
assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length);
@@ -251,13 +274,34 @@
when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(entry));
setStateAsleep();
- mLogger.onDozingChanged(false); // Wake to lockscreen
+
+ // Wake to lockscreen
+ setStateAwake();
+
assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length);
Notifications.Notification n = mNotificationPanelLoggerFake.get(0).list.notifications[0];
assertEquals(0, n.instanceId);
}
+ private void setStateAsleep() {
+ mKeyguardRepository.setWakefulnessModel(
+ new WakefulnessModel(
+ WakefulnessState.ASLEEP,
+ WakeSleepReason.OTHER,
+ WakeSleepReason.OTHER));
+ mTestScope.getTestScheduler().runCurrent();
+ }
+
+ private void setStateAwake() {
+ mKeyguardRepository.setWakefulnessModel(
+ new WakefulnessModel(
+ WakefulnessState.AWAKE,
+ WakeSleepReason.OTHER,
+ WakeSleepReason.OTHER));
+ mTestScope.getTestScheduler().runCurrent();
+ }
+
private class TestableNotificationLogger extends NotificationLogger {
TestableNotificationLogger(NotificationListener notificationListener,
@@ -266,7 +310,8 @@
NotificationVisibilityProvider visibilityProvider,
NotifPipeline notifPipeline,
StatusBarStateControllerImpl statusBarStateController,
- ShadeExpansionStateManager shadeExpansionStateManager,
+ WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
+ JavaAdapter javaAdapter,
IStatusBarService barService,
ExpansionStateLogger expansionStateLogger) {
super(
@@ -276,7 +321,8 @@
visibilityProvider,
notifPipeline,
statusBarStateController,
- shadeExpansionStateManager,
+ windowRootViewVisibilityInteractor,
+ javaAdapter,
expansionStateLogger,
mNotificationPanelLoggerFake
);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index 0cc0b98..7c8199e 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 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.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.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 @@
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 @@
/*allowLongPress=*/ false,
onUserInteractionCallback,
falsingManager,
- falsingCollector,
featureFlags,
peopleNotificationIdentifier,
Optional.of(bubblesManager),
@@ -226,20 +223,20 @@
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 @@
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 @@
whenever(view.privateLayout).thenReturn(childView)
val notification = Notification.Builder(mContext).build()
- val sbn = SbnBuilder().setNotification(notification)
- .setUser(UserHandle.of(USER_ALL))
- .build()
- whenever(view.entry).thenReturn(NotificationEntryBuilder()
- .setSbn(sbn)
- .setUser(UserHandle.of(USER_ALL))
- .build())
+ val sbn =
+ SbnBuilder().setNotification(notification).setUser(UserHandle.of(USER_ALL)).build()
+ whenever(view.entry)
+ .thenReturn(
+ NotificationEntryBuilder().setSbn(sbn).setUser(UserHandle.of(USER_ALL)).build()
+ )
controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, 9, "1")
verify(childView).setBubblesEnabledForUser(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 705d52b..9e0f83c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -37,6 +37,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -66,11 +67,16 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.keyguard.TestScopeProvider;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -84,6 +90,9 @@
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.wmshell.BubblesManager;
import org.junit.Before;
@@ -97,6 +106,8 @@
import java.util.Optional;
+import kotlinx.coroutines.test.TestScope;
+
/**
* Tests for {@link NotificationGutsManager}.
*/
@@ -108,6 +119,10 @@
private NotificationChannel mTestNotificationChannel = new NotificationChannel(
TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
+
+ private TestScope mTestScope = TestScopeProvider.getTestScope();
+ private JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
+ private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
private TestableLooper mTestableLooper;
private Handler mHandler;
private NotificationTestHelper mHelper;
@@ -124,6 +139,7 @@
@Mock private AccessibilityManager mAccessibilityManager;
@Mock private HighPriorityProvider mHighPriorityProvider;
@Mock private INotificationManager mINotificationManager;
+ @Mock private IStatusBarService mBarService;
@Mock private LauncherApps mLauncherApps;
@Mock private ShortcutManager mShortcutManager;
@Mock private ChannelEditorDialogController mChannelEditorDialogController;
@@ -140,6 +156,8 @@
@Mock private UserManager mUserManager;
+ private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
+
@Before
public void setUp() {
mTestableLooper = TestableLooper.get(this);
@@ -148,21 +166,42 @@
mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this));
when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
- mGutsManager = new NotificationGutsManager(mContext, mHandler, mHandler,
+ mWindowRootViewVisibilityInteractor = new WindowRootViewVisibilityInteractor(
+ mTestScope.getBackgroundScope(),
+ new WindowRootViewVisibilityRepository(mBarService, mExecutor),
+ new FakeKeyguardRepository(),
+ mHeadsUpManagerPhone);
+
+ mGutsManager = new NotificationGutsManager(
+ mContext,
+ mHandler,
+ mHandler,
+ mJavaAdapter,
mAccessibilityManager,
- mHighPriorityProvider, mINotificationManager, mUserManager,
- mPeopleSpaceWidgetManager, mLauncherApps, mShortcutManager,
- mChannelEditorDialogController, mContextTracker, mAssistantFeedbackController,
- Optional.of(mBubblesManager), new UiEventLoggerFake(), mOnUserInteractionCallback,
+ mHighPriorityProvider,
+ mINotificationManager,
+ mUserManager,
+ mPeopleSpaceWidgetManager,
+ mLauncherApps,
+ mShortcutManager,
+ mChannelEditorDialogController,
+ mContextTracker,
+ mAssistantFeedbackController,
+ Optional.of(mBubblesManager),
+ new UiEventLoggerFake(),
+ mOnUserInteractionCallback,
mShadeController,
+ mWindowRootViewVisibilityInteractor,
mNotificationLockscreenUserManager,
mStatusBarStateController,
mDeviceProvisionedController,
mMetricsLogger,
- mHeadsUpManagerPhone, mActivityStarter);
+ mHeadsUpManagerPhone,
+ mActivityStarter);
mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
mOnSettingsClickListener);
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
+ mGutsManager.start();
}
////////////////////////////////////////////////////////////////////////////////////////////////
@@ -210,6 +249,62 @@
}
@Test
+ public void testLockscreenShadeVisible_visible_gutsNotClosed() {
+ // First, start out lockscreen or shade as not visible
+ mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false);
+ mTestScope.getTestScheduler().runCurrent();
+
+ NotificationGuts guts = mock(NotificationGuts.class);
+ mGutsManager.setExposedGuts(guts);
+
+ // WHEN the lockscreen or shade becomes visible
+ mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
+ mTestScope.getTestScheduler().runCurrent();
+
+ // THEN the guts are not closed
+ verify(guts, never()).removeCallbacks(any());
+ verify(guts, never()).closeControls(
+ anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void testLockscreenShadeVisible_notVisible_gutsClosed() {
+ // First, start out lockscreen or shade as visible
+ mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
+ mTestScope.getTestScheduler().runCurrent();
+
+ NotificationGuts guts = mock(NotificationGuts.class);
+ mGutsManager.setExposedGuts(guts);
+
+ // WHEN the lockscreen or shade is no longer visible
+ mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false);
+ mTestScope.getTestScheduler().runCurrent();
+
+ // THEN the guts are closed
+ verify(guts).removeCallbacks(any());
+ verify(guts).closeControls(
+ /* leavebehinds= */ eq(true),
+ /* controls= */ eq(true),
+ /* x= */ anyInt(),
+ /* y= */ anyInt(),
+ /* force= */ eq(true));
+ }
+
+ @Test
+ public void testLockscreenShadeVisible_notVisible_listContainerReset() {
+ // First, start out lockscreen or shade as visible
+ mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
+ mTestScope.getTestScheduler().runCurrent();
+
+ // WHEN the lockscreen or shade is no longer visible
+ mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false);
+ mTestScope.getTestScheduler().runCurrent();
+
+ // THEN the list container is reset
+ verify(mNotificationListContainer).resetExposedMenuView(anyBoolean(), anyBoolean());
+ }
+
+ @Test
public void testChangeDensityOrFontScale() {
NotificationGuts guts = spy(new NotificationGuts(mContext));
when(guts.post(any())).thenAnswer(invocation -> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 0425830..9dfcb3f 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 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 @@
mock(OnExpandClickListener.class),
mock(ExpandableNotificationRow.CoordinateOnClickListener.class),
new FalsingManagerFake(),
- new FalsingCollectorFake(),
mStatusBarStateController,
mPeopleNotificationIdentifier,
mOnUserInteractionCallback,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index f47efe3..5a1450f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -24,7 +24,6 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-import static junit.framework.TestCase.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -62,7 +61,6 @@
import android.os.IThermalService;
import android.os.Looper;
import android.os.PowerManager;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.service.dreams.IDreamManager;
import android.support.test.metricshelper.MetricsAsserts;
@@ -73,13 +71,7 @@
import android.util.SparseArray;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
-import android.view.ViewRootImpl;
import android.view.WindowManager;
-import android.window.BackEvent;
-import android.window.OnBackAnimationCallback;
-import android.window.OnBackInvokedCallback;
-import android.window.OnBackInvokedDispatcher;
-import android.window.WindowOnBackInvokedDispatcher;
import androidx.test.filters.SmallTest;
@@ -125,6 +117,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.recents.ScreenPinningRequest;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.shade.CameraLauncher;
@@ -158,7 +151,6 @@
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
@@ -167,10 +159,7 @@
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
@@ -199,7 +188,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -227,7 +215,6 @@
@Mock private KeyguardIndicationController mKeyguardIndicationController;
@Mock private NotificationStackScrollLayout mStackScroller;
@Mock private NotificationStackScrollLayoutController mStackScrollerController;
- @Mock private NotificationListContainer mNotificationListContainer;
@Mock private HeadsUpManagerPhone mHeadsUpManager;
@Mock private NotificationPanelViewController mNotificationPanelViewController;
@Mock private ShadeLogger mShadeLogger;
@@ -254,7 +241,6 @@
@Mock private StatusBarNotificationPresenter mNotificationPresenter;
@Mock private NotificationActivityStarter mNotificationActivityStarter;
@Mock private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
- @Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock private StatusBarSignalPolicy mStatusBarSignalPolicy;
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@@ -325,18 +311,11 @@
@Mock private Lazy<CameraLauncher> mCameraLauncherLazy;
@Mock private CameraLauncher mCameraLauncher;
@Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
- /**
- * The process of registering/unregistering a predictive back callback requires a
- * ViewRootImpl, which is present IRL, but may be missing during a Mockito unit test.
- * To prevent an NPE during test execution, we explicitly craft and provide a fake ViewRootImpl.
- */
- @Mock private ViewRootImpl mViewRootImpl;
- @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
@Mock private UserTracker mUserTracker;
@Mock private FingerprintManager mFingerprintManager;
- @Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
@Mock IPowerManager mPowerManagerService;
@Mock ActivityStarter mActivityStarter;
+ @Mock private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
private ShadeController mShadeController;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
@@ -388,18 +367,6 @@
mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class));
mMetricsLogger = new FakeMetricsLogger();
- NotificationLogger notificationLogger = new NotificationLogger(
- mNotificationListener,
- mUiBgExecutor,
- mNotifLiveDataStore,
- mVisibilityProvider,
- mock(NotifPipeline.class),
- mStatusBarStateController,
- mShadeExpansionStateManager,
- mExpansionStateLogger,
- new NotificationPanelLoggerFake()
- );
- notificationLogger.setVisibilityReporter(mock(Runnable.class));
when(mCommandQueue.asBinder()).thenReturn(new Binder());
@@ -448,6 +415,7 @@
mCommandQueue,
mMainExecutor,
mock(LogBuffer.class),
+ mock(WindowRootViewVisibilityInteractor.class),
mKeyguardStateController,
mStatusBarStateController,
mStatusBarKeyguardViewManager,
@@ -490,7 +458,6 @@
new FalsingCollectorFake(),
mBroadcastDispatcher,
mNotificationGutsManager,
- notificationLogger,
mNotificationInterruptStateProvider,
new ShadeExpansionStateManager(),
mKeyguardViewMediator,
@@ -541,6 +508,7 @@
() -> mCentralSurfacesCommandQueueCallbacks,
mPluginManager,
mShadeController,
+ mWindowRootViewVisibilityInteractor,
mStatusBarKeyguardViewManager,
mViewMediatorCallback,
mInitController,
@@ -578,16 +546,9 @@
mUserTracker,
() -> mFingerprintManager,
mActivityStarter
- ) {
- @Override
- protected ViewRootImpl getViewRootImpl() {
- return mViewRootImpl;
- }
- };
+ );
mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver);
mCentralSurfaces.initShadeVisibilityListener();
- when(mViewRootImpl.getOnBackInvokedDispatcher())
- .thenReturn(mOnBackInvokedDispatcher);
when(mKeyguardViewMediator.registerCentralSurfaces(
any(CentralSurfacesImpl.class),
any(NotificationPanelViewController.class),
@@ -609,7 +570,6 @@
PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "sysui:GestureWakeLock");
mCentralSurfaces.startKeyguard();
mInitController.executePostInitTasks();
- notificationLogger.setUpWithContainer(mNotificationListContainer);
mCentralSurfaces.registerCallbacks();
}
@@ -799,151 +759,6 @@
}
@Test
- public void testLogHidden() {
- try {
- mCentralSurfaces.handleVisibleToUserChanged(false);
- mUiBgExecutor.runAllReady();
- verify(mBarService, times(1)).onPanelHidden();
- verify(mBarService, never()).onPanelRevealed(anyBoolean(), anyInt());
- } catch (RemoteException e) {
- fail();
- }
- }
-
- /**
- * Do the following:
- * 1. verify that a predictive back callback is registered when CSurf becomes visible
- * 2. verify that the same callback is unregistered when CSurf becomes invisible
- */
- @Test
- public void testPredictiveBackCallback_registration() {
- mCentralSurfaces.handleVisibleToUserChanged(true);
- verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
- eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
- mOnBackInvokedCallback.capture());
-
- mCentralSurfaces.handleVisibleToUserChanged(false);
- verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback(
- eq(mOnBackInvokedCallback.getValue()));
- }
-
- /**
- * Do the following:
- * 1. capture the predictive back callback during registration
- * 2. call the callback directly
- * 3. verify that the ShadeController's panel collapse animation is invoked
- */
- @Test
- public void testPredictiveBackCallback_invocationCollapsesPanel() {
- mCentralSurfaces.handleVisibleToUserChanged(true);
- verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
- eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
- mOnBackInvokedCallback.capture());
-
- when(mBackActionInteractor.shouldBackBeHandled()).thenReturn(true);
- mOnBackInvokedCallback.getValue().onBackInvoked();
- verify(mBackActionInteractor).onBackRequested();
- }
-
- /**
- * When back progress is at 100%, the onBackProgressed animation driver inside
- * NotificationPanelViewController should be invoked appropriately (with 1.0f passed in).
- */
- @Test
- public void testPredictiveBackAnimation_progressMaxScalesPanel() {
- mCentralSurfaces.handleVisibleToUserChanged(true);
- verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
- eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
- mOnBackInvokedCallback.capture());
-
- OnBackAnimationCallback onBackAnimationCallback =
- (OnBackAnimationCallback) (mOnBackInvokedCallback.getValue());
- when(mBackActionInteractor.shouldBackBeHandled()).thenReturn(true);
- when(mNotificationPanelViewController.canBeCollapsed()).thenReturn(true);
-
- BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 100.0f, 1.0f, BackEvent.EDGE_LEFT);
- onBackAnimationCallback.onBackProgressed(fakeSwipeInFromLeftEdge);
- verify(mNotificationPanelViewController).onBackProgressed(eq(1.0f));
- }
-
- /**
- * When back progress is at 0%, the onBackProgressed animation driver inside
- * NotificationPanelViewController should be invoked appropriately (with 0.0f passed in).
- */
- @Test
- public void testPredictiveBackAnimation_progressMinScalesPanel() {
- mCentralSurfaces.handleVisibleToUserChanged(true);
- verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
- eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
- mOnBackInvokedCallback.capture());
-
- OnBackAnimationCallback onBackAnimationCallback =
- (OnBackAnimationCallback) (mOnBackInvokedCallback.getValue());
- when(mBackActionInteractor.shouldBackBeHandled()).thenReturn(true);
- when(mNotificationPanelViewController.canBeCollapsed()).thenReturn(true);
-
- BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 10.0f, 0.0f, BackEvent.EDGE_LEFT);
- onBackAnimationCallback.onBackProgressed(fakeSwipeInFromLeftEdge);
- verify(mNotificationPanelViewController).onBackProgressed(eq(0.0f));
- }
-
- @Test
- public void testPanelOpenForHeadsUp() {
- when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
- when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
- when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5);
- when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(true);
- mCentralSurfaces.setBarStateForTest(SHADE);
-
- try {
- mCentralSurfaces.handleVisibleToUserChanged(true);
- mUiBgExecutor.runAllReady();
- verify(mBarService, never()).onPanelHidden();
- verify(mBarService, times(1)).onPanelRevealed(false, 1);
- } catch (RemoteException e) {
- fail();
- }
- mMainExecutor.runAllReady();
- }
-
- @Test
- public void testPanelOpenAndClear() {
- when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
- when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5);
-
- when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(false);
- mCentralSurfaces.setBarStateForTest(SHADE);
-
- try {
- mCentralSurfaces.handleVisibleToUserChanged(true);
- mUiBgExecutor.runAllReady();
- verify(mBarService, never()).onPanelHidden();
- verify(mBarService, times(1)).onPanelRevealed(true, 5);
- } catch (RemoteException e) {
- fail();
- }
- mMainExecutor.runAllReady();
- }
-
- @Test
- public void testPanelOpenAndNoClear() {
- when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
- when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5);
- when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(false);
- mCentralSurfaces.setBarStateForTest(StatusBarState.KEYGUARD);
-
- try {
- mCentralSurfaces.handleVisibleToUserChanged(true);
- mUiBgExecutor.runAllReady();
- verify(mBarService, never()).onPanelHidden();
- verify(mBarService, times(1)).onPanelRevealed(false, 5);
- } catch (RemoteException e) {
- fail();
- }
- mMainExecutor.runAllReady();
- }
-
- @Test
public void testDump_DoesNotCrash() {
mCentralSurfaces.dump(new PrintWriter(new ByteArrayOutputStream()), null);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index 9795b9d..71c27de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
import com.android.systemui.privacy.PrivacyItemController
import com.android.systemui.privacy.logging.PrivacyLogger
@@ -316,5 +317,7 @@
suspend fun emit(value: State) = flow.emit(value)
override val connectedDisplayState: Flow<State>
get() = flow
+ override val pendingDisplay: Flow<PendingDisplay?>
+ get() = TODO("Not yet implemented")
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt
index 08e89fb..6f04f36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt
@@ -220,30 +220,6 @@
/** Regression test for b/255428281. */
@Test
- fun internalAndExternalIconWithSameName_internalRemoved_viaRemoveAll_externalStays() {
- val slotName = "mute"
-
- // Internal
- underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
-
- // External
- underTest.setIconFromTile(slotName, createExternalIcon())
-
- // WHEN the internal icon is removed via #removeAllIconsForSlot
- underTest.removeAllIconsForSlot(slotName)
-
- // THEN the internal icon is removed but the external icon remains
- assertThat(iconList.slots).hasSize(2)
- assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
- assertThat(iconList.slots[1].name).isEqualTo(slotName)
- assertThat(iconList.slots[0].hasIconsInSlot()).isTrue()
- assertThat(iconList.slots[1].hasIconsInSlot()).isFalse() // Indicates removal
-
- verify(iconGroup).onRemoveIcon(1)
- }
-
- /** Regression test for b/255428281. */
- @Test
fun internalAndExternalIconWithSameName_internalUpdatedIndependently() {
val slotName = "mute"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 085ec27..0ff6f20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -120,34 +120,6 @@
verify(manager, never()).onRemoveIcon(anyInt());
}
- @Test
- public void testRemoveAllIconsForSlot_ignoredForNewPipeline() {
- IconManager manager = mock(IconManager.class);
-
- // GIVEN the new pipeline is on
- StatusBarPipelineFlags flags = mock(StatusBarPipelineFlags.class);
- when(flags.isIconControlledByFlags("test_icon")).thenReturn(true);
-
- StatusBarIconController iconController = new StatusBarIconControllerImpl(
- mContext,
- mock(CommandQueue.class),
- mock(DemoModeController.class),
- mock(ConfigurationController.class),
- mock(TunerService.class),
- mock(DumpManager.class),
- mock(StatusBarIconList.class),
- flags
- );
-
- iconController.addIconGroup(manager);
-
- // WHEN a request to remove a new icon is sent
- iconController.removeAllIconsForSlot("test_icon");
-
- // THEN it is not removed for those icons
- verify(manager, never()).onRemoveIcon(anyInt());
- }
-
private <T extends IconManager & TestableIconManager> void testCallOnAdd_forManager(T manager) {
StatusBarIconHolder holder = holderForType(TYPE_ICON);
manager.onIconAdded(0, "test_slot", false, holder);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractorTest.kt
new file mode 100644
index 0000000..5784be3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractorTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.ethernet.domain
+
+import androidx.test.filters.SmallTest
+import com.android.settingslib.AccessibilityContentDescriptions
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+@SmallTest
+class EthernetInteractorTest : SysuiTestCase() {
+ private val connectivityRepository = FakeConnectivityRepository()
+ private val underTest = EthernetInteractor(connectivityRepository)
+
+ private val testScope = TestScope()
+
+ @Test
+ fun icon_default_validated() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ connectivityRepository.setEthernetConnected(default = true, validated = true)
+
+ val expected =
+ Icon.Resource(
+ R.drawable.stat_sys_ethernet_fully,
+ ContentDescription.Resource(
+ AccessibilityContentDescriptions.ETHERNET_CONNECTION_VALUES[1]
+ )
+ )
+
+ assertThat(latest).isEqualTo(expected)
+ }
+
+ @Test
+ fun icon_default_notValidated() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ connectivityRepository.setEthernetConnected(default = true, validated = false)
+
+ val expected =
+ Icon.Resource(
+ R.drawable.stat_sys_ethernet,
+ ContentDescription.Resource(
+ AccessibilityContentDescriptions.ETHERNET_CONNECTION_VALUES[0]
+ )
+ )
+
+ assertThat(latest).isEqualTo(expected)
+ }
+
+ @Test
+ fun icon_notDefault_validated() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ connectivityRepository.setEthernetConnected(default = false, validated = true)
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun icon_notDefault_notValidated() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ connectivityRepository.setEthernetConnected(default = false, validated = false)
+
+ assertThat(latest).isNull()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index ff28753..812e91b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -64,6 +64,28 @@
_dataEnabled.value = enabled
}
+ /**
+ * Set [primaryLevel] and [cdmaLevel]. Convenient when you don't care about the connection type
+ */
+ fun setAllLevels(level: Int) {
+ cdmaLevel.value = level
+ primaryLevel.value = level
+ }
+
+ /**
+ * Set both [isRoaming] and [cdmaRoaming] properties, in the event that you don't care about the
+ * connection type
+ */
+ fun setAllRoaming(roaming: Boolean) {
+ isRoaming.value = roaming
+ cdmaRoaming.value = roaming
+ }
+
+ /** Set the correct [resolvedNetworkType] for the given group via its lookup key */
+ fun setNetworkTypeKey(key: String) {
+ resolvedNetworkType.value = ResolvedNetworkType.DefaultNetworkType(key)
+ }
+
companion object {
const val DEFAULT_NETWORK_NAME = "default name"
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index 9c0cb17..ede02d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -674,6 +674,7 @@
val realRepo =
MobileConnectionRepositoryImpl(
SUB_ID,
+ context,
subscriptionModel,
DEFAULT_NAME_MODEL,
SEP,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index e50e5e3..3af960b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -16,18 +16,22 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+import android.content.BroadcastReceiver
+import android.content.Context
import android.content.Intent
import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
import android.telephony.NetworkRegistrationInfo
import android.telephony.ServiceState
import android.telephony.ServiceState.STATE_IN_SERVICE
import android.telephony.ServiceState.STATE_OUT_OF_SERVICE
+import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.DataActivityListener
import android.telephony.TelephonyCallback.ServiceStateListener
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED
import android.telephony.TelephonyManager.DATA_ACTIVITY_DORMANT
import android.telephony.TelephonyManager.DATA_ACTIVITY_IN
import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT
@@ -75,6 +79,7 @@
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -88,6 +93,7 @@
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@@ -100,6 +106,7 @@
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: MobileInputLogger
@Mock private lateinit var tableLogger: TableLogBuffer
+ @Mock private lateinit var context: Context
private val mobileMappings = FakeMobileMappingsProxy()
private val systemUiCarrierConfig =
@@ -129,6 +136,7 @@
underTest =
MobileConnectionRepositoryImpl(
SUB_1_ID,
+ context,
subscriptionModel,
DEFAULT_NAME_MODEL,
SEP,
@@ -706,7 +714,9 @@
val job = underTest.networkName.onEach { latest = it }.launchIn(this)
val intent = spnIntent()
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ captor.value!!.onReceive(context, intent)
assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
@@ -720,13 +730,16 @@
val job = underTest.networkName.onEach { latest = it }.launchIn(this)
val intent = spnIntent()
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ captor.value!!.onReceive(context, intent)
+
assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
// WHEN an intent with a different subId is sent
val wrongSubIntent = spnIntent(subId = 101)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, wrongSubIntent)
+ captor.value!!.onReceive(context, wrongSubIntent)
// THEN the previous intent's name is still used
assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
@@ -741,7 +754,10 @@
val job = underTest.networkName.onEach { latest = it }.launchIn(this)
val intent = spnIntent()
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ captor.value!!.onReceive(context, intent)
+
assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
val intentWithoutInfo =
@@ -750,7 +766,7 @@
showPlmn = false,
)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intentWithoutInfo)
+ captor.value!!.onReceive(context, intentWithoutInfo)
assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
@@ -893,7 +909,7 @@
plmn: String = PLMN,
): Intent =
Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED).apply {
- putExtra(EXTRA_SUBSCRIPTION_ID, subId)
+ putExtra(EXTRA_SUBSCRIPTION_INDEX, subId)
putExtra(EXTRA_SHOW_SPN, showSpn)
putExtra(EXTRA_SPN, spn)
putExtra(EXTRA_SHOW_PLMN, showPlmn)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
index ea60aa7..852ed20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
@@ -125,6 +125,7 @@
underTest =
MobileConnectionRepositoryImpl(
SUB_1_ID,
+ context,
subscriptionModel,
DEFAULT_NAME,
SEP,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index fd05cc4..6f9764a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -175,6 +175,7 @@
connectionFactory =
MobileConnectionRepositoryImpl.Factory(
+ context,
fakeBroadcastDispatcher,
telephonyManager = telephonyManager,
bgDispatcher = dispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index a3df785..de2b6a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -16,12 +16,11 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
-import android.telephony.CellSignalStrength
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.flow.MutableStateFlow
@@ -30,8 +29,6 @@
) : MobileIconInteractor {
override val alwaysShowDataRatIcon = MutableStateFlow(false)
- override val alwaysUseCdmaLevel = MutableStateFlow(false)
-
override val activity =
MutableStateFlow(
DataActivityModel(
@@ -55,14 +52,8 @@
override val carrierName = MutableStateFlow("demo mode")
- private val _isEmergencyOnly = MutableStateFlow(false)
- override val isEmergencyOnly = _isEmergencyOnly
-
override val isRoaming = MutableStateFlow(false)
- private val _isFailedConnection = MutableStateFlow(false)
- override val isDefaultConnectionFailed = _isFailedConnection
-
override val isDataConnected = MutableStateFlow(true)
override val isInService = MutableStateFlow(true)
@@ -70,40 +61,21 @@
private val _isDataEnabled = MutableStateFlow(true)
override val isDataEnabled = _isDataEnabled
- private val _isDefaultDataEnabled = MutableStateFlow(true)
- override val isDefaultDataEnabled = _isDefaultDataEnabled
-
- private val _level = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
- override val level = _level
-
- private val _numberOfLevels = MutableStateFlow(DEFAULT_NUM_LEVELS)
- override val numberOfLevels = _numberOfLevels
-
override val isForceHidden = MutableStateFlow(false)
override val isAllowedDuringAirplaneMode = MutableStateFlow(false)
- fun setIsEmergencyOnly(emergency: Boolean) {
- _isEmergencyOnly.value = emergency
- }
+ override val signalLevelIcon: MutableStateFlow<SignalIconModel> =
+ MutableStateFlow(
+ SignalIconModel(
+ level = 0,
+ numberOfLevels = 4,
+ showExclamationMark = false,
+ carrierNetworkChange = false,
+ )
+ )
fun setIsDataEnabled(enabled: Boolean) {
_isDataEnabled.value = enabled
}
-
- fun setIsDefaultDataEnabled(disabled: Boolean) {
- _isDefaultDataEnabled.value = disabled
- }
-
- fun setIsFailedConnection(failed: Boolean) {
- _isFailedConnection.value = failed
- }
-
- fun setLevel(level: Int) {
- _level.value = level
- }
-
- fun setNumberOfLevels(num: Int) {
- _numberOfLevels.value = num
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 82b7ec4..75d1869 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -58,6 +58,8 @@
private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
+ override val activeDataIconInteractor = MutableStateFlow(null)
+
override val alwaysShowDataRatIcon = MutableStateFlow(false)
override val alwaysUseCdmaLevel = MutableStateFlow(false)
@@ -78,7 +80,7 @@
override val isForceHidden = MutableStateFlow(false)
/** Always returns a new fake interactor */
- override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor {
+ override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor {
return FakeMobileIconInteractor(tableLogBuffer).also { interactorCache[subId] = it }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index e3c59ad..e2f9119 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -35,6 +35,7 @@
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FOUR_G
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.THREE_G
import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -75,22 +76,12 @@
@Before
fun setUp() {
underTest = createInteractor()
+
+ mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true
+ connectionRepository.isInService.value = true
}
@Test
- fun gsm_level_default_unknown() =
- testScope.runTest {
- connectionRepository.isGsm.value = true
-
- var latest: Int? = null
- val job = underTest.level.onEach { latest = it }.launchIn(this)
-
- assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
-
- job.cancel()
- }
-
- @Test
fun gsm_usesGsmLevel() =
testScope.runTest {
connectionRepository.isGsm.value = true
@@ -98,7 +89,7 @@
connectionRepository.cdmaLevel.value = CDMA_LEVEL
var latest: Int? = null
- val job = underTest.level.onEach { latest = it }.launchIn(this)
+ val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
assertThat(latest).isEqualTo(GSM_LEVEL)
@@ -114,7 +105,7 @@
mobileIconsInteractor.alwaysUseCdmaLevel.value = true
var latest: Int? = null
- val job = underTest.level.onEach { latest = it }.launchIn(this)
+ val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
assertThat(latest).isEqualTo(GSM_LEVEL)
@@ -127,7 +118,7 @@
connectionRepository.isGsm.value = false
var latest: Int? = null
- val job = underTest.level.onEach { latest = it }.launchIn(this)
+ val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
job.cancel()
@@ -142,7 +133,7 @@
mobileIconsInteractor.alwaysUseCdmaLevel.value = true
var latest: Int? = null
- val job = underTest.level.onEach { latest = it }.launchIn(this)
+ val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
assertThat(latest).isEqualTo(CDMA_LEVEL)
@@ -158,7 +149,7 @@
mobileIconsInteractor.alwaysUseCdmaLevel.value = false
var latest: Int? = null
- val job = underTest.level.onEach { latest = it }.launchIn(this)
+ val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
assertThat(latest).isEqualTo(GSM_LEVEL)
@@ -169,7 +160,7 @@
fun numberOfLevels_comesFromRepo() =
testScope.runTest {
var latest: Int? = null
- val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this)
+ val job = underTest.signalLevelIcon.onEach { latest = it.numberOfLevels }.launchIn(this)
connectionRepository.numberOfLevels.value = 5
assertThat(latest).isEqualTo(5)
@@ -295,50 +286,6 @@
}
@Test
- fun alwaysUseCdmaLevel_matchesParent() =
- testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.alwaysUseCdmaLevel.onEach { latest = it }.launchIn(this)
-
- mobileIconsInteractor.alwaysUseCdmaLevel.value = true
- assertThat(latest).isTrue()
-
- mobileIconsInteractor.alwaysUseCdmaLevel.value = false
- assertThat(latest).isFalse()
-
- job.cancel()
- }
-
- @Test
- fun test_isDefaultDataEnabled_matchesParent() =
- testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isDefaultDataEnabled.onEach { latest = it }.launchIn(this)
-
- mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true
- assertThat(latest).isTrue()
-
- mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = false
- assertThat(latest).isFalse()
-
- job.cancel()
- }
-
- @Test
- fun test_isDefaultConnectionFailed_matchedParent() =
- testScope.runTest {
- val job = underTest.isDefaultConnectionFailed.launchIn(this)
-
- mobileIconsInteractor.isDefaultConnectionFailed.value = false
- assertThat(underTest.isDefaultConnectionFailed.value).isFalse()
-
- mobileIconsInteractor.isDefaultConnectionFailed.value = true
- assertThat(underTest.isDefaultConnectionFailed.value).isTrue()
-
- job.cancel()
- }
-
- @Test
fun dataState_connected() =
testScope.runTest {
var latest: Boolean? = null
@@ -541,6 +488,142 @@
assertThat(latest).isFalse()
}
+ @Test
+ fun iconId_correctLevel_notCutout() =
+ testScope.runTest {
+ connectionRepository.isInService.value = true
+ connectionRepository.primaryLevel.value = 1
+ connectionRepository.setDataEnabled(false)
+
+ var latest: SignalIconModel? = null
+ val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest?.level).isEqualTo(1)
+ assertThat(latest?.showExclamationMark).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun icon_usesLevelFromInteractor() =
+ testScope.runTest {
+ connectionRepository.isInService.value = true
+
+ var latest: SignalIconModel? = null
+ val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.primaryLevel.value = 3
+ assertThat(latest!!.level).isEqualTo(3)
+
+ connectionRepository.primaryLevel.value = 1
+ assertThat(latest!!.level).isEqualTo(1)
+
+ job.cancel()
+ }
+
+ @Test
+ fun icon_usesNumberOfLevelsFromInteractor() =
+ testScope.runTest {
+ var latest: SignalIconModel? = null
+ val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.numberOfLevels.value = 5
+ assertThat(latest!!.numberOfLevels).isEqualTo(5)
+
+ connectionRepository.numberOfLevels.value = 2
+ assertThat(latest!!.numberOfLevels).isEqualTo(2)
+
+ job.cancel()
+ }
+
+ @Test
+ fun icon_defaultDataDisabled_showExclamationTrue() =
+ testScope.runTest {
+ mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = false
+
+ var latest: SignalIconModel? = null
+ val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest!!.showExclamationMark).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun icon_defaultConnectionFailed_showExclamationTrue() =
+ testScope.runTest {
+ mobileIconsInteractor.isDefaultConnectionFailed.value = true
+
+ var latest: SignalIconModel? = null
+ val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest!!.showExclamationMark).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun icon_enabledAndNotFailed_showExclamationFalse() =
+ testScope.runTest {
+ connectionRepository.isInService.value = true
+ mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true
+ mobileIconsInteractor.isDefaultConnectionFailed.value = false
+
+ var latest: SignalIconModel? = null
+ val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest!!.showExclamationMark).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun icon_usesEmptyState_whenNotInService() =
+ testScope.runTest {
+ var latest: SignalIconModel? = null
+ val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.isInService.value = false
+
+ assertThat(latest?.level).isEqualTo(0)
+ assertThat(latest?.showExclamationMark).isTrue()
+
+ // Changing the level doesn't overwrite the disabled state
+ connectionRepository.primaryLevel.value = 2
+ assertThat(latest?.level).isEqualTo(0)
+ assertThat(latest?.showExclamationMark).isTrue()
+
+ // Once back in service, the regular icon appears
+ connectionRepository.isInService.value = true
+ assertThat(latest?.level).isEqualTo(2)
+ assertThat(latest?.showExclamationMark).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun icon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() =
+ testScope.runTest {
+ var latest: SignalIconModel? = null
+ val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.isInService.value = true
+ connectionRepository.carrierNetworkChangeActive.value = true
+ connectionRepository.primaryLevel.value = 1
+ connectionRepository.cdmaLevel.value = 1
+
+ assertThat(latest!!.level).isEqualTo(1)
+ assertThat(latest!!.carrierNetworkChange).isTrue()
+
+ // SignalIconModel respects the current level
+ connectionRepository.primaryLevel.value = 2
+
+ assertThat(latest!!.level).isEqualTo(2)
+ assertThat(latest!!.carrierNetworkChange).isTrue()
+
+ job.cancel()
+ }
+
private fun createInteractor(
overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 3e6f909..b4c7578 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -781,6 +781,16 @@
job.cancel()
}
+ @Test
+ fun iconInteractor_cachedPerSubId() =
+ testScope.runTest {
+ val interactor1 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID)
+ val interactor2 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID)
+
+ assertThat(interactor1).isNotNull()
+ assertThat(interactor1).isSameInstanceAs(interactor2)
+ }
+
/**
* Convenience method for creating a pair of subscriptions to test the filteredSubscriptions
* flow.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
index 01c388a..90a8946 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
@@ -19,6 +19,7 @@
import androidx.test.filters.SmallTest
import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
index e59d90f..1878329 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -17,18 +17,26 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import androidx.test.filters.SmallTest
-import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
-import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
-import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModelTest.Companion.defaultSignal
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -50,11 +58,18 @@
private lateinit var homeIcon: HomeMobileIconViewModel
private lateinit var qsIcon: QsMobileIconViewModel
private lateinit var keyguardIcon: KeyguardMobileIconViewModel
- private lateinit var interactor: FakeMobileIconInteractor
+ private lateinit var iconsInteractor: MobileIconsInteractor
+ private lateinit var interactor: MobileIconInteractor
+ private lateinit var connectionsRepository: FakeMobileConnectionsRepository
+ private lateinit var repository: FakeMobileConnectionRepository
private lateinit var airplaneModeInteractor: AirplaneModeInteractor
+
+ private val connectivityRepository = FakeConnectivityRepository()
+
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var constants: ConnectivityConstants
@Mock private lateinit var tableLogBuffer: TableLogBuffer
+ @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -67,16 +82,51 @@
FakeAirplaneModeRepository(),
FakeConnectivityRepository(),
)
- interactor = FakeMobileIconInteractor(tableLogBuffer)
- interactor.apply {
- setLevel(1)
- setIsDefaultDataEnabled(true)
- setIsFailedConnection(false)
- setIsEmergencyOnly(false)
- setNumberOfLevels(4)
- networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
- isDataConnected.value = true
- }
+ connectionsRepository =
+ FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogBuffer)
+ repository =
+ FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer).apply {
+ isInService.value = true
+ cdmaLevel.value = 1
+ primaryLevel.value = 1
+ isEmergencyOnly.value = false
+ numberOfLevels.value = 4
+ resolvedNetworkType.value = ResolvedNetworkType.DefaultNetworkType(lookupKey = "3G")
+ dataConnectionState.value = DataConnectionState.Connected
+ }
+
+ connectionsRepository.activeMobileDataRepository.value = repository
+
+ connectivityRepository.apply { setMobileConnected() }
+
+ iconsInteractor =
+ MobileIconsInteractorImpl(
+ connectionsRepository,
+ carrierConfigTracker,
+ tableLogBuffer,
+ connectivityRepository,
+ FakeUserSetupRepository(),
+ testScope.backgroundScope,
+ context,
+ )
+
+ interactor =
+ MobileIconInteractorImpl(
+ testScope.backgroundScope,
+ iconsInteractor.activeDataConnectionHasDataEnabled,
+ iconsInteractor.alwaysShowDataRatIcon,
+ iconsInteractor.alwaysUseCdmaLevel,
+ iconsInteractor.isSingleCarrier,
+ iconsInteractor.mobileIsDefault,
+ iconsInteractor.defaultMobileIconMapping,
+ iconsInteractor.defaultMobileIconGroup,
+ iconsInteractor.isDefaultConnectionFailed,
+ iconsInteractor.isForceHidden,
+ repository,
+ context,
+ MobileIconCarrierIdOverridesFake()
+ )
+
commonImpl =
MobileIconViewModel(
SUB_1_ID,
@@ -109,7 +159,7 @@
assertThat(latestQs).isEqualTo(expected)
assertThat(latestKeyguard).isEqualTo(expected)
- interactor.setLevel(2)
+ repository.setAllLevels(2)
expected = defaultSignal(level = 2)
assertThat(latestHome).isEqualTo(expected)
@@ -123,5 +173,16 @@
companion object {
private const val SUB_1_ID = 1
+ private const val NUM_LEVELS = 4
+
+ /** Convenience constructor for these tests */
+ fun defaultSignal(level: Int = 1): SignalIconModel {
+ return SignalIconModel(
+ level,
+ NUM_LEVELS,
+ showExclamationMark = false,
+ carrierNetworkChange = false,
+ )
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index 72feec7..796d5ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -19,20 +19,30 @@
import androidx.test.filters.SmallTest
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.TelephonyIcons.G
import com.android.settingslib.mobile.TelephonyIcons.THREE_G
import com.android.settingslib.mobile.TelephonyIcons.UNKNOWN
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
-import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
-import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -51,12 +61,18 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class MobileIconViewModelTest : SysuiTestCase() {
+ private var connectivityRepository = FakeConnectivityRepository()
+
private lateinit var underTest: MobileIconViewModel
- private lateinit var interactor: FakeMobileIconInteractor
+ private lateinit var interactor: MobileIconInteractorImpl
+ private lateinit var iconsInteractor: MobileIconsInteractorImpl
+ private lateinit var repository: FakeMobileConnectionRepository
+ private lateinit var connectionsRepository: FakeMobileConnectionsRepository
private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
private lateinit var airplaneModeInteractor: AirplaneModeInteractor
@Mock private lateinit var constants: ConnectivityConstants
@Mock private lateinit var tableLogBuffer: TableLogBuffer
+ @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -66,23 +82,53 @@
MockitoAnnotations.initMocks(this)
whenever(constants.hasDataCapabilities).thenReturn(true)
+ connectionsRepository =
+ FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogBuffer)
+
+ repository =
+ FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer).apply {
+ setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ isInService.value = true
+ dataConnectionState.value = DataConnectionState.Connected
+ dataEnabled.value = true
+ }
+ connectionsRepository.activeMobileDataRepository.value = repository
+ connectionsRepository.mobileIsDefault.value = true
+
airplaneModeRepository = FakeAirplaneModeRepository()
airplaneModeInteractor =
AirplaneModeInteractor(
airplaneModeRepository,
- FakeConnectivityRepository(),
+ connectivityRepository,
)
- interactor = FakeMobileIconInteractor(tableLogBuffer)
- interactor.apply {
- setLevel(1)
- setIsDefaultDataEnabled(true)
- setIsFailedConnection(false)
- setIsEmergencyOnly(false)
- setNumberOfLevels(4)
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
- isDataConnected.value = true
- }
+ iconsInteractor =
+ MobileIconsInteractorImpl(
+ connectionsRepository,
+ carrierConfigTracker,
+ tableLogBuffer,
+ connectivityRepository,
+ FakeUserSetupRepository(),
+ testScope.backgroundScope,
+ context,
+ )
+
+ interactor =
+ MobileIconInteractorImpl(
+ testScope.backgroundScope,
+ iconsInteractor.activeDataConnectionHasDataEnabled,
+ iconsInteractor.alwaysShowDataRatIcon,
+ iconsInteractor.alwaysUseCdmaLevel,
+ iconsInteractor.isSingleCarrier,
+ iconsInteractor.mobileIsDefault,
+ iconsInteractor.defaultMobileIconMapping,
+ iconsInteractor.defaultMobileIconGroup,
+ iconsInteractor.isDefaultConnectionFailed,
+ iconsInteractor.isForceHidden,
+ repository,
+ context,
+ MobileIconCarrierIdOverridesFake()
+ )
createAndSetViewModel()
}
@@ -108,7 +154,6 @@
val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
airplaneModeRepository.setIsAirplaneMode(false)
- interactor.isForceHidden.value = false
assertThat(latest).isTrue()
@@ -122,8 +167,8 @@
val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
airplaneModeRepository.setIsAirplaneMode(true)
- interactor.isAllowedDuringAirplaneMode.value = false
- interactor.isForceHidden.value = false
+ repository.isAllowedDuringAirplaneMode.value = false
+ connectivityRepository.setForceHiddenIcons(setOf())
assertThat(latest).isFalse()
@@ -138,8 +183,8 @@
val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
airplaneModeRepository.setIsAirplaneMode(true)
- interactor.isAllowedDuringAirplaneMode.value = true
- interactor.isForceHidden.value = false
+ repository.isAllowedDuringAirplaneMode.value = true
+ connectivityRepository.setForceHiddenIcons(setOf())
assertThat(latest).isTrue()
@@ -153,7 +198,7 @@
val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
airplaneModeRepository.setIsAirplaneMode(false)
- interactor.isForceHidden.value = true
+ connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
assertThat(latest).isFalse()
@@ -167,156 +212,29 @@
val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
airplaneModeRepository.setIsAirplaneMode(false)
- interactor.isForceHidden.value = false
+ connectivityRepository.setForceHiddenIcons(setOf())
assertThat(latest).isTrue()
airplaneModeRepository.setIsAirplaneMode(true)
assertThat(latest).isFalse()
- interactor.isAllowedDuringAirplaneMode.value = true
+ repository.isAllowedDuringAirplaneMode.value = true
assertThat(latest).isTrue()
- interactor.isForceHidden.value = true
+ connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
assertThat(latest).isFalse()
job.cancel()
}
@Test
- fun iconId_correctLevel_notCutout() =
- testScope.runTest {
- var latest: SignalIconModel? = null
- val job = underTest.icon.onEach { latest = it }.launchIn(this)
- val expected = defaultSignal()
-
- assertThat(latest).isEqualTo(expected)
-
- job.cancel()
- }
-
- @Test
- fun icon_usesLevelFromInteractor() =
- testScope.runTest {
- var latest: SignalIconModel? = null
- val job = underTest.icon.onEach { latest = it }.launchIn(this)
-
- interactor.level.value = 3
- assertThat(latest!!.level).isEqualTo(3)
-
- interactor.level.value = 1
- assertThat(latest!!.level).isEqualTo(1)
-
- job.cancel()
- }
-
- @Test
- fun icon_usesNumberOfLevelsFromInteractor() =
- testScope.runTest {
- var latest: SignalIconModel? = null
- val job = underTest.icon.onEach { latest = it }.launchIn(this)
-
- interactor.numberOfLevels.value = 5
- assertThat(latest!!.numberOfLevels).isEqualTo(5)
-
- interactor.numberOfLevels.value = 2
- assertThat(latest!!.numberOfLevels).isEqualTo(2)
-
- job.cancel()
- }
-
- @Test
- fun icon_defaultDataDisabled_showExclamationTrue() =
- testScope.runTest {
- interactor.setIsDefaultDataEnabled(false)
-
- var latest: SignalIconModel? = null
- val job = underTest.icon.onEach { latest = it }.launchIn(this)
-
- assertThat(latest!!.showExclamationMark).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun icon_defaultConnectionFailed_showExclamationTrue() =
- testScope.runTest {
- interactor.isDefaultConnectionFailed.value = true
-
- var latest: SignalIconModel? = null
- val job = underTest.icon.onEach { latest = it }.launchIn(this)
-
- assertThat(latest!!.showExclamationMark).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun icon_enabledAndNotFailed_showExclamationFalse() =
- testScope.runTest {
- interactor.setIsDefaultDataEnabled(true)
- interactor.isDefaultConnectionFailed.value = false
-
- var latest: SignalIconModel? = null
- val job = underTest.icon.onEach { latest = it }.launchIn(this)
-
- assertThat(latest!!.showExclamationMark).isFalse()
-
- job.cancel()
- }
-
- @Test
- fun icon_usesEmptyState_whenNotInService() =
- testScope.runTest {
- var latest: SignalIconModel? = null
- val job = underTest.icon.onEach { latest = it }.launchIn(this)
-
- interactor.isInService.value = false
-
- var expected = emptySignal()
-
- assertThat(latest).isEqualTo(expected)
-
- // Changing the level doesn't overwrite the disabled state
- interactor.level.value = 2
- assertThat(latest).isEqualTo(expected)
-
- // Once back in service, the regular icon appears
- interactor.isInService.value = true
- expected = defaultSignal(level = 2)
- assertThat(latest).isEqualTo(expected)
-
- job.cancel()
- }
-
- @Test
- fun icon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() =
- testScope.runTest {
- var latest: SignalIconModel? = null
- val job = underTest.icon.onEach { latest = it }.launchIn(this)
-
- interactor.carrierNetworkChangeActive.value = true
- interactor.level.value = 1
-
- assertThat(latest!!.level).isEqualTo(1)
- assertThat(latest!!.carrierNetworkChange).isTrue()
-
- // SignalIconModel respects the current level
- interactor.level.value = 2
-
- assertThat(latest!!.level).isEqualTo(2)
- assertThat(latest!!.carrierNetworkChange).isTrue()
-
- job.cancel()
- }
-
- @Test
fun contentDescription_notInService_usesNoPhone() =
testScope.runTest {
var latest: ContentDescription? = null
val job = underTest.contentDescription.onEach { latest = it }.launchIn(this)
- interactor.isInService.value = false
+ repository.isInService.value = false
assertThat((latest as ContentDescription.Resource).res)
.isEqualTo(PHONE_SIGNAL_STRENGTH_NONE)
@@ -330,13 +248,11 @@
var latest: ContentDescription? = null
val job = underTest.contentDescription.onEach { latest = it }.launchIn(this)
- interactor.isInService.value = true
-
- interactor.level.value = 2
+ repository.setAllLevels(2)
assertThat((latest as ContentDescription.Resource).res)
.isEqualTo(PHONE_SIGNAL_STRENGTH[2])
- interactor.level.value = 0
+ repository.setAllLevels(0)
assertThat((latest as ContentDescription.Resource).res)
.isEqualTo(PHONE_SIGNAL_STRENGTH[0])
@@ -351,7 +267,8 @@
THREE_G.dataType,
ContentDescription.Resource(THREE_G.dataContentDescription)
)
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
+ connectionsRepository.mobileIsDefault.value = true
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
@@ -364,9 +281,9 @@
@Test
fun networkType_null_whenDisabled() =
testScope.runTest {
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
- interactor.setIsDataEnabled(false)
- interactor.mobileIsDefault.value = true
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ repository.setDataEnabled(false)
+ connectionsRepository.mobileIsDefault.value = true
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
@@ -378,9 +295,9 @@
@Test
fun networkType_null_whenCarrierNetworkChangeActive() =
testScope.runTest {
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
- interactor.carrierNetworkChangeActive.value = true
- interactor.mobileIsDefault.value = true
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ repository.carrierNetworkChangeActive.value = true
+ connectionsRepository.mobileIsDefault.value = true
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
@@ -397,10 +314,10 @@
THREE_G.dataType,
ContentDescription.Resource(THREE_G.dataContentDescription)
)
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
- interactor.setIsDataEnabled(true)
- interactor.isDataConnected.value = true
- interactor.mobileIsDefault.value = true
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ repository.setDataEnabled(true)
+ repository.dataConnectionState.value = DataConnectionState.Connected
+ connectionsRepository.mobileIsDefault.value = true
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
@@ -418,15 +335,13 @@
ContentDescription.Resource(THREE_G.dataContentDescription)
)
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
assertThat(latest).isEqualTo(initial)
- interactor.isDataConnected.value = false
- yield()
+ repository.dataConnectionState.value = DataConnectionState.Disconnected
assertThat(latest).isNull()
@@ -441,15 +356,13 @@
THREE_G.dataType,
ContentDescription.Resource(THREE_G.dataContentDescription)
)
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
- interactor.setIsDataEnabled(true)
+ repository.dataEnabled.value = true
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
assertThat(latest).isEqualTo(expected)
- interactor.setIsDataEnabled(false)
- yield()
+ repository.dataEnabled.value = false
assertThat(latest).isNull()
@@ -459,9 +372,10 @@
@Test
fun networkType_alwaysShow_shownEvenWhenDisabled() =
testScope.runTest {
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
- interactor.setIsDataEnabled(false)
- interactor.alwaysShowDataRatIcon.value = true
+ repository.dataEnabled.value = false
+
+ connectionsRepository.defaultDataSubRatConfig.value =
+ MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
@@ -479,9 +393,11 @@
@Test
fun networkType_alwaysShow_shownEvenWhenDisconnected() =
testScope.runTest {
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
- interactor.isDataConnected.value = false
- interactor.alwaysShowDataRatIcon.value = true
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ repository.dataConnectionState.value = DataConnectionState.Disconnected
+
+ connectionsRepository.defaultDataSubRatConfig.value =
+ MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
@@ -499,9 +415,10 @@
@Test
fun networkType_alwaysShow_shownEvenWhenFailedConnection() =
testScope.runTest {
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
- interactor.setIsFailedConnection(true)
- interactor.alwaysShowDataRatIcon.value = true
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ connectionsRepository.mobileIsDefault.value = true
+ connectionsRepository.defaultDataSubRatConfig.value =
+ MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
@@ -517,16 +434,24 @@
}
@Test
- fun networkType_alwaysShow_notShownWhenInvalidDataTypeIcon() =
+ fun networkType_alwaysShow_usesDefaultIconWhenInvalid() =
testScope.runTest {
- // The UNKNOWN icon group doesn't have a valid data type icon ID
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(UNKNOWN)
- interactor.alwaysShowDataRatIcon.value = true
+ // The UNKNOWN icon group doesn't have a valid data type icon ID, and the logic from the
+ // old pipeline was to use the default icon group if the map doesn't exist
+ repository.setNetworkTypeKey(UNKNOWN.name)
+ connectionsRepository.defaultDataSubRatConfig.value =
+ MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
- assertThat(latest).isNull()
+ val expected =
+ Icon.Resource(
+ connectionsRepository.defaultMobileIconGroup.value.dataType,
+ ContentDescription.Resource(G.dataContentDescription)
+ )
+
+ assertThat(latest).isEqualTo(expected)
job.cancel()
}
@@ -534,9 +459,10 @@
@Test
fun networkType_alwaysShow_shownWhenNotDefault() =
testScope.runTest {
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
- interactor.mobileIsDefault.value = false
- interactor.alwaysShowDataRatIcon.value = true
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ connectionsRepository.mobileIsDefault.value = false
+ connectionsRepository.defaultDataSubRatConfig.value =
+ MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
@@ -554,9 +480,9 @@
@Test
fun networkType_notShownWhenNotDefault() =
testScope.runTest {
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
- interactor.isDataConnected.value = true
- interactor.mobileIsDefault.value = false
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ repository.dataConnectionState.value = DataConnectionState.Connected
+ connectionsRepository.mobileIsDefault.value = false
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
@@ -569,13 +495,14 @@
@Test
fun roaming() =
testScope.runTest {
- interactor.isRoaming.value = true
+ repository.setAllRoaming(true)
+
var latest: Boolean? = null
val job = underTest.roaming.onEach { latest = it }.launchIn(this)
assertThat(latest).isTrue()
- interactor.isRoaming.value = false
+ repository.setAllRoaming(false)
assertThat(latest).isFalse()
@@ -599,7 +526,7 @@
val containerJob =
underTest.activityInVisible.onEach { containerVisible = it }.launchIn(this)
- interactor.activity.value =
+ repository.dataActivityDirection.value =
DataActivityModel(
hasActivityIn = true,
hasActivityOut = true,
@@ -631,7 +558,7 @@
val containerJob =
underTest.activityContainerVisible.onEach { containerVisible = it }.launchIn(this)
- interactor.activity.value =
+ repository.dataActivityDirection.value =
DataActivityModel(
hasActivityIn = true,
hasActivityOut = false,
@@ -643,7 +570,7 @@
assertThat(outVisible).isFalse()
assertThat(containerVisible).isTrue()
- interactor.activity.value =
+ repository.dataActivityDirection.value =
DataActivityModel(
hasActivityIn = false,
hasActivityOut = true,
@@ -653,7 +580,7 @@
assertThat(outVisible).isTrue()
assertThat(containerVisible).isTrue()
- interactor.activity.value =
+ repository.dataActivityDirection.value =
DataActivityModel(
hasActivityIn = false,
hasActivityOut = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
index 065dfba..e42515e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
@@ -154,33 +154,6 @@
}
@Test
- fun caching_mobileIconInteractorIsReusedForSameSubId() =
- testScope.runTest {
- val interactor1 = underTest.mobileIconInteractorForSub(1)
- val interactor2 = underTest.mobileIconInteractorForSub(1)
-
- assertThat(interactor1).isSameInstanceAs(interactor2)
- }
-
- @Test
- fun caching_invalidInteractorssAreRemovedFromCacheWhenSubDisappears() =
- testScope.runTest {
- // Retrieve interactors to trigger caching
- val interactor1 = underTest.mobileIconInteractorForSub(1)
- val interactor2 = underTest.mobileIconInteractorForSub(2)
-
- // Both impls are cached
- assertThat(underTest.mobileIconInteractorSubIdCache)
- .containsExactly(1, interactor1, 2, interactor2)
-
- // SUB_1 is removed from the list...
- interactor.filteredSubscriptions.value = listOf(SUB_2)
-
- // ... and dropped from the cache
- assertThat(underTest.mobileIconInteractorSubIdCache).containsExactly(2, interactor2)
- }
-
- @Test
fun firstMobileSubShowingNetworkTypeIcon_noSubs_false() =
testScope.runTest {
var latest: Boolean? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
index 8f28cc0..e44ff8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
@@ -27,7 +27,7 @@
MutableStateFlow(emptySet())
override val forceHiddenSlots: StateFlow<Set<ConnectivitySlot>> = _forceHiddenIcons
- override val defaultConnections: StateFlow<DefaultConnectionModel> =
+ override val defaultConnections: MutableStateFlow<DefaultConnectionModel> =
MutableStateFlow(DefaultConnectionModel())
override val vcnSubId: MutableStateFlow<Int?> = MutableStateFlow(null)
@@ -35,4 +35,43 @@
fun setForceHiddenIcons(hiddenIcons: Set<ConnectivitySlot>) {
_forceHiddenIcons.value = hiddenIcons
}
+
+ /**
+ * Convenience for setting mobile data connected, disconnected, or validated. Defaults to
+ * setting mobile connected && validated, since the default state is disconnected && not
+ * validated
+ */
+ fun setMobileConnected(
+ default: Boolean = true,
+ validated: Boolean = true,
+ ) {
+ defaultConnections.value =
+ DefaultConnectionModel(
+ mobile = DefaultConnectionModel.Mobile(default),
+ isValidated = validated,
+ )
+ }
+
+ /** Similar convenience method for ethernet */
+ fun setEthernetConnected(
+ default: Boolean = true,
+ validated: Boolean = true,
+ ) {
+ defaultConnections.value =
+ DefaultConnectionModel(
+ ethernet = DefaultConnectionModel.Ethernet(default),
+ isValidated = validated,
+ )
+ }
+
+ fun setWifiConnected(
+ default: Boolean = true,
+ validated: Boolean = true,
+ ) {
+ defaultConnections.value =
+ DefaultConnectionModel(
+ wifi = DefaultConnectionModel.Wifi(default),
+ isValidated = validated,
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
new file mode 100644
index 0000000..8150a31
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.shared.ui.model.SignalIcon
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.InternetTileViewModel.Companion.NOT_CONNECTED_NETWORKS_UNAVAILABLE
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class InternetTileViewModelTest : SysuiTestCase() {
+ private lateinit var underTest: InternetTileViewModel
+ private lateinit var mobileIconsInteractor: MobileIconsInteractor
+
+ private val airplaneModeRepository = FakeAirplaneModeRepository()
+ private val connectivityRepository = FakeConnectivityRepository()
+ private val ethernetInteractor = EthernetInteractor(connectivityRepository)
+ private val wifiRepository = FakeWifiRepository()
+ private val userSetupRepo = FakeUserSetupRepository()
+ private val testScope = TestScope()
+ private val wifiInteractor =
+ WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope)
+
+ private val tableLogBuffer: TableLogBuffer = mock()
+ private val carrierConfigTracker: CarrierConfigTracker = mock()
+
+ private val mobileConnectionsRepository =
+ FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogBuffer)
+ private val mobileConnectionRepository =
+ FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer)
+
+ @Before
+ fun setUp() {
+ mobileConnectionRepository.apply {
+ setNetworkTypeKey(mobileConnectionsRepository.GSM_KEY)
+ isInService.value = true
+ dataConnectionState.value = DataConnectionState.Connected
+ dataEnabled.value = true
+ }
+
+ mobileConnectionsRepository.apply {
+ activeMobileDataRepository.value = mobileConnectionRepository
+ activeMobileDataSubscriptionId.value = SUB_1_ID
+ setMobileConnectionRepositoryMap(mapOf(SUB_1_ID to mobileConnectionRepository))
+ }
+
+ mobileIconsInteractor =
+ MobileIconsInteractorImpl(
+ mobileConnectionsRepository,
+ carrierConfigTracker,
+ tableLogBuffer,
+ connectivityRepository,
+ userSetupRepo,
+ testScope.backgroundScope,
+ context,
+ )
+
+ underTest =
+ InternetTileViewModel(
+ airplaneModeRepository,
+ connectivityRepository,
+ ethernetInteractor,
+ mobileIconsInteractor,
+ wifiInteractor,
+ context,
+ testScope.backgroundScope,
+ )
+ }
+
+ @Test
+ fun noDefault_noNetworksAvailable() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ connectivityRepository.defaultConnections.value = DefaultConnectionModel()
+
+ assertThat(latest?.secondaryLabel)
+ .isEqualTo(Text.Resource(R.string.quick_settings_networks_unavailable))
+ assertThat(latest?.iconId).isEqualTo(R.drawable.ic_qs_no_internet_unavailable)
+ }
+
+ @Test
+ fun noDefault_networksAvailable() =
+ testScope.runTest {
+ // TODO: support [WifiInteractor.areNetworksAvailable]
+ }
+
+ @Test
+ fun wifiDefaultAndActive() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ val networkModel =
+ WifiNetworkModel.Active(
+ networkId = 1,
+ level = 4,
+ ssid = "test ssid",
+ )
+
+ connectivityRepository.setWifiConnected()
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+
+ // Type is [Visible] since that is the only model that stores a resId
+ val expectedIcon: WifiIcon.Visible =
+ WifiIcon.fromModel(networkModel, context) as WifiIcon.Visible
+
+ assertThat(latest?.secondaryTitle).isEqualTo("test ssid")
+ assertThat(latest?.secondaryLabel).isNull()
+ assertThat(latest?.icon).isEqualTo(ResourceIcon.get(expectedIcon.icon.res))
+ assertThat(latest?.iconId).isNull()
+ }
+
+ @Test
+ fun wifiDefaultAndNotActive_noNetworksAvailable() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ val networkModel = WifiNetworkModel.Inactive
+
+ connectivityRepository.setWifiConnected(validated = false)
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+ wifiRepository.wifiScanResults.value = emptyList()
+
+ assertThat(latest).isEqualTo(NOT_CONNECTED_NETWORKS_UNAVAILABLE)
+ }
+
+ @Test
+ fun wifiDefaultAndNotActive_networksAvailable() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ val networkModel = WifiNetworkModel.Inactive
+
+ connectivityRepository.setWifiConnected(validated = false)
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+ wifiRepository.wifiScanResults.value = listOf(WifiScanEntry("test 1"))
+
+ assertThat(latest?.secondaryLabel).isNull()
+ assertThat(latest?.secondaryTitle)
+ .isEqualTo(context.getString(R.string.quick_settings_networks_available))
+ assertThat(latest?.icon).isNull()
+ assertThat(latest?.iconId).isEqualTo(R.drawable.ic_qs_no_internet_available)
+ }
+
+ @Test
+ fun mobileDefault_usesNetworkNameAndIcon() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ connectivityRepository.setMobileConnected()
+ mobileConnectionsRepository.mobileIsDefault.value = true
+ mobileConnectionRepository.apply {
+ setAllLevels(3)
+ setAllRoaming(false)
+ networkName.value = NetworkNameModel.Default("test network")
+ }
+
+ assertThat(latest?.secondaryTitle).contains("test network")
+ assertThat(latest?.secondaryLabel).isNull()
+ assertThat(latest?.icon).isInstanceOf(SignalIcon::class.java)
+ assertThat(latest?.iconId).isNull()
+ }
+
+ @Test
+ fun ethernetDefault_validated_matchesInteractor() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+ val ethernetIcon by collectLastValue(ethernetInteractor.icon)
+
+ connectivityRepository.setEthernetConnected(default = true, validated = true)
+
+ assertThat(latest?.secondaryLabel).isNull()
+ assertThat(latest?.secondaryTitle)
+ .isEqualTo(ethernetIcon!!.contentDescription.toString())
+ assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet_fully)
+ assertThat(latest?.icon).isNull()
+ }
+
+ @Test
+ fun ethernetDefault_notValidated_matchesInteractor() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+ val ethernetIcon by collectLastValue(ethernetInteractor.icon)
+
+ connectivityRepository.setEthernetConnected(default = true, validated = false)
+
+ assertThat(latest?.secondaryLabel).isNull()
+ assertThat(latest?.secondaryTitle)
+ .isEqualTo(ethernetIcon!!.contentDescription.toString())
+ assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet)
+ assertThat(latest?.icon).isNull()
+ }
+
+ companion object {
+ const val SUB_1_ID = 1
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
index 4f7bb72..106b548 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -19,6 +19,7 @@
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryHelper.ACTIVITY_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -39,6 +40,9 @@
private val _wifiActivity = MutableStateFlow(ACTIVITY_DEFAULT)
override val wifiActivity: StateFlow<DataActivityModel> = _wifiActivity
+ override val wifiScanResults: MutableStateFlow<List<WifiScanEntry>> =
+ MutableStateFlow(emptyList())
+
fun setIsWifiEnabled(enabled: Boolean) {
_isWifiEnabled.value = enabled
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index bea1154..c2e75aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -27,6 +27,7 @@
import android.net.TransportInfo
import android.net.VpnTransportInfo
import android.net.vcn.VcnTransportInfo
+import android.net.wifi.ScanResult
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.TrafficStateCallback
@@ -45,6 +46,7 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -1205,6 +1207,58 @@
.isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true))
}
+ @Test
+ fun wifiScanResults_containsSsidList() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiScanResults)
+
+ val scanResults =
+ listOf(
+ ScanResult().also { it.SSID = "ssid 1" },
+ ScanResult().also { it.SSID = "ssid 2" },
+ ScanResult().also { it.SSID = "ssid 3" },
+ ScanResult().also { it.SSID = "ssid 4" },
+ ScanResult().also { it.SSID = "ssid 5" },
+ )
+ whenever(wifiManager.scanResults).thenReturn(scanResults)
+ getScanResultsCallback().onScanResultsAvailable()
+
+ val expected =
+ listOf(
+ WifiScanEntry(ssid = "ssid 1"),
+ WifiScanEntry(ssid = "ssid 2"),
+ WifiScanEntry(ssid = "ssid 3"),
+ WifiScanEntry(ssid = "ssid 4"),
+ WifiScanEntry(ssid = "ssid 5"),
+ )
+
+ assertThat(latest).isEqualTo(expected)
+ }
+
+ @Test
+ fun wifiScanResults_updates() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiScanResults)
+
+ var scanResults =
+ listOf(
+ ScanResult().also { it.SSID = "ssid 1" },
+ ScanResult().also { it.SSID = "ssid 2" },
+ ScanResult().also { it.SSID = "ssid 3" },
+ ScanResult().also { it.SSID = "ssid 4" },
+ ScanResult().also { it.SSID = "ssid 5" },
+ )
+ whenever(wifiManager.scanResults).thenReturn(scanResults)
+ getScanResultsCallback().onScanResultsAvailable()
+
+ // New scan representing no results
+ scanResults = emptyList()
+ whenever(wifiManager.scanResults).thenReturn(scanResults)
+ getScanResultsCallback().onScanResultsAvailable()
+
+ assertThat(latest).isEmpty()
+ }
+
private fun createRepo(): WifiRepositoryImpl {
return WifiRepositoryImpl(
fakeBroadcastDispatcher,
@@ -1240,6 +1294,13 @@
return callbackCaptor.value!!
}
+ private fun getScanResultsCallback(): WifiManager.ScanResultsCallback {
+ testScope.runCurrent()
+ val callbackCaptor = argumentCaptor<WifiManager.ScanResultsCallback>()
+ verify(wifiManager).registerScanResultsCallback(any(), callbackCaptor.capture())
+ return callbackCaptor.value!!
+ }
+
private fun createWifiNetworkCapabilities(
transportInfo: TransportInfo,
isValidated: Boolean = true,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
index 662e36a..afab623 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
+import android.net.wifi.ScanResult
import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.UNKNOWN_SSID
import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo
@@ -32,6 +33,7 @@
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -77,6 +79,7 @@
featureFlags,
testScope.backgroundScope,
executor,
+ dispatcher,
wifiPickerTrackerFactory,
wifiManager,
logger,
@@ -1137,6 +1140,58 @@
.isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true))
}
+ @Test
+ fun wifiScanResults_containsSsidList() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiScanResults)
+
+ val scanResults =
+ listOf(
+ ScanResult().also { it.SSID = "ssid 1" },
+ ScanResult().also { it.SSID = "ssid 2" },
+ ScanResult().also { it.SSID = "ssid 3" },
+ ScanResult().also { it.SSID = "ssid 4" },
+ ScanResult().also { it.SSID = "ssid 5" },
+ )
+ whenever(wifiManager.scanResults).thenReturn(scanResults)
+ getScanResultsCallback().onScanResultsAvailable()
+
+ val expected =
+ listOf(
+ WifiScanEntry(ssid = "ssid 1"),
+ WifiScanEntry(ssid = "ssid 2"),
+ WifiScanEntry(ssid = "ssid 3"),
+ WifiScanEntry(ssid = "ssid 4"),
+ WifiScanEntry(ssid = "ssid 5"),
+ )
+
+ assertThat(latest).isEqualTo(expected)
+ }
+
+ @Test
+ fun wifiScanResults_updates() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiScanResults)
+
+ var scanResults =
+ listOf(
+ ScanResult().also { it.SSID = "ssid 1" },
+ ScanResult().also { it.SSID = "ssid 2" },
+ ScanResult().also { it.SSID = "ssid 3" },
+ ScanResult().also { it.SSID = "ssid 4" },
+ ScanResult().also { it.SSID = "ssid 5" },
+ )
+ whenever(wifiManager.scanResults).thenReturn(scanResults)
+ getScanResultsCallback().onScanResultsAvailable()
+
+ // New scan representing no results
+ scanResults = listOf()
+ whenever(wifiManager.scanResults).thenReturn(scanResults)
+ getScanResultsCallback().onScanResultsAvailable()
+
+ assertThat(latest).isEmpty()
+ }
+
private fun getCallback(): WifiPickerTracker.WifiPickerTrackerCallback {
testScope.runCurrent()
return callbackCaptor.value
@@ -1156,6 +1211,13 @@
}
}
+ private fun getScanResultsCallback(): WifiManager.ScanResultsCallback {
+ testScope.runCurrent()
+ val callbackCaptor = argumentCaptor<WifiManager.ScanResultsCallback>()
+ verify(wifiManager).registerScanResultsCallback(any(), callbackCaptor.capture())
+ return callbackCaptor.value!!
+ }
+
private companion object {
const val TITLE = "AB"
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
index 6fe88c1..1db8065 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
@@ -21,11 +21,13 @@
import androidx.test.filters.SmallTest
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -56,7 +58,8 @@
fun setUp() {
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
- underTest = WifiInteractorImpl(connectivityRepository, wifiRepository)
+ underTest =
+ WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope)
}
@Test
@@ -300,4 +303,76 @@
job.cancel()
}
+
+ @Test
+ fun areNetworksAvailable_noneActive_noResults() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.areNetworksAvailable)
+
+ wifiRepository.wifiScanResults.value = emptyList()
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun areNetworksAvailable_noneActive_nonEmptyResults() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.areNetworksAvailable)
+
+ wifiRepository.wifiScanResults.value =
+ listOf(
+ WifiScanEntry(ssid = "ssid 1"),
+ WifiScanEntry(ssid = "ssid 2"),
+ WifiScanEntry(ssid = "ssid 3"),
+ )
+
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun areNetworksAvailable_activeNetwork_resultsIncludeOtherNetworks() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.areNetworksAvailable)
+
+ wifiRepository.wifiScanResults.value =
+ listOf(
+ WifiScanEntry(ssid = "ssid 1"),
+ WifiScanEntry(ssid = "ssid 2"),
+ WifiScanEntry(ssid = "ssid 3"),
+ )
+
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active(
+ ssid = "ssid 2",
+ networkId = 1,
+ level = 2,
+ )
+ )
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun areNetworksAvailable_activeNetwork_onlyResultIsTheActiveNetwork() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.areNetworksAvailable)
+
+ wifiRepository.wifiScanResults.value =
+ listOf(
+ WifiScanEntry(ssid = "ssid 2"),
+ )
+
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active(
+ ssid = "ssid 2",
+ networkId = 1,
+ level = 2,
+ )
+ )
+
+ assertThat(latest).isFalse()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 3f49935..a0d4d13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -82,8 +82,8 @@
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
- interactor = WifiInteractorImpl(connectivityRepository, wifiRepository)
scope = CoroutineScope(Dispatchers.Unconfined)
+ interactor = WifiInteractorImpl(connectivityRepository, wifiRepository, scope)
airplaneModeViewModel =
AirplaneModeViewModelImpl(
AirplaneModeInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
index e6724d8..1d1b84c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -40,7 +40,7 @@
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel.Companion.NO_INTERNET
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon.Companion.NO_INTERNET
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -83,8 +83,8 @@
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
- interactor = WifiInteractorImpl(connectivityRepository, wifiRepository)
scope = CoroutineScope(IMMEDIATE)
+ interactor = WifiInteractorImpl(connectivityRepository, wifiRepository, scope)
airplaneModeViewModel =
AirplaneModeViewModelImpl(
AirplaneModeInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index bdeba2a..5aacc66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -74,7 +74,8 @@
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
- interactor = WifiInteractorImpl(connectivityRepository, wifiRepository)
+ interactor =
+ WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope)
airplaneModeViewModel =
AirplaneModeViewModelImpl(
AirplaneModeInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index c12df98..5256245 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -57,6 +57,7 @@
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
@@ -71,6 +72,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -129,6 +131,9 @@
private FakeFeatureFlags mFeatureFlags;
private int mLongestHideShowAnimationDuration = 250;
+ @Rule
+ public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -242,7 +247,6 @@
| AccessibilityManager.FLAG_CONTENT_TEXT);
}
-
@Test
public void testComputeTimeout_withHovering() {
Mockito.reset(mAccessibilityMgr);
@@ -669,11 +673,12 @@
@After
public void teardown() {
- cleanUp(mDialog);
setOrientation(mOriginalOrientation);
+ mAnimatorTestRule.advanceTimeBy(mLongestHideShowAnimationDuration);
mTestableLooper.moveTimeForward(mLongestHideShowAnimationDuration);
mTestableLooper.processAllMessages();
reset(mPostureController);
+ cleanUp(mDialog);
}
private void cleanUp(VolumeDialogImpl dialog) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 949c477..1b623a3 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 @@
mConfigurationController,
mKeyguardViewMediator,
mKeyguardBypassController,
+ syncExecutor,
mColorExtractor,
mDumpManager,
mKeyguardStateController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 715d661..6f51d1b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -33,10 +33,17 @@
/** Fake [DisplayRepository] implementation for testing. */
class FakeDisplayRepository : DisplayRepository {
private val flow = MutableSharedFlow<Set<Display>>()
+ private val pendingDisplayFlow = MutableSharedFlow<Int?>()
/** Emits [value] as [displays] flow value. */
suspend fun emit(value: Set<Display>) = flow.emit(value)
+ /** Emits [value] as [pendingDisplayId] flow value. */
+ suspend fun emit(value: Int?) = pendingDisplayFlow.emit(value)
+
override val displays: Flow<Set<Display>>
get() = flow
+
+ override val pendingDisplayId: Flow<Int?>
+ get() = pendingDisplayFlow
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index faebcaa..cc0c943 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -81,7 +81,7 @@
override val linearDozeAmount: Flow<Float> = _dozeAmount
private val _statusBarState = MutableStateFlow(StatusBarState.SHADE)
- override val statusBarState: Flow<StatusBarState> = _statusBarState
+ override val statusBarState: StateFlow<StatusBarState> = _statusBarState
private val _dozeTransitionModel = MutableStateFlow(DozeTransitionModel())
override val dozeTransitionModel: Flow<DozeTransitionModel> = _dozeTransitionModel
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt
index 3334f3e..b92d946 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt
@@ -32,6 +32,8 @@
var lastWakeWhy: String? = null
var lastWakeReason: Int? = null
+ var userTouchRegistered = false
+
fun setInteractive(value: Boolean) {
_isInteractive.value = value
}
@@ -40,4 +42,8 @@
lastWakeWhy = why
lastWakeReason = wakeReason
}
+
+ override fun userTouch() {
+ userTouchRegistered = true
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 6b40f8e..f7db44e 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.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.classifier.domain.interactor.FalsingInteractor
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
@@ -37,6 +40,7 @@
import com.android.systemui.keyguard.shared.model.WakeSleepReason
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.SceneContainerConfig
@@ -93,8 +97,13 @@
}
}
+ val powerRepository: FakePowerRepository by lazy { FakePowerRepository() }
+
private val context = test.context
+ private val falsingCollectorFake: FalsingCollector by lazy { FalsingCollectorFake() }
+ private var falsingInteractor: FalsingInteractor? = null
+
fun fakeSceneContainerRepository(
containerConfig: SceneContainerConfig = fakeSceneContainerConfig(),
): SceneContainerRepository {
@@ -126,6 +135,7 @@
return SceneInteractor(
applicationScope = applicationScope(),
repository = repository,
+ powerRepository = powerRepository,
logger = mock(),
)
}
@@ -149,10 +159,6 @@
)
}
- fun keyguardRepository(): FakeKeyguardRepository {
- return keyguardRepository
- }
-
fun keyguardInteractor(repository: KeyguardRepository): KeyguardInteractor {
return KeyguardInteractor(
repository = repository,
@@ -175,6 +181,7 @@
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
featureFlags = featureFlags,
+ falsingInteractor = falsingInteractor(),
)
}
@@ -191,6 +198,14 @@
)
}
+ fun falsingInteractor(collector: FalsingCollector = falsingCollector()): FalsingInteractor {
+ return falsingInteractor ?: FalsingInteractor(collector).also { falsingInteractor = it }
+ }
+
+ fun falsingCollector(): FalsingCollector {
+ return falsingCollectorFake
+ }
+
private fun applicationScope(): CoroutineScope {
return testScope.backgroundScope
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index 03e3423..3774d1d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -84,10 +84,6 @@
}
@Override
- public void removeAllIconsForSlot(String slot) {
- }
-
- @Override
public void setIconAccessibilityLiveRegion(String slot, int mode) {
}
diff --git a/packages/services/VirtualCamera/OWNERS b/packages/services/VirtualCamera/OWNERS
new file mode 100644
index 0000000..c66443f
--- /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 f3a540b..cd83f8f 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 @@
@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 4688658..423b85f 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 @@
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 @@
*/
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 @@
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 @@
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 @@
}
public void destroy() {
+ cancelCurrentRequest();
unbind();
+ mCallbacks = null;
}
}
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
index 3fd6fe8..9bab261 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -78,11 +78,14 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
+import sun.misc.Unsafe;
+
/**
* <p>PinnerService pins important files for key processes in memory.</p>
* <p>Files to pin are specified in the config_defaultPinnerServiceFiles
@@ -150,6 +153,11 @@
@GuardedBy("this")
private ArraySet<Integer> mPinKeys;
+ private static final long MAX_ANON_SIZE = 2L * (1L << 30); // 2GB
+ private long mPinAnonSize;
+ private long mPinAnonAddress;
+ private long mCurrentlyPinnedAnonSize;
+
// Resource-configured pinner flags;
private final boolean mConfiguredToPinCamera;
private final boolean mConfiguredToPinHome;
@@ -550,6 +558,11 @@
pinKeys.add(KEY_ASSISTANT);
}
+ mPinAnonSize = DeviceConfig.getLong(DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
+ "pin_anon_size",
+ SystemProperties.getLong("pinner.pin_anon_size", 0));
+ mPinAnonSize = Math.max(0, Math.min(mPinAnonSize, MAX_ANON_SIZE));
+
return pinKeys;
}
@@ -589,6 +602,7 @@
int key = currentPinKeys.valueAt(i);
pinApp(key, userHandle, true /* force */);
}
+ pinAnonRegion();
}
/**
@@ -673,6 +687,64 @@
}
/**
+ * Pin an empty anonymous region. This should only be used for ablation experiments.
+ */
+ private void pinAnonRegion() {
+ if (mPinAnonSize == 0) {
+ return;
+ }
+ long alignedPinSize = mPinAnonSize;
+ if (alignedPinSize % PAGE_SIZE != 0) {
+ alignedPinSize -= alignedPinSize % PAGE_SIZE;
+ Slog.e(TAG, "pinAnonRegion: aligning size to " + alignedPinSize);
+ }
+ if (mPinAnonAddress != 0
+ && mCurrentlyPinnedAnonSize != alignedPinSize) {
+ unpinAnonRegion();
+ }
+ long address = 0;
+ try {
+ address = Os.mmap(0, alignedPinSize,
+ OsConstants.PROT_READ | OsConstants.PROT_WRITE,
+ OsConstants.MAP_PRIVATE | OsConstants.MAP_ANONYMOUS,
+ new FileDescriptor(), /*offset=*/0);
+
+ Unsafe tempUnsafe = null;
+ Class<sun.misc.Unsafe> clazz = sun.misc.Unsafe.class;
+ for (java.lang.reflect.Field f : clazz.getDeclaredFields()) {
+ f.setAccessible(true);
+ Object obj = f.get(null);
+ if (clazz.isInstance(obj)) {
+ tempUnsafe = clazz.cast(obj);
+ }
+ }
+ if (tempUnsafe == null) {
+ throw new Exception("Couldn't get Unsafe");
+ }
+ Method setMemory = clazz.getMethod("setMemory", long.class, long.class, byte.class);
+ setMemory.invoke(tempUnsafe, address, alignedPinSize, (byte) 1);
+ Os.mlock(address, alignedPinSize);
+ mCurrentlyPinnedAnonSize = alignedPinSize;
+ mPinAnonAddress = address;
+ address = -1;
+ Slog.e(TAG, "pinAnonRegion success, size=" + mCurrentlyPinnedAnonSize);
+ } catch (Exception ex) {
+ Slog.e(TAG, "Could not pin anon region of size " + alignedPinSize, ex);
+ return;
+ } finally {
+ if (address >= 0) {
+ safeMunmap(address, alignedPinSize);
+ }
+ }
+ }
+
+ private void unpinAnonRegion() {
+ if (mPinAnonAddress != 0) {
+ safeMunmap(mPinAnonAddress, mCurrentlyPinnedAnonSize);
+ }
+ }
+
+ /**
* @return The maximum amount of bytes to be pinned for an app of type {@code key}.
*/
private int getSizeLimitForKey(@AppKey int key) {
@@ -1083,6 +1155,9 @@
totalSize += pf.bytesPinned;
}
}
+ if (mPinAnonAddress != 0) {
+ pw.format("Pinned anon region: %s\n", mCurrentlyPinnedAnonSize);
+ }
pw.format("Total size: %s\n", totalSize);
pw.println();
if (!mPendingRepin.isEmpty()) {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 4d249ab..a787644 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_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 @@
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 @@
}
break;
}
+ case H_REMOUNT_VOLUMES_ON_MOVE: {
+ remountVolumesForRunningUsersOnMove();
+ break;
+ }
}
}
}
@@ -1286,6 +1292,44 @@
}
}
+ /**
+ * 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 @@
mPrimaryStorageUuid = mMoveTargetUuid;
writeSettingsLocked();
+ mHandler.obtainMessage(H_REMOUNT_VOLUMES_ON_MOVE).sendToTarget();
}
if (PackageManager.isMoveStatusFinished(status)) {
@@ -4640,7 +4685,7 @@
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 @@
}
}
+ @Override
+ public IFsveritySetupAuthToken createFsveritySetupAuthToken(ParcelFileDescriptor authFd,
+ int appUid, @UserIdInt int userId) throws IOException {
+ try {
+ return mInstaller.createFsveritySetupAuthToken(authFd, appUid, userId);
+ } catch (Installer.InstallerException e) {
+ throw new IOException(e);
+ }
+ }
+
+ @Override
+ public int enableFsverity(IFsveritySetupAuthToken authToken, String filePath,
+ String packageName) throws IOException {
+ try {
+ return mInstaller.enableFsverity(authToken, filePath, packageName);
+ } catch (Installer.InstallerException e) {
+ throw new IOException(e);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 03022b0..cd0a9d2 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -74,6 +74,15 @@
{
"name": "CtsVcnTestCases",
"file_patterns": ["VcnManagementService\\.java"]
+ },
+ {
+ "name": "FrameworksNetTests",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ }
+ ],
+ "file_patterns": ["VpnManagerService\\.java"]
}
],
"presubmit-large": [
@@ -91,6 +100,21 @@
}
],
"file_patterns": ["ClipboardService\\.java"]
+ },
+ {
+ "name": "CtsHostsideNetworkTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ }
+ ],
+ "file_patterns": ["VpnManagerService\\.java"]
}
]
}
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 6baae4b..e17424b 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -937,7 +937,7 @@
mActivity.addErrorToDropBox(
dropboxTag, null, "system_server", null, null, null,
null, report.toString(), stack, null, null, null,
- errorId);
+ errorId, null);
}
}
};
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c4816fb..5773e20 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2115,6 +2115,34 @@
mVoiceInteractionManagerProvider = provider;
}
+ /**
+ * Represents volatile states associated with a Dropbox entry.
+ * <p>
+ * These states, such as the process frozen state, can change quickly over time and thus
+ * should be captured as soon as possible to ensure accurate state. If a state is undefined,
+ * it means that the state was not read early and a fallback value can be used.
+ * </p>
+ */
+ static class VolatileDropboxEntryStates {
+ private final Boolean mIsProcessFrozen;
+
+ private VolatileDropboxEntryStates(Boolean frozenState) {
+ this.mIsProcessFrozen = frozenState;
+ }
+
+ public static VolatileDropboxEntryStates withProcessFrozenState(boolean frozenState) {
+ return new VolatileDropboxEntryStates(frozenState);
+ }
+
+ public static VolatileDropboxEntryStates emptyVolatileDropboxEnytyStates() {
+ return new VolatileDropboxEntryStates(null);
+ }
+
+ public Boolean isProcessFrozen() {
+ return mIsProcessFrozen;
+ }
+ }
+
static class MemBinder extends Binder {
ActivityManagerService mActivityManagerService;
private final PriorityDump.PriorityDumper mPriorityDumper =
@@ -8941,7 +8969,7 @@
addErrorToDropBox(
eventType, r, processName, null, null, null, null, null, null, crashInfo,
- new Float(loadingProgress), incrementalMetrics, null);
+ new Float(loadingProgress), incrementalMetrics, null, null);
// For GWP-ASan recoverable crashes, don't make the app crash (the whole point of
// 'recoverable' is that the app doesn't crash). Normally, for nonrecoreable native crashes,
@@ -9052,7 +9080,7 @@
final StringBuilder sb = new StringBuilder(1024);
synchronized (sb) {
- appendDropBoxProcessHeaders(process, processName, sb);
+ appendDropBoxProcessHeaders(process, processName, null, sb);
sb.append("Build: ").append(Build.FINGERPRINT).append("\n");
sb.append("System-App: ").append(isSystemApp).append("\n");
sb.append("Uptime-Millis: ").append(info.violationUptimeMillis).append("\n");
@@ -9155,7 +9183,7 @@
callingPid, (r != null) ? r.getProcessClassEnum() : 0);
addErrorToDropBox("wtf", r, processName, null, null, null, tag, null, null, crashInfo,
- null, null, null);
+ null, null, null, null);
return r;
}
@@ -9180,7 +9208,7 @@
for (Pair<String, ApplicationErrorReport.CrashInfo> p = list.poll();
p != null; p = list.poll()) {
addErrorToDropBox("wtf", proc, "system_server", null, null, null, p.first, null, null,
- p.second, null, null, null);
+ p.second, null, null, null, null);
}
}
@@ -9203,7 +9231,7 @@
* to append various headers to the dropbox log text.
*/
void appendDropBoxProcessHeaders(ProcessRecord process, String processName,
- final StringBuilder sb) {
+ final VolatileDropboxEntryStates volatileStates, final StringBuilder sb) {
// Watchdog thread ends up invoking this function (with
// a null ProcessRecord) to add the stack file to dropbox.
// Do not acquire a lock on this (am) in such cases, as it
@@ -9222,7 +9250,12 @@
sb.append("PID: ").append(process.getPid()).append("\n");
sb.append("UID: ").append(process.uid).append("\n");
if (process.mOptRecord != null) {
- sb.append("Frozen: ").append(process.mOptRecord.isFrozen()).append("\n");
+ // Use 'isProcessFrozen' from 'volatileStates' if it'snon-null (present),
+ // otherwise use 'isFrozen' from 'mOptRecord'.
+ sb.append("Frozen: ").append(
+ (volatileStates != null && volatileStates.isProcessFrozen() != null)
+ ? volatileStates.isProcessFrozen() : process.mOptRecord.isFrozen()
+ ).append("\n");
}
int flags = process.info.flags;
final IPackageManager pm = AppGlobals.getPackageManager();
@@ -9335,7 +9368,7 @@
String subject, final String report, final File dataFile,
final ApplicationErrorReport.CrashInfo crashInfo,
@Nullable Float loadingProgress, @Nullable IncrementalMetrics incrementalMetrics,
- @Nullable UUID errorId) {
+ @Nullable UUID errorId, @Nullable VolatileDropboxEntryStates volatileStates) {
// NOTE -- this must never acquire the ActivityManagerService lock,
// otherwise the watchdog may be prevented from resetting the system.
@@ -9357,7 +9390,7 @@
if (rateLimitResult.shouldRateLimit()) return;
final StringBuilder sb = new StringBuilder(1024);
- appendDropBoxProcessHeaders(process, processName, sb);
+ appendDropBoxProcessHeaders(process, processName, volatileStates, sb);
if (process != null) {
sb.append("Foreground: ")
.append(process.isInterestingToUserLocked() ? "Yes" : "No")
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index e7f4bf9..46e5523 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1843,7 +1843,8 @@
dropBuilder.append(catSw.toString());
FrameworkStatsLog.write(FrameworkStatsLog.LOW_MEM_REPORTED);
mService.addErrorToDropBox("lowmem", null, "system_server", null,
- null, null, tag.toString(), dropBuilder.toString(), null, null, null, null, null);
+ null, null, tag.toString(), dropBuilder.toString(), null, null, null, null, null,
+ null);
synchronized (mService) {
long now = SystemClock.uptimeMillis();
if (mLastMemUsageReportTime < now) {
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 09df277..40b1de6 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -297,6 +297,7 @@
ArrayList<Integer> firstPids = new ArrayList<>(5);
SparseBooleanArray lastPids = new SparseBooleanArray(20);
+ ActivityManagerService.VolatileDropboxEntryStates volatileDropboxEntriyStates = null;
mApp.getWindowProcessController().appEarlyNotResponding(annotation, () -> {
latencyTracker.waitingOnAMSLockStarted();
@@ -343,6 +344,9 @@
synchronized (mProcLock) {
latencyTracker.waitingOnProcLockEnded();
setNotResponding(true);
+ volatileDropboxEntriyStates =
+ ActivityManagerService.VolatileDropboxEntryStates
+ .withProcessFrozenState(mApp.mOptRecord.isFrozen());
}
// Log the ANR to the event log.
@@ -620,7 +624,8 @@
? (ProcessRecord) parentProcess.mOwner : null;
mService.addErrorToDropBox("anr", mApp, mApp.processName, activityShortComponentName,
parentShortComponentName, parentPr, null, report.toString(), tracesFile,
- null, new Float(loadingProgress), incrementalMetrics, errorId);
+ null, new Float(loadingProgress), incrementalMetrics, errorId,
+ volatileDropboxEntriyStates);
if (mApp.getWindowProcessController().appNotResponding(info.toString(),
() -> {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 97fb0e7..3d11c68 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -5643,7 +5643,7 @@
if (logToDropbox) {
final long now = SystemClock.elapsedRealtime();
final StringBuilder sb = new StringBuilder();
- mService.appendDropBoxProcessHeaders(app, app.processName, sb);
+ mService.appendDropBoxProcessHeaders(app, app.processName, null, sb);
sb.append("Reason: " + reason).append("\n");
sb.append("Requester UID: " + requester).append("\n");
dbox.addText(DROPBOX_TAG_IMPERCEPTIBLE_KILL, sb.toString());
diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING
index 68062b5..65f6af7 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 2003619..035bea3 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 @@
* 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 e109cc8..707240b 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 @@
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 3d1b162..e8a20de 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 @@
@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 @@
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 @@
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 @@
}
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 74e1410..8122b1d 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 @@
// 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 @@
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 2ff695d..0fc8aba 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 @@
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 @@
final Intent intent = new Intent(FINGERPRINT_ENROLL_ACTION);
intent.setPackage(SETTINGS_PACKAGE);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- context.startActivity(intent);
final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */,
diff --git a/services/core/java/com/android/server/connectivity/TEST_MAPPING b/services/core/java/com/android/server/connectivity/TEST_MAPPING
new file mode 100644
index 0000000..687d4b0
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/TEST_MAPPING
@@ -0,0 +1,30 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksNetTests",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ }
+ ],
+ "file_patterns": ["Vpn\\.java", "VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
+ }
+ ],
+ "presubmit-large": [
+ {
+ "name": "CtsHostsideNetworkTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ }
+ ],
+ "file_patterns": ["Vpn\\.java", "VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index cba5039..ff35b19 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 @@
}
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 @@
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 @@
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 @@
*/
private void startOrMigrateIkeSession(@Nullable Network underlyingNetwork) {
if (underlyingNetwork == null) {
+ // For null underlyingNetwork case, there will not be a NetworkAgent available so
+ // no underlying network update is necessary here. Note that updating
+ // mNetworkCapabilities here would also be reasonable, but it will be updated next
+ // time the VPN connects anyway.
Log.d(TAG, "There is no active network for starting an IKE session");
return;
}
+ final List<Network> networks = Collections.singletonList(underlyingNetwork);
+ // Update network capabilities if underlying network is changed.
+ if (!networks.equals(mNetworkCapabilities.getUnderlyingNetworks())) {
+ mNetworkCapabilities =
+ new NetworkCapabilities.Builder(mNetworkCapabilities)
+ .setUnderlyingNetworks(networks)
+ .build();
+ // No NetworkAgent case happens when Vpn tries to start a new VPN. The underlying
+ // network update will be done later with NetworkAgent connected event.
+ if (mNetworkAgent != null) {
+ doSetUnderlyingNetworks(mNetworkAgent, networks);
+ }
+ }
+
if (maybeMigrateIkeSessionAndUpdateVpnTransportInfo(underlyingNetwork)) return;
startIkeSession(underlyingNetwork);
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index c04c279..39172b8 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -25,6 +25,7 @@
import android.os.IBinder;
import android.os.PowerManager;
import android.os.SystemClock;
+import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.MathUtils;
@@ -33,6 +34,7 @@
import android.view.SurfaceControlHdrLayerInfoListener;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
import com.android.server.display.DisplayManagerService.Clock;
@@ -99,6 +101,7 @@
private boolean mIsHdrLayerPresent = false;
// mMaxDesiredHdrSdrRatio should only be applied when there is a valid backlight->nits mapping
private float mMaxDesiredHdrSdrRatio = DEFAULT_MAX_DESIRED_HDR_SDR_RATIO;
+ private boolean mForceHbmChangeCallback = false;
private boolean mIsBlockedByLowPowerMode = false;
private int mWidth;
private int mHeight;
@@ -484,7 +487,8 @@
private void updateHbmMode() {
int newHbmMode = calculateHighBrightnessMode();
updateHbmStats(newHbmMode);
- if (mHbmMode != newHbmMode) {
+ if (mHbmMode != newHbmMode || mForceHbmChangeCallback) {
+ mForceHbmChangeCallback = false;
mHbmMode = newHbmMode;
mHbmChangeCallback.run();
}
@@ -600,26 +604,32 @@
public void onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers,
int maxW, int maxH, int flags, float maxDesiredHdrSdrRatio) {
mHandler.post(() -> {
+ Trace.traceBegin(Trace.TRACE_TAG_POWER, "HBMController#onHdrInfoChanged");
mIsHdrLayerPresent = numberOfHdrLayers > 0
&& (float) (maxW * maxH) >= ((float) (mWidth * mHeight)
* mHbmData.minimumHdrPercentOfScreen);
- final float candidateDesiredHdrSdrRatio =
+ float candidateDesiredHdrSdrRatio =
mIsHdrLayerPresent && mHdrBrightnessCfg != null
? maxDesiredHdrSdrRatio
: DEFAULT_MAX_DESIRED_HDR_SDR_RATIO;
- if (candidateDesiredHdrSdrRatio >= 1.0f) {
- mMaxDesiredHdrSdrRatio = candidateDesiredHdrSdrRatio;
- } else {
+ if (candidateDesiredHdrSdrRatio < 1.0f) {
Slog.w(TAG, "Ignoring invalid desired HDR/SDR Ratio: "
+ candidateDesiredHdrSdrRatio);
- mMaxDesiredHdrSdrRatio = DEFAULT_MAX_DESIRED_HDR_SDR_RATIO;
+ candidateDesiredHdrSdrRatio = DEFAULT_MAX_DESIRED_HDR_SDR_RATIO;
+ }
+
+ if (!BrightnessSynchronizer.floatEquals(
+ mMaxDesiredHdrSdrRatio, candidateDesiredHdrSdrRatio)) {
+ mForceHbmChangeCallback = true;
+ mMaxDesiredHdrSdrRatio = candidateDesiredHdrSdrRatio;
}
// Calling the brightness update so that we can recalculate
// brightness with HDR in mind.
onBrightnessChanged(mBrightness, mUnthrottledBrightness, mThrottlingReason);
+ Trace.traceEnd(Trace.TRACE_TAG_POWER);
});
}
}
diff --git a/services/core/java/com/android/server/display/WifiDisplayController.java b/services/core/java/com/android/server/display/WifiDisplayController.java
index f6d06aa..955b8d9 100644
--- a/services/core/java/com/android/server/display/WifiDisplayController.java
+++ b/services/core/java/com/android/server/display/WifiDisplayController.java
@@ -1062,8 +1062,10 @@
}
private static WifiDisplay createWifiDisplay(WifiP2pDevice device) {
+ WifiP2pWfdInfo wfdInfo = device.getWfdInfo();
+ boolean isSessionAvailable = wfdInfo != null && wfdInfo.isSessionAvailable();
return new WifiDisplay(device.deviceAddress, device.deviceName, null,
- true, device.getWfdInfo().isSessionAvailable(), false);
+ true, isSessionAvailable, false);
}
private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6f0a4b4..1afa3ed 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_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 @@
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2)
private static final long NOTIFICATION_LOG_ASSISTANT_CANCEL = 195579280L;
+ private static final Duration POST_WAKE_LOCK_TIMEOUT = Duration.ofSeconds(30);
+
private IActivityManager mAm;
private ActivityTaskManagerInternal mAtm;
private ActivityManager mActivityManager;
@@ -2328,8 +2329,7 @@
mRankingHandler = rankingHandler;
mConditionProviders = conditionProviders;
mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders,
- new SysUiStatsEvent.BuilderFactory(), flagResolver,
- new ZenModeEventLogger(mPackageManagerClient));
+ flagResolver, new ZenModeEventLogger(mPackageManagerClient));
mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
@Override
public void onConfigChanged() {
@@ -2390,7 +2390,6 @@
mNotificationChannelLogger,
mAppOps,
mUserProfiles,
- new SysUiStatsEvent.BuilderFactory(),
mShowReviewPermissionsNotification);
mRankingHelper = new RankingHelper(getContext(),
mRankingHandler,
@@ -6678,22 +6677,14 @@
}
private PostNotificationTracker acquireWakeLockForPost(String pkg, int uid) {
- if (mFlagResolver.isEnabled(WAKE_LOCK_FOR_POSTING_NOTIFICATION)
- && Binder.withCleanCallingIdentity(
- () -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.NOTIFY_WAKELOCK, false))) {
- // The package probably doesn't have WAKE_LOCK permission and should not require it.
- return Binder.withCleanCallingIdentity(() -> {
- WakeLock wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
- "NotificationManagerService:post:" + pkg);
- wakeLock.setWorkSource(new WorkSource(uid, pkg));
- // TODO(b/275044361): Adjust to a more reasonable number when we have the data.
- wakeLock.acquire(30_000);
- return mPostNotificationTrackerFactory.newTracker(wakeLock);
- });
- } else {
- return mPostNotificationTrackerFactory.newTracker(null);
- }
+ // The package probably doesn't have WAKE_LOCK permission and should not require it.
+ return Binder.withCleanCallingIdentity(() -> {
+ WakeLock wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ "NotificationManagerService:post:" + pkg);
+ wakeLock.setWorkSource(new WorkSource(uid, pkg));
+ wakeLock.acquire(POST_WAKE_LOCK_TIMEOUT.toMillis());
+ return mPostNotificationTrackerFactory.newTracker(wakeLock);
+ });
}
/**
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 0e37f10..b132a23 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -25,7 +25,6 @@
import static android.app.NotificationManager.IMPORTANCE_MAX;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
-import static android.util.StatsLog.ANNOTATION_ID_IS_UID;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
@@ -78,6 +77,7 @@
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
@@ -169,7 +169,6 @@
* fields.
*/
private static final int DEFAULT_LOCKED_APP_FIELDS = 0;
- private final SysUiStatsEvent.BuilderFactory mStatsEventBuilderFactory;
/**
* All user-lockable fields for a given application.
@@ -208,7 +207,6 @@
ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager,
NotificationChannelLogger notificationChannelLogger,
AppOpsManager appOpsManager, ManagedServices.UserProfiles userProfiles,
- SysUiStatsEvent.BuilderFactory statsEventBuilderFactory,
boolean showReviewPermissionsNotification) {
mContext = context;
mZenModeHelper = zenHelper;
@@ -219,7 +217,6 @@
mNotificationChannelLogger = notificationChannelLogger;
mAppOps = appOpsManager;
mUserProfiles = userProfiles;
- mStatsEventBuilderFactory = statsEventBuilderFactory;
mShowReviewPermissionsNotification = showReviewPermissionsNotification;
XML_VERSION = 4;
@@ -2190,11 +2187,7 @@
break;
}
pulledEvents++;
- SysUiStatsEvent.Builder event = mStatsEventBuilderFactory.newBuilder()
- .setAtomId(PACKAGE_NOTIFICATION_PREFERENCES);
final PackagePreferences r = mPackagePreferences.valueAt(i);
- event.writeInt(r.uid);
- event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
// collect whether this package's importance info was user-set for later, if needed
// before the migration is enabled, this will simply default to false in all cases.
@@ -2214,15 +2207,7 @@
pkgsWithPermissionsToHandle.remove(key);
}
- event.writeInt(importance);
- event.writeInt(r.visibility);
- event.writeInt(r.lockedAppFields);
-
- // optional bool user_set_importance = 5;
- event.writeBoolean(importanceIsUserSet);
-
- // optional FsiState fsi_state = 6;
final boolean isStickyHunFlagEnabled = SystemUiSystemPropertiesFlags.getResolver()
.isEnabled(NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI);
@@ -2232,20 +2217,23 @@
final int fsiState = getFsiState(r.pkg, r.uid, requestedFSIPermission,
isStickyHunFlagEnabled);
- event.writeInt(fsiState);
-
- // optional bool is_fsi_permission_user_set = 7;
final int currentPermissionFlags = mPm.getPermissionFlags(
android.Manifest.permission.USE_FULL_SCREEN_INTENT, r.pkg,
UserHandle.getUserHandleForUid(r.uid));
- final boolean isUserSet =
+ final boolean fsiIsUserSet =
isFsiPermissionUserSet(r.pkg, r.uid, fsiState, currentPermissionFlags,
isStickyHunFlagEnabled);
- event.writeBoolean(isUserSet);
-
- events.add(event.build());
+ events.add(FrameworkStatsLog.buildStatsEvent(
+ PACKAGE_NOTIFICATION_PREFERENCES,
+ /* optional int32 uid = 1 [(is_uid) = true] */ r.uid,
+ /* optional int32 importance = 2 */ importance,
+ /* optional int32 visibility = 3 */ r.visibility,
+ /* optional int32 user_locked_fields = 4 */ r.lockedAppFields,
+ /* optional bool user_set_importance = 5 */ importanceIsUserSet,
+ /* optional FsiState fsi_state = 6 */ fsiState,
+ /* optional bool is_fsi_permission_user_set = 7 */ fsiIsUserSet));
}
}
@@ -2256,18 +2244,18 @@
break;
}
pulledEvents++;
- SysUiStatsEvent.Builder event = mStatsEventBuilderFactory.newBuilder()
- .setAtomId(PACKAGE_NOTIFICATION_PREFERENCES);
- event.writeInt(p.first);
- event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
- event.writeInt(pkgPermissions.get(p).first ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
-
- // fill out the rest of the fields with default values so as not to confuse the
- // builder
- event.writeInt(DEFAULT_VISIBILITY);
- event.writeInt(DEFAULT_LOCKED_APP_FIELDS);
- event.writeBoolean(pkgPermissions.get(p).second); // user_set_importance field
- events.add(event.build());
+ // Because all fields are required in FrameworkStatsLog.buildStatsEvent, we have
+ // to fill in default values for all the unspecified fields.
+ events.add(FrameworkStatsLog.buildStatsEvent(
+ PACKAGE_NOTIFICATION_PREFERENCES,
+ /* optional int32 uid = 1 [(is_uid) = true] */ p.first,
+ /* optional int32 importance = 2 */ pkgPermissions.get(p).first
+ ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE,
+ /* optional int32 visibility = 3 */ DEFAULT_VISIBILITY,
+ /* optional int32 user_locked_fields = 4 */ DEFAULT_LOCKED_APP_FIELDS,
+ /* optional bool user_set_importance = 5 */ pkgPermissions.get(p).second,
+ /* optional FsiState fsi_state = 6 */ 0,
+ /* optional bool is_fsi_permission_user_set = 7 */ false));
}
}
}
@@ -2288,20 +2276,21 @@
if (++totalChannelsPulled > NOTIFICATION_CHANNEL_PULL_LIMIT) {
break;
}
- SysUiStatsEvent.Builder event = mStatsEventBuilderFactory.newBuilder()
- .setAtomId(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES);
- event.writeInt(r.uid);
- event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
- event.writeString(channel.getId());
- event.writeString(channel.getName().toString());
- event.writeString(channel.getDescription());
- event.writeInt(channel.getImportance());
- event.writeInt(channel.getUserLockedFields());
- event.writeBoolean(channel.isDeleted());
- event.writeBoolean(channel.getConversationId() != null);
- event.writeBoolean(channel.isDemoted());
- event.writeBoolean(channel.isImportantConversation());
- events.add(event.build());
+ events.add(FrameworkStatsLog.buildStatsEvent(
+ PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES,
+ /* optional int32 uid = 1 [(is_uid) = true] */ r.uid,
+ /* optional string channel_id = 2 */ channel.getId(),
+ /* optional string channel_name = 3 */ channel.getName().toString(),
+ /* optional string description = 4 */ channel.getDescription(),
+ /* optional int32 importance = 5 */ channel.getImportance(),
+ /* optional int32 user_locked_fields = 6 */
+ channel.getUserLockedFields(),
+ /* optional bool is_deleted = 7 */ channel.isDeleted(),
+ /* optional bool is_conversation = 8 */
+ channel.getConversationId() != null,
+ /* optional bool is_demoted_conversation = 9 */ channel.isDemoted(),
+ /* optional bool is_important_conversation = 10 */
+ channel.isImportantConversation()));
}
}
}
@@ -2323,16 +2312,15 @@
if (++totalGroupsPulled > NOTIFICATION_CHANNEL_GROUP_PULL_LIMIT) {
break;
}
- SysUiStatsEvent.Builder event = mStatsEventBuilderFactory.newBuilder()
- .setAtomId(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES);
- event.writeInt(r.uid);
- event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
- event.writeString(groupChannel.getId());
- event.writeString(groupChannel.getName().toString());
- event.writeString(groupChannel.getDescription());
- event.writeBoolean(groupChannel.isBlocked());
- event.writeInt(groupChannel.getUserLockedFields());
- events.add(event.build());
+ events.add(FrameworkStatsLog.buildStatsEvent(
+ PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES,
+ /* optional int32 uid = 1 [(is_uid) = true] */ r.uid,
+ /* optional string group_id = 2 */ groupChannel.getId(),
+ /* optional string group_name = 3 */ groupChannel.getName().toString(),
+ /* optional string description = 4 */ groupChannel.getDescription(),
+ /* optional bool is_blocked = 5 */ groupChannel.isBlocked(),
+ /* optional int32 user_locked_fields = 6 */
+ groupChannel.getUserLockedFields()));
}
}
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 1f5bd3e..e490745 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -21,7 +21,6 @@
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_REMOVED;
import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
import static android.service.notification.NotificationServiceProto.ROOT_CONFIG;
-import static android.util.StatsLog.ANNOTATION_ID_IS_UID;
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
@@ -81,6 +80,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -116,7 +116,6 @@
private final SettingsObserver mSettingsObserver;
private final AppOpsManager mAppOps;
private final NotificationManager mNotificationManager;
- private final SysUiStatsEvent.BuilderFactory mStatsEventBuilderFactory;
private ZenModeConfig mDefaultConfig;
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
private final ZenModeFiltering mFiltering;
@@ -152,7 +151,6 @@
private String[] mPriorityOnlyDndExemptPackages;
public ZenModeHelper(Context context, Looper looper, ConditionProviders conditionProviders,
- SysUiStatsEvent.BuilderFactory statsEventBuilderFactory,
SystemUiSystemPropertiesFlags.FlagResolver flagResolver,
ZenModeEventLogger zenModeEventLogger) {
mContext = context;
@@ -174,7 +172,6 @@
mFiltering = new ZenModeFiltering(mContext);
mConditions = new ZenModeConditions(this, conditionProviders);
mServiceConfig = conditionProviders.getConfig();
- mStatsEventBuilderFactory = statsEventBuilderFactory;
mFlagResolver = flagResolver;
mZenModeEventLogger = zenModeEventLogger;
}
@@ -1314,17 +1311,14 @@
for (int i = 0; i < numConfigs; i++) {
final int user = mConfigs.keyAt(i);
final ZenModeConfig config = mConfigs.valueAt(i);
- SysUiStatsEvent.Builder data = mStatsEventBuilderFactory.newBuilder()
- .setAtomId(DND_MODE_RULE)
- .writeInt(user)
- .writeBoolean(config.manualRule != null) // enabled
- .writeBoolean(config.areChannelsBypassingDnd)
- .writeInt(ROOT_CONFIG)
- .writeString("") // name, empty for root config
- .writeInt(Process.SYSTEM_UID) // system owns root config
- .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
- .writeByteArray(config.toZenPolicy().toProto());
- events.add(data.build());
+ events.add(FrameworkStatsLog.buildStatsEvent(DND_MODE_RULE,
+ /* optional int32 user = 1 */ user,
+ /* optional bool enabled = 2 */ config.manualRule != null,
+ /* optional bool channels_bypassing = 3 */ config.areChannelsBypassingDnd,
+ /* optional LoggedZenMode zen_mode = 4 */ ROOT_CONFIG,
+ /* optional string id = 5 */ "", // empty for root config
+ /* optional int32 uid = 6 */ Process.SYSTEM_UID, // system owns root config
+ /* optional DNDPolicyProto policy = 7 */ config.toZenPolicy().toProto()));
if (config.manualRule != null) {
ruleToProtoLocked(user, config.manualRule, true, events);
}
@@ -1355,21 +1349,18 @@
}
SysUiStatsEvent.Builder data;
- data = mStatsEventBuilderFactory.newBuilder()
- .setAtomId(DND_MODE_RULE)
- .writeInt(user)
- .writeBoolean(rule.enabled)
- .writeBoolean(false) // channels_bypassing unused for rules
- .writeInt(rule.zenMode)
- .writeString(id)
- .writeInt(getPackageUid(pkg, user))
- .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
byte[] policyProto = new byte[]{};
if (rule.zenPolicy != null) {
policyProto = rule.zenPolicy.toProto();
}
- data.writeByteArray(policyProto);
- events.add(data.build());
+ events.add(FrameworkStatsLog.buildStatsEvent(DND_MODE_RULE,
+ /* optional int32 user = 1 */ user,
+ /* optional bool enabled = 2 */ rule.enabled,
+ /* optional bool channels_bypassing = 3 */ false, // unused for rules
+ /* optional android.stats.dnd.ZenMode zen_mode = 4 */ rule.zenMode,
+ /* optional string id = 5 */ id,
+ /* optional int32 uid = 6 */ getPackageUid(pkg, user),
+ /* optional DNDPolicyProto policy = 7 */ policyProto));
}
private int getPackageUid(String pkg, int user) {
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 66a1703..a988821 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.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 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 @@
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 @@
}
}
if (clearPackageStateAndReturn) {
- clearPackageStateForUserLIF(ps, userId, outInfo, flags);
+ mRemovePackageHelper.clearPackageStateForUserLIF(ps, userId, outInfo, flags);
mPm.scheduleWritePackageRestrictions(user);
return;
}
@@ -531,55 +520,6 @@
}
}
- private void clearPackageStateForUserLIF(PackageSetting ps, int userId,
- PackageRemovedInfo outInfo, int flags) {
- final AndroidPackage pkg;
- final SharedUserSetting sus;
- synchronized (mPm.mLock) {
- pkg = mPm.mPackages.get(ps.getPackageName());
- sus = mPm.mSettings.getSharedUserSettingLPr(ps);
- }
-
- mAppDataHelper.destroyAppProfilesLIF(pkg);
-
- final List<AndroidPackage> sharedUserPkgs =
- sus != null ? sus.getPackages() : Collections.emptyList();
- final PreferredActivityHelper preferredActivityHelper = new PreferredActivityHelper(mPm);
- final int[] userIds = (userId == UserHandle.USER_ALL) ? mUserManagerInternal.getUserIds()
- : new int[] {userId};
- for (int nextUserId : userIds) {
- if (DEBUG_REMOVE) {
- Slog.d(TAG, "Updating package:" + ps.getPackageName() + " install state for user:"
- + nextUserId);
- }
- if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
- mAppDataHelper.destroyAppDataLIF(pkg, nextUserId,
- FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL);
- ps.setCeDataInode(-1, nextUserId);
- }
- mAppDataHelper.clearKeystoreData(nextUserId, ps.getAppId());
- preferredActivityHelper.clearPackagePreferredActivities(ps.getPackageName(),
- nextUserId);
- mPm.mDomainVerificationManager.clearPackageForUser(ps.getPackageName(), nextUserId);
- }
- mPermissionManager.onPackageUninstalled(ps.getPackageName(), ps.getAppId(), ps, pkg,
- sharedUserPkgs, userId);
-
- if (outInfo != null) {
- if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
- outInfo.mDataRemoved = true;
- }
- outInfo.mRemovedPackage = ps.getPackageName();
- outInfo.mInstallerPackageName = ps.getInstallSource().mInstallerPackageName;
- outInfo.mIsStaticSharedLib = pkg != null && pkg.getStaticSharedLibraryName() != null;
- outInfo.mRemovedAppId = ps.getAppId();
- outInfo.mRemovedUsers = userIds;
- outInfo.mBroadcastUsers = userIds;
- outInfo.mIsExternal = ps.isExternalStorage();
- outInfo.mRemovedPackageVersionCode = ps.getVersionCode();
- }
- }
-
@GuardedBy("mPm.mInstallLock")
private void deleteInstalledPackageLIF(PackageSetting ps,
boolean deleteCodeAndResources, int flags, @NonNull int[] allUserHandles,
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index 76203ac..8f7b721 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -30,6 +30,7 @@
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageArchiverService;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageDeleteObserver2;
import android.content.pm.IPackageInstaller;
@@ -99,6 +100,9 @@
private final PackageInstallerService mInstallerService;
@NonNull
+ private final PackageArchiverService mPackageArchiverService;
+
+ @NonNull
private final PackageProperty mPackageProperty;
@NonNull
@@ -127,7 +131,8 @@
@Nullable ComponentName instantAppResolverSettingsComponent,
@NonNull String requiredSupplementalProcessPackage,
@Nullable String servicesExtensionPackageName,
- @Nullable String sharedSystemSharedLibraryPackageName) {
+ @Nullable String sharedSystemSharedLibraryPackageName,
+ @NonNull PackageArchiverService packageArchiverService) {
mService = service;
mContext = context;
mDexOptHelper = dexOptHelper;
@@ -143,6 +148,7 @@
mRequiredSupplementalProcessPackage = requiredSupplementalProcessPackage;
mServicesExtensionPackageName = servicesExtensionPackageName;
mSharedSystemSharedLibraryPackageName = sharedSystemSharedLibraryPackageName;
+ mPackageArchiverService = packageArchiverService;
}
protected Computer snapshot() {
@@ -616,6 +622,12 @@
@Override
@Deprecated
+ public final IPackageArchiverService getPackageArchiverService() {
+ return mPackageArchiverService;
+ }
+
+ @Override
+ @Deprecated
public final void getPackageSizeInfo(final String packageName, int userId,
final IPackageStatsObserver observer) {
throw new UnsupportedOperationException(
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 9ac983d..6233c9b 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.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 @@
}
}
+ /**
+ * Returns an auth token for the provided writable FD.
+ *
+ * @param authFd a file descriptor to proof that the caller can write to the file.
+ * @param appUid uid of the calling app.
+ * @param userId id of the user whose app file to enable fs-verity.
+ *
+ * @return authToken, or null if a remote call shouldn't be continued. See {@link
+ * #checkBeforeRemote}.
+ *
+ * @throws InstallerException if the remote call failed.
+ */
+ public IInstalld.IFsveritySetupAuthToken createFsveritySetupAuthToken(
+ ParcelFileDescriptor authFd, int appUid, @UserIdInt int userId)
+ throws InstallerException {
+ if (!checkBeforeRemote()) {
+ return null;
+ }
+ try {
+ return mInstalld.createFsveritySetupAuthToken(authFd, appUid, userId);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
+ /**
+ * Enables fs-verity to the given app file.
+ *
+ * @param authToken a token previously returned from {@link #createFsveritySetupAuthToken}.
+ * @param filePath file path of the package to enable fs-verity.
+ * @param packageName name of the package.
+ *
+ * @return 0 if the operation was successful, otherwise {@code errno}.
+ *
+ * @throws InstallerException if the remote call failed (e.g. see {@link #checkBeforeRemote}).
+ */
+ public int enableFsverity(IInstalld.IFsveritySetupAuthToken authToken, String filePath,
+ String packageName) throws InstallerException {
+ if (!checkBeforeRemote()) {
+ throw new InstallerException("fs-verity wasn't enabled with an isolated installer");
+ }
+ BlockGuard.getVmPolicy().onPathAccess(filePath);
+ try {
+ return mInstalld.enableFsverity(authToken, filePath, packageName);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
public static class InstallerException extends Exception {
public InstallerException(String detailMessage) {
super(detailMessage);
diff --git a/services/core/java/com/android/server/pm/ArchiveManager.java b/services/core/java/com/android/server/pm/PackageArchiverService.java
similarity index 62%
rename from services/core/java/com/android/server/pm/ArchiveManager.java
rename to services/core/java/com/android/server/pm/PackageArchiverService.java
index 5435206..9c31dc9 100644
--- a/services/core/java/com/android/server/pm/ArchiveManager.java
+++ b/services/core/java/com/android/server/pm/PackageArchiverService.java
@@ -22,11 +22,13 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.IntentSender;
+import android.content.pm.IPackageArchiverService;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.os.Binder;
+import android.os.ParcelableException;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -47,7 +49,9 @@
* while the data directory is kept. Archived apps are included in the list of launcher apps where
* tapping them re-installs the full app.
*/
-final class ArchiveManager {
+public class PackageArchiverService extends IPackageArchiverService.Stub {
+
+ private static final String TAG = "PackageArchiver";
private final Context mContext;
private final PackageManagerService mPm;
@@ -55,36 +59,39 @@
@Nullable
private LauncherApps mLauncherApps;
- ArchiveManager(Context context, PackageManagerService mPm) {
+ public PackageArchiverService(Context context, PackageManagerService mPm) {
this.mContext = context;
this.mPm = mPm;
}
- void archiveApp(
+ @Override
+ public void requestArchive(
@NonNull String packageName,
@NonNull String callerPackageName,
- @NonNull UserHandle user,
- @NonNull IntentSender intentSender) throws PackageManager.NameNotFoundException {
+ @NonNull IntentSender intentSender,
+ @NonNull UserHandle userHandle) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(callerPackageName);
- Objects.requireNonNull(user);
Objects.requireNonNull(intentSender);
+ Objects.requireNonNull(userHandle);
Computer snapshot = mPm.snapshotComputer();
- int callingUid = Binder.getCallingUid();
- int userId = user.getIdentifier();
- String callingPackageName = snapshot.getNameForUid(callingUid);
- snapshot.enforceCrossUserPermission(callingUid, userId, true, true,
+ int userId = userHandle.getIdentifier();
+ int binderUid = Binder.getCallingUid();
+ int providedUid = snapshot.getPackageUid(callerPackageName, 0, userId);
+ snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
"archiveApp");
- verifyCaller(callerPackageName, callingPackageName);
- PackageStateInternal ps = getPackageState(packageName, snapshot, callingUid, user);
- verifyInstaller(packageName, ps.getInstallSource());
+ verifyCaller(providedUid, binderUid);
+ PackageStateInternal ps = getPackageState(packageName, snapshot, binderUid, userId);
+ verifyInstaller(packageName, ps);
+ // TODO(b/291569242) Verify that this list is not empty and return failure with
+ // intentsender
List<LauncherActivityInfo> mainActivities = getLauncherApps().getActivityList(
ps.getPackageName(),
new UserHandle(userId));
- // TODO(b/291569242) Verify that this list is not empty and return failure with intentsender
+ // TODO(b/282952870) Bug: should happen after the uninstall completes successfully
storeArchiveState(ps, mainActivities, userId);
// TODO(b/278553670) Add special strings for the delete dialog
@@ -93,15 +100,25 @@
callerPackageName, DELETE_KEEP_DATA, intentSender, userId);
}
+ private static void verifyInstaller(String packageName, PackageStateInternal ps) {
+ if (ps.getInstallSource().mUpdateOwnerPackageName == null
+ && ps.getInstallSource().mInstallerPackageName == null) {
+ throw new ParcelableException(
+ new PackageManager.NameNotFoundException(
+ TextUtils.formatSimple("No installer found to archive app %s.",
+ packageName)));
+ }
+ }
+
@NonNull
private static PackageStateInternal getPackageState(String packageName,
- Computer snapshot, int callingUid, UserHandle user)
- throws PackageManager.NameNotFoundException {
+ Computer snapshot, int callingUid, int userId) {
PackageStateInternal ps = snapshot.getPackageStateFiltered(packageName, callingUid,
- user.getIdentifier());
+ userId);
if (ps == null) {
- throw new PackageManager.NameNotFoundException(
- TextUtils.formatSimple("Package %s not found.", packageName));
+ throw new ParcelableException(
+ new PackageManager.NameNotFoundException(
+ TextUtils.formatSimple("Package %s not found.", packageName)));
}
return ps;
}
@@ -114,8 +131,7 @@
}
private void storeArchiveState(PackageStateInternal ps,
- List<LauncherActivityInfo> mainActivities, int userId)
- throws PackageManager.NameNotFoundException {
+ List<LauncherActivityInfo> mainActivities, int userId) {
List<ArchiveActivityInfo> activityInfos = new ArrayList<>();
for (int i = 0; i < mainActivities.size(); i++) {
// TODO(b/278553670) Extract and store launcher icons
@@ -130,41 +146,34 @@
? installSource.mUpdateOwnerPackageName : installSource.mInstallerPackageName;
synchronized (mPm.mLock) {
- getPackageSetting(ps.getPackageName(), userId).modifyUserState(userId).setArchiveState(
- new ArchiveState(activityInfos, installerPackageName));
+ PackageSetting packageSetting = getPackageSettingLocked(ps.getPackageName(), userId);
+ packageSetting
+ .modifyUserState(userId)
+ .setArchiveState(new ArchiveState(activityInfos, installerPackageName));
}
}
@NonNull
@GuardedBy("mPm.mLock")
- private PackageSetting getPackageSetting(String packageName, int userId)
- throws PackageManager.NameNotFoundException {
+ private PackageSetting getPackageSettingLocked(String packageName, int userId) {
PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
+ // Shouldn't happen, we already verify presence of the package in getPackageState()
if (ps == null || !ps.getUserStateOrDefault(userId).isInstalled()) {
- throw new PackageManager.NameNotFoundException(
- TextUtils.formatSimple("Package %s not found.", packageName));
+ throw new ParcelableException(
+ new PackageManager.NameNotFoundException(
+ TextUtils.formatSimple("Package %s not found.", packageName)));
}
return ps;
}
- private static void verifyCaller(String callerPackageName, String callingPackageName) {
- if (!TextUtils.equals(callingPackageName, callerPackageName)) {
+ private static void verifyCaller(int providedUid, int binderUid) {
+ if (providedUid != binderUid) {
throw new SecurityException(
TextUtils.formatSimple(
- "The callerPackageName %s set by the caller doesn't match the "
- + "caller's own package name %s.",
- callerPackageName,
- callingPackageName));
- }
- }
-
- private static void verifyInstaller(String packageName, InstallSource installSource) {
- // TODO(b/291060290) Verify installer supports unarchiving
- if (installSource.mUpdateOwnerPackageName == null
- && installSource.mInstallerPackageName == null) {
- throw new SecurityException(
- TextUtils.formatSimple("No installer found to archive app %s.",
- packageName));
+ "The UID %s of callerPackageName set by the caller doesn't match the "
+ + "caller's actual UID %s.",
+ providedUid,
+ binderUid));
}
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 7ccf713..ac78429 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -796,6 +796,8 @@
final PackageInstallerService mInstallerService;
+ final PackageArchiverService mArchiverService;
+
final ArtManagerService mArtManagerService;
// TODO(b/260124949): Remove these.
@@ -1624,7 +1626,8 @@
(i, pm) -> new CrossProfileIntentFilterHelper(i.getSettings(),
i.getUserManagerService(), i.getLock(), i.getUserManagerInternal(),
context),
- (i, pm) -> new UpdateOwnershipHelper());
+ (i, pm) -> new UpdateOwnershipHelper(),
+ (i, pm) -> new PackageArchiverService(i.getContext(), pm));
if (Build.VERSION.SDK_INT <= 0) {
Slog.w(TAG, "**** ro.build.version.sdk not set!");
@@ -1769,6 +1772,7 @@
mFactoryTest = testParams.factoryTest;
mIncrementalManager = testParams.incrementalManager;
mInstallerService = testParams.installerService;
+ mArchiverService = testParams.archiverService;
mInstantAppRegistry = testParams.instantAppRegistry;
mChangedPackagesTracker = testParams.changedPackagesTracker;
mInstantAppResolverConnection = testParams.instantAppResolverConnection;
@@ -1967,9 +1971,6 @@
mApexManager = injector.getApexManager();
mAppsFilter = mInjector.getAppsFilter();
- mInstantAppRegistry = new InstantAppRegistry(mContext, mPermissionManager,
- mInjector.getUserManagerInternal(), new DeletePackageHelper(this));
-
mChangedPackagesTracker = new ChangedPackagesTracker();
mAppInstallDir = new File(Environment.getDataDirectory(), "app");
@@ -1983,8 +1984,11 @@
mAppDataHelper = new AppDataHelper(this);
mInstallPackageHelper = new InstallPackageHelper(this, mAppDataHelper);
mRemovePackageHelper = new RemovePackageHelper(this, mAppDataHelper);
- mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper,
- mAppDataHelper);
+ mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper);
+
+ mInstantAppRegistry = new InstantAppRegistry(mContext, mPermissionManager,
+ mInjector.getUserManagerInternal(), mDeletePackageHelper);
+
mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper);
mPreferredActivityHelper = new PreferredActivityHelper(this);
mResolveIntentHelper = new ResolveIntentHelper(mContext, mPreferredActivityHelper,
@@ -2348,6 +2352,7 @@
});
mInstallerService = mInjector.getPackageInstallerService();
+ mArchiverService = mInjector.getPackageArchiverService();
final ComponentName instantAppResolverComponent = getInstantAppResolver(computer);
if (instantAppResolverComponent != null) {
if (DEBUG_INSTANT) {
@@ -4608,7 +4613,7 @@
mDomainVerificationConnection, mInstallerService, mPackageProperty,
mResolveComponentName, mInstantAppResolverSettingsComponent,
mRequiredSdkSandboxPackage, mServicesExtensionPackageName,
- mSharedSystemSharedLibraryPackageName);
+ mSharedSystemSharedLibraryPackageName, mArchiverService);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index 51840e7..9495279 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -127,6 +127,8 @@
mPreparingPackageParserProducer;
private final Singleton<PackageInstallerService>
mPackageInstallerServiceProducer;
+ private final Singleton<PackageArchiverService>
+ mPackageArchiverServiceProducer;
private final ProducerWithArgument<InstantAppResolverConnection, ComponentName>
mInstantAppResolverConnectionProducer;
private final Singleton<LegacyPermissionManagerInternal>
@@ -185,7 +187,8 @@
Producer<IBackupManager> iBackupManager,
Producer<SharedLibrariesImpl> sharedLibrariesProducer,
Producer<CrossProfileIntentFilterHelper> crossProfileIntentFilterHelperProducer,
- Producer<UpdateOwnershipHelper> updateOwnershipHelperProducer) {
+ Producer<UpdateOwnershipHelper> updateOwnershipHelperProducer,
+ Producer<PackageArchiverService> packageArchiverServiceProducer) {
mContext = context;
mLock = lock;
mInstaller = installer;
@@ -241,6 +244,7 @@
mCrossProfileIntentFilterHelperProducer = new Singleton<>(
crossProfileIntentFilterHelperProducer);
mUpdateOwnershipHelperProducer = new Singleton<>(updateOwnershipHelperProducer);
+ mPackageArchiverServiceProducer = new Singleton<>(packageArchiverServiceProducer);
}
/**
@@ -387,6 +391,10 @@
return mPackageInstallerServiceProducer.get(this, mPackageManager);
}
+ public PackageArchiverService getPackageArchiverService() {
+ return mPackageArchiverServiceProducer.get(this, mPackageManager);
+ }
+
public InstantAppResolverConnection getInstantAppResolverConnection(
ComponentName instantAppResolverComponent) {
return mInstantAppResolverConnectionProducer.produce(
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index ca57209..b91ce4b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -60,6 +60,7 @@
public @Nullable String incidentReportApproverPackage;
public IncrementalManager incrementalManager;
public PackageInstallerService installerService;
+ public PackageArchiverService archiverService;
public InstantAppRegistry instantAppRegistry;
public ChangedPackagesTracker changedPackagesTracker = new ChangedPackagesTracker();
public InstantAppResolverConnection instantAppResolverConnection;
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 6d3b26c..d4f30fe 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 @@
}
}
+ 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 @@
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 @@
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 dd434fbe..3e4dd16 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 @@
// 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 b2dcf37..24323c8 100644
--- a/services/core/java/com/android/server/pm/permission/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
@@ -4,7 +4,7 @@
"name": "CtsPermissionTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
"include-filter": "android.permission.cts.BackgroundPermissionsTest"
@@ -32,7 +32,7 @@
"name": "CtsPermissionPolicyTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
"include-filter": "android.permissionpolicy.cts.RestrictedPermissionsTest"
diff --git a/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java b/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java
index a2177e8..0bb969f 100644
--- a/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java
+++ b/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java
@@ -17,13 +17,13 @@
import android.content.pm.parsing.ApkLiteParseUtils;
import android.content.pm.parsing.PackageLite;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils.ParseFlags;
import android.content.res.ApkAssets;
import android.content.res.AssetManager;
import android.os.Build;
import com.android.internal.util.ArrayUtils;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils.ParseFlags;
import libcore.io.IoUtils;
@@ -82,8 +82,8 @@
}
AssetManager assets = new AssetManager();
- assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- Build.VERSION.RESOURCES_SDK_INT);
+ assets.setConfiguration(0, 0, null, new String[0], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, Build.VERSION.RESOURCES_SDK_INT);
assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
mCachedAssetManager = assets;
diff --git a/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java b/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java
index 1a8c1996..56d92fb 100644
--- a/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java
+++ b/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java
@@ -80,8 +80,8 @@
private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) {
final AssetManager assets = new AssetManager();
- assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- Build.VERSION.RESOURCES_SDK_INT);
+ assets.setConfiguration(0, 0, null, new String[0], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, Build.VERSION.RESOURCES_SDK_INT);
assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
return assets;
}
diff --git a/services/core/java/com/android/server/recoverysystem/hal/BootControlHIDL.java b/services/core/java/com/android/server/recoverysystem/hal/BootControlHIDL.java
index 65325c2..7c4d787 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.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 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 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 9529621..3aed6e3 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.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.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 @@
.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 @@
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 @@
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 @@
} 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 f744d00..565eb6e 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 @@
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 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.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 @@
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 @@
/** 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 @@
@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 @@
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 @@
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 @@
}
}
+ private int computeCurrentLightColor() {
+ long liveAmbientLightTotal = getLiveAmbientLightTotal();
+ long currentInterval = getCurrentIntervalMillis();
+
+ for (int i = 0; i < mThresholds.length; i++) {
+ if (liveAmbientLightTotal < currentInterval * mThresholds[i]) {
+ return mColors[i];
+ }
+ }
+ return mColors[mColors.length - 1];
+ }
+
private void updateSensorListener(boolean shouldSessionEnd) {
if (shouldSessionEnd && mIsAmbientLightListenerRegistered) {
mSensorManager.unregisterListener(this);
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 7ea9870..234e3f4 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -41,6 +41,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.graphics.Rect;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
@@ -94,6 +95,7 @@
import android.view.InputChannel;
import android.view.Surface;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
@@ -177,6 +179,11 @@
private final ActivityManager mActivityManager;
+ private boolean mExternalInputLoggingDisplayNameFilterEnabled = false;
+ private final HashSet<String> mExternalInputLoggingDeviceOnScreenDisplayNames =
+ new HashSet<String>();
+ private final List<String> mExternalInputLoggingDeviceBrandNames = new ArrayList<String>();
+
public TvInputManagerService(Context context) {
super(context);
@@ -192,6 +199,8 @@
synchronized (mLock) {
getOrCreateUserStateLocked(mCurrentUserId);
}
+
+ initExternalInputLoggingConfigs();
}
@Override
@@ -224,6 +233,21 @@
}
}
+ private void initExternalInputLoggingConfigs() {
+ mExternalInputLoggingDisplayNameFilterEnabled = mContext.getResources().getBoolean(
+ R.bool.config_tvExternalInputLoggingDisplayNameFilterEnabled);
+ if (!mExternalInputLoggingDisplayNameFilterEnabled) {
+ return;
+ }
+ final String[] deviceOnScreenDisplayNames = mContext.getResources().getStringArray(
+ R.array.config_tvExternalInputLoggingDeviceOnScreenDisplayNames);
+ final String[] deviceBrandNames = mContext.getResources().getStringArray(
+ R.array.config_tvExternalInputLoggingDeviceBrandNames);
+ mExternalInputLoggingDeviceOnScreenDisplayNames.addAll(
+ Arrays.asList(deviceOnScreenDisplayNames));
+ mExternalInputLoggingDeviceBrandNames.addAll(Arrays.asList(deviceBrandNames));
+ }
+
private void registerBroadcastReceivers() {
PackageMonitor monitor = new PackageMonitor() {
private void buildTvInputList(String[] packages) {
@@ -3073,13 +3097,32 @@
hdmiPort = hdmiDeviceInfo.getPortId();
if (hdmiDeviceInfo.isCecDevice()) {
displayName = hdmiDeviceInfo.getDisplayName();
+ if (mExternalInputLoggingDisplayNameFilterEnabled) {
+ displayName = filterExternalInputLoggingDisplayName(displayName);
+ }
vendorId = hdmiDeviceInfo.getVendorId();
}
}
}
FrameworkStatsLog.write(FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT, eventType, inputState,
- inputType, displayName, vendorId, hdmiPort, tifSessionId);
+ inputType, vendorId, hdmiPort, tifSessionId, displayName);
+ }
+
+ private String filterExternalInputLoggingDisplayName(String displayName) {
+ String nullDisplayName = "NULL_DISPLAY_NAME", filteredDisplayName = "FILTERED_DISPLAY_NAME";
+ if (displayName == null) {
+ return nullDisplayName;
+ }
+ if (mExternalInputLoggingDeviceOnScreenDisplayNames.contains(displayName)) {
+ return displayName;
+ }
+ for (String brandName : mExternalInputLoggingDeviceBrandNames) {
+ if (displayName.toUpperCase().contains(brandName.toUpperCase())) {
+ return brandName;
+ }
+ }
+ return filteredDisplayName;
}
private static final class UserState {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9eec5f8..6085488 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -225,7 +225,6 @@
import static com.android.server.wm.LetterboxConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
import static com.android.server.wm.StartingData.AFTER_TRANSACTION_COPY_TO_CLIENT;
-import static com.android.server.wm.StartingData.AFTER_TRANSACTION_IDLE;
import static com.android.server.wm.StartingData.AFTER_TRANSACTION_REMOVE_DIRECTLY;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
@@ -2698,7 +2697,8 @@
private void requestCopySplashScreen() {
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_COPYING;
- if (!mAtmService.mTaskOrganizerController.copySplashScreenView(getTask())) {
+ if (mStartingSurface == null || !mAtmService.mTaskOrganizerController.copySplashScreenView(
+ getTask(), mStartingSurface.mTaskOrganizer)) {
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
removeStartingWindow();
}
@@ -2711,12 +2711,14 @@
*/
void onCopySplashScreenFinish(SplashScreenViewParcelable parcelable) {
removeTransferSplashScreenTimeout();
- // unable to copy from shell, maybe it's not a splash screen. or something went wrong.
- // either way, abort and reset the sequence.
- if (parcelable == null
+ final SurfaceControl windowAnimationLeash = (parcelable == null
|| mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING
|| mStartingWindow == null || mStartingWindow.mRemoved
- || finishing) {
+ || finishing) ? null
+ : TaskOrganizerController.applyStartingWindowAnimation(mStartingWindow);
+ if (windowAnimationLeash == null) {
+ // Unable to copy from shell, maybe it's not a splash screen, or something went wrong.
+ // Either way, abort and reset the sequence.
if (parcelable != null) {
parcelable.clearIfNeeded();
}
@@ -2724,9 +2726,6 @@
removeStartingWindow();
return;
}
- // schedule attach splashScreen to client
- final SurfaceControl windowAnimationLeash = TaskOrganizerController
- .applyStartingWindowAnimation(mStartingWindow);
try {
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT;
mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
@@ -2766,7 +2765,8 @@
&& (mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH
|| mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_IDLE)) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Cleaning splash screen token=%s", this);
- mAtmService.mTaskOrganizerController.onAppSplashScreenViewRemoved(getTask());
+ mAtmService.mTaskOrganizerController.onAppSplashScreenViewRemoved(getTask(),
+ mStartingSurface != null ? mStartingSurface.mTaskOrganizer : null);
}
}
@@ -2854,7 +2854,6 @@
} else if (lastData.mRemoveAfterTransaction == AFTER_TRANSACTION_COPY_TO_CLIENT) {
removeStartingWindow();
}
- lastData.mRemoveAfterTransaction = AFTER_TRANSACTION_IDLE;
}
void removeStartingWindowAnimation(boolean prepareAnimation) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 59159bb..42c3630 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 @@
mRecentTasks.notifyTaskPersisterLocked(task, flush);
}
- boolean isKeyguardLocked(int displayId) {
- return mKeyguardController.isKeyguardLocked(displayId);
- }
-
/**
* Clears launch params for the given package.
*
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index eb15b31..6eb9ed69 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -794,6 +794,14 @@
return false;
}
+ // Try pausing the existing resumed activity in the same TaskFragment if any.
+ final TaskFragment taskFragment = r.getTaskFragment();
+ if (taskFragment != null && taskFragment.getResumedActivity() != null) {
+ if (taskFragment.startPausing(mUserLeaving, false /* uiSleeping */, r, "realStart")) {
+ return false;
+ }
+ }
+
final Task task = r.getTask();
final Task rootTask = task.getRootTask();
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 8c1d8fa..1a319ad 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 @@
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 @@
}
}
- /**
- * 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 @@
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 6b4cc25..57f8268 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 @@
// transition exists, so this affects only when no lock screen is set. Otherwise
// keyguard going away animation will be played.
// See also AppTransitionController#getTransitCompatType for more details.
- if ((!mTaskSupervisor.getKeyguardController().isDisplayOccluded(display.mDisplayId)
+ if ((!mTaskSupervisor.getKeyguardController().isKeyguardOccluded(display.mDisplayId)
&& token.mTag.equals(KEYGUARD_SLEEP_TOKEN_TAG))
|| token.mTag.equals(DISPLAY_OFF_SLEEP_TOKEN_TAG)) {
display.mSkipAppTransitionAnimation = true;
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index a23547e..2d281c4 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -76,7 +76,7 @@
* This starting window should be removed after applying the start transaction of transition,
* which ensures the app window has shown.
*/
- @AfterTransaction int mRemoveAfterTransaction;
+ @AfterTransaction int mRemoveAfterTransaction = AFTER_TRANSACTION_IDLE;
/** Whether to prepare the removal animation. */
boolean mPrepareRemoveAnimation;
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index 0bb773a..a55c232 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.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 @@
}
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 @@
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 @@
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 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 f9bbc68..387a876 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 @@
&& 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 3d01001..41e49b9 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 @@
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 @@
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 @@
}
}
- 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 @@
* @see com.android.wm.shell.ShellTaskOrganizer#onAppSplashScreenViewRemoved(int)
* @see SplashScreenView#remove()
*/
- public void onAppSplashScreenViewRemoved(Task task) {
+ public void onAppSplashScreenViewRemoved(Task task, ITaskOrganizer organizer) {
final Task rootTask = task.getRootTask();
if (rootTask == null) {
return;
}
- final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
+ final ITaskOrganizer lastOrganizer = organizer != null ? organizer : getTaskOrganizer();
if (lastOrganizer == null) {
return;
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index e9af42b..81f91c7 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1127,6 +1127,9 @@
// though, notify the controller to prevent degenerate cases.
if (!r.isVisibleRequested()) {
mController.mValidateCommitVis.add(r);
+ } else {
+ // Make sure onAppTransitionFinished can be notified.
+ mParticipants.add(r);
}
return;
}
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 31afcbf..4d73358 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -519,7 +519,8 @@
for (int i = mFixedRotationTransformState.mAssociatedTokens.size() - 1; i >= 0; i--) {
final ActivityRecord r =
mFixedRotationTransformState.mAssociatedTokens.get(i).asActivityRecord();
- if (r != null && r.isInTransition()) {
+ // Only care about the transition at Activity/Task level.
+ if (r != null && r.inTransitionSelfOrParent() && !r.mDisplayContent.inTransition()) {
return true;
}
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 788299c..6e0d98c 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -733,7 +733,13 @@
if (controller == nullptr) {
ensureSpriteControllerLocked();
- controller = PointerController::create(this, mLooper, *mLocked.spriteController);
+ static const bool ENABLE_POINTER_CHOREOGRAPHER =
+ sysprop::InputProperties::enable_pointer_choreographer().value_or(false);
+
+ // Disable the functionality of the legacy PointerController if PointerChoreographer is
+ // enabled.
+ controller = PointerController::create(this, mLooper, *mLocked.spriteController,
+ /*enabled=*/!ENABLE_POINTER_CHOREOGRAPHER);
mLocked.legacyPointerController = controller;
updateInactivityTimeoutLocked();
}
@@ -745,7 +751,7 @@
std::scoped_lock _l(mLock);
ensureSpriteControllerLocked();
std::shared_ptr<PointerController> pc =
- PointerController::create(this, mLooper, *mLocked.spriteController);
+ PointerController::create(this, mLooper, *mLocked.spriteController, /*enabled=*/true);
mLocked.pointerControllers.emplace_back(pc);
return pc;
}
diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java
index ea5ce65..49962ea 100644
--- a/services/net/java/android/net/util/NetworkConstants.java
+++ b/services/net/java/android/net/util/NetworkConstants.java
@@ -52,7 +52,6 @@
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 b2dcf37..24323c8 100644
--- a/services/permission/TEST_MAPPING
+++ b/services/permission/TEST_MAPPING
@@ -4,7 +4,7 @@
"name": "CtsPermissionTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
"include-filter": "android.permission.cts.BackgroundPermissionsTest"
@@ -32,7 +32,7 @@
"name": "CtsPermissionPolicyTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
"include-filter": "android.permissionpolicy.cts.RestrictedPermissionsTest"
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt
index 5f26d6f..cd37674 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt
@@ -31,6 +31,7 @@
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mockito.doReturn
import kotlin.test.assertFailsWith
class PackageManagerLocalSnapshotTest {
@@ -154,7 +155,7 @@
put(packageStateUser0.packageName, packageStateUser0)
put(packageStateUser10.packageName, packageStateUser10)
}
- whenever(this.packageStates) { packageStates }
+ doReturn(packageStates).whenever(this).packageStates
whenever(getPackageStateFiltered(anyString(), anyInt(), anyInt())) {
packageStates[arguments[0]]?.takeUnless {
shouldFilterApplication(it, arguments[1] as Int, arguments[2] as Int)
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index 55645d7..9fbf86e 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -48,6 +48,7 @@
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.anyString
+import org.mockito.Mockito.doReturn
import org.mockito.Mockito.eq
import org.mockito.Mockito.mock
import org.mockito.Mockito.verifyNoMoreInteractions
@@ -351,12 +352,12 @@
whenever(this.domainSetId) { domainSetId }
whenever(getUserStateOrDefault(0)) { PackageUserStateInternal.DEFAULT }
whenever(getUserStateOrDefault(1)) { PackageUserStateInternal.DEFAULT }
- whenever(userStates) {
+ doReturn(
SparseArray<PackageUserStateInternal>().apply {
this[0] = PackageUserStateInternal.DEFAULT
this[1] = PackageUserStateInternal.DEFAULT
}
- }
+ ).whenever(this).userStates
whenever(isSystem) { false }
whenever(signingDetails) { SigningDetails.UNKNOWN }
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
index 86c4335..47d9196 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
@@ -44,6 +44,7 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mockito.doReturn
import java.util.UUID
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.test.assertFailsWith
@@ -555,12 +556,12 @@
whenever(this.domainSetId) { domainSetId }
whenever(getUserStateOrDefault(0)) { pkgUserState0() }
whenever(getUserStateOrDefault(1)) { pkgUserState1() }
- whenever(userStates) {
+ doReturn(
SparseArray<PackageUserStateInternal>().apply {
this[0] = pkgUserState0()
this[1] = pkgUserState1()
}
- }
+ ).whenever(this).userStates
whenever(isSystem) { false }
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
index e55ff3b..98d7801 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
@@ -1084,12 +1084,12 @@
whenever(this.domainSetId) { domainSetId }
whenever(getUserStateOrDefault(0)) { PackageUserStateInternal.DEFAULT }
whenever(getUserStateOrDefault(10)) { PackageUserStateInternal.DEFAULT }
- whenever(userStates) {
+ doReturn(
SparseArray<PackageUserStateInternal>().apply {
this[0] = PackageUserStateInternal.DEFAULT
this[1] = PackageUserStateInternal.DEFAULT
}
- }
+ ).whenever(this).userStates
whenever(isSystem) { isSystemApp }
val mockSigningDetails = SigningDetails(arrayOf(spy(Signature(signature)) {
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
index 427b5b3..4a211df 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
@@ -41,6 +41,7 @@
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.anyString
+import org.mockito.Mockito.doReturn
import org.mockito.Mockito.eq
import org.mockito.Mockito.verify
import java.util.UUID
@@ -218,12 +219,12 @@
whenever(domainSetId) { TEST_UUID }
whenever(getUserStateOrDefault(0)) { PackageUserStateInternal.DEFAULT }
whenever(getUserStateOrDefault(10)) { PackageUserStateInternal.DEFAULT }
- whenever(userStates) {
+ doReturn(
SparseArray<PackageUserStateInternal>().apply {
this[0] = PackageUserStateInternal.DEFAULT
this[1] = PackageUserStateInternal.DEFAULT
}
- }
+ ).whenever(this).userStates
whenever(isSystem) { false }
}
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
index 6bb5f39..d54d608 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
@@ -41,6 +41,8 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mockito.doReturn
+
import java.util.UUID
class DomainVerificationUserStateOverrideTest {
@@ -155,12 +157,12 @@
whenever(this.domainSetId) { domainSetId }
whenever(getUserStateOrDefault(0)) { PackageUserStateInternal.DEFAULT }
whenever(getUserStateOrDefault(1)) { PackageUserStateInternal.DEFAULT }
- whenever(userStates) {
+ doReturn(
SparseArray<PackageUserStateInternal>().apply {
this[0] = PackageUserStateInternal.DEFAULT
this[1] = PackageUserStateInternal.DEFAULT
}
- }
+ ).whenever(this).userStates
whenever(isSystem) { false }
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index 76e6ec7..8e01a11 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -32,8 +32,10 @@
import static org.mockito.Mockito.anyFloat;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -390,6 +392,35 @@
assertEquals(Float.POSITIVE_INFINITY, hbmc.getHdrBrightnessValue(), 0.0);
}
+ @Test
+ public void testHdrRespectsChangingDesiredHdrSdrRatio() {
+ final Runnable hbmChangedCallback = mock(Runnable.class);
+ final HighBrightnessModeController hbmc = new TestHbmBuilder()
+ .setClock(new OffsettableClock())
+ .setHdrBrightnessConfig(mHdrBrightnessDeviceConfigMock)
+ .setHbmChangedCallback(hbmChangedCallback)
+ .build();
+
+ // Passthrough return the max desired hdr/sdr ratio
+ when(mHdrBrightnessDeviceConfigMock.getHdrBrightnessFromSdr(anyFloat(), anyFloat()))
+ .thenAnswer(i -> i.getArgument(1));
+
+ hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 2.0f /*maxDesiredHdrSdrRatio*/);
+ advanceTime(0);
+ assertEquals(2.0f, hbmc.getHdrBrightnessValue(), EPSILON);
+ verify(hbmChangedCallback, times(1)).run();
+
+ // Verify that a change in only the desired hdrSdrRatio still results in the changed
+ // callback being invoked
+ hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/,
+ 3.0f /*maxDesiredHdrSdrRatio*/);
+ advanceTime(0);
+ assertEquals(3.0f, hbmc.getHdrBrightnessValue(), 0.0);
+ verify(hbmChangedCallback, times(2)).run();
+ }
+
@Test
public void testHdrTrumpsSunlight() {
@@ -698,6 +729,7 @@
private class TestHbmBuilder {
OffsettableClock mClock;
HighBrightnessModeController.HdrBrightnessDeviceConfig mHdrBrightnessCfg;
+ Runnable mHdrChangedCallback = () -> {};
TestHbmBuilder setClock(OffsettableClock clock) {
mClock = clock;
@@ -711,6 +743,11 @@
return this;
}
+ TestHbmBuilder setHbmChangedCallback(Runnable runnable) {
+ mHdrChangedCallback = runnable;
+ return this;
+ }
+
HighBrightnessModeController build() {
initHandler(mClock);
if (mHighBrightnessModeMetadata == null) {
@@ -718,8 +755,8 @@
}
return new HighBrightnessModeController(mInjectorMock, mHandler, DISPLAY_WIDTH,
DISPLAY_HEIGHT, mDisplayToken, mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX,
- DEFAULT_HBM_DATA, mHdrBrightnessCfg, () -> {}, mHighBrightnessModeMetadata,
- mContextSpy);
+ DEFAULT_HBM_DATA, mHdrBrightnessCfg, mHdrChangedCallback,
+ mHighBrightnessModeMetadata, mContextSpy);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 3c75332..e578ea3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -567,9 +567,8 @@
@Throws(Exception::class)
private fun stageInstantAppResolverScan() {
- whenever(mocks.resources.getStringArray(R.array.config_ephemeralResolverPackage)) {
- arrayOf("com.android.test.ephemeral.resolver")
- }
+ doReturn(arrayOf("com.android.test.ephemeral.resolver"))
+ .whenever(mocks.resources).getStringArray(R.array.config_ephemeralResolverPackage)
stageScanNewPackage("com.android.test.ephemeral.resolver",
1L, getPartitionFromFlag(PackageManagerService.SCAN_AS_PRODUCT).privAppFolder,
withPackage = { pkg: PackageImpl ->
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
similarity index 74%
rename from services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java
rename to services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
index a8b0a7b..c7e1bda 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
@@ -37,10 +37,9 @@
import android.content.pm.VersionedPackage;
import android.os.Binder;
import android.os.Build;
-import android.os.Process;
+import android.os.ParcelableException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
-import android.text.TextUtils;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -62,7 +61,7 @@
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
-public class ArchiveManagerTest {
+public class PackageArchiverServiceTest {
private static final String PACKAGE = "com.example";
private static final String CALLER_PACKAGE = "com.vending";
@@ -95,10 +94,11 @@
private final List<LauncherActivityInfo> mLauncherActivityInfos = createLauncherActivities();
+ private final int mUserId = UserHandle.CURRENT.getIdentifier();
+
private PackageSetting mPackageSetting;
- private PackageManagerService mPm;
- private ArchiveManager mArchiveManager;
+ private PackageArchiverService mArchiveService;
@Before
public void setUp() throws Exception {
@@ -106,7 +106,7 @@
mMockSystem.system().stageNominalSystemState();
when(mMockSystem.mocks().getInjector().getPackageInstallerService()).thenReturn(
mInstallerService);
- mPm = spy(new PackageManagerService(mMockSystem.mocks().getInjector(),
+ PackageManagerService pm = spy(new PackageManagerService(mMockSystem.mocks().getInjector(),
/* factoryTest= */false,
MockSystem.Companion.getDEFAULT_VERSION_INFO().fingerprint,
/* isEngBuild= */ false,
@@ -124,37 +124,42 @@
when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps);
when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
mLauncherActivityInfos);
- doReturn(mComputer).when(mPm).snapshotComputer();
- when(mComputer.getNameForUid(eq(Binder.getCallingUid()))).thenReturn(CALLER_PACKAGE);
- mArchiveManager = new ArchiveManager(mContext, mPm);
+ doReturn(mComputer).when(pm).snapshotComputer();
+ when(mComputer.getPackageUid(eq(CALLER_PACKAGE), eq(0L), eq(mUserId))).thenReturn(
+ Binder.getCallingUid());
+ mArchiveService = new PackageArchiverService(mContext, pm);
}
@Test
public void archiveApp_callerPackageNameIncorrect() {
Exception e = assertThrows(
SecurityException.class,
- () -> mArchiveManager.archiveApp(PACKAGE, "different", UserHandle.CURRENT,
- mIntentSender)
+ () -> mArchiveService.requestArchive(PACKAGE, "different", mIntentSender,
+ UserHandle.CURRENT
+ )
);
assertThat(e).hasMessageThat().isEqualTo(
String.format(
- "The callerPackageName %s set by the caller doesn't match the "
- + "caller's own package name %s.",
- "different",
- CALLER_PACKAGE));
+ "The UID %s of callerPackageName set by the caller doesn't match the "
+ + "caller's actual UID %s.",
+ 0,
+ Binder.getCallingUid()));
}
@Test
public void archiveApp_packageNotInstalled() {
- when(mMockSystem.mocks().getSettings().getPackageLPr(eq(PACKAGE))).thenReturn(
+ when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
null);
Exception e = assertThrows(
- PackageManager.NameNotFoundException.class,
- () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT,
- mIntentSender)
+ ParcelableException.class,
+ () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+ UserHandle.CURRENT
+ )
);
- assertThat(e).hasMessageThat().isEqualTo(String.format("Package %s not found.", PACKAGE));
+ assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+ assertThat(e.getCause()).hasMessageThat().isEqualTo(
+ String.format("Package %s not found.", PACKAGE));
}
@Test
@@ -162,11 +167,14 @@
mPackageSetting.modifyUserState(UserHandle.CURRENT.getIdentifier()).setInstalled(false);
Exception e = assertThrows(
- PackageManager.NameNotFoundException.class,
- () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT,
- mIntentSender)
+ ParcelableException.class,
+ () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+ UserHandle.CURRENT
+ )
);
- assertThat(e).hasMessageThat().isEqualTo(String.format("Package %s not found.", PACKAGE));
+ assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+ assertThat(e.getCause()).hasMessageThat().isEqualTo(
+ String.format("Package %s not found.", PACKAGE));
}
@Test
@@ -183,17 +191,18 @@
when(mPackageState.getInstallSource()).thenReturn(otherInstallSource);
Exception e = assertThrows(
- SecurityException.class,
- () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, Process.myUserHandle(),
- mIntentSender)
+ ParcelableException.class,
+ () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+ UserHandle.CURRENT
+ )
);
- assertThat(e).hasMessageThat().isEqualTo(
- TextUtils.formatSimple("No installer found to archive app %s.",
- PACKAGE));
+ assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+ assertThat(e.getCause()).hasMessageThat().isEqualTo(
+ String.format("No installer found to archive app %s.", PACKAGE));
}
@Test
- public void archiveApp_success() throws PackageManager.NameNotFoundException {
+ public void archiveApp_success() {
List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>();
for (LauncherActivityInfo mainActivity : createLauncherActivities()) {
// TODO(b/278553670) Extract and store launcher icons
@@ -203,7 +212,8 @@
activityInfos.add(activityInfo);
}
- mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT, mIntentSender);
+ mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
+
verify(mInstallerService).uninstall(
eq(new VersionedPackage(PACKAGE, PackageManager.VERSION_CODE_HIGHEST)),
eq(CALLER_PACKAGE), eq(DELETE_KEEP_DATA), eq(mIntentSender),
diff --git a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java
index 20cfd59..dc04b6a 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 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.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.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,11 +99,64 @@
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;
+ public CameraPrivacyLightController prepareDefaultCameraPrivacyLightController() {
+ return prepareDefaultCameraPrivacyLightController(List.of(getNextLight(true)));
+ }
+
+ 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);
+ }
+ }
+
+ 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(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(exemptedPackages)
+ .when(() -> PermissionManager.getIndicatorExemptedPackages(any()));
+
+ return new CameraPrivacyLightController(context, looper);
+ }
+
@Before
public void setUp() {
mMockitoSession = ExtendedMockito.mockitoSession()
@@ -115,49 +164,33 @@
.strictness(Strictness.WARN)
.spyStatic(PermissionManager.class)
.startMocking();
-
- doReturn(mDayColor).when(mContext).getColor(R.color.camera_privacy_light_day);
- doReturn(mNightColor).when(mContext).getColor(R.color.camera_privacy_light_night);
-
- doReturn(mResources).when(mContext).getResources();
- doReturn(mCameraPrivacyLightAlsAveragingIntervalMillis).when(mResources)
- .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(mLights).when(mLightsManager).getLights();
- doReturn(mLightsSession).when(mLightsManager).openSession(anyInt());
- doReturn(mLightSensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT);
-
- doReturn(mExemptedPackages)
- .when(() -> PermissionManager.getIndicatorExemptedPackages(any()));
}
@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 @@
@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 @@
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 @@
@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 @@
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");
+ String exemptPackage = "pkg1";
+ prepareCameraPrivacyLightController(List.of(getNextLight(true)),
+ Set.of(exemptPackage), true, mDefaultColors, mDefaultAlsThresholdsLux,
+ mDefaultAlsAveragingIntervalMillis);
- createCameraPrivacyLightController();
-
- 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 @@
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();
+ for (int i = 0; i < mDefaultColors.length; i++) {
+ int expectedColor = mDefaultColors[i];
+ int alsLuxValue = i
+ == mDefaultAlsThresholdsLux.length
+ ? mDefaultAlsThresholdsLux[i - 1] : mDefaultAlsThresholdsLux[i] - 1;
- 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);
+ notifySensorEvent(cplc, elapsedTime, alsLuxValue);
+ elapsedTime += mDefaultAlsAveragingIntervalMillis + 1;
+ notifySensorEvent(cplc, elapsedTime, alsLuxValue);
- sensorEventValues[0] = getLightSensorValue(brightnessValue);
- sensorListener.onSensorChanged(sensorEvent);
-
- verify(mLightsSession, atLeastOnce()).requestLights(mLightsRequestCaptor.capture());
- for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) {
- assertEquals(color, lightState.getColor());
+ 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 @@
return light;
}
- private float getLightSensorValue(int i) {
- return (float) Math.exp(i / CameraPrivacyLightController.LIGHT_VALUE_MULTIPLIER);
- }
-
private void openCamera() {
verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture());
mAppOpsListenerCaptor.getValue().onOpActiveChanged(OPSTR_CAMERA, 10001, "pkg", true);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index b79c7be..349a597 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -303,6 +303,7 @@
});
}
+ @FlakyTest(bugId = 297879316)
@Test
public void testStates_areMutuallyExclusive() {
forEachState(state1 -> {
@@ -523,6 +524,7 @@
});
}
+ @FlakyTest(bugId = 297879316)
@Test
public void testTwoFingersOneTap_activatedState_dispatchMotionEvents() {
goFromStateIdleTo(STATE_ACTIVATED);
@@ -583,6 +585,7 @@
returnToNormalFrom(STATE_ACTIVATED);
}
+ @FlakyTest(bugId = 297879316)
@Test
public void testFirstFingerSwipe_twoPointerDownAndActivatedState_panningState() {
goFromStateIdleTo(STATE_ACTIVATED);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index bbbab21..a0bca3b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -50,6 +50,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.platform.test.annotations.FlakyTest;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
import android.view.InputDevice;
@@ -307,6 +308,7 @@
MagnificationScaleProvider.MAX_SCALE);
}
+ @FlakyTest(bugId = 297879435)
@Test
public void logTrackingTypingFocus_processScroll_logDuration() {
WindowMagnificationManager spyWindowMagnificationManager = spy(mWindowMagnificationManager);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
index 64e776e..a621c0c 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 @@
.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 @@
.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 @@
.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 @@
.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 @@
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 @@
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 dde2a3c..0c0d47a 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 @@
}
@Test
+ public void persistFrrStats_multiUser_newUser_shouldUpdateRecord() throws JSONException {
+ AuthenticationStats authenticationStats1 = new AuthenticationStats(USER_ID_1,
+ 300 /* totalAttempts */, 10 /* rejectedAttempts */,
+ 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+ AuthenticationStats authenticationStats2 = new AuthenticationStats(USER_ID_2,
+ 100 /* totalAttempts */, 5 /* rejectedAttempts */,
+ 1 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+
+ // Sets up the shared preference with user 1 only.
+ when(mSharedPreferences.getStringSet(eq(KEY), anySet())).thenReturn(
+ Set.of(buildFrrStats(authenticationStats1)));
+
+ // Add data for user 2.
+ mAuthenticationStatsPersister.persistFrrStats(authenticationStats2.getUserId(),
+ authenticationStats2.getTotalAttempts(),
+ authenticationStats2.getRejectedAttempts(),
+ authenticationStats2.getEnrollmentNotifications(),
+ authenticationStats2.getModality());
+
+ verify(mEditor).putStringSet(eq(KEY), mStringSetArgumentCaptor.capture());
+ assertThat(mStringSetArgumentCaptor.getValue())
+ .contains(buildFrrStats(authenticationStats2));
+ }
+
+ @Test
public void removeFrrStats_existingUser_shouldUpdateRecord() throws JSONException {
AuthenticationStats authenticationStats = new AuthenticationStats(USER_ID_1,
300 /* totalAttempts */, 10 /* rejectedAttempts */,
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 4b65895..2dacda0 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -34,6 +34,7 @@
"platform-test-annotations",
"platformprotosnano",
"statsdprotolite",
+ "StatsdTestUtils",
"hamcrest-library",
"servicestests-utils",
"testables",
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 37f4983..70e5c2e1 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 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.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 @@
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 @@
}
@Test
- public void enqueueNotification_wakeLockSystemPropertyOff_noWakeLock() throws Exception {
- mTestFlagResolver.setFlagOverride(WAKE_LOCK_FOR_POSTING_NOTIFICATION, false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.NOTIFY_WAKELOCK, "true", false);
-
- mBinderService.enqueueNotificationWithTag(PKG, PKG,
- "enqueueNotification_setsWakeLockWorkSource", 0,
- generateNotificationRecord(null).getNotification(), 0);
- waitForIdle();
-
- verifyZeroInteractions(mPowerManager);
- }
-
- @Test
- public void enqueueNotification_wakeLockDeviceConfigOff_noWakeLock() throws Exception {
- mTestFlagResolver.setFlagOverride(WAKE_LOCK_FOR_POSTING_NOTIFICATION, true);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.NOTIFY_WAKELOCK, "false", false);
-
- mBinderService.enqueueNotificationWithTag(PKG, PKG,
- "enqueueNotification_setsWakeLockWorkSource", 0,
- generateNotificationRecord(null).getNotification(), 0);
- waitForIdle();
-
- verifyZeroInteractions(mPowerManager);
- }
-
- @Test
public void testCancelNonexistentNotification() throws Exception {
mBinderService.cancelNotificationWithTag(PKG, PKG,
"testCancelNonexistentNotification", 0, 0);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 9b745f5..020afdb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -44,22 +44,11 @@
import static android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION;
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.os.UserHandle.USER_SYSTEM;
-import static android.util.StatsLog.ANNOTATION_ID_IS_UID;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS;
-import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
-import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
-import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.CHANNEL_ID_FIELD_NUMBER;
-import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.CHANNEL_NAME_FIELD_NUMBER;
-import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IMPORTANCE_FIELD_NUMBER;
-import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IS_CONVERSATION_FIELD_NUMBER;
-import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IS_DELETED_FIELD_NUMBER;
-import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IS_DEMOTED_CONVERSATION_FIELD_NUMBER;
-import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IS_IMPORTANT_CONVERSATION_FIELD_NUMBER;
-import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.UID_FIELD_NUMBER;
import static com.android.server.notification.NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER;
import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE;
import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT;
@@ -134,6 +123,7 @@
import android.util.IntArray;
import android.util.Pair;
import android.util.StatsEvent;
+import android.util.StatsEventTestUtils;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
@@ -144,11 +134,14 @@
import com.android.internal.config.sysui.TestableFlagResolver;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import com.android.os.AtomsProto;
+import com.android.os.AtomsProto.PackageNotificationChannelPreferences;
import com.android.os.AtomsProto.PackageNotificationPreferences;
import com.android.server.UiServiceTestCase;
import com.android.server.notification.PermissionHelper.PackagePermission;
import com.google.common.collect.ImmutableList;
+import com.google.protobuf.InvalidProtocolBufferException;
import org.json.JSONArray;
import org.json.JSONObject;
@@ -218,7 +211,6 @@
private PreferencesHelper mHelper;
private AudioAttributes mAudioAttributes;
private NotificationChannelLoggerFake mLogger = new NotificationChannelLoggerFake();
- private WrappedSysUiStatsEvent.WrappedBuilderFactory mStatsEventBuilderFactory;
@Before
public void setUp() throws Exception {
@@ -331,11 +323,9 @@
when(mUserProfiles.getCurrentProfileIds()).thenReturn(IntArray.wrap(new int[] {0}));
- mStatsEventBuilderFactory = new WrappedSysUiStatsEvent.WrappedBuilderFactory();
-
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mStatsEventBuilderFactory, false);
+ false);
resetZenModeHelper();
mAudioAttributes = new AudioAttributes.Builder()
@@ -683,7 +673,7 @@
public void testReadXml_oldXml_migrates() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mStatsEventBuilderFactory, /* showReviewPermissionsNotification= */ true);
+ /* showReviewPermissionsNotification= */ true);
String xml = "<ranking version=\"2\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" uid=\"" + UID_N_MR1
@@ -825,7 +815,7 @@
public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mStatsEventBuilderFactory, /* showReviewPermissionsNotification= */ true);
+ /* showReviewPermissionsNotification= */ true);
String xml = "<ranking version=\"3\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -883,7 +873,7 @@
public void testReadXml_newXml_permissionNotificationOff() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mStatsEventBuilderFactory, /* showReviewPermissionsNotification= */ false);
+ /* showReviewPermissionsNotification= */ false);
String xml = "<ranking version=\"3\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -941,7 +931,7 @@
public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mStatsEventBuilderFactory, /* showReviewPermissionsNotification= */ true);
+ /* showReviewPermissionsNotification= */ true);
String xml = "<ranking version=\"4\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -1521,7 +1511,7 @@
mHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mStatsEventBuilderFactory, false);
+ false);
NotificationChannel channel =
new NotificationChannel("id", "name", IMPORTANCE_LOW);
@@ -5392,7 +5382,7 @@
}
@Test
- public void testPullPackageChannelPreferencesStats() {
+ public void testPullPackageChannelPreferencesStats() throws InvalidProtocolBufferException {
String channelId = "parent";
String name = "messages";
NotificationChannel fodderA = new NotificationChannel("a", "a", IMPORTANCE_LOW);
@@ -5406,25 +5396,40 @@
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
- int found = 0;
- for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
- if (builder.getAtomId() == PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES
- && channelId.equals(builder.getValue(CHANNEL_ID_FIELD_NUMBER))) {
- ++found;
- assertEquals("uid", UID_O, builder.getValue(UID_FIELD_NUMBER));
- assertTrue("uid annotation", builder.getBooleanAnnotation(
- UID_FIELD_NUMBER, ANNOTATION_ID_IS_UID));
- assertEquals("importance", IMPORTANCE_DEFAULT, builder.getValue(
- IMPORTANCE_FIELD_NUMBER));
- assertEquals("name", name, builder.getValue(CHANNEL_NAME_FIELD_NUMBER));
- assertFalse("isconv", builder.getBoolean(IS_CONVERSATION_FIELD_NUMBER));
- assertFalse("deleted", builder.getBoolean(IS_DELETED_FIELD_NUMBER));
+ // number of channels with preferences should be 3 total
+ assertEquals("expected number of events", 3, events.size());
+ for (StatsEvent ev : events) {
+ // all of these events should be of PackageNotificationChannelPreferences type,
+ // and therefore we expect the atom to have this field.
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+ assertTrue(atom.hasPackageNotificationChannelPreferences());
+ PackageNotificationChannelPreferences p =
+ atom.getPackageNotificationChannelPreferences();
+
+ // uid is shared across all channels; conversation & deleted are not set in any of
+ // these channels; beyond that check individual channel properties
+ assertEquals("uid", UID_O, p.getUid());
+ assertFalse("is conversation", p.getIsConversation());
+ assertFalse("is deleted", p.getIsDeleted());
+
+ String eventChannelId = p.getChannelId();
+ if (eventChannelId.equals(channelId)) {
+ assertEquals("channel name", name, p.getChannelName());
+ assertEquals("importance", IMPORTANCE_DEFAULT, p.getImportance());
+ assertFalse("is conversation", p.getIsConversation());
+ } else if (eventChannelId.equals("a")) {
+ assertEquals("channel name", "a", p.getChannelName());
+ assertEquals("importance", IMPORTANCE_LOW, p.getImportance());
+ } else { // b
+ assertEquals("channel name", "b", p.getChannelName());
+ assertEquals("importance", IMPORTANCE_HIGH, p.getImportance());
}
}
}
@Test
- public void testPullPackageChannelPreferencesStats_one_to_one() {
+ public void testPullPackageChannelPreferencesStats_one_to_one()
+ throws InvalidProtocolBufferException {
NotificationChannel channelA = new NotificationChannel("a", "a", IMPORTANCE_LOW);
mHelper.createNotificationChannel(PKG_O, UID_O, channelA, true, false, UID_O, false);
NotificationChannel channelB = new NotificationChannel("b", "b", IMPORTANCE_LOW);
@@ -5437,19 +5442,22 @@
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
- int found = 0;
- for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
- if (builder.getAtomId() == PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES) {
- Object id = builder.getValue(CHANNEL_ID_FIELD_NUMBER);
- assertTrue("missing channel in the output", channels.contains(id));
- channels.remove(id);
- }
+ assertEquals("total events", 3, events.size());
+ for (StatsEvent ev : events) {
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+ assertTrue(atom.hasPackageNotificationChannelPreferences());
+ PackageNotificationChannelPreferences p =
+ atom.getPackageNotificationChannelPreferences();
+ String id = p.getChannelId();
+ assertTrue("missing channel in the output", channels.contains(id));
+ channels.remove(id);
}
assertTrue("unexpected channel in output", channels.isEmpty());
}
@Test
- public void testPullPackageChannelPreferencesStats_conversation() {
+ public void testPullPackageChannelPreferencesStats_conversation()
+ throws InvalidProtocolBufferException {
String conversationId = "friend";
NotificationChannel parent =
@@ -5467,21 +5475,25 @@
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
- for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
- if (builder.getAtomId() == PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES
- && channelId.equals(builder.getValue(CHANNEL_ID_FIELD_NUMBER))) {
- assertTrue("isConveration should be true", builder.getBoolean(
- IS_CONVERSATION_FIELD_NUMBER));
- assertFalse("not demoted", builder.getBoolean(
- IS_DEMOTED_CONVERSATION_FIELD_NUMBER));
- assertFalse("not important", builder.getBoolean(
- IS_IMPORTANT_CONVERSATION_FIELD_NUMBER));
+ // In this case, we want to check the properties of the conversation channel (not parent)
+ assertEquals("total events", 2, events.size());
+ for (StatsEvent ev : events) {
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+ assertTrue(atom.hasPackageNotificationChannelPreferences());
+ PackageNotificationChannelPreferences p =
+ atom.getPackageNotificationChannelPreferences();
+
+ if (channelId.equals(p.getChannelId())) {
+ assertTrue("isConversation should be true", p.getIsConversation());
+ assertFalse("not demoted", p.getIsDemotedConversation());
+ assertFalse("not important", p.getIsImportantConversation());
}
}
}
@Test
- public void testPullPackageChannelPreferencesStats_conversation_demoted() {
+ public void testPullPackageChannelPreferencesStats_conversation_demoted()
+ throws InvalidProtocolBufferException {
NotificationChannel parent =
new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT);
mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false, UID_O, false);
@@ -5496,21 +5508,23 @@
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
- for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
- if (builder.getAtomId() == PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES
- && channelId.equals(builder.getValue(CHANNEL_ID_FIELD_NUMBER))) {
- assertTrue("isConveration should be true", builder.getBoolean(
- IS_CONVERSATION_FIELD_NUMBER));
- assertTrue("is demoted", builder.getBoolean(
- IS_DEMOTED_CONVERSATION_FIELD_NUMBER));
- assertFalse("not important", builder.getBoolean(
- IS_IMPORTANT_CONVERSATION_FIELD_NUMBER));
+ assertEquals("total events", 2, events.size());
+ for (StatsEvent ev : events) {
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+ assertTrue(atom.hasPackageNotificationChannelPreferences());
+ PackageNotificationChannelPreferences p =
+ atom.getPackageNotificationChannelPreferences();
+ if (channelId.equals(p.getChannelId())) {
+ assertTrue("isConversation should be true", p.getIsConversation());
+ assertTrue("is demoted", p.getIsDemotedConversation());
+ assertFalse("not important", p.getIsImportantConversation());
}
}
}
@Test
- public void testPullPackageChannelPreferencesStats_conversation_priority() {
+ public void testPullPackageChannelPreferencesStats_conversation_priority()
+ throws InvalidProtocolBufferException {
NotificationChannel parent =
new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT);
mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false, UID_O, false);
@@ -5525,21 +5539,23 @@
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
- for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
- if (builder.getAtomId() == PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES
- && channelId.equals(builder.getValue(CHANNEL_ID_FIELD_NUMBER))) {
- assertTrue("isConveration should be true", builder.getBoolean(
- IS_CONVERSATION_FIELD_NUMBER));
- assertFalse("not demoted", builder.getBoolean(
- IS_DEMOTED_CONVERSATION_FIELD_NUMBER));
- assertTrue("is important", builder.getBoolean(
- IS_IMPORTANT_CONVERSATION_FIELD_NUMBER));
+ assertEquals("total events", 2, events.size());
+ for (StatsEvent ev : events) {
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+ assertTrue(atom.hasPackageNotificationChannelPreferences());
+ PackageNotificationChannelPreferences p =
+ atom.getPackageNotificationChannelPreferences();
+ if (channelId.equals(p.getChannelId())) {
+ assertTrue("isConversation should be true", p.getIsConversation());
+ assertFalse("not demoted", p.getIsDemotedConversation());
+ assertTrue("is important", p.getIsImportantConversation());
}
}
}
@Test
- public void testPullPackagePreferencesStats_postPermissionMigration() {
+ public void testPullPackagePreferencesStats_postPermissionMigration()
+ throws InvalidProtocolBufferException {
// make sure there's at least one channel for each package we want to test
NotificationChannel channelA = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channelA, true, false,
@@ -5568,23 +5584,18 @@
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackagePreferencesStats(events, appPermissions);
- int found = 0;
- for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
- if (builder.getAtomId() == PACKAGE_NOTIFICATION_PREFERENCES) {
- ++found;
- int uid = builder.getInt(PackageNotificationPreferences.UID_FIELD_NUMBER);
- boolean userSet = builder.getBoolean(
- PackageNotificationPreferences.USER_SET_IMPORTANCE_FIELD_NUMBER);
+ assertEquals("total number of packages", 3, events.size());
+ for (StatsEvent ev : events) {
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+ assertTrue(atom.hasPackageNotificationPreferences());
+ PackageNotificationPreferences p = atom.getPackageNotificationPreferences();
+ int uid = p.getUid();
- // if it's one of the expected ids, then make sure the importance matches
- assertTrue(expected.containsKey(uid));
- assertThat(expected.get(uid).first).isEqualTo(
- builder.getInt(PackageNotificationPreferences.IMPORTANCE_FIELD_NUMBER));
- assertThat(expected.get(uid).second).isEqualTo(userSet);
- }
+ // if it's one of the expected ids, then make sure the importance matches
+ assertTrue(expected.containsKey(uid));
+ assertThat(expected.get(uid).first).isEqualTo(p.getImportance());
+ assertThat(expected.get(uid).second).isEqualTo(p.getUserSetImportance());
}
- // should have at least one entry for each of the packages we expected to see
- assertThat(found).isAtLeast(3);
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/WrappedSysUiStatsEvent.java b/services/tests/uiservicestests/src/com/android/server/notification/WrappedSysUiStatsEvent.java
deleted file mode 100644
index 89adc72..0000000
--- a/services/tests/uiservicestests/src/com/android/server/notification/WrappedSysUiStatsEvent.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.notification;
-
-import android.util.StatsEvent;
-
-import com.android.server.notification.SysUiStatsEvent.Builder;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * Wrapper for SysUiStatsEvent that implements validation.
- */
-public class WrappedSysUiStatsEvent {
-
- static class WrappedBuilder extends Builder {
- private ArrayList<Object> mValues;
- private HashMap<Integer, HashMap<Byte, Object>> mAnnotations;
- private int mAtomId;
- private int mLastIndex;
- private boolean mBuilt;
-
- WrappedBuilder(StatsEvent.Builder builder) {
- super(builder);
- mValues = new ArrayList<>();
- mAnnotations = new HashMap<>();
- mValues.add(0); // proto fields are 1-based
- }
-
- @Override
- public Builder setAtomId(int atomId) {
- mAtomId = atomId;
- super.setAtomId(atomId);
- return this;
- }
-
- @Override
- public Builder writeInt(int value) {
- addValue(Integer.valueOf(value));
- super.writeInt(value);
- return this;
- }
-
- @Override
- public Builder addBooleanAnnotation(byte annotation, boolean value) {
- addAnnotation(annotation, Boolean.valueOf(value));
- super.addBooleanAnnotation(annotation, value);
- return this;
- }
-
- @Override
- public Builder writeString(String value) {
- addValue(value);
- super.writeString(value);
- return this;
- }
-
- @Override
- public Builder writeBoolean(boolean value) {
- addValue(Boolean.valueOf(value));
- super.writeBoolean(value);
- return this;
- }
-
- @Override
- public StatsEvent build() {
- mBuilt = true;
- return super.build();
- }
-
- public Object getValue(int index) {
- return index < mValues.size() ? mValues.get(index) : null;
- }
-
- /** useful to make assertTrue() statements more readable. */
- public boolean getBoolean(int index) {
- return (Boolean) mValues.get(index);
- }
-
- /** useful to make assertTrue() statements more readable. */
- public int getInt(int index) {
- return (Integer) mValues.get(index);
- }
-
- /** useful to make assertTrue() statements more readable. */
- public String getString(int index) {
- return (String) mValues.get(index);
- }
-
- private void addValue(Object value) {
- mLastIndex = mValues.size();
- mValues.add(value);
- }
-
- private void addAnnotation(byte annotation, Object value) {
- Integer key = Integer.valueOf(mLastIndex);
- if (!mAnnotations.containsKey(key)) {
- mAnnotations.put(key, new HashMap<>());
- }
- mAnnotations.get(key).put(Byte.valueOf(annotation), value);
- }
-
- public boolean getBooleanAnnotation(int i, byte a) {
- return ((Boolean) mAnnotations.get(Integer.valueOf(i)).get(Byte.valueOf(a)))
- .booleanValue();
- }
-
- public int getAtomId() {
- return mAtomId;
- }
- }
-
- static class WrappedBuilderFactory extends SysUiStatsEvent.BuilderFactory {
- public List<WrappedBuilder> builders;
-
- WrappedBuilderFactory() {
- builders = new ArrayList<>();
- }
-
- @Override
- Builder newBuilder() {
- WrappedBuilder b = new WrappedBuilder(StatsEvent.newBuilder());
- builders.add(b);
- return b;
- }
- }
-}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 3ee75de..e540068 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -33,18 +33,12 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
import static android.service.notification.Condition.STATE_FALSE;
import static android.service.notification.Condition.STATE_TRUE;
-import static android.util.StatsLog.ANNOTATION_ID_IS_UID;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.LOG_DND_STATE_EVENTS;
-import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
-import static com.android.os.dnd.DNDModeProto.CHANNELS_BYPASSING_FIELD_NUMBER;
-import static com.android.os.dnd.DNDModeProto.ENABLED_FIELD_NUMBER;
-import static com.android.os.dnd.DNDModeProto.ID_FIELD_NUMBER;
-import static com.android.os.dnd.DNDModeProto.UID_FIELD_NUMBER;
-import static com.android.os.dnd.DNDModeProto.ZEN_MODE_FIELD_NUMBER;
import static com.android.os.dnd.DNDProtoEnums.PEOPLE_STARRED;
import static com.android.os.dnd.DNDProtoEnums.ROOT_CONFIG;
import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW;
@@ -73,6 +67,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.SuppressLint;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
@@ -106,6 +101,7 @@
import android.util.ArrayMap;
import android.util.Log;
import android.util.StatsEvent;
+import android.util.StatsEventTestUtils;
import android.util.Xml;
import com.android.internal.R;
@@ -113,12 +109,15 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import com.android.os.AtomsProto;
+import com.android.os.dnd.DNDModeProto;
import com.android.os.dnd.DNDPolicyProto;
import com.android.os.dnd.DNDProtoEnums;
import com.android.server.UiServiceTestCase;
import com.android.server.notification.ManagedServices.UserProfiles;
import com.google.common.collect.ImmutableList;
+import com.google.protobuf.InvalidProtocolBufferException;
import org.junit.Before;
import org.junit.Test;
@@ -140,13 +139,13 @@
import java.util.Objects;
@SmallTest
+@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class ZenModeHelperTest extends UiServiceTestCase {
private static final String EVENTS_DEFAULT_RULE_ID = "EVENTS_DEFAULT_RULE";
private static final String SCHEDULE_DEFAULT_RULE_ID = "EVERY_NIGHT_DEFAULT_RULE";
- private static final int ZEN_MODE_FOR_TESTING = 99;
private static final String CUSTOM_PKG_NAME = "not.android";
private static final int CUSTOM_PKG_UID = 1;
private static final String CUSTOM_RULE_ID = "custom_rule";
@@ -159,7 +158,6 @@
private ZenModeHelper mZenModeHelper;
private ContentResolver mContentResolver;
@Mock AppOpsManager mAppOps;
- private WrappedSysUiStatsEvent.WrappedBuilderFactory mStatsEventBuilderFactory;
TestableFlagResolver mTestFlagResolver = new TestableFlagResolver();
ZenModeEventLoggerFake mZenModeEventLogger;
@@ -178,7 +176,6 @@
Log.d("ZenModeHelperTest", "Couldn't mock default zen mode config xml file err=" +
e.toString());
}
- mStatsEventBuilderFactory = new WrappedSysUiStatsEvent.WrappedBuilderFactory();
when(mContext.getSystemService(AppOpsManager.class)).thenReturn(mAppOps);
when(mContext.getSystemService(NotificationManager.class)).thenReturn(mNotificationManager);
@@ -188,8 +185,7 @@
mConditionProviders.addSystemProvider(new ScheduleConditionProvider());
mZenModeEventLogger = new ZenModeEventLoggerFake(mPackageManager);
mZenModeHelper = new ZenModeHelper(mContext, mTestableLooper.getLooper(),
- mConditionProviders, mStatsEventBuilderFactory, mTestFlagResolver,
- mZenModeEventLogger);
+ mConditionProviders, mTestFlagResolver, mZenModeEventLogger);
ResolveInfo ri = new ResolveInfo();
ri.activityInfo = new ActivityInfo();
@@ -911,37 +907,35 @@
}
@Test
- public void testProto() {
+ public void testProto() throws InvalidProtocolBufferException {
mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
// existence of manual rule means it should be in output
mZenModeHelper.mConfig.manualRule = new ZenModeConfig.ZenRule();
mZenModeHelper.mConfig.manualRule.pkg = "android"; // system
+ mZenModeHelper.mConfig.automaticRules = new ArrayMap<>(); // no automatic rules
- int n = mZenModeHelper.mConfig.automaticRules.size();
- List<String> ids = new ArrayList<>(n);
- for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) {
- ids.add(rule.id);
- }
+ List<String> ids = new ArrayList<>();
ids.add(ZenModeConfig.MANUAL_RULE_ID);
ids.add(""); // for ROOT_CONFIG, logged with empty string as id
List<StatsEvent> events = new LinkedList<>();
mZenModeHelper.pullRules(events);
- assertEquals(n + 2, events.size()); // automatic rules + manual rule + root config
- for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
- if (builder.getAtomId() == DND_MODE_RULE) {
- if (builder.getInt(ZEN_MODE_FIELD_NUMBER) == ROOT_CONFIG) {
- assertTrue(builder.getBoolean(ENABLED_FIELD_NUMBER));
- assertFalse(builder.getBoolean(CHANNELS_BYPASSING_FIELD_NUMBER));
- }
- assertEquals(Process.SYSTEM_UID, builder.getInt(UID_FIELD_NUMBER));
- assertTrue(builder.getBooleanAnnotation(UID_FIELD_NUMBER, ANNOTATION_ID_IS_UID));
- String name = (String) builder.getValue(ID_FIELD_NUMBER);
- assertTrue("unexpected rule id", ids.contains(name));
- ids.remove(name);
- } else {
- fail("unexpected atom id: " + builder.getAtomId());
+ assertEquals(2, events.size()); // manual rule + root config
+ for (StatsEvent ev : events) {
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+ assertTrue(atom.hasDndModeRule());
+ DNDModeProto cfg = atom.getDndModeRule();
+ // Additional check for ID to clearly identify the root config because there's some
+ // odd behavior in the test util around enum value of 0 (the usual default, but not in
+ // this case).
+ if (cfg.getZenMode().getNumber() == ROOT_CONFIG && cfg.getId().equals("")) {
+ assertTrue(cfg.getEnabled());
+ assertFalse(cfg.getChannelsBypassing());
}
+ assertEquals(Process.SYSTEM_UID, cfg.getUid());
+ String name = cfg.getId();
+ assertTrue("unexpected rule id", ids.contains(name));
+ ids.remove(name);
}
assertEquals("extra rule in output", 0, ids.size());
}
@@ -949,28 +943,61 @@
@Test
public void testProtoWithAutoRule() throws Exception {
setupZenConfig();
- // one enabled automatic rule
- mZenModeHelper.mConfig.automaticRules = getCustomAutomaticRules(ZEN_MODE_FOR_TESTING);
+ // one enabled automatic rule. we use a non-usual zen mode value (though it has to be
+ // a real one in the enum because non-valid enum values are reverted to default).
+ mZenModeHelper.mConfig.automaticRules = getCustomAutomaticRules(ZEN_MODE_ALARMS);
List<StatsEvent> events = new LinkedList<>();
mZenModeHelper.pullRules(events);
boolean foundCustomEvent = false;
- for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
- if (builder.getAtomId() == DND_MODE_RULE) {
- if (ZEN_MODE_FOR_TESTING == builder.getInt(ZEN_MODE_FIELD_NUMBER)) {
- foundCustomEvent = true;
- assertEquals(CUSTOM_PKG_UID, builder.getInt(UID_FIELD_NUMBER));
- assertTrue(builder.getBoolean(ENABLED_FIELD_NUMBER));
- }
- } else {
- fail("unexpected atom id: " + builder.getAtomId());
+ for (StatsEvent ev : events) {
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+ assertTrue(atom.hasDndModeRule());
+ DNDModeProto cfg = atom.getDndModeRule();
+ if (cfg.getZenMode().getNumber() == ZEN_MODE_ALARMS) {
+ foundCustomEvent = true;
+ assertEquals(CUSTOM_PKG_UID, cfg.getUid());
+ assertTrue(cfg.getEnabled());
}
}
assertTrue("couldn't find custom rule", foundCustomEvent);
}
@Test
+ public void testProtoWithDefaultAutoRules() throws Exception {
+ setupZenConfig();
+ // clear the automatic rules so we can reset to only the default rules
+ mZenModeHelper.mConfig.automaticRules = new ArrayMap<>();
+
+ // read in XML to restore the default rules
+ ByteArrayOutputStream baos = writeXmlAndPurge(5);
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(baos.toByteArray())), null);
+ parser.nextTag();
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ List<StatsEvent> events = new LinkedList<>();
+ mZenModeHelper.pullRules(events);
+
+ // list for tracking which ids we've seen in the pulled atom output
+ List<String> ids = new ArrayList<>();
+ ids.addAll(ZenModeConfig.DEFAULT_RULE_IDS);
+ ids.add(""); // empty string for root config
+
+ for (StatsEvent ev : events) {
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+ assertTrue(atom.hasDndModeRule());
+ DNDModeProto cfg = atom.getDndModeRule();
+ if (!ids.contains(cfg.getId())) {
+ fail("unexpected ID found: " + cfg.getId());
+ }
+ ids.remove(cfg.getId());
+ }
+ assertEquals("default ID(s) not found", 0, ids.size());
+ }
+
+ @Test
public void ruleUidsCached() throws Exception {
setupZenConfig();
// one enabled automatic rule
@@ -1019,10 +1046,11 @@
List<StatsEvent> events = new LinkedList<>();
mZenModeHelper.pullRules(events);
- boolean foundCustomEvent = false;
- for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
- if (builder.getAtomId() == DND_MODE_RULE
- && "customRule".equals(builder.getString(ID_FIELD_NUMBER))) {
+ for (StatsEvent ev : events) {
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+ assertTrue(atom.hasDndModeRule());
+ DNDModeProto cfg = atom.getDndModeRule();
+ if ("customRule".equals(cfg.getId())) {
fail("non-default IDs should be redacted");
}
}
@@ -1039,14 +1067,17 @@
mZenModeHelper.pullRules(events);
boolean foundManualRule = false;
- for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
- if (builder.getAtomId() == DND_MODE_RULE
- && ZenModeConfig.MANUAL_RULE_ID.equals(builder.getString(ID_FIELD_NUMBER))) {
- assertEquals(0, builder.getInt(UID_FIELD_NUMBER));
+ for (StatsEvent ev : events) {
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+ assertTrue(atom.hasDndModeRule());
+ DNDModeProto cfg = atom.getDndModeRule();
+ if (ZenModeConfig.MANUAL_RULE_ID.equals(cfg.getId())) {
+ assertEquals(0, cfg.getUid());
foundManualRule = true;
}
}
- assertTrue("couldn't find manual rule", foundManualRule); }
+ assertTrue("couldn't find manual rule", foundManualRule);
+ }
@Test
public void testWriteXml_onlyBackupsTargetUser() throws Exception {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 302ad7f..31682bc 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 @@
// 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 @@
// 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 @@
// 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 d169a58..5341588 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 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.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.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.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 @@
*/
@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 @@
doReturn(true).when(record)
.checkEnterPictureInPictureState("enterPictureInPictureMode", false);
doReturn(false).when(record).inPinnedWindowingMode();
- doReturn(false).when(mAtm).isKeyguardLocked(anyInt());
+ doReturn(false).when(record).isKeyguardLocked();
//to simulate NPE
doReturn(null).when(record).getParent();
- mAtm.mActivityClientController.enterPictureInPictureMode(token, params);
+ mAtm.mActivityClientController.enterPictureInPictureMode(record.token, params);
//if record's null parent is not handled gracefully, test will fail with NPE
-
- mockSession.finishMocking();
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
index 4473a31..c84fe08 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
@@ -123,7 +123,7 @@
controller.setContentRecordingSessionLocked(mWaitingDisplaySession, mWm);
verify(mVirtualDisplayContent, atLeastOnce()).setContentRecordingSession(
mWaitingDisplaySession);
- verify(mVirtualDisplayContent).updateRecording();
+ verify(mVirtualDisplayContent, atLeastOnce()).updateRecording();
// WHEN updating the session on the same display, so no longer waiting to record.
ContentRecordingSession sessionUpdate = ContentRecordingSession.createTaskSession(
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index a2b7da3..ae4ebc1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1698,14 +1698,17 @@
final Task task = app.getTask();
final ActivityRecord app2 = new ActivityBuilder(mWm.mAtmService).setTask(task).build();
mDisplayContent.setFixedRotationLaunchingApp(app2, (mDisplayContent.getRotation() + 1) % 4);
- doReturn(true).when(app).isInTransition();
+ doReturn(true).when(app).inTransitionSelfOrParent();
// If the task contains a transition, this should be no-op.
mDisplayContent.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(app.token);
assertTrue(app2.hasFixedRotationTransform());
assertTrue(mDisplayContent.hasTopFixedRotationLaunchingApp());
- doReturn(false).when(app).isInTransition();
+ // The display should be unlikely to be in transition, but if it happens, the fixed
+ // rotation should proceed to finish because the activity/task level transition is finished.
+ doReturn(true).when(mDisplayContent).inTransition();
+ doReturn(false).when(app).inTransitionSelfOrParent();
// Although this notifies app instead of app2 that uses the fixed rotation, app2 should
// still finish the transform because there is no more transition event.
mDisplayContent.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(app.token);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
index c3db241..4165911 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
@@ -26,7 +26,6 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -35,9 +34,14 @@
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
+import static java.util.Objects.requireNonNull;
+
import android.app.ActivityThread;
import android.app.IApplicationThread;
+import android.app.WindowConfiguration;
+import android.content.ComponentCallbacks;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.hardware.display.DisplayManagerGlobal;
@@ -50,6 +54,8 @@
import android.window.WindowContextInfo;
import android.window.WindowTokenClient;
+import androidx.annotation.NonNull;
+
import com.android.server.inputmethod.InputMethodDialogWindowContext;
import org.junit.After;
@@ -58,6 +64,9 @@
import org.junit.runner.RunWith;
import org.mockito.Mockito;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
// TODO(b/157888351): Move the test to inputmethod package once we find the way to test the
// scenario there.
/**
@@ -138,44 +147,96 @@
@Test
public void testGetSettingsContextOnDualDisplayContent() {
final Context context = mWindowContext.get(mSecondaryDisplay.getDisplayId());
+ final MaxBoundsVerifier maxBoundsVerifier = new MaxBoundsVerifier();
+ context.registerComponentCallbacks(maxBoundsVerifier);
+
final WindowTokenClient tokenClient = (WindowTokenClient) context.getWindowContextToken();
- assertNotNull(tokenClient);
- spyOn(tokenClient);
+ spyOn(requireNonNull(tokenClient));
final DisplayArea.Tokens imeContainer = mSecondaryDisplay.getImeContainer();
spyOn(imeContainer);
assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay);
- mSecondaryDisplay.mFirstRoot.placeImeContainer(imeContainer);
+ final DisplayAreaGroup firstDaGroup = mSecondaryDisplay.mFirstRoot;
+ maxBoundsVerifier.setMaxBounds(firstDaGroup.getMaxBounds());
+
+ firstDaGroup.placeImeContainer(imeContainer);
verify(imeContainer, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
- eq(mSecondaryDisplay.mFirstRoot.getConfiguration()));
+ eq(firstDaGroup.getConfiguration()));
verify(tokenClient, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
- eq(mSecondaryDisplay.mFirstRoot.getConfiguration()),
+ eq(firstDaGroup.getConfiguration()),
eq(mSecondaryDisplay.mDisplayId));
- assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mFirstRoot);
+ assertThat(imeContainer.getRootDisplayArea()).isEqualTo(firstDaGroup);
+ maxBoundsVerifier.waitAndAssertMaxMetricsMatches();
assertImeSwitchContextMetricsValidity(context, mSecondaryDisplay);
// Clear the previous invocation histories in case we may count the previous
// onConfigurationChanged invocation into the next verification.
clearInvocations(tokenClient, imeContainer);
- mSecondaryDisplay.mSecondRoot.placeImeContainer(imeContainer);
+ final DisplayAreaGroup secondDaGroup = mSecondaryDisplay.mSecondRoot;
+ maxBoundsVerifier.setMaxBounds(secondDaGroup.getMaxBounds());
+
+ secondDaGroup.placeImeContainer(imeContainer);
verify(imeContainer, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
- eq(mSecondaryDisplay.mSecondRoot.getConfiguration()));
+ eq(secondDaGroup.getConfiguration()));
verify(tokenClient, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
- eq(mSecondaryDisplay.mSecondRoot.getConfiguration()),
+ eq(secondDaGroup.getConfiguration()),
eq(mSecondaryDisplay.mDisplayId));
- assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mSecondRoot);
+ assertThat(imeContainer.getRootDisplayArea()).isEqualTo(secondDaGroup);
+ maxBoundsVerifier.waitAndAssertMaxMetricsMatches();
assertImeSwitchContextMetricsValidity(context, mSecondaryDisplay);
}
private void assertImeSwitchContextMetricsValidity(Context context, DisplayContent dc) {
assertThat(context.getDisplayId()).isEqualTo(dc.getDisplayId());
+ final Rect imeContainerBounds = dc.getImeContainer().getBounds();
final Rect contextBounds = context.getSystemService(WindowManager.class)
.getMaximumWindowMetrics().getBounds();
- final Rect imeContainerBounds = dc.getImeContainer().getBounds();
assertThat(contextBounds).isEqualTo(imeContainerBounds);
}
+
+ private static final class MaxBoundsVerifier implements ComponentCallbacks {
+
+ private CountDownLatch mLatch;
+
+ private Rect mMaxBounds;
+
+ /**
+ * Sets max bounds to verify whether it matches the
+ * {@link WindowConfiguration#getMaxBounds()} reported from
+ * {@link #onConfigurationChanged(Configuration)} callback, and also resets the count down
+ * latch.
+ *
+ * @param maxBounds max bounds to verify
+ */
+ private void setMaxBounds(@NonNull Rect maxBounds) {
+ mMaxBounds = maxBounds;
+ mLatch = new CountDownLatch(1);
+ }
+
+ /**
+ * Waits for the {@link #onConfigurationChanged(Configuration)} callback whose the reported
+ * {@link WindowConfiguration#getMaxBounds()} matches {@link #mMaxBounds}.
+ */
+ private void waitAndAssertMaxMetricsMatches() {
+ try {
+ assertThat(mLatch.await(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Test failed because of " + e);
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ if (newConfig.windowConfiguration.getMaxBounds().equals(mMaxBounds)) {
+ mLatch.countDown();
+ }
+ }
+
+ @Override
+ public void onLowMemory() {}
+ }
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 13e5ff1..0bb75d8 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1999,7 +1999,7 @@
"lte_plus_threshold_bandwidth_khz_int";
/**
- * The combined channel bandwidth threshold (non-inclusive) in KHz required to display the
+ * The combined channel bandwidth threshold (inclusive) in KHz required to display the
* NR advanced (i.e. 5G+) data icon. It is 0 by default, meaning minimum bandwidth check is
* not enabled. Other factors like bands or frequency can also determine whether the NR
* advanced data icon is shown or not.
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp
index b94d14f..0fc2617 100644
--- a/tools/aapt/AaptAssets.cpp
+++ b/tools/aapt/AaptAssets.cpp
@@ -1540,7 +1540,7 @@
}
const String8& featureOfBase = bundle->getFeatureOfPackage();
- if (!featureOfBase.isEmpty()) {
+ if (!featureOfBase.empty()) {
if (bundle->getVerbose()) {
printf("Including base feature resources from package: %s\n",
featureOfBase.c_str());
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 5a06b10..60f3f27 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -1133,7 +1133,7 @@
if (code == ResXMLTree::END_TAG) {
depth--;
if (depth < 2) {
- if (withinSupportsInput && !supportedInput.isEmpty()) {
+ if (withinSupportsInput && !supportedInput.empty()) {
printf("supports-input: '");
const size_t N = supportedInput.size();
for (size_t i=0; i<N; i++) {
@@ -1300,7 +1300,7 @@
ResTable::normalizeForOutput(versionName.c_str()).c_str());
String8 splitName = AaptXml::getAttribute(tree, NULL, "split");
- if (!splitName.isEmpty()) {
+ if (!splitName.empty()) {
printf(" split='%s'", ResTable::normalizeForOutput(
splitName.c_str()).c_str());
}
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 9c944e0..4a360ed 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1285,7 +1285,7 @@
packageType = ResourceTable::SharedLibrary;
} else if (bundle->getExtending()) {
packageType = ResourceTable::System;
- } else if (!bundle->getFeatureOfPackage().isEmpty()) {
+ } else if (!bundle->getFeatureOfPackage().empty()) {
packageType = ResourceTable::AppFeature;
}
@@ -3144,7 +3144,7 @@
tree.restart();
- if (!startTags.isEmpty()) {
+ if (!startTags.empty()) {
bool haveStart = false;
while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
if (code != ResXMLTree::START_TAG) {
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 449e080..bc252cf 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -1813,7 +1813,7 @@
mTypeIdOffset = findLargestTypeIdForPackage(assets->getIncludedResources(), mAssetsPackage);
const String8& featureAfter = bundle->getFeatureAfterPackage();
- if (!featureAfter.isEmpty()) {
+ if (!featureAfter.empty()) {
AssetManager featureAssetManager;
if (!featureAssetManager.addAssetPath(featureAfter, NULL)) {
fprintf(stderr, "ERROR: Feature package '%s' not found.\n",
@@ -1823,7 +1823,7 @@
const ResTable& featureTable = featureAssetManager.getResources(false);
mTypeIdOffset = std::max(mTypeIdOffset,
- findLargestTypeIdForPackage(featureTable, mAssetsPackage));
+ findLargestTypeIdForPackage(featureTable, mAssetsPackage));
}
return NO_ERROR;
@@ -3252,7 +3252,7 @@
// If we're building splits, then each invocation of the flattening
// step will have 'missing' entries. Don't warn/error for this case.
- if (bundle->getSplitConfigurations().isEmpty()) {
+ if (bundle->getSplitConfigurations().empty()) {
bool missing_entry = false;
const char* log_prefix = bundle->getErrorOnMissingConfigEntry() ?
"error" : "warning";
@@ -4858,7 +4858,7 @@
Vector<sp<XMLNode> > nodesToVisit;
nodesToVisit.push(root);
- while (!nodesToVisit.isEmpty()) {
+ while (!nodesToVisit.empty()) {
sp<XMLNode> node = nodesToVisit.top();
nodesToVisit.pop();
diff --git a/tools/aapt/SourcePos.cpp b/tools/aapt/SourcePos.cpp
index e130286..354a65c 100644
--- a/tools/aapt/SourcePos.cpp
+++ b/tools/aapt/SourcePos.cpp
@@ -78,7 +78,7 @@
break;
}
- if (!this->file.isEmpty()) {
+ if (!this->file.empty()) {
if (this->line >= 0) {
fprintf(to, "%s:%d: %s%s\n", this->file.c_str(), this->line, type, this->error.c_str());
} else {
diff --git a/tools/aapt2/DominatorTree_test.cpp b/tools/aapt2/DominatorTree_test.cpp
index 52949da..a0679a6 100644
--- a/tools/aapt2/DominatorTree_test.cpp
+++ b/tools/aapt2/DominatorTree_test.cpp
@@ -50,8 +50,7 @@
private:
void VisitConfig(const DominatorTree::Node* node, const int indent) {
auto config_string = node->value()->config.toString();
- buffer_ << std::string(indent, ' ')
- << (config_string.isEmpty() ? "<default>" : config_string)
+ buffer_ << std::string(indent, ' ') << (config_string.empty() ? "<default>" : config_string)
<< std::endl;
}
diff --git a/tools/split-select/SplitDescription.cpp b/tools/split-select/SplitDescription.cpp
index 4e2b48e..7150008 100644
--- a/tools/split-select/SplitDescription.cpp
+++ b/tools/split-select/SplitDescription.cpp
@@ -70,7 +70,7 @@
String8 SplitDescription::toString() const {
String8 extension;
if (abi != abi::Variant_none) {
- if (extension.isEmpty()) {
+ if (extension.empty()) {
extension.append(":");
} else {
extension.append("-");