Merge "Fix java crash by inconsistent single-line view and view model" into main
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java
index 238c028..9eac108 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java
@@ -18,6 +18,7 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
+import android.util.Log;
import androidx.test.filters.LargeTest;
@@ -47,6 +48,8 @@
@RunWith(JUnitParamsRunner.class)
@LargeTest
public class CipherPerfTest {
+ private static final String TAG = "android.libcore.regression.CipherPerfTest";
+
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
public static Collection getCases() {
@@ -71,6 +74,10 @@
}
for (int keySize : keySizes) {
for (int inputSize : inputSizes) {
+ Log.i(TAG,
+ "param[" + params.size() + "] = " + mode.name() + ", "
+ + padding.name() + ", " + keySize + ", " + inputSize
+ + ", " + implementation.name());
params.add(
new Object[] {
mode, padding, keySize, inputSize, implementation
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index 52a761f..31d2ecd 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -34,6 +34,7 @@
import android.os.Parcelable;
import android.os.PersistableBundle;
import android.os.RemoteException;
+import android.os.Process;
import android.system.SystemCleaner;
import android.util.Log;
@@ -638,6 +639,12 @@
* @hide
*/
public void enableCleaner() {
+ // JobParameters objects are passed by reference in local Binder
+ // transactions for clients running as SYSTEM. The life cycle of the
+ // JobParameters objects are no longer controlled by the client.
+ if (Process.myUid() == Process.SYSTEM_UID) {
+ return;
+ }
if (mJobCleanupCallback == null) {
initCleaner(new JobCleanupCallback(IJobCallback.Stub.asInterface(callback), jobId));
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 67b3280..4f91361 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6858,43 +6858,43 @@
@FlaggedApi("android.app.api_rich_ongoing") public static class Notification.ProgressStyle extends android.app.Notification.Style {
ctor public Notification.ProgressStyle();
+ method @NonNull public android.app.Notification.ProgressStyle addProgressPoint(@NonNull android.app.Notification.ProgressStyle.Point);
method @NonNull public android.app.Notification.ProgressStyle addProgressSegment(@NonNull android.app.Notification.ProgressStyle.Segment);
- method @NonNull public android.app.Notification.ProgressStyle addProgressStep(@NonNull android.app.Notification.ProgressStyle.Step);
method public int getProgress();
method @Nullable public android.graphics.drawable.Icon getProgressEndIcon();
method public int getProgressMax();
+ method @NonNull public java.util.List<android.app.Notification.ProgressStyle.Point> getProgressPoints();
method @NonNull public java.util.List<android.app.Notification.ProgressStyle.Segment> getProgressSegments();
method @Nullable public android.graphics.drawable.Icon getProgressStartIcon();
- method @NonNull public java.util.List<android.app.Notification.ProgressStyle.Step> getProgressSteps();
method @Nullable public android.graphics.drawable.Icon getProgressTrackerIcon();
method public boolean isProgressIndeterminate();
method public boolean isStyledByProgress();
method @NonNull public android.app.Notification.ProgressStyle setProgress(int);
method @NonNull public android.app.Notification.ProgressStyle setProgressEndIcon(@Nullable android.graphics.drawable.Icon);
method @NonNull public android.app.Notification.ProgressStyle setProgressIndeterminate(boolean);
+ method @NonNull public android.app.Notification.ProgressStyle setProgressPoints(@NonNull java.util.List<android.app.Notification.ProgressStyle.Point>);
method @NonNull public android.app.Notification.ProgressStyle setProgressSegments(@NonNull java.util.List<android.app.Notification.ProgressStyle.Segment>);
method @NonNull public android.app.Notification.ProgressStyle setProgressStartIcon(@Nullable android.graphics.drawable.Icon);
- method @NonNull public android.app.Notification.ProgressStyle setProgressSteps(@NonNull java.util.List<android.app.Notification.ProgressStyle.Step>);
method @NonNull public android.app.Notification.ProgressStyle setProgressTrackerIcon(@Nullable android.graphics.drawable.Icon);
method @NonNull public android.app.Notification.ProgressStyle setStyledByProgress(boolean);
}
+ public static final class Notification.ProgressStyle.Point {
+ ctor public Notification.ProgressStyle.Point(int);
+ method @ColorInt public int getColor();
+ method public int getId();
+ method public int getPosition();
+ method @NonNull public android.app.Notification.ProgressStyle.Point setColor(@ColorInt int);
+ method @NonNull public android.app.Notification.ProgressStyle.Point setId(int);
+ }
+
public static final class Notification.ProgressStyle.Segment {
ctor public Notification.ProgressStyle.Segment(int);
method @ColorInt public int getColor();
+ method public int getId();
method public int getLength();
- method public int getStableId();
method @NonNull public android.app.Notification.ProgressStyle.Segment setColor(@ColorInt int);
- method @NonNull public android.app.Notification.ProgressStyle.Segment setStableId(int);
- }
-
- public static final class Notification.ProgressStyle.Step {
- ctor public Notification.ProgressStyle.Step(int);
- method @ColorInt public int getColor();
- method public int getPosition();
- method public int getStableId();
- method @NonNull public android.app.Notification.ProgressStyle.Step setColor(@ColorInt int);
- method @NonNull public android.app.Notification.ProgressStyle.Step setStableId(int);
+ method @NonNull public android.app.Notification.ProgressStyle.Segment setId(int);
}
public abstract static class Notification.Style {
@@ -8787,7 +8787,7 @@
@FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public abstract class AppFunctionService extends android.app.Service {
ctor public AppFunctionService();
method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
- method @Deprecated @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
+ method @Deprecated @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
method @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
}
@@ -8822,13 +8822,12 @@
field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.ExecuteAppFunctionResponse> CREATOR;
field public static final String PROPERTY_RETURN_VALUE = "returnValue";
field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2
- field public static final int RESULT_CANCELLED = 7; // 0x7
+ field public static final int RESULT_CANCELLED = 6; // 0x6
field public static final int RESULT_DENIED = 1; // 0x1
- field public static final int RESULT_DISABLED = 6; // 0x6
+ field public static final int RESULT_DISABLED = 5; // 0x5
field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
field public static final int RESULT_OK = 0; // 0x0
- field public static final int RESULT_TIMED_OUT = 5; // 0x5
}
}
@@ -12114,6 +12113,7 @@
method @NonNull public void setResourceValue(@NonNull String, int, @NonNull String, @Nullable String);
method @NonNull public void setResourceValue(@NonNull String, @NonNull android.os.ParcelFileDescriptor, @Nullable String);
method @FlaggedApi("android.content.res.asset_file_descriptor_frro") @NonNull public void setResourceValue(@NonNull String, @NonNull android.content.res.AssetFileDescriptor, @Nullable String);
+ method @FlaggedApi("android.content.res.dimension_frro") public void setResourceValue(@NonNull String, float, int, @Nullable String);
method public void setTargetOverlayable(@Nullable String);
}
@@ -32821,7 +32821,7 @@
field @NonNull public static final String RELEASE_OR_PREVIEW_DISPLAY;
field @Deprecated public static final String SDK;
field public static final int SDK_INT;
- field @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static final int SDK_MINOR_INT;
+ field @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static final int SDK_INT_FULL;
field public static final String SECURITY_PATCH;
}
@@ -32865,6 +32865,9 @@
field public static final int VANILLA_ICE_CREAM = 35; // 0x23
}
+ @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static class Build.VERSION_CODES_FULL {
+ }
+
public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
ctor public Bundle();
ctor public Bundle(ClassLoader);
@@ -54956,6 +54959,7 @@
method public boolean addAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
method public void addAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener, @Nullable android.os.Handler);
method public void addAudioDescriptionRequestedChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
+ method @FlaggedApi("com.android.graphics.hwui.flags.high_contrast_text_small_text_rect") public void addHighContrastTextStateChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.HighContrastTextStateChangeListener);
method public boolean addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
method public void addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, @Nullable android.os.Handler);
method @ColorInt public int getAccessibilityFocusColor();
@@ -54968,12 +54972,14 @@
method public static boolean isAccessibilityButtonSupported();
method public boolean isAudioDescriptionRequested();
method public boolean isEnabled();
+ method @FlaggedApi("com.android.graphics.hwui.flags.high_contrast_text_small_text_rect") public boolean isHighContrastTextEnabled();
method public boolean isRequestFromAccessibilityTool();
method public boolean isTouchExplorationEnabled();
method public void removeAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer);
method public boolean removeAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
method public boolean removeAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
method public boolean removeAudioDescriptionRequestedChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
+ method @FlaggedApi("com.android.graphics.hwui.flags.high_contrast_text_small_text_rect") public void removeHighContrastTextStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.HighContrastTextStateChangeListener);
method public boolean removeTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
method public void sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
field public static final int FLAG_CONTENT_CONTROLS = 4; // 0x4
@@ -54993,6 +54999,10 @@
method public void onAudioDescriptionRequestedChanged(boolean);
}
+ @FlaggedApi("com.android.graphics.hwui.flags.high_contrast_text_small_text_rect") public static interface AccessibilityManager.HighContrastTextStateChangeListener {
+ method public void onHighContrastTextStateChanged(boolean);
+ }
+
public static interface AccessibilityManager.TouchExplorationStateChangeListener {
method public void onTouchExplorationStateChanged(boolean);
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 1a1f354..4b6c62e 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -91,6 +91,7 @@
field public static final String BIND_TRANSLATION_SERVICE = "android.permission.BIND_TRANSLATION_SERVICE";
field public static final String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
field public static final String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
+ field @FlaggedApi("android.content.pm.verification_service") public static final String BIND_VERIFICATION_AGENT = "android.permission.BIND_VERIFICATION_AGENT";
field public static final String BIND_VISUAL_QUERY_DETECTION_SERVICE = "android.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE";
field public static final String BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE = "android.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE";
field public static final String BIND_WEARABLE_SENSING_SERVICE = "android.permission.BIND_WEARABLE_SENSING_SERVICE";
@@ -412,6 +413,7 @@
field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String USE_ON_DEVICE_INTELLIGENCE = "android.permission.USE_ON_DEVICE_INTELLIGENCE";
field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
field public static final String UWB_PRIVILEGED = "android.permission.UWB_PRIVILEGED";
+ field @FlaggedApi("android.content.pm.verification_service") public static final String VERIFICATION_AGENT = "android.permission.VERIFICATION_AGENT";
field @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public static final String VIBRATE_VENDOR_EFFECTS = "android.permission.VIBRATE_VENDOR_EFFECTS";
field public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS";
field public static final String WHITELIST_RESTRICTED_PERMISSIONS = "android.permission.WHITELIST_RESTRICTED_PERMISSIONS";
@@ -4303,6 +4305,7 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT) public abstract void verifyIntentFilter(int, int, @NonNull java.util.List<java.lang.String>);
field public static final String ACTION_REQUEST_PERMISSIONS = "android.content.pm.action.REQUEST_PERMISSIONS";
field public static final String ACTION_REQUEST_PERMISSIONS_FOR_OTHER = "android.content.pm.action.REQUEST_PERMISSIONS_FOR_OTHER";
+ field @FlaggedApi("android.content.pm.verification_service") public static final String ACTION_VERIFY_PACKAGE = "android.content.pm.action.VERIFY_PACKAGE";
field @FlaggedApi("android.content.pm.asl_in_apk_app_metadata_source") public static final int APP_METADATA_SOURCE_APK = 1; // 0x1
field @FlaggedApi("android.content.pm.asl_in_apk_app_metadata_source") public static final int APP_METADATA_SOURCE_INSTALLER = 2; // 0x2
field @FlaggedApi("android.content.pm.asl_in_apk_app_metadata_source") public static final int APP_METADATA_SOURCE_SYSTEM_IMAGE = 3; // 0x3
@@ -4616,6 +4619,61 @@
}
+package android.content.pm.verify.pkg {
+
+ @FlaggedApi("android.content.pm.verification_service") public final class VerificationSession implements android.os.Parcelable {
+ method public int describeContents();
+ method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public long extendTimeRemaining(long);
+ method @NonNull public java.util.List<android.content.pm.SharedLibraryInfo> getDeclaredLibraries();
+ method @NonNull public android.os.PersistableBundle getExtensionParams();
+ method public int getId();
+ method public int getInstallSessionId();
+ method @NonNull public String getPackageName();
+ method @NonNull public android.content.pm.SigningInfo getSigningInfo();
+ method @NonNull public android.net.Uri getStagedPackageUri();
+ method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public long getTimeoutTime();
+ method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationComplete(@NonNull android.content.pm.verify.pkg.VerificationStatus);
+ method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationComplete(@NonNull android.content.pm.verify.pkg.VerificationStatus, @NonNull android.os.PersistableBundle);
+ method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationIncomplete(int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.pkg.VerificationSession> CREATOR;
+ field public static final int VERIFICATION_INCOMPLETE_NETWORK_LIMITED = 2; // 0x2
+ field public static final int VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE = 1; // 0x1
+ field public static final int VERIFICATION_INCOMPLETE_UNKNOWN = 0; // 0x0
+ }
+
+ @FlaggedApi("android.content.pm.verification_service") public final class VerificationStatus implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getAslStatus();
+ method @NonNull public String getFailureMessage();
+ method public boolean isVerified();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.pkg.VerificationStatus> CREATOR;
+ field public static final int VERIFIER_STATUS_ASL_BAD = 2; // 0x2
+ field public static final int VERIFIER_STATUS_ASL_GOOD = 1; // 0x1
+ field public static final int VERIFIER_STATUS_ASL_UNDEFINED = 0; // 0x0
+ }
+
+ public static final class VerificationStatus.Builder {
+ ctor public VerificationStatus.Builder();
+ method @NonNull public android.content.pm.verify.pkg.VerificationStatus build();
+ method @NonNull public android.content.pm.verify.pkg.VerificationStatus.Builder setAslStatus(int);
+ method @NonNull public android.content.pm.verify.pkg.VerificationStatus.Builder setFailureMessage(@NonNull String);
+ method @NonNull public android.content.pm.verify.pkg.VerificationStatus.Builder setVerified(boolean);
+ }
+
+ @FlaggedApi("android.content.pm.verification_service") public abstract class VerifierService extends android.app.Service {
+ ctor public VerifierService();
+ method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent);
+ method public abstract void onPackageNameAvailable(@NonNull String);
+ method public abstract void onVerificationCancelled(@NonNull String);
+ method public abstract void onVerificationRequired(@NonNull android.content.pm.verify.pkg.VerificationSession);
+ method public abstract void onVerificationRetry(@NonNull android.content.pm.verify.pkg.VerificationSession);
+ method public abstract void onVerificationTimeout(int);
+ }
+
+}
+
package android.content.rollback {
public final class PackageRollbackInfo implements android.os.Parcelable {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 5b556cc..95d3ea5 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1960,8 +1960,12 @@
@Override
public void dumpCacheInfo(ParcelFileDescriptor pfd, String[] args) {
- PropertyInvalidatedCache.dumpCacheInfo(pfd, args);
- IoUtils.closeQuietly(pfd);
+ try {
+ PropertyInvalidatedCache.dumpCacheInfo(pfd, args);
+ BroadcastStickyCache.dump(pfd);
+ } finally {
+ IoUtils.closeQuietly(pfd);
+ }
}
private File getDatabasesDir(Context context) {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index f27dc32..5907af0 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -10153,6 +10153,9 @@
}
p.writeInt(Parcel.EX_HAS_NOTED_APPOPS_REPLY_HEADER);
+ final int sizePosition = p.dataPosition();
+ // Write size placeholder. With this size we can easily skip it in native.
+ p.writeInt(0);
int numAttributionWithNotesAppOps = notedAppOps.size();
p.writeInt(numAttributionWithNotesAppOps);
@@ -10169,6 +10172,12 @@
}
}
}
+
+ final int payloadPosition = p.dataPosition();
+ p.setDataPosition(sizePosition);
+ // Total header size including 4 bytes size itself.
+ p.writeInt(payloadPosition - sizePosition);
+ p.setDataPosition(payloadPosition);
}
/**
@@ -10182,6 +10191,8 @@
* @hide
*/
public static void readAndLogNotedAppops(@NonNull Parcel p) {
+ // Skip size.
+ p.readInt();
int numAttributionsWithNotedAppOps = p.readInt();
for (int i = 0; i < numAttributionsWithNotedAppOps; i++) {
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index edcdb6c..f34341f 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -34,6 +34,7 @@
import android.util.proto.ProtoOutputStream;
import android.util.proto.WireTypeMismatchException;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -777,7 +778,9 @@
mStartComponent = other.mStartComponent;
}
- private ApplicationStartInfo(@NonNull Parcel in) {
+ /** @hide */
+ @VisibleForTesting
+ public ApplicationStartInfo(@NonNull Parcel in) {
mStartupState = in.readInt();
mPid = in.readInt();
mRealUid = in.readInt();
@@ -1061,12 +1064,21 @@
if (other == null || !(other instanceof ApplicationStartInfo)) {
return false;
}
+
final ApplicationStartInfo o = (ApplicationStartInfo) other;
- return mPid == o.mPid && mRealUid == o.mRealUid && mPackageUid == o.mPackageUid
- && mDefiningUid == o.mDefiningUid && mReason == o.mReason
- && mStartupState == o.mStartupState && mStartType == o.mStartType
- && mLaunchMode == o.mLaunchMode && TextUtils.equals(mProcessName, o.mProcessName)
- && timestampsEquals(o) && mWasForceStopped == o.mWasForceStopped
+
+ return mPid == o.mPid
+ && mRealUid == o.mRealUid
+ && mPackageUid == o.mPackageUid
+ && mDefiningUid == o.mDefiningUid
+ && mReason == o.mReason
+ && mStartupState == o.mStartupState
+ && mStartType == o.mStartType
+ && mLaunchMode == o.mLaunchMode
+ && TextUtils.equals(mPackageName, o.mPackageName)
+ && TextUtils.equals(mProcessName, o.mProcessName)
+ && timestampsEquals(o)
+ && mWasForceStopped == o.mWasForceStopped
&& mMonoticCreationTimeMs == o.mMonoticCreationTimeMs
&& mStartComponent == o.mStartComponent;
}
@@ -1074,7 +1086,7 @@
@Override
public int hashCode() {
return Objects.hash(mPid, mRealUid, mPackageUid, mDefiningUid, mReason, mStartupState,
- mStartType, mLaunchMode, mProcessName, mStartupTimestampsNs,
+ mStartType, mLaunchMode, mPackageName, mProcessName, mStartupTimestampsNs,
mMonoticCreationTimeMs, mStartComponent);
}
diff --git a/core/java/android/app/BroadcastStickyCache.java b/core/java/android/app/BroadcastStickyCache.java
index d6f061b..ea81731 100644
--- a/core/java/android/app/BroadcastStickyCache.java
+++ b/core/java/android/app/BroadcastStickyCache.java
@@ -27,16 +27,21 @@
import android.net.nsd.NsdManager;
import android.net.wifi.WifiManager;
import android.net.wifi.p2p.WifiP2pManager;
+import android.os.ParcelFileDescriptor;
import android.os.SystemProperties;
import android.os.UpdateLock;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
import android.view.WindowManagerPolicyConstants;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastPrintWriter;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
import java.util.ArrayList;
/** @hide */
@@ -214,6 +219,41 @@
}
}
+ public static void dump(@NonNull ParcelFileDescriptor pfd) {
+ if (!Flags.useStickyBcastCache()) {
+ return;
+ }
+ final PrintWriter pw = new FastPrintWriter(new FileOutputStream(pfd.getFileDescriptor()));
+ synchronized (sCachedStickyBroadcasts) {
+ dumpLocked(pw);
+ }
+ pw.flush();
+ }
+
+ @GuardedBy("sCachedStickyBroadcasts")
+ private static void dumpLocked(@NonNull PrintWriter pw) {
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(
+ pw, " " /* singleIndent */, " " /* prefix */);
+ ipw.println("Cached sticky broadcasts:");
+ ipw.increaseIndent();
+ final int count = sCachedStickyBroadcasts.size();
+ if (count == 0) {
+ ipw.println("<empty>");
+ } else {
+ for (int i = 0; i < count; ++i) {
+ final CachedStickyBroadcast cachedStickyBroadcast = sCachedStickyBroadcasts.get(i);
+ ipw.print("Entry #"); ipw.print(i); ipw.println(":");
+ ipw.increaseIndent();
+ ipw.print("filter="); ipw.println(cachedStickyBroadcast.filter.toLongString());
+ ipw.print("intent="); ipw.println(cachedStickyBroadcast.intent);
+ ipw.print("version="); ipw.println(cachedStickyBroadcast.version);
+ ipw.print("handle="); ipw.println(cachedStickyBroadcast.propertyHandle);
+ ipw.decreaseIndent();
+ }
+ }
+ ipw.decreaseIndent();
+ }
+
private static final class CachedStickyBroadcast {
@NonNull public final IntentFilter filter;
@Nullable public Intent intent;
diff --git a/core/java/android/app/IUserSwitchObserver.aidl b/core/java/android/app/IUserSwitchObserver.aidl
index cfdb426..1ff7a17 100644
--- a/core/java/android/app/IUserSwitchObserver.aidl
+++ b/core/java/android/app/IUserSwitchObserver.aidl
@@ -19,10 +19,10 @@
import android.os.IRemoteCallback;
/** {@hide} */
-oneway interface IUserSwitchObserver {
+interface IUserSwitchObserver {
void onBeforeUserSwitching(int newUserId);
- void onUserSwitching(int newUserId, IRemoteCallback reply);
- void onUserSwitchComplete(int newUserId);
- void onForegroundProfileSwitch(int newProfileId);
- void onLockedBootComplete(int newUserId);
+ oneway void onUserSwitching(int newUserId, IRemoteCallback reply);
+ oneway void onUserSwitchComplete(int newUserId);
+ oneway void onForegroundProfileSwitch(int newProfileId);
+ oneway void onLockedBootComplete(int newUserId);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index e8b0a36f..0812a13 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1637,16 +1637,16 @@
public static final String EXTRA_PROGRESS_SEGMENTS = "android.progressSegments";
/**
- * {@link #extras} key: an arraylist of {@link android.app.Notification.ProgressStyle.Step}
+ * {@link #extras} key: an arraylist of {@link ProgressStyle.Point}
* bundles provided by a
* {@link android.app.Notification.ProgressStyle} notification as supplied to
- * {@link ProgressStyle#setProgressSteps}
- * or {@link ProgressStyle#addProgressStep(ProgressStyle.Step)}.
+ * {@link ProgressStyle#setProgressPoints}
+ * or {@link ProgressStyle#addProgressPoint(ProgressStyle.Point)}.
* This extra is a parcelable array list of bundles.
* @hide
*/
@FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
- public static final String EXTRA_PROGRESS_STEPS = "android.progressSteps";
+ public static final String EXTRA_PROGRESS_POINTS = "android.progressPoints";
/**
* {@link #extras} key: whether the progress bar should be styled by its progress as
@@ -11159,7 +11159,7 @@
/**
* A Notification Style used to to define a notification whose expanded state includes
- * a highly customizable progress bar with segments, steps, a custom tracker icon,
+ * a highly customizable progress bar with segments, points, a custom tracker icon,
* and custom icons at the start and end of the progress bar.
*
* This style is suggested for use cases where the app is showing a tracker to the
@@ -11185,8 +11185,8 @@
* .addProgressSegment(new Segment(552).setColor(Color.YELLOW))
* .addProgressSegment(new Segment(253).setColor(Color.YELLOW))
* .addProgressSegment(new Segment(94).setColor(Color.BLUE))
- * .addProgressStep(new Step(60).setColor(Color.RED))
- * .addProgressStep(new Step(560).setColor(Color.YELLOW))
+ * .addProgressPoint(new Point(60).setColor(Color.RED))
+ * .addProgressPoint(new Point(560).setColor(Color.YELLOW))
* )
* </pre>
*
@@ -11199,17 +11199,17 @@
*/
@FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
public static class ProgressStyle extends Notification.Style {
- private static final String KEY_ELEMENT_STABLE_ID = "stableId";
+ private static final String KEY_ELEMENT_ID = "id";
private static final String KEY_ELEMENT_COLOR = "colorInt";
private static final String KEY_SEGMENT_LENGTH = "length";
- private static final String KEY_STEP_POSITION = "position";
+ private static final String KEY_POINT_POSITION = "position";
private static final int MAX_PROGRESS_SEGMENT_LIMIT = 15;
- private static final int MAX_PROGRESS_STEP_LIMIT = 5;
+ private static final int MAX_PROGRESS_STOP_LIMIT = 5;
private static final int DEFAULT_PROGRESS_MAX = 100;
private List<Segment> mProgressSegments = new ArrayList<>();
- private List<Step> mProgressSteps = new ArrayList<>();
+ private List<Point> mProgressPoints = new ArrayList<>();
private int mProgress = 0;
@@ -11246,7 +11246,7 @@
nonIndeterminateCheckResult = !Objects.equals(mProgress, progressStyle.mProgress)
|| !Objects.equals(mIsStyledByProgress, progressStyle.mIsStyledByProgress)
|| !Objects.equals(mProgressSegments, progressStyle.mProgressSegments)
- || !Objects.equals(mProgressSteps, progressStyle.mProgressSteps)
+ || !Objects.equals(mProgressPoints, progressStyle.mProgressPoints)
|| !Objects.equals(mTrackerIcon, progressStyle.mTrackerIcon);
}
@@ -11300,48 +11300,47 @@
}
/**
- * Gets the steps that are displayed on the progress bar.
+ * Gets the points that are displayed on the progress bar.
*.
- * @see #setProgressSteps
- * @see #addProgressStep
- * @see Step
+ * @see #setProgressPoints
+ * @see #addProgressPoint
+ * @see Point
*/
- public @NonNull List<Step> getProgressSteps() {
- return mProgressSteps;
+ public @NonNull List<Point> getProgressPoints() {
+ return mProgressPoints;
}
/**
- * Replaces all the progress steps.
+ * Replaces all the progress points.
*
- * Steps are designated points within a progressbar to visualize
- * distinct stages or milestones.
- * For example, you might use steps to mark stops in a multi-stop
- * navigation journey, where each step represents a destination.
- * @see Step
+ * Points within a progress bar are used to visualize distinct stages or milestones.
+ * For example, you might use points to mark stops in a multi-stop
+ * navigation journey, where each point represents a destination.
+ * @see Point
*/
- public @NonNull ProgressStyle setProgressSteps(@NonNull List<Step> steps) {
- mProgressSteps = new ArrayList<>(steps);
+ public @NonNull ProgressStyle setProgressPoints(@NonNull List<Point> points) {
+ mProgressPoints = new ArrayList<>(points);
return this;
}
/**
- * Adds another step.
+ * Adds another point.
*
- * Steps are designated points within a progressbar to visualize
- * distinct stages or milestones.
- * For example, you might use steps to mark stops in a multi-stop
- * navigation journey, where each step represents a destination.
+ * Points within a progress bar are used to visualize distinct stages or milestones.
*
- * Steps can be added in any order, as their
+ * For example, you might use points to mark stops in a multi-stop
+ * navigation journey, where each point represents a destination.
+ *
+ * Points can be added in any order, as their
* position within the progress bar is determined by their individual
- * {@link Step#getPosition()}.
- * @see Step
+ * {@link Point#getPosition()}.
+ * @see Point
*/
- public @NonNull ProgressStyle addProgressStep(@NonNull Step step) {
- if (mProgressSteps == null) {
- mProgressSteps = new ArrayList<>();
+ public @NonNull ProgressStyle addProgressPoint(@NonNull Point point) {
+ if (mProgressPoints == null) {
+ mProgressPoints = new ArrayList<>();
}
- mProgressSteps.add(step);
+ mProgressPoints.add(point);
return this;
}
@@ -11414,7 +11413,7 @@
* When specified, the following fields are ignored:
* @see #setProgress
* @see #setProgressSegments
- * @see #setProgressSteps
+ * @see #setProgressPoints
* @see #setProgressTrackerIcon
* @see #setStyledByProgress
*
@@ -11435,7 +11434,7 @@
}
/**
- * Indicates whether the segments and steps will be styled differently
+ * Indicates whether the segments and points will be styled differently
* based on whether they are behind or ahead of the current progress.
* When true, segments appearing ahead of the current progress will be given a
* slightly different appearance to indicate that it is part of the progress bar
@@ -11558,8 +11557,8 @@
super.addExtras(extras);
extras.putParcelableArrayList(EXTRA_PROGRESS_SEGMENTS,
getProgressSegmentsAsBundleList(mProgressSegments));
- extras.putParcelableArrayList(EXTRA_PROGRESS_STEPS,
- getProgressStepsAsBundleList(mProgressSteps));
+ extras.putParcelableArrayList(EXTRA_PROGRESS_POINTS,
+ getProgressPointsAsBundleList(mProgressPoints));
extras.putInt(EXTRA_PROGRESS, mProgress);
extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, mIndeterminate);
@@ -11599,8 +11598,8 @@
mTrackerIcon = extras.getParcelable(EXTRA_PROGRESS_TRACKER_ICON, Icon.class);
mStartIcon = extras.getParcelable(EXTRA_PROGRESS_START_ICON, Icon.class);
mEndIcon = extras.getParcelable(EXTRA_PROGRESS_END_ICON, Icon.class);
- mProgressSteps = getProgressStepsFromBundleList(
- extras.getParcelableArrayList(EXTRA_PROGRESS_STEPS, Bundle.class));
+ mProgressPoints = getProgressPointsFromBundleList(
+ extras.getParcelableArrayList(EXTRA_PROGRESS_POINTS, Bundle.class));
}
/**
@@ -11613,6 +11612,30 @@
// actually be included.
return true;
}
+ /**
+ * @hide
+ */
+ @Override
+ public RemoteViews makeContentView(boolean increasedHeight) {
+ final StandardTemplateParams p = mBuilder.mParams.reset()
+ .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
+ .hideProgress(true)
+ .fillTextsFrom(mBuilder);
+
+ return getStandardView(mBuilder.getBaseLayoutResource(), p, null /* result */);
+ }
+ /**
+ * @hide
+ */
+ @Override
+ public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
+ final StandardTemplateParams p = mBuilder.mParams.reset()
+ .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
+ .hideProgress(true)
+ .fillTextsFrom(mBuilder);
+
+ return getStandardView(mBuilder.getHeadsUpBaseLayoutResource(), p, null /* result */);
+ }
private static @NonNull ArrayList<Bundle> getProgressSegmentsAsBundleList(
@Nullable List<Segment> progressSegments) {
@@ -11626,7 +11649,7 @@
final Bundle bundle = new Bundle();
bundle.putInt(KEY_SEGMENT_LENGTH, segment.getLength());
- bundle.putInt(KEY_ELEMENT_STABLE_ID, segment.getStableId());
+ bundle.putInt(KEY_ELEMENT_ID, segment.getId());
bundle.putInt(KEY_ELEMENT_COLOR, segment.getColor());
segments.add(bundle);
@@ -11647,11 +11670,11 @@
continue;
}
- final int stableId = segmentBundle.getInt(KEY_ELEMENT_STABLE_ID);
+ final int id = segmentBundle.getInt(KEY_ELEMENT_ID);
final int color = segmentBundle.getInt(KEY_ELEMENT_COLOR,
Notification.COLOR_DEFAULT);
final Segment segment = new Segment(length)
- .setStableId(stableId).setColor(color);
+ .setId(id).setColor(color);
segments.add(segment);
}
@@ -11660,48 +11683,48 @@
return segments;
}
- private static @NonNull ArrayList<Bundle> getProgressStepsAsBundleList(
- @Nullable List<Step> progressSteps) {
- final ArrayList<Bundle> steps = new ArrayList<>();
- if (progressSteps != null && !progressSteps.isEmpty()) {
- for (int i = 0; i < progressSteps.size(); i++) {
- final Step step = progressSteps.get(i);
- if (step.getPosition() < 0) {
+ private static @NonNull ArrayList<Bundle> getProgressPointsAsBundleList(
+ @Nullable List<Point> progressPoints) {
+ final ArrayList<Bundle> points = new ArrayList<>();
+ if (progressPoints != null && !progressPoints.isEmpty()) {
+ for (int i = 0; i < progressPoints.size(); i++) {
+ final Point point = progressPoints.get(i);
+ if (point.getPosition() < 0) {
continue;
}
final Bundle bundle = new Bundle();
- bundle.putInt(KEY_STEP_POSITION, step.getPosition());
- bundle.putInt(KEY_ELEMENT_STABLE_ID, step.getStableId());
- bundle.putInt(KEY_ELEMENT_COLOR, step.getColor());
+ bundle.putInt(KEY_POINT_POSITION, point.getPosition());
+ bundle.putInt(KEY_ELEMENT_ID, point.getId());
+ bundle.putInt(KEY_ELEMENT_COLOR, point.getColor());
- steps.add(bundle);
+ points.add(bundle);
}
}
- return steps;
+ return points;
}
- private static @NonNull List<Step> getProgressStepsFromBundleList(
- @Nullable List<Bundle> stepBundleList) {
- final ArrayList<Step> steps = new ArrayList<>();
+ private static @NonNull List<Point> getProgressPointsFromBundleList(
+ @Nullable List<Bundle> pointBundleList) {
+ final ArrayList<Point> points = new ArrayList<>();
- if (stepBundleList != null && !stepBundleList.isEmpty()) {
- for (int i = 0; i < stepBundleList.size(); i++) {
- final Bundle segmentBundle = stepBundleList.get(i);
- final int position = segmentBundle.getInt(KEY_STEP_POSITION);
+ if (pointBundleList != null && !pointBundleList.isEmpty()) {
+ for (int i = 0; i < pointBundleList.size(); i++) {
+ final Bundle pointBundle = pointBundleList.get(i);
+ final int position = pointBundle.getInt(KEY_POINT_POSITION);
if (position < 0) {
continue;
}
- final int stableId = segmentBundle.getInt(KEY_ELEMENT_STABLE_ID);
- final int color = segmentBundle.getInt(KEY_ELEMENT_COLOR,
+ final int id = pointBundle.getInt(KEY_ELEMENT_ID);
+ final int color = pointBundle.getInt(KEY_ELEMENT_COLOR,
Notification.COLOR_DEFAULT);
- final Step step = new Step(position).setStableId(stableId).setColor(color);
- steps.add(step);
+ final Point point = new Point(position).setId(id).setColor(color);
+ points.add(point);
}
}
- return steps;
+ return points;
}
/**
@@ -11712,7 +11735,7 @@
*/
public static final class Segment {
private int mLength;
- private int mStableId = 0;
+ private int mId = 0;
@ColorInt
private int mColor = Notification.COLOR_DEFAULT;
@@ -11735,19 +11758,19 @@
}
/**
- * Gets the stable id of this Segment.
+ * Gets the id of this Segment.
*
- * @see #setStableId
+ * @see #setId
*/
- public int getStableId() {
- return mStableId;
+ public int getId() {
+ return mId;
}
/**
* Optional ID used to uniquely identify the element across updates.
*/
- public @NonNull Segment setStableId(int stableId) {
- mStableId = stableId;
+ public @NonNull Segment setId(int id) {
+ mId = id;
return this;
}
@@ -11776,45 +11799,44 @@
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- Segment segment = (Segment) o;
- return mLength == segment.mLength && mStableId == segment.mStableId
+ final Segment segment = (Segment) o;
+ return mLength == segment.mLength && mId == segment.mId
&& mColor == segment.mColor;
}
@Override
public int hashCode() {
- return Objects.hash(mLength, mStableId, mColor);
+ return Objects.hash(mLength, mId, mColor);
}
}
/**
- * A step within the progress bar, defining its position and color.
- * Steps are designated points within a progressbar to visualize
- * distinct stages or milestones.
- * For example, you might use steps to mark stops in a multi-stop
- * navigation journey, where each step represents a destination.
+ * A point within the progress bar, defining its position and color.
+ * Points within a progress bar are used to visualize distinct stages or milestones.
+ * For example, you might use points to mark stops in a multi-stop
+ * navigation journey, where each point represents a destination.
*/
- public static final class Step {
+ public static final class Point {
private int mPosition;
- private int mStableId;
+ private int mId;
@ColorInt
private int mColor = Notification.COLOR_DEFAULT;
/**
- * Create a step element.
- * The position of this step on the progress bar
+ * Create a point element.
+ * The position of this point on the progress bar
* relative to {@link ProgressStyle#getProgressMax}
* @param position
* See {@link #getPosition}
*/
- public Step(int position) {
+ public Point(int position) {
mPosition = position;
}
/**
- * Gets the position of this Step.
- * The position of this step on the progress bar
+ * Gets the position of this Point.
+ * The position of this point on the progress bar
* relative to {@link ProgressStyle#getProgressMax}.
*/
public int getPosition() {
@@ -11823,17 +11845,17 @@
/**
- * Optional ID used to uniqurely identify the element across updates.
+ * Optional ID used to uniquely identify the element across updates.
*/
- public int getStableId() {
- return mStableId;
+ public int getId() {
+ return mId;
}
/**
- * Optional ID used to uniqurely identify the element across updates.
+ * Optional ID used to uniquely identify the element across updates.
*/
- public @NonNull Step setStableId(int stableId) {
- mStableId = stableId;
+ public @NonNull Point setId(int id) {
+ mId = id;
return this;
}
@@ -11850,7 +11872,7 @@
/**
* Optional color of this Segment
*/
- public @NonNull Step setColor(@ColorInt int color) {
+ public @NonNull Point setColor(@ColorInt int color) {
mColor = color;
return this;
}
@@ -11862,14 +11884,14 @@
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- Step step = (Step) o;
- return mPosition == step.mPosition && mStableId == step.mStableId
- && mColor == step.mColor;
+ final Point point = (Point) o;
+ return mPosition == point.mPosition && mId == point.mId
+ && mColor == point.mColor;
}
@Override
public int hashCode() {
- return Objects.hash(mPosition, mStableId, mColor);
+ return Objects.hash(mPosition, mId, mColor);
}
}
}
diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java
index 8e41773..7a68a65 100644
--- a/core/java/android/app/appfunctions/AppFunctionService.java
+++ b/core/java/android/app/appfunctions/AppFunctionService.java
@@ -35,6 +35,7 @@
import android.os.CancellationSignal;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.util.Log;
import java.util.function.Consumer;
@@ -166,9 +167,13 @@
*/
@MainThread
@Deprecated
- public abstract void onExecuteFunction(
+ public void onExecuteFunction(
@NonNull ExecuteAppFunctionRequest request,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+ @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
+ Log.w(
+ "AppFunctionService",
+ "Calling deprecated default implementation of onExecuteFunction");
+ }
/**
* Called by the system to execute a specific app function.
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index 2851e92..a879b1b 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -96,17 +96,14 @@
*/
public static final int RESULT_INVALID_ARGUMENT = 4;
- /** The operation was timed out. */
- public static final int RESULT_TIMED_OUT = 5;
-
/** The caller tried to execute a disabled app function. */
- public static final int RESULT_DISABLED = 6;
+ public static final int RESULT_DISABLED = 5;
/**
* The operation was cancelled. Use this error code to report that a cancellation is done after
* receiving a cancellation signal.
*/
- public static final int RESULT_CANCELLED = 7;
+ public static final int RESULT_CANCELLED = 6;
/** The result code of the app function execution. */
@ResultCode private final int mResultCode;
@@ -282,7 +279,6 @@
RESULT_APP_UNKNOWN_ERROR,
RESULT_INTERNAL_ERROR,
RESULT_INVALID_ARGUMENT,
- RESULT_TIMED_OUT,
RESULT_DISABLED,
RESULT_CANCELLED
})
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 8916ce2..6fe0a73 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -84,11 +84,16 @@
int getDevicePolicy(int policyType);
/**
- * Returns whether the device has a valid microphone.
- */
+ * Returns whether the device has a valid microphone.
+ */
boolean hasCustomAudioInputSupport();
/**
+ * Returns whether this device is allowed to create mirror displays.
+ */
+ boolean canCreateMirrorDisplays();
+
+ /**
* Closes the virtual device and frees all associated resources.
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index e9fa3e1..9eb6d56 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -132,8 +132,16 @@
}
flag {
- namespace: "virtual_devices"
- name: "camera_timestamp_from_surface"
- description: "Pass the surface timestamp to the capture result"
- bug: "351341245"
+ namespace: "virtual_devices"
+ name: "camera_timestamp_from_surface"
+ description: "Pass the surface timestamp to the capture result"
+ bug: "351341245"
+}
+
+flag {
+ namespace: "virtual_devices"
+ name: "enable_limited_vdm_role"
+ description: "New VDM role without trusted displays or input"
+ bug: "370657575"
+ is_exported: true
}
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index 40ffb0f..64e9c33 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -476,6 +476,20 @@
return entry;
}
+ @NonNull
+ private static FabricatedOverlayInternalEntry generateFabricatedOverlayInternalEntry(
+ @NonNull String resourceName, float dimensionValue,
+ @TypedValue.ComplexDimensionUnit int dimensionUnit, @Nullable String configuration) {
+ final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
+ entry.resourceName = resourceName;
+ entry.dataType = TypedValue.TYPE_DIMENSION;
+ Preconditions.checkArgumentInRange(dimensionUnit,
+ TypedValue.COMPLEX_UNIT_PX, TypedValue.COMPLEX_UNIT_MM, "dimensionUnit");
+ entry.data = TypedValue.createComplexDimension(dimensionValue, dimensionUnit);
+ entry.configuration = configuration;
+ return entry;
+ }
+
/**
* Sets the resource value in the fabricated overlay for the integer-like types with the
* configuration.
@@ -586,4 +600,25 @@
mOverlay.entries.add(
generateFabricatedOverlayInternalEntry(resourceName, value, configuration));
}
+
+ /**
+ * Sets the resource value in the fabricated overlay for the dimension type with the
+ * configuration.
+ *
+ * @param resourceName name of the target resource to overlay (in the form
+ * [package]:type/entry)
+ * @param dimensionValue the float representing the dimension value
+ * @param dimensionUnit the integer representing the dimension unit
+ * @param configuration The string representation of the config this overlay is enabled for
+ */
+ @FlaggedApi(android.content.res.Flags.FLAG_DIMENSION_FRRO)
+ public void setResourceValue(
+ @NonNull String resourceName,
+ float dimensionValue,
+ @TypedValue.ComplexDimensionUnit int dimensionUnit,
+ @Nullable String configuration) {
+ ensureValidResourceName(resourceName);
+ mOverlay.entries.add(generateFabricatedOverlayInternalEntry(resourceName, dimensionValue,
+ dimensionUnit, configuration));
+ }
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index fb2655c..e985f88 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5039,6 +5039,25 @@
"android.content.pm.action.REQUEST_PERMISSIONS_FOR_OTHER";
/**
+ * Used by the system to query a {@link android.content.pm.verify.pkg.VerifierService} provider,
+ * which registers itself via an intent-filter handling this action.
+ *
+ * <p class="note">Only the system can bind to such a verifier service. This is protected by the
+ * {@link android.Manifest.permission#BIND_VERIFICATION_AGENT} permission. The verifier service
+ * app should protect the service by adding this permission in the service declaration in its
+ * manifest.
+ * <p>
+ * A verifier service must be a privileged app and hold the
+ * {@link android.Manifest.permission#VERIFICATION_AGENT} permission.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(android.content.pm.Flags.FLAG_VERIFICATION_SERVICE)
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
+ public static final String ACTION_VERIFY_PACKAGE = "android.content.pm.action.VERIFY_PACKAGE";
+
+ /**
* The names of the requested permissions.
* <p>
* <strong>Type:</strong> String[]
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 160cbdf..300740e 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -309,3 +309,11 @@
description: "Feature flag to enable the holder of SYSTEM_APP_PROTECTION_SERVICE role to silently delete packages. To be deprecated by delete_packages_silently."
bug: "361776825"
}
+
+flag {
+ name: "verification_service"
+ namespace: "package_manager_service"
+ description: "Feature flag to enable the new verification service."
+ bug: "360129103"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index fd1a896..d5edc92 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -68,6 +68,13 @@
}
flag {
+ name: "multiuser_widget"
+ namespace: "multiuser"
+ description: "Implement the Multiuser Widget"
+ bug: "365748524"
+}
+
+flag {
name: "enable_biometrics_to_unlock_private_space"
is_exported: true
namespace: "profile_experiences"
diff --git a/core/java/android/content/pm/verify/pkg/IVerificationSessionCallback.aidl b/core/java/android/content/pm/verify/pkg/IVerificationSessionCallback.aidl
new file mode 100644
index 0000000..38a7956
--- /dev/null
+++ b/core/java/android/content/pm/verify/pkg/IVerificationSessionCallback.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.pkg;
+
+import android.content.pm.verify.pkg.VerificationStatus;
+import android.os.PersistableBundle;
+
+/**
+ * Oneway interface that allows the verifier to send response or verification results back to
+ * the system.
+ * @hide
+ */
+oneway interface IVerificationSessionCallback {
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+ void reportVerificationIncomplete(int verificationId, int reason);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+ void reportVerificationComplete(int verificationId, in VerificationStatus status);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+ void reportVerificationCompleteWithExtensionResponse(int verificationId, in VerificationStatus status, in PersistableBundle response);
+}
diff --git a/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl b/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl
new file mode 100644
index 0000000..7a9484a
--- /dev/null
+++ b/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.pkg;
+
+/**
+ * Non-oneway interface that allows the verifier to retrieve information from the system.
+ * @hide
+ */
+interface IVerificationSessionInterface {
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+ long getTimeoutTime(int verificationId);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+ long extendTimeRemaining(int verificationId, long additionalMs);
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/verify/pkg/IVerifierService.aidl b/core/java/android/content/pm/verify/pkg/IVerifierService.aidl
new file mode 100644
index 0000000..d3071fd
--- /dev/null
+++ b/core/java/android/content/pm/verify/pkg/IVerifierService.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.pkg;
+
+import android.content.pm.verify.pkg.VerificationSession;
+
+/**
+ * Oneway interface that allows the system to communicate to the verifier service agent.
+ * @hide
+ */
+oneway interface IVerifierService {
+ void onPackageNameAvailable(in String packageName);
+ void onVerificationCancelled(in String packageName);
+ void onVerificationRequired(in VerificationSession session);
+ void onVerificationRetry(in VerificationSession session);
+ void onVerificationTimeout(int verificationId);
+}
\ No newline at end of file
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt b/core/java/android/content/pm/verify/pkg/VerificationSession.aidl
similarity index 72%
copy from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt
copy to core/java/android/content/pm/verify/pkg/VerificationSession.aidl
index e52a6e1..ac85585 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt
+++ b/core/java/android/content/pm/verify/pkg/VerificationSession.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.util
+package android.content.pm.verify.pkg;
-/** Holds a [newValue] emitted from a `TFlow`, along with the [previousValue] emitted value. */
-data class WithPrev<out S, out T : S>(val previousValue: S, val newValue: T)
+/** @hide */
+parcelable VerificationSession;
diff --git a/core/java/android/content/pm/verify/pkg/VerificationSession.java b/core/java/android/content/pm/verify/pkg/VerificationSession.java
new file mode 100644
index 0000000..70b4a02
--- /dev/null
+++ b/core/java/android/content/pm/verify/pkg/VerificationSession.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.pkg;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.pm.Flags;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.SigningInfo;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This class is used by the system to describe the details about a verification request sent to the
+ * verification agent, aka the verifier. It includes the interfaces for the verifier to communicate
+ * back to the system.
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE)
+@SystemApi
+public final class VerificationSession implements Parcelable {
+ /**
+ * The verification cannot be completed because of unknown reasons.
+ */
+ public static final int VERIFICATION_INCOMPLETE_UNKNOWN = 0;
+ /**
+ * The verification cannot be completed because the network is unavailable.
+ */
+ public static final int VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE = 1;
+ /**
+ * The verification cannot be completed because the network is limited.
+ */
+ public static final int VERIFICATION_INCOMPLETE_NETWORK_LIMITED = 2;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"VERIFICATION_INCOMPLETE_"}, value = {
+ VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE,
+ VERIFICATION_INCOMPLETE_NETWORK_LIMITED,
+ VERIFICATION_INCOMPLETE_UNKNOWN,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface VerificationIncompleteReason {
+ }
+
+ private final int mId;
+ private final int mInstallSessionId;
+ @NonNull
+ private final String mPackageName;
+ @NonNull
+ private final Uri mStagedPackageUri;
+ @NonNull
+ private final SigningInfo mSigningInfo;
+ @NonNull
+ private final List<SharedLibraryInfo> mDeclaredLibraries;
+ @NonNull
+ private final PersistableBundle mExtensionParams;
+ @NonNull
+ private final IVerificationSessionInterface mSession;
+ @NonNull
+ private final IVerificationSessionCallback mCallback;
+
+ /**
+ * Constructor used by the system to describe the details of a verification session.
+ * @hide
+ */
+ public VerificationSession(int id, int installSessionId, @NonNull String packageName,
+ @NonNull Uri stagedPackageUri, @NonNull SigningInfo signingInfo,
+ @NonNull List<SharedLibraryInfo> declaredLibraries,
+ @NonNull PersistableBundle extensionParams,
+ @NonNull IVerificationSessionInterface session,
+ @NonNull IVerificationSessionCallback callback) {
+ mId = id;
+ mInstallSessionId = installSessionId;
+ mPackageName = packageName;
+ mStagedPackageUri = stagedPackageUri;
+ mSigningInfo = signingInfo;
+ mDeclaredLibraries = declaredLibraries;
+ mExtensionParams = extensionParams;
+ mSession = session;
+ mCallback = callback;
+ }
+
+ /**
+ * A unique identifier tied to this specific verification session.
+ */
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * The package name of the app that is to be verified.
+ */
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * The id of the installation session associated with the verification.
+ */
+ public int getInstallSessionId() {
+ return mInstallSessionId;
+ }
+
+ /**
+ * The Uri of the path where the package's code files are located.
+ */
+ public @NonNull Uri getStagedPackageUri() {
+ return mStagedPackageUri;
+ }
+
+ /**
+ * Signing info of the package to be verified.
+ */
+ public @NonNull SigningInfo getSigningInfo() {
+ return mSigningInfo;
+ }
+
+ /**
+ * Returns a mapping of any shared libraries declared in the manifest
+ * to the {@link SharedLibraryInfo#Type} that is declared. This will be an empty
+ * map if no shared libraries are declared by the package.
+ */
+ @NonNull
+ public List<SharedLibraryInfo> getDeclaredLibraries() {
+ return Collections.unmodifiableList(mDeclaredLibraries);
+ }
+
+ /**
+ * Returns any extension params associated with the verification request.
+ */
+ @NonNull
+ public PersistableBundle getExtensionParams() {
+ return mExtensionParams;
+ }
+
+ /**
+ * Get the value of Clock.elapsedRealtime() at which time this verification
+ * will timeout as incomplete if no other verification response is provided.
+ */
+ @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
+ public long getTimeoutTime() {
+ try {
+ return mSession.getTimeoutTime(mId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Extend the timeout for this session by the provided additionalMs to
+ * fetch relevant information over the network or wait for the network.
+ * This may be called multiple times. If the request would bypass any max
+ * duration by the system, the method will return a lower value than the
+ * requested amount that indicates how much the time was extended.
+ */
+ @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
+ public long extendTimeRemaining(long additionalMs) {
+ try {
+ return mSession.extendTimeRemaining(mId, additionalMs);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Report to the system that verification could not be completed along
+ * with an approximate reason to pass on to the installer.
+ */
+ @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
+ public void reportVerificationIncomplete(@VerificationIncompleteReason int reason) {
+ try {
+ mCallback.reportVerificationIncomplete(mId, reason);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Report to the system that the verification has completed and the
+ * install process may act on that status to either block in the case
+ * of failure or continue to process the install in the case of success.
+ */
+ @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
+ public void reportVerificationComplete(@NonNull VerificationStatus status) {
+ try {
+ mCallback.reportVerificationComplete(mId, status);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Same as {@link #reportVerificationComplete(VerificationStatus)}, but also provide
+ * a result to the extension params provided in the request, which will be passed to the
+ * installer in the installation result.
+ */
+ @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
+ public void reportVerificationComplete(@NonNull VerificationStatus status,
+ @NonNull PersistableBundle response) {
+ try {
+ mCallback.reportVerificationCompleteWithExtensionResponse(mId, status, response);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private VerificationSession(@NonNull Parcel in) {
+ mId = in.readInt();
+ mInstallSessionId = in.readInt();
+ mPackageName = in.readString8();
+ mStagedPackageUri = Uri.CREATOR.createFromParcel(in);
+ mSigningInfo = SigningInfo.CREATOR.createFromParcel(in);
+ mDeclaredLibraries = in.createTypedArrayList(SharedLibraryInfo.CREATOR);
+ mExtensionParams = in.readPersistableBundle(getClass().getClassLoader());
+ mSession = IVerificationSessionInterface.Stub.asInterface(in.readStrongBinder());
+ mCallback = IVerificationSessionCallback.Stub.asInterface(in.readStrongBinder());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mId);
+ dest.writeInt(mInstallSessionId);
+ dest.writeString8(mPackageName);
+ Uri.writeToParcel(dest, mStagedPackageUri);
+ mSigningInfo.writeToParcel(dest, flags);
+ dest.writeTypedList(mDeclaredLibraries);
+ dest.writePersistableBundle(mExtensionParams);
+ dest.writeStrongBinder(mSession.asBinder());
+ dest.writeStrongBinder(mCallback.asBinder());
+ }
+
+ @NonNull
+ public static final Creator<VerificationSession> CREATOR = new Creator<>() {
+ @Override
+ public VerificationSession createFromParcel(@NonNull Parcel in) {
+ return new VerificationSession(in);
+ }
+
+ @Override
+ public VerificationSession[] newArray(int size) {
+ return new VerificationSession[size];
+ }
+ };
+}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt b/core/java/android/content/pm/verify/pkg/VerificationStatus.aidl
similarity index 72%
copy from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt
copy to core/java/android/content/pm/verify/pkg/VerificationStatus.aidl
index e52a6e1..6a1cb4f 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt
+++ b/core/java/android/content/pm/verify/pkg/VerificationStatus.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.util
+package android.content.pm.verify.pkg;
-/** Holds a [newValue] emitted from a `TFlow`, along with the [previousValue] emitted value. */
-data class WithPrev<out S, out T : S>(val previousValue: S, val newValue: T)
+/** @hide */
+parcelable VerificationStatus;
diff --git a/core/java/android/content/pm/verify/pkg/VerificationStatus.java b/core/java/android/content/pm/verify/pkg/VerificationStatus.java
new file mode 100644
index 0000000..4d0379d7
--- /dev/null
+++ b/core/java/android/content/pm/verify/pkg/VerificationStatus.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.pkg;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.pm.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class is used by the verifier to describe the status of the verification request, whether
+ * it's successful or it has failed along with any relevant details.
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE)
+public final class VerificationStatus implements Parcelable {
+ /**
+ * The ASL status has not been determined. This happens in situations where the verification
+ * service is not monitoring ASLs, and means the ASL data in the app is not necessarily bad but
+ * can't be trusted.
+ */
+ public static final int VERIFIER_STATUS_ASL_UNDEFINED = 0;
+
+ /**
+ * The app's ASL data is considered to be in a good state.
+ */
+ public static final int VERIFIER_STATUS_ASL_GOOD = 1;
+
+ /**
+ * There is something bad in the app's ASL data; the user should be warned about this when shown
+ * the ASL data and/or appropriate decisions made about the use of this data by the platform.
+ */
+ public static final int VERIFIER_STATUS_ASL_BAD = 2;
+
+ /** @hide */
+ @IntDef(prefix = {"VERIFIER_STATUS_ASL_"}, value = {
+ VERIFIER_STATUS_ASL_UNDEFINED,
+ VERIFIER_STATUS_ASL_GOOD,
+ VERIFIER_STATUS_ASL_BAD,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface VerifierStatusAsl {}
+
+ private boolean mIsVerified;
+ private @VerifierStatusAsl int mAslStatus;
+ @NonNull
+ private String mFailuresMessage = "";
+
+ private VerificationStatus() {}
+
+ /**
+ * @return whether the status is set to verified or not.
+ */
+ public boolean isVerified() {
+ return mIsVerified;
+ }
+
+ /**
+ * @return the failure message associated with the failure status.
+ */
+ @NonNull
+ public String getFailureMessage() {
+ return mFailuresMessage;
+ }
+
+ /**
+ * @return the asl status.
+ */
+ public @VerifierStatusAsl int getAslStatus() {
+ return mAslStatus;
+ }
+
+ /**
+ * Builder to construct a {@link VerificationStatus} object.
+ */
+ public static final class Builder {
+ final VerificationStatus mStatus = new VerificationStatus();
+
+ /**
+ * Set in the status whether the verification has succeeded or failed.
+ */
+ @NonNull
+ public Builder setVerified(boolean verified) {
+ mStatus.mIsVerified = verified;
+ return this;
+ }
+
+ /**
+ * Set a developer-facing failure message to include in the verification failure status.
+ */
+ @NonNull
+ public Builder setFailureMessage(@NonNull String failureMessage) {
+ mStatus.mFailuresMessage = failureMessage;
+ return this;
+ }
+
+ /**
+ * Set the ASL status, as defined in {@link VerifierStatusAsl}.
+ */
+ @NonNull
+ public Builder setAslStatus(@VerifierStatusAsl int aslStatus) {
+ mStatus.mAslStatus = aslStatus;
+ return this;
+ }
+
+ /**
+ * Build the status object.
+ */
+ @NonNull
+ public VerificationStatus build() {
+ return mStatus;
+ }
+ }
+
+ private VerificationStatus(Parcel in) {
+ mIsVerified = in.readBoolean();
+ mAslStatus = in.readInt();
+ mFailuresMessage = in.readString8();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeBoolean(mIsVerified);
+ dest.writeInt(mAslStatus);
+ dest.writeString8(mFailuresMessage);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Creator<VerificationStatus> CREATOR = new Creator<>() {
+ @Override
+ public VerificationStatus createFromParcel(@NonNull Parcel in) {
+ return new VerificationStatus(in);
+ }
+
+ @Override
+ public VerificationStatus[] newArray(int size) {
+ return new VerificationStatus[size];
+ }
+ };
+}
diff --git a/core/java/android/content/pm/verify/pkg/VerifierService.java b/core/java/android/content/pm/verify/pkg/VerifierService.java
new file mode 100644
index 0000000..ccf2119
--- /dev/null
+++ b/core/java/android/content/pm/verify/pkg/VerifierService.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.pkg;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.Flags;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+
+/**
+ * A base service implementation for the verifier agent to implement.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE)
+public abstract class VerifierService extends Service {
+ /**
+ * Called when a package name is available for a pending verification,
+ * giving the verifier opportunity to pre-fetch any relevant information
+ * that may be needed should a verification for the package be required.
+ */
+ public abstract void onPackageNameAvailable(@NonNull String packageName);
+
+ /**
+ * Called when a package recently provided via {@link #onPackageNameAvailable}
+ * is no longer expected to be installed. This is a hint that any pre-fetch or
+ * cache created as a result of the previous call may be be cleared.
+ * <p>This method will never be called after {@link #onVerificationRequired} is called for the
+ * same package. Once a verification is officially requested by
+ * {@link #onVerificationRequired}, it cannot be cancelled.
+ * </p>
+ */
+ public abstract void onVerificationCancelled(@NonNull String packageName);
+
+ /**
+ * Called when an application needs to be verified. Details about the
+ * verification and actions that can be taken on it will be encapsulated in
+ * the provided {@link VerificationSession} parameter.
+ */
+ public abstract void onVerificationRequired(@NonNull VerificationSession session);
+
+ /**
+ * Called when a verification needs to be retried. This can be encountered
+ * when a prior verification was marked incomplete and the user has indicated
+ * that they've resolved the issue, or when a timeout is reached, but the
+ * the system is attempting to retry. Details about the
+ * verification and actions that can be taken on it will be encapsulated in
+ * the provided {@link VerificationSession} parameter.
+ */
+ public abstract void onVerificationRetry(@NonNull VerificationSession session);
+
+ /**
+ * Called in the case that an active verification has failed. Any APIs called
+ * on the {@link VerificationSession} instance associated with this {@code verificationId} will
+ * throw an {@link IllegalStateException}.
+ */
+ public abstract void onVerificationTimeout(int verificationId);
+
+ /**
+ * Called when the verifier service is bound to the system.
+ */
+ public @Nullable IBinder onBind(@Nullable Intent intent) {
+ if (intent == null || !PackageManager.ACTION_VERIFY_PACKAGE.equals(intent.getAction())) {
+ return null;
+ }
+ return new IVerifierService.Stub() {
+ @Override
+ public void onPackageNameAvailable(@NonNull String packageName) {
+ VerifierService.this.onPackageNameAvailable(packageName);
+ }
+
+ @Override
+ public void onVerificationCancelled(@NonNull String packageName) {
+ VerifierService.this.onVerificationCancelled(packageName);
+ }
+
+ @Override
+ public void onVerificationRequired(@NonNull VerificationSession session) {
+ VerifierService.this.onVerificationRequired(session);
+ }
+
+ @Override
+ public void onVerificationRetry(@NonNull VerificationSession session) {
+ VerifierService.this.onVerificationRetry(session);
+ }
+
+ @Override
+ public void onVerificationTimeout(int verificationId) {
+ VerifierService.this.onVerificationTimeout(verificationId);
+ }
+ };
+ }
+}
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index a5f8199..0af2f25 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -66,3 +66,11 @@
# This flag is read at boot time.
is_fixed_read_only: true
}
+
+flag {
+ name: "dimension_frro"
+ is_exported: true
+ namespace: "resource_manager"
+ description: "Feature flag for passing a dimension to create an frro"
+ bug: "369672322"
+}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index b11961c..e3fdd26 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -139,6 +139,13 @@
public static final int DISMISSED_REASON_CONTENT_VIEW_MORE_OPTIONS = 8;
/**
+ * Dialog dismissal due to the system being unable to retrieve a WindowManager instance required
+ * to show the dialog.
+ * @hide
+ */
+ public static final int DISMISSED_REASON_ERROR_NO_WM = 9;
+
+ /**
* @hide
*/
@IntDef({DISMISSED_REASON_BIOMETRIC_CONFIRMED,
@@ -148,7 +155,8 @@
DISMISSED_REASON_ERROR,
DISMISSED_REASON_SERVER_REQUESTED,
DISMISSED_REASON_CREDENTIAL_CONFIRMED,
- DISMISSED_REASON_CONTENT_VIEW_MORE_OPTIONS})
+ DISMISSED_REASON_CONTENT_VIEW_MORE_OPTIONS,
+ DISMISSED_REASON_ERROR_NO_WM})
@Retention(RetentionPolicy.SOURCE)
public @interface DismissedReason {}
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 7185719..6affd12 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -792,7 +792,6 @@
public void setVirtualDisplaySurface(IVirtualDisplayCallback token, Surface surface) {
try {
mDm.setVirtualDisplaySurface(token, surface);
- setVirtualDisplayState(token, surface != null);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
@@ -815,14 +814,6 @@
}
}
- void setVirtualDisplayState(IVirtualDisplayCallback token, boolean isOn) {
- try {
- mDm.setVirtualDisplayState(token, isOn);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- }
-
void setVirtualDisplayRotation(IVirtualDisplayCallback token, @Surface.Rotation int rotation) {
try {
mDm.setVirtualDisplayRotation(token, rotation);
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index e598097..36e816a 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -455,6 +455,11 @@
public abstract void onPresentation(int displayId, boolean isShown);
/**
+ * Called upon the usage of stylus.
+ */
+ public abstract void stylusGestureStarted(long eventTime);
+
+ /**
* Describes the requested power state of the display.
*
* This object is intended to describe the general characteristics of the
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index aa1539f6..b612bca 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -115,9 +115,6 @@
void releaseVirtualDisplay(in IVirtualDisplayCallback token);
// No permissions required but must be same Uid as the creator.
- void setVirtualDisplayState(in IVirtualDisplayCallback token, boolean isOn);
-
- // No permissions required but must be same Uid as the creator.
void setVirtualDisplayRotation(in IVirtualDisplayCallback token, int rotation);
// Get a stable metric for the device's display size. No permissions required.
diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java
index 6cc938f..32b6405 100644
--- a/core/java/android/hardware/display/VirtualDisplay.java
+++ b/core/java/android/hardware/display/VirtualDisplay.java
@@ -112,18 +112,6 @@
}
/**
- * Sets the on/off state for a virtual display.
- *
- * @param isOn Whether the display should be on or off.
- * @hide
- */
- public void setDisplayState(boolean isOn) {
- if (mToken != null) {
- mGlobal.setVirtualDisplayState(mToken, isOn);
- }
- }
-
- /**
* Sets the rotation of the virtual display.
*
* @param rotation the new rotation of the display. May be one of {@link Surface#ROTATION_0},
diff --git a/core/java/android/net/vcn/VcnTransportInfo.java b/core/java/android/net/vcn/VcnTransportInfo.java
index f546910..1fc91ee 100644
--- a/core/java/android/net/vcn/VcnTransportInfo.java
+++ b/core/java/android/net/vcn/VcnTransportInfo.java
@@ -17,9 +17,11 @@
package android.net.vcn;
import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
+import static android.net.vcn.VcnGatewayConnectionConfig.MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS;
import static android.net.vcn.VcnGatewayConnectionConfig.MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.NetworkCapabilities;
@@ -29,6 +31,8 @@
import android.os.Parcelable;
import android.telephony.SubscriptionManager;
+import com.android.internal.util.Preconditions;
+
import java.util.Objects;
/**
@@ -47,6 +51,7 @@
*
* @hide
*/
+// TODO: Do not store WifiInfo and subscription ID in VcnTransportInfo anymore
public class VcnTransportInfo implements TransportInfo, Parcelable {
@Nullable private final WifiInfo mWifiInfo;
private final int mSubId;
@@ -195,4 +200,42 @@
return new VcnTransportInfo[size];
}
};
+
+ /** This class can be used to construct a {@link VcnTransportInfo}. */
+ public static final class Builder {
+ private int mMinUdpPort4500NatTimeoutSeconds = MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET;
+
+ /** Construct Builder */
+ public Builder() {}
+
+ /**
+ * Sets the maximum supported IKEv2/IPsec NATT keepalive timeout.
+ *
+ * <p>This is used as a power-optimization hint for other IKEv2/IPsec use cases (e.g. VPNs,
+ * or IWLAN) to reduce the necessary keepalive frequency, thus conserving power and data.
+ *
+ * @param minUdpPort4500NatTimeoutSeconds the maximum keepalive timeout supported by the VCN
+ * Gateway Connection, generally the minimum duration a NAT mapping is cached on the VCN
+ * Gateway.
+ * @return this {@link Builder} instance, for chaining
+ */
+ @NonNull
+ public Builder setMinUdpPort4500NatTimeoutSeconds(
+ @IntRange(from = MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS)
+ int minUdpPort4500NatTimeoutSeconds) {
+ Preconditions.checkArgument(
+ minUdpPort4500NatTimeoutSeconds >= MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS,
+ "Timeout must be at least 120s");
+
+ mMinUdpPort4500NatTimeoutSeconds = minUdpPort4500NatTimeoutSeconds;
+ return Builder.this;
+ }
+
+ /** Build a VcnTransportInfo instance */
+ @NonNull
+ public VcnTransportInfo build() {
+ return new VcnTransportInfo(
+ null /* wifiInfo */, INVALID_SUBSCRIPTION_ID, mMinUdpPort4500NatTimeoutSeconds);
+ }
+ }
}
diff --git a/core/java/android/net/vcn/VcnUtils.java b/core/java/android/net/vcn/VcnUtils.java
new file mode 100644
index 0000000..6dc5180
--- /dev/null
+++ b/core/java/android/net/vcn/VcnUtils.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.vcn;
+
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.TransportInfo;
+import android.net.wifi.WifiInfo;
+
+import java.util.List;
+
+/**
+ * Utility class for VCN callers get information from VCN network
+ *
+ * @hide
+ */
+public class VcnUtils {
+ /** Get the WifiInfo of the VCN's underlying WiFi network */
+ @Nullable
+ public static WifiInfo getWifiInfoFromVcnCaps(
+ @NonNull ConnectivityManager connectivityMgr,
+ @NonNull NetworkCapabilities networkCapabilities) {
+ final NetworkCapabilities underlyingCaps =
+ getVcnUnderlyingCaps(connectivityMgr, networkCapabilities);
+
+ if (underlyingCaps == null) {
+ return null;
+ }
+
+ final TransportInfo underlyingTransportInfo = underlyingCaps.getTransportInfo();
+ if (!(underlyingTransportInfo instanceof WifiInfo)) {
+ return null;
+ }
+
+ return (WifiInfo) underlyingTransportInfo;
+ }
+
+ /** Get the subscription ID of the VCN's underlying Cell network */
+ public static int getSubIdFromVcnCaps(
+ @NonNull ConnectivityManager connectivityMgr,
+ @NonNull NetworkCapabilities networkCapabilities) {
+ final NetworkCapabilities underlyingCaps =
+ getVcnUnderlyingCaps(connectivityMgr, networkCapabilities);
+
+ if (underlyingCaps == null) {
+ return INVALID_SUBSCRIPTION_ID;
+ }
+
+ final NetworkSpecifier underlyingNetworkSpecifier = underlyingCaps.getNetworkSpecifier();
+ if (!(underlyingNetworkSpecifier instanceof TelephonyNetworkSpecifier)) {
+ return INVALID_SUBSCRIPTION_ID;
+ }
+
+ return ((TelephonyNetworkSpecifier) underlyingNetworkSpecifier).getSubscriptionId();
+ }
+
+ @Nullable
+ private static NetworkCapabilities getVcnUnderlyingCaps(
+ @NonNull ConnectivityManager connectivityMgr,
+ @NonNull NetworkCapabilities networkCapabilities) {
+ // Return null if it is not a VCN network
+ if (networkCapabilities.getTransportInfo() == null
+ || !(networkCapabilities.getTransportInfo() instanceof VcnTransportInfo)) {
+ return null;
+ }
+
+ // As of Android 16, VCN has one underlying network, and only one. If there are more
+ // than one networks due to future changes in the VCN mainline code, just take the first
+ // network
+ final List<Network> underlyingNws = networkCapabilities.getUnderlyingNetworks();
+ if (underlyingNws == null) {
+ return null;
+ }
+
+ return connectivityMgr.getNetworkCapabilities(underlyingNws.get(0));
+ }
+}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index b9b5295..c41e626 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -3088,8 +3088,9 @@
public static final String[] HISTORY_EVENT_CHECKIN_NAMES = new String[] {
"Enl", "Epr", "Efg", "Etp", "Esy", "Ewl", "Ejb", "Eur", "Euf", "Ecn",
- "Eac", "Epi", "Epu", "Eal", "Est", "Eai", "Eaa", "Etw",
- "Esw", "Ewa", "Elw", "Eec", "Esc", "Eds"
+ "Eac", "Epi", "Epu", "Eal", "Est", "Eai", "Eaa",
+ "Etw", "Esw", "Ewa", "Elw", "Esc",
+ "Eds"
};
@FunctionalInterface
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index a8267d1..479aa98 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressAutoDoc;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.ActivityThread;
@@ -401,33 +402,42 @@
* device. This value never changes while a device is booted, but it may
* increase when the hardware manufacturer provides an OTA update.
* <p>
- * Together with {@link SDK_MINOR_INT}, this constant defines the
- * <pre>major.minor</pre> version of Android. <pre>SDK_INT</pre> is
- * increased and <pre>SDK_MINOR_INT</pre> is set to 0 on new Android
- * dessert releases. Between these, Android may also release so called
- * minor releases where <pre>SDK_INT</pre> remains unchanged and
- * <pre>SDK_MINOR_INT</pre> is increased. Minor releases can add new
- * APIs, and have stricter guarantees around backwards compatibility
- * (e.g. no changes gated by <pre>targetSdkVersion</pre>) compared to
- * major releases.
+ * This constant records the major version of Android. Use {@link
+ * SDK_INT_FULL} if you need to consider the minor version of Android
+ * as well.
* <p>
* Possible values are defined in {@link Build.VERSION_CODES}.
+ * @see #SDK_INT_FULL
*/
public static final int SDK_INT = SystemProperties.getInt(
"ro.build.version.sdk", 0);
/**
- * The minor SDK version of the software currently running on this hardware
- * device. This value never changes while a device is booted, but it may
- * increase when the hardware manufacturer provides an OTA update.
+ * The major and minor SDK version of the software currently running on
+ * this hardware device. This value never changes while a device is
+ * booted, but it may increase when the hardware manufacturer provides
+ * an OTA update.
* <p>
- * Together with {@link SDK_INT}, this constant defines the
- * <pre>major.minor</pre> version of Android. See {@link SDK_INT} for
- * more information.
+ * <code>SDK_INT</code> is increased on new Android dessert releases,
+ * also called major releases. Between these, Android may also release
+ * minor releases where <code>SDK_INT</code> remains unchanged. Minor
+ * releases can add new APIs, and have stricter guarantees around
+ * backwards compatibility (e.g. no changes gated by
+ * <code>targetSdkVersion</code>) compared to major releases.
+ * <p>
+ * <code>SDK_INT_FULL</code> is increased on every release.
+ * <p>
+ * Possible values are defined in {@link
+ * android.os.Build.VERSION_CODES_FULL}.
*/
@FlaggedApi(Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME)
- public static final int SDK_MINOR_INT = SystemProperties.getInt(
- "ro.build.version.sdk_minor", 0);
+ public static final int SDK_INT_FULL;
+
+ static {
+ SDK_INT_FULL = VERSION_CODES_FULL.SDK_INT_MULTIPLIER
+ * SystemProperties.getInt("ro.build.version.sdk", 0)
+ + SystemProperties.getInt("ro.build.version.sdk_minor", 0);
+ }
/**
* The SDK version of the software that <em>initially</em> shipped on
@@ -1264,6 +1274,25 @@
}
/**
+ * Enumeration of the currently known SDK major and minor version codes.
+ * The numbers increase for every release, and are guaranteed to be ordered
+ * by the release date of each release. The actual values should be
+ * considered an implementation detail, and the current encoding scheme may
+ * change in the future.
+ *
+ * @see android.os.Build.VERSION#SDK_INT_FULL
+ */
+ @FlaggedApi(Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME)
+ @SuppressLint("AcronymName")
+ public static class VERSION_CODES_FULL {
+ private VERSION_CODES_FULL() {}
+
+ // Use the last 5 digits for the minor version. This allows the
+ // minor version to be set to CUR_DEVELOPMENT.
+ private static final int SDK_INT_MULTIPLIER = 100000;
+ }
+
+ /**
* The vendor API for 2024 Q2
*
* <p>For Android 14-QPR3 and later, the vendor API level is completely decoupled from the SDK
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index beb9a93..2d3dd1b 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -598,6 +598,11 @@
final String abi = chooseAbi(angleInfo);
// Build a path that includes installed native libs and APK
+ // TODO (b/370113081): If the native libraries are not found in this path,
+ // the system libraries will be loaded instead.
+ // This can happen if the ANGLE APK is present,
+ // but accidentally packaged without native libraries.
+ // TBD if this should fail instead of falling back to the system version.
final String paths = angleInfo.nativeLibraryDir
+ File.pathSeparator
+ angleInfo.sourceDir
diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java
index 2cb86f7..769cbdd 100644
--- a/core/java/android/os/RemoteCallbackList.java
+++ b/core/java/android/os/RemoteCallbackList.java
@@ -92,9 +92,9 @@
/**
* Add a new callback to the list. This callback will remain in the list
* until a corresponding call to {@link #unregister} or its hosting process
- * goes away. If the callback was already registered (determined by
+ * goes away. If the callback was already registered (determined by
* checking to see if the {@link IInterface#asBinder callback.asBinder()}
- * object is already in the list), then it will be left as-is.
+ * object is already in the list), then it will be replaced with the new callback.
* Registrations are not counted; a single call to {@link #unregister}
* will remove a callback after any number calls to register it.
*
@@ -106,7 +106,7 @@
*
* @param cookie Optional additional data to be associated with this
* callback.
- *
+ *
* @return Returns true if the callback was successfully added to the list.
* Returns false if it was not added, either because {@link #kill} had
* previously been called or the callback's process has gone away.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d82af55..a2c41c1 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2092,23 +2092,6 @@
public static final String ACTION_ZEN_MODE_SETTINGS = "android.settings.ZEN_MODE_SETTINGS";
/**
- * Activity Action: Show Zen Mode visual effects configuration settings.
- *
- * @hide
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ZEN_MODE_BLOCKED_EFFECTS_SETTINGS =
- "android.settings.ZEN_MODE_BLOCKED_EFFECTS_SETTINGS";
-
- /**
- * Activity Action: Show Zen Mode onboarding activity.
- *
- * @hide
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ZEN_MODE_ONBOARDING = "android.settings.ZEN_MODE_ONBOARDING";
-
- /**
* Activity Action: Show Zen Mode (aka Do Not Disturb) priority configuration settings.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@@ -8748,35 +8731,6 @@
/** @hide */ public static final int ZEN_DURATION_FOREVER = 0;
/**
- * If nonzero, will show the zen upgrade notification when the user toggles DND on/off.
- * @hide
- */
- @Readable
- public static final String SHOW_ZEN_UPGRADE_NOTIFICATION = "show_zen_upgrade_notification";
-
- /**
- * If nonzero, will show the zen update settings suggestion.
- * @hide
- */
- @Readable
- public static final String SHOW_ZEN_SETTINGS_SUGGESTION = "show_zen_settings_suggestion";
-
- /**
- * If nonzero, zen has not been updated to reflect new changes.
- * @hide
- */
- @Readable
- public static final String ZEN_SETTINGS_UPDATED = "zen_settings_updated";
-
- /**
- * If nonzero, zen setting suggestion has been viewed by user
- * @hide
- */
- @Readable
- public static final String ZEN_SETTINGS_SUGGESTION_VIEWED =
- "zen_settings_suggestion_viewed";
-
- /**
* Whether the in call notification is enabled to play sound during calls. The value is
* boolean (1 or 0).
* @hide
@@ -18072,10 +18026,6 @@
MOVED_TO_SECURE = new HashSet<>(8);
MOVED_TO_SECURE.add(Global.INSTALL_NON_MARKET_APPS);
MOVED_TO_SECURE.add(Global.ZEN_DURATION);
- MOVED_TO_SECURE.add(Global.SHOW_ZEN_UPGRADE_NOTIFICATION);
- MOVED_TO_SECURE.add(Global.SHOW_ZEN_SETTINGS_SUGGESTION);
- MOVED_TO_SECURE.add(Global.ZEN_SETTINGS_UPDATED);
- MOVED_TO_SECURE.add(Global.ZEN_SETTINGS_SUGGESTION_VIEWED);
MOVED_TO_SECURE.add(Global.CHARGING_SOUNDS_ENABLED);
MOVED_TO_SECURE.add(Global.CHARGING_VIBRATION_ENABLED);
MOVED_TO_SECURE.add(Global.NOTIFICATION_BUBBLES);
@@ -18910,40 +18860,6 @@
@Readable
public static final String SHOW_MUTE_IN_CRASH_DIALOG = "show_mute_in_crash_dialog";
-
- /**
- * If nonzero, will show the zen upgrade notification when the user toggles DND on/off.
- * @hide
- * @deprecated - Use {@link android.provider.Settings.Secure#SHOW_ZEN_UPGRADE_NOTIFICATION}
- */
- @Deprecated
- public static final String SHOW_ZEN_UPGRADE_NOTIFICATION = "show_zen_upgrade_notification";
-
- /**
- * If nonzero, will show the zen update settings suggestion.
- * @hide
- * @deprecated - Use {@link android.provider.Settings.Secure#SHOW_ZEN_SETTINGS_SUGGESTION}
- */
- @Deprecated
- public static final String SHOW_ZEN_SETTINGS_SUGGESTION = "show_zen_settings_suggestion";
-
- /**
- * If nonzero, zen has not been updated to reflect new changes.
- * @deprecated - Use {@link android.provider.Settings.Secure#ZEN_SETTINGS_UPDATED}
- * @hide
- */
- @Deprecated
- public static final String ZEN_SETTINGS_UPDATED = "zen_settings_updated";
-
- /**
- * If nonzero, zen setting suggestion has been viewed by user
- * @hide
- * @deprecated - Use {@link android.provider.Settings.Secure#ZEN_SETTINGS_SUGGESTION_VIEWED}
- */
- @Deprecated
- public static final String ZEN_SETTINGS_SUGGESTION_VIEWED =
- "zen_settings_suggestion_viewed";
-
/**
* Backup and restore agent timeout parameters.
* These parameters are represented by a comma-delimited key-value list.
diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java
index a9c5790..c9f4647 100644
--- a/core/java/android/service/notification/ZenModeDiff.java
+++ b/core/java/android/service/notification/ZenModeDiff.java
@@ -25,6 +25,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.LinkedHashMap;
import java.util.Objects;
import java.util.Set;
@@ -63,6 +64,7 @@
public static class FieldDiff<T> {
private final T mFrom;
private final T mTo;
+ private final BaseDiff mDetailedDiff;
/**
* Constructor to create a FieldDiff object with the given values.
@@ -72,6 +74,19 @@
public FieldDiff(@Nullable T from, @Nullable T to) {
mFrom = from;
mTo = to;
+ mDetailedDiff = null;
+ }
+
+ /**
+ * Constructor to create a FieldDiff object with the given values, and that has a
+ * detailed BaseDiff.
+ * @param from from (old) value
+ * @param to to (new) value
+ */
+ public FieldDiff(@Nullable T from, @Nullable T to, @Nullable BaseDiff detailedDiff) {
+ mFrom = from;
+ mTo = to;
+ mDetailedDiff = detailedDiff;
}
/**
@@ -93,6 +108,9 @@
*/
@Override
public String toString() {
+ if (mDetailedDiff != null) {
+ return mDetailedDiff.toString();
+ }
return mFrom + "->" + mTo;
}
@@ -100,6 +118,9 @@
* Returns whether this represents an actual diff.
*/
public boolean hasDiff() {
+ if (mDetailedDiff != null) {
+ return mDetailedDiff.hasDiff();
+ }
// note that Objects.equals handles null values gracefully.
return !Objects.equals(mFrom, mTo);
}
@@ -115,7 +136,8 @@
@ExistenceChange private int mExists = NONE;
// Map from field name to diffs for any standalone fields in the object.
- private ArrayMap<String, FieldDiff> mFields = new ArrayMap<>();
+ // LinkedHashMap is specifically chosen here to show insertion order when keys are fetched.
+ private LinkedHashMap<String, FieldDiff> mFields = new LinkedHashMap<>();
// Functions for actually diffing objects and string representations have to be implemented
// by subclasses.
@@ -550,8 +572,16 @@
if (!Objects.equals(from.enabler, to.enabler)) {
addField(FIELD_ENABLER, new FieldDiff<>(from.enabler, to.enabler));
}
- if (!Objects.equals(from.zenPolicy, to.zenPolicy)) {
- addField(FIELD_ZEN_POLICY, new FieldDiff<>(from.zenPolicy, to.zenPolicy));
+ if (android.app.Flags.modesApi()) {
+ PolicyDiff policyDiff = new PolicyDiff(from.zenPolicy, to.zenPolicy);
+ if (policyDiff.hasDiff()) {
+ addField(FIELD_ZEN_POLICY, new FieldDiff<>(from.zenPolicy, to.zenPolicy,
+ policyDiff));
+ }
+ } else {
+ if (!Objects.equals(from.zenPolicy, to.zenPolicy)) {
+ addField(FIELD_ZEN_POLICY, new FieldDiff<>(from.zenPolicy, to.zenPolicy));
+ }
}
if (from.modified != to.modified) {
addField(FIELD_MODIFIED, new FieldDiff<>(from.modified, to.modified));
@@ -560,9 +590,12 @@
addField(FIELD_PKG, new FieldDiff<>(from.pkg, to.pkg));
}
if (android.app.Flags.modesApi()) {
- if (!Objects.equals(from.zenDeviceEffects, to.zenDeviceEffects)) {
+ DeviceEffectsDiff deviceEffectsDiff = new DeviceEffectsDiff(from.zenDeviceEffects,
+ to.zenDeviceEffects);
+ if (deviceEffectsDiff.hasDiff()) {
addField(FIELD_ZEN_DEVICE_EFFECTS,
- new FieldDiff<>(from.zenDeviceEffects, to.zenDeviceEffects));
+ new FieldDiff<>(from.zenDeviceEffects, to.zenDeviceEffects,
+ deviceEffectsDiff));
}
if (!Objects.equals(from.triggerDescription, to.triggerDescription)) {
addField(FIELD_TRIGGER_DESCRIPTION,
@@ -630,7 +663,7 @@
sb.append(key);
sb.append(":");
- sb.append(diff);
+ sb.append(diff.toString());
}
if (becameActive()) {
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 3599332..ec5b488 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -178,3 +178,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "handwriting_unsupported_show_soft_input_fix"
+ namespace: "text"
+ description: "Don't show soft keyboard on stylus input if text field doesn't support handwriting and getShowSoftInputOnFocus() returns false."
+ bug: "363180475"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/tracing/TEST_MAPPING b/core/java/android/tracing/TEST_MAPPING
new file mode 100644
index 0000000..b51d19d
--- /dev/null
+++ b/core/java/android/tracing/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+ "postsubmit": [
+ {
+ "name": "TracingTests"
+ },
+ {
+ "name": "ProtologPerfTests"
+ }
+ ]
+}
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index f132963..c217999 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -19,6 +19,7 @@
import static com.android.text.flags.Flags.handwritingCursorPosition;
import static com.android.text.flags.Flags.handwritingTrackDisabled;
import static com.android.text.flags.Flags.handwritingUnsupportedMessage;
+import static com.android.text.flags.Flags.handwritingUnsupportedShowSoftInputFix;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
@@ -241,7 +242,11 @@
if (!candidateView.hasFocus()) {
requestFocusWithoutReveal(candidateView);
}
- mImm.showSoftInput(candidateView, 0);
+ if (!handwritingUnsupportedShowSoftInputFix()
+ || (candidateView instanceof TextView tv
+ && tv.getShowSoftInputOnFocus())) {
+ mImm.showSoftInput(candidateView, 0);
+ }
mState.mHandled = true;
mState.mShouldInitHandwriting = false;
motionEvent.setAction((motionEvent.getAction()
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 40a75fd..8fb17c7 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -238,7 +238,7 @@
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
-import android.view.accessibility.AccessibilityManager.HighTextContrastChangeListener;
+import android.view.accessibility.AccessibilityManager.HighContrastTextStateChangeListener;
import android.view.accessibility.AccessibilityNodeIdManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -1800,8 +1800,8 @@
}
mAccessibilityManager.addAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager, mHandler);
- mAccessibilityManager.addHighTextContrastStateChangeListener(
- mHighContrastTextManager, mHandler);
+ mAccessibilityManager.addHighContrastTextStateChangeListener(
+ mExecutor, mHighContrastTextManager);
DisplayManagerGlobal
.getInstance()
.registerDisplayListener(
@@ -1838,7 +1838,7 @@
private void unregisterListeners() {
mAccessibilityManager.removeAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager);
- mAccessibilityManager.removeHighTextContrastStateChangeListener(
+ mAccessibilityManager.removeHighContrastTextStateChangeListener(
mHighContrastTextManager);
DisplayManagerGlobal
.getInstance()
@@ -11907,12 +11907,12 @@
}
}
- final class HighContrastTextManager implements HighTextContrastChangeListener {
+ final class HighContrastTextManager implements HighContrastTextStateChangeListener {
HighContrastTextManager() {
- ThreadedRenderer.setHighContrastText(mAccessibilityManager.isHighTextContrastEnabled());
+ ThreadedRenderer.setHighContrastText(mAccessibilityManager.isHighContrastTextEnabled());
}
@Override
- public void onHighTextContrastStateChanged(boolean enabled) {
+ public void onHighContrastTextStateChanged(boolean enabled) {
ThreadedRenderer.setHighContrastText(enabled);
destroyAndInvalidate();
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 0582afe..381006c 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1345,7 +1345,7 @@
}
/**
- * <p>Sets the desired about of HDR headroom to be used when rendering as a ratio of
+ * <p>Sets the desired amount of HDR headroom to be used when rendering as a ratio of
* targetHdrPeakBrightnessInNits / targetSdrWhitePointInNits. Only applies when
* {@link #setColorMode(int)} is {@link ActivityInfo#COLOR_MODE_HDR}</p>
*
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 2b7cf42..fd57aec 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -253,7 +253,7 @@
boolean mIsTouchExplorationEnabled;
@UnsupportedAppUsage(trackingBug = 123768939L)
- boolean mIsHighTextContrastEnabled;
+ boolean mIsHighContrastTextEnabled;
boolean mIsAudioDescriptionByDefaultRequested;
@@ -276,8 +276,8 @@
private final ArrayMap<TouchExplorationStateChangeListener, Handler>
mTouchExplorationStateChangeListeners = new ArrayMap<>();
- private final ArrayMap<HighTextContrastChangeListener, Handler>
- mHighTextContrastStateChangeListeners = new ArrayMap<>();
+ private final ArrayMap<HighContrastTextStateChangeListener, Executor>
+ mHighContrastTextStateChangeListeners = new ArrayMap<>();
private final ArrayMap<AccessibilityServicesStateChangeListener, Executor>
mServicesStateChangeListeners = new ArrayMap<>();
@@ -356,21 +356,20 @@
}
/**
- * Listener for the system high text contrast state. To listen for changes to
- * the high text contrast state on the device, implement this interface and
+ * Listener for the system high contrast text state. To listen for changes to
+ * the high contrast text state on the device, implement this interface and
* register it with the system by calling
- * {@link #addHighTextContrastStateChangeListener}.
- *
- * @hide
+ * {@link #addHighContrastTextStateChangeListener}.
*/
- public interface HighTextContrastChangeListener {
+ @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public interface HighContrastTextStateChangeListener {
/**
- * Called when the high text contrast enabled state changes.
+ * Called when the high contrast text enabled state changes.
*
- * @param enabled Whether high text contrast is enabled.
+ * @param enabled Whether high contrast text is enabled.
*/
- void onHighTextContrastStateChanged(boolean enabled);
+ void onHighContrastTextStateChanged(boolean enabled);
}
/**
@@ -655,24 +654,23 @@
}
/**
- * Returns if the high text contrast in the system is enabled.
+ * Returns if high contrast text in the system is enabled.
* <p>
* <strong>Note:</strong> You need to query this only if you application is
* doing its own rendering and does not rely on the platform rendering pipeline.
* </p>
*
- * @return True if high text contrast is enabled, false otherwise.
+ * @return True if high contrast text is enabled, false otherwise.
*
- * @hide
*/
- @UnsupportedAppUsage
- public boolean isHighTextContrastEnabled() {
+ @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public boolean isHighContrastTextEnabled() {
synchronized (mLock) {
IAccessibilityManager service = getServiceLocked();
if (service == null) {
return false;
}
- return mIsHighTextContrastEnabled;
+ return mIsHighContrastTextEnabled;
}
}
@@ -1303,32 +1301,32 @@
}
/**
- * Registers a {@link HighTextContrastChangeListener} for changes in
- * the global high text contrast state of the system.
+ * Registers a {@link HighContrastTextStateChangeListener} for changes in
+ * the global high contrast text state of the system.
*
- * @param listener The listener.
- *
- * @hide
+ * @param executor a executor to call the listener from
+ * @param listener The listener to be called
*/
- public void addHighTextContrastStateChangeListener(
- @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {
+ @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void addHighContrastTextStateChangeListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull HighContrastTextStateChangeListener listener
+ ) {
synchronized (mLock) {
- mHighTextContrastStateChangeListeners
- .put(listener, (handler == null) ? mHandler : handler);
+ mHighContrastTextStateChangeListeners.put(listener, executor);
}
}
/**
- * Unregisters a {@link HighTextContrastChangeListener}.
+ * Unregisters a {@link HighContrastTextStateChangeListener}.
*
* @param listener The listener.
- *
- * @hide
*/
- public void removeHighTextContrastStateChangeListener(
- @NonNull HighTextContrastChangeListener listener) {
+ @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void removeHighContrastTextStateChangeListener(
+ @NonNull HighContrastTextStateChangeListener listener) {
synchronized (mLock) {
- mHighTextContrastStateChangeListeners.remove(listener);
+ mHighContrastTextStateChangeListeners.remove(listener);
}
}
@@ -1505,13 +1503,13 @@
final boolean wasEnabled = isEnabled();
final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
- final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled;
+ final boolean wasHighTextContrastEnabled = mIsHighContrastTextEnabled;
final boolean wasAudioDescriptionByDefaultRequested = mIsAudioDescriptionByDefaultRequested;
// Ensure listeners get current state from isZzzEnabled() calls.
mIsEnabled = enabled;
mIsTouchExplorationEnabled = touchExplorationEnabled;
- mIsHighTextContrastEnabled = highTextContrastEnabled;
+ mIsHighContrastTextEnabled = highTextContrastEnabled;
mIsAudioDescriptionByDefaultRequested = audioDescriptionEnabled;
if (wasEnabled != isEnabled()) {
@@ -1523,7 +1521,7 @@
}
if (wasHighTextContrastEnabled != highTextContrastEnabled) {
- notifyHighTextContrastStateChanged();
+ notifyHighContrastTextStateChanged();
}
if (wasAudioDescriptionByDefaultRequested
@@ -2397,24 +2395,24 @@
}
/**
- * Notifies the registered {@link HighTextContrastChangeListener}s.
+ * Notifies the registered {@link HighContrastTextStateChangeListener}s.
*/
- private void notifyHighTextContrastStateChanged() {
+ private void notifyHighContrastTextStateChanged() {
final boolean isHighTextContrastEnabled;
- final ArrayMap<HighTextContrastChangeListener, Handler> listeners;
+ final ArrayMap<HighContrastTextStateChangeListener, Executor> listeners;
synchronized (mLock) {
- if (mHighTextContrastStateChangeListeners.isEmpty()) {
+ if (mHighContrastTextStateChangeListeners.isEmpty()) {
return;
}
- isHighTextContrastEnabled = mIsHighTextContrastEnabled;
- listeners = new ArrayMap<>(mHighTextContrastStateChangeListeners);
+ isHighTextContrastEnabled = mIsHighContrastTextEnabled;
+ listeners = new ArrayMap<>(mHighContrastTextStateChangeListeners);
}
final int numListeners = listeners.size();
for (int i = 0; i < numListeners; i++) {
- final HighTextContrastChangeListener listener = listeners.keyAt(i);
- listeners.valueAt(i).post(() ->
- listener.onHighTextContrastStateChanged(isHighTextContrastEnabled));
+ final HighContrastTextStateChangeListener listener = listeners.keyAt(i);
+ listeners.valueAt(i).execute(() ->
+ listener.onHighContrastTextStateChanged(isHighTextContrastEnabled));
}
}
diff --git a/core/java/android/view/autofill/AutofillStateFingerprint.java b/core/java/android/view/autofill/AutofillStateFingerprint.java
index 2db4285..7f3858e 100644
--- a/core/java/android/view/autofill/AutofillStateFingerprint.java
+++ b/core/java/android/view/autofill/AutofillStateFingerprint.java
@@ -97,7 +97,6 @@
if (sDebug) {
Log.d(TAG, "Autofillable views count prior to auth:" + autofillableViews.size());
}
-// ArrayList<Integer> hashes = getFingerprintIds(autofillableViews);
ArrayMap<Integer, View> hashes = getFingerprintIds(autofillableViews);
for (Map.Entry<Integer, View> entry : hashes.entrySet()) {
@@ -123,7 +122,6 @@
if (view != null) {
int id = getEphemeralFingerprintId(view, 0 /* position irrelevant */);
AutofillId autofillId = view.getAutofillId();
- autofillId.setSessionId(mSessionId);
mHashToAutofillIdMap.put(id, autofillId);
} else {
if (sDebug) {
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index fbc30ed..b22aa22 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -162,6 +162,13 @@
}
flag {
+ name: "enable_a11y_metrics"
+ namespace: "lse_desktop_experience"
+ description: "Whether to enable log collection for a11y actions in desktop windowing mode"
+ bug: "341319597"
+}
+
+flag {
name: "enable_caption_compat_inset_force_consumption"
namespace: "lse_desktop_experience"
description: "Enables force-consumption of caption bar insets for immersive apps in freeform"
@@ -306,3 +313,10 @@
description: "Allow entering desktop mode by default on freeform displays"
bug: "361419732"
}
+
+flag {
+ name: "enable_desktop_app_launch_alttab_transitions"
+ namespace: "lse_desktop_experience"
+ description: "Enables custom transitions for alt-tab app launches in Desktop Mode."
+ bug: "370735595"
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java
index fef5e83..4aebde5 100644
--- a/core/java/com/android/internal/notification/SystemNotificationChannels.java
+++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java
@@ -26,6 +26,7 @@
import android.os.RemoteException;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Arrays;
@@ -37,38 +38,40 @@
* @deprecated Legacy system channel, which is no longer used,
*/
@Deprecated public static String VIRTUAL_KEYBOARD = "VIRTUAL_KEYBOARD";
- public static String PHYSICAL_KEYBOARD = "PHYSICAL_KEYBOARD";
- public static String SECURITY = "SECURITY";
- public static String CAR_MODE = "CAR_MODE";
- public static String ACCOUNT = "ACCOUNT";
- public static String DEVELOPER = "DEVELOPER";
- public static String DEVELOPER_IMPORTANT = "DEVELOPER_IMPORTANT";
- public static String UPDATES = "UPDATES";
- public static String NETWORK_STATUS = "NETWORK_STATUS";
- public static String NETWORK_ALERTS = "NETWORK_ALERTS";
- public static String NETWORK_AVAILABLE = "NETWORK_AVAILABLE";
- public static String VPN = "VPN";
+ public static final String PHYSICAL_KEYBOARD = "PHYSICAL_KEYBOARD";
+ public static final String SECURITY = "SECURITY";
+ public static final String CAR_MODE = "CAR_MODE";
+ public static final String ACCOUNT = "ACCOUNT";
+ public static final String DEVELOPER = "DEVELOPER";
+ public static final String DEVELOPER_IMPORTANT = "DEVELOPER_IMPORTANT";
+ public static final String UPDATES = "UPDATES";
+ public static final String NETWORK_STATUS = "NETWORK_STATUS";
+ public static final String NETWORK_ALERTS = "NETWORK_ALERTS";
+ public static final String NETWORK_AVAILABLE = "NETWORK_AVAILABLE";
+ public static final String VPN = "VPN";
/**
* @deprecated Legacy device admin channel with low importance which is no longer used,
* Use the high importance {@link #DEVICE_ADMIN} channel instead.
*/
- @Deprecated public static String DEVICE_ADMIN_DEPRECATED = "DEVICE_ADMIN";
- public static String DEVICE_ADMIN = "DEVICE_ADMIN_ALERTS";
- public static String ALERTS = "ALERTS";
- public static String RETAIL_MODE = "RETAIL_MODE";
- public static String USB = "USB";
- public static String FOREGROUND_SERVICE = "FOREGROUND_SERVICE";
- public static String HEAVY_WEIGHT_APP = "HEAVY_WEIGHT_APP";
+ @Deprecated public static final String DEVICE_ADMIN_DEPRECATED = "DEVICE_ADMIN";
+ public static final String DEVICE_ADMIN = "DEVICE_ADMIN_ALERTS";
+ public static final String ALERTS = "ALERTS";
+ public static final String RETAIL_MODE = "RETAIL_MODE";
+ public static final String USB = "USB";
+ public static final String FOREGROUND_SERVICE = "FOREGROUND_SERVICE";
+ public static final String HEAVY_WEIGHT_APP = "HEAVY_WEIGHT_APP";
/**
* @deprecated Legacy system changes channel with low importance which is no longer used,
* Use the default importance {@link #SYSTEM_CHANGES} channel instead.
*/
- @Deprecated public static String SYSTEM_CHANGES_DEPRECATED = "SYSTEM_CHANGES";
- public static String SYSTEM_CHANGES = "SYSTEM_CHANGES_ALERTS";
- public static String DO_NOT_DISTURB = "DO_NOT_DISTURB";
- public static String ACCESSIBILITY_MAGNIFICATION = "ACCESSIBILITY_MAGNIFICATION";
- public static String ACCESSIBILITY_SECURITY_POLICY = "ACCESSIBILITY_SECURITY_POLICY";
- public static String ABUSIVE_BACKGROUND_APPS = "ABUSIVE_BACKGROUND_APPS";
+ @Deprecated public static final String SYSTEM_CHANGES_DEPRECATED = "SYSTEM_CHANGES";
+ public static final String SYSTEM_CHANGES = "SYSTEM_CHANGES_ALERTS";
+ public static final String ACCESSIBILITY_MAGNIFICATION = "ACCESSIBILITY_MAGNIFICATION";
+ public static final String ACCESSIBILITY_SECURITY_POLICY = "ACCESSIBILITY_SECURITY_POLICY";
+ public static final String ABUSIVE_BACKGROUND_APPS = "ABUSIVE_BACKGROUND_APPS";
+
+ @VisibleForTesting
+ static final String OBSOLETE_DO_NOT_DISTURB = "DO_NOT_DISTURB";
public static void createAll(Context context) {
final NotificationManager nm = context.getSystemService(NotificationManager.class);
@@ -193,11 +196,6 @@
.build());
channelsList.add(systemChanges);
- NotificationChannel dndChanges = new NotificationChannel(DO_NOT_DISTURB,
- context.getString(R.string.notification_channel_do_not_disturb),
- NotificationManager.IMPORTANCE_LOW);
- channelsList.add(dndChanges);
-
final NotificationChannel newFeaturePrompt = new NotificationChannel(
ACCESSIBILITY_MAGNIFICATION,
context.getString(R.string.notification_channel_accessibility_magnification),
@@ -218,6 +216,9 @@
channelsList.add(abusiveBackgroundAppsChannel);
nm.createNotificationChannels(channelsList);
+
+ // Delete channels created by previous Android versions that are no longer used.
+ nm.deleteNotificationChannel(OBSOLETE_DO_NOT_DISTURB);
}
private static String getDeviceAdminNotificationChannelName(Context context) {
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 4d0cd27..f3dc896 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -56,6 +56,7 @@
import android.tracing.perfetto.Producer;
import android.tracing.perfetto.TracingContext;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import android.util.LongArray;
import android.util.Slog;
@@ -351,6 +352,10 @@
}
private void registerGroupsLocally(@NonNull IProtoLogGroup[] protoLogGroups) {
+ // Verify we don't have id collisions, if we do we want to know as soon as possible and
+ // we might want to manually specify an id for the group with a collision
+ verifyNoCollisionsOrDuplicates(protoLogGroups);
+
final var groupsLoggingToLogcat = new ArrayList<String>();
for (IProtoLogGroup protoLogGroup : protoLogGroups) {
mLogGroups.put(protoLogGroup.name(), protoLogGroup);
@@ -369,6 +374,19 @@
}
}
+ private void verifyNoCollisionsOrDuplicates(@NonNull IProtoLogGroup[] protoLogGroups) {
+ final var groupId = new ArraySet<Integer>();
+
+ for (IProtoLogGroup protoLogGroup : protoLogGroups) {
+ if (groupId.contains(protoLogGroup.getId())) {
+ throw new RuntimeException(
+ "Group with same id (" + protoLogGroup.getId() + ") registered twice. "
+ + "Potential duplicate or hash id collision.");
+ }
+ groupId.add(protoLogGroup.getId());
+ }
+ }
+
/**
* Responds to a shell command.
*/
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index adf03fe..60213b1 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -22,8 +22,8 @@
import com.android.internal.protolog.common.IProtoLogGroup;
import com.android.internal.protolog.common.LogLevel;
-import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
/**
* ProtoLog API - exposes static logging methods. Usage of this API is similar
@@ -73,7 +73,7 @@
if (sProtoLogInstance != null) {
// The ProtoLog instance has already been initialized in this process
final var alreadyRegisteredGroups = sProtoLogInstance.getRegisteredGroups();
- final var allGroups = new ArrayList<>(alreadyRegisteredGroups);
+ final var allGroups = new HashSet<>(alreadyRegisteredGroups);
allGroups.addAll(Arrays.stream(groups).toList());
groups = allGroups.toArray(new IProtoLogGroup[0]);
}
diff --git a/core/java/com/android/internal/protolog/TEST_MAPPING b/core/java/com/android/internal/protolog/TEST_MAPPING
index 37d57ee..b51d19d 100644
--- a/core/java/com/android/internal/protolog/TEST_MAPPING
+++ b/core/java/com/android/internal/protolog/TEST_MAPPING
@@ -1,6 +1,9 @@
{
"postsubmit": [
{
+ "name": "TracingTests"
+ },
+ {
"name": "ProtologPerfTests"
}
]
diff --git a/core/jni/jni_wrappers.h b/core/jni/jni_wrappers.h
index 21b5b13..e3e17ee 100644
--- a/core/jni/jni_wrappers.h
+++ b/core/jni/jni_wrappers.h
@@ -79,13 +79,14 @@
jniMethodFormat = value;
}
-// Potentially translates the given JNINativeMethods if setJniMethodFormat has been set.
-// Has no effect otherwise
-inline const JNINativeMethod* maybeRenameJniMethods(const JNINativeMethod* gMethods,
- int numMethods) {
+// Register the native methods, potenially applying the jniMethodFormat if it has been set.
+static inline int jniRegisterMaybeRenamedNativeMethods(JNIEnv* env, const char* className,
+ const JNINativeMethod* gMethods,
+ int numMethods) {
if (jniMethodFormat.empty()) {
- return gMethods;
+ return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
+
// Make a copy of gMethods with reformatted method names.
JNINativeMethod* modifiedMethods = new JNINativeMethod[numMethods];
LOG_ALWAYS_FATAL_IF(!modifiedMethods, "Failed to allocate a copy of the JNI methods");
@@ -103,13 +104,17 @@
std::strcpy(modifiedNameChars, modifiedName.c_str());
modifiedMethods[i].name = modifiedNameChars;
}
- return modifiedMethods;
+ int res = jniRegisterNativeMethods(env, className, modifiedMethods, numMethods);
+ for (int i = 0; i < numMethods; i++) {
+ delete[] modifiedMethods[i].name;
+ }
+ delete[] modifiedMethods;
+ return res;
}
static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods) {
- const JNINativeMethod* modifiedMethods = maybeRenameJniMethods(gMethods, numMethods);
- int res = jniRegisterNativeMethods(env, className, modifiedMethods, numMethods);
+ int res = jniRegisterMaybeRenamedNativeMethods(env, className, gMethods, numMethods);
LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
return res;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d35c66e..ed33ede 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8449,6 +8449,29 @@
<permission android:name="android.permission.RESERVED_FOR_TESTING_SIGNATURE"
android:protectionLevel="signature"/>
+ <!-- @SystemApi
+ @FlaggedApi("android.content.pm.verification_service")
+ Allows app to be the verification agent to verify packages.
+ <p>Protection level: signature|privileged
+ @hide
+ -->
+ <permission android:name="android.permission.VERIFICATION_AGENT"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.content.pm.verification_service" />
+
+ <!-- @SystemApi
+ @FlaggedApi("android.content.pm.verification_service")
+ Must be required by a privileged {@link android.content.pm.verify.pkg.VerifierService}
+ to ensure that only the system can bind to it.
+ This permission should not be held by anything other than the system.
+ <p>Not for use by third-party applications. </p>
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_VERIFICATION_AGENT"
+ android:protectionLevel="internal"
+ android:featureFlag="android.content.pm.verification_service" />
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5c0dca2..1a3a30d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1250,6 +1250,7 @@
a watch, setting this config is no-op.
0 - Nothing
1 - Switch to the recent app
+ 2 - Launch the default fitness app
-->
<integer name="config_doublePressOnStemPrimaryBehavior">0</integer>
@@ -2309,10 +2310,6 @@
spatial audio is enabled for a newly connected audio device -->
<bool name="config_spatial_audio_head_tracking_enabled_default">false</bool>
- <!-- Flag indicating whether platform level volume adjustments are enabled for remote sessions
- on grouped devices. -->
- <bool name="config_volumeAdjustmentForRemoteGroupSessions">true</bool>
-
<!-- Flag indicating current media Output Switcher version. -->
<integer name="config_mediaOutputSwitchDialogVersion">1</integer>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d634210..7aca535 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5818,16 +5818,6 @@
<!-- Title for the notification channel notifying user of settings system changes. [CHAR LIMIT=NONE] -->
<string name="notification_channel_system_changes">System changes</string>
- <!-- Title for the notification channel notifying user of do not disturb system changes (i.e. Do Not Disturb has changed). [CHAR LIMIT=NONE] -->
- <string name="notification_channel_do_not_disturb">Do Not Disturb</string>
- <!-- Title of notification indicating do not disturb visual interruption settings have changed when upgrading to P -->
- <string name="zen_upgrade_notification_visd_title">New: Do Not Disturb is hiding notifications</string>
- <!-- Content of notification indicating users can tap on the notification to go to dnd behavior settings -->
- <string name="zen_upgrade_notification_visd_content">Tap to learn more and change.</string>
- <!-- Title of notification indicating do not disturb settings have changed when upgrading to P -->
- <string name="zen_upgrade_notification_title">Do Not Disturb has changed</string>
- <!-- Content of notification indicating users can tap on the notification to go to dnd behavior settings -->
- <string name="zen_upgrade_notification_content">Tap to check what\'s blocked.</string>
<!-- Notification permission informational notification text -->
<!-- Title for notification inviting users to review their notification settings [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 807df1b..d5298ac 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3937,7 +3937,6 @@
<java-symbol type="string" name="notification_channel_usb" />
<java-symbol type="string" name="notification_channel_heavy_weight_app" />
<java-symbol type="string" name="notification_channel_system_changes" />
- <java-symbol type="string" name="notification_channel_do_not_disturb" />
<java-symbol type="string" name="notification_channel_accessibility_magnification" />
<java-symbol type="string" name="notification_channel_accessibility_security_policy" />
<java-symbol type="string" name="notification_channel_display" />
@@ -4164,11 +4163,6 @@
<!-- For Wear devices -->
<java-symbol type="array" name="config_wearActivityModeRadios" />
- <java-symbol type="string" name="zen_upgrade_notification_title" />
- <java-symbol type="string" name="zen_upgrade_notification_content" />
- <java-symbol type="string" name="zen_upgrade_notification_visd_title" />
- <java-symbol type="string" name="zen_upgrade_notification_visd_content" />
-
<java-symbol type="string" name="review_notification_settings_title" />
<java-symbol type="string" name="review_notification_settings_text" />
<java-symbol type="string" name="review_notification_settings_remind_me_action" />
@@ -5102,8 +5096,6 @@
<java-symbol type="dimen" name="config_wallpaperDimAmount" />
- <java-symbol type="bool" name="config_volumeAdjustmentForRemoteGroupSessions" />
-
<java-symbol type="integer" name="config_mediaOutputSwitchDialogVersion" />
<!-- List of shared library packages that should be loaded by the classloader after the
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 9821d43..56e18e6 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -250,7 +250,7 @@
"androidx.test.rules",
"androidx.test.ext.junit",
"androidx.test.uiautomator_uiautomator",
- "compatibility-device-util-axt",
+ "compatibility-device-util-axt-ravenwood",
"flag-junit",
"platform-test-annotations",
"flag-junit",
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index e9b137c..48e2620 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -2301,13 +2301,13 @@
@Test
@EnableFlags(Flags.FLAG_API_RICH_ONGOING)
- public void progressStyle_onProgressStepChange_visiblyDifferent() {
+ public void progressStyle_onProgressPointChange_visiblyDifferent() {
final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
.setStyle(new Notification.ProgressStyle()
- .addProgressStep(new Notification.ProgressStyle.Step(10)));
+ .addProgressPoint(new Notification.ProgressStyle.Point(10)));
final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
.setStyle(new Notification.ProgressStyle()
- .addProgressStep(new Notification.ProgressStyle.Step(12)));
+ .addProgressPoint(new Notification.ProgressStyle.Point(12)));
assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
.isTrue();
@@ -2315,13 +2315,13 @@
@Test
@EnableFlags(Flags.FLAG_API_RICH_ONGOING)
- public void indeterminateProgressStyle_onProgressStepChange_visiblyNotDifferent() {
+ public void indeterminateProgressStyle_onProgressPointChange_visiblyNotDifferent() {
final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
.setStyle(new Notification.ProgressStyle().setProgressIndeterminate(true)
- .addProgressStep(new Notification.ProgressStyle.Step(10)));
+ .addProgressPoint(new Notification.ProgressStyle.Point(10)));
final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
.setStyle(new Notification.ProgressStyle().setProgressIndeterminate(true)
- .addProgressStep(new Notification.ProgressStyle.Step(12)));
+ .addProgressPoint(new Notification.ProgressStyle.Point(12)));
assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
.isFalse();
diff --git a/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java b/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java
new file mode 100644
index 0000000..987f68d
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.SigningInfo;
+import android.content.pm.VersionedPackage;
+import android.content.pm.verify.pkg.IVerificationSessionCallback;
+import android.content.pm.verify.pkg.IVerificationSessionInterface;
+import android.content.pm.verify.pkg.VerificationSession;
+import android.content.pm.verify.pkg.VerificationStatus;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.PersistableBundle;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VerificationSessionTest {
+ private static final int TEST_ID = 100;
+ private static final int TEST_INSTALL_SESSION_ID = 33;
+ private static final String TEST_PACKAGE_NAME = "com.foo";
+ private static final Uri TEST_PACKAGE_URI = Uri.parse("test://test");
+ private static final SigningInfo TEST_SIGNING_INFO = new SigningInfo();
+ private static final SharedLibraryInfo TEST_SHARED_LIBRARY_INFO1 =
+ new SharedLibraryInfo("sharedLibPath1", TEST_PACKAGE_NAME,
+ Collections.singletonList("path1"), "sharedLib1", 101,
+ SharedLibraryInfo.TYPE_DYNAMIC, new VersionedPackage(TEST_PACKAGE_NAME, 1),
+ null, null, false);
+ private static final SharedLibraryInfo TEST_SHARED_LIBRARY_INFO2 =
+ new SharedLibraryInfo("sharedLibPath2", TEST_PACKAGE_NAME,
+ Collections.singletonList("path2"), "sharedLib2", 102,
+ SharedLibraryInfo.TYPE_DYNAMIC,
+ new VersionedPackage(TEST_PACKAGE_NAME, 2), null, null, false);
+ private static final long TEST_TIMEOUT_TIME = System.currentTimeMillis();
+ private static final long TEST_EXTEND_TIME = 2000L;
+ private static final String TEST_KEY = "test key";
+ private static final String TEST_VALUE = "test value";
+
+ private final ArrayList<SharedLibraryInfo> mTestDeclaredLibraries = new ArrayList<>();
+ private final PersistableBundle mTestExtensionParams = new PersistableBundle();
+ @Mock
+ private IVerificationSessionInterface mTestSessionInterface;
+ @Mock
+ private IVerificationSessionCallback mTestCallback;
+ private VerificationSession mTestSession;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mTestDeclaredLibraries.add(TEST_SHARED_LIBRARY_INFO1);
+ mTestDeclaredLibraries.add(TEST_SHARED_LIBRARY_INFO2);
+ mTestExtensionParams.putString(TEST_KEY, TEST_VALUE);
+ mTestSession = new VerificationSession(TEST_ID, TEST_INSTALL_SESSION_ID,
+ TEST_PACKAGE_NAME, TEST_PACKAGE_URI, TEST_SIGNING_INFO, mTestDeclaredLibraries,
+ mTestExtensionParams, mTestSessionInterface, mTestCallback);
+ }
+
+ @Test
+ public void testParcel() {
+ Parcel parcel = Parcel.obtain();
+ mTestSession.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ VerificationSession sessionFromParcel =
+ VerificationSession.CREATOR.createFromParcel(parcel);
+ assertThat(sessionFromParcel.getId()).isEqualTo(TEST_ID);
+ assertThat(sessionFromParcel.getInstallSessionId()).isEqualTo(TEST_INSTALL_SESSION_ID);
+ assertThat(sessionFromParcel.getPackageName()).isEqualTo(TEST_PACKAGE_NAME);
+ assertThat(sessionFromParcel.getStagedPackageUri()).isEqualTo(TEST_PACKAGE_URI);
+ assertThat(sessionFromParcel.getSigningInfo().getSigningDetails())
+ .isEqualTo(TEST_SIGNING_INFO.getSigningDetails());
+ List<SharedLibraryInfo> declaredLibrariesFromParcel =
+ sessionFromParcel.getDeclaredLibraries();
+ assertThat(declaredLibrariesFromParcel).hasSize(2);
+ // SharedLibraryInfo doesn't have a "equals" method, so we have to check it indirectly
+ assertThat(declaredLibrariesFromParcel.getFirst().toString())
+ .isEqualTo(TEST_SHARED_LIBRARY_INFO1.toString());
+ assertThat(declaredLibrariesFromParcel.get(1).toString())
+ .isEqualTo(TEST_SHARED_LIBRARY_INFO2.toString());
+ // We can't directly test with PersistableBundle.equals() because the parceled bundle's
+ // structure is different, but all the key/value pairs should be preserved as before.
+ assertThat(sessionFromParcel.getExtensionParams().getString(TEST_KEY))
+ .isEqualTo(mTestExtensionParams.getString(TEST_KEY));
+ }
+
+ @Test
+ public void testInterface() throws Exception {
+ when(mTestSessionInterface.getTimeoutTime(anyInt())).thenAnswer(i -> TEST_TIMEOUT_TIME);
+ when(mTestSessionInterface.extendTimeRemaining(anyInt(), anyLong())).thenAnswer(
+ i -> i.getArguments()[1]);
+
+ assertThat(mTestSession.getTimeoutTime()).isEqualTo(TEST_TIMEOUT_TIME);
+ verify(mTestSessionInterface, times(1)).getTimeoutTime(eq(TEST_ID));
+ assertThat(mTestSession.extendTimeRemaining(TEST_EXTEND_TIME)).isEqualTo(TEST_EXTEND_TIME);
+ verify(mTestSessionInterface, times(1)).extendTimeRemaining(
+ eq(TEST_ID), eq(TEST_EXTEND_TIME));
+ }
+
+ @Test
+ public void testCallback() throws Exception {
+ PersistableBundle response = new PersistableBundle();
+ response.putString("test key", "test value");
+ final VerificationStatus status =
+ new VerificationStatus.Builder().setVerified(true).build();
+ mTestSession.reportVerificationComplete(status);
+ verify(mTestCallback, times(1)).reportVerificationComplete(
+ eq(TEST_ID), eq(status));
+ mTestSession.reportVerificationComplete(status, response);
+ verify(mTestCallback, times(1))
+ .reportVerificationCompleteWithExtensionResponse(
+ eq(TEST_ID), eq(status), eq(response));
+
+ final int reason = VerificationSession.VERIFICATION_INCOMPLETE_UNKNOWN;
+ mTestSession.reportVerificationIncomplete(reason);
+ verify(mTestCallback, times(1)).reportVerificationIncomplete(
+ eq(TEST_ID), eq(reason));
+ }
+}
diff --git a/core/tests/coretests/src/android/content/pm/verify/VerificationStatusTest.java b/core/tests/coretests/src/android/content/pm/verify/VerificationStatusTest.java
new file mode 100644
index 0000000..67d407a
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/verify/VerificationStatusTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.pm.verify.pkg.VerificationStatus;
+import android.os.Parcel;
+import android.os.PersistableBundle;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VerificationStatusTest {
+ private static final boolean TEST_VERIFIED = true;
+ private static final int TEST_ASL_STATUS = VerificationStatus.VERIFIER_STATUS_ASL_GOOD;
+ private static final String TEST_FAILURE_MESSAGE = "test test";
+ private static final String TEST_KEY = "test key";
+ private static final String TEST_VALUE = "test value";
+ private final PersistableBundle mTestExtras = new PersistableBundle();
+ private VerificationStatus mStatus;
+
+ @Before
+ public void setUpWithBuilder() {
+ mTestExtras.putString(TEST_KEY, TEST_VALUE);
+ mStatus = new VerificationStatus.Builder()
+ .setAslStatus(TEST_ASL_STATUS)
+ .setFailureMessage(TEST_FAILURE_MESSAGE)
+ .setVerified(TEST_VERIFIED)
+ .build();
+ }
+
+ @Test
+ public void testGetters() {
+ assertThat(mStatus.isVerified()).isEqualTo(TEST_VERIFIED);
+ assertThat(mStatus.getAslStatus()).isEqualTo(TEST_ASL_STATUS);
+ assertThat(mStatus.getFailureMessage()).isEqualTo(TEST_FAILURE_MESSAGE);
+ }
+
+ @Test
+ public void testParcel() {
+ Parcel parcel = Parcel.obtain();
+ mStatus.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ VerificationStatus statusFromParcel = VerificationStatus.CREATOR.createFromParcel(parcel);
+ assertThat(statusFromParcel.isVerified()).isEqualTo(TEST_VERIFIED);
+ assertThat(statusFromParcel.getAslStatus()).isEqualTo(TEST_ASL_STATUS);
+ assertThat(statusFromParcel.getFailureMessage()).isEqualTo(TEST_FAILURE_MESSAGE);
+ }
+}
diff --git a/core/tests/coretests/src/android/content/pm/verify/VerifierServiceTest.java b/core/tests/coretests/src/android/content/pm/verify/VerifierServiceTest.java
new file mode 100644
index 0000000..7f73a1e
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/verify/VerifierServiceTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.SigningInfo;
+import android.content.pm.verify.pkg.IVerifierService;
+import android.content.pm.verify.pkg.VerificationSession;
+import android.content.pm.verify.pkg.VerifierService;
+import android.net.Uri;
+import android.os.PersistableBundle;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VerifierServiceTest {
+ private static final int TEST_ID = 100;
+ private static final int TEST_INSTALL_SESSION_ID = 33;
+ private static final String TEST_PACKAGE_NAME = "com.foo";
+ private static final Uri TEST_PACKAGE_URI = Uri.parse("test://test");
+ private static final SigningInfo TEST_SIGNING_INFO = new SigningInfo();
+ private VerifierService mService;
+ private VerificationSession mSession;
+
+ @Before
+ public void setUp() {
+ mService = Mockito.mock(VerifierService.class, Answers.CALLS_REAL_METHODS);
+ mSession = new VerificationSession(TEST_ID, TEST_INSTALL_SESSION_ID,
+ TEST_PACKAGE_NAME, TEST_PACKAGE_URI, TEST_SIGNING_INFO,
+ new ArrayList<>(),
+ new PersistableBundle(), null, null);
+ }
+
+ @Test
+ public void testBind() throws Exception {
+ Intent intent = Mockito.mock(Intent.class);
+ when(intent.getAction()).thenReturn(PackageManager.ACTION_VERIFY_PACKAGE);
+ IVerifierService binder =
+ (IVerifierService) mService.onBind(intent);
+ assertThat(binder).isNotNull();
+ binder.onPackageNameAvailable(TEST_PACKAGE_NAME);
+ verify(mService).onPackageNameAvailable(eq(TEST_PACKAGE_NAME));
+ binder.onVerificationCancelled(TEST_PACKAGE_NAME);
+ verify(mService).onVerificationCancelled(eq(TEST_PACKAGE_NAME));
+ binder.onVerificationRequired(mSession);
+ verify(mService).onVerificationRequired(eq(mSession));
+ binder.onVerificationRetry(mSession);
+ verify(mService).onVerificationRetry(eq(mSession));
+ binder.onVerificationTimeout(TEST_ID);
+ verify(mService).onVerificationTimeout(eq(TEST_ID));
+ }
+
+ @Test
+ public void testBindFailsWithWrongIntent() {
+ Intent intent = Mockito.mock(Intent.class);
+ when(intent.getAction()).thenReturn(Intent.ACTION_SEND);
+ assertThat(mService.onBind(intent)).isNull();
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/notification/SystemNotificationChannelsTest.java b/core/tests/coretests/src/com/android/internal/notification/SystemNotificationChannelsTest.java
new file mode 100644
index 0000000..0bf406c
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/notification/SystemNotificationChannelsTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.notification;
+
+import static com.android.internal.notification.SystemNotificationChannels.ABUSIVE_BACKGROUND_APPS;
+import static com.android.internal.notification.SystemNotificationChannels.ACCESSIBILITY_MAGNIFICATION;
+import static com.android.internal.notification.SystemNotificationChannels.ACCESSIBILITY_SECURITY_POLICY;
+import static com.android.internal.notification.SystemNotificationChannels.ACCOUNT;
+import static com.android.internal.notification.SystemNotificationChannels.ALERTS;
+import static com.android.internal.notification.SystemNotificationChannels.CAR_MODE;
+import static com.android.internal.notification.SystemNotificationChannels.DEVELOPER;
+import static com.android.internal.notification.SystemNotificationChannels.DEVELOPER_IMPORTANT;
+import static com.android.internal.notification.SystemNotificationChannels.DEVICE_ADMIN;
+import static com.android.internal.notification.SystemNotificationChannels.FOREGROUND_SERVICE;
+import static com.android.internal.notification.SystemNotificationChannels.HEAVY_WEIGHT_APP;
+import static com.android.internal.notification.SystemNotificationChannels.NETWORK_ALERTS;
+import static com.android.internal.notification.SystemNotificationChannels.NETWORK_AVAILABLE;
+import static com.android.internal.notification.SystemNotificationChannels.NETWORK_STATUS;
+import static com.android.internal.notification.SystemNotificationChannels.OBSOLETE_DO_NOT_DISTURB;
+import static com.android.internal.notification.SystemNotificationChannels.PHYSICAL_KEYBOARD;
+import static com.android.internal.notification.SystemNotificationChannels.RETAIL_MODE;
+import static com.android.internal.notification.SystemNotificationChannels.SECURITY;
+import static com.android.internal.notification.SystemNotificationChannels.SYSTEM_CHANGES;
+import static com.android.internal.notification.SystemNotificationChannels.UPDATES;
+import static com.android.internal.notification.SystemNotificationChannels.USB;
+import static com.android.internal.notification.SystemNotificationChannels.VPN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.verify;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.testing.TestableContext;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class SystemNotificationChannelsTest {
+
+ @Rule public TestableContext mContext = new TestableContext(
+ ApplicationProvider.getApplicationContext());
+
+ @Mock private NotificationManager mNm;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext.addMockSystemService(NotificationManager.class, mNm);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void createAll_createsExpectedChannels() {
+ ArgumentCaptor<List<NotificationChannel>> createdChannelsCaptor =
+ ArgumentCaptor.forClass(List.class);
+
+ SystemNotificationChannels.createAll(mContext);
+
+ verify(mNm).createNotificationChannels(createdChannelsCaptor.capture());
+ List<NotificationChannel> createdChannels = createdChannelsCaptor.getValue();
+ assertThat(createdChannels.stream().map(NotificationChannel::getId).toList())
+ .containsExactly(PHYSICAL_KEYBOARD, SECURITY, CAR_MODE, ACCOUNT, DEVELOPER,
+ DEVELOPER_IMPORTANT, UPDATES, NETWORK_STATUS, NETWORK_ALERTS,
+ NETWORK_AVAILABLE, VPN, DEVICE_ADMIN, ALERTS, RETAIL_MODE, USB,
+ FOREGROUND_SERVICE, HEAVY_WEIGHT_APP, SYSTEM_CHANGES,
+ ACCESSIBILITY_MAGNIFICATION, ACCESSIBILITY_SECURITY_POLICY,
+ ABUSIVE_BACKGROUND_APPS);
+ }
+
+ @Test
+ public void createAll_deletesObsoleteChannels() {
+ ArgumentCaptor<String> deletedChannelCaptor = ArgumentCaptor.forClass(String.class);
+
+ SystemNotificationChannels.createAll(mContext);
+
+ verify(mNm, atLeastOnce()).deleteNotificationChannel(deletedChannelCaptor.capture());
+ List<String> deletedChannels = deletedChannelCaptor.getAllValues();
+ assertThat(deletedChannels).containsExactly(OBSOLETE_DO_NOT_DISTURB);
+ }
+}
diff --git a/core/tests/utiltests/Android.bp b/core/tests/utiltests/Android.bp
index cdc8a9e..7cf49ab 100644
--- a/core/tests/utiltests/Android.bp
+++ b/core/tests/utiltests/Android.bp
@@ -61,7 +61,7 @@
"androidx.annotation_annotation",
"androidx.test.rules",
"frameworks-base-testutils",
- "servicestests-utils",
+ "servicestests-utils-ravenwood",
],
srcs: [
"src/android/util/IRemoteMemoryIntArray.aidl",
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 0b3e545..28c2ca3 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -155,7 +155,7 @@
/**
* Indicates whether this Canvas is drawing high contrast text.
*
- * @see android.view.accessibility.AccessibilityManager#isHighTextContrastEnabled()
+ * @see android.view.accessibility.AccessibilityManager#isHighContrastTextEnabled()
* @return True if high contrast text is enabled, false otherwise.
*
* @hide
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index a796ecc..e493ed1 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -197,6 +197,7 @@
android_library {
name: "WindowManager-Shell",
srcs: [
+ "src/com/android/wm/shell/EventLogTags.logtags",
":wm_shell_protolog_src",
// TODO(b/168581922) protologtool do not support kotlin(*.kt)
":wm_shell-sources-kt",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/EventLogTags.logtags b/libs/WindowManager/Shell/src/com/android/wm/shell/EventLogTags.logtags
new file mode 100644
index 0000000..db960d1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/EventLogTags.logtags
@@ -0,0 +1,11 @@
+# See system/logging/logcat/event.logtags for a description of the format of this file.
+
+option java_package com.android.wm.shell
+
+# Do not change these names without updating the checkin_events setting in
+# google3/googledata/wireless/android/provisioning/gservices.config !!
+#
+
+38500 wm_shell_enter_desktop_mode (EnterReason|1|5),(SessionId|1|5)
+38501 wm_shell_exit_desktop_mode (ExitReason|1|5),(SessionId|1|5)
+38502 wm_shell_desktop_mode_task_update (TaskEvent|1|5),(InstanceId|1|5),(uid|1|5),(TaskHeight|1),(TaskWidth|1),(TaskX|1),(TaskY|1),(SessionId|1|5),(MinimiseReason|1|5),(UnminimiseReason|1|5),(VisibleTaskCount|1)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index af4a0c5..03b7c8b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -2001,6 +2001,10 @@
// in bubble bar mode, let the request to show the expanded view come from launcher.
// only collapse here if we're collapsing.
if (mLayerView != null && !isExpanded) {
+ if (mBubblePositioner.isImeVisible()) {
+ // If we're collapsing, hide the IME
+ hideCurrentInputMethod();
+ }
mLayerView.collapse();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index ec235a5..2a90017 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -353,6 +353,11 @@
if (isDragging != mIsDragging) {
mIsDragging = isDragging;
updateSamplingState();
+
+ if (isDragging && mPositioner.isImeVisible()) {
+ // Hide the IME when dragging begins
+ mManager.hideCurrentInputMethod();
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 8ce3884..7293956 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -851,10 +851,11 @@
static DesktopWindowingEducationTooltipController
provideDesktopWindowingEducationTooltipController(
Context context,
- AdditionalSystemViewContainer.Factory additionalSystemViewContainerFactory
+ AdditionalSystemViewContainer.Factory additionalSystemViewContainerFactory,
+ DisplayController displayController
) {
return new DesktopWindowingEducationTooltipController(context,
- additionalSystemViewContainerFactory);
+ additionalSystemViewContainerFactory, displayController);
}
@OptIn(markerClass = ExperimentalCoroutinesApi.class)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index 02cbe01..5a277316f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -19,6 +19,8 @@
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.protolog.ProtoLog
import com.android.internal.util.FrameworkStatsLog
+import com.android.window.flags.Flags
+import com.android.wm.shell.EventLogTags
import com.android.wm.shell.protolog.ShellProtoLogGroup
/** Event logger for logging desktop mode session events */
@@ -41,6 +43,7 @@
/* exitReason */ 0,
/* session_id */ sessionId
)
+ EventLogTags.writeWmShellEnterDesktopMode(enterReason.reason, sessionId)
}
/**
@@ -61,6 +64,7 @@
/* exitReason */ exitReason.reason,
/* session_id */ sessionId
)
+ EventLogTags.writeWmShellExitDesktopMode(exitReason.reason, sessionId)
}
/**
@@ -76,7 +80,8 @@
)
logTaskUpdate(
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED,
- sessionId, taskUpdate)
+ sessionId, taskUpdate
+ )
}
/**
@@ -92,7 +97,8 @@
)
logTaskUpdate(
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED,
- sessionId, taskUpdate)
+ sessionId, taskUpdate
+ )
}
/**
@@ -108,7 +114,46 @@
)
logTaskUpdate(
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED,
- sessionId, taskUpdate)
+ sessionId, taskUpdate
+ )
+ }
+
+ /**
+ * Logs that a task resize event is starting with [taskSizeUpdate] within a
+ * Desktop mode [sessionId].
+ */
+ fun logTaskResizingStarted(sessionId: Int, taskSizeUpdate: TaskSizeUpdate) {
+ if (!Flags.enableResizingMetrics()) return
+
+ ProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Logging task resize is starting, session: %s taskId: %s",
+ sessionId,
+ taskSizeUpdate.instanceId
+ )
+ logTaskSizeUpdated(
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE,
+ sessionId, taskSizeUpdate
+ )
+ }
+
+ /**
+ * Logs that a task resize event is ending with [taskSizeUpdate] within a
+ * Desktop mode [sessionId].
+ */
+ fun logTaskResizingEnded(sessionId: Int, taskSizeUpdate: TaskSizeUpdate) {
+ if (!Flags.enableResizingMetrics()) return
+
+ ProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Logging task resize is ending, session: %s taskId: %s",
+ sessionId,
+ taskSizeUpdate.instanceId
+ )
+ logTaskSizeUpdated(
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE,
+ sessionId, taskSizeUpdate
+ )
}
private fun logTaskUpdate(taskEvent: Int, sessionId: Int, taskUpdate: TaskUpdate) {
@@ -135,6 +180,56 @@
/* visible_task_count */
taskUpdate.visibleTaskCount
)
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
+ /* task_event */
+ taskEvent,
+ /* instance_id */
+ taskUpdate.instanceId,
+ /* uid */
+ taskUpdate.uid,
+ /* task_height */
+ taskUpdate.taskHeight,
+ /* task_width */
+ taskUpdate.taskWidth,
+ /* task_x */
+ taskUpdate.taskX,
+ /* task_y */
+ taskUpdate.taskY,
+ /* session_id */
+ sessionId,
+ taskUpdate.minimizeReason?.reason ?: UNSET_MINIMIZE_REASON,
+ taskUpdate.unminimizeReason?.reason ?: UNSET_UNMINIMIZE_REASON,
+ /* visible_task_count */
+ taskUpdate.visibleTaskCount
+ )
+ }
+
+ private fun logTaskSizeUpdated(
+ resizingStage: Int,
+ sessionId: Int,
+ taskSizeUpdate: TaskSizeUpdate
+ ) {
+ FrameworkStatsLog.write(
+ DESKTOP_MODE_TASK_SIZE_UPDATED_ATOM_ID,
+ /* resize_trigger */
+ taskSizeUpdate.resizeTrigger?.trigger ?: ResizeTrigger.UNKNOWN_RESIZE_TRIGGER.trigger,
+ /* resizing_stage */
+ resizingStage,
+ /* input_method */
+ taskSizeUpdate.inputMethod?.method ?: InputMethod.UNKNOWN_INPUT_METHOD.method,
+ /* desktop_mode_session_id */
+ sessionId,
+ /* instance_id */
+ taskSizeUpdate.instanceId,
+ /* uid */
+ taskSizeUpdate.uid,
+ /* task_height */
+ taskSizeUpdate.taskHeight,
+ /* task_width */
+ taskSizeUpdate.taskWidth,
+ /* display_area */
+ taskSizeUpdate.displayArea
+ )
}
companion object {
@@ -163,13 +258,35 @@
val visibleTaskCount: Int,
)
+ /**
+ * Describes a task size update (resizing, snapping or maximizing to
+ * stable bounds).
+ *
+ * @property resizeTrigger the trigger for task resize
+ * @property inputMethod the input method for resizing this task
+ * @property instanceId instance id of the task
+ * @property uid uid of the app associated with the task
+ * @property taskHeight height of the task in dp
+ * @property taskWidth width of the task in dp
+ * @property displayArea the display size of the screen in dp
+ */
+ data class TaskSizeUpdate(
+ val resizeTrigger: ResizeTrigger? = null,
+ val inputMethod: InputMethod? = null,
+ val instanceId: Int,
+ val uid: Int,
+ val taskHeight: Int,
+ val taskWidth: Int,
+ val displayArea: Int,
+ )
+
// Default value used when the task was not minimized.
@VisibleForTesting
const val UNSET_MINIMIZE_REASON =
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__UNSET_MINIMIZE
/** The reason a task was minimized. */
- enum class MinimizeReason (val reason: Int) {
+ enum class MinimizeReason(val reason: Int) {
TASK_LIMIT(
FrameworkStatsLog
.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_TASK_LIMIT
@@ -186,7 +303,7 @@
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNSET_UNMINIMIZE
/** The reason a task was unminimized. */
- enum class UnminimizeReason (val reason: Int) {
+ enum class UnminimizeReason(val reason: Int) {
UNKNOWN(
FrameworkStatsLog
.DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_UNKNOWN
@@ -250,8 +367,88 @@
SCREEN_OFF(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__SCREEN_OFF)
}
+ /**
+ * Enum ResizeTrigger mapped to the ResizeTrigger definition in
+ * stats/atoms/desktopmode/desktopmode_extensions_atoms.proto
+ */
+ enum class ResizeTrigger(val trigger: Int) {
+ UNKNOWN_RESIZE_TRIGGER(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__UNKNOWN_RESIZE_TRIGGER
+ ),
+ CORNER(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER
+ ),
+ EDGE(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__EDGE_RESIZE_TRIGGER
+ ),
+ TILING_DIVIDER(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__TILING_DIVIDER_RESIZE_TRIGGER
+ ),
+ MAXIMIZE_BUTTON(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__MAXIMIZE_BUTTON_RESIZE_TRIGGER
+ ),
+ DOUBLE_TAP_APP_HEADER(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__DOUBLE_TAP_APP_HEADER_RESIZE_TRIGGER
+ ),
+ DRAG_LEFT(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__DRAG_LEFT_RESIZE_TRIGGER
+ ),
+ DRAG_RIGHT(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__DRAG_RIGHT_RESIZE_TRIGGER
+ ),
+ SNAP_LEFT_MENU(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__SNAP_LEFT_MENU_RESIZE_TRIGGER
+ ),
+ SNAP_RIGHT_MENU(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__SNAP_RIGHT_MENU_RESIZE_TRIGGER
+ ),
+ }
+
+ /**
+ * Enum InputMethod mapped to the InputMethod definition in
+ * stats/atoms/desktopmode/desktopmode_extensions_atoms.proto
+ */
+ enum class InputMethod(val method: Int) {
+ UNKNOWN_INPUT_METHOD(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD
+ ),
+ TOUCH(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__TOUCH_INPUT_METHOD
+ ),
+ STYLUS(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__STYLUS_INPUT_METHOD
+ ),
+ MOUSE(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__MOUSE_INPUT_METHOD
+ ),
+ TOUCHPAD(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__TOUCHPAD_INPUT_METHOD
+ ),
+ KEYBOARD(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__KEYBOARD_INPUT_METHOD
+ ),
+ }
+
private const val DESKTOP_MODE_ATOM_ID = FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED
private const val DESKTOP_MODE_TASK_UPDATE_ATOM_ID =
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE
+ private const val DESKTOP_MODE_TASK_SIZE_UPDATED_ATOM_ID =
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 955fe83..985224e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -350,8 +350,17 @@
/** Minimizes the task for [taskId] and [displayId] */
fun minimizeTask(displayId: Int, taskId: Int) {
- logD("Minimize Task: display=%d, task=%d", displayId, taskId)
- desktopTaskDataByDisplayId.getOrCreate(displayId).minimizedTasks.add(taskId)
+ if (displayId == INVALID_DISPLAY) {
+ // When a task vanishes it doesn't have a displayId. Find the display of the task and
+ // mark it as minimized.
+ getDisplayIdForTask(taskId)?.let {
+ minimizeTask(it, taskId)
+ } ?: logW("Minimize task: No display id found for task: taskId=%d", taskId)
+ } else {
+ logD("Minimize Task: display=%d, task=%d", displayId, taskId)
+ desktopTaskDataByDisplayId.getOrCreate(displayId).minimizedTasks.add(taskId)
+ }
+
if (Flags.enableDesktopWindowingPersistence()) {
updatePersistentRepository(displayId)
}
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 125805c..fcd2f8c 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
@@ -44,7 +44,6 @@
import android.view.DragEvent
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
-import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
@@ -407,7 +406,7 @@
interactionJankMonitor.begin(taskSurface, context, handler,
CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
dragToDesktopTransitionHandler.startDragToDesktopTransition(
- taskInfo.taskId,
+ taskInfo,
dragToDesktopValueAnimator
)
}
@@ -550,7 +549,29 @@
/** Move a task to the front */
fun moveTaskToFront(taskId: Int) {
- shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveTaskToFront(task) }
+ val task = shellTaskOrganizer.getRunningTaskInfo(taskId)
+ if (task == null) moveBackgroundTaskToFront(taskId) else moveTaskToFront(task)
+ }
+
+ /**
+ * Launch a background task in desktop. Note that this should be used when we are already in
+ * desktop. If outside of desktop and want to launch a background task in desktop, use
+ * [moveBackgroundTaskToDesktop] instead.
+ */
+ private fun moveBackgroundTaskToFront(taskId: Int) {
+ logV("moveBackgroundTaskToFront taskId=%s", taskId)
+ val wct = WindowContainerTransaction()
+ // TODO: b/342378842 - Instead of using default display, support multiple displays
+ val taskToMinimize: RunningTaskInfo? =
+ addAndGetMinimizeChangesIfNeeded(DEFAULT_DISPLAY, wct, taskId)
+ wct.startTask(
+ taskId,
+ ActivityOptions.makeBasic().apply {
+ launchWindowingMode = WINDOWING_MODE_FREEFORM
+ }.toBundle(),
+ )
+ val transition = transitions.startTransition(TRANSIT_OPEN, wct, null /* handler */)
+ addPendingMinimizeTransition(transition, taskToMinimize)
}
/** Move a task to the front */
@@ -558,7 +579,8 @@
logV("moveTaskToFront taskId=%s", taskInfo.taskId)
val wct = WindowContainerTransaction()
wct.reorder(taskInfo.token, true)
- val taskToMinimize = addAndGetMinimizeChangesIfNeeded(taskInfo.displayId, wct, taskInfo)
+ val taskToMinimize =
+ addAndGetMinimizeChangesIfNeeded(taskInfo.displayId, wct, taskInfo.taskId)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
val transition = transitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */)
addPendingMinimizeTransition(transition, taskToMinimize)
@@ -1254,7 +1276,7 @@
}
// Desktop Mode is showing and we're launching a new Task - we might need to minimize
// a Task.
- val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task)
+ val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task.taskId)
if (taskToMinimize != null) {
addPendingMinimizeTransition(transition, taskToMinimize)
return wct
@@ -1280,7 +1302,8 @@
// Desktop Mode is already showing and we're launching a new Task - we might need to
// minimize another Task.
- val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task)
+ val taskToMinimize =
+ addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task.taskId)
addPendingMinimizeTransition(transition, taskToMinimize)
}
}
@@ -1313,14 +1336,11 @@
// Remove wallpaper activity when the last active task is removed
removeWallpaperActivity(wct)
}
- taskRepository.addClosingTask(task.displayId, task.taskId)
- // If a CLOSE is triggered on a desktop task, remove the task.
- if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue() &&
- taskRepository.isVisibleTask(task.taskId) &&
- transitionType == TRANSIT_CLOSE
- ) {
- wct.removeTask(task.token)
+
+ if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
+ taskRepository.addClosingTask(task.displayId, task.taskId)
}
+
taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
doesAnyTaskRequireTaskbarRounding(
task.displayId,
@@ -1425,12 +1445,12 @@
private fun addAndGetMinimizeChangesIfNeeded(
displayId: Int,
wct: WindowContainerTransaction,
- newTaskInfo: RunningTaskInfo
+ newTaskId: Int
): RunningTaskInfo? {
if (!desktopTasksLimiter.isPresent) return null
return desktopTasksLimiter
.get()
- .addAndGetMinimizeTaskChangesIfNeeded(displayId, wct, newTaskInfo)
+ .addAndGetMinimizeTaskChangesIfNeeded(displayId, wct, newTaskId)
}
private fun addPendingMinimizeTransition(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index d84349b..37ad0c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -24,6 +24,7 @@
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
+import android.window.flags.DesktopModeFlags
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW
import com.android.internal.jank.InteractionJankMonitor
@@ -161,6 +162,8 @@
@VisibleForTesting
inner class LeftoverMinimizedTasksRemover : DesktopModeTaskRepository.ActiveTasksListener {
override fun onActiveTasksChanged(displayId: Int) {
+ // If back navigation is enabled, we shouldn't remove the leftover tasks
+ if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return
val wct = WindowContainerTransaction()
removeLeftoverMinimizedTasks(displayId, wct)
shellTaskOrganizer.applyTransaction(wct)
@@ -208,15 +211,15 @@
fun addAndGetMinimizeTaskChangesIfNeeded(
displayId: Int,
wct: WindowContainerTransaction,
- newFrontTaskInfo: RunningTaskInfo,
+ newFrontTaskId: Int,
): RunningTaskInfo? {
ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopTasksLimiter: addMinimizeBackTaskChangesIfNeeded, newFrontTask=%d",
- newFrontTaskInfo.taskId)
+ newFrontTaskId)
val newTaskListOrderedFrontToBack = createOrderedTaskListWithGivenTaskInFront(
taskRepository.getActiveNonMinimizedOrderedTasks(displayId),
- newFrontTaskInfo.taskId)
+ newFrontTaskId)
val taskToMinimize = getTaskToMinimizeIfNeeded(newTaskListOrderedFrontToBack)
if (taskToMinimize != null) {
wct.reorder(taskToMinimize.token, false /* onTop */)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index 4796c4d..b20c9fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -29,6 +29,7 @@
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
@@ -67,9 +68,29 @@
) {
// TODO: b/332682201 Update repository state
updateWallpaperToken(info)
-
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
handleBackNavigation(info)
+ removeTaskIfNeeded(info)
+ }
+ }
+
+ private fun removeTaskIfNeeded(info: TransitionInfo) {
+ // Since we are no longer removing all the tasks [onTaskVanished], we need to remove them by
+ // checking the transitions.
+ if (!TransitionUtil.isOpeningType(info.type)) return
+ // Remove a task from the repository if the app is launched outside of desktop.
+ for (change in info.changes) {
+ val taskInfo = change.taskInfo
+ if (taskInfo == null || taskInfo.taskId == -1) continue
+
+ if (desktopModeTaskRepository.isActiveTask(taskInfo.taskId)
+ && taskInfo.windowingMode != WINDOWING_MODE_FREEFORM
+ ) {
+ desktopModeTaskRepository.removeFreeformTask(
+ taskInfo.displayId,
+ taskInfo.taskId
+ )
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 2bc01b2..8e264b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -109,8 +109,8 @@
* after one of the "end" or "cancel" transitions is merged into this transition.
*/
fun startDragToDesktopTransition(
- taskId: Int,
- dragToDesktopAnimator: MoveToDesktopAnimator,
+ taskInfo: RunningTaskInfo,
+ dragToDesktopAnimator: MoveToDesktopAnimator
) {
if (inProgress) {
ProtoLog.v(
@@ -137,23 +137,26 @@
)
val wct = WindowContainerTransaction()
wct.sendPendingIntent(pendingIntent, launchHomeIntent, Bundle())
+ // The home launch done above will result in an attempt to move the task to pip if
+ // applicable, resulting in a broken state. Prevent that here.
+ wct.setDoNotPip(taskInfo.token)
val startTransitionToken =
transitions.startTransition(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, wct, this)
transitionState =
- if (isSplitTask(taskId)) {
+ if (isSplitTask(taskInfo.taskId)) {
val otherTask =
- getOtherSplitTask(taskId)
+ getOtherSplitTask(taskInfo.taskId)
?: throw IllegalStateException("Expected split task to have a counterpart.")
TransitionState.FromSplit(
- draggedTaskId = taskId,
+ draggedTaskId = taskInfo.taskId,
dragAnimator = dragToDesktopAnimator,
startTransitionToken = startTransitionToken,
otherSplitTask = otherTask
)
} else {
TransitionState.FromFullscreen(
- draggedTaskId = taskId,
+ draggedTaskId = taskInfo.taskId,
dragAnimator = dragToDesktopAnimator,
startTransitionToken = startTransitionToken
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 83cc18b..7f7f105 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.util.SparseArray;
import android.view.SurfaceControl;
+import android.window.flags.DesktopModeFlags;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -121,7 +122,16 @@
if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
mDesktopModeTaskRepository.ifPresent(repository -> {
- repository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId);
+ // TODO: b/370038902 - Handle Activity#finishAndRemoveTask.
+ if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()
+ || repository.isClosingTask(taskInfo.taskId)) {
+ // A task that's vanishing should be removed:
+ // - If it's closed by the X button which means it's marked as a closing task.
+ repository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId);
+ } else {
+ repository.updateTaskVisibility(taskInfo.displayId, taskInfo.taskId, false);
+ repository.minimizeTask(taskInfo.displayId, taskInfo.taskId);
+ }
});
}
mWindowDecorationViewModel.onTaskVanished(taskInfo);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 1b9bf2a..dc0bc78 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -304,54 +304,28 @@
if (pipChange == null) {
return false;
}
- WindowContainerToken pipTaskToken = pipChange.getContainer();
SurfaceControl pipLeash = pipChange.getLeash();
+ Preconditions.checkNotNull(pipLeash, "Leash is null for swipe-up transition.");
- if (pipTaskToken == null || pipLeash == null) {
- return false;
- }
-
- SurfaceControl overlayLeash = mPipTransitionState.getSwipePipToHomeOverlay();
- PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams;
-
- Rect appBounds = mPipTransitionState.getSwipePipToHomeAppBounds();
- Rect destinationBounds = pipChange.getEndAbsBounds();
-
- float aspectRatio = pipChange.getTaskInfo().pictureInPictureParams.getAspectRatioFloat();
-
- // We fake the source rect hint when the one prvided by the app is invalid for
- // the animation with an app icon overlay.
- Rect animationSrcRectHint = overlayLeash == null ? params.getSourceRectHint()
- : PipUtils.getEnterPipWithOverlaySrcRectHint(appBounds, aspectRatio);
-
- WindowContainerTransaction finishWct = new WindowContainerTransaction();
- SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
-
- final float scale = (float) destinationBounds.width() / animationSrcRectHint.width();
- startTransaction.setWindowCrop(pipLeash, animationSrcRectHint);
- startTransaction.setPosition(pipLeash,
- destinationBounds.left - animationSrcRectHint.left * scale,
- destinationBounds.top - animationSrcRectHint.top * scale);
- startTransaction.setScale(pipLeash, scale, scale);
-
- if (overlayLeash != null) {
+ final Rect destinationBounds = pipChange.getEndAbsBounds();
+ final SurfaceControl swipePipToHomeOverlay = mPipTransitionState.getSwipePipToHomeOverlay();
+ if (swipePipToHomeOverlay != null) {
final int overlaySize = PipContentOverlay.PipAppIconOverlay.getOverlaySize(
mPipTransitionState.getSwipePipToHomeAppBounds(), destinationBounds);
-
- // Overlay needs to be adjusted once a new draw comes in resetting surface transform.
- tx.setScale(overlayLeash, 1f, 1f);
- tx.setPosition(overlayLeash, (destinationBounds.width() - overlaySize) / 2f,
- (destinationBounds.height() - overlaySize) / 2f);
+ // It is possible we reparent the PIP activity to a new PIP task (in multi-activity
+ // apps), so we should also reparent the overlay to the final PIP task.
+ startTransaction.reparent(swipePipToHomeOverlay, pipLeash)
+ .setLayer(swipePipToHomeOverlay, Integer.MAX_VALUE)
+ .setScale(swipePipToHomeOverlay, 1f, 1f)
+ .setPosition(swipePipToHomeOverlay,
+ (destinationBounds.width() - overlaySize) / 2f,
+ (destinationBounds.height() - overlaySize) / 2f);
}
+
+ startTransaction.merge(finishTransaction);
startTransaction.apply();
-
- tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(),
- this::onClientDrawAtTransitionEnd);
- finishWct.setBoundsChangeTransaction(pipTaskToken, tx);
-
- // Note that finishWct should be free of any actual WM state changes; we are using
- // it for syncing with the client draw after delayed configuration changes are dispatched.
- finishCallback.onTransitionFinished(finishWct.isEmpty() ? null : finishWct);
+ finishCallback.onTransitionFinished(null /* finishWct */);
+ onClientDrawAtTransitionEnd();
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 03ff1aa..c9c0873 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -32,6 +32,7 @@
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
+import android.graphics.Point;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Slog;
@@ -46,6 +47,7 @@
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
+import com.android.window.flags.Flags;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
@@ -421,6 +423,16 @@
if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) {
mostRecentFreeformTaskIndex = recentTasks.size();
}
+ // If task has their app bounds set to null which happens after reboot, set the
+ // app bounds to persisted lastFullscreenBounds. Also set the position in parent
+ // to the top left of the bounds.
+ if (Flags.enableDesktopWindowingPersistence()
+ && taskInfo.configuration.windowConfiguration.getAppBounds() == null) {
+ taskInfo.configuration.windowConfiguration.setAppBounds(
+ taskInfo.lastNonFullscreenBounds);
+ taskInfo.positionInParent = new Point(taskInfo.lastNonFullscreenBounds.left,
+ taskInfo.lastNonFullscreenBounds.top);
+ }
freeformTasks.add(taskInfo);
if (mDesktopModeTaskRepository.get().isMinimizedTask(taskInfo.taskId)) {
minimizedFreeformTasks.add(taskInfo.taskId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 8077aee..f7ed1dd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -52,7 +52,6 @@
import android.util.IntArray;
import android.util.Pair;
import android.util.Slog;
-import android.view.Display;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.PictureInPictureSurfaceTransaction;
@@ -910,6 +909,14 @@
"task #" + taskInfo.taskId + " is always_on_top");
return;
}
+ if (TransitionUtil.isClosingType(change.getMode())
+ && taskInfo != null && taskInfo.lastParentTaskIdBeforePip > 0) {
+ // Pinned task is closing as a side effect of the removal of its original Task,
+ // such transition should be handled by PiP. So cancel the merge here.
+ cancel(false /* toHome */, false /* withScreenshots */,
+ "task #" + taskInfo.taskId + " is removed with its original parent");
+ return;
+ }
final boolean isRootTask = taskInfo != null
&& TransitionInfo.isIndependent(change, info);
final boolean isRecentsTask = mRecentsTask != null
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 5daa3ee..ada1e0b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -824,6 +824,7 @@
WindowInsets.Type.systemBars() & ~WindowInsets.Type.captionBar(),
false /* ignoreVisibility */);
relayoutParams.mCaptionTopPadding = systemBarInsets.top;
+ relayoutParams.mIsInsetSource = false;
}
// Report occluding elements as bounding rects to the insets system so that apps can
// draw in the empty space in the center:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 000beba1..f8aed41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -346,7 +346,7 @@
private void updateCaptionInsets(RelayoutParams params, WindowContainerTransaction wct,
RelayoutResult<T> outResult, Rect taskBounds) {
- if (!mIsCaptionVisible) {
+ if (!mIsCaptionVisible || !params.mIsInsetSource) {
if (mWindowDecorationInsets != null) {
mWindowDecorationInsets.remove(wct);
mWindowDecorationInsets = null;
@@ -724,6 +724,7 @@
int mCaptionWidthId;
final List<OccludingCaptionElement> mOccludingCaptionElements = new ArrayList<>();
int mInputFeatures;
+ boolean mIsInsetSource = true;
@InsetsSource.Flags int mInsetSourceFlags;
int mShadowRadiusId;
@@ -743,6 +744,7 @@
mCaptionWidthId = Resources.ID_NULL;
mOccludingCaptionElements.clear();
mInputFeatures = 0;
+ mIsInsetSource = true;
mInsetSourceFlags = 0;
mShadowRadiusId = Resources.ID_NULL;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt
index 98413ee..a9a16bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt
@@ -30,9 +30,13 @@
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
+import android.window.DisplayAreaInfo
+import android.window.WindowContainerTransaction
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.wm.shell.R
+import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener
+import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.shared.animation.PhysicsAnimator
import com.android.wm.shell.windowdecor.WindowManagerWrapper
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
@@ -44,7 +48,8 @@
class DesktopWindowingEducationTooltipController(
private val context: Context,
private val additionalSystemViewContainerFactory: AdditionalSystemViewContainer.Factory,
-) {
+ private val displayController: DisplayController,
+) : OnDisplayChangingListener {
// TODO: b/369384567 - Set tooltip color scheme to match LT/DT of app theme
private var tooltipView: View? = null
private var animator: PhysicsAnimator<View>? = null
@@ -53,6 +58,20 @@
}
private var popupWindow: AdditionalSystemViewContainer? = null
+ override fun onDisplayChange(
+ displayId: Int,
+ fromRotation: Int,
+ toRotation: Int,
+ newDisplayAreaInfo: DisplayAreaInfo?,
+ t: WindowContainerTransaction?
+ ) {
+ // Exit if the rotation hasn't changed or is changed by 180 degrees. [fromRotation] and
+ // [toRotation] can be one of the [@Surface.Rotation] values.
+ if ((fromRotation % 2 == toRotation % 2)) return
+ hideEducationTooltip()
+ // TODO: b/370820018 - Update tooltip position on orientation change instead of dismissing
+ }
+
/**
* Shows education tooltip.
*
@@ -64,6 +83,7 @@
tooltipView = createEducationTooltipView(tooltipViewConfig, taskId)
animator = createAnimator()
animateShowTooltipTransition()
+ displayController.addDisplayChangingController(this)
}
/** Hide the current education view if visible */
@@ -145,6 +165,7 @@
animator = null
popupWindow?.releaseView()
popupWindow = null
+ displayController.removeDisplayChangingController(this)
}
private fun createTooltipPopupWindow(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
index ca97229..d7a132d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -16,13 +16,22 @@
package com.android.wm.shell.desktopmode
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
import com.android.internal.util.FrameworkStatsLog
import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.window.flags.Flags
+import com.android.wm.shell.EventLogTags
+import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskSizeUpdate
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_MINIMIZE_REASON
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_UNMINIMIZE_REASON
@@ -34,14 +43,19 @@
/**
* Tests for [DesktopModeEventLogger].
*/
-class DesktopModeEventLoggerTest {
+class DesktopModeEventLoggerTest : ShellTestCase() {
private val desktopModeEventLogger = DesktopModeEventLogger()
@JvmField
- @Rule
+ @Rule(order = 0)
val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
- .mockStatic(FrameworkStatsLog::class.java).build()!!
+ .mockStatic(FrameworkStatsLog::class.java)
+ .mockStatic(EventLogTags::class.java).build()!!
+
+ @JvmField
+ @Rule(order = 1)
+ val setFlagsRule = SetFlagsRule()
@Test
fun logSessionEnter_enterReason() = runBlocking {
@@ -60,6 +74,11 @@
eq(SESSION_ID)
)
}
+ verify {
+ EventLogTags.writeWmShellEnterDesktopMode(
+ eq(EnterReason.UNKNOWN_ENTER.reason),
+ eq(SESSION_ID))
+ }
}
@Test
@@ -79,6 +98,11 @@
eq(SESSION_ID)
)
}
+ verify {
+ EventLogTags.writeWmShellExitDesktopMode(
+ eq(ExitReason.UNKNOWN_EXIT.reason),
+ eq(SESSION_ID))
+ }
}
@Test
@@ -108,6 +132,22 @@
/* visible_task_count */
eq(TASK_COUNT))
}
+
+ verify {
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
+ eq(FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED),
+ eq(TASK_UPDATE.instanceId),
+ eq(TASK_UPDATE.uid),
+ eq(TASK_UPDATE.taskHeight),
+ eq(TASK_UPDATE.taskWidth),
+ eq(TASK_UPDATE.taskX),
+ eq(TASK_UPDATE.taskY),
+ eq(SESSION_ID),
+ eq(UNSET_MINIMIZE_REASON),
+ eq(UNSET_UNMINIMIZE_REASON),
+ eq(TASK_COUNT))
+ }
}
@Test
@@ -137,6 +177,22 @@
/* visible_task_count */
eq(TASK_COUNT))
}
+
+ verify {
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
+ eq(FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED),
+ eq(TASK_UPDATE.instanceId),
+ eq(TASK_UPDATE.uid),
+ eq(TASK_UPDATE.taskHeight),
+ eq(TASK_UPDATE.taskWidth),
+ eq(TASK_UPDATE.taskX),
+ eq(TASK_UPDATE.taskY),
+ eq(SESSION_ID),
+ eq(UNSET_MINIMIZE_REASON),
+ eq(UNSET_UNMINIMIZE_REASON),
+ eq(TASK_COUNT))
+ }
}
@Test
@@ -167,6 +223,22 @@
/* visible_task_count */
eq(TASK_COUNT))
}
+
+ verify {
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
+ eq(FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
+ eq(TASK_UPDATE.instanceId),
+ eq(TASK_UPDATE.uid),
+ eq(TASK_UPDATE.taskHeight),
+ eq(TASK_UPDATE.taskWidth),
+ eq(TASK_UPDATE.taskX),
+ eq(TASK_UPDATE.taskY),
+ eq(SESSION_ID),
+ eq(UNSET_MINIMIZE_REASON),
+ eq(UNSET_UNMINIMIZE_REASON),
+ eq(TASK_COUNT))
+ }
}
@Test
@@ -200,6 +272,22 @@
/* visible_task_count */
eq(TASK_COUNT))
}
+
+ verify {
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
+ eq(FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
+ eq(TASK_UPDATE.instanceId),
+ eq(TASK_UPDATE.uid),
+ eq(TASK_UPDATE.taskHeight),
+ eq(TASK_UPDATE.taskWidth),
+ eq(TASK_UPDATE.taskX),
+ eq(TASK_UPDATE.taskY),
+ eq(SESSION_ID),
+ eq(MinimizeReason.TASK_LIMIT.reason),
+ eq(UNSET_UNMINIMIZE_REASON),
+ eq(TASK_COUNT))
+ }
}
@Test
@@ -233,9 +321,83 @@
/* visible_task_count */
eq(TASK_COUNT))
}
+
+ verify {
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
+ eq(FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
+ eq(TASK_UPDATE.instanceId),
+ eq(TASK_UPDATE.uid),
+ eq(TASK_UPDATE.taskHeight),
+ eq(TASK_UPDATE.taskWidth),
+ eq(TASK_UPDATE.taskX),
+ eq(TASK_UPDATE.taskY),
+ eq(SESSION_ID),
+ eq(UNSET_MINIMIZE_REASON),
+ eq(UnminimizeReason.TASKBAR_TAP.reason),
+ eq(TASK_COUNT))
+ }
}
- companion object {
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESIZING_METRICS)
+ fun logTaskResizingStarted_logsTaskSizeUpdatedWithStartResizingStage() = runBlocking {
+ desktopModeEventLogger.logTaskResizingStarted(sessionId = SESSION_ID, createTaskSizeUpdate())
+
+ verify {
+ FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
+ /* resize_trigger */
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__UNKNOWN_RESIZE_TRIGGER),
+ /* resizing_stage */
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE),
+ /* input_method */
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD),
+ /* desktop_mode_session_id */
+ eq(SESSION_ID),
+ /* instance_id */
+ eq(TASK_SIZE_UPDATE.instanceId),
+ /* uid */
+ eq(TASK_SIZE_UPDATE.uid),
+ /* task_height */
+ eq(TASK_SIZE_UPDATE.taskHeight),
+ /* task_width */
+ eq(TASK_SIZE_UPDATE.taskWidth),
+ /* display_area */
+ eq(TASK_SIZE_UPDATE.displayArea),
+ )
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESIZING_METRICS)
+ fun logTaskResizingEnded_logsTaskSizeUpdatedWithEndResizingStage() = runBlocking {
+ desktopModeEventLogger.logTaskResizingEnded(sessionId = SESSION_ID, createTaskSizeUpdate())
+
+ verify {
+ FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
+ /* resize_trigger */
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__UNKNOWN_RESIZE_TRIGGER),
+ /* resizing_stage */
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE),
+ /* input_method */
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD),
+ /* desktop_mode_session_id */
+ eq(SESSION_ID),
+ /* instance_id */
+ eq(TASK_SIZE_UPDATE.instanceId),
+ /* uid */
+ eq(TASK_SIZE_UPDATE.uid),
+ /* task_height */
+ eq(TASK_SIZE_UPDATE.taskHeight),
+ /* task_width */
+ eq(TASK_SIZE_UPDATE.taskWidth),
+ /* display_area */
+ eq(TASK_SIZE_UPDATE.displayArea),
+ )
+ }
+ }
+
+ private companion object {
private const val SESSION_ID = 1
private const val TASK_ID = 1
private const val TASK_UID = 1
@@ -244,16 +406,40 @@
private const val TASK_HEIGHT = 100
private const val TASK_WIDTH = 100
private const val TASK_COUNT = 1
+ private const val DISPLAY_AREA = 1000
private val TASK_UPDATE = TaskUpdate(
TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y,
visibleTaskCount = TASK_COUNT,
)
+ private val TASK_SIZE_UPDATE = TaskSizeUpdate(
+ resizeTrigger = ResizeTrigger.UNKNOWN_RESIZE_TRIGGER,
+ inputMethod = InputMethod.UNKNOWN_INPUT_METHOD,
+ TASK_ID,
+ TASK_UID,
+ TASK_HEIGHT,
+ TASK_WIDTH,
+ DISPLAY_AREA,
+ )
+
+ private fun createTaskSizeUpdate(
+ resizeTrigger: ResizeTrigger = ResizeTrigger.UNKNOWN_RESIZE_TRIGGER,
+ inputMethod: InputMethod = InputMethod.UNKNOWN_INPUT_METHOD,
+ ) = TaskSizeUpdate(
+ resizeTrigger,
+ inputMethod,
+ TASK_ID,
+ TASK_UID,
+ TASK_HEIGHT,
+ TASK_WIDTH,
+ DISPLAY_AREA,
+ )
+
private fun createTaskUpdate(
minimizeReason: MinimizeReason? = null,
unminimizeReason: UnminimizeReason? = null,
) = TaskUpdate(TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y, minimizeReason,
unminimizeReason, TASK_COUNT)
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 794f9d8..97ceecc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -820,6 +820,18 @@
}
@Test
+ fun minimizeTask_withInvalidDisplay_minimizesCorrectTask() {
+ repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 0)
+ repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 0)
+
+ repo.minimizeTask(displayId = INVALID_DISPLAY, taskId = 0)
+
+ assertThat(repo.isMinimizedTask(taskId = 0)).isTrue()
+ assertThat(repo.isMinimizedTask(taskId = 1)).isFalse()
+ assertThat(repo.isMinimizedTask(taskId = 2)).isFalse()
+ }
+
+ @Test
fun unminimizeTask_unminimizesTask() {
repo.minimizeTask(displayId = 0, taskId = 0)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 8870846..2ddb1ac 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -1350,6 +1350,32 @@
}
@Test
+ fun moveTaskToFront_backgroundTask_launchesTask() {
+ val task = createTaskInfo(1)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
+
+ controller.moveTaskToFront(task.taskId)
+
+ val wct = getLatestWct(type = TRANSIT_OPEN)
+ assertThat(wct.hierarchyOps).hasSize(1)
+ wct.assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ fun moveTaskToFront_backgroundTaskBringsTasksOverLimit_minimizesBackTask() {
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+ val task = createTaskInfo(1001)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null)
+
+ controller.moveTaskToFront(task.taskId)
+
+ val wct = getLatestWct(type = TRANSIT_OPEN)
+ assertThat(wct.hierarchyOps.size).isEqualTo(2) // launch + minimize
+ wct.assertReorderAt(0, freeformTasks[0], toTop = false)
+ wct.assertLaunchTaskAt(1, task.taskId, WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
fun moveToNextDisplay_noOtherDisplays() {
whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY))
val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
@@ -2075,11 +2101,8 @@
}
@Test
- @DisableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
- )
- fun handleRequest_backTransition_singleTaskNoToken_noWallpaper_noBackNav_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
+ fun handleRequest_backTransition_singleTaskNoToken_noWallpaper_doesNotHandle() {
val task = setUpFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
@@ -2112,8 +2135,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun handleRequest_backTransition_singleTaskNoToken_noBackNav_doesNotHandle() {
+ fun handleRequest_backTransition_singleTaskNoToken_doesNotHandle() {
val task = setUpFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
@@ -2122,11 +2144,8 @@
}
@Test
- @DisableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_backTransition_singleTaskWithToken_noWallpaper_noBackNav_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
+ fun handleRequest_backTransition_singleTaskWithToken_noWallpaper_doesNotHandle() {
val task = setUpFreeformTask()
taskRepository.wallpaperActivityToken = MockToken().token()
@@ -2149,11 +2168,8 @@
}
@Test
- @DisableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_backTransition_multipleTasks_noWallpaper_noBackNav_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
+ fun handleRequest_backTransition_multipleTasks_noWallpaper_doesNotHandle() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -2165,7 +2181,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_multipleTasks_noBackNav_doesNotHandle() {
+ fun handleRequest_backTransition_multipleTasks_doesNotHandle() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -2211,11 +2227,8 @@
}
@Test
- @EnableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_backTransition_nonMinimizadTask_withWallpaper_withBackNav_removesWallpaper() {
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
+ fun handleRequest_backTransition_nonMinimizadTask_withWallpaper_removesWallpaper() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
@@ -2231,11 +2244,8 @@
}
@Test
- @DisableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_closeTransition_singleTaskNoToken_noWallpaper_noBackNav_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
+ fun handleRequest_closeTransition_singleTaskNoToken_noWallpaper_doesNotHandle() {
val task = setUpFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
@@ -2244,22 +2254,8 @@
}
@Test
- @EnableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_closeTransition_singleTaskNoToken_withWallpaper_withBackNav_removesTask() {
- val task = setUpFreeformTask()
-
- val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
-
- assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, task.token)
- }
-
- @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun handleRequest_closeTransition_singleTaskNoToken_noBackNav_doesNotHandle() {
+ fun handleRequest_closeTransition_singleTaskNoToken_doesNotHandle() {
val task = setUpFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
@@ -2268,11 +2264,8 @@
}
@Test
- @DisableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_closeTransition_singleTaskWithToken_noWallpaper_noBackNav_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_singleTaskWithToken_noWallpaper_doesNotHandle() {
val task = setUpFreeformTask()
taskRepository.wallpaperActivityToken = MockToken().token()
@@ -2282,26 +2275,8 @@
}
@Test
- @EnableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_closeTransition_singleTaskWithToken_removesWallpaperAndTask() {
- val task = setUpFreeformTask()
- val wallpaperToken = MockToken().token()
-
- taskRepository.wallpaperActivityToken = wallpaperToken
- val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
-
- // Should create remove wallpaper transaction
- assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
- result.assertRemoveAt(index = 1, task.token)
- }
-
- @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun handleRequest_closeTransition_singleTaskWithToken_withWallpaper_noBackNav_removesWallpaper() {
+ fun handleRequest_closeTransition_singleTaskWithToken_withWallpaper_removesWallpaper() {
val task = setUpFreeformTask()
val wallpaperToken = MockToken().token()
@@ -2313,11 +2288,8 @@
}
@Test
- @DisableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_closeTransition_multipleTasks_noWallpaper_noBackNav_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_multipleTasks_noWallpaper_doesNotHandle() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -2328,25 +2300,8 @@
}
@Test
- @EnableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_closeTransition_multipleTasks_withWallpaper_withBackNav_removesTask() {
- val task1 = setUpFreeformTask()
- setUpFreeformTask()
-
- taskRepository.wallpaperActivityToken = MockToken().token()
- val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
-
- assertNotNull(result, "Should handle request")
- result.assertRemoveAt(index = 0, task1.token)
- }
-
- @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun handleRequest_closeTransition_multipleTasksFlagEnabled_noBackNav_doesNotHandle() {
+ fun handleRequest_closeTransition_multipleTasksFlagEnabled_doesNotHandle() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -2357,28 +2312,8 @@
}
@Test
- @EnableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_closeTransition_multipleTasksSingleNonClosing_removesWallpaperAndTask() {
- val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val wallpaperToken = MockToken().token()
-
- taskRepository.wallpaperActivityToken = wallpaperToken
- taskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
- val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
-
- // Should create remove wallpaper transaction
- assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
- result.assertRemoveAt(index = 1, task1.token)
- }
-
- @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun handleRequest_closeTransition_multipleTasksSingleNonClosing_noBackNav_removesWallpaper() {
+ fun handleRequest_closeTransition_multipleTasksSingleNonClosing_removesWallpaper() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
@@ -2392,28 +2327,8 @@
}
@Test
- @EnableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_closeTransition_multipleTasksOneNonMinimized_removesWallpaperAndTask() {
- val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val wallpaperToken = MockToken().token()
-
- taskRepository.wallpaperActivityToken = wallpaperToken
- taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
- val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
-
- // Should create remove wallpaper transaction
- assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
- result.assertRemoveAt(index = 1, task1.token)
- }
-
- @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun handleRequest_closeTransition_multipleTasksSingleNonMinimized_noBackNav_removesWallpaper() {
+ fun handleRequest_closeTransition_multipleTasksSingleNonMinimized_removesWallpaper() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
@@ -2427,11 +2342,8 @@
}
@Test
- @EnableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_closeTransition_minimizadTask_withWallpaper_withBackNav_removesWallpaper() {
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
+ fun handleRequest_closeTransition_minimizadTask_withWallpaper_removesWallpaper() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index 045e077..bdcb459 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -19,6 +19,8 @@
import android.app.ActivityManager.RunningTaskInfo
import android.os.Binder
import android.os.Handler
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
@@ -33,6 +35,7 @@
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW
import com.android.internal.jank.InteractionJankMonitor
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.ShellExecutor
@@ -243,6 +246,7 @@
}
@Test
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun removeLeftoverMinimizedTasks_activeNonMinimizedTasksStillAround_doesNothing() {
desktopTaskRepo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1)
desktopTaskRepo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2)
@@ -256,6 +260,7 @@
}
@Test
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun removeLeftoverMinimizedTasks_noMinimizedTasks_doesNothing() {
val wct = WindowContainerTransaction()
desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks(
@@ -265,6 +270,7 @@
}
@Test
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun removeLeftoverMinimizedTasks_onlyMinimizedTasksLeft_removesAllMinimizedTasks() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
@@ -283,6 +289,20 @@
}
@Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun removeLeftoverMinimizedTasks_onlyMinimizedTasksLeft_backNavEnabled_doesNothing() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task1.taskId)
+ desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+
+ val wct = WindowContainerTransaction()
+ desktopTasksLimiter.leftoverMinimizedTasksRemover.onActiveTasksChanged(DEFAULT_DISPLAY)
+
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
fun addAndGetMinimizeTaskChangesIfNeeded_tasksWithinLimit_noTaskMinimized() {
(1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
@@ -291,7 +311,7 @@
desktopTasksLimiter.addAndGetMinimizeTaskChangesIfNeeded(
displayId = DEFAULT_DISPLAY,
wct = wct,
- newFrontTaskInfo = setUpFreeformTask())
+ newFrontTaskId = setUpFreeformTask().taskId)
assertThat(minimizedTaskId).isNull()
assertThat(wct.hierarchyOps).isEmpty() // No reordering operations added
@@ -307,7 +327,7 @@
desktopTasksLimiter.addAndGetMinimizeTaskChangesIfNeeded(
displayId = DEFAULT_DISPLAY,
wct = wct,
- newFrontTaskInfo = setUpFreeformTask())
+ newFrontTaskId = setUpFreeformTask().taskId)
assertThat(minimizedTaskId).isEqualTo(tasks.first())
assertThat(wct.hierarchyOps.size).isEqualTo(1)
@@ -325,7 +345,7 @@
desktopTasksLimiter.addAndGetMinimizeTaskChangesIfNeeded(
displayId = 0,
wct = wct,
- newFrontTaskInfo = setUpFreeformTask())
+ newFrontTaskId = setUpFreeformTask().taskId)
assertThat(minimizedTaskId).isNull()
assertThat(wct.hierarchyOps).isEmpty() // No reordering operations added
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index c989d16..42fcc83 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -18,11 +18,13 @@
import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.platform.test.annotations.EnableFlags
import android.view.Display.DEFAULT_DISPLAY
+import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.IWindowContainerToken
import android.window.TransitionInfo
@@ -110,6 +112,24 @@
verify(taskRepository, never()).minimizeTask(task.displayId, task.taskId)
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun removeTasks_onTaskFullscreenLaunch_taskRemovedFromRepo() {
+ val task = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)
+ whenever(taskRepository.getVisibleTaskCount(any())).thenReturn(1)
+ whenever(taskRepository.isActiveTask(task.taskId)).thenReturn(true)
+
+ transitionObserver.onTransitionReady(
+ transition = mock(),
+ info = createOpenTransition(task),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+
+ verify(taskRepository, never()).minimizeTask(task.displayId, task.taskId)
+ verify(taskRepository).removeFreeformTask(task.displayId, task.taskId)
+ }
+
private fun createBackNavigationTransition(
task: RunningTaskInfo?
): TransitionInfo {
@@ -125,11 +145,26 @@
}
}
- private fun createTaskInfo(id: Int) =
+ private fun createOpenTransition(
+ task: RunningTaskInfo?
+ ): TransitionInfo {
+ return TransitionInfo(TRANSIT_OPEN, 0 /* flags */).apply {
+ addChange(
+ Change(mock(), mock()).apply {
+ mode = TRANSIT_OPEN
+ parent = null
+ taskInfo = task
+ flags = flags
+ }
+ )
+ }
+ }
+
+ private fun createTaskInfo(id: Int, windowingMode: Int = WINDOWING_MODE_FREEFORM) =
RunningTaskInfo().apply {
taskId = id
displayId = DEFAULT_DISPLAY
- configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ configuration.windowConfiguration.windowingMode = windowingMode
token = WindowContainerToken(Mockito.mock(IWindowContainerToken::class.java))
baseIntent = Intent().apply {
component = ComponentName("package", "component.name")
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index d9387d2..230f7e6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -581,7 +581,7 @@
)
)
.thenReturn(token)
- handler.startDragToDesktopTransition(task.taskId, dragAnimator)
+ handler.startDragToDesktopTransition(task, dragAnimator)
return token
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
index 763d015..3b2c7e6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
@@ -18,15 +18,19 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Display.INVALID_DISPLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.platform.test.annotations.EnableFlags;
import android.view.SurfaceControl;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -139,6 +143,40 @@
verify(mLaunchAdjacentController).setLaunchAdjacentEnabled(true);
}
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ public void onTaskVanished_nonClosingTask_isMinimized() {
+ ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isVisible = true;
+
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ task.isVisible = false;
+ task.displayId = INVALID_DISPLAY;
+ mFreeformTaskListener.onTaskVanished(task);
+
+ verify(mDesktopModeTaskRepository).minimizeTask(task.displayId, task.taskId);
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ public void onTaskVanished_closingTask_isNotMinimized() {
+ ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isVisible = true;
+
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ when(mDesktopModeTaskRepository.isClosingTask(task.taskId)).thenReturn(true);
+ task.isVisible = false;
+ task.displayId = INVALID_DISPLAY;
+ mFreeformTaskListener.onTaskVanished(task);
+
+ verify(mDesktopModeTaskRepository, never()).minimizeTask(task.displayId, task.taskId);
+ verify(mDesktopModeTaskRepository).removeFreeformTask(task.displayId, task.taskId);
+ }
+
@After
public void tearDown() {
mMockitoSession.finishMocking();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 753d4cd..0364b51 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -22,6 +22,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import static org.junit.Assert.assertEquals;
@@ -50,6 +51,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.platform.test.annotations.DisableFlags;
@@ -441,6 +443,40 @@
}
@Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+ public void testGetRecentTasks_hasDesktopTasks_persistenceEnabled_freeformTaskHaveBoundsSet() {
+ ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
+ ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+
+ t1.lastNonFullscreenBounds = new Rect(100, 200, 300, 400);
+ t2.lastNonFullscreenBounds = new Rect(150, 250, 350, 450);
+ setRawList(t1, t2);
+
+ when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
+ when(mDesktopModeTaskRepository.isActiveTask(2)).thenReturn(true);
+
+ ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
+ MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+
+ assertEquals(1, recentTasks.size());
+ GroupedRecentTaskInfo freeformGroup = recentTasks.get(0);
+
+ // Check bounds
+ assertEquals(t1.lastNonFullscreenBounds, freeformGroup.getTaskInfoList().get(
+ 0).configuration.windowConfiguration.getAppBounds());
+ assertEquals(t2.lastNonFullscreenBounds, freeformGroup.getTaskInfoList().get(
+ 1).configuration.windowConfiguration.getAppBounds());
+
+ // Check position in parent
+ assertEquals(new Point(t1.lastNonFullscreenBounds.left,
+ t1.lastNonFullscreenBounds.top),
+ freeformGroup.getTaskInfoList().get(0).positionInParent);
+ assertEquals(new Point(t2.lastNonFullscreenBounds.left,
+ t2.lastNonFullscreenBounds.top),
+ freeformGroup.getTaskInfoList().get(1).positionInParent);
+ }
+
+ @Test
public void testRemovedTaskRemovesSplit() {
ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
@@ -623,6 +659,7 @@
private ActivityManager.RecentTaskInfo makeTaskInfo(int taskId) {
ActivityManager.RecentTaskInfo info = new ActivityManager.RecentTaskInfo();
info.taskId = taskId;
+ info.lastNonFullscreenBounds = new Rect();
return info;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 3e7f3bd..ed752fd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -619,6 +619,27 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ public void updateRelayoutParams_header_notAnInsetsSourceInFullyImmersive() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false,
+ /* isStatusBarVisible */ true,
+ /* isKeyguardVisibleAndOccluded */ false,
+ /* inFullImmersiveMode */ true,
+ new InsetsState());
+
+ assertThat(relayoutParams.mIsInsetSource).isFalse();
+ }
+
+ @Test
@DisableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
public void updateRelayoutParams_header_statusBarInvisible_captionVisible() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 94cabc4..54dd15ba 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -650,6 +650,57 @@
}
@Test
+ public void testRelayout_notAnInsetsSource_doesNotAddInsets() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setVisible(true)
+ .setBounds(new Rect(0, 0, 1000, 1000))
+ .build();
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+ mRelayoutParams.mIsInsetSource = false;
+ windowDecor.relayout(taskInfo);
+
+ // Never added.
+ verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
+ verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
+ }
+
+ @Test
+ public void testRelayout_notAnInsetsSource_hadInsetsBefore_removesInsets() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setVisible(true)
+ .setBounds(new Rect(0, 0, 1000, 1000))
+ .build();
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+ mRelayoutParams.mIsCaptionVisible = true;
+ mRelayoutParams.mIsInsetSource = true;
+ windowDecor.relayout(taskInfo);
+
+ mRelayoutParams.mIsCaptionVisible = true;
+ mRelayoutParams.mIsInsetSource = false;
+ windowDecor.relayout(taskInfo);
+
+ // Insets should be removed.
+ verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(captionBar()));
+ verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(mandatorySystemGestures()));
+ }
+
+ @Test
public void testClose_withExistingInsets_insetsRemoved() {
final Display defaultDisplay = mock(Display.class);
doReturn(defaultDisplay).when(mMockDisplayController)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt
index 5594981..6749776 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt
@@ -24,12 +24,16 @@
import android.testing.TestableLooper
import android.testing.TestableResources
import android.view.MotionEvent
+import android.view.Surface.ROTATION_180
+import android.view.Surface.ROTATION_90
import android.view.View
import android.view.WindowManager
import android.widget.TextView
+import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
import com.android.wm.shell.R
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipArrowDirection
import com.google.common.truth.Truth.assertThat
@@ -42,9 +46,11 @@
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -52,6 +58,8 @@
class DesktopWindowingEducationTooltipControllerTest : ShellTestCase() {
@Mock private lateinit var mockWindowManager: WindowManager
@Mock private lateinit var mockViewContainerFactory: AdditionalSystemViewContainer.Factory
+ @Mock private lateinit var mockDisplayController: DisplayController
+ @Mock private lateinit var mockPopupWindow: AdditionalSystemViewContainer
private lateinit var testableResources: TestableResources
private lateinit var testableContext: TestableContext
private lateinit var tooltipController: DesktopWindowingEducationTooltipController
@@ -69,7 +77,8 @@
Context.LAYOUT_INFLATER_SERVICE, context.getSystemService(Context.LAYOUT_INFLATER_SERVICE))
testableContext.addMockSystemService(WindowManager::class.java, mockWindowManager)
tooltipController =
- DesktopWindowingEducationTooltipController(testableContext, mockViewContainerFactory)
+ DesktopWindowingEducationTooltipController(
+ testableContext, mockViewContainerFactory, mockDisplayController)
}
@Test
@@ -218,6 +227,25 @@
verify(mockLambda).invoke()
}
+ @Test
+ fun showEducationTooltip_displayRotationChanged_hidesTooltip() {
+ whenever(
+ mockViewContainerFactory.create(any(), any(), any(), any(), any(), any(), any(), any()))
+ .thenReturn(mockPopupWindow)
+ val tooltipViewConfig = createTooltipConfig()
+
+ tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123)
+ tooltipController.onDisplayChange(
+ /* displayId= */ 123,
+ /* fromRotation= */ ROTATION_90,
+ /* toRotation= */ ROTATION_180,
+ /* newDisplayAreaInfo= */ null,
+ WindowContainerTransaction())
+
+ verify(mockPopupWindow, times(1)).releaseView()
+ verify(mockDisplayController, atLeastOnce()).removeDisplayChangingController(any())
+ }
+
private fun createTooltipConfig(
@LayoutRes tooltipViewLayout: Int = R.layout.desktop_windowing_education_top_arrow_tooltip,
tooltipViewGlobalCoordinates: Point = Point(0, 0),
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index bb0fc41..bc269fe 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -16,7 +16,7 @@
ctor public AppFunctionService();
method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
method @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
- method @Deprecated @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ method @Deprecated @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE";
field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
}
@@ -45,12 +45,12 @@
method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle);
field public static final String PROPERTY_RETURN_VALUE = "returnValue";
field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2
+ field public static final int RESULT_CANCELLED = 6; // 0x6
field public static final int RESULT_DENIED = 1; // 0x1
- field public static final int RESULT_DISABLED = 6; // 0x6
+ field public static final int RESULT_DISABLED = 5; // 0x5
field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
field public static final int RESULT_OK = 0; // 0x0
- field public static final int RESULT_TIMED_OUT = 5; // 0x5
}
}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
index 6023c97..6e91de6 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
@@ -26,6 +26,7 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.CancellationSignal;
+import android.util.Log;
import java.util.function.Consumer;
@@ -143,7 +144,11 @@
*/
@MainThread
@Deprecated
- public abstract void onExecuteFunction(
+ public void onExecuteFunction(
@NonNull ExecuteAppFunctionRequest request,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+ @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
+ Log.w(
+ "AppFunctionService",
+ "Calling deprecated default implementation of onExecuteFunction");
+ }
}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
index c7ce95b..d87fec79 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
@@ -73,11 +73,14 @@
*/
public static final int RESULT_INVALID_ARGUMENT = 4;
- /** The operation was timed out. */
- public static final int RESULT_TIMED_OUT = 5;
-
/** The caller tried to execute a disabled app function. */
- public static final int RESULT_DISABLED = 6;
+ public static final int RESULT_DISABLED = 5;
+
+ /**
+ * The operation was cancelled. Use this error code to report that a cancellation is done after
+ * receiving a cancellation signal.
+ */
+ public static final int RESULT_CANCELLED = 6;
/** The result code of the app function execution. */
@ResultCode private final int mResultCode;
@@ -236,7 +239,6 @@
RESULT_APP_UNKNOWN_ERROR,
RESULT_INTERNAL_ERROR,
RESULT_INVALID_ARGUMENT,
- RESULT_TIMED_OUT,
RESULT_DISABLED
})
@Retention(RetentionPolicy.SOURCE)
diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp
index 27add35..4305196 100644
--- a/libs/hwui/AutoBackendTextureRelease.cpp
+++ b/libs/hwui/AutoBackendTextureRelease.cpp
@@ -141,6 +141,13 @@
return;
}
+ if (!RenderThread::isCurrent()) {
+ // releaseQueueOwnership needs to run on RenderThread to prevent multithread calling
+ // setBackendTextureState will operate skia resource cache which need single owner
+ RenderThread::getInstance().queue().post([this, context]() { releaseQueueOwnership(context); });
+ return;
+ }
+
LOG_ALWAYS_FATAL_IF(Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan);
if (mBackendTexture.isValid()) {
// Passing in VK_IMAGE_LAYOUT_UNDEFINED means we keep the old layout.
diff --git a/libs/hwui/Gainmap.cpp b/libs/hwui/Gainmap.cpp
index 30f401e..ea955e2 100644
--- a/libs/hwui/Gainmap.cpp
+++ b/libs/hwui/Gainmap.cpp
@@ -15,12 +15,37 @@
*/
#include "Gainmap.h"
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkColorFilter.h>
+#include <SkImagePriv.h>
+#include <SkPaint.h>
+
+#include "HardwareBitmapUploader.h"
+
namespace android::uirenderer {
sp<Gainmap> Gainmap::allocateHardwareGainmap(const sp<Gainmap>& srcGainmap) {
auto gainmap = sp<Gainmap>::make();
gainmap->info = srcGainmap->info;
- const SkBitmap skSrcBitmap = srcGainmap->bitmap->getSkBitmap();
+ SkBitmap skSrcBitmap = srcGainmap->bitmap->getSkBitmap();
+ if (skSrcBitmap.info().colorType() == kAlpha_8_SkColorType &&
+ !HardwareBitmapUploader::hasAlpha8Support()) {
+ // The regular Bitmap::allocateHardwareBitmap will do a conversion that preserves channels,
+ // so alpha8 maps to the alpha channel of rgba. However, for gainmaps we will interpret
+ // the data of an rgba buffer differently as we'll only look at the rgb channels
+ // So we need to map alpha8 to rgbx_8888 essentially
+ SkBitmap bitmap;
+ bitmap.allocPixels(skSrcBitmap.info().makeColorType(kN32_SkColorType));
+ SkCanvas canvas(bitmap);
+ SkPaint paint;
+ const float alphaToOpaque[] = {0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 255};
+ paint.setColorFilter(SkColorFilters::Matrix(alphaToOpaque, SkColorFilters::Clamp::kNo));
+ canvas.drawImage(SkMakeImageFromRasterBitmap(skSrcBitmap, kNever_SkCopyPixelsMode), 0, 0,
+ SkSamplingOptions{}, &paint);
+ skSrcBitmap = bitmap;
+ }
sk_sp<Bitmap> skBitmap(Bitmap::allocateHardwareBitmap(skSrcBitmap));
if (!skBitmap.get()) {
return nullptr;
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 588463c..e074a27 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -501,18 +501,13 @@
SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination, bool isShared) {
ATRACE_CALL();
SkGainmapInfo gainmapInfo;
- std::unique_ptr<SkStream> gainmapStream;
+ std::unique_ptr<SkAndroidCodec> gainmapCodec;
{
- ATRACE_NAME("getAndroidGainmap");
- if (!mCodec->getAndroidGainmap(&gainmapInfo, &gainmapStream)) {
+ ATRACE_NAME("getGainmapAndroidCodec");
+ if (!mCodec->getGainmapAndroidCodec(&gainmapInfo, &gainmapCodec)) {
return SkCodec::kSuccess;
}
}
- auto gainmapCodec = SkAndroidCodec::MakeFromStream(std::move(gainmapStream));
- if (!gainmapCodec) {
- ALOGW("Failed to create codec for gainmap stream");
- return SkCodec::kInvalidInput;
- }
ImageDecoder decoder{std::move(gainmapCodec)};
// Gainmap inherits the origin of the containing image
decoder.mOverrideOrigin.emplace(getOrigin());
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index 785aef3..49a7f73 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -183,14 +183,8 @@
needsFineScale(fullSize.height(), decodedSize.height(), sampleSize);
}
-static bool decodeGainmap(std::unique_ptr<SkStream> gainmapStream, const SkGainmapInfo& gainmapInfo,
+static bool decodeGainmap(std::unique_ptr<SkAndroidCodec> codec, const SkGainmapInfo& gainmapInfo,
sp<uirenderer::Gainmap>* outGainmap, const int sampleSize, float scale) {
- std::unique_ptr<SkAndroidCodec> codec;
- codec = SkAndroidCodec::MakeFromStream(std::move(gainmapStream), nullptr);
- if (!codec) {
- ALOGE("Can not create a codec for Gainmap.");
- return false;
- }
SkColorType decodeColorType = kN32_SkColorType;
if (codec->getInfo().colorType() == kGray_8_SkColorType) {
decodeColorType = kGray_8_SkColorType;
@@ -613,15 +607,15 @@
bool hasGainmap = false;
SkGainmapInfo gainmapInfo;
- std::unique_ptr<SkStream> gainmapStream = nullptr;
+ std::unique_ptr<SkAndroidCodec> gainmapCodec;
sp<uirenderer::Gainmap> gainmap = nullptr;
if (result == SkCodec::kSuccess) {
- hasGainmap = codec->getAndroidGainmap(&gainmapInfo, &gainmapStream);
+ hasGainmap = codec->getGainmapAndroidCodec(&gainmapInfo, &gainmapCodec);
}
if (hasGainmap) {
hasGainmap =
- decodeGainmap(std::move(gainmapStream), gainmapInfo, &gainmap, sampleSize, scale);
+ decodeGainmap(std::move(gainmapCodec), gainmapInfo, &gainmap, sampleSize, scale);
}
if (!isMutable && javaBitmap == NULL) {
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index 6a65b82..f7e8e07 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -48,25 +48,14 @@
}
SkGainmapInfo gainmapInfo;
- std::unique_ptr<SkStream> gainmapStream;
+ std::unique_ptr<SkAndroidCodec> gainmapCodec;
std::unique_ptr<skia::BitmapRegionDecoder> gainmapBRD = nullptr;
- if (mainImageBRD->getAndroidGainmap(&gainmapInfo, &gainmapStream)) {
- sk_sp<SkData> data = nullptr;
- if (gainmapStream->getMemoryBase()) {
- // It is safe to make without copy because we'll hold onto the stream.
- data = SkData::MakeWithoutCopy(gainmapStream->getMemoryBase(),
- gainmapStream->getLength());
- } else {
- data = SkCopyStreamToData(gainmapStream.get());
- // We don't need to hold the stream anymore
- gainmapStream = nullptr;
- }
- gainmapBRD = skia::BitmapRegionDecoder::Make(std::move(data));
+ if (!mainImageBRD->getGainmapBitmapRegionDecoder(&gainmapInfo, &gainmapBRD)) {
+ gainmapBRD = nullptr;
}
- return std::unique_ptr<BitmapRegionDecoderWrapper>(
- new BitmapRegionDecoderWrapper(std::move(mainImageBRD), std::move(gainmapBRD),
- gainmapInfo, std::move(gainmapStream)));
+ return std::unique_ptr<BitmapRegionDecoderWrapper>(new BitmapRegionDecoderWrapper(
+ std::move(mainImageBRD), std::move(gainmapBRD), gainmapInfo));
}
SkEncodedImageFormat getEncodedFormat() { return mMainImageBRD->getEncodedFormat(); }
@@ -191,16 +180,14 @@
private:
BitmapRegionDecoderWrapper(std::unique_ptr<skia::BitmapRegionDecoder> mainImageBRD,
std::unique_ptr<skia::BitmapRegionDecoder> gainmapBRD,
- SkGainmapInfo info, std::unique_ptr<SkStream> stream)
+ SkGainmapInfo info)
: mMainImageBRD(std::move(mainImageBRD))
, mGainmapBRD(std::move(gainmapBRD))
- , mGainmapInfo(info)
- , mGainmapStream(std::move(stream)) {}
+ , mGainmapInfo(info) {}
std::unique_ptr<skia::BitmapRegionDecoder> mMainImageBRD;
std::unique_ptr<skia::BitmapRegionDecoder> mGainmapBRD;
SkGainmapInfo mGainmapInfo;
- std::unique_ptr<SkStream> mGainmapStream;
};
} // namespace android
diff --git a/libs/hwui/jni/graphics_jni_helpers.h b/libs/hwui/jni/graphics_jni_helpers.h
index 78db54a..91db134 100644
--- a/libs/hwui/jni/graphics_jni_helpers.h
+++ b/libs/hwui/jni/graphics_jni_helpers.h
@@ -80,9 +80,52 @@
return static_cast<T>(res);
}
+// Inline variable that specifies the method binding format.
+// The expected format is 'XX${method}XX', where ${method} represents the original method name.
+// This variable is shared across all translation units. This is treated as a global variable as
+// per C++ 17.
+inline std::string jniMethodFormat;
+
+inline static void setJniMethodFormat(std::string value) {
+ jniMethodFormat = value;
+}
+
+// Register the native methods, potenially applying the jniMethodFormat if it has been set.
+static inline int jniRegisterMaybeRenamedNativeMethods(JNIEnv* env, const char* className,
+ const JNINativeMethod* gMethods,
+ int numMethods) {
+ if (jniMethodFormat.empty()) {
+ return jniRegisterNativeMethods(env, className, gMethods, numMethods);
+ }
+
+ // Make a copy of gMethods with reformatted method names.
+ JNINativeMethod* modifiedMethods = new JNINativeMethod[numMethods];
+ LOG_ALWAYS_FATAL_IF(!modifiedMethods, "Failed to allocate a copy of the JNI methods");
+
+ size_t methodNamePos = jniMethodFormat.find("${method}");
+ LOG_ALWAYS_FATAL_IF(methodNamePos == std::string::npos,
+ "Invalid jniMethodFormat: could not find '${method}' in pattern");
+
+ for (int i = 0; i < numMethods; i++) {
+ modifiedMethods[i] = gMethods[i];
+ std::string modifiedName = jniMethodFormat;
+ modifiedName.replace(methodNamePos, 9, gMethods[i].name);
+ char* modifiedNameChars = new char[modifiedName.length() + 1];
+ LOG_ALWAYS_FATAL_IF(!modifiedNameChars, "Failed to allocate the new method name");
+ std::strcpy(modifiedNameChars, modifiedName.c_str());
+ modifiedMethods[i].name = modifiedNameChars;
+ }
+ int res = jniRegisterNativeMethods(env, className, modifiedMethods, numMethods);
+ for (int i = 0; i < numMethods; i++) {
+ delete[] modifiedMethods[i].name;
+ }
+ delete[] modifiedMethods;
+ return res;
+}
+
static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods) {
- int res = jniRegisterNativeMethods(env, className, gMethods, numMethods);
+ int res = jniRegisterMaybeRenamedNativeMethods(env, className, gMethods, numMethods);
LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
return res;
}
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index 9899e4e..83a4dd5 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -22,7 +22,6 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.res.Resources;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -57,8 +56,6 @@
}
};
- private static final String TAG = "RoutingSessionInfo";
-
private static final String KEY_GROUP_ROUTE = "androidx.mediarouter.media.KEY_GROUP_ROUTE";
private static final String KEY_VOLUME_HANDLING = "volumeHandling";
@@ -142,15 +139,7 @@
mVolume = builder.mVolume;
mIsSystemSession = builder.mIsSystemSession;
-
- boolean volumeAdjustmentForRemoteGroupSessions = Resources.getSystem().getBoolean(
- com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
- mVolumeHandling =
- defineVolumeHandling(
- mIsSystemSession,
- builder.mVolumeHandling,
- mSelectedRoutes,
- volumeAdjustmentForRemoteGroupSessions);
+ mVolumeHandling = builder.mVolumeHandling;
mControlHints = updateVolumeHandlingInHints(builder.mControlHints, mVolumeHandling);
mTransferReason = builder.mTransferReason;
@@ -207,20 +196,6 @@
return controlHints;
}
- @MediaRoute2Info.PlaybackVolume
- private static int defineVolumeHandling(
- boolean isSystemSession,
- @MediaRoute2Info.PlaybackVolume int volumeHandling,
- List<String> selectedRoutes,
- boolean volumeAdjustmentForRemoteGroupSessions) {
- if (!isSystemSession
- && !volumeAdjustmentForRemoteGroupSessions
- && selectedRoutes.size() > 1) {
- return MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
- }
- return volumeHandling;
- }
-
@NonNull
private static String ensureString(@Nullable String str) {
return str != null ? str : "";
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java
index 3955ff0..5f5058d 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java
@@ -18,8 +18,6 @@
import static com.google.common.truth.Truth.assertThat;
-import android.content.res.Resources;
-import android.media.MediaRoute2Info;
import android.media.RoutingSessionInfo;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -95,24 +93,4 @@
assertThat(sessionInfoWithProviderId2.getTransferableRoutes())
.isEqualTo(sessionInfoWithProviderId.getTransferableRoutes());
}
-
- @Test
- public void testGetVolumeHandlingGroupSession() {
- RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
- TEST_ID, TEST_CLIENT_PACKAGE_NAME)
- .setName(TEST_NAME)
- .addSelectedRoute(TEST_ROUTE_ID_0)
- .addSelectedRoute(TEST_ROUTE_ID_2)
- .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
- .build();
-
- boolean volumeAdjustmentForRemoteGroupSessions = Resources.getSystem().getBoolean(
- com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
-
- int expectedResult = volumeAdjustmentForRemoteGroupSessions
- ? MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE :
- MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
-
- assertThat(sessionInfo.getVolumeHandling()).isEqualTo(expectedResult);
- }
}
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index e7cb76c..96b7c13 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -223,6 +223,7 @@
field public static final String CATEGORY_PAYMENT = "payment";
field public static final String EXTRA_CATEGORY = "category";
field public static final String EXTRA_SERVICE_COMPONENT = "component";
+ field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_DEFAULT = 3; // 0x3
field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_DH = 0; // 0x0
field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE = 1; // 0x1
field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC = 2; // 0x2
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 94231b0..4428ade 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -58,12 +58,16 @@
@FlaggedApi("android.nfc.nfc_oem_extension") public final class NfcOemExtension {
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void clearPreference();
method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull public java.util.List<java.lang.String> getActiveNfceeList();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.nfc.RoutingStatus getRoutingStatus();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean hasUserEnabledNfc();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isAutoChangeEnabled();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagPresent();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void maybeTriggerFirmwareUpdate();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void overwriteRoutingTable(int, int, int);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void pausePolling(int);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcOemExtension.Callback);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void resumePolling();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAutoChangeEnabled(boolean);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void setControllerAlwaysOnMode(int);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void synchronizeScreenState();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void triggerInitialization();
@@ -90,7 +94,11 @@
method public void onEnable(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onEnableFinished(int);
method public void onEnableStarted();
+ method public void onGetOemAppSearchIntent(@NonNull java.util.List<java.lang.String>, @NonNull java.util.function.Consumer<android.content.Intent>);
method public void onHceEventReceived(int);
+ method public void onLaunchHceAppChooserActivity(@NonNull String, @NonNull java.util.List<android.nfc.cardemulation.ApduServiceInfo>, @NonNull android.content.ComponentName, @NonNull String);
+ method public void onLaunchHceTapAgainDialog(@NonNull android.nfc.cardemulation.ApduServiceInfo, @NonNull String);
+ method public void onNdefMessage(@NonNull android.nfc.Tag, @NonNull android.nfc.NdefMessage, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onNdefRead(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onReaderOptionChanged(boolean);
method public void onRfDiscoveryStarted(boolean);
@@ -101,6 +109,12 @@
method public void onTagDispatch(@NonNull java.util.function.Consumer<java.lang.Boolean>);
}
+ @FlaggedApi("android.nfc.nfc_oem_extension") public class RoutingStatus {
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int getDefaultIsoDepRoute();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int getDefaultOffHostRoute();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int getDefaultRoute();
+ }
+
}
package android.nfc.cardemulation {
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
index 19b9e0f..1eae3c6 100644
--- a/nfc/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -51,4 +51,8 @@
void overrideRoutingTable(int userHandle, String protocol, String technology, in String pkg);
void recoverRoutingTable(int userHandle);
boolean isEuiccSupported();
+ void setAutoChangeStatus(boolean state);
+ boolean isAutoChangeEnabled();
+ List<String> getRoutingStatus();
+ void overwriteRoutingTable(int userHandle, String emptyAid, String protocol, String tech);
}
diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
index e49ef7e..48c7ee6 100644
--- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
+++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
@@ -15,9 +15,14 @@
*/
package android.nfc;
+import android.content.ComponentName;
+import android.nfc.cardemulation.ApduServiceInfo;
+import android.nfc.NdefMessage;
import android.nfc.Tag;
import android.os.ResultReceiver;
+import java.util.List;
+
/**
* @hide
*/
@@ -41,4 +46,8 @@
void onCardEmulationActivated(boolean isActivated);
void onRfFieldActivated(boolean isActivated);
void onRfDiscoveryStarted(boolean isDiscoveryStarted);
+ void onGetOemAppSearchIntent(in List<String> firstPackage, in ResultReceiver intentConsumer);
+ void onNdefMessage(in Tag tag, in NdefMessage message, in ResultReceiver hasOemExecutableContent);
+ void onLaunchHceAppChooserActivity(in String selectedAid, in List<ApduServiceInfo> services, in ComponentName failedComponent, in String category);
+ void onLaunchHceTapAgainActivity(in ApduServiceInfo service, in String category);
}
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index 6d5c069..fb63b5c 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -16,6 +16,12 @@
package android.nfc;
+import static android.nfc.cardemulation.CardEmulation.PROTOCOL_AND_TECHNOLOGY_ROUTE_DH;
+import static android.nfc.cardemulation.CardEmulation.PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE;
+import static android.nfc.cardemulation.CardEmulation.PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC;
+import static android.nfc.cardemulation.CardEmulation.routeIntToString;
+
+import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -23,8 +29,14 @@
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.nfc.cardemulation.ApduServiceInfo;
+import android.nfc.cardemulation.CardEmulation;
+import android.nfc.cardemulation.CardEmulation.ProtocolAndTechnologyRoute;
import android.os.Binder;
+import android.os.Bundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.util.Log;
@@ -306,6 +318,60 @@
* @param isDiscoveryStarted true, if RF discovery started, else RF state is Idle.
*/
void onRfDiscoveryStarted(boolean isDiscoveryStarted);
+
+ /**
+ * Gets the intent to find the OEM package in the OEM App market. If the consumer returns
+ * {@code null} or a timeout occurs, the intent from the first available package will be
+ * used instead.
+ *
+ * @param packages the OEM packages name stored in the tag
+ * @param intentConsumer The {@link Consumer} to be completed.
+ * The {@link Consumer#accept(Object)} should be called with
+ * the Intent required.
+ *
+ */
+ void onGetOemAppSearchIntent(@NonNull List<String> packages,
+ @NonNull Consumer<Intent> intentConsumer);
+
+ /**
+ * Checks if the NDEF message contains any specific OEM package executable content
+ *
+ * @param tag the {@link android.nfc.Tag Tag}
+ * @param message NDEF Message to read from tag
+ * @param hasOemExecutableContent The {@link Consumer} to be completed. If there is
+ * OEM package executable content, the
+ * {@link Consumer#accept(Object)} should be called with
+ * {@link Boolean#TRUE}, otherwise call with
+ * {@link Boolean#FALSE}.
+ */
+ void onNdefMessage(@NonNull Tag tag, @NonNull NdefMessage message,
+ @NonNull Consumer<Boolean> hasOemExecutableContent);
+
+ /**
+ * Callback to indicate the app chooser activity should be launched for handling CE
+ * transaction. This is invoked for example when there are more than 1 app installed that
+ * can handle the HCE transaction. OEMs can launch the Activity based
+ * on their requirement.
+ *
+ * @param selectedAid the selected AID from APDU
+ * @param services {@link ApduServiceInfo} of the service triggering the activity
+ * @param failedComponent the component failed to be resolved
+ * @param category the category of the service
+ */
+ void onLaunchHceAppChooserActivity(@NonNull String selectedAid,
+ @NonNull List<ApduServiceInfo> services,
+ @NonNull ComponentName failedComponent,
+ @NonNull String category);
+
+ /**
+ * Callback to indicate tap again dialog should be launched for handling HCE transaction.
+ * This is invoked for example when a CE service needs the device to unlocked before
+ * handling the transaction. OEMs can launch the Activity based on their requirement.
+ *
+ * @param service {@link ApduServiceInfo} of the service triggering the dialog
+ * @param category the category of the service
+ */
+ void onLaunchHceTapAgainDialog(@NonNull ApduServiceInfo service, @NonNull String category);
}
@@ -523,6 +589,85 @@
NfcAdapter.callService(() -> NfcAdapter.sService.resumePolling());
}
+ /**
+ * Set whether to enable auto routing change or not (enabled by default).
+ * If disabled, routing targets are limited to a single off-host destination.
+ *
+ * @param state status of auto routing change, true if enable, otherwise false
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ public void setAutoChangeEnabled(boolean state) {
+ NfcAdapter.callService(() ->
+ NfcAdapter.sCardEmulationService.setAutoChangeStatus(state));
+ }
+
+ /**
+ * Check if auto routing change is enabled or not.
+ *
+ * @return true if enabled, otherwise false
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ public boolean isAutoChangeEnabled() {
+ return NfcAdapter.callServiceReturn(() ->
+ NfcAdapter.sCardEmulationService.isAutoChangeEnabled(), false);
+ }
+
+ /**
+ * Get current routing status
+ *
+ * @return {@link RoutingStatus} indicating the default route, default ISO-DEP
+ * route and default off-host route.
+ */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ public RoutingStatus getRoutingStatus() {
+ List<String> status = NfcAdapter.callServiceReturn(() ->
+ NfcAdapter.sCardEmulationService.getRoutingStatus(), new ArrayList<>());
+ return new RoutingStatus(routeStringToInt(status.get(0)),
+ routeStringToInt(status.get(1)),
+ routeStringToInt(status.get(2)));
+ }
+
+ /**
+ * Overwrites NFC controller routing table, which includes Protocol Route, Technology Route,
+ * and Empty AID Route.
+ *
+ * The parameter set to
+ * {@link ProtocolAndTechnologyRoute#PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET}
+ * can be used to keep current values for that entry. At least one route should be overridden
+ * when calling this API, otherwise throw {@link IllegalArgumentException}.
+ *
+ * @param protocol ISO-DEP route destination, where the possible inputs are defined in
+ * {@link ProtocolAndTechnologyRoute}.
+ * @param technology Tech-A, Tech-B and Tech-F route destination, where the possible inputs
+ * are defined in
+ * {@link ProtocolAndTechnologyRoute}
+ * @param emptyAid Zero-length AID route destination, where the possible inputs are defined in
+ * {@link ProtocolAndTechnologyRoute}
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ public void overwriteRoutingTable(
+ @CardEmulation.ProtocolAndTechnologyRoute int protocol,
+ @CardEmulation.ProtocolAndTechnologyRoute int technology,
+ @CardEmulation.ProtocolAndTechnologyRoute int emptyAid) {
+
+ String protocolRoute = routeIntToString(protocol);
+ String technologyRoute = routeIntToString(technology);
+ String emptyAidRoute = routeIntToString(emptyAid);
+
+ NfcAdapter.callService(() ->
+ NfcAdapter.sCardEmulationService.overwriteRoutingTable(
+ mContext.getUser().getIdentifier(),
+ emptyAidRoute,
+ protocolRoute,
+ technologyRoute
+ ));
+ }
+
private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub {
@Override
@@ -562,25 +707,25 @@
public void onApplyRouting(ResultReceiver isSkipped) throws RemoteException {
mCallbackMap.forEach((cb, ex) ->
handleVoidCallback(
- new ReceiverWrapper(isSkipped), cb::onApplyRouting, ex));
+ new ReceiverWrapper<>(isSkipped), cb::onApplyRouting, ex));
}
@Override
public void onNdefRead(ResultReceiver isSkipped) throws RemoteException {
mCallbackMap.forEach((cb, ex) ->
handleVoidCallback(
- new ReceiverWrapper(isSkipped), cb::onNdefRead, ex));
+ new ReceiverWrapper<>(isSkipped), cb::onNdefRead, ex));
}
@Override
public void onEnable(ResultReceiver isAllowed) throws RemoteException {
mCallbackMap.forEach((cb, ex) ->
handleVoidCallback(
- new ReceiverWrapper(isAllowed), cb::onEnable, ex));
+ new ReceiverWrapper<>(isAllowed), cb::onEnable, ex));
}
@Override
public void onDisable(ResultReceiver isAllowed) throws RemoteException {
mCallbackMap.forEach((cb, ex) ->
handleVoidCallback(
- new ReceiverWrapper(isAllowed), cb::onDisable, ex));
+ new ReceiverWrapper<>(isAllowed), cb::onDisable, ex));
}
@Override
public void onBootStarted() throws RemoteException {
@@ -616,7 +761,7 @@
public void onTagDispatch(ResultReceiver isSkipped) throws RemoteException {
mCallbackMap.forEach((cb, ex) ->
handleVoidCallback(
- new ReceiverWrapper(isSkipped), cb::onTagDispatch, ex));
+ new ReceiverWrapper<>(isSkipped), cb::onTagDispatch, ex));
}
@Override
public void onRoutingChanged() throws RemoteException {
@@ -635,6 +780,59 @@
handleVoidCallback(enabled, cb::onReaderOptionChanged, ex));
}
+ @Override
+ public void onGetOemAppSearchIntent(List<String> packages, ResultReceiver intentConsumer)
+ throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoid2ArgCallback(packages, new ReceiverWrapper<>(intentConsumer),
+ cb::onGetOemAppSearchIntent, ex));
+ }
+
+ @Override
+ public void onNdefMessage(Tag tag, NdefMessage message,
+ ResultReceiver hasOemExecutableContent) throws RemoteException {
+ mCallbackMap.forEach((cb, ex) -> {
+ synchronized (mLock) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ ex.execute(() -> cb.onNdefMessage(
+ tag, message, new ReceiverWrapper<>(hasOemExecutableContent)));
+ } catch (RuntimeException exception) {
+ throw exception;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onLaunchHceAppChooserActivity(String selectedAid,
+ List<ApduServiceInfo> services,
+ ComponentName failedComponent, String category)
+ throws RemoteException {
+ mCallbackMap.forEach((cb, ex) -> {
+ synchronized (mLock) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ ex.execute(() -> cb.onLaunchHceAppChooserActivity(
+ selectedAid, services, failedComponent, category));
+ } catch (RuntimeException exception) {
+ throw exception;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onLaunchHceTapAgainActivity(ApduServiceInfo service, String category)
+ throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoid2ArgCallback(service, category, cb::onLaunchHceTapAgainDialog, ex));
+ }
+
private <T> void handleVoidCallback(
T input, Consumer<T> callbackMethod, Executor executor) {
synchronized (mLock) {
@@ -718,7 +916,16 @@
}
}
- private class ReceiverWrapper implements Consumer<Boolean> {
+ private @CardEmulation.ProtocolAndTechnologyRoute int routeStringToInt(String route) {
+ return switch (route) {
+ case "DH" -> PROTOCOL_AND_TECHNOLOGY_ROUTE_DH;
+ case "eSE" -> PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE;
+ case "SIM" -> PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC;
+ default -> throw new IllegalStateException("Unexpected value: " + route);
+ };
+ }
+
+ private class ReceiverWrapper<T> implements Consumer<T> {
private final ResultReceiver mResultReceiver;
ReceiverWrapper(ResultReceiver resultReceiver) {
@@ -726,12 +933,19 @@
}
@Override
- public void accept(Boolean result) {
- mResultReceiver.send(result ? 1 : 0, null);
+ public void accept(T result) {
+ if (result instanceof Boolean) {
+ mResultReceiver.send((Boolean) result ? 1 : 0, null);
+ } else if (result instanceof Intent) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable("intent", (Intent) result);
+ mResultReceiver.send(0, bundle);
+ }
+
}
@Override
- public Consumer<Boolean> andThen(Consumer<? super Boolean> after) {
+ public Consumer<T> andThen(Consumer<? super T> after) {
return Consumer.super.andThen(after);
}
}
diff --git a/nfc/java/android/nfc/RoutingStatus.java b/nfc/java/android/nfc/RoutingStatus.java
new file mode 100644
index 0000000..4a1b1f3
--- /dev/null
+++ b/nfc/java/android/nfc/RoutingStatus.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.nfc;
+
+import android.annotation.FlaggedApi;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.nfc.cardemulation.CardEmulation;
+
+/**
+ * A class indicating default route, ISO-DEP route and off-host route.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+public class RoutingStatus {
+ private final @CardEmulation.ProtocolAndTechnologyRoute int mDefaultRoute;
+ private final @CardEmulation.ProtocolAndTechnologyRoute int mDefaultIsoDepRoute;
+ private final @CardEmulation.ProtocolAndTechnologyRoute int mDefaultOffHostRoute;
+
+ RoutingStatus(@CardEmulation.ProtocolAndTechnologyRoute int mDefaultRoute,
+ @CardEmulation.ProtocolAndTechnologyRoute int mDefaultIsoDepRoute,
+ @CardEmulation.ProtocolAndTechnologyRoute int mDefaultOffHostRoute) {
+ this.mDefaultRoute = mDefaultRoute;
+ this.mDefaultIsoDepRoute = mDefaultIsoDepRoute;
+ this.mDefaultOffHostRoute = mDefaultOffHostRoute;
+ }
+
+ /**
+ * Getter of the default route.
+ * @return an integer defined in
+ * {@link android.nfc.cardemulation.CardEmulation.ProtocolAndTechnologyRoute}
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @CardEmulation.ProtocolAndTechnologyRoute
+ public int getDefaultRoute() {
+ return mDefaultRoute;
+ }
+
+ /**
+ * Getter of the default ISO-DEP route.
+ * @return an integer defined in
+ * {@link android.nfc.cardemulation.CardEmulation.ProtocolAndTechnologyRoute}
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @CardEmulation.ProtocolAndTechnologyRoute
+ public int getDefaultIsoDepRoute() {
+ return mDefaultIsoDepRoute;
+ }
+
+ /**
+ * Getter of the default off-host route.
+ * @return an integer defined in
+ * {@link android.nfc.cardemulation.CardEmulation.ProtocolAndTechnologyRoute}
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @CardEmulation.ProtocolAndTechnologyRoute
+ public int getDefaultOffHostRoute() {
+ return mDefaultOffHostRoute;
+ }
+
+}
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index 4be082c..d8f04c5 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -168,6 +168,12 @@
public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC = 2;
/**
+ * Route to the default value in config file.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
+ public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_DEFAULT = 3;
+
+ /**
* Route unset.
*/
@FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
@@ -895,45 +901,47 @@
PROTOCOL_AND_TECHNOLOGY_ROUTE_DH,
PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE,
PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC,
- PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET
+ PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET,
+ PROTOCOL_AND_TECHNOLOGY_ROUTE_DEFAULT
})
@Retention(RetentionPolicy.SOURCE)
public @interface ProtocolAndTechnologyRoute {}
- /**
- * Setting NFC controller routing table, which includes Protocol Route and Technology Route,
- * while this Activity is in the foreground.
- *
- * The parameter set to {@link #PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET}
- * can be used to keep current values for that entry. Either
- * Protocol Route or Technology Route should be override when calling this API, otherwise
- * throw {@link IllegalArgumentException}.
- * <p>
- * Example usage in an Activity that requires to set proto route to "ESE" and keep tech route:
- * <pre>
- * protected void onResume() {
- * mNfcAdapter.overrideRoutingTable(
- * this, {@link #PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE}, null);
- * }</pre>
- * </p>
- * Also activities must call {@link #recoverRoutingTable(Activity)}
- * when it goes to the background. Only the package of the
- * currently preferred service (the service set as preferred by the current foreground
- * application via {@link CardEmulation#setPreferredService(Activity, ComponentName)} or the
- * current Default Wallet Role Holder {@link RoleManager#ROLE_WALLET}),
- * otherwise a call to this method will fail and throw {@link SecurityException}.
- * @param activity The Activity that requests NFC controller routing table to be changed.
- * @param protocol ISO-DEP route destination, where the possible inputs are defined
- * in {@link ProtocolAndTechnologyRoute}.
- * @param technology Tech-A, Tech-B and Tech-F route destination, where the possible inputs
- * are defined in {@link ProtocolAndTechnologyRoute}
- * @throws SecurityException if the caller is not the preferred NFC service
- * @throws IllegalArgumentException if the activity is not resumed or the caller is not in the
- * foreground.
- * <p>
- * This is a high risk API and only included to support mainline effort
- * @hide
- */
+ /**
+ * Setting NFC controller routing table, which includes Protocol Route and Technology Route,
+ * while this Activity is in the foreground.
+ *
+ * The parameter set to {@link #PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET}
+ * can be used to keep current values for that entry. Either
+ * Protocol Route or Technology Route should be override when calling this API, otherwise
+ * throw {@link IllegalArgumentException}.
+ * <p>
+ * Example usage in an Activity that requires to set proto route to "ESE" and keep tech route:
+ * <pre>
+ * protected void onResume() {
+ * mNfcAdapter.overrideRoutingTable(
+ * this, {@link #PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE},
+ * {@link #PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET});
+ * }</pre>
+ * </p>
+ * Also activities must call {@link #recoverRoutingTable(Activity)}
+ * when it goes to the background. Only the package of the
+ * currently preferred service (the service set as preferred by the current foreground
+ * application via {@link CardEmulation#setPreferredService(Activity, ComponentName)} or the
+ * current Default Wallet Role Holder {@link RoleManager#ROLE_WALLET}),
+ * otherwise a call to this method will fail and throw {@link SecurityException}.
+ * @param activity The Activity that requests NFC controller routing table to be changed.
+ * @param protocol ISO-DEP route destination, where the possible inputs are defined
+ * in {@link ProtocolAndTechnologyRoute}.
+ * @param technology Tech-A, Tech-B and Tech-F route destination, where the possible inputs
+ * are defined in {@link ProtocolAndTechnologyRoute}
+ * @throws SecurityException if the caller is not the preferred NFC service
+ * @throws IllegalArgumentException if the activity is not resumed or the caller is not in the
+ * foreground.
+ * <p>
+ * This is a high risk API and only included to support mainline effort
+ * @hide
+ */
@SystemApi
@FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
public void overrideRoutingTable(
@@ -942,26 +950,14 @@
if (!activity.isResumed()) {
throw new IllegalArgumentException("Activity must be resumed.");
}
- String protocolRoute = switch (protocol) {
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_DH -> "DH";
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE -> "ESE";
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC -> "UICC";
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET -> null;
- default -> throw new IllegalStateException("Unexpected value: " + protocol);
- };
- String technologyRoute = switch (technology) {
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_DH -> "DH";
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE -> "ESE";
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC -> "UICC";
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET -> null;
- default -> throw new IllegalStateException("Unexpected value: " + protocol);
- };
+ String protocolRoute = routeIntToString(protocol);
+ String technologyRoute = routeIntToString(technology);
callService(() ->
sService.overrideRoutingTable(
- mContext.getUser().getIdentifier(),
- protocolRoute,
- technologyRoute,
- mContext.getPackageName()));
+ mContext.getUser().getIdentifier(),
+ protocolRoute,
+ technologyRoute,
+ mContext.getPackageName()));
}
/**
@@ -1068,4 +1064,16 @@
}
return defaultReturn;
}
+
+ /** @hide */
+ public static String routeIntToString(@ProtocolAndTechnologyRoute int route) {
+ return switch (route) {
+ case PROTOCOL_AND_TECHNOLOGY_ROUTE_DH -> "DH";
+ case PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE -> "eSE";
+ case PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC -> "SIM";
+ case PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET -> null;
+ case PROTOCOL_AND_TECHNOLOGY_ROUTE_DEFAULT -> "default";
+ default -> throw new IllegalStateException("Unexpected value: " + route);
+ };
+ }
}
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index a57d6eb..b266912 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -50,13 +50,13 @@
<!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
<!-- Confirmation for associating an application with a companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
- <string name="title_app_streaming">Allow <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to stream your <xliff:g id="device_type" example="phone">%2$s</xliff:g>\u2019s apps to <strong><xliff:g id="device_name" example="Chromebook">%3$s</xliff:g></strong>?</string>
+ <string name="title_app_streaming">Allow <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to stream your <xliff:g id="device_type" example="phone">%2$s</xliff:g>\u2019s apps and system features to <strong><xliff:g id="device_name" example="Chromebook">%3$s</xliff:g></strong>?</string>
<!-- Summary for associating an application with a companion device of APP_STREAMING profile [CHAR LIMIT=NONE] -->
- <string name="summary_app_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will have access to anything that’s visible or played on the <xliff:g id="device_type" example="phone">%2$s</xliff:g>, including audio, photos, passwords, and messages.<br/><br/><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will be able to stream apps to <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g> until you remove access to this permission.</string>
+ <string name="summary_app_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will have access to anything that’s visible or played on your <xliff:g id="device_type" example="phone">%2$s</xliff:g>, including audio, photos, payment info, passwords, and messages.<br/><br/><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will be able to stream apps to <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g> until you remove access to this permission.</string>
<!-- Description of the helper dialog for APP_STREAMING profile. [CHAR LIMIT=NONE] -->
- <string name="helper_summary_app_streaming"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_name" example="Chromebook">%2$s</xliff:g> to display and stream apps between your devices</string>
+ <string name="helper_summary_app_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> is requesting permission on behalf of <xliff:g id="device_name" example="Chromebook">%2$s</xliff:g> to stream apps and system features from your <xliff:g id="device_type" example="phone">%3$s</xliff:g></string>
<!-- ================= DEVICE_PROFILE_AUTOMOTIVE_PROJECTION ================= -->
@@ -80,13 +80,13 @@
<!-- ================= DEVICE_PROFILE_NEARBY_DEVICE_STREAMING ================= -->
<!-- Confirmation for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile (type) [CHAR LIMIT=NONE] -->
- <string name="title_nearby_device_streaming">Allow <strong><xliff:g id="device_name" example="NearbyStreamer">%1$s</xliff:g></strong> to stream your <xliff:g id="device_type" example="phone">%2$s</xliff:g>\u2019s apps and system features to <strong><xliff:g id="device_name" example="Chromebook">%3$s</xliff:g></strong>?</string>
+ <string name="title_nearby_device_streaming">Allow <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to stream your <xliff:g id="device_type" example="phone">%2$s</xliff:g>\u2019s apps to <strong><xliff:g id="device_name" example="Chromebook">%3$s</xliff:g></strong>?</string>
<!-- Summary for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile [CHAR LIMIT=NONE] -->
- <string name="summary_nearby_device_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will have access to anything that’s visible or played on your <xliff:g id="device_type" example="phone">%2$s</xliff:g>, including audio, photos, payment info, passwords, and messages.<br/><br/><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will be able to stream apps and system features to <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g> until you remove access to this permission.</string>
+ <string name="summary_nearby_device_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will have access to anything that’s visible or played on <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g>, including audio, photos, payment info, passwords, and messages.<br/><br/><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will be able to stream apps to <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g> until you remove access to this permission.</string>
<!-- Description of the helper dialog for NEARBY_DEVICE_STREAMING profile. [CHAR LIMIT=NONE] -->
- <string name="helper_summary_nearby_device_streaming"><xliff:g id="app_name" example="NearbyStreamerApp">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_name" example="NearbyDevice">%2$s</xliff:g> to stream apps and other system features between your devices</string>
+ <string name="helper_summary_nearby_device_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> is requesting permission on behalf of <xliff:g id="device_name" example="Chromebook">%2$s</xliff:g> to stream apps from your <xliff:g id="device_type" example="phone">%3$s</xliff:g></string>
<!-- ================= null profile ================= -->
diff --git a/packages/SettingsLib/ButtonPreference/res/values-v35/attrs_expressive.xml b/packages/SettingsLib/ButtonPreference/res/values-v35/attrs_expressive.xml
deleted file mode 100644
index a1761e5..0000000
--- a/packages/SettingsLib/ButtonPreference/res/values-v35/attrs_expressive.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2024 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
- <declare-styleable name="ButtonPreference">
- <attr name="buttonType" format="enum">
- <enum name="filled" value="0"/>
- <enum name="tonal" value="1"/>
- <enum name="outline" value="2"/>
- </attr>
- <attr name="buttonSize" format="enum">
- <enum name="normal" value="0"/>
- <enum name="large" value="1"/>
- <enum name="extra" value="2"/>
- </attr>
- </declare-styleable>
-</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/values/attrs.xml b/packages/SettingsLib/ButtonPreference/res/values/attrs.xml
index 9c1e503f..970eeb2 100644
--- a/packages/SettingsLib/ButtonPreference/res/values/attrs.xml
+++ b/packages/SettingsLib/ButtonPreference/res/values/attrs.xml
@@ -18,12 +18,12 @@
<resources>
<declare-styleable name="ButtonPreference">
<attr name="android:gravity" />
- <attr name="buttonType" format="enum">
+ <attr name="buttonPreferenceType" format="enum">
<enum name="filled" value="0"/>
<enum name="tonal" value="1"/>
<enum name="outline" value="2"/>
</attr>
- <attr name="buttonSize" format="enum">
+ <attr name="buttonPreferenceSize" format="enum">
<enum name="normal" value="0"/>
<enum name="large" value="1"/>
<enum name="extra" value="2"/>
diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
index 0041eb2..979ff96 100644
--- a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
+++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
@@ -137,8 +137,8 @@
mGravity = a.getInt(R.styleable.ButtonPreference_android_gravity, Gravity.START);
if (SettingsThemeHelper.isExpressiveTheme(context)) {
- int type = a.getInt(R.styleable.ButtonPreference_buttonType, 0);
- int size = a.getInt(R.styleable.ButtonPreference_buttonSize, 0);
+ int type = a.getInt(R.styleable.ButtonPreference_buttonPreferenceType, 0);
+ int size = a.getInt(R.styleable.ButtonPreference_buttonPreferenceSize, 0);
resId = ButtonStyle.getLayoutId(type, size);
}
a.recycle();
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 9d56c77..744e97e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -30,9 +30,10 @@
import android.icu.text.NumberFormat;
import android.location.LocationManager;
import android.media.AudioManager;
+import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.TetheringManager;
-import android.net.vcn.VcnTransportInfo;
+import android.net.vcn.VcnUtils;
import android.net.wifi.WifiInfo;
import android.os.BatteryManager;
import android.os.Build;
@@ -737,14 +738,9 @@
* @param networkCapabilities NetworkCapabilities of the network.
*/
@Nullable
- public static WifiInfo tryGetWifiInfoForVcn(NetworkCapabilities networkCapabilities) {
- if (networkCapabilities.getTransportInfo() == null
- || !(networkCapabilities.getTransportInfo() instanceof VcnTransportInfo)) {
- return null;
- }
- VcnTransportInfo vcnTransportInfo =
- (VcnTransportInfo) networkCapabilities.getTransportInfo();
- return vcnTransportInfo.getWifiInfo();
+ public static WifiInfo tryGetWifiInfoForVcn(
+ ConnectivityManager connectivityMgr, NetworkCapabilities networkCapabilities) {
+ return VcnUtils.getWifiInfoFromVcnCaps(connectivityMgr, networkCapabilities);
}
/** Whether there is any incompatible chargers in the current UsbPort? */
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 015356e..cea3d17 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -30,6 +30,7 @@
import android.net.ScoredNetwork;
import android.net.TransportInfo;
import android.net.vcn.VcnTransportInfo;
+import android.net.vcn.VcnUtils;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiNetworkScoreCache;
@@ -394,10 +395,7 @@
TransportInfo transportInfo = networkCapabilities.getTransportInfo();
if (transportInfo instanceof VcnTransportInfo) {
- // This VcnTransportInfo logic is copied from
- // [com.android.settingslib.Utils.tryGetWifiInfoForVcn]. It's copied instead of
- // re-used because it makes the logic here clearer.
- return ((VcnTransportInfo) transportInfo).getWifiInfo();
+ return VcnUtils.getWifiInfoFromVcnCaps(mConnectivityManager, networkCapabilities);
} else if (transportInfo instanceof WifiInfo) {
return (WifiInfo) transportInfo;
} else {
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 5e31da4..4dc8424 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -168,10 +168,6 @@
Settings.Secure.SHOW_NOTIFICATION_SNOOZE,
Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
Settings.Secure.ZEN_DURATION,
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION,
- Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION,
- Settings.Secure.ZEN_SETTINGS_UPDATED,
- Settings.Secure.ZEN_SETTINGS_SUGGESTION_VIEWED,
Settings.Secure.CHARGING_SOUNDS_ENABLED,
Settings.Secure.CHARGING_VIBRATION_ENABLED,
Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index f6e1057..0773bd7 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -186,7 +186,7 @@
VALIDATORS.put(
Global.STEM_PRIMARY_BUTTON_SHORT_PRESS, new InclusiveIntegerRangeValidator(0, 1));
VALIDATORS.put(
- Global.STEM_PRIMARY_BUTTON_DOUBLE_PRESS, new InclusiveIntegerRangeValidator(0, 1));
+ Global.STEM_PRIMARY_BUTTON_DOUBLE_PRESS, new InclusiveIntegerRangeValidator(0, 2));
VALIDATORS.put(
Global.STEM_PRIMARY_BUTTON_TRIPLE_PRESS, new InclusiveIntegerRangeValidator(0, 1));
VALIDATORS.put(
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index b3f7374..688676d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -247,10 +247,6 @@
VALIDATORS.put(Secure.SHOW_NOTIFICATION_SNOOZE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.NOTIFICATION_HISTORY_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ZEN_DURATION, ANY_INTEGER_VALIDATOR);
- VALIDATORS.put(Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.SHOW_ZEN_SETTINGS_SUGGESTION, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.ZEN_SETTINGS_UPDATED, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.ZEN_SETTINGS_SUGGESTION_VIEWED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.CHARGING_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.CHARGING_VIBRATION_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 3c24f5c..2034f36 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2734,18 +2734,6 @@
Settings.Secure.ZEN_DURATION,
SecureSettingsProto.Zen.DURATION);
dumpSetting(s, p,
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION,
- SecureSettingsProto.Zen.SHOW_ZEN_UPGRADE_NOTIFICATION);
- dumpSetting(s, p,
- Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION,
- SecureSettingsProto.Zen.SHOW_ZEN_SETTINGS_SUGGESTION);
- dumpSetting(s, p,
- Settings.Secure.ZEN_SETTINGS_UPDATED,
- SecureSettingsProto.Zen.SETTINGS_UPDATED);
- dumpSetting(s, p,
- Settings.Secure.ZEN_SETTINGS_SUGGESTION_VIEWED,
- SecureSettingsProto.Zen.SETTINGS_SUGGESTION_VIEWED);
- dumpSetting(s, p,
Settings.Secure.CHARGE_OPTIMIZATION_MODE,
SecureSettingsProto.CHARGE_OPTIMIZATION_MODE);
p.end(zenToken);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 749ad0a..a8af43f5 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -4771,9 +4771,9 @@
}
if (currentVersion == 169) {
- // Version 169: Set the default value for Secure Settings ZEN_DURATION,
- // SHOW_ZEN_SETTINGS_SUGGESTION, ZEN_SETTINGS_UPDATE and
- // ZEN_SETTINGS_SUGGESTION_VIEWED
+ // Version 169: Set the default value for Secure Settings ZEN_DURATION.
+ // Also used to update SHOW_ZEN_SETTINGS_SUGGESTION, ZEN_SETTINGS_UPDATE and
+ // ZEN_SETTINGS_SUGGESTION_VIEWED, but those properties are gone now.
final SettingsState globalSettings = getGlobalSettingsLocked();
final Setting globalZenDuration = globalSettings.getSettingLocked(
@@ -4801,33 +4801,6 @@
SettingsState.SYSTEM_PACKAGE_NAME);
}
- // SHOW_ZEN_SETTINGS_SUGGESTION
- final Setting currentShowZenSettingSuggestion = secureSettings.getSettingLocked(
- Secure.SHOW_ZEN_SETTINGS_SUGGESTION);
- if (currentShowZenSettingSuggestion.isNull()) {
- secureSettings.insertSettingOverrideableByRestoreLocked(
- Secure.SHOW_ZEN_SETTINGS_SUGGESTION, "1",
- null, true, SettingsState.SYSTEM_PACKAGE_NAME);
- }
-
- // ZEN_SETTINGS_UPDATED
- final Setting currentUpdatedSetting = secureSettings.getSettingLocked(
- Secure.ZEN_SETTINGS_UPDATED);
- if (currentUpdatedSetting.isNull()) {
- secureSettings.insertSettingOverrideableByRestoreLocked(
- Secure.ZEN_SETTINGS_UPDATED, "0",
- null, true, SettingsState.SYSTEM_PACKAGE_NAME);
- }
-
- // ZEN_SETTINGS_SUGGESTION_VIEWED
- final Setting currentSettingSuggestionViewed = secureSettings.getSettingLocked(
- Secure.ZEN_SETTINGS_SUGGESTION_VIEWED);
- if (currentSettingSuggestionViewed.isNull()) {
- secureSettings.insertSettingOverrideableByRestoreLocked(
- Secure.ZEN_SETTINGS_SUGGESTION_VIEWED, "0",
- null, true, SettingsState.SYSTEM_PACKAGE_NAME);
- }
-
currentVersion = 170;
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 3c634f0..011ffbc 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -610,7 +610,7 @@
flag.getPackageName(),
flag.getFlagName(),
flag.getServerFlagValue(),
- false);
+ StorageRequestMessage.SERVER_ON_REBOOT);
}
if (flag.getHasLocalOverride()) {
@@ -619,7 +619,7 @@
flag.getPackageName(),
flag.getFlagName(),
flag.getLocalFlagValue(),
- true);
+ StorageRequestMessage.LOCAL_ON_REBOOT);
}
}
diff --git a/packages/SettingsProvider/test/AndroidTest.xml b/packages/SettingsProvider/test/AndroidTest.xml
index dccc2d3..541a294 100644
--- a/packages/SettingsProvider/test/AndroidTest.xml
+++ b/packages/SettingsProvider/test/AndroidTest.xml
@@ -32,7 +32,7 @@
<option name="package" value="com.android.providers.setting.test" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
<option name="hidden-api-checks" value="false"/>
- <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
- <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser" />
+ <option name="exclude-annotation" value="com.android.bedstead.enterprise.annotations.RequireRunOnWorkProfile" />
+ <option name="exclude-annotation" value="com.android.bedstead.multiuser.annotations.RequireRunOnSecondaryUser" />
</test>
</configuration>
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
index e4898da..e86e727 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
@@ -31,10 +31,10 @@
import com.android.bedstead.enterprise.annotations.EnsureHasWorkProfile;
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
import com.android.bedstead.harrier.annotations.RequireFeature;
import com.android.bedstead.harrier.annotations.RequireRunOnInitialUser;
-import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
+import com.android.bedstead.multiuser.annotations.EnsureHasSecondaryUser;
+import com.android.bedstead.multiuser.annotations.RequireRunOnPrimaryUser;
import com.android.bedstead.nene.TestApis;
import com.android.bedstead.nene.users.UserReference;
import com.android.bedstead.nene.users.UserType;
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index d98b2da5..6c3ba68 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -637,6 +637,13 @@
}
flag {
+ name: "screenshot_policy_split_and_desktop_mode"
+ namespace: "systemui"
+ description: "Improves screenshot policy handling for split screen and desktop mode."
+ bug: "365597999"
+}
+
+flag {
name: "run_fingerprint_detect_on_dismissible_keyguard"
namespace: "systemui"
description: "Run fingerprint detect instead of authenticate if the keyguard is dismissible."
@@ -1074,6 +1081,16 @@
}
flag {
+ name: "dream_overlay_updated_font"
+ namespace: "systemui"
+ description: "Flag to enable updated font settings for dream overlay"
+ bug: "349656117"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "app_clips_backlinks"
namespace: "systemui"
description: "Enables Backlinks improvement feature in App Clips"
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 dc9e267..56de096 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
@@ -132,7 +132,6 @@
state = state,
modifier = modifier.fillMaxSize(),
swipeSourceDetector = viewModel.edgeDetector,
- gestureFilter = viewModel::shouldFilterGesture,
) {
sceneByKey.forEach { (sceneKey, scene) ->
scene(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index ac58ab5..340ac32 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -1,5 +1,6 @@
package com.android.systemui.scene.ui.composable
+import androidx.compose.animation.core.spring
import androidx.compose.foundation.gestures.Orientation
import com.android.compose.animation.scene.ProgressConverter
import com.android.compose.animation.scene.TransitionKey
@@ -12,6 +13,8 @@
import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
import com.android.systemui.scene.ui.composable.transitions.bouncerToGoneTransition
import com.android.systemui.scene.ui.composable.transitions.bouncerToLockscreenPreview
+import com.android.systemui.scene.ui.composable.transitions.communalToBouncerTransition
+import com.android.systemui.scene.ui.composable.transitions.communalToShadeTransition
import com.android.systemui.scene.ui.composable.transitions.goneToQuickSettingsTransition
import com.android.systemui.scene.ui.composable.transitions.goneToShadeTransition
import com.android.systemui.scene.ui.composable.transitions.goneToSplitShadeTransition
@@ -44,6 +47,7 @@
// Overscroll progress starts linearly with some resistance (3f) and slowly approaches 0.2f
defaultOverscrollProgressConverter = ProgressConverter.tanh(maxProgress = 0.2f, tilt = 3f)
+ defaultSwipeSpec = spring(stiffness = 300f, dampingRatio = 0.8f, visibilityThreshold = 0.5f)
// Scene transitions
@@ -87,6 +91,8 @@
sharedElement(Notifications.Elements.NotificationStackPlaceholder, enabled = false)
sharedElement(Notifications.Elements.HeadsUpNotificationPlaceholder, enabled = false)
}
+ from(Scenes.Communal, to = Scenes.Shade) { communalToShadeTransition() }
+ from(Scenes.Communal, to = Scenes.Bouncer) { communalToBouncerTransition() }
// Overlay transitions
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToBouncerTransition.kt
similarity index 72%
copy from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt
copy to packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToBouncerTransition.kt
index e52a6e1..d7173fd 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToBouncerTransition.kt
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.util
+package com.android.systemui.scene.ui.composable.transitions
-/** Holds a [newValue] emitted from a `TFlow`, along with the [previousValue] emitted value. */
-data class WithPrev<out S, out T : S>(val previousValue: S, val newValue: T)
+import com.android.compose.animation.scene.TransitionBuilder
+
+fun TransitionBuilder.communalToBouncerTransition() {
+ toBouncerTransition()
+}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToShadeTransition.kt
similarity index 72%
copy from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt
copy to packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToShadeTransition.kt
index e52a6e1..ba920ac 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToShadeTransition.kt
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.util
+package com.android.systemui.scene.ui.composable.transitions
-/** Holds a [newValue] emitted from a `TFlow`, along with the [previousValue] emitted value. */
-data class WithPrev<out S, out T : S>(val previousValue: S, val newValue: T)
+import com.android.compose.animation.scene.TransitionBuilder
+
+fun TransitionBuilder.communalToShadeTransition() {
+ toShadeTransition()
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
index 4c0efd2..dd37b53 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
@@ -1,29 +1,11 @@
package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.CubicBezierEasing
-import androidx.compose.animation.core.tween
-import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.TransitionBuilder
-import com.android.compose.animation.scene.UserActionDistance
import com.android.systemui.bouncer.ui.composable.Bouncer
-const val FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION = 0.5f
-const val FROM_LOCK_SCREEN_TO_BOUNCER_SWIPE_DISTANCE_FRACTION = 0.5f
-
fun TransitionBuilder.lockscreenToBouncerTransition() {
- spec = tween(durationMillis = 500)
-
- distance = UserActionDistance { fromSceneSize, _ ->
- fromSceneSize.height * FROM_LOCK_SCREEN_TO_BOUNCER_SWIPE_DISTANCE_FRACTION
- }
-
- translate(Bouncer.Elements.Content, y = 300.dp)
- fractionRange(end = FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION) {
- fade(Bouncer.Elements.Background)
- }
- fractionRange(start = FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION) {
- fade(Bouncer.Elements.Content)
- }
+ toBouncerTransition()
}
fun TransitionBuilder.bouncerToLockscreenPreview() {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt
new file mode 100644
index 0000000..de76f70
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.UserActionDistance
+import com.android.systemui.bouncer.ui.composable.Bouncer
+
+const val TO_BOUNCER_FADE_FRACTION = 0.5f
+private const val TO_BOUNCER_SWIPE_DISTANCE_FRACTION = 0.5f
+
+fun TransitionBuilder.toBouncerTransition() {
+ spec = tween(durationMillis = 500)
+
+ distance = UserActionDistance { fromSceneSize, _ ->
+ fromSceneSize.height * TO_BOUNCER_SWIPE_DISTANCE_FRACTION
+ }
+
+ translate(Bouncer.Elements.Content, y = 300.dp)
+ fractionRange(end = TO_BOUNCER_FADE_FRACTION) { fade(Bouncer.Elements.Background) }
+ fractionRange(start = TO_BOUNCER_FADE_FRACTION) { fade(Bouncer.Elements.Content) }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index c8adac0..085157a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -124,10 +124,6 @@
overSlop: Float,
pointersDown: Int,
): DragController {
- if (startedPosition != null && layoutImpl.gestureFilter(startedPosition)) {
- return NoOpDragController
- }
-
if (overSlop == 0f) {
val oldDragController = dragController
check(oldDragController != null && oldDragController.isDrivingTransition) {
@@ -230,7 +226,6 @@
val fromSource = resolveSwipeSource(startedPosition)
val upOrLeft = resolveSwipe(pointersDown, fromSource, isUpOrLeft = true)
val downOrRight = resolveSwipe(pointersDown, fromSource, isUpOrLeft = false)
-
return if (fromSource == null) {
Swipes(
upOrLeft = null,
@@ -370,10 +365,18 @@
return 0f
}
+ val currentTransitionIrreversible =
+ if (swipeAnimation.isUpOrLeft) {
+ swipes.upOrLeftResult?.isIrreversible ?: false
+ } else {
+ swipes.downOrRightResult?.isIrreversible ?: false
+ }
+
val needNewTransition =
- hasReachedToContent ||
- result.toContent(layoutState.currentScene) != swipeAnimation.toContent ||
- result.transitionKey != swipeAnimation.contentTransition.key
+ !currentTransitionIrreversible &&
+ (hasReachedToContent ||
+ result.toContent(layoutState.currentScene) != swipeAnimation.toContent ||
+ result.transitionKey != swipeAnimation.contentTransition.key)
if (needNewTransition) {
// Make sure the current transition will finish to the right current scene.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 6e89814..c9a4d58 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -47,9 +47,6 @@
* @param state the state of this layout.
* @param swipeSourceDetector the edge detector used to detect which edge a swipe is started from,
* if any.
- * @param gestureFilter decides whether a drag gesture that started at the given start position
- * should be filtered. If the lambda returns `true`, the drag gesture will be ignored. If it
- * returns `false`, the drag gesture will be handled.
* @param transitionInterceptionThreshold used during a scene transition. For the scene to be
* intercepted, the progress value must be above the threshold, and below (1 - threshold).
* @param builder the configuration of the different scenes and overlays of this layout.
@@ -60,7 +57,6 @@
modifier: Modifier = Modifier,
swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
swipeDetector: SwipeDetector = DefaultSwipeDetector,
- gestureFilter: (startedPosition: Offset) -> Boolean = DefaultGestureFilter,
@FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0.05f,
builder: SceneTransitionLayoutScope.() -> Unit,
) {
@@ -69,7 +65,6 @@
modifier,
swipeSourceDetector,
swipeDetector,
- gestureFilter,
transitionInterceptionThreshold,
onLayoutImpl = null,
builder,
@@ -503,6 +498,12 @@
* bigger than 100% when the user released their finger. `
*/
open val requiresFullDistanceSwipe: Boolean,
+
+ /**
+ * Whether swiping back in the opposite direction past the origin point of the swipe can replace
+ * the action with the action for the opposite direction.
+ */
+ open val isIrreversible: Boolean = false,
) {
internal abstract fun toContent(currentScene: SceneKey): ContentKey
@@ -512,6 +513,7 @@
val toScene: SceneKey,
override val transitionKey: TransitionKey? = null,
override val requiresFullDistanceSwipe: Boolean = false,
+ override val isIrreversible: Boolean = false,
) : UserActionResult(transitionKey, requiresFullDistanceSwipe) {
override fun toContent(currentScene: SceneKey): ContentKey = toScene
}
@@ -521,6 +523,7 @@
val overlay: OverlayKey,
override val transitionKey: TransitionKey? = null,
override val requiresFullDistanceSwipe: Boolean = false,
+ override val isIrreversible: Boolean = false,
) : UserActionResult(transitionKey, requiresFullDistanceSwipe) {
override fun toContent(currentScene: SceneKey): ContentKey = overlay
}
@@ -563,7 +566,14 @@
* the user released their finger.
*/
requiresFullDistanceSwipe: Boolean = false,
- ): UserActionResult = ChangeScene(toScene, transitionKey, requiresFullDistanceSwipe)
+
+ /**
+ * Whether swiping back in the opposite direction past the origin point of the swipe can
+ * replace the action with the action for the opposite direction.
+ */
+ isIrreversible: Boolean = false,
+ ): UserActionResult =
+ ChangeScene(toScene, transitionKey, requiresFullDistanceSwipe, isIrreversible)
/** A [UserActionResult] that shows [toOverlay]. */
operator fun invoke(
@@ -621,7 +631,6 @@
modifier: Modifier = Modifier,
swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
swipeDetector: SwipeDetector = DefaultSwipeDetector,
- gestureFilter: (startedPosition: Offset) -> Boolean = DefaultGestureFilter,
transitionInterceptionThreshold: Float = 0f,
onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null,
builder: SceneTransitionLayoutScope.() -> Unit,
@@ -638,7 +647,6 @@
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = builder,
animationScope = animationScope,
- gestureFilter = gestureFilter,
)
.also { onLayoutImpl?.invoke(it) }
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 9e7be37..65c4043 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -31,7 +31,6 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.ApproachLayoutModifierNode
import androidx.compose.ui.layout.ApproachMeasureScope
import androidx.compose.ui.layout.LookaheadScope
@@ -71,7 +70,6 @@
* animations.
*/
internal val animationScope: CoroutineScope,
- internal val gestureFilter: (startedPosition: Offset) -> Boolean,
) {
/**
* The map of [Scene]s.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index b358faf..879dc54 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -152,6 +152,7 @@
internal val DefaultSwipeSpec =
spring(
stiffness = Spring.StiffnessMediumLow,
+ dampingRatio = Spring.DampingRatioLowBouncy,
visibilityThreshold = OffsetVisibilityThreshold,
)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt
index f758102..54ee783 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt
@@ -17,7 +17,6 @@
package com.android.compose.animation.scene
import androidx.compose.runtime.Stable
-import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.PointerInputChange
/** {@link SwipeDetector} helps determine whether a swipe gestured has occurred. */
@@ -32,8 +31,6 @@
val DefaultSwipeDetector = PassthroughSwipeDetector()
-val DefaultGestureFilter = { _: Offset -> false }
-
/** An {@link SwipeDetector} implementation that recognizes a swipe on any input. */
class PassthroughSwipeDetector : SwipeDetector {
override fun detectSwipe(change: PointerInputChange): Boolean {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index a6ebb0e..a3641e6 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -34,71 +34,76 @@
* Note: Call [reset] before destroying this object to make sure you always get a call to [onStop]
* after [onStart].
*
- * @sample com.android.compose.animation.scene.rememberSwipeToSceneNestedScrollConnection
+ * @sample LargeTopAppBarNestedScrollConnection
+ * @sample com.android.compose.animation.scene.NestedScrollHandlerImpl.nestedScrollConnection
*/
class PriorityNestedScrollConnection(
- private val canStartPreScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
- private val canStartPostScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
- private val canStartPostFling: (velocityAvailable: Velocity) -> Boolean,
+ orientation: Orientation,
+ private val canStartPreScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
+ private val canStartPostScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
+ private val canStartPostFling: (velocityAvailable: Float) -> Boolean,
private val canContinueScroll: (source: NestedScrollSource) -> Boolean,
private val canScrollOnFling: Boolean,
- private val onStart: (offsetAvailable: Offset) -> Unit,
- private val onScroll: (offsetAvailable: Offset) -> Offset,
- private val onStop: (velocityAvailable: Velocity) -> SuspendedValue<Velocity>,
-) : NestedScrollConnection {
+ private val onStart: (offsetAvailable: Float) -> Unit,
+ private val onScroll: (offsetAvailable: Float) -> Float,
+ private val onStop: (velocityAvailable: Float) -> SuspendedValue<Float>,
+) : NestedScrollConnection, SpaceVectorConverter by SpaceVectorConverter(orientation) {
/** In priority mode [onPreScroll] events are first consumed by the parent, via [onScroll]. */
private var isPriorityMode = false
- private var offsetScrolledBeforePriorityMode = Offset.Zero
+ private var offsetScrolledBeforePriorityMode = 0f
override fun onPostScroll(
consumed: Offset,
available: Offset,
source: NestedScrollSource,
): Offset {
+ val availableFloat = available.toFloat()
// The offset before the start takes into account the up and down movements, starting from
// the beginning or from the last fling gesture.
- val offsetBeforeStart = offsetScrolledBeforePriorityMode - available
+ val offsetBeforeStart = offsetScrolledBeforePriorityMode - availableFloat
if (
isPriorityMode ||
(source == NestedScrollSource.SideEffect && !canScrollOnFling) ||
- !canStartPostScroll(available, offsetBeforeStart)
+ !canStartPostScroll(availableFloat, offsetBeforeStart)
) {
// The priority mode cannot start so we won't consume the available offset.
return Offset.Zero
}
- return onPriorityStart(available)
+ return onPriorityStart(availableFloat).toOffset()
}
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
if (!isPriorityMode) {
if (source == NestedScrollSource.UserInput || canScrollOnFling) {
- if (canStartPreScroll(available, offsetScrolledBeforePriorityMode)) {
- return onPriorityStart(available)
+ val availableFloat = available.toFloat()
+ if (canStartPreScroll(availableFloat, offsetScrolledBeforePriorityMode)) {
+ return onPriorityStart(availableFloat).toOffset()
}
// We want to track the amount of offset consumed before entering priority mode
- offsetScrolledBeforePriorityMode += available
+ offsetScrolledBeforePriorityMode += availableFloat
}
return Offset.Zero
}
+ val availableFloat = available.toFloat()
if (!canContinueScroll(source)) {
// Step 3a: We have lost priority and we no longer need to intercept scroll events.
- onPriorityStop(velocity = Velocity.Zero)
+ onPriorityStop(velocity = 0f)
- // We've just reset offsetScrolledBeforePriorityMode to Offset.Zero
+ // We've just reset offsetScrolledBeforePriorityMode to 0f
// We want to track the amount of offset consumed before entering priority mode
- offsetScrolledBeforePriorityMode += available
+ offsetScrolledBeforePriorityMode += availableFloat
return Offset.Zero
}
// Step 2: We have the priority and can consume the scroll events.
- return onScroll(available)
+ return onScroll(availableFloat).toOffset()
}
override suspend fun onPreFling(available: Velocity): Velocity {
@@ -108,15 +113,16 @@
}
// Step 3b: The finger is lifted, we can stop intercepting scroll events and use the speed
// of the fling gesture.
- return onPriorityStop(velocity = available).invoke()
+ return onPriorityStop(velocity = available.toFloat()).invoke().toVelocity()
}
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
+ val availableFloat = available.toFloat()
if (isPriorityMode) {
- return onPriorityStop(velocity = available).invoke()
+ return onPriorityStop(velocity = availableFloat).invoke().toVelocity()
}
- if (!canStartPostFling(available)) {
+ if (!canStartPostFling(availableFloat)) {
return Velocity.Zero
}
@@ -124,11 +130,11 @@
// given the available velocity.
// TODO(b/291053278): Remove canStartPostFling() and instead make it possible to define the
// overscroll behavior on the Scene level.
- val smallOffset = Offset(available.x.sign, available.y.sign)
- onPriorityStart(available = smallOffset)
+ val smallOffset = availableFloat.sign
+ onPriorityStart(availableOffset = smallOffset)
// This is the last event of a scroll gesture.
- return onPriorityStop(available).invoke()
+ return onPriorityStop(availableFloat).invoke().toVelocity()
}
/**
@@ -138,10 +144,10 @@
*/
fun reset() {
// Step 3c: To ensure that an onStop is always called for every onStart.
- onPriorityStop(velocity = Velocity.Zero)
+ onPriorityStop(velocity = 0f)
}
- private fun onPriorityStart(available: Offset): Offset {
+ private fun onPriorityStart(availableOffset: Float): Float {
if (isPriorityMode) {
error("This should never happen, onPriorityStart() was called when isPriorityMode")
}
@@ -152,17 +158,17 @@
// Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is
// lifted (step 3b), or this object has been destroyed (step 3c).
- onStart(available)
+ onStart(availableOffset)
- return onScroll(available)
+ return onScroll(availableOffset)
}
- private fun onPriorityStop(velocity: Velocity): SuspendedValue<Velocity> {
+ private fun onPriorityStop(velocity: Float): SuspendedValue<Float> {
// We can restart tracking the consumed offsets from scratch.
- offsetScrolledBeforePriorityMode = Offset.Zero
+ offsetScrolledBeforePriorityMode = 0f
if (!isPriorityMode) {
- return { Velocity.Zero }
+ return { 0f }
}
isPriorityMode = false
@@ -170,38 +176,3 @@
return onStop(velocity)
}
}
-
-fun PriorityNestedScrollConnection(
- orientation: Orientation,
- canStartPreScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
- canStartPostScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
- canStartPostFling: (velocityAvailable: Float) -> Boolean,
- canContinueScroll: (source: NestedScrollSource) -> Boolean,
- canScrollOnFling: Boolean,
- onStart: (offsetAvailable: Float) -> Unit,
- onScroll: (offsetAvailable: Float) -> Float,
- onStop: (velocityAvailable: Float) -> SuspendedValue<Float>,
-) =
- with(SpaceVectorConverter(orientation)) {
- PriorityNestedScrollConnection(
- canStartPreScroll = { offsetAvailable: Offset, offsetBeforeStart: Offset ->
- canStartPreScroll(offsetAvailable.toFloat(), offsetBeforeStart.toFloat())
- },
- canStartPostScroll = { offsetAvailable: Offset, offsetBeforeStart: Offset ->
- canStartPostScroll(offsetAvailable.toFloat(), offsetBeforeStart.toFloat())
- },
- canStartPostFling = { velocityAvailable: Velocity ->
- canStartPostFling(velocityAvailable.toFloat())
- },
- canContinueScroll = canContinueScroll,
- canScrollOnFling = canScrollOnFling,
- onStart = { offsetAvailable -> onStart(offsetAvailable.toFloat()) },
- onScroll = { offsetAvailable: Offset ->
- onScroll(offsetAvailable.toFloat()).toOffset()
- },
- onStop = { velocityAvailable: Velocity ->
- val consumedVelocity = onStop(velocityAvailable.toFloat())
- suspend { consumedVelocity.invoke().toVelocity() }
- },
- )
- }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index 16dc0d5..ecef6be 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -109,8 +109,6 @@
val transitionInterceptionThreshold = 0.05f
- var gestureFilter: (startedPosition: Offset) -> Boolean = DefaultGestureFilter
-
private val layoutImpl =
SceneTransitionLayoutImpl(
state = layoutState,
@@ -123,7 +121,6 @@
// Use testScope and not backgroundScope here because backgroundScope does not
// work well with advanceUntilIdle(), which is used by some tests.
animationScope = testScope,
- gestureFilter = { startedPosition -> gestureFilter.invoke(startedPosition) },
)
.apply { setContentsAndLayoutTargetSizeForTest(LAYOUT_SIZE) }
@@ -352,13 +349,6 @@
}
@Test
- fun onDragStarted_doesNotStartTransition_whenGestureFiltered() = runGestureTest {
- gestureFilter = { _ -> true }
- onDragStarted(overSlop = down(fractionOfScreen = 0.1f), expectedConsumedOverSlop = 0f)
- assertIdle(currentScene = SceneA)
- }
-
- @Test
fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest {
val dragController = onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
@@ -517,6 +507,54 @@
}
@Test
+ fun onDragWithActionsInBothDirections_dragToOppositeDirectionReplacesAction() = runGestureTest {
+ // We are on SceneA. UP -> B, DOWN-> C.
+ val dragController = onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
+ assertTransition(
+ currentScene = SceneA,
+ fromScene = SceneA,
+ toScene = SceneB,
+ progress = 0.2f,
+ )
+
+ // Reverse drag direction, it will replace the previous transition
+ dragController.onDragDelta(pixels = down(fractionOfScreen = 0.5f))
+ assertTransition(
+ currentScene = SceneA,
+ fromScene = SceneA,
+ toScene = SceneC,
+ progress = 0.3f,
+ )
+ }
+
+ @Test
+ fun onDragWithActionsInBothDirections_dragToOppositeDirectionNotReplaceable() = runGestureTest {
+ // We are on SceneA. UP -> B, DOWN-> C. The up swipe is not replaceable though.
+ mutableUserActionsA =
+ mapOf(Swipe.Up to UserActionResult(SceneB, isIrreversible = true), Swipe.Down to SceneC)
+ val dragController =
+ onDragStarted(
+ startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE * 0.5f),
+ overSlop = up(fractionOfScreen = 0.2f),
+ )
+ assertTransition(
+ currentScene = SceneA,
+ fromScene = SceneA,
+ toScene = SceneB,
+ progress = 0.2f,
+ )
+
+ // Reverse drag direction, it cannot replace the previous transition
+ dragController.onDragDelta(pixels = down(fractionOfScreen = 0.5f))
+ assertTransition(
+ currentScene = SceneA,
+ fromScene = SceneA,
+ toScene = SceneB,
+ progress = -0.3f,
+ )
+ }
+
+ @Test
fun onDragFromEdge_startTransitionToEdgeAction() = runGestureTest {
navigateToSceneC()
@@ -1251,7 +1289,8 @@
fun overscroll_releaseBetween0And100Percent_up() = runGestureTest {
// Make scene B overscrollable.
layoutState.transitions = transitions {
- from(SceneA, to = SceneB) { spec = spring(dampingRatio = Spring.DampingRatioNoBouncy) }
+ defaultSwipeSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)
+ from(SceneA, to = SceneB) {}
overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) }
}
@@ -1282,7 +1321,8 @@
fun overscroll_releaseBetween0And100Percent_down() = runGestureTest {
// Make scene C overscrollable.
layoutState.transitions = transitions {
- from(SceneA, to = SceneC) { spec = spring(dampingRatio = Spring.DampingRatioNoBouncy) }
+ defaultSwipeSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)
+ from(SceneA, to = SceneC) {}
overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
index bde7699..badc43b 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
@@ -18,8 +18,9 @@
package com.android.compose.nestedscroll
+import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.UserInput
import androidx.compose.ui.unit.Velocity
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
@@ -35,13 +36,14 @@
private var canStartPostFling = false
private var canContinueScroll = false
private var isStarted = false
- private var lastScroll: Offset? = null
- private var returnOnScroll = Offset.Zero
- private var lastStop: Velocity? = null
- private var returnOnStop = Velocity.Zero
+ private var lastScroll: Float? = null
+ private var returnOnScroll = 0f
+ private var lastStop: Float? = null
+ private var returnOnStop = 0f
private val scrollConnection =
PriorityNestedScrollConnection(
+ orientation = Orientation.Vertical,
canStartPreScroll = { _, _ -> canStartPreScroll },
canStartPostScroll = { _, _ -> canStartPostScroll },
canStartPostFling = { canStartPostFling },
@@ -58,11 +60,6 @@
},
)
- private val offset1 = Offset(1f, 1f)
- private val offset2 = Offset(2f, 2f)
- private val velocity1 = Velocity(1f, 1f)
- private val velocity2 = Velocity(2f, 2f)
-
@Test
fun step1_priorityModeShouldStartOnlyOnPreScroll() = runTest {
canStartPreScroll = true
@@ -70,7 +67,7 @@
scrollConnection.onPostScroll(
consumed = Offset.Zero,
available = Offset.Zero,
- source = NestedScrollSource.Drag,
+ source = UserInput,
)
assertThat(isStarted).isEqualTo(false)
@@ -80,7 +77,7 @@
scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero)
assertThat(isStarted).isEqualTo(false)
- scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+ scrollConnection.onPreScroll(available = Offset.Zero, source = UserInput)
assertThat(isStarted).isEqualTo(true)
}
@@ -89,7 +86,7 @@
scrollConnection.onPostScroll(
consumed = Offset.Zero,
available = Offset.Zero,
- source = NestedScrollSource.Drag,
+ source = UserInput,
)
}
@@ -97,7 +94,7 @@
fun step1_priorityModeShouldStartOnlyOnPostScroll() = runTest {
canStartPostScroll = true
- scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+ scrollConnection.onPreScroll(available = Offset.Zero, source = UserInput)
assertThat(isStarted).isEqualTo(false)
scrollConnection.onPreFling(available = Velocity.Zero)
@@ -115,7 +112,7 @@
scrollConnection.onPostScroll(
consumed = Offset.Zero,
available = Offset.Zero,
- source = NestedScrollSource.Drag,
+ source = UserInput,
)
assertThat(isStarted).isEqualTo(false)
@@ -128,12 +125,12 @@
canStartPostScroll = true
scrollConnection.onPostScroll(
- consumed = offset1,
- available = offset2,
- source = NestedScrollSource.Drag,
+ consumed = Offset(1f, 1f),
+ available = Offset(2f, 2f),
+ source = UserInput,
)
- assertThat(lastScroll).isEqualTo(offset2)
+ assertThat(lastScroll).isEqualTo(2f)
}
@Test
@@ -141,13 +138,13 @@
startPriorityModePostScroll()
canContinueScroll = true
- scrollConnection.onPreScroll(available = offset1, source = NestedScrollSource.Drag)
- assertThat(lastScroll).isEqualTo(offset1)
+ scrollConnection.onPreScroll(available = Offset(1f, 1f), source = UserInput)
+ assertThat(lastScroll).isEqualTo(1f)
canContinueScroll = false
- scrollConnection.onPreScroll(available = offset2, source = NestedScrollSource.Drag)
- assertThat(lastScroll).isNotEqualTo(offset2)
- assertThat(lastScroll).isEqualTo(offset1)
+ scrollConnection.onPreScroll(available = Offset(2f, 2f), source = UserInput)
+ assertThat(lastScroll).isNotEqualTo(2f)
+ assertThat(lastScroll).isEqualTo(1f)
}
@Test
@@ -155,7 +152,7 @@
startPriorityModePostScroll()
canContinueScroll = false
- scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+ scrollConnection.onPreScroll(available = Offset.Zero, source = UserInput)
assertThat(lastStop).isNotNull()
}
@@ -184,22 +181,22 @@
fun receive_onPostFling() = runTest {
canStartPostFling = true
- scrollConnection.onPostFling(consumed = velocity1, available = velocity2)
+ scrollConnection.onPostFling(consumed = Velocity(1f, 1f), available = Velocity(2f, 2f))
- assertThat(lastStop).isEqualTo(velocity2)
+ assertThat(lastStop).isEqualTo(2f)
}
@Test
fun step1_priorityModeShouldStartOnlyOnPostFling() = runTest {
canStartPostFling = true
- scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+ scrollConnection.onPreScroll(available = Offset.Zero, source = UserInput)
assertThat(isStarted).isEqualTo(false)
scrollConnection.onPostScroll(
consumed = Offset.Zero,
available = Offset.Zero,
- source = NestedScrollSource.Drag,
+ source = UserInput,
)
assertThat(isStarted).isEqualTo(false)
diff --git a/packages/SystemUI/customization/Android.bp b/packages/SystemUI/customization/Android.bp
index c399abc..81d92fa 100644
--- a/packages/SystemUI/customization/Android.bp
+++ b/packages/SystemUI/customization/Android.bp
@@ -36,6 +36,7 @@
"SystemUIPluginLib",
"SystemUIUnfoldLib",
"kotlinx_coroutines",
+ "monet",
"dagger2",
"jsr330",
],
diff --git a/packages/SystemUI/customization/res/values/ids.xml b/packages/SystemUI/customization/res/values/ids.xml
index 5eafbfc..ec466f0 100644
--- a/packages/SystemUI/customization/res/values/ids.xml
+++ b/packages/SystemUI/customization/res/values/ids.xml
@@ -6,4 +6,13 @@
<item type="id" name="weather_clock_weather_icon" />
<item type="id" name="weather_clock_temperature" />
<item type="id" name="weather_clock_alarm_dnd" />
+
+ <item type="id" name="HOUR_DIGIT_PAIR"/>
+ <item type="id" name="MINUTE_DIGIT_PAIR"/>
+ <item type="id" name="HOUR_FIRST_DIGIT"/>
+ <item type="id" name="HOUR_SECOND_DIGIT"/>
+ <item type="id" name="MINUTE_FIRST_DIGIT"/>
+ <item type="id" name="MINUTE_SECOND_DIGIT"/>
+ <item type="id" name="TIME_FULL_FORMAT"/>
+ <item type="id" name="DATE_FORMAT"/>
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 1863cd8..9877406 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -62,6 +62,7 @@
// implement the get method and ensure a value is returned before initialization is complete.
private var logger = DEFAULT_LOGGER
get() = field ?: DEFAULT_LOGGER
+
var messageBuffer: MessageBuffer
get() = logger.buffer
set(value) {
@@ -123,24 +124,24 @@
attrs,
R.styleable.AnimatableClockView,
defStyleAttr,
- defStyleRes
+ defStyleRes,
)
try {
dozingWeightInternal =
animatableClockViewAttributes.getInt(
R.styleable.AnimatableClockView_dozeWeight,
- /* default = */ 100
+ /* default = */ 100,
)
lockScreenWeightInternal =
animatableClockViewAttributes.getInt(
R.styleable.AnimatableClockView_lockScreenWeight,
- /* default = */ 300
+ /* default = */ 300,
)
chargeAnimationDelay =
animatableClockViewAttributes.getInt(
R.styleable.AnimatableClockView_chargeAnimationDelay,
- /* default = */ 200
+ /* default = */ 200,
)
} finally {
animatableClockViewAttributes.recycle()
@@ -151,14 +152,14 @@
attrs,
android.R.styleable.TextView,
defStyleAttr,
- defStyleRes
+ defStyleRes,
)
try {
isSingleLineInternal =
textViewAttributes.getBoolean(
android.R.styleable.TextView_singleLine,
- /* default = */ false
+ /* default = */ false,
)
} finally {
textViewAttributes.recycle()
@@ -280,7 +281,7 @@
text: CharSequence,
start: Int,
lengthBefore: Int,
- lengthAfter: Int
+ lengthAfter: Int,
) {
logger.d({ "onTextChanged($str1)" }) { str1 = text.toString() }
super.onTextChanged(text, start, lengthBefore, lengthAfter)
@@ -305,7 +306,7 @@
interpolator = null,
duration = 0,
delay = 0,
- onAnimationEnd = null
+ onAnimationEnd = null,
)
setTextStyle(
weight = lockScreenWeight,
@@ -314,7 +315,7 @@
interpolator = null,
duration = COLOR_ANIM_DURATION,
delay = 0,
- onAnimationEnd = null
+ onAnimationEnd = null,
)
}
@@ -327,7 +328,7 @@
interpolator = null,
duration = 0,
delay = 0,
- onAnimationEnd = null
+ onAnimationEnd = null,
)
setTextStyle(
weight = lockScreenWeight,
@@ -336,7 +337,7 @@
duration = APPEAR_ANIM_DURATION,
interpolator = Interpolators.EMPHASIZED_DECELERATE,
delay = 0,
- onAnimationEnd = null
+ onAnimationEnd = null,
)
}
@@ -353,7 +354,7 @@
interpolator = null,
duration = 0,
delay = 0,
- onAnimationEnd = null
+ onAnimationEnd = null,
)
setTextStyle(
weight = dozingWeightInternal,
@@ -362,7 +363,7 @@
interpolator = Interpolators.EMPHASIZED_DECELERATE,
duration = ANIMATION_DURATION_FOLD_TO_AOD.toLong(),
delay = 0,
- onAnimationEnd = null
+ onAnimationEnd = null,
)
}
@@ -381,7 +382,7 @@
interpolator = null,
duration = CHARGE_ANIM_DURATION_PHASE_1,
delay = 0,
- onAnimationEnd = null
+ onAnimationEnd = null,
)
}
setTextStyle(
@@ -391,7 +392,7 @@
interpolator = null,
duration = CHARGE_ANIM_DURATION_PHASE_0,
delay = chargeAnimationDelay.toLong(),
- onAnimationEnd = startAnimPhase2
+ onAnimationEnd = startAnimPhase2,
)
}
@@ -404,7 +405,7 @@
interpolator = null,
duration = DOZE_ANIM_DURATION,
delay = 0,
- onAnimationEnd = null
+ onAnimationEnd = null,
)
}
@@ -444,7 +445,7 @@
interpolator: TimeInterpolator?,
duration: Long,
delay: Long,
- onAnimationEnd: Runnable?
+ onAnimationEnd: Runnable?,
) {
textAnimator?.let {
it.setTextStyle(
@@ -454,7 +455,7 @@
duration = duration,
interpolator = interpolator,
delay = delay,
- onAnimationEnd = onAnimationEnd
+ onAnimationEnd = onAnimationEnd,
)
it.glyphFilter = glyphFilter
}
@@ -468,7 +469,7 @@
duration = duration,
interpolator = interpolator,
delay = delay,
- onAnimationEnd = onAnimationEnd
+ onAnimationEnd = onAnimationEnd,
)
textAnimator.glyphFilter = glyphFilter
}
@@ -476,6 +477,7 @@
}
fun refreshFormat() = refreshFormat(DateFormat.is24HourFormat(context))
+
fun refreshFormat(use24HourFormat: Boolean) {
Patterns.update(context)
@@ -560,18 +562,11 @@
* @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means
* it finished moving.
*/
- fun offsetGlyphsForStepClockAnimation(
- distance: Float,
- fraction: Float,
- ) {
+ fun offsetGlyphsForStepClockAnimation(distance: Float, fraction: Float) {
for (i in 0 until NUM_DIGITS) {
val dir = if (isLayoutRtl) -1 else 1
val digitFraction =
- getDigitFraction(
- digit = i,
- isMovingToCenter = distance > 0,
- fraction = fraction,
- )
+ getDigitFraction(digit = i, isMovingToCenter = distance > 0, fraction = fraction)
val moveAmountForDigit = dir * distance * digitFraction
glyphOffsets[i] = moveAmountForDigit
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AssetLoader.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AssetLoader.kt
new file mode 100644
index 0000000..d001ef96
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AssetLoader.kt
@@ -0,0 +1,448 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.clocks
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.content.res.Resources
+import android.graphics.Color
+import android.graphics.Typeface
+import android.graphics.drawable.Drawable
+import android.util.TypedValue
+import com.android.internal.graphics.ColorUtils
+import com.android.internal.graphics.cam.Cam
+import com.android.internal.graphics.cam.CamUtils
+import com.android.internal.policy.SystemBarUtils
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.core.MessageBuffer
+import com.android.systemui.monet.ColorScheme
+import com.android.systemui.monet.Style as MonetStyle
+import com.android.systemui.monet.TonalPalette
+import java.io.IOException
+import kotlin.math.abs
+
+class AssetLoader
+private constructor(
+ private val pluginCtx: Context,
+ private val sysuiCtx: Context,
+ private val baseDir: String,
+ var colorScheme: ColorScheme?,
+ var seedColor: Int?,
+ var overrideChroma: Float?,
+ val typefaceCache: TypefaceCache,
+ val getThemeSeedColor: (Context) -> Int,
+ messageBuffer: MessageBuffer,
+) {
+ val logger = Logger(messageBuffer, TAG)
+ private val resources =
+ listOf(
+ Pair(pluginCtx.resources, pluginCtx.packageName),
+ Pair(sysuiCtx.resources, sysuiCtx.packageName),
+ )
+
+ constructor(
+ pluginCtx: Context,
+ sysuiCtx: Context,
+ baseDir: String,
+ messageBuffer: MessageBuffer,
+ getThemeSeedColor: ((Context) -> Int)? = null,
+ ) : this(
+ pluginCtx,
+ sysuiCtx,
+ baseDir,
+ colorScheme = null,
+ seedColor = null,
+ overrideChroma = null,
+ typefaceCache =
+ TypefaceCache(messageBuffer) { Typeface.createFromAsset(pluginCtx.assets, it) },
+ getThemeSeedColor = getThemeSeedColor ?: Companion::getThemeSeedColor,
+ messageBuffer = messageBuffer,
+ )
+
+ fun listAssets(path: String): List<String> {
+ return pluginCtx.resources.assets.list("$baseDir$path")?.toList() ?: emptyList()
+ }
+
+ fun tryReadString(resStr: String): String? = tryRead(resStr, ::readString)
+
+ fun readString(resStr: String): String {
+ val resPair = resolveResourceId(resStr)
+ if (resPair == null) {
+ throw IOException("Failed to parse string: $resStr")
+ }
+
+ val (res, id) = resPair
+ return res.getString(id)
+ }
+
+ fun tryReadColor(resStr: String): Int? = tryRead(resStr, ::readColor)
+
+ fun readColor(resStr: String): Int {
+ if (resStr.startsWith("#")) {
+ return Color.parseColor(resStr)
+ }
+
+ val schemeColor = tryParseColorFromScheme(resStr)
+ if (schemeColor != null) {
+ logColor("ColorScheme: $resStr", schemeColor)
+ return checkChroma(schemeColor)
+ }
+
+ val result = resolveColorResourceId(resStr)
+ if (result == null) {
+ throw IOException("Failed to parse color: $resStr")
+ }
+
+ val (res, colorId, targetTone) = result
+ val color = res.getColor(colorId)
+ if (targetTone == null || TonalPalette.SHADE_KEYS.contains(targetTone.toInt())) {
+ logColor("Resources: $resStr", color)
+ return checkChroma(color)
+ } else {
+ val interpolatedColor =
+ ColorStateList.valueOf(color)
+ .withLStar((1000f - targetTone) / 10f)
+ .getDefaultColor()
+ logColor("Resources (interpolated tone): $resStr", interpolatedColor)
+ return checkChroma(interpolatedColor)
+ }
+ }
+
+ private fun checkChroma(color: Int): Int {
+ return overrideChroma?.let {
+ val cam = Cam.fromInt(color)
+ val tone = CamUtils.lstarFromInt(color)
+ val result = ColorUtils.CAMToColor(cam.hue, it, tone)
+ logColor("Chroma override", result)
+ result
+ } ?: color
+ }
+
+ private fun tryParseColorFromScheme(resStr: String): Int? {
+ val colorScheme = this.colorScheme
+ if (colorScheme == null) {
+ logger.w("No color scheme available")
+ return null
+ }
+
+ val (packageName, category, name) = parseResourceId(resStr)
+ if (packageName != "android" || category != "color") {
+ logger.w("Failed to parse package from $resStr")
+ return null
+ }
+
+ var parts = name.split('_')
+ if (parts.size != 3) {
+ logger.w("Failed to find palette and shade from $name")
+ return null
+ }
+ val (_, paletteKey, shadeKeyStr) = parts
+
+ val palette =
+ when (paletteKey) {
+ "accent1" -> colorScheme.accent1
+ "accent2" -> colorScheme.accent2
+ "accent3" -> colorScheme.accent3
+ "neutral1" -> colorScheme.neutral1
+ "neutral2" -> colorScheme.neutral2
+ else -> return null
+ }
+
+ if (shadeKeyStr.contains("+") || shadeKeyStr.contains("-")) {
+ val signIndex = shadeKeyStr.indexOfLast { it == '-' || it == '+' }
+ // Use the tone of the seed color if it was set explicitly.
+ var baseTone =
+ if (seedColor != null) colorScheme.seedTone.toFloat()
+ else shadeKeyStr.substring(0, signIndex).toFloatOrNull()
+ val diff = shadeKeyStr.substring(signIndex).toFloatOrNull()
+
+ if (baseTone == null) {
+ logger.w("Failed to parse base tone from $shadeKeyStr")
+ return null
+ }
+
+ if (diff == null) {
+ logger.w("Failed to parse relative tone from $shadeKeyStr")
+ return null
+ }
+ return palette.getAtTone(baseTone + diff)
+ } else {
+ val shadeKey = shadeKeyStr.toIntOrNull()
+ if (shadeKey == null) {
+ logger.w("Failed to parse tone from $shadeKeyStr")
+ return null
+ }
+ return palette.allShadesMapped.get(shadeKey) ?: palette.getAtTone(shadeKey.toFloat())
+ }
+ }
+
+ fun readFontAsset(resStr: String): Typeface = typefaceCache.getTypeface(resStr)
+
+ fun tryReadTextAsset(path: String?): String? = tryRead(path, ::readTextAsset)
+
+ fun readTextAsset(path: String): String {
+ return pluginCtx.resources.assets.open("$baseDir$path").use { stream ->
+ val buffer = ByteArray(stream.available())
+ stream.read(buffer)
+ String(buffer)
+ }
+ }
+
+ fun tryReadDrawableAsset(path: String?): Drawable? = tryRead(path, ::readDrawableAsset)
+
+ fun readDrawableAsset(path: String): Drawable {
+ var result: Drawable?
+
+ if (path.startsWith("@")) {
+ val pair = resolveResourceId(path)
+ if (pair == null) {
+ throw IOException("Failed to parse $path to an id")
+ }
+ val (res, id) = pair
+ result = res.getDrawable(id)
+ } else if (path.endsWith("xml")) {
+ // TODO(b/248609434): Support xml files in assets
+ throw IOException("Cannot load xml files from assets")
+ } else {
+ // Attempt to load as if it's a bitmap and directly loadable
+ result =
+ pluginCtx.resources.assets.open("$baseDir$path").use { stream ->
+ Drawable.createFromResourceStream(
+ pluginCtx.resources,
+ TypedValue(),
+ stream,
+ null,
+ )
+ }
+ }
+
+ return result ?: throw IOException("Failed to load: $baseDir$path")
+ }
+
+ fun parseResourceId(resStr: String): Triple<String?, String, String> {
+ if (!resStr.startsWith("@")) {
+ throw IOException("Invalid resource id: $resStr; Must start with '@'")
+ }
+
+ // Parse out resource string
+ val parts = resStr.drop(1).split('/', ':')
+ return when (parts.size) {
+ 2 -> Triple(null, parts[0], parts[1])
+ 3 -> Triple(parts[0], parts[1], parts[2])
+ else -> throw IOException("Failed to parse resource string: $resStr")
+ }
+ }
+
+ fun resolveColorResourceId(resStr: String): Triple<Resources, Int, Float?>? {
+ var (packageName, category, name) = parseResourceId(resStr)
+
+ // Convert relative tonal specifiers to standard
+ val relIndex = name.indexOfLast { it == '_' }
+ val isToneRelative = name.contains("-") || name.contains("+")
+ val targetTone =
+ if (packageName != "android") {
+ null
+ } else if (isToneRelative) {
+ val signIndex = name.indexOfLast { it == '-' || it == '+' }
+ val baseTone = name.substring(relIndex + 1, signIndex).toFloatOrNull()
+ var diff = name.substring(signIndex).toFloatOrNull()
+ if (baseTone == null || diff == null) {
+ logger.w("Failed to parse relative tone from $name")
+ return null
+ }
+ baseTone + diff
+ } else {
+ val absTone = name.substring(relIndex + 1).toFloatOrNull()
+ if (absTone == null) {
+ logger.w("Failed to parse absolute tone from $name")
+ return null
+ }
+ absTone
+ }
+
+ if (
+ targetTone != null &&
+ (isToneRelative || !TonalPalette.SHADE_KEYS.contains(targetTone.toInt()))
+ ) {
+ val closeTone = TonalPalette.SHADE_KEYS.minBy { abs(it - targetTone) }
+ val prevName = name
+ name = name.substring(0, relIndex + 1) + closeTone
+ logger.i("Converted $prevName to $name")
+ }
+
+ val result = resolveResourceId(packageName, category, name)
+ if (result == null) {
+ return null
+ }
+
+ val (res, resId) = result
+ return Triple(res, resId, targetTone)
+ }
+
+ fun resolveResourceId(resStr: String): Pair<Resources, Int>? {
+ val (packageName, category, name) = parseResourceId(resStr)
+ return resolveResourceId(packageName, category, name)
+ }
+
+ fun resolveResourceId(
+ packageName: String?,
+ category: String,
+ name: String,
+ ): Pair<Resources, Int>? {
+ for ((res, ctxPkgName) in resources) {
+ val result = res.getIdentifier(name, category, packageName ?: ctxPkgName)
+ if (result != 0) {
+ return Pair(res, result)
+ }
+ }
+ return null
+ }
+
+ private fun <TArg : Any, TRes : Any> tryRead(arg: TArg?, fn: (TArg) -> TRes): TRes? {
+ try {
+ if (arg == null) {
+ return null
+ }
+ return fn(arg)
+ } catch (ex: IOException) {
+ logger.w("Failed to read $arg", ex)
+ return null
+ }
+ }
+
+ fun assetExists(path: String): Boolean {
+ try {
+ if (path.startsWith("@")) {
+ val pair = resolveResourceId(path)
+ val colorPair = resolveColorResourceId(path)
+ return pair != null || colorPair != null
+ } else {
+ val stream = pluginCtx.resources.assets.open("$baseDir$path")
+ if (stream == null) {
+ return false
+ }
+
+ stream.close()
+ return true
+ }
+ } catch (ex: IOException) {
+ return false
+ }
+ }
+
+ fun copy(messageBuffer: MessageBuffer? = null): AssetLoader =
+ AssetLoader(
+ pluginCtx,
+ sysuiCtx,
+ baseDir,
+ colorScheme,
+ seedColor,
+ overrideChroma,
+ typefaceCache,
+ getThemeSeedColor,
+ messageBuffer ?: logger.buffer,
+ )
+
+ fun setSeedColor(seedColor: Int?, style: MonetStyle?) {
+ this.seedColor = seedColor
+ refreshColorPalette(style)
+ }
+
+ fun refreshColorPalette(style: MonetStyle?) {
+ val seedColor =
+ this.seedColor ?: getThemeSeedColor(sysuiCtx).also { logColor("Theme Seed Color", it) }
+ this.colorScheme =
+ ColorScheme(
+ seedColor,
+ false, // darkTheme is not used for palette generation
+ style ?: MonetStyle.CLOCK,
+ )
+
+ // Enforce low chroma on output colors if low chroma theme is selected
+ this.overrideChroma = run {
+ val cam = colorScheme?.seed?.let { Cam.fromInt(it) }
+ if (cam != null && cam.chroma < LOW_CHROMA_LIMIT) {
+ return@run cam.chroma * LOW_CHROMA_SCALE
+ }
+ return@run null
+ }
+ }
+
+ fun getClockPaddingStart(): Int {
+ val result = resolveResourceId(null, "dimen", "clock_padding_start")
+ if (result != null) {
+ val (res, id) = result
+ return res.getDimensionPixelSize(id)
+ }
+ return -1
+ }
+
+ fun getStatusBarHeight(): Int {
+ val display = pluginCtx.getDisplayNoVerify()
+ if (display != null) {
+ return SystemBarUtils.getStatusBarHeight(pluginCtx.resources, display.cutout)
+ }
+
+ logger.w("No display available; falling back to android.R.dimen.status_bar_height")
+ val statusBarHeight = resolveResourceId("android", "dimen", "status_bar_height")
+ if (statusBarHeight != null) {
+ val (res, resId) = statusBarHeight
+ return res.getDimensionPixelSize(resId)
+ }
+
+ throw Exception("Could not fetch StatusBarHeight")
+ }
+
+ fun getResourcesId(name: String): Int = getResource("id", name) { _, id -> id }
+
+ fun getDimen(name: String): Int = getResource("dimen", name, Resources::getDimensionPixelSize)
+
+ fun getString(name: String): String = getResource("string", name, Resources::getString)
+
+ private fun <T> getResource(
+ category: String,
+ name: String,
+ getter: (res: Resources, id: Int) -> T,
+ ): T {
+ val result = resolveResourceId(null, category, name)
+ if (result != null) {
+ val (res, id) = result
+ if (id == -1) throw Exception("Cannot find id of $id from $TAG")
+ return getter(res, id)
+ }
+ throw Exception("Cannot find id of $name from $TAG")
+ }
+
+ private fun logColor(name: String, color: Int) {
+ if (DEBUG_COLOR) {
+ val cam = Cam.fromInt(color)
+ val tone = CamUtils.lstarFromInt(color)
+ logger.i("$name -> (hue: ${cam.hue}, chroma: ${cam.chroma}, tone: $tone)")
+ }
+ }
+
+ companion object {
+ private val DEBUG_COLOR = true
+ private val LOW_CHROMA_LIMIT = 15
+ private val LOW_CHROMA_SCALE = 1.5f
+ private val TAG = AssetLoader::class.simpleName!!
+
+ private fun getThemeSeedColor(ctx: Context): Int {
+ return ctx.resources.getColor(android.R.color.system_palette_key_color_primary_light)
+ }
+ }
+}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockAnimation.kt
similarity index 72%
copy from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt
copy to packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockAnimation.kt
index e52a6e1..5a04169 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockAnimation.kt
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.util
+package com.android.systemui.shared.clocks
-/** Holds a [newValue] emitted from a `TFlow`, along with the [previousValue] emitted value. */
-data class WithPrev<out S, out T : S>(val previousValue: S, val newValue: T)
+object ClockAnimation {
+ const val NUM_CLOCK_FONT_ANIMATION_STEPS = 30
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt
new file mode 100644
index 0000000..f5e8432
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.clocks
+
+import android.graphics.Point
+import android.view.animation.Interpolator
+import com.android.app.animation.Interpolators
+import com.android.internal.annotations.Keep
+import com.android.systemui.monet.Style as MonetStyle
+import com.android.systemui.shared.clocks.view.HorizontalAlignment
+import com.android.systemui.shared.clocks.view.VerticalAlignment
+
+/** Data format for a simple asset-defined clock */
+@Keep
+data class ClockDesign(
+ val id: String,
+ val name: String? = null,
+ val description: String? = null,
+ val thumbnail: String? = null,
+ val large: ClockFace? = null,
+ val small: ClockFace? = null,
+ val colorPalette: MonetStyle? = null,
+)
+
+/** Describes a clock using layers */
+@Keep
+data class ClockFace(
+ val layers: List<ClockLayer> = listOf<ClockLayer>(),
+ val layerBounds: LayerBounds = LayerBounds.FIT,
+ val wallpaper: String? = null,
+ val faceLayout: DigitalFaceLayout? = null,
+ val pickerScale: ClockFaceScaleInPicker? = ClockFaceScaleInPicker(1.0f, 1.0f),
+)
+
+@Keep data class ClockFaceScaleInPicker(val scaleX: Float, val scaleY: Float)
+
+/** Base Type for a Clock Layer */
+@Keep
+interface ClockLayer {
+ /** Override of face LayerBounds setting for this layer */
+ val layerBounds: LayerBounds?
+}
+
+/** Clock layer that renders a static asset */
+@Keep
+data class AssetLayer(
+ /** Asset to render in this layer */
+ val asset: AssetReference,
+ override val layerBounds: LayerBounds? = null,
+) : ClockLayer
+
+/** Clock layer that renders the time (or a component of it) using numerals */
+@Keep
+data class DigitalHandLayer(
+ /** See SimpleDateFormat for timespec format info */
+ val timespec: DigitalTimespec,
+ val style: TextStyle,
+ // adoStyle concrete type must match style,
+ // cause styles will transition between style and aodStyle
+ val aodStyle: TextStyle?,
+ val timer: Int? = null,
+ override val layerBounds: LayerBounds? = null,
+ var faceLayout: DigitalFaceLayout? = null,
+ // we pass 12-hour format from json, which will be converted to 24-hour format in codes
+ val dateTimeFormat: String,
+ val alignment: DigitalAlignment?,
+ // ratio of margins to measured size, currently used for handwritten clocks
+ val marginRatio: DigitalMarginRatio? = DigitalMarginRatio(),
+) : ClockLayer
+
+/** Clock layer that renders the time (or a component of it) using numerals */
+@Keep
+data class ComposedDigitalHandLayer(
+ val customizedView: String? = null,
+ /** See SimpleDateFormat for timespec format info */
+ val digitalLayers: List<DigitalHandLayer> = listOf<DigitalHandLayer>(),
+ override val layerBounds: LayerBounds? = null,
+) : ClockLayer
+
+@Keep
+data class DigitalAlignment(
+ val horizontalAlignment: HorizontalAlignment?,
+ val verticalAlignment: VerticalAlignment?,
+)
+
+@Keep
+data class DigitalMarginRatio(
+ val left: Float = 0F,
+ val top: Float = 0F,
+ val right: Float = 0F,
+ val bottom: Float = 0F,
+)
+
+/** Clock layer which renders a component of the time using an analog hand */
+@Keep
+data class AnalogHandLayer(
+ val timespec: AnalogTimespec,
+ val tickMode: AnalogTickMode,
+ val asset: AssetReference,
+ val timer: Int? = null,
+ val clock_pivot: Point = Point(0, 0),
+ val asset_pivot: Point? = null,
+ val length: Float = 1f,
+ override val layerBounds: LayerBounds? = null,
+) : ClockLayer
+
+/** Clock layer which renders the time using an AVD */
+@Keep
+data class AnimatedHandLayer(
+ val timespec: AnalogTimespec,
+ val asset: AssetReference,
+ val timer: Int? = null,
+ override val layerBounds: LayerBounds? = null,
+) : ClockLayer
+
+/** A collection of asset references for use in different device modes */
+@Keep
+data class AssetReference(
+ val light: String,
+ val dark: String,
+ val doze: String? = null,
+ val lightTint: String? = null,
+ val darkTint: String? = null,
+ val dozeTint: String? = null,
+)
+
+/**
+ * Core TextStyling attributes for text clocks. Both color and sizing information can be applied to
+ * either subtype.
+ */
+@Keep
+interface TextStyle {
+ // fontSizeScale is a scale factor applied to the default clock's font size.
+ val fontSizeScale: Float?
+}
+
+/**
+ * This specifies a font and styling parameters for that font. This is rendered using a text view
+ * and the text animation classes used by the default clock. To ensure default value take effects,
+ * all parameters MUST have a default value
+ */
+@Keep
+data class FontTextStyle(
+ // Font to load and use in the TextView
+ val fontFamily: String? = null,
+ val lineHeight: Float? = null,
+ val borderWidth: String? = null,
+ // ratio of borderWidth / fontSize
+ val borderWidthScale: Float? = null,
+ // A color literal like `#FF00FF` or a color resource like `@android:color/system_accent1_100`
+ val fillColorLight: String? = null,
+ // A color literal like `#FF00FF` or a color resource like `@android:color/system_accent1_100`
+ val fillColorDark: String? = null,
+ override val fontSizeScale: Float? = null,
+ /**
+ * use `wdth` for width, `wght` for weight, 'opsz' for optical size single quote for tag name,
+ * and no quote for value separate different axis with `,` e.g. "'wght' 1000, 'wdth' 108, 'opsz'
+ * 90"
+ */
+ var fontVariation: String? = null,
+ // used when alternate in one font file is needed
+ var fontFeatureSettings: String? = null,
+ val renderType: RenderType = RenderType.STROKE_TEXT,
+ val outlineColor: String? = null,
+ val transitionDuration: Long = -1L,
+ val transitionInterpolator: InterpolatorEnum? = null,
+) : TextStyle
+
+/**
+ * As an alternative to using a font, we can instead render a digital clock using a set of drawables
+ * for each numeral, and optionally a colon. These drawables will be rendered directly after sizing
+ * and placing them. This may be easier than generating a font file in some cases, and is provided
+ * for ease of use. Unlike fonts, these are not localizable to other numeric systems (like Burmese).
+ */
+@Keep
+data class LottieTextStyle(
+ val numbers: List<String> = listOf(),
+ // Spacing between numbers, dimension string
+ val spacing: String = "0dp",
+ // Colon drawable may be omitted if unused in format spec
+ val colon: String? = null,
+ // key is keypath name to get strokes from lottie, value is the color name to query color in
+ // palette, e.g. @android:color/system_accent1_100
+ val fillColorLightMap: Map<String, String>? = null,
+ val fillColorDarkMap: Map<String, String>? = null,
+ override val fontSizeScale: Float? = null,
+ val paddingVertical: String = "0dp",
+ val paddingHorizontal: String = "0dp",
+) : TextStyle
+
+/** Layer sizing mode for the clockface or layer */
+enum class LayerBounds {
+ /**
+ * Sized so the larger dimension matches the allocated space. This results in some of the
+ * allocated space being unused.
+ */
+ FIT,
+
+ /**
+ * Sized so the smaller dimension matches the allocated space. This will clip some content to
+ * the edges of the space.
+ */
+ FILL,
+
+ /** Fills the allocated space exactly by stretching the layer */
+ STRETCH,
+}
+
+/** Ticking mode for analog hands. */
+enum class AnalogTickMode {
+ SWEEP,
+ TICK,
+}
+
+/** Timspec options for Analog Hands. Named for tick interval. */
+enum class AnalogTimespec {
+ SECONDS,
+ MINUTES,
+ HOURS,
+ HOURS_OF_DAY,
+ DAY_OF_WEEK,
+ DAY_OF_MONTH,
+ DAY_OF_YEAR,
+ WEEK,
+ MONTH,
+ TIMER,
+}
+
+enum class DigitalTimespec {
+ TIME_FULL_FORMAT,
+ DIGIT_PAIR,
+ FIRST_DIGIT,
+ SECOND_DIGIT,
+ DATE_FORMAT,
+}
+
+enum class DigitalFaceLayout {
+ // can only use HH_PAIR, MM_PAIR from DigitalTimespec
+ TWO_PAIRS_VERTICAL,
+ TWO_PAIRS_HORIZONTAL,
+ // can only use HOUR_FIRST_DIGIT, HOUR_SECOND_DIGIT, MINUTE_FIRST_DIGIT, MINUTE_SECOND_DIGIT
+ // from DigitalTimespec, used for tabular layout when the font doesn't support tnum
+ FOUR_DIGITS_ALIGN_CENTER,
+ FOUR_DIGITS_HORIZONTAL,
+}
+
+enum class RenderType {
+ CHANGE_WEIGHT,
+ HOLLOW_TEXT,
+ STROKE_TEXT,
+ OUTER_OUTLINE_TEXT,
+}
+
+enum class InterpolatorEnum(factory: () -> Interpolator) {
+ STANDARD({ Interpolators.STANDARD }),
+ EMPHASIZED({ Interpolators.EMPHASIZED });
+
+ val interpolator: Interpolator by lazy(factory)
+}
+
+fun generateDigitalLayerIdString(layer: DigitalHandLayer): String {
+ return if (
+ layer.timespec == DigitalTimespec.TIME_FULL_FORMAT ||
+ layer.timespec == DigitalTimespec.DATE_FORMAT
+ ) {
+ layer.timespec.toString()
+ } else {
+ if ("h" in layer.dateTimeFormat) {
+ "HOUR" + "_" + layer.timespec.toString()
+ } else {
+ "MINUTE" + "_" + layer.timespec.toString()
+ }
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 954155d..9da3022 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -65,7 +65,7 @@
private fun <TKey : Any, TVal : Any> ConcurrentHashMap<TKey, TVal>.concurrentGetOrPut(
key: TKey,
value: TVal,
- onNew: (TVal) -> Unit
+ onNew: (TVal) -> Unit,
): TVal {
val result = this.putIfAbsent(key, value)
if (result == null) {
@@ -110,7 +110,7 @@
selfChange: Boolean,
uris: Collection<Uri>,
flags: Int,
- userId: Int
+ userId: Int,
) {
scope.launch(bgDispatcher) { querySettings() }
}
@@ -180,7 +180,7 @@
override fun onPluginLoaded(
plugin: ClockProviderPlugin,
pluginContext: Context,
- manager: PluginLifecycleManager<ClockProviderPlugin>
+ manager: PluginLifecycleManager<ClockProviderPlugin>,
) {
plugin.initialize(clockBuffers)
@@ -218,7 +218,7 @@
override fun onPluginUnloaded(
plugin: ClockProviderPlugin,
- manager: PluginLifecycleManager<ClockProviderPlugin>
+ manager: PluginLifecycleManager<ClockProviderPlugin>,
) {
for (clock in plugin.getClocks()) {
val id = clock.clockId
@@ -290,12 +290,12 @@
Settings.Secure.getStringForUser(
context.contentResolver,
Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
- ActivityManager.getCurrentUser()
+ ActivityManager.getCurrentUser(),
)
} else {
Settings.Secure.getString(
context.contentResolver,
- Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
)
}
@@ -320,13 +320,13 @@
context.contentResolver,
Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
json,
- ActivityManager.getCurrentUser()
+ ActivityManager.getCurrentUser(),
)
} else {
Settings.Secure.putString(
context.contentResolver,
Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
- json
+ json,
)
}
} catch (ex: Exception) {
@@ -418,7 +418,7 @@
pluginManager.addPluginListener(
pluginListener,
ClockProviderPlugin::class.java,
- /*allowMultiple=*/ true
+ /*allowMultiple=*/ true,
)
scope.launch(bgDispatcher) { querySettings() }
@@ -427,7 +427,7 @@
Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
/*notifyForDescendants=*/ false,
settingObserver,
- UserHandle.USER_ALL
+ UserHandle.USER_ALL,
)
ActivityManager.getService().registerUserSwitchObserver(userSwitchObserver, TAG)
@@ -435,7 +435,7 @@
context.contentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
/*notifyForDescendants=*/ false,
- settingObserver
+ settingObserver,
)
}
}
@@ -504,7 +504,7 @@
val isCurrent = currentClockId == info.metadata.clockId
logger.log(
if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
- { "Connected $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
+ { "Connected $str1 @$str2" + if (bool1) " (Current Clock)" else "" },
) {
str1 = info.metadata.clockId
str2 = info.manager.toString()
@@ -516,7 +516,7 @@
val isCurrent = currentClockId == info.metadata.clockId
logger.log(
if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
- { "Loaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
+ { "Loaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" },
) {
str1 = info.metadata.clockId
str2 = info.manager.toString()
@@ -532,7 +532,7 @@
val isCurrent = currentClockId == info.metadata.clockId
logger.log(
if (isCurrent) LogLevel.WARNING else LogLevel.DEBUG,
- { "Unloaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
+ { "Unloaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" },
) {
str1 = info.metadata.clockId
str2 = info.manager.toString()
@@ -548,7 +548,7 @@
val isCurrent = currentClockId == info.metadata.clockId
logger.log(
if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
- { "Disconnected $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
+ { "Disconnected $str1 @$str2" + if (bool1) " (Current Clock)" else "" },
) {
str1 = info.metadata.clockId
str2 = info.manager.toString()
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 4802e34..07191c6 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -34,7 +34,7 @@
val layoutInflater: LayoutInflater,
val resources: Resources,
val hasStepClockAnimation: Boolean = false,
- val migratedClocks: Boolean = false
+ val migratedClocks: Boolean = false,
) : ClockProvider {
private var messageBuffers: ClockMessageBuffers? = null
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DigitTranslateAnimator.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DigitTranslateAnimator.kt
new file mode 100644
index 0000000..3869706
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DigitTranslateAnimator.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.clocks
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.TimeInterpolator
+import android.animation.ValueAnimator
+import android.graphics.Point
+
+class DigitTranslateAnimator(val updateCallback: () -> Unit) {
+ val DEFAULT_ANIMATION_DURATION = 500L
+ val updatedTranslate = Point(0, 0)
+
+ val baseTranslation = Point(0, 0)
+ var targetTranslation: Point? = null
+ val bounceAnimator: ValueAnimator =
+ ValueAnimator.ofFloat(1f).apply {
+ duration = DEFAULT_ANIMATION_DURATION
+ addUpdateListener {
+ updateTranslation(it.animatedFraction, updatedTranslate)
+ updateCallback()
+ }
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ rebase()
+ }
+
+ override fun onAnimationCancel(animation: Animator) {
+ rebase()
+ }
+ }
+ )
+ }
+
+ fun rebase() {
+ baseTranslation.x = updatedTranslate.x
+ baseTranslation.y = updatedTranslate.y
+ }
+
+ fun animatePosition(
+ animate: Boolean = true,
+ delay: Long = 0,
+ duration: Long = -1L,
+ interpolator: TimeInterpolator? = null,
+ targetTranslation: Point? = null,
+ onAnimationEnd: Runnable? = null,
+ ) {
+ this.targetTranslation = targetTranslation ?: Point(0, 0)
+ if (animate) {
+ bounceAnimator.cancel()
+ bounceAnimator.startDelay = delay
+ bounceAnimator.duration =
+ if (duration == -1L) {
+ DEFAULT_ANIMATION_DURATION
+ } else {
+ duration
+ }
+ interpolator?.let { bounceAnimator.interpolator = it }
+ if (onAnimationEnd != null) {
+ val listener =
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ onAnimationEnd.run()
+ bounceAnimator.removeListener(this)
+ }
+
+ override fun onAnimationCancel(animation: Animator) {
+ bounceAnimator.removeListener(this)
+ }
+ }
+ bounceAnimator.addListener(listener)
+ }
+ bounceAnimator.start()
+ } else {
+ // No animation is requested, thus set base and target state to the same state.
+ updateTranslation(1F, updatedTranslate)
+ rebase()
+ updateCallback()
+ }
+ }
+
+ fun updateTranslation(progress: Float, outPoint: Point) {
+ outPoint.x =
+ (baseTranslation.x + progress * (targetTranslation!!.x - baseTranslation.x)).toInt()
+ outPoint.y =
+ (baseTranslation.y + progress * (targetTranslation!!.y - baseTranslation.y)).toInt()
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DimensionParser.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DimensionParser.kt
new file mode 100644
index 0000000..2be6c65
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DimensionParser.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.clocks
+
+import android.content.Context
+import android.util.TypedValue
+import java.util.regex.Pattern
+
+class DimensionParser(private val ctx: Context) {
+ fun convert(dimension: String?): Float? {
+ if (dimension == null) {
+ return null
+ }
+ return convert(dimension)
+ }
+
+ fun convert(dimension: String): Float {
+ val metrics = ctx.resources.displayMetrics
+ val (value, unit) = parse(dimension)
+ return TypedValue.applyDimension(unit, value, metrics)
+ }
+
+ fun parse(dimension: String): Pair<Float, Int> {
+ val matcher = parserPattern.matcher(dimension)
+ if (!matcher.matches()) {
+ throw NumberFormatException("Failed to parse '$dimension'")
+ }
+
+ val value =
+ matcher.group(1)?.toFloat() ?: throw NumberFormatException("Bad value in '$dimension'")
+ val unit =
+ dimensionMap.get(matcher.group(3) ?: "")
+ ?: throw NumberFormatException("Bad unit in '$dimension'")
+ return Pair(value, unit)
+ }
+
+ private companion object {
+ val parserPattern = Pattern.compile("(\\d+(\\.\\d+)?)([a-z]+)")
+ val dimensionMap =
+ mapOf(
+ "dp" to TypedValue.COMPLEX_UNIT_DIP,
+ "dip" to TypedValue.COMPLEX_UNIT_DIP,
+ "sp" to TypedValue.COMPLEX_UNIT_SP,
+ "px" to TypedValue.COMPLEX_UNIT_PX,
+ "pt" to TypedValue.COMPLEX_UNIT_PT,
+ "mm" to TypedValue.COMPLEX_UNIT_MM,
+ "in" to TypedValue.COMPLEX_UNIT_IN,
+ )
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/LogUtil.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/LogUtil.kt
new file mode 100644
index 0000000..34cb4ef
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/LogUtil.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.clocks
+
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogcatOnlyMessageBuffer
+import com.android.systemui.log.core.Logger
+
+object LogUtil {
+ // Used when MessageBuffers are not provided by the host application
+ val DEFAULT_MESSAGE_BUFFER = LogcatOnlyMessageBuffer(LogLevel.INFO)
+
+ // Only intended for use during initialization steps where the correct logger doesn't exist yet
+ val FALLBACK_INIT_LOGGER = Logger(LogcatOnlyMessageBuffer(LogLevel.ERROR), "CLOCK_INIT")
+
+ // Debug is primarially used for tests, but can also be used for tracking down hard issues.
+ val DEBUG_MESSAGE_BUFFER = LogcatOnlyMessageBuffer(LogLevel.DEBUG)
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/TypefaceCache.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/TypefaceCache.kt
new file mode 100644
index 0000000..f5a9375
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/TypefaceCache.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.clocks
+
+import android.graphics.Typeface
+import com.android.systemui.animation.TypefaceVariantCache
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.core.MessageBuffer
+import java.lang.ref.ReferenceQueue
+import java.lang.ref.WeakReference
+
+class TypefaceCache(messageBuffer: MessageBuffer, val typefaceFactory: (String) -> Typeface) {
+ private val logger = Logger(messageBuffer, this::class.simpleName!!)
+
+ private data class CacheKey(val res: String, val fvar: String?)
+
+ private inner class WeakTypefaceRef(val key: CacheKey, typeface: Typeface) :
+ WeakReference<Typeface>(typeface, queue)
+
+ private var totalHits = 0
+
+ private var totalMisses = 0
+
+ private var totalEvictions = 0
+
+ // We use a map of WeakRefs here instead of an LruCache. This prevents needing to resize the
+ // cache depending on the number of distinct fonts used by a clock, as different clocks have
+ // different numbers of simultaneously loaded and configured fonts. Because our clocks tend to
+ // initialize a number of parallel views and animators, our usages of Typefaces overlap. As a
+ // result, once a typeface is no longer being used, it is unlikely to be recreated immediately.
+ private val cache = mutableMapOf<CacheKey, WeakTypefaceRef>()
+ private val queue = ReferenceQueue<Typeface>()
+
+ fun getTypeface(res: String): Typeface {
+ checkQueue()
+ val key = CacheKey(res, null)
+ cache.get(key)?.get()?.let {
+ logHit(key)
+ return it
+ }
+
+ logMiss(key)
+ val result = typefaceFactory(res)
+ cache.put(key, WeakTypefaceRef(key, result))
+ return result
+ }
+
+ fun getVariantCache(res: String): TypefaceVariantCache {
+ val baseTypeface = getTypeface(res)
+ return object : TypefaceVariantCache {
+ override fun getTypefaceForVariant(fvar: String?): Typeface? {
+ checkQueue()
+ val key = CacheKey(res, fvar)
+ cache.get(key)?.get()?.let {
+ logHit(key)
+ return it
+ }
+
+ logMiss(key)
+ return TypefaceVariantCache.createVariantTypeface(baseTypeface, fvar).also {
+ cache.put(key, WeakTypefaceRef(key, it))
+ }
+ }
+ }
+ }
+
+ private fun logHit(key: CacheKey) {
+ totalHits++
+ if (DEBUG_HITS)
+ logger.i({ "HIT: $str1; Total: $int1" }) {
+ str1 = key.toString()
+ int1 = totalHits
+ }
+ }
+
+ private fun logMiss(key: CacheKey) {
+ totalMisses++
+ logger.w({ "MISS: $str1; Total: $int1" }) {
+ str1 = key.toString()
+ int1 = totalMisses
+ }
+ }
+
+ private fun logEviction(key: CacheKey) {
+ totalEvictions++
+ logger.i({ "EVICTED: $str1; Total: $int1" }) {
+ str1 = key.toString()
+ int1 = totalEvictions
+ }
+ }
+
+ private fun checkQueue() =
+ generateSequence { queue.poll() }
+ .filterIsInstance<WeakTypefaceRef>()
+ .forEach {
+ logEviction(it.key)
+ cache.remove(it.key)
+ }
+
+ companion object {
+ private val DEBUG_HITS = false
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt
new file mode 100644
index 0000000..eb72346
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.clocks.view
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Point
+import android.view.View
+import android.widget.FrameLayout
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.core.MessageBuffer
+import com.android.systemui.plugins.clocks.AlarmData
+import com.android.systemui.plugins.clocks.WeatherData
+import com.android.systemui.plugins.clocks.ZenData
+import com.android.systemui.shared.clocks.AssetLoader
+import com.android.systemui.shared.clocks.LogUtil
+import java.util.Locale
+
+abstract class DigitalClockFaceView(ctx: Context, messageBuffer: MessageBuffer) : FrameLayout(ctx) {
+ protected val logger = Logger(messageBuffer, this::class.simpleName!!)
+ get() = field ?: LogUtil.FALLBACK_INIT_LOGGER
+
+ abstract var digitalClockTextViewMap: MutableMap<Int, SimpleDigitalClockTextView>
+
+ @VisibleForTesting
+ var isAnimationEnabled = true
+ set(value) {
+ field = value
+ digitalClockTextViewMap.forEach { _, view -> view.isAnimationEnabled = value }
+ }
+
+ var dozeFraction: Float = 0F
+ set(value) {
+ field = value
+ digitalClockTextViewMap.forEach { _, view -> view.dozeFraction = field }
+ }
+
+ val dozeControlState = DozeControlState()
+
+ var isReactiveTouchInteractionEnabled = false
+ set(value) {
+ field = value
+ }
+
+ open val text: String?
+ get() = null
+
+ open fun refreshTime() = logger.d("refreshTime()")
+
+ override fun invalidate() {
+ logger.d("invalidate()")
+ super.invalidate()
+ }
+
+ override fun requestLayout() {
+ logger.d("requestLayout()")
+ super.requestLayout()
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ logger.d("onMeasure()")
+ calculateSize(widthMeasureSpec, heightMeasureSpec)?.let { setMeasuredDimension(it.x, it.y) }
+ ?: run { super.onMeasure(widthMeasureSpec, heightMeasureSpec) }
+ calculateLeftTopPosition()
+ dozeControlState.animateReady = true
+ }
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ logger.d("onLayout()")
+ super.onLayout(changed, left, top, right, bottom)
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ text?.let { logger.d({ "onDraw($str1)" }) { str1 = it } } ?: run { logger.d("onDraw()") }
+ super.onDraw(canvas)
+ }
+
+ /*
+ * Called in onMeasure to generate width/height overrides to the normal measuring logic. A null
+ * result causes the normal view measuring logic to execute.
+ */
+ protected open fun calculateSize(widthMeasureSpec: Int, heightMeasureSpec: Int): Point? = null
+
+ protected open fun calculateLeftTopPosition() {}
+
+ override fun addView(child: View?) {
+ if (child == null) return
+ logger.d({ "addView($str1 @$int1)" }) {
+ str1 = child::class.simpleName!!
+ int1 = child.id
+ }
+ super.addView(child)
+ if (child is SimpleDigitalClockTextView) {
+ digitalClockTextViewMap[child.id] = child
+ }
+ child.setWillNotDraw(true)
+ }
+
+ open fun animateDoze(isDozing: Boolean, isAnimated: Boolean) {
+ digitalClockTextViewMap.forEach { _, view -> view.animateDoze(isDozing, isAnimated) }
+ }
+
+ open fun animateCharge() {
+ digitalClockTextViewMap.forEach { _, view -> view.animateCharge() }
+ }
+
+ open fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) {}
+
+ fun updateColors(assets: AssetLoader, isRegionDark: Boolean) {
+ digitalClockTextViewMap.forEach { _, view -> view.updateColors(assets, isRegionDark) }
+ invalidate()
+ }
+
+ fun onFontSettingChanged(fontSizePx: Float) {
+ digitalClockTextViewMap.forEach { _, view -> view.applyTextSize(fontSizePx) }
+ }
+
+ open val hasCustomWeatherDataDisplay
+ get() = false
+
+ open val hasCustomPositionUpdatedAnimation
+ get() = false
+
+ /** True if it's large weather clock, will use weatherBlueprint in compose */
+ open val useCustomClockScene
+ get() = false
+
+ // TODO: implement ClockEventUnion?
+ open fun onLocaleChanged(locale: Locale) {}
+
+ open fun onWeatherDataChanged(data: WeatherData) {}
+
+ open fun onAlarmDataChanged(data: AlarmData) {}
+
+ open fun onZenDataChanged(data: ZenData) {}
+
+ open fun onPickerCarouselSwiping(swipingFraction: Float) {}
+
+ open fun isAlignedWithScreen(): Boolean = false
+
+ /**
+ * animateDoze needs correct translate value, which is calculated in onMeasure so we need to
+ * delay this animation when we get correct values
+ */
+ class DozeControlState {
+ var animateDoze: () -> Unit = {}
+ set(value) {
+ if (animateReady) {
+ value()
+ field = {}
+ } else {
+ field = value
+ }
+ }
+
+ var animateReady = false
+ set(value) {
+ if (value) {
+ animateDoze()
+ animateDoze = {}
+ }
+ field = value
+ }
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
new file mode 100644
index 0000000..c29c8da
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.clocks.view
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Point
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import android.widget.RelativeLayout
+import com.android.app.animation.Interpolators
+import com.android.systemui.customization.R
+import com.android.systemui.log.core.MessageBuffer
+import com.android.systemui.shared.clocks.AssetLoader
+import com.android.systemui.shared.clocks.DigitTranslateAnimator
+import com.android.systemui.shared.clocks.FontTextStyle
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
+
+fun clamp(value: Float, minVal: Float, maxVal: Float): Float = max(min(value, maxVal), minVal)
+
+class FlexClockView(context: Context, val assetLoader: AssetLoader, messageBuffer: MessageBuffer) :
+ DigitalClockFaceView(context, messageBuffer) {
+ override var digitalClockTextViewMap = mutableMapOf<Int, SimpleDigitalClockTextView>()
+ val digitLeftTopMap = mutableMapOf<Int, Point>()
+ var maxSingleDigitHeight = -1
+ var maxSingleDigitWidth = -1
+ val lockscreenTranslate = Point(0, 0)
+ val aodTranslate = Point(0, 0)
+
+ init {
+ setWillNotDraw(false)
+ layoutParams =
+ RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ )
+ }
+
+ private var prevX = 0f
+ private var prevY = 0f
+ private var isDown = false
+
+ // TODO(b/340253296): Genericize; json spec
+ private var wght = 603f
+ private var wdth = 100f
+
+ // TODO(b/340253296): Json spec
+ private val MAX_WGHT = 950f
+ private val MIN_WGHT = 50f
+ private val WGHT_SCALE = 0.5f
+
+ private val MAX_WDTH = 150f
+ private val MIN_WDTH = 0f
+ private val WDTH_SCALE = 0.2f
+
+ override fun onTouchEvent(evt: MotionEvent): Boolean {
+ // TODO(b/340253296): implement on DigitalClockFaceView?
+ if (!isReactiveTouchInteractionEnabled) {
+ return super.onTouchEvent(evt)
+ }
+
+ when (evt.action) {
+ MotionEvent.ACTION_DOWN -> {
+ isDown = true
+ prevX = evt.x
+ prevY = evt.y
+ return true
+ }
+
+ MotionEvent.ACTION_MOVE -> {
+ if (!isDown) {
+ return super.onTouchEvent(evt)
+ }
+
+ wdth = clamp(wdth + (evt.x - prevX) * WDTH_SCALE, MIN_WDTH, MAX_WDTH)
+ wght = clamp(wght + (evt.y - prevY) * WGHT_SCALE, MIN_WGHT, MAX_WGHT)
+ prevX = evt.x
+ prevY = evt.y
+
+ // TODO(b/340253296): Genericize; json spec
+ val fvar = "'wght' $wght, 'wdth' $wdth, 'opsz' 144, 'ROND' 100"
+ digitalClockTextViewMap.forEach { (_, view) ->
+ val textStyle = view.textStyle as FontTextStyle
+ textStyle.fontVariation = fvar
+ view.applyStyles(assetLoader, textStyle, view.aodStyle)
+ }
+
+ requestLayout()
+ invalidate()
+ return true
+ }
+
+ MotionEvent.ACTION_UP -> {
+ isDown = false
+ return true
+ }
+ }
+
+ return super.onTouchEvent(evt)
+ }
+
+ override fun addView(child: View?) {
+ super.addView(child)
+ (child as SimpleDigitalClockTextView).digitTranslateAnimator =
+ DigitTranslateAnimator(::invalidate)
+ }
+
+ protected override fun calculateSize(widthMeasureSpec: Int, heightMeasureSpec: Int): Point {
+ digitalClockTextViewMap.forEach { (_, textView) ->
+ textView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
+ }
+ val textView = digitalClockTextViewMap[R.id.HOUR_FIRST_DIGIT]!!
+ maxSingleDigitHeight = textView.measuredHeight
+ maxSingleDigitWidth = textView.measuredWidth
+ aodTranslate.x = -(maxSingleDigitWidth * AOD_HORIZONTAL_TRANSLATE_RATIO).toInt()
+ aodTranslate.y = (maxSingleDigitHeight * AOD_VERTICAL_TRANSLATE_RATIO).toInt()
+ return Point(
+ ((maxSingleDigitWidth + abs(aodTranslate.x)) * 2),
+ ((maxSingleDigitHeight + abs(aodTranslate.y)) * 2),
+ )
+ }
+
+ protected override fun calculateLeftTopPosition() {
+ digitLeftTopMap[R.id.HOUR_FIRST_DIGIT] = Point(0, 0)
+ digitLeftTopMap[R.id.HOUR_SECOND_DIGIT] = Point(maxSingleDigitWidth, 0)
+ digitLeftTopMap[R.id.MINUTE_FIRST_DIGIT] = Point(0, maxSingleDigitHeight)
+ digitLeftTopMap[R.id.MINUTE_SECOND_DIGIT] = Point(maxSingleDigitWidth, maxSingleDigitHeight)
+ digitLeftTopMap.forEach { _, point ->
+ point.x += abs(aodTranslate.x)
+ point.y += abs(aodTranslate.y)
+ }
+ }
+
+ override fun refreshTime() {
+ super.refreshTime()
+ digitalClockTextViewMap.forEach { (_, textView) -> textView.refreshText() }
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+ digitalClockTextViewMap.forEach { (id, _) ->
+ val textView = digitalClockTextViewMap[id]!!
+ canvas.translate(digitLeftTopMap[id]!!.x.toFloat(), digitLeftTopMap[id]!!.y.toFloat())
+ textView.draw(canvas)
+ canvas.translate(-digitLeftTopMap[id]!!.x.toFloat(), -digitLeftTopMap[id]!!.y.toFloat())
+ }
+ }
+
+ override fun animateDoze(isDozing: Boolean, isAnimated: Boolean) {
+ dozeControlState.animateDoze = {
+ super.animateDoze(isDozing, isAnimated)
+ if (maxSingleDigitHeight == -1) {
+ measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
+ }
+ digitalClockTextViewMap.forEach { (id, textView) ->
+ textView.digitTranslateAnimator?.let {
+ if (!isDozing) {
+ it.animatePosition(
+ animate = isAnimated && isAnimationEnabled,
+ interpolator = Interpolators.EMPHASIZED,
+ duration = AOD_TRANSITION_DURATION,
+ targetTranslation =
+ updateDirectionalTargetTranslate(id, lockscreenTranslate),
+ )
+ } else {
+ it.animatePosition(
+ animate = isAnimated && isAnimationEnabled,
+ interpolator = Interpolators.EMPHASIZED,
+ duration = AOD_TRANSITION_DURATION,
+ onAnimationEnd = null,
+ targetTranslation = updateDirectionalTargetTranslate(id, aodTranslate),
+ )
+ }
+ }
+ }
+ }
+ }
+
+ override fun animateCharge() {
+ super.animateCharge()
+ digitalClockTextViewMap.forEach { (id, textView) ->
+ textView.digitTranslateAnimator?.let {
+ it.animatePosition(
+ animate = isAnimationEnabled,
+ interpolator = Interpolators.EMPHASIZED,
+ duration = CHARGING_TRANSITION_DURATION,
+ onAnimationEnd = {
+ it.animatePosition(
+ animate = isAnimationEnabled,
+ interpolator = Interpolators.EMPHASIZED,
+ duration = CHARGING_TRANSITION_DURATION,
+ targetTranslation =
+ updateDirectionalTargetTranslate(
+ id,
+ if (dozeFraction == 1F) aodTranslate else lockscreenTranslate,
+ ),
+ )
+ },
+ targetTranslation =
+ updateDirectionalTargetTranslate(
+ id,
+ if (dozeFraction == 1F) lockscreenTranslate else aodTranslate,
+ ),
+ )
+ }
+ }
+ }
+
+ companion object {
+ val AOD_TRANSITION_DURATION = 750L
+ val CHARGING_TRANSITION_DURATION = 300L
+
+ val AOD_HORIZONTAL_TRANSLATE_RATIO = 0.15F
+ val AOD_VERTICAL_TRANSLATE_RATIO = 0.075F
+
+ // Use the sign of targetTranslation to control the direction of digit translation
+ fun updateDirectionalTargetTranslate(id: Int, targetTranslation: Point): Point {
+ val outPoint = Point(targetTranslation)
+ when (id) {
+ R.id.HOUR_FIRST_DIGIT -> {
+ outPoint.x *= -1
+ outPoint.y *= -1
+ }
+
+ R.id.HOUR_SECOND_DIGIT -> {
+ outPoint.x *= 1
+ outPoint.y *= -1
+ }
+
+ R.id.MINUTE_FIRST_DIGIT -> {
+ outPoint.x *= -1
+ outPoint.y *= 1
+ }
+
+ R.id.MINUTE_SECOND_DIGIT -> {
+ outPoint.x *= 1
+ outPoint.y *= 1
+ }
+ }
+ return outPoint
+ }
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
new file mode 100644
index 0000000..74617b1
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
@@ -0,0 +1,654 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.clocks.view
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Point
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
+import android.graphics.Rect
+import android.text.Layout
+import android.text.TextPaint
+import android.util.AttributeSet
+import android.util.Log
+import android.util.MathUtils
+import android.util.TypedValue
+import android.view.View.MeasureSpec.AT_MOST
+import android.view.View.MeasureSpec.EXACTLY
+import android.view.animation.Interpolator
+import android.widget.TextView
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.animation.TextAnimator
+import com.android.systemui.animation.TypefaceVariantCache
+import com.android.systemui.customization.R
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.core.MessageBuffer
+import com.android.systemui.shared.clocks.AssetLoader
+import com.android.systemui.shared.clocks.ClockAnimation
+import com.android.systemui.shared.clocks.DigitTranslateAnimator
+import com.android.systemui.shared.clocks.DimensionParser
+import com.android.systemui.shared.clocks.FontTextStyle
+import com.android.systemui.shared.clocks.LogUtil
+import com.android.systemui.shared.clocks.RenderType
+import com.android.systemui.shared.clocks.TextStyle
+import java.lang.Thread
+import kotlin.math.ceil
+import kotlin.math.max
+import kotlin.math.min
+
+private val TAG = SimpleDigitalClockTextView::class.simpleName!!
+
+@SuppressLint("AppCompatCustomView")
+open class SimpleDigitalClockTextView(
+ ctx: Context,
+ messageBuffer: MessageBuffer,
+ attrs: AttributeSet? = null,
+) : TextView(ctx, attrs), SimpleDigitalClockView {
+ val lockScreenPaint = TextPaint()
+ override lateinit var textStyle: FontTextStyle
+ lateinit var aodStyle: FontTextStyle
+ private val parser = DimensionParser(ctx)
+ var maxSingleDigitHeight = -1
+ var maxSingleDigitWidth = -1
+ var digitTranslateAnimator: DigitTranslateAnimator? = null
+ var aodFontSizePx: Float = -1F
+ var isVertical: Boolean = false
+
+ // Store the font size when there's no height constraint as a reference when adjusting font size
+ private var lastUnconstrainedTextSize: Float = Float.MAX_VALUE
+ // Calculated by height of styled text view / text size
+ // Used as a factor to calculate a smaller font size when text height is constrained
+ @VisibleForTesting var fontSizeAdjustFactor = 1F
+
+ private val initThread = Thread.currentThread()
+
+ // textBounds is the size of text in LS, which only measures current text in lockscreen style
+ var textBounds = Rect()
+ // prevTextBounds and targetTextBounds are to deal with dozing animation between LS and AOD
+ // especially for the textView which has different bounds during the animation
+ // prevTextBounds holds the state we are transitioning from
+ private val prevTextBounds = Rect()
+ // targetTextBounds holds the state we are interpolating to
+ private val targetTextBounds = Rect()
+ protected val logger = Logger(messageBuffer, this::class.simpleName!!)
+ get() = field ?: LogUtil.FALLBACK_INIT_LOGGER
+
+ private var aodDozingInterpolator: Interpolator? = null
+
+ @VisibleForTesting lateinit var textAnimator: TextAnimator
+ @VisibleForTesting var outlineAnimator: TextAnimator? = null
+ // used for hollow style for AOD version
+ // because stroke style for some fonts have some unwanted inner strokes
+ // we want to draw this layer on top to oclude them
+ @VisibleForTesting var innerAnimator: TextAnimator? = null
+
+ lateinit var typefaceCache: TypefaceVariantCache
+ private set
+
+ private fun setTypefaceCache(value: TypefaceVariantCache) {
+ typefaceCache = value
+ if (this::textAnimator.isInitialized) {
+ textAnimator.typefaceCache = value
+ }
+ outlineAnimator?.typefaceCache = value
+ innerAnimator?.typefaceCache = value
+ }
+
+ @VisibleForTesting
+ var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator = { layout, invalidateCb ->
+ TextAnimator(layout, ClockAnimation.NUM_CLOCK_FONT_ANIMATION_STEPS, invalidateCb).also {
+ if (this::typefaceCache.isInitialized) {
+ it.typefaceCache = typefaceCache
+ }
+ }
+ }
+
+ override var verticalAlignment: VerticalAlignment = VerticalAlignment.CENTER
+ override var horizontalAlignment: HorizontalAlignment = HorizontalAlignment.LEFT
+ override var isAnimationEnabled = true
+ override var dozeFraction: Float = 0F
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ // Have to passthrough to unify View with SimpleDigitalClockView
+ override var text: String
+ get() = super.getText().toString()
+ set(value) = super.setText(value)
+
+ var textBorderWidth = 0F
+ var aodBorderWidth = 0F
+ var baselineFromMeasure = 0
+
+ var textFillColor: Int? = null
+ var textOutlineColor = TEXT_OUTLINE_DEFAULT_COLOR
+ var aodFillColor = AOD_DEFAULT_COLOR
+ var aodOutlineColor = AOD_OUTLINE_DEFAULT_COLOR
+
+ override fun updateColors(assets: AssetLoader, isRegionDark: Boolean) {
+ val fillColor = if (isRegionDark) textStyle.fillColorLight else textStyle.fillColorDark
+ textFillColor =
+ fillColor?.let { assets.readColor(it) }
+ ?: assets.seedColor
+ ?: getDefaultColor(assets, isRegionDark)
+ // for NumberOverlapView to read correct color
+ lockScreenPaint.color = textFillColor as Int
+ textStyle.outlineColor?.let { textOutlineColor = assets.readColor(it) }
+ ?: run { textOutlineColor = TEXT_OUTLINE_DEFAULT_COLOR }
+ (aodStyle.fillColorLight ?: aodStyle.fillColorDark)?.let {
+ aodFillColor = assets.readColor(it)
+ } ?: run { aodFillColor = AOD_DEFAULT_COLOR }
+ aodStyle.outlineColor?.let { aodOutlineColor = assets.readColor(it) }
+ ?: run { aodOutlineColor = AOD_OUTLINE_DEFAULT_COLOR }
+ if (dozeFraction < 1f) {
+ textAnimator.setTextStyle(color = textFillColor, animate = false)
+ outlineAnimator?.setTextStyle(color = textOutlineColor, animate = false)
+ }
+ invalidate()
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ logger.d("onMeasure()")
+ if (isVertical) {
+ // use at_most to avoid apply measuredWidth from last measuring to measuredHeight
+ // cause we use max to setMeasuredDimension
+ super.onMeasure(
+ MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), AT_MOST),
+ MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), AT_MOST),
+ )
+ } else {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ }
+
+ val layout = this.layout
+ if (layout != null) {
+ if (!this::textAnimator.isInitialized) {
+ textAnimator = textAnimatorFactory(layout, ::invalidate)
+ outlineAnimator = textAnimatorFactory(layout) {}
+ innerAnimator = textAnimatorFactory(layout) {}
+ setInterpolatorPaint()
+ } else {
+ textAnimator.updateLayout(layout)
+ outlineAnimator?.updateLayout(layout)
+ innerAnimator?.updateLayout(layout)
+ }
+ baselineFromMeasure = layout.getLineBaseline(0)
+ } else {
+ val currentThread = Thread.currentThread()
+ Log.wtf(
+ TAG,
+ "TextView.getLayout() is null after measure! " +
+ "currentThread=$currentThread; initThread=$initThread",
+ )
+ }
+
+ var expectedWidth: Int
+ var expectedHeight: Int
+
+ if (MeasureSpec.getMode(heightMeasureSpec) == EXACTLY) {
+ // For view which has fixed height, e.g. small clock,
+ // we should always return the size required from parent view
+ expectedHeight = heightMeasureSpec
+ } else {
+ expectedHeight =
+ MeasureSpec.makeMeasureSpec(
+ if (isSingleDigit()) {
+ maxSingleDigitHeight
+ } else {
+ textBounds.height() + 2 * lockScreenPaint.strokeWidth.toInt()
+ },
+ MeasureSpec.getMode(measuredHeight),
+ )
+ }
+ if (MeasureSpec.getMode(widthMeasureSpec) == EXACTLY) {
+ expectedWidth = widthMeasureSpec
+ } else {
+ expectedWidth =
+ MeasureSpec.makeMeasureSpec(
+ if (isSingleDigit()) {
+ maxSingleDigitWidth
+ } else {
+ max(
+ textBounds.width() + 2 * lockScreenPaint.strokeWidth.toInt(),
+ MeasureSpec.getSize(measuredWidth),
+ )
+ },
+ MeasureSpec.getMode(measuredWidth),
+ )
+ }
+
+ if (isVertical) {
+ expectedWidth = expectedHeight.also { expectedHeight = expectedWidth }
+ }
+ setMeasuredDimension(expectedWidth, expectedHeight)
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ if (isVertical) {
+ canvas.save()
+ canvas.translate(0F, measuredHeight.toFloat())
+ canvas.rotate(-90F)
+ }
+ logger.d({ "onDraw(); ls: $str1; aod: $str2;" }) {
+ str1 = textAnimator.textInterpolator.shapedText
+ str2 = outlineAnimator?.textInterpolator?.shapedText
+ }
+ val translation = getLocalTranslation()
+ canvas.translate(translation.x.toFloat(), translation.y.toFloat())
+ digitTranslateAnimator?.let {
+ canvas.translate(it.updatedTranslate.x.toFloat(), it.updatedTranslate.y.toFloat())
+ }
+
+ if (aodStyle.renderType == RenderType.HOLLOW_TEXT) {
+ canvas.saveLayer(
+ -translation.x.toFloat(),
+ -translation.y.toFloat(),
+ (-translation.x + measuredWidth).toFloat(),
+ (-translation.y + measuredHeight).toFloat(),
+ null,
+ )
+ outlineAnimator?.draw(canvas)
+ canvas.saveLayer(
+ -translation.x.toFloat(),
+ -translation.y.toFloat(),
+ (-translation.x + measuredWidth).toFloat(),
+ (-translation.y + measuredHeight).toFloat(),
+ Paint().also { it.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT) },
+ )
+ innerAnimator?.draw(canvas)
+ canvas.restore()
+ canvas.restore()
+ } else if (aodStyle.renderType != RenderType.CHANGE_WEIGHT) {
+ outlineAnimator?.draw(canvas)
+ }
+ textAnimator.draw(canvas)
+
+ digitTranslateAnimator?.let {
+ canvas.translate(-it.updatedTranslate.x.toFloat(), -it.updatedTranslate.y.toFloat())
+ }
+ canvas.translate(-translation.x.toFloat(), -translation.y.toFloat())
+ if (isVertical) {
+ canvas.restore()
+ }
+ }
+
+ override fun invalidate() {
+ logger.d("invalidate()")
+ super.invalidate()
+ (parent as? DigitalClockFaceView)?.invalidate()
+ }
+
+ override fun refreshTime() {
+ logger.d("refreshTime()")
+ refreshText()
+ }
+
+ override fun animateDoze(isDozing: Boolean, isAnimated: Boolean) {
+ if (!this::textAnimator.isInitialized) {
+ return
+ }
+ val fvar = if (isDozing) aodStyle.fontVariation else textStyle.fontVariation
+ textAnimator.setTextStyle(
+ animate = isAnimated && isAnimationEnabled,
+ color = if (isDozing) aodFillColor else textFillColor,
+ textSize = if (isDozing) aodFontSizePx else lockScreenPaint.textSize,
+ fvar = fvar,
+ duration = aodStyle.transitionDuration,
+ interpolator = aodDozingInterpolator,
+ )
+ updateTextBoundsForTextAnimator()
+ outlineAnimator?.setTextStyle(
+ animate = isAnimated && isAnimationEnabled,
+ color = if (isDozing) aodOutlineColor else textOutlineColor,
+ textSize = if (isDozing) aodFontSizePx else lockScreenPaint.textSize,
+ fvar = fvar,
+ strokeWidth = if (isDozing) aodBorderWidth else textBorderWidth,
+ duration = aodStyle.transitionDuration,
+ interpolator = aodDozingInterpolator,
+ )
+ innerAnimator?.setTextStyle(
+ animate = isAnimated && isAnimationEnabled,
+ color = Color.WHITE,
+ textSize = if (isDozing) aodFontSizePx else lockScreenPaint.textSize,
+ fvar = fvar,
+ duration = aodStyle.transitionDuration,
+ interpolator = aodDozingInterpolator,
+ )
+ }
+
+ override fun animateCharge() {
+ if (!this::textAnimator.isInitialized || textAnimator.isRunning()) {
+ // Skip charge animation if dozing animation is already playing.
+ return
+ }
+ logger.d("animateCharge()")
+ val middleFvar = if (dozeFraction == 0F) aodStyle.fontVariation else textStyle.fontVariation
+ val endFvar = if (dozeFraction == 0F) textStyle.fontVariation else aodStyle.fontVariation
+ val startAnimPhase2 = Runnable {
+ textAnimator.setTextStyle(fvar = endFvar, animate = isAnimationEnabled)
+ outlineAnimator?.setTextStyle(fvar = endFvar, animate = isAnimationEnabled)
+ innerAnimator?.setTextStyle(fvar = endFvar, animate = isAnimationEnabled)
+ updateTextBoundsForTextAnimator()
+ }
+ textAnimator.setTextStyle(
+ fvar = middleFvar,
+ animate = isAnimationEnabled,
+ onAnimationEnd = startAnimPhase2,
+ )
+ outlineAnimator?.setTextStyle(fvar = middleFvar, animate = isAnimationEnabled)
+ innerAnimator?.setTextStyle(fvar = middleFvar, animate = isAnimationEnabled)
+ updateTextBoundsForTextAnimator()
+ }
+
+ fun refreshText() {
+ lockScreenPaint.getTextBounds(text, 0, text.length, textBounds)
+ if (this::textAnimator.isInitialized) {
+ textAnimator.textInterpolator.targetPaint.getTextBounds(
+ text,
+ 0,
+ text.length,
+ targetTextBounds,
+ )
+ }
+ if (layout == null) {
+ requestLayout()
+ } else {
+ textAnimator.updateLayout(layout)
+ outlineAnimator?.updateLayout(layout)
+ innerAnimator?.updateLayout(layout)
+ }
+ }
+
+ private fun isSingleDigit(): Boolean {
+ return id == R.id.HOUR_FIRST_DIGIT ||
+ id == R.id.HOUR_SECOND_DIGIT ||
+ id == R.id.MINUTE_FIRST_DIGIT ||
+ id == R.id.MINUTE_SECOND_DIGIT
+ }
+
+ private fun updateInterpolatedTextBounds(): Rect {
+ val interpolatedTextBounds = Rect()
+ if (textAnimator.animator.animatedFraction != 1.0f && textAnimator.animator.isRunning) {
+ interpolatedTextBounds.left =
+ MathUtils.lerp(
+ prevTextBounds.left,
+ targetTextBounds.left,
+ textAnimator.animator.animatedValue as Float,
+ )
+ .toInt()
+
+ interpolatedTextBounds.right =
+ MathUtils.lerp(
+ prevTextBounds.right,
+ targetTextBounds.right,
+ textAnimator.animator.animatedValue as Float,
+ )
+ .toInt()
+
+ interpolatedTextBounds.top =
+ MathUtils.lerp(
+ prevTextBounds.top,
+ targetTextBounds.top,
+ textAnimator.animator.animatedValue as Float,
+ )
+ .toInt()
+
+ interpolatedTextBounds.bottom =
+ MathUtils.lerp(
+ prevTextBounds.bottom,
+ targetTextBounds.bottom,
+ textAnimator.animator.animatedValue as Float,
+ )
+ .toInt()
+ } else {
+ interpolatedTextBounds.set(targetTextBounds)
+ }
+ return interpolatedTextBounds
+ }
+
+ private fun updateXtranslation(inPoint: Point, interpolatedTextBounds: Rect): Point {
+ val viewWidth = if (isVertical) measuredHeight else measuredWidth
+ when (horizontalAlignment) {
+ HorizontalAlignment.LEFT -> {
+ inPoint.x = lockScreenPaint.strokeWidth.toInt() - interpolatedTextBounds.left
+ }
+ HorizontalAlignment.RIGHT -> {
+ inPoint.x =
+ viewWidth - interpolatedTextBounds.right - lockScreenPaint.strokeWidth.toInt()
+ }
+ HorizontalAlignment.CENTER -> {
+ inPoint.x =
+ (viewWidth - interpolatedTextBounds.width()) / 2 - interpolatedTextBounds.left
+ }
+ }
+ return inPoint
+ }
+
+ // translation of reference point of text
+ // used for translation when calling textInterpolator
+ fun getLocalTranslation(): Point {
+ val viewHeight = if (isVertical) measuredWidth else measuredHeight
+ val interpolatedTextBounds = updateInterpolatedTextBounds()
+ val localTranslation = Point(0, 0)
+ val correctedBaseline = if (baseline != -1) baseline else baselineFromMeasure
+ // get the change from current baseline to expected baseline
+ when (verticalAlignment) {
+ VerticalAlignment.CENTER -> {
+ localTranslation.y =
+ ((viewHeight - interpolatedTextBounds.height()) / 2 -
+ interpolatedTextBounds.top -
+ correctedBaseline)
+ }
+ VerticalAlignment.TOP -> {
+ localTranslation.y =
+ (-interpolatedTextBounds.top + lockScreenPaint.strokeWidth - correctedBaseline)
+ .toInt()
+ }
+ VerticalAlignment.BOTTOM -> {
+ localTranslation.y =
+ viewHeight -
+ interpolatedTextBounds.bottom -
+ lockScreenPaint.strokeWidth.toInt() -
+ correctedBaseline
+ }
+ VerticalAlignment.BASELINE -> {
+ localTranslation.y = -lockScreenPaint.strokeWidth.toInt()
+ }
+ }
+
+ return updateXtranslation(localTranslation, interpolatedTextBounds)
+ }
+
+ override fun applyStyles(assets: AssetLoader, textStyle: TextStyle, aodStyle: TextStyle?) {
+ this.textStyle = textStyle as FontTextStyle
+ val typefaceName = "fonts/" + textStyle.fontFamily
+ setTypefaceCache(assets.typefaceCache.getVariantCache(typefaceName))
+ lockScreenPaint.strokeJoin = Paint.Join.ROUND
+ lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(textStyle.fontVariation)
+ textStyle.fontFeatureSettings?.let {
+ lockScreenPaint.fontFeatureSettings = it
+ fontFeatureSettings = it
+ }
+ typeface = lockScreenPaint.typeface
+ textStyle.lineHeight?.let { lineHeight = it.toInt() }
+ // borderWidth in textStyle and aodStyle is used to draw,
+ // strokeWidth in lockScreenPaint is used to measure and get enough space for the text
+ textStyle.borderWidth?.let { textBorderWidth = parser.convert(it) }
+
+ if (aodStyle != null && aodStyle is FontTextStyle) {
+ this.aodStyle = aodStyle
+ } else {
+ this.aodStyle = textStyle.copy()
+ }
+ this.aodStyle.transitionInterpolator?.let { aodDozingInterpolator = it.interpolator }
+ aodBorderWidth = parser.convert(this.aodStyle.borderWidth ?: DEFAULT_AOD_STROKE_WIDTH)
+ lockScreenPaint.strokeWidth = ceil(max(textBorderWidth, aodBorderWidth))
+ measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
+ setInterpolatorPaint()
+ recomputeMaxSingleDigitSizes()
+ invalidate()
+ }
+
+ // When constrainedByHeight is on, targetFontSizePx is the constrained height of textView
+ override fun applyTextSize(targetFontSizePx: Float?, constrainedByHeight: Boolean) {
+ val adjustedFontSizePx = adjustFontSize(targetFontSizePx, constrainedByHeight)
+ val fontSizePx = adjustedFontSizePx * (textStyle.fontSizeScale ?: 1f)
+ aodFontSizePx =
+ adjustedFontSizePx * (aodStyle.fontSizeScale ?: textStyle.fontSizeScale ?: 1f)
+ if (fontSizePx > 0) {
+ setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSizePx)
+ lockScreenPaint.textSize = textSize
+ lockScreenPaint.getTextBounds(text, 0, text.length, textBounds)
+ targetTextBounds.set(textBounds)
+ }
+ if (!constrainedByHeight) {
+ val lastUnconstrainedHeight = textBounds.height() + lockScreenPaint.strokeWidth * 2
+ fontSizeAdjustFactor = lastUnconstrainedHeight / lastUnconstrainedTextSize
+ }
+ textStyle.borderWidthScale?.let {
+ textBorderWidth = fontSizePx * it
+ if (dozeFraction < 1.0F) {
+ outlineAnimator?.setTextStyle(strokeWidth = textBorderWidth, animate = false)
+ }
+ }
+ aodStyle.borderWidthScale?.let {
+ aodBorderWidth = fontSizePx * it
+ if (dozeFraction > 0.0F) {
+ outlineAnimator?.setTextStyle(strokeWidth = aodBorderWidth, animate = false)
+ }
+ }
+
+ lockScreenPaint.strokeWidth = ceil(max(textBorderWidth, aodBorderWidth))
+ recomputeMaxSingleDigitSizes()
+
+ if (this::textAnimator.isInitialized) {
+ textAnimator.setTextStyle(textSize = lockScreenPaint.textSize, animate = false)
+ }
+ outlineAnimator?.setTextStyle(textSize = lockScreenPaint.textSize, animate = false)
+ innerAnimator?.setTextStyle(textSize = lockScreenPaint.textSize, animate = false)
+ }
+
+ private fun recomputeMaxSingleDigitSizes() {
+ val rectForCalculate = Rect()
+ maxSingleDigitHeight = 0
+ maxSingleDigitWidth = 0
+
+ for (i in 0..9) {
+ lockScreenPaint.getTextBounds(i.toString(), 0, 1, rectForCalculate)
+ maxSingleDigitHeight = max(maxSingleDigitHeight, rectForCalculate.height())
+ maxSingleDigitWidth = max(maxSingleDigitWidth, rectForCalculate.width())
+ }
+ maxSingleDigitWidth += 2 * lockScreenPaint.strokeWidth.toInt()
+ maxSingleDigitHeight += 2 * lockScreenPaint.strokeWidth.toInt()
+ }
+
+ // called without animation, can be used to set the initial state of animator
+ private fun setInterpolatorPaint() {
+ if (this::textAnimator.isInitialized) {
+ // set initial style
+ textAnimator.textInterpolator.targetPaint.set(lockScreenPaint)
+ textAnimator.textInterpolator.onTargetPaintModified()
+ textAnimator.setTextStyle(
+ fvar = textStyle.fontVariation,
+ textSize = lockScreenPaint.textSize,
+ color = textFillColor,
+ animate = false,
+ )
+ }
+
+ if (outlineAnimator != null) {
+ outlineAnimator!!
+ .textInterpolator
+ .targetPaint
+ .set(
+ TextPaint(lockScreenPaint).also {
+ it.style =
+ if (aodStyle.renderType == RenderType.HOLLOW_TEXT)
+ Paint.Style.FILL_AND_STROKE
+ else Paint.Style.STROKE
+ }
+ )
+ outlineAnimator!!.textInterpolator.onTargetPaintModified()
+ outlineAnimator!!.setTextStyle(
+ fvar = aodStyle.fontVariation,
+ textSize = lockScreenPaint.textSize,
+ color = Color.TRANSPARENT,
+ animate = false,
+ )
+ }
+
+ if (innerAnimator != null) {
+ innerAnimator!!
+ .textInterpolator
+ .targetPaint
+ .set(TextPaint(lockScreenPaint).also { it.style = Paint.Style.FILL })
+ innerAnimator!!.textInterpolator.onTargetPaintModified()
+ innerAnimator!!.setTextStyle(
+ fvar = aodStyle.fontVariation,
+ textSize = lockScreenPaint.textSize,
+ color = Color.WHITE,
+ animate = false,
+ )
+ }
+ }
+
+ /* Called after textAnimator.setTextStyle
+ * textAnimator.setTextStyle will update targetPaint,
+ * and rebase if previous animator is canceled
+ * so basePaint will store the state we transition from
+ * and targetPaint will store the state we transition to
+ */
+ private fun updateTextBoundsForTextAnimator() {
+ textAnimator.textInterpolator.basePaint.getTextBounds(text, 0, text.length, prevTextBounds)
+ textAnimator.textInterpolator.targetPaint.getTextBounds(
+ text,
+ 0,
+ text.length,
+ targetTextBounds,
+ )
+ }
+
+ /*
+ * Adjust text size to adapt to large display / font size
+ * where the text view will be constrained by height
+ */
+ private fun adjustFontSize(targetFontSizePx: Float?, constrainedByHeight: Boolean): Float {
+ return if (constrainedByHeight) {
+ min((targetFontSizePx ?: 0F) / fontSizeAdjustFactor, lastUnconstrainedTextSize)
+ } else {
+ lastUnconstrainedTextSize = targetFontSizePx ?: 1F
+ lastUnconstrainedTextSize
+ }
+ }
+
+ companion object {
+ val DEFAULT_AOD_STROKE_WIDTH = "2dp"
+ val TEXT_OUTLINE_DEFAULT_COLOR = Color.TRANSPARENT
+ val AOD_DEFAULT_COLOR = Color.TRANSPARENT
+ val AOD_OUTLINE_DEFAULT_COLOR = Color.WHITE
+ private val DEFAULT_LIGHT_COLOR = "@android:color/system_accent1_100+0"
+ private val DEFAULT_DARK_COLOR = "@android:color/system_accent2_600+0"
+
+ fun getDefaultColor(assets: AssetLoader, isRegionDark: Boolean) =
+ assets.readColor(if (isRegionDark) DEFAULT_LIGHT_COLOR else DEFAULT_DARK_COLOR)
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockView.kt
new file mode 100644
index 0000000..bbd2d3d
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockView.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.clocks.view
+
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.shared.clocks.AssetLoader
+import com.android.systemui.shared.clocks.TextStyle
+
+interface SimpleDigitalClockView {
+ var text: String
+ var verticalAlignment: VerticalAlignment
+ var horizontalAlignment: HorizontalAlignment
+ var dozeFraction: Float
+ val textStyle: TextStyle
+ @VisibleForTesting var isAnimationEnabled: Boolean
+
+ fun applyStyles(assets: AssetLoader, textStyle: TextStyle, aodStyle: TextStyle?)
+
+ fun applyTextSize(targetFontSizePx: Float?, constrainedByHeight: Boolean = false)
+
+ fun updateColors(assets: AssetLoader, isRegionDark: Boolean)
+
+ fun refreshTime()
+
+ fun animateCharge()
+
+ fun animateDoze(isDozing: Boolean, isAnimated: Boolean)
+}
+
+enum class VerticalAlignment {
+ TOP,
+ BOTTOM,
+ BASELINE, // default
+ CENTER,
+}
+
+enum class HorizontalAlignment {
+ LEFT,
+ RIGHT,
+ CENTER, // default
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSpecTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSpecTest.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSpecTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSpecTest.kt
index 791a26e..6f43c20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSpecTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSpecTest.kt
@@ -16,9 +16,9 @@
package com.android.systemui.accessibility
-import android.testing.AndroidTestingRunner
import android.util.Size
import androidx.test.filters.SmallTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize
import com.google.common.truth.Truth.assertThat
@@ -26,7 +26,7 @@
import org.junit.runner.RunWith
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class WindowMagnificationFrameSpecTest : SysuiTestCase() {
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 65825b2..2dcbdc8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+import static android.view.Display.INVALID_DISPLAY;
import static com.google.common.truth.Truth.assertThat;
@@ -68,10 +69,12 @@
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.os.Handler;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.UserManager;
import android.testing.TestableContext;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+import android.view.Display;
import android.view.DisplayInfo;
import android.view.Surface;
import android.view.WindowManager;
@@ -210,6 +213,7 @@
.thenReturn(true);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
.thenReturn(true);
+ when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(false);
when(mDialog1.getOpPackageName()).thenReturn("Dialog1");
when(mDialog2.getOpPackageName()).thenReturn("Dialog2");
@@ -462,7 +466,7 @@
@Test
public void testShowInvoked_whenSystemRequested() {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
- verify(mDialog1).show(any());
+ verify(mDialog1).show(mWindowManager);
}
@Test
@@ -679,7 +683,7 @@
// 2) Client cancels authentication
showDialog(new int[0] /* sensorIds */, true /* credentialAllowed */);
- verify(mDialog1).show(any());
+ verify(mDialog1).show(mWindowManager);
final byte[] credentialAttestation = generateRandomHAT();
@@ -695,7 +699,7 @@
@Test
public void testShowNewDialog_beforeOldDialogDismissed_SkipsAnimations() {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
- verify(mDialog1).show(any());
+ verify(mDialog1).show(mWindowManager);
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
@@ -703,7 +707,7 @@
verify(mDialog1).dismissWithoutCallback(eq(false) /* animate */);
// Second dialog should be shown without animation
- verify(mDialog2).show(any());
+ verify(mDialog2).show(mWindowManager);
}
@Test
@@ -990,13 +994,97 @@
verify(mDialog1, never()).show(any());
}
+ @Test
+ public void testShowDialog_visibleBackgroundUser() {
+ int backgroundUserId = 1001;
+ int backgroundDisplayId = 1001;
+ when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(true);
+ WindowManager wm = mockBackgroundUser(backgroundUserId, backgroundDisplayId,
+ true /* isVisible */, true /* hasUserManager */, true /* hasDisplay */);
+
+ showDialog(new int[]{1} /* sensorIds */, backgroundUserId /* userId */,
+ false /* credentialAllowed */);
+
+ verify(mDialog1).show(wm);
+ }
+
+ @Test
+ public void testShowDialog_invisibleBackgroundUser_defaultWM() {
+ int backgroundUserId = 1001;
+ when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(true);
+ mockBackgroundUser(backgroundUserId, INVALID_DISPLAY,
+ false /* isVisible */, true /* hasUserManager */, true /* hasDisplay */);
+
+ showDialog(new int[]{1} /* sensorIds */, backgroundUserId /* userId */,
+ false /* credentialAllowed */);
+
+ verify(mDialog1).show(mWindowManager);
+ }
+
+ @Test
+ public void testShowDialog_visibleBackgroundUser_noUserManager_dismissError()
+ throws RemoteException {
+ int backgroundUserId = 1001;
+ int backgroundDisplayId = 1001;
+ when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(true);
+ mockBackgroundUser(backgroundUserId, backgroundDisplayId,
+ true /* isVisible */, false /* hasUserManager */, true /* hasDisplay */);
+
+ showDialog(new int[]{1} /* sensorIds */, backgroundUserId /* userId */,
+ false /* credentialAllowed */);
+
+ verify(mDialog1, never()).show(any());
+ verify(mReceiver).onDialogDismissed(
+ eq(BiometricPrompt.DISMISSED_REASON_ERROR_NO_WM),
+ eq(null) /* credentialAttestation */);
+ }
+
+ @Test
+ public void testShowDialog_visibleBackgroundUser_invalidDisplayId_dismissError()
+ throws RemoteException {
+ int backgroundUserId = 1001;
+ when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(true);
+ mockBackgroundUser(backgroundUserId, INVALID_DISPLAY,
+ true /* isVisible */, true /* hasUserManager */, false /* hasDisplay */);
+
+ showDialog(new int[]{1} /* sensorIds */, backgroundUserId /* userId */,
+ false /* credentialAllowed */);
+
+ verify(mDialog1, never()).show(any());
+ verify(mReceiver).onDialogDismissed(
+ eq(BiometricPrompt.DISMISSED_REASON_ERROR_NO_WM),
+ eq(null) /* credentialAttestation */);
+ }
+
+ @Test
+ public void testShowDialog_visibleBackgroundUser_invalidDisplay_dismissError()
+ throws RemoteException {
+ int backgroundUserId = 1001;
+ int backgroundDisplayId = 1001;
+ when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(true);
+ mockBackgroundUser(backgroundUserId, backgroundDisplayId,
+ true /* isVisible */, true /* hasUserManager */, false /* hasDisplay */);
+
+ showDialog(new int[]{1} /* sensorIds */, backgroundUserId /* userId */,
+ false /* credentialAllowed */);
+
+ verify(mDialog1, never()).show(any());
+ verify(mReceiver).onDialogDismissed(
+ eq(BiometricPrompt.DISMISSED_REASON_ERROR_NO_WM),
+ eq(null) /* credentialAttestation */);
+ }
+
private void showDialog(int[] sensorIds, boolean credentialAllowed) {
+ showDialog(sensorIds, 0 /* userId */, credentialAllowed);
+ }
+
+ private void showDialog(int[] sensorIds, int userId, boolean credentialAllowed) {
mAuthController.showAuthenticationDialog(createTestPromptInfo(),
mReceiver /* receiver */,
sensorIds,
credentialAllowed,
true /* requireConfirmation */,
- 0 /* userId */,
+ userId /* userId */,
0 /* operationId */,
"testPackage",
REQUEST_ID);
@@ -1059,6 +1147,40 @@
assertTrue(mAuthController.isFaceAuthEnrolled(userId));
}
+ /**
+ * Create mocks related to visible background users.
+ *
+ * @param userId the user id of the background user to mock
+ * @param displayId display id of the background user
+ * @param isVisible whether the background user is a visible background user or not
+ * @param hasUserManager simulate whether the background user's context will return a mock
+ * UserManager instance or null
+ * @param hasDisplay simulate whether the background user's context will return a mock Display
+ * instance or null
+ * @return mock WindowManager instance associated with the background user's display context
+ */
+ private WindowManager mockBackgroundUser(int userId, int displayId, boolean isVisible,
+ boolean hasUserManager, boolean hasDisplay) {
+ Context mockUserContext = mock(Context.class);
+ Context mockDisplayContext = mock(Context.class);
+ UserManager mockUserManager = mock(UserManager.class);
+ Display mockDisplay = mock(Display.class);
+ WindowManager mockDisplayWM = mock(WindowManager.class);
+ doReturn(mockUserContext).when(mContextSpy).createContextAsUser(eq(UserHandle.of(userId)),
+ anyInt());
+ if (hasUserManager) {
+ when(mockUserContext.getSystemService(UserManager.class)).thenReturn(mockUserManager);
+ }
+ when(mockUserManager.isUserVisible()).thenReturn(isVisible);
+ when(mockUserManager.getMainDisplayIdAssignedToUser()).thenReturn(displayId);
+ if (hasDisplay) {
+ when(mDisplayManager.getDisplay(displayId)).thenReturn(mockDisplay);
+ }
+ doReturn(mockDisplayContext).when(mContextSpy).createDisplayContext(mockDisplay);
+ when(mockDisplayContext.getSystemService(WindowManager.class)).thenReturn(mockDisplayWM);
+ return mockDisplayWM;
+ }
+
private final class TestableAuthController extends AuthController {
private int mBuildCount = 0;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable
index 0d369a3..97f2e56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable
@@ -67,7 +67,6 @@
import com.android.systemui.scene.shared.model.sceneDataSourceDelegator
import com.android.systemui.scene.ui.composable.Scene
import com.android.systemui.scene.ui.composable.SceneContainer
-import com.android.systemui.settings.displayTracker
import com.android.systemui.testKosmos
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.awaitCancellation
@@ -127,7 +126,7 @@
private val sceneContainerViewModel by lazy {
kosmos.sceneContainerViewModelFactory
- .create(view, kosmos.displayTracker.defaultDisplayId, {})
+ .create(view) {}
.apply { setTransitionState(transitionState) }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/ZigZagClassifierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/ZigZagClassifierTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/ZigZagClassifierTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/ZigZagClassifierTest.java
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
index d4d966a..2312bbd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
@@ -22,6 +22,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.nano.CommunalHubState
+import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.lifecycle.InstantTaskExecutorRule
import com.google.common.truth.Truth.assertThat
@@ -102,7 +103,7 @@
widgetId = widgetId,
provider = provider,
rank = rank,
- userSerialNumber = userSerialNumber
+ userSerialNumber = userSerialNumber,
)
}
assertThat(widgets())
@@ -110,7 +111,7 @@
communalItemRankEntry1,
communalWidgetItemEntry1,
communalItemRankEntry2,
- communalWidgetItemEntry2
+ communalWidgetItemEntry2,
)
}
@@ -129,7 +130,7 @@
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
- userSerialNumber = userSerialNumber
+ userSerialNumber = userSerialNumber,
)
}
@@ -165,7 +166,7 @@
communalItemRankEntry1,
communalWidgetItemEntry1,
communalItemRankEntry2,
- communalWidgetItemEntry2
+ communalWidgetItemEntry2,
)
communalWidgetDao.deleteWidgetById(communalWidgetItemEntry1.widgetId)
@@ -251,6 +252,7 @@
componentName = "pk_name/cls_name_4",
itemId = 4L,
userSerialNumber = 0,
+ spanY = 3,
)
assertThat(widgets())
.containsExactly(
@@ -267,6 +269,68 @@
}
@Test
+ fun addWidget_withDifferentSpanY_readsCorrectValuesInDb() =
+ testScope.runTest {
+ val widgets = collectLastValue(communalWidgetDao.getWidgets())
+
+ // Add widgets with different spanY values
+ communalWidgetDao.addWidget(
+ widgetId = 1,
+ provider = ComponentName("pkg_name", "cls_name_1"),
+ rank = 0,
+ userSerialNumber = 0,
+ spanY = CommunalContentSize.FULL.span,
+ )
+ communalWidgetDao.addWidget(
+ widgetId = 2,
+ provider = ComponentName("pkg_name", "cls_name_2"),
+ rank = 1,
+ userSerialNumber = 0,
+ spanY = CommunalContentSize.HALF.span,
+ )
+ communalWidgetDao.addWidget(
+ widgetId = 3,
+ provider = ComponentName("pkg_name", "cls_name_3"),
+ rank = 2,
+ userSerialNumber = 0,
+ spanY = CommunalContentSize.THIRD.span,
+ )
+
+ // Verify that the widgets have the correct spanY values
+ assertThat(widgets())
+ .containsExactly(
+ CommunalItemRank(uid = 1L, rank = 0),
+ CommunalWidgetItem(
+ uid = 1L,
+ widgetId = 1,
+ componentName = "pkg_name/cls_name_1",
+ itemId = 1L,
+ userSerialNumber = 0,
+ spanY = CommunalContentSize.FULL.span,
+ ),
+ CommunalItemRank(uid = 2L, rank = 1),
+ CommunalWidgetItem(
+ uid = 2L,
+ widgetId = 2,
+ componentName = "pkg_name/cls_name_2",
+ itemId = 2L,
+ userSerialNumber = 0,
+ spanY = CommunalContentSize.HALF.span,
+ ),
+ CommunalItemRank(uid = 3L, rank = 2),
+ CommunalWidgetItem(
+ uid = 3L,
+ widgetId = 3,
+ componentName = "pkg_name/cls_name_3",
+ itemId = 3L,
+ userSerialNumber = 0,
+ spanY = CommunalContentSize.THIRD.span,
+ ),
+ )
+ .inOrder()
+ }
+
+ @Test
fun restoreCommunalHubState() =
testScope.runTest {
// Set up db
@@ -288,6 +352,7 @@
componentName = fakeWidget.componentName,
itemId = rank.uid,
userSerialNumber = fakeWidget.userSerialNumber,
+ spanY = 3,
)
expected[rank] = widget
}
@@ -343,6 +408,7 @@
componentName = widgetInfo1.provider.flattenToString(),
itemId = communalItemRankEntry1.uid,
userSerialNumber = widgetInfo1.userSerialNumber,
+ spanY = 3,
)
val communalWidgetItemEntry2 =
CommunalWidgetItem(
@@ -351,6 +417,7 @@
componentName = widgetInfo2.provider.flattenToString(),
itemId = communalItemRankEntry2.uid,
userSerialNumber = widgetInfo2.userSerialNumber,
+ spanY = 3,
)
val communalWidgetItemEntry3 =
CommunalWidgetItem(
@@ -359,6 +426,7 @@
componentName = widgetInfo3.provider.flattenToString(),
itemId = communalItemRankEntry3.uid,
userSerialNumber = widgetInfo3.userSerialNumber,
+ spanY = 3,
)
val fakeState =
CommunalHubState().apply {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
index eba395b..596db07 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
@@ -117,6 +117,7 @@
componentName = defaultWidgets[0],
rank = 0,
userSerialNumber = 0,
+ spanY = 3,
)
verify(communalWidgetDao)
.addWidget(
@@ -124,6 +125,7 @@
componentName = defaultWidgets[1],
rank = 1,
userSerialNumber = 0,
+ spanY = 3,
)
verify(communalWidgetDao)
.addWidget(
@@ -131,6 +133,7 @@
componentName = defaultWidgets[2],
rank = 2,
userSerialNumber = 0,
+ spanY = 3,
)
}
@@ -152,6 +155,7 @@
componentName = any(),
rank = anyInt(),
userSerialNumber = anyInt(),
+ spanY = anyInt(),
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 980a5ec..3d30ecc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -143,7 +143,8 @@
fun communalWidgets_queryWidgetsFromDb() =
testScope.runTest {
val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1)
- val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L, 0)
+ val communalWidgetItemEntry =
+ CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L, 0, 3)
fakeWidgets.value = mapOf(communalItemRankEntry to communalWidgetItemEntry)
fakeProviders.value = mapOf(1 to providerInfoA)
@@ -169,19 +170,15 @@
fakeWidgets.value =
mapOf(
CommunalItemRank(uid = 1L, rank = 1) to
- CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0),
+ CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0, 3),
CommunalItemRank(uid = 2L, rank = 2) to
- CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0),
+ CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0, 3),
CommunalItemRank(uid = 3L, rank = 3) to
- CommunalWidgetItem(uid = 3L, 3, "pk_3/cls_3", 3L, 0),
+ CommunalWidgetItem(uid = 3L, 3, "pk_3/cls_3", 3L, 0, 3),
CommunalItemRank(uid = 4L, rank = 4) to
- CommunalWidgetItem(uid = 4L, 4, "pk_4/cls_4", 4L, 0),
+ CommunalWidgetItem(uid = 4L, 4, "pk_4/cls_4", 4L, 0, 3),
)
- fakeProviders.value =
- mapOf(
- 1 to providerInfoA,
- 2 to providerInfoB,
- )
+ fakeProviders.value = mapOf(1 to providerInfoA, 2 to providerInfoB)
// Expect to see only widget 1 and 2
val communalWidgets by collectLastValue(underTest.communalWidgets)
@@ -207,15 +204,11 @@
fakeWidgets.value =
mapOf(
CommunalItemRank(uid = 1L, rank = 1) to
- CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0),
+ CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0, 3),
CommunalItemRank(uid = 2L, rank = 2) to
- CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0),
+ CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0, 3),
)
- fakeProviders.value =
- mapOf(
- 1 to providerInfoA,
- 2 to providerInfoB,
- )
+ fakeProviders.value = mapOf(1 to providerInfoA, 2 to providerInfoB)
// Expect two widgets
val communalWidgets by collectLastValue(underTest.communalWidgets)
@@ -235,11 +228,7 @@
)
// Provider info updated for widget 1
- fakeProviders.value =
- mapOf(
- 1 to providerInfoC,
- 2 to providerInfoB,
- )
+ fakeProviders.value = mapOf(1 to providerInfoC, 2 to providerInfoB)
runCurrent()
assertThat(communalWidgets)
@@ -269,7 +258,7 @@
whenever(
communalWidgetHost.allocateIdAndBindWidget(
any<ComponentName>(),
- any<UserHandle>()
+ any<UserHandle>(),
)
)
.thenReturn(id)
@@ -294,7 +283,7 @@
whenever(
communalWidgetHost.allocateIdAndBindWidget(
any<ComponentName>(),
- any<UserHandle>()
+ any<UserHandle>(),
)
)
.thenReturn(id)
@@ -303,7 +292,7 @@
verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
verify(communalWidgetDao, never())
- .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt())
+ .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt(), anyInt())
verify(appWidgetHost).deleteAppWidgetId(id)
// Verify backup not requested
@@ -321,7 +310,7 @@
whenever(
communalWidgetHost.allocateIdAndBindWidget(
any<ComponentName>(),
- any<UserHandle>()
+ any<UserHandle>(),
)
)
.thenReturn(id)
@@ -332,7 +321,7 @@
verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
verify(communalWidgetDao, never())
- .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt())
+ .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt(), anyInt())
verify(appWidgetHost).deleteAppWidgetId(id)
// Verify backup not requested
@@ -350,7 +339,7 @@
whenever(
communalWidgetHost.allocateIdAndBindWidget(
any<ComponentName>(),
- any<UserHandle>()
+ any<UserHandle>(),
)
)
.thenReturn(id)
@@ -650,8 +639,10 @@
eq(newWidgetId),
componentNameCaptor.capture(),
eq(2),
- eq(testUserSerialNumber(workProfile))
+ eq(testUserSerialNumber(workProfile)),
+ anyInt(),
)
+
assertThat(componentNameCaptor.firstValue)
.isEqualTo(ComponentName("pk_name", "fake_widget_2"))
}
@@ -662,9 +653,9 @@
fakeWidgets.value =
mapOf(
CommunalItemRank(uid = 1L, rank = 1) to
- CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0),
+ CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0, 3),
CommunalItemRank(uid = 2L, rank = 2) to
- CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0),
+ CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0, 3),
)
// Widget 1 is installed
@@ -707,7 +698,7 @@
fakeWidgets.value =
mapOf(
CommunalItemRank(uid = 1L, rank = 1) to
- CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0),
+ CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0, 3)
)
// Widget 1 is pending install
@@ -732,7 +723,7 @@
componentName = ComponentName("pk_1", "cls_1"),
icon = fakeIcon,
user = mainUser,
- ),
+ )
)
// Package for widget 1 finished installing
@@ -749,10 +740,23 @@
appWidgetId = 1,
providerInfo = providerInfoA,
rank = 1,
- ),
+ )
)
}
+ @Test
+ fun updateWidgetSpanY_updatesWidgetInDaoAndRequestsBackup() =
+ testScope.runTest {
+ val widgetId = 1
+ val newSpanY = 6
+
+ underTest.updateWidgetSpanY(widgetId, newSpanY)
+ runCurrent()
+
+ verify(communalWidgetDao).updateWidgetSpanY(widgetId, newSpanY)
+ verify(backupManager).dataChanged()
+ }
+
private fun setAppWidgetIds(ids: List<Int>) {
whenever(appWidgetHost.appWidgetIds).thenReturn(ids.toIntArray())
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
index 58b59ff..755c4eb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
@@ -85,7 +85,8 @@
assertThat(actions).isNotEmpty()
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
- assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))
+ assertThat(actions?.get(Swipe.Down))
+ .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
setUpState(
isShadeTouchable = false,
@@ -102,7 +103,8 @@
assertThat(actions).isNotEmpty()
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
- assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))
+ assertThat(actions?.get(Swipe.Down))
+ .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
}
@Test
@@ -120,7 +122,7 @@
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
+ .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
setUpState(
isShadeTouchable = false,
@@ -138,7 +140,7 @@
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
+ .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
}
@Test
@@ -156,7 +158,9 @@
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Overlays.NotificationsShade))
+ .isEqualTo(
+ UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
+ )
setUpState(
isShadeTouchable = false,
@@ -170,7 +174,9 @@
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Overlays.NotificationsShade))
+ .isEqualTo(
+ UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
+ )
}
private fun TestScope.setUpState(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch
diff --git a/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
index 8f9e238..8b13411 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
@@ -145,7 +145,7 @@
fakeInputManager.addPhysicalKeyboard(
PHYSICAL_NOT_FULL_KEYBOARD_ID,
- isFullKeyboard = false
+ isFullKeyboard = false,
)
assertThat(isKeyboardConnected).isFalse()
@@ -223,7 +223,7 @@
backlightListenerCaptor.value.onBacklightChanged(
current = 1,
max = 5,
- triggeredByKeyPress = false
+ triggeredByKeyPress = false,
)
assertThat(backlight).isNull()
}
@@ -239,7 +239,7 @@
backlightListenerCaptor.value.onBacklightChanged(
current = 1,
max = 5,
- triggeredByKeyPress = true
+ triggeredByKeyPress = true,
)
assertThat(backlight).isNotNull()
}
@@ -318,15 +318,75 @@
}
}
+ @Test
+ fun connectedKeyboards_emitsAllKeyboards() {
+ testScope.runTest {
+ val firstKeyboard = Keyboard(vendorId = 1, productId = 1)
+ val secondKeyboard = Keyboard(vendorId = 2, productId = 2)
+ captureDeviceListener()
+ val keyboards by collectLastValueImmediately(underTest.connectedKeyboards)
+
+ fakeInputManager.addPhysicalKeyboard(
+ PHYSICAL_FULL_KEYBOARD_ID,
+ vendorId = firstKeyboard.vendorId,
+ productId = firstKeyboard.productId,
+ )
+ assertThat(keyboards)
+ .containsExactly(Keyboard(firstKeyboard.vendorId, firstKeyboard.productId))
+
+ fakeInputManager.addPhysicalKeyboard(
+ ANOTHER_PHYSICAL_FULL_KEYBOARD_ID,
+ vendorId = secondKeyboard.vendorId,
+ productId = secondKeyboard.productId,
+ )
+ assertThat(keyboards)
+ .containsExactly(
+ Keyboard(firstKeyboard.vendorId, firstKeyboard.productId),
+ Keyboard(secondKeyboard.vendorId, secondKeyboard.productId),
+ )
+ }
+ }
+
+ @Test
+ fun connectedKeyboards_emitsOnlyFullPhysicalKeyboards() {
+ testScope.runTest {
+ captureDeviceListener()
+ val keyboards by collectLastValueImmediately(underTest.connectedKeyboards)
+
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addDevice(VIRTUAL_FULL_KEYBOARD_ID, SOURCE_KEYBOARD)
+ fakeInputManager.addPhysicalKeyboard(
+ PHYSICAL_NOT_FULL_KEYBOARD_ID,
+ isFullKeyboard = false,
+ )
+
+ assertThat(keyboards).hasSize(1)
+ }
+ }
+
+ @Test
+ fun connectedKeyboards_emitsOnlyConnectedKeyboards() {
+ testScope.runTest {
+ captureDeviceListener()
+ val keyboards by collectLastValueImmediately(underTest.connectedKeyboards)
+
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.removeDevice(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
+
+ assertThat(keyboards).hasSize(1)
+ }
+ }
+
private fun KeyboardBacklightListener.onBacklightChanged(
current: Int,
max: Int,
- triggeredByKeyPress: Boolean = true
+ triggeredByKeyPress: Boolean = true,
) {
onKeyboardBacklightChanged(
/* deviceId= */ 0,
TestBacklightState(current, max),
- triggeredByKeyPress
+ triggeredByKeyPress,
)
}
@@ -343,7 +403,7 @@
private class TestBacklightState(
private val brightnessLevel: Int,
- private val maxBrightnessLevel: Int
+ private val maxBrightnessLevel: Int,
) : KeyboardBacklightState() {
override fun getBrightnessLevel() = brightnessLevel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
index 073ed61..b6ec6a6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -21,16 +21,23 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.fakeLightRevealScrimRepository
+import com.android.systemui.keyguard.data.repository.DEFAULT_REVEAL_DURATION
import com.android.systemui.keyguard.data.repository.DEFAULT_REVEAL_EFFECT
import com.android.systemui.keyguard.data.repository.FakeLightRevealScrimRepository
+import com.android.systemui.keyguard.data.repository.FakeLightRevealScrimRepository.RevealAnimatorRequest
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -52,6 +59,8 @@
private val fakeKeyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val fakePowerRepository = kosmos.fakePowerRepository
+
private val underTest = kosmos.lightRevealScrimInteractor
private val reveal1 =
@@ -107,4 +116,50 @@
runCurrent()
assertEquals(listOf(DEFAULT_REVEAL_EFFECT, reveal1, reveal2), values)
}
+
+ @Test
+ fun transitionToAod_folding_doesNotAnimateTheScrim() =
+ kosmos.testScope.runTest {
+ updateWakefulness(goToSleepReason = WakeSleepReason.FOLD)
+ runCurrent()
+
+ // Transition to AOD
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(to = KeyguardState.AOD, transitionState = TransitionState.STARTED)
+ )
+ runCurrent()
+
+ assertThat(fakeLightRevealScrimRepository.revealAnimatorRequests.last())
+ .isEqualTo(RevealAnimatorRequest(reveal = false, duration = 0))
+ }
+
+ @Test
+ fun transitionToAod_powerButton_animatesTheScrim() =
+ kosmos.testScope.runTest {
+ updateWakefulness(goToSleepReason = WakeSleepReason.POWER_BUTTON)
+ runCurrent()
+
+ // Transition to AOD
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(to = KeyguardState.AOD, transitionState = TransitionState.STARTED)
+ )
+ runCurrent()
+
+ assertThat(fakeLightRevealScrimRepository.revealAnimatorRequests.last())
+ .isEqualTo(
+ RevealAnimatorRequest(
+ reveal = false,
+ duration = DEFAULT_REVEAL_DURATION
+ )
+ )
+ }
+
+ private fun updateWakefulness(goToSleepReason: WakeSleepReason) {
+ fakePowerRepository.updateWakefulness(
+ rawState = WakefulnessState.STARTING_TO_SLEEP,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = goToSleepReason,
+ powerButtonLaunchGestureTriggered = false
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
index fb1bf28..6397979 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
@@ -303,7 +303,8 @@
// Top edge is not applicable in dual shade, as well as two-finger swipe.
assertThat(downDestination).isNull()
} else {
- assertThat(downDestination).isEqualTo(ShowOverlay(Overlays.NotificationsShade))
+ assertThat(downDestination)
+ .isEqualTo(ShowOverlay(Overlays.NotificationsShade, isIrreversible = true))
assertThat(downDestination?.transitionKey).isNull()
}
@@ -320,7 +321,7 @@
downWithTwoPointers -> assertThat(downFromTopRightDestination).isNull()
else -> {
assertThat(downFromTopRightDestination)
- .isEqualTo(ShowOverlay(Overlays.QuickSettingsShade))
+ .isEqualTo(ShowOverlay(Overlays.QuickSettingsShade, isIrreversible = true))
assertThat(downFromTopRightDestination?.transitionKey).isNull()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/log/table/TableLogBufferTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/log/table/TableLogBufferTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt
index 84ec1a5..77be8c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.mediarouter.data.repository
import androidx.test.filters.SmallTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
@@ -28,9 +29,11 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
class MediaRouterRepositoryTest : SysuiTestCase() {
val kosmos = Kosmos()
val testScope = kosmos.testScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelForceQSTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelForceQSTest.kt
index acd69af..da16640 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelForceQSTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelForceQSTest.kt
@@ -26,10 +26,11 @@
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@RunWithLooper
class QSFragmentComposeViewModelForceQSTest(private val testData: TestData) :
AbstractQSFragmentComposeViewModelTest() {
@@ -75,7 +76,7 @@
companion object {
private const val EXPANSION = 0.3f
- @Parameterized.Parameters(name = "{0}")
+ @Parameters(name = "{0}")
@JvmStatic
fun createTestData(): List<TestData> {
return statusBarStates.flatMap { statusBarState ->
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
index 5c47f55..47fae9f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
@@ -106,7 +106,7 @@
runCurrent()
assertThat(userActions?.get(swipeDownFromTopWithTwoFingers()))
- .isEqualTo(UserActionResult(Scenes.QuickSettings))
+ .isEqualTo(UserActionResult(Scenes.QuickSettings, isIrreversible = true))
}
@Test
@@ -118,7 +118,7 @@
runCurrent()
assertThat(userActions?.get(swipeDownFromTopWithTwoFingers()))
- .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
+ .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilterTest.kt
deleted file mode 100644
index efde1ec..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilterTest.kt
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
-package com.android.systemui.scene.ui.viewmodel
-
-import android.graphics.Region
-import android.view.setSystemGestureExclusionRegion
-import androidx.compose.ui.geometry.Offset
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.lifecycle.activateIn
-import com.android.systemui.scene.sceneContainerGestureFilterFactory
-import com.android.systemui.settings.displayTracker
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class SceneContainerGestureFilterTest : SysuiTestCase() {
-
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
- private val displayId = kosmos.displayTracker.defaultDisplayId
-
- private val underTest = kosmos.sceneContainerGestureFilterFactory.create(displayId)
- private val activationJob = Job()
-
- @Test
- fun shouldFilterGesture_whenNoRegion_returnsFalse() =
- testScope.runTest {
- activate()
- setSystemGestureExclusionRegion(displayId, null)
- runCurrent()
-
- assertThat(underTest.shouldFilterGesture(Offset(100f, 100f))).isFalse()
- }
-
- @Test
- fun shouldFilterGesture_whenOutsideRegion_returnsFalse() =
- testScope.runTest {
- activate()
- setSystemGestureExclusionRegion(displayId, Region(0, 0, 200, 200))
- runCurrent()
-
- assertThat(underTest.shouldFilterGesture(Offset(300f, 100f))).isFalse()
- }
-
- @Test
- fun shouldFilterGesture_whenInsideRegion_returnsTrue() =
- testScope.runTest {
- activate()
- setSystemGestureExclusionRegion(displayId, Region(0, 0, 200, 200))
- runCurrent()
-
- assertThat(underTest.shouldFilterGesture(Offset(100f, 100f))).isTrue()
- }
-
- @Test(expected = IllegalStateException::class)
- fun shouldFilterGesture_beforeActivation_throws() =
- testScope.runTest {
- setSystemGestureExclusionRegion(displayId, Region(0, 0, 200, 200))
- runCurrent()
-
- underTest.shouldFilterGesture(Offset(100f, 100f))
- }
-
- @Test(expected = IllegalStateException::class)
- fun shouldFilterGesture_afterCancellation_throws() =
- testScope.runTest {
- activate()
- setSystemGestureExclusionRegion(displayId, Region(0, 0, 200, 200))
- runCurrent()
-
- cancel()
-
- underTest.shouldFilterGesture(Offset(100f, 100f))
- }
-
- private fun TestScope.activate() {
- underTest.activateIn(testScope, activationJob)
- runCurrent()
- }
-
- private fun TestScope.cancel() {
- activationJob.cancel()
- runCurrent()
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerHapticsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerHapticsViewModelTest.kt
index 664315d..ca9500b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerHapticsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerHapticsViewModelTest.kt
@@ -51,7 +51,7 @@
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -134,7 +134,7 @@
runCurrent()
// THEN the view does not play a haptic feedback constant
- verifyZeroInteractions(view)
+ verifyNoMoreInteractions(view)
}
@EnableFlags(Flags.FLAG_MSDL_FEEDBACK, Flags.FLAG_DUAL_SHADE)
@@ -202,7 +202,7 @@
runCurrent()
// THEN the view does not play a haptic feedback constant
- verifyZeroInteractions(view)
+ verifyNoMoreInteractions(view)
}
private fun createTransitionState(from: SceneKey, to: ContentKey) =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index a37f511..4ec0802 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -39,7 +39,6 @@
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
-import com.android.systemui.settings.displayTracker
import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shared.flag.DualShade
@@ -82,7 +81,6 @@
underTest =
kosmos.sceneContainerViewModelFactory.create(
view,
- kosmos.displayTracker.defaultDisplayId,
{ motionEventHandler ->
this@SceneContainerViewModelTest.motionEventHandler = motionEventHandler
},
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt
index 15d6881..fcb366b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.EnableSceneContainer
@@ -50,6 +51,7 @@
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
@@ -248,6 +250,27 @@
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Communal))
}
+ @Test
+ fun upTransitionSceneKey_neverGoesBackToShadeScene() =
+ testScope.runTest {
+ val actions by collectValues(underTest.actions)
+ val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ kosmos.sceneInteractor.changeScene(Scenes.Shade, "")
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+
+ kosmos.sceneInteractor.changeScene(Scenes.QuickSettings, "")
+ assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+
+ actions.forEachIndexed { index, map ->
+ assertWithMessage(
+ "Actions on index $index is incorrectly mapping back to the Shade scene!"
+ )
+ .that((map[Swipe.Up] as? UserActionResult.ChangeScene)?.toScene)
+ .isNotEqualTo(Scenes.Shade)
+ }
+ }
+
private fun TestScope.setDeviceEntered(isEntered: Boolean) {
if (isEntered) {
// Unlock the device marking the device has entered.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 523a89a..5b0b59d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -71,7 +71,11 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor;
+import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.DisableSceneContainer;
+import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.flags.FakeFeatureFlagsClassic;
import com.android.systemui.log.LogWtfHandlerRule;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -90,6 +94,10 @@
import com.google.android.collect.Lists;
+import dagger.Lazy;
+
+import kotlinx.coroutines.flow.StateFlow;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -152,6 +160,12 @@
private BroadcastDispatcher mBroadcastDispatcher;
@Mock
private KeyguardStateController mKeyguardStateController;
+ @Mock
+ private Lazy<DeviceUnlockedInteractor> mDeviceUnlockedInteractorLazy;
+ @Mock
+ private DeviceUnlockedInteractor mDeviceUnlockedInteractor;
+ @Mock
+ private StateFlow<DeviceUnlockStatus> mDeviceUnlockStatusStateFlow;
private UserInfo mCurrentUser;
private UserInfo mSecondaryUser;
@@ -238,6 +252,9 @@
mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
mLockscreenUserManager.setUpWithPresenter(mPresenter);
+ when(mDeviceUnlockedInteractor.getDeviceUnlockStatus())
+ .thenReturn(mDeviceUnlockStatusStateFlow);
+
mBackgroundExecutor.runAllReady();
}
@@ -493,7 +510,8 @@
}
@Test
- public void testUpdateIsPublicMode() {
+ @DisableSceneContainer
+ public void testUpdateIsPublicMode_sceneContainerDisabled() {
when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
when(mKeyguardStateController.isShowing()).thenReturn(false);
@@ -527,6 +545,57 @@
mBackgroundExecutor.runAllReady();
assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
verify(listener, never()).onNotificationStateChanged();
+
+ verify(mDeviceUnlockedInteractorLazy, never()).get();
+ }
+
+ @Test
+ @EnableSceneContainer
+ public void testUpdateIsPublicMode_sceneContainerEnabled() {
+ when(mDeviceUnlockedInteractorLazy.get()).thenReturn(mDeviceUnlockedInteractor);
+
+ // device is unlocked
+ when(mDeviceUnlockStatusStateFlow.getValue()).thenReturn(new DeviceUnlockStatus(
+ /* isUnlocked = */ true,
+ /* deviceUnlockSource = */ null
+ ));
+
+ NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class);
+ mLockscreenUserManager.addNotificationStateChangedListener(listener);
+ mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+
+ // first call explicitly sets user 0 to not public; notifies
+ mLockscreenUserManager.updatePublicMode();
+ mBackgroundExecutor.runAllReady();
+ assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener).onNotificationStateChanged();
+ clearInvocations(listener);
+
+ // calling again has no changes; does not notify
+ mLockscreenUserManager.updatePublicMode();
+ mBackgroundExecutor.runAllReady();
+ assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener, never()).onNotificationStateChanged();
+
+ // device is not unlocked
+ when(mDeviceUnlockStatusStateFlow.getValue()).thenReturn(new DeviceUnlockStatus(
+ /* isUnlocked = */ false,
+ /* deviceUnlockSource = */ null
+ ));
+
+ // Calling again with device now not unlocked makes user 0 public; notifies
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ mLockscreenUserManager.updatePublicMode();
+ mBackgroundExecutor.runAllReady();
+ assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener).onNotificationStateChanged();
+ clearInvocations(listener);
+
+ // calling again has no changes; does not notify
+ mLockscreenUserManager.updatePublicMode();
+ mBackgroundExecutor.runAllReady();
+ assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener, never()).onNotificationStateChanged();
}
@Test
@@ -972,7 +1041,9 @@
mSettings,
mock(DumpManager.class),
mock(LockPatternUtils.class),
- mFakeFeatureFlags);
+ mFakeFeatureFlags,
+ mDeviceUnlockedInteractorLazy
+ );
}
public BroadcastReceiver getBaseBroadcastReceiverForTest() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
index 28857a0..34f4608 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
@@ -208,8 +208,8 @@
assertThat(footerVisible).isTrue()
}
- @EnableFlags(ModesEmptyShadeFix.FLAG_NAME)
@Test
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
fun onClick_whenHistoryDisabled_leadsToSettingsPage() =
testScope.runTest {
val onClick by collectLastValue(underTest.onClick)
@@ -222,8 +222,8 @@
assertThat(onClick?.backStack).isEmpty()
}
- @EnableFlags(ModesEmptyShadeFix.FLAG_NAME)
@Test
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
fun onClick_whenHistoryEnabled_leadsToHistoryPage() =
testScope.runTest {
val onClick by collectLastValue(underTest.onClick)
@@ -237,8 +237,8 @@
.containsExactly(Settings.ACTION_NOTIFICATION_SETTINGS)
}
- @EnableFlags(ModesEmptyShadeFix.FLAG_NAME)
@Test
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
fun onClick_whenOneModeHidingNotifications_leadsToModeSettings() =
testScope.runTest {
val onClick by collectLastValue(underTest.onClick)
@@ -263,8 +263,8 @@
.containsExactly(Settings.ACTION_ZEN_MODE_SETTINGS)
}
- @EnableFlags(ModesEmptyShadeFix.FLAG_NAME)
@Test
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
fun onClick_whenMultipleModesHidingNotifications_leadsToGeneralModesSettings() =
testScope.runTest {
val onClick by collectLastValue(underTest.onClick)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
index a0f6431..9d93a9c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
@@ -61,7 +61,7 @@
interactor,
kosmos.testDispatcher,
mockDialogDelegate,
- mockDialogEventLogger
+ mockDialogEventLogger,
)
@Test
@@ -97,7 +97,7 @@
assertThat(tiles?.size).isEqualTo(3)
with(tiles?.elementAt(0)!!) {
assertThat(this.text).isEqualTo("Disabled by other")
- assertThat(this.subtext).isEqualTo("Set up")
+ assertThat(this.subtext).isEqualTo("Not set")
assertThat(this.enabled).isEqualTo(false)
}
with(tiles?.elementAt(1)!!) {
@@ -323,10 +323,10 @@
assertThat(tiles!!).hasSize(6)
assertThat(tiles!![0].subtext).isEqualTo("When the going gets tough")
assertThat(tiles!![1].subtext).isEqualTo("On • When in Rome")
- assertThat(tiles!![2].subtext).isEqualTo("Set up")
+ assertThat(tiles!![2].subtext).isEqualTo("Not set")
assertThat(tiles!![3].subtext).isEqualTo("Off")
assertThat(tiles!![4].subtext).isEqualTo("On")
- assertThat(tiles!![5].subtext).isEqualTo("Set up")
+ assertThat(tiles!![5].subtext).isEqualTo("Not set")
}
@Test
@@ -387,7 +387,7 @@
}
with(tiles?.elementAt(2)!!) {
assertThat(this.stateDescription).isEqualTo("Off")
- assertThat(this.subtextDescription).isEqualTo("Set up")
+ assertThat(this.subtextDescription).isEqualTo("Not set")
}
with(tiles?.elementAt(3)!!) {
assertThat(this.stateDescription).isEqualTo("Off")
@@ -399,7 +399,7 @@
}
with(tiles?.elementAt(5)!!) {
assertThat(this.stateDescription).isEqualTo("Off")
- assertThat(this.subtextDescription).isEqualTo("Set up")
+ assertThat(this.subtextDescription).isEqualTo("Not set")
}
// All tiles have the same long click info
@@ -451,7 +451,7 @@
.setName("Active without manual")
.setActive(true)
.setManualInvocationAllowed(false)
- .build(),
+ .build()
)
)
runCurrent()
@@ -492,7 +492,7 @@
.setId("ID")
.setName("Disabled by other")
.setEnabled(false, /* byUser= */ false)
- .build(),
+ .build()
)
)
runCurrent()
@@ -500,7 +500,7 @@
assertThat(tiles?.size).isEqualTo(1)
with(tiles?.elementAt(0)!!) {
assertThat(this.text).isEqualTo("Disabled by other")
- assertThat(this.subtext).isEqualTo("Set up")
+ assertThat(this.subtext).isEqualTo("Not set")
assertThat(this.enabled).isEqualTo(false)
// Click the tile
@@ -519,7 +519,7 @@
// Check that nothing happened to the tile
with(tiles?.elementAt(0)!!) {
assertThat(this.text).isEqualTo("Disabled by other")
- assertThat(this.subtext).isEqualTo("Set up")
+ assertThat(this.subtext).isEqualTo("Not set")
assertThat(this.enabled).isEqualTo(false)
}
}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c76b35f..2ddaa56 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1120,7 +1120,7 @@
<string name="zen_mode_off">Off</string>
<!-- Modes: label for a mode that needs to be set up [CHAR LIMIT=35] -->
- <string name="zen_mode_set_up">Set up</string>
+ <string name="zen_mode_set_up">Not set</string>
<!-- Modes: label for a mode that cannot be manually turned on [CHAR LIMIT=35] -->
<string name="zen_mode_no_manual_invocation">Manage in settings</string>
@@ -1827,6 +1827,12 @@
<!-- Name of the alarm status bar icon. -->
<string name="status_bar_alarm">Alarm</string>
+ <!-- Format string for the content description of the icon that indicates that a Mode is on.
+ For example, if the mode name is Bedtime, this will be "Bedtime is on". This content
+ description will be associated to the mode icon in status bar, smartspace, and everyone else
+ where it might be displayed without text. [CHAR LIMIT=NONE] -->
+ <string name="active_mode_content_description"><xliff:g id="modeName" example="Do Not Disturb">%1$s</xliff:g> is on</string>
+
<!-- Wallet strings -->
<!-- Wallet empty state, title [CHAR LIMIT=32] -->
<string name="wallet_title">Wallet</string>
diff --git a/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/4.json b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/4.json
new file mode 100644
index 0000000..c3fb8d4
--- /dev/null
+++ b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/4.json
@@ -0,0 +1,88 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 4,
+ "identityHash": "a49f2f7d25cf12d1baf9a3a3e6243b64",
+ "entities": [
+ {
+ "tableName": "communal_widget_table",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `widget_id` INTEGER NOT NULL, `component_name` TEXT NOT NULL, `item_id` INTEGER NOT NULL, `user_serial_number` INTEGER NOT NULL DEFAULT -1, `span_y` INTEGER NOT NULL DEFAULT 3)",
+ "fields": [
+ {
+ "fieldPath": "uid",
+ "columnName": "uid",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "widgetId",
+ "columnName": "widget_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "componentName",
+ "columnName": "component_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "itemId",
+ "columnName": "item_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userSerialNumber",
+ "columnName": "user_serial_number",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "-1"
+ },
+ {
+ "fieldPath": "spanY",
+ "columnName": "span_y",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "3"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "uid"
+ ]
+ }
+ },
+ {
+ "tableName": "communal_item_rank_table",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `rank` INTEGER NOT NULL DEFAULT 0)",
+ "fields": [
+ {
+ "fieldPath": "uid",
+ "columnName": "uid",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "rank",
+ "columnName": "rank",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "uid"
+ ]
+ }
+ }
+ ],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a49f2f7d25cf12d1baf9a3a3e6243b64')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index b39aae9..a5bd559 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -19,6 +19,7 @@
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR;
+import static android.view.Display.INVALID_DISPLAY;
import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy;
@@ -54,6 +55,7 @@
import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.Handler;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import android.util.RotationUtils;
@@ -211,9 +213,13 @@
}
};
- private void closeDialog(String reason) {
+ private void closeDialog(String reasonString) {
+ closeDialog(BiometricPrompt.DISMISSED_REASON_USER_CANCEL, reasonString);
+ }
+
+ private void closeDialog(@DismissedReason int reason, String reasonString) {
if (isShowing()) {
- Log.i(TAG, "Close BP, reason :" + reason);
+ Log.i(TAG, "Close BP, reason :" + reasonString);
mCurrentDialog.dismissWithoutCallback(true /* animate */);
mCurrentDialog = null;
@@ -223,8 +229,7 @@
try {
if (mReceiver != null) {
- mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
- null /* credentialAttestation */);
+ mReceiver.onDialogDismissed(reason, null /* credentialAttestation */);
mReceiver = null;
}
} catch (RemoteException e) {
@@ -251,25 +256,7 @@
private void cancelIfOwnerIsNotInForeground() {
mExecution.assertIsMainThread();
- if (mCurrentDialog != null) {
- try {
- mCurrentDialog.dismissWithoutCallback(true /* animate */);
- mCurrentDialog = null;
-
- for (Callback cb : mCallbacks) {
- cb.onBiometricPromptDismissed();
- }
-
- if (mReceiver != null) {
- mReceiver.onDialogDismissed(
- BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
- null /* credentialAttestation */);
- mReceiver = null;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Remote exception", e);
- }
- }
+ closeDialog("owner not in foreground");
}
/**
@@ -1271,10 +1258,44 @@
if (!promptInfo.isAllowBackgroundAuthentication() && isOwnerInBackground()) {
cancelIfOwnerIsNotInForeground();
} else {
- mCurrentDialog.show(mWindowManager);
+ WindowManager wm = getWindowManagerForUser(userId);
+ if (wm != null) {
+ mCurrentDialog.show(wm);
+ } else {
+ closeDialog(BiometricPrompt.DISMISSED_REASON_ERROR_NO_WM,
+ "unable to get WM instance for user");
+ }
}
}
+ @Nullable
+ private WindowManager getWindowManagerForUser(int userId) {
+ if (!mUserManager.isVisibleBackgroundUsersSupported()) {
+ return mWindowManager;
+ }
+ UserManager um = mContext.createContextAsUser(UserHandle.of(userId),
+ 0 /* flags */).getSystemService(UserManager.class);
+ if (um == null) {
+ Log.e(TAG, "unable to get UserManager for user=" + userId);
+ return null;
+ }
+ if (!um.isUserVisible()) {
+ // not visible user - use default window manager
+ return mWindowManager;
+ }
+ int displayId = um.getMainDisplayIdAssignedToUser();
+ if (displayId == INVALID_DISPLAY) {
+ Log.e(TAG, "unable to get display assigned to user=" + userId);
+ return null;
+ }
+ Display display = mDisplayManager.getDisplay(displayId);
+ if (display == null) {
+ Log.e(TAG, "unable to get Display for user=" + userId);
+ return null;
+ }
+ return mContext.createDisplayContext(display).getSystemService(WindowManager.class);
+ }
+
private void onDialogDismissed(@DismissedReason int reason) {
if (DEBUG) Log.d(TAG, "onDialogDismissed: " + reason);
if (mCurrentDialog == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index aabfbd1..65c01ed 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -710,9 +710,16 @@
@Override
public void onShareButtonTapped() {
if (clipboardSharedTransitions()) {
- if (mClipboardModel.getType() != ClipboardModel.Type.OTHER) {
- finishWithSharedTransition(CLIPBOARD_OVERLAY_SHARE_TAPPED,
- IntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext));
+ switch (mClipboardModel.getType()) {
+ case TEXT:
+ case URI:
+ finish(CLIPBOARD_OVERLAY_SHARE_TAPPED,
+ IntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext));
+ break;
+ case IMAGE:
+ finishWithSharedTransition(CLIPBOARD_OVERLAY_SHARE_TAPPED,
+ IntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext));
+ break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
index 8f1854f..17f4f0c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
@@ -26,7 +26,7 @@
import androidx.sqlite.db.SupportSQLiteDatabase
import com.android.systemui.res.R
-@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 3)
+@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 4)
abstract class CommunalDatabase : RoomDatabase() {
abstract fun communalWidgetDao(): CommunalWidgetDao
@@ -43,19 +43,16 @@
* @param callback An optional callback registered to the database. Only effective when a
* new instance is created.
*/
- fun getInstance(
- context: Context,
- callback: Callback? = null,
- ): CommunalDatabase {
+ fun getInstance(context: Context, callback: Callback? = null): CommunalDatabase {
if (instance == null) {
instance =
Room.databaseBuilder(
context,
CommunalDatabase::class.java,
- context.resources.getString(R.string.config_communalDatabase)
+ context.resources.getString(R.string.config_communalDatabase),
)
.also { builder ->
- builder.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
+ builder.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
builder.fallbackToDestructiveMigration(dropAllTables = true)
callback?.let { callback -> builder.addCallback(callback) }
}
@@ -103,5 +100,21 @@
)
}
}
+
+ /**
+ * This migration adds a span_y column to the communal_widget_table and sets its default
+ * value to 3.
+ */
+ @VisibleForTesting
+ val MIGRATION_3_4 =
+ object : Migration(3, 4) {
+ override fun migrate(db: SupportSQLiteDatabase) {
+ Log.i(TAG, "Migrating from version 3 to 4")
+ db.execSQL(
+ "ALTER TABLE communal_widget_table " +
+ "ADD COLUMN span_y INTEGER NOT NULL DEFAULT 3"
+ )
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt
index e33aead..f9d2a84 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt
@@ -40,6 +40,12 @@
*/
@ColumnInfo(name = "user_serial_number", defaultValue = "$USER_SERIAL_NUMBER_UNDEFINED")
val userSerialNumber: Int,
+
+ /**
+ * The vertical span of the widget. Span_Y default value corresponds to
+ * CommunalContentSize.HALF.span
+ */
+ @ColumnInfo(name = "span_y", defaultValue = "3") val spanY: Int,
) {
companion object {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index 93b86bd..5dd4c1c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -25,6 +25,7 @@
import androidx.room.Transaction
import androidx.sqlite.db.SupportSQLiteDatabase
import com.android.systemui.communal.nano.CommunalHubState
+import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.widgets.CommunalWidgetHost
import com.android.systemui.communal.widgets.CommunalWidgetModule.Companion.DEFAULT_WIDGETS
import com.android.systemui.dagger.SysUISingleton
@@ -153,14 +154,15 @@
@Query(
"INSERT INTO communal_widget_table" +
- "(widget_id, component_name, item_id, user_serial_number) " +
- "VALUES(:widgetId, :componentName, :itemId, :userSerialNumber)"
+ "(widget_id, component_name, item_id, user_serial_number, span_y) " +
+ "VALUES(:widgetId, :componentName, :itemId, :userSerialNumber, :spanY)"
)
fun insertWidget(
widgetId: Int,
componentName: String,
itemId: Long,
userSerialNumber: Int,
+ spanY: Int = 3,
): Long
@Query("INSERT INTO communal_item_rank_table(rank) VALUES(:rank)")
@@ -169,6 +171,9 @@
@Query("UPDATE communal_item_rank_table SET rank = :order WHERE uid = :itemUid")
fun updateItemRank(itemUid: Long, order: Int)
+ @Query("UPDATE communal_widget_table SET span_y = :spanY WHERE widget_id = :widgetId")
+ fun updateWidgetSpanY(widgetId: Int, spanY: Int)
+
@Query("DELETE FROM communal_widget_table") fun clearCommunalWidgetsTable()
@Query("DELETE FROM communal_item_rank_table") fun clearCommunalItemRankTable()
@@ -189,12 +194,14 @@
provider: ComponentName,
rank: Int? = null,
userSerialNumber: Int,
+ spanY: Int = CommunalContentSize.HALF.span,
): Long {
return addWidget(
widgetId = widgetId,
componentName = provider.flattenToString(),
rank = rank,
userSerialNumber = userSerialNumber,
+ spanY = spanY,
)
}
@@ -204,6 +211,7 @@
componentName: String,
rank: Int? = null,
userSerialNumber: Int,
+ spanY: Int = 3,
): Long {
val widgets = getWidgetsNow()
@@ -224,6 +232,7 @@
componentName = componentName,
itemId = insertItemRank(newRank),
userSerialNumber = userSerialNumber,
+ spanY = spanY,
)
}
@@ -246,7 +255,8 @@
clearCommunalItemRankTable()
state.widgets.forEach {
- addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber)
+ val spanY = if (it.spanY != 0) it.spanY else CommunalContentSize.HALF.span
+ addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber, spanY)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index 6cdd9ff..3312f3c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -92,6 +92,14 @@
/** Aborts the restore process and removes files from disk if necessary. */
fun abortRestoreWidgets()
+
+ /**
+ * Update the spanY of a widget in the database.
+ *
+ * @param widgetId id of the widget to update.
+ * @param spanY new spanY value for the widget.
+ */
+ fun updateWidgetSpanY(widgetId: Int, spanY: Int)
}
@SysUISingleton
@@ -118,20 +126,30 @@
/** Widget metadata from database + matching [AppWidgetProviderInfo] if any. */
private val widgetEntries: Flow<List<CommunalWidgetEntry>> =
- combine(
- communalWidgetDao.getWidgets(),
- communalWidgetHost.appWidgetProviders,
- ) { entries, providers ->
+ combine(communalWidgetDao.getWidgets(), communalWidgetHost.appWidgetProviders) {
+ entries,
+ providers ->
entries.mapNotNull { (rank, widget) ->
CommunalWidgetEntry(
appWidgetId = widget.widgetId,
componentName = widget.componentName,
rank = rank.rank,
- providerInfo = providers[widget.widgetId]
+ providerInfo = providers[widget.widgetId],
)
}
}
+ override fun updateWidgetSpanY(widgetId: Int, spanY: Int) {
+ bgScope.launch {
+ communalWidgetDao.updateWidgetSpanY(widgetId, spanY)
+ logger.i({ "Updated spanY of widget $int1 to $int2." }) {
+ int1 = widgetId
+ int2 = spanY
+ }
+ backupManager.dataChanged()
+ }
+ }
+
@OptIn(ExperimentalCoroutinesApi::class)
override val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
widgetEntries
@@ -197,6 +215,7 @@
provider = provider,
rank = rank,
userSerialNumber = userManager.getUserSerialNumber(user.identifier),
+ spanY = 3,
)
backupManager.dataChanged()
} else {
@@ -325,6 +344,7 @@
componentName = restoredWidget.componentName
rank = restoredWidget.rank
userSerialNumber = userManager.getUserSerialNumber(newUser.identifier)
+ spanY = restoredWidget.spanY
}
}
val newState = CommunalHubState().apply { widgets = newWidgets.toTypedArray() }
@@ -383,6 +403,7 @@
appWidgetId = entry.appWidgetId,
providerInfo = entry.providerInfo!!,
rank = entry.rank,
+ spanY = entry.spanY,
)
}
@@ -400,6 +421,7 @@
appWidgetId = entry.appWidgetId,
providerInfo = entry.providerInfo!!,
rank = entry.rank,
+ spanY = entry.spanY,
)
}
@@ -412,6 +434,7 @@
componentName = componentName,
icon = session.icon,
user = session.user,
+ spanY = entry.spanY,
)
} else {
null
@@ -423,5 +446,6 @@
val componentName: String,
val rank: Int,
var providerInfo: AppWidgetProviderInfo? = null,
+ var spanY: Int = 3,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto b/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto
index bc14ae1..7602a7a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto
+++ b/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto
@@ -38,5 +38,8 @@
// Serial number of the user associated with the widget.
int32 user_serial_number = 4;
+
+ // The vertical span of the widget
+ int32 span_y = 5;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
index 63b1a14..bcbc8f6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
@@ -31,6 +31,7 @@
override val appWidgetId: Int,
val providerInfo: AppWidgetProviderInfo,
override val rank: Int,
+ val spanY: Int = 3,
) : CommunalWidgetContentModel
/** Widget is pending installation */
@@ -40,5 +41,6 @@
val componentName: ComponentName,
val icon: Bitmap?,
val user: UserHandle,
+ val spanY: Int = 3,
) : CommunalWidgetContentModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt
index 099e3fc..4b9ac1d 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt
@@ -21,9 +21,10 @@
import android.view.View
import android.widget.TextClock
import com.android.internal.util.Preconditions
-import com.android.systemui.res.R
+import com.android.systemui.Flags
import com.android.systemui.complication.DreamClockTimeComplication
import com.android.systemui.complication.DreamClockTimeComplication.DreamClockTimeViewHolder
+import com.android.systemui.res.R
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
@@ -71,9 +72,13 @@
/* root = */ null,
/* attachToRoot = */ false,
) as TextClock,
- "R.layout.dream_overlay_complication_clock_time did not properly inflate"
+ "R.layout.dream_overlay_complication_clock_time did not properly inflate",
)
- view.setFontVariationSettings(TAG_WEIGHT + WEIGHT)
+ if (Flags.dreamOverlayUpdatedFont()) {
+ view.setFontVariationSettings("'wght' 600, 'opsz' 96")
+ } else {
+ view.setFontVariationSettings(TAG_WEIGHT + WEIGHT)
+ }
return view
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index a94fbd9..a5b2277 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -56,6 +56,7 @@
import com.android.systemui.scene.SceneContainerFrameworkModule;
import com.android.systemui.screenshot.ReferenceScreenshotModule;
import com.android.systemui.settings.MultiUserUtilsModule;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.ShadeModule;
import com.android.systemui.startable.Dependencies;
@@ -178,9 +179,9 @@
@Provides
@SysUISingleton
static IndividualSensorPrivacyController provideIndividualSensorPrivacyController(
- SensorPrivacyManager sensorPrivacyManager) {
+ SensorPrivacyManager sensorPrivacyManager, UserTracker userTracker) {
IndividualSensorPrivacyController spC = new IndividualSensorPrivacyControllerImpl(
- sensorPrivacyManager);
+ sensorPrivacyManager, userTracker);
spC.init();
return spC;
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/data/repository/InputDeviceRepository.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/data/repository/InputDeviceRepository.kt
index 5a008bd..7711c48 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/data/repository/InputDeviceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/data/repository/InputDeviceRepository.kt
@@ -38,7 +38,7 @@
constructor(
@Background private val backgroundHandler: Handler,
@Background private val backgroundScope: CoroutineScope,
- private val inputManager: InputManager
+ private val inputManager: InputManager,
) {
sealed interface DeviceChange
@@ -50,11 +50,11 @@
data object FreshStart : DeviceChange
/**
- * Emits collection of all currently connected keyboards and what was the last [DeviceChange].
- * It emits collection so that every new subscriber to this SharedFlow can get latest state of
- * all keyboards. Otherwise we might get into situation where subscriber timing on
- * initialization matter and later subscriber will only get latest device and will miss all
- * previous devices.
+ * Emits collection of all currently connected input devices and what was the last
+ * [DeviceChange]. It emits collection so that every new subscriber to this SharedFlow can get
+ * latest state of all input devices. Otherwise we might get into situation where subscriber
+ * timing on initialization matter and later subscriber will only get latest device and will
+ * miss all previous devices.
*/
// TODO(b/351984587): Replace with StateFlow
@SuppressLint("SharedFlowCreation")
@@ -79,11 +79,7 @@
inputManager.registerInputDeviceListener(listener, backgroundHandler)
awaitClose { inputManager.unregisterInputDeviceListener(listener) }
}
- .shareIn(
- scope = backgroundScope,
- started = SharingStarted.Lazily,
- replay = 1,
- )
+ .shareIn(scope = backgroundScope, started = SharingStarted.Lazily, replay = 1)
private fun <T> SendChannel<T>.sendWithLogging(element: T) {
trySendWithFailureLogging(element, TAG)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/CommandLineKeyboardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/CommandLineKeyboardRepository.kt
index f49cfdd..021c069 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/CommandLineKeyboardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/CommandLineKeyboardRepository.kt
@@ -50,6 +50,8 @@
private val _newlyConnectedKeyboard: MutableStateFlow<Keyboard?> = MutableStateFlow(null)
override val newlyConnectedKeyboard: Flow<Keyboard> = _newlyConnectedKeyboard.filterNotNull()
+ override val connectedKeyboards: Flow<Set<Keyboard>> = MutableStateFlow(emptySet())
+
init {
Log.i(TAG, "initializing shell command $COMMAND")
commandRegistry.registerCommand(COMMAND) { KeyboardCommand() }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
index a20dfa5..3329fe2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
@@ -61,6 +61,9 @@
*/
val newlyConnectedKeyboard: Flow<Keyboard>
+ /** Emits set of currently connected keyboards */
+ val connectedKeyboards: Flow<Set<Keyboard>>
+
/**
* Emits [BacklightModel] whenever user changes backlight level from keyboard press. Can only
* happen when physical keyboard is connected
@@ -74,7 +77,7 @@
constructor(
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val inputManager: InputManager,
- inputDeviceRepository: InputDeviceRepository
+ inputDeviceRepository: InputDeviceRepository,
) : KeyboardRepository {
@FlowPreview
@@ -93,6 +96,13 @@
.mapNotNull { deviceIdToKeyboard(it) }
.flowOn(backgroundDispatcher)
+ override val connectedKeyboards: Flow<Set<Keyboard>> =
+ inputDeviceRepository.deviceChange
+ .map { (deviceIds, _) -> deviceIds }
+ .map { deviceIds -> deviceIds.filter { isPhysicalFullKeyboard(it) } }
+ .distinctUntilChanged()
+ .map { deviceIds -> deviceIds.mapNotNull { deviceIdToKeyboard(it) }.toSet() }
+
override val isAnyKeyboardConnected: Flow<Boolean> =
inputDeviceRepository.deviceChange
.map { (ids, _) -> ids.any { id -> isPhysicalFullKeyboard(id) } }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 9443570..1497026 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -19,10 +19,12 @@
import com.android.keyguard.logging.ScrimLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.DEFAULT_REVEAL_DURATION
import com.android.systemui.keyguard.data.repository.LightRevealScrimRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.shared.model.ScreenPowerState
+import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.util.kotlin.sample
import dagger.Lazy
@@ -50,11 +52,29 @@
scope.launch {
transitionInteractor.startedKeyguardTransitionStep.collect {
scrimLogger.d(TAG, "listenForStartedKeyguardTransitionStep", it)
- lightRevealScrimRepository.startRevealAmountAnimator(willBeRevealedInState(it.to))
+ val animationDuration =
+ if (it.to == KeyguardState.AOD && isLastSleepDueToFold) {
+ // Do not animate the scrim when folding as we want to cover the screen
+ // with the scrim immediately while displays are switching.
+ // This is needed to play the fold to AOD animation which starts with
+ // fully black screen (see FoldAodAnimationController)
+ 0L
+ } else {
+ DEFAULT_REVEAL_DURATION
+ }
+
+ lightRevealScrimRepository.startRevealAmountAnimator(
+ willBeRevealedInState(it.to),
+ duration = animationDuration
+ )
}
}
}
+ private val isLastSleepDueToFold: Boolean
+ get() = powerInteractor.get().detailedWakefulness.value
+ .lastSleepReason == WakeSleepReason.FOLD
+
/**
* Whenever a keyguard transition starts, sample the latest reveal effect from the repository
* and use that for the starting transition.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index 8386628..57cb10f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -121,7 +121,10 @@
private fun applyClockDefaultConstraints(context: Context, constraints: ConstraintSet) {
constraints.apply {
constrainWidth(R.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT)
- constrainHeight(R.id.lockscreen_clock_view_large, ConstraintSet.MATCH_CONSTRAINT)
+ // The following two lines make lockscreen_clock_view_large is constrained to available
+ // height when it goes beyond constraints; otherwise, it use WRAP_CONTENT
+ constrainHeight(R.id.lockscreen_clock_view_large, WRAP_CONTENT)
+ constrainMaxHeight(R.id.lockscreen_clock_view_large, 0)
val largeClockTopMargin =
SystemBarUtils.getStatusBarHeight(context) +
context.resources.getDimensionPixelSize(
@@ -138,7 +141,7 @@
R.id.lockscreen_clock_view_large,
ConstraintSet.END,
PARENT_ID,
- ConstraintSet.END
+ ConstraintSet.END,
)
// In preview, we'll show UDFPS icon for UDFPS devices
@@ -160,14 +163,14 @@
BOTTOM,
PARENT_ID,
BOTTOM,
- clockBottomMargin
+ clockBottomMargin,
)
}
constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT)
constrainHeight(
R.id.lockscreen_clock_view,
- context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height)
+ context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height),
)
connect(
R.id.lockscreen_clock_view,
@@ -175,7 +178,7 @@
PARENT_ID,
START,
context.resources.getDimensionPixelSize(customizationR.dimen.clock_padding_start) +
- context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+ context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal),
)
val smallClockTopMargin =
context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
@@ -188,7 +191,7 @@
context: Context,
rootView: ConstraintLayout,
previewClock: ClockController,
- viewModel: KeyguardPreviewClockViewModel
+ viewModel: KeyguardPreviewClockViewModel,
) {
val cs = ConstraintSet().apply { clone(rootView) }
applyClockDefaultConstraints(context, cs)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
index c0b9efaa..914730e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
@@ -25,7 +25,7 @@
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.ui.composable.transitions.FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION
+import com.android.systemui.scene.ui.composable.transitions.TO_BOUNCER_FADE_FRACTION
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -49,14 +49,12 @@
duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
edge = Edge.create(from = LOCKSCREEN, to = Scenes.Bouncer),
)
- .setupWithoutSceneContainer(
- edge = Edge.create(from = LOCKSCREEN, to = PRIMARY_BOUNCER),
- )
+ .setupWithoutSceneContainer(edge = Edge.create(from = LOCKSCREEN, to = PRIMARY_BOUNCER))
private val alphaForAnimationStep: (Float) -> Float =
when {
SceneContainerFlag.isEnabled -> { step ->
- 1f - Math.min((step / FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION), 1f)
+ 1f - Math.min((step / TO_BOUNCER_FADE_FRACTION), 1f)
}
else -> { step -> 1f - step }
}
@@ -64,7 +62,7 @@
val shortcutsAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
- onStep = alphaForAnimationStep
+ onStep = alphaForAnimationStep,
)
val lockscreenAlpha: Flow<Float> = shortcutsAlpha
@@ -76,8 +74,8 @@
duration = 250.milliseconds,
onStep = { 1f - it },
onCancel = { 0f },
- onFinish = { 0f }
+ onFinish = { 0f },
),
- flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f)
+ flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index f7a505a..5048a5d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -326,6 +326,11 @@
logGesture(mInRejectedExclusion
? SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED_REJECTED
: SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED);
+ if (!mInRejectedExclusion) {
+ // Log successful back gesture to contextual edu stats
+ mOverviewProxyService.updateContextualEduStats(mIsTrackpadThreeFingerSwipe,
+ GestureType.BACK);
+ }
}
@Override
@@ -1153,8 +1158,6 @@
if (mAllowGesture) {
if (mBackAnimation != null) {
mBackAnimation.onThresholdCrossed();
- mOverviewProxyService.updateContextualEduStats(
- mIsTrackpadThreeFingerSwipe, GestureType.BACK);
} else {
pilferPointers();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index a4fe4e3..ad76b4f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -169,50 +169,34 @@
private void enableZenMode(@Nullable Expandable expandable) {
int zenDuration = mSettingZenDuration.getValue();
- boolean showOnboarding = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0) != 0
- && Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ZEN_SETTINGS_UPDATED, 0) != 1;
- if (showOnboarding) {
- // don't show on-boarding again or notification ever
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
- // turn on DND
- mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
- // show on-boarding screen
- Intent intent = new Intent(Settings.ZEN_MODE_ONBOARDING);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
- } else {
- switch (zenDuration) {
- case Settings.Secure.ZEN_DURATION_PROMPT:
- mUiHandler.post(() -> {
- Dialog dialog = makeZenModeDialog();
- if (expandable != null) {
- DialogTransitionAnimator.Controller controller =
- expandable.dialogTransitionController(new DialogCuj(
- InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- INTERACTION_JANK_TAG));
- if (controller != null) {
- mDialogTransitionAnimator.show(dialog,
- controller, /* animateBackgroundBoundsChange= */ false);
- } else {
- dialog.show();
- }
+ switch (zenDuration) {
+ case Settings.Secure.ZEN_DURATION_PROMPT:
+ mUiHandler.post(() -> {
+ Dialog dialog = makeZenModeDialog();
+ if (expandable != null) {
+ DialogTransitionAnimator.Controller controller =
+ expandable.dialogTransitionController(new DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG));
+ if (controller != null) {
+ mDialogTransitionAnimator.show(dialog,
+ controller, /* animateBackgroundBoundsChange= */ false);
} else {
dialog.show();
}
- });
- break;
- case Settings.Secure.ZEN_DURATION_FOREVER:
- mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
- break;
- default:
- Uri conditionId = ZenModeConfig.toTimeCondition(mContext, zenDuration,
- mHost.getUserId(), true).id;
- mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
- conditionId, TAG);
- }
+ } else {
+ dialog.show();
+ }
+ });
+ break;
+ case Settings.Secure.ZEN_DURATION_FOREVER:
+ mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
+ break;
+ default:
+ Uri conditionId = ZenModeConfig.toTimeCondition(mContext, zenDuration,
+ mHost.getUserId(), true).id;
+ mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ conditionId, TAG);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepository.kt
deleted file mode 100644
index a8d0777..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepository.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.data.repository
-
-import android.graphics.Region
-import android.view.ISystemGestureExclusionListener
-import android.view.IWindowManager
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
-import javax.inject.Inject
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-
-@SysUISingleton
-class SystemGestureExclusionRepository
-@Inject
-constructor(private val windowManager: IWindowManager) {
-
- /**
- * Returns [Flow] of the [Region] in which system gestures should be excluded on the display
- * identified with [displayId].
- */
- fun exclusionRegion(displayId: Int): Flow<Region?> {
- return conflatedCallbackFlow {
- val listener =
- object : ISystemGestureExclusionListener.Stub() {
- override fun onSystemGestureExclusionChanged(
- displayId: Int,
- restrictedRegion: Region?,
- unrestrictedRegion: Region?,
- ) {
- trySend(restrictedRegion)
- }
- }
- windowManager.registerSystemGestureExclusionListener(listener, displayId)
-
- awaitClose {
- windowManager.unregisterSystemGestureExclusionListener(listener, displayId)
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractor.kt
deleted file mode 100644
index 4cee874..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractor.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.domain.interactor
-
-import android.graphics.Region
-import com.android.systemui.scene.data.repository.SystemGestureExclusionRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-class SystemGestureExclusionInteractor
-@Inject
-constructor(private val repository: SystemGestureExclusionRepository) {
-
- /**
- * Returns [Flow] of the [Region] in which system gestures should be excluded on the display
- * identified with [displayId].
- */
- fun exclusionRegion(displayId: Int): Flow<Region?> {
- return repository.exclusionRegion(displayId)
- }
-}
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 a8be580..38f4e73 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
@@ -107,13 +107,7 @@
view.viewModel(
traceName = "SceneWindowRootViewBinder",
minWindowLifecycleState = WindowLifecycleState.ATTACHED,
- factory = {
- viewModelFactory.create(
- view,
- view.context.displayId,
- motionEventHandlerReceiver,
- )
- },
+ factory = { viewModelFactory.create(view, motionEventHandlerReceiver) },
) { viewModel ->
try {
view.setViewTreeOnBackPressedDispatcherOwner(
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilter.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilter.kt
deleted file mode 100644
index a1d915a..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilter.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.ui.viewmodel
-
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.geometry.Offset
-import com.android.systemui.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.Hydrator
-import com.android.systemui.scene.domain.interactor.SystemGestureExclusionInteractor
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
-import kotlin.math.roundToInt
-
-/** Decides whether drag gestures should be filtered out in the scene container framework. */
-class SceneContainerGestureFilter
-@AssistedInject
-constructor(interactor: SystemGestureExclusionInteractor, @Assisted displayId: Int) :
- ExclusiveActivatable() {
-
- private val hydrator = Hydrator("SceneContainerGestureFilter.hydrator")
- private val exclusionRegion by
- hydrator.hydratedStateOf(
- traceName = "exclusionRegion",
- initialValue = null,
- source = interactor.exclusionRegion(displayId),
- )
-
- override suspend fun onActivated(): Nothing {
- hydrator.activate()
- }
-
- /**
- * Returns `true` if a drag gesture starting at [startPosition] should be filtered out (e.g.
- * ignored, `false` otherwise.
- *
- * Invoke this and pass in the position of the `ACTION_DOWN` pointer event that began the
- * gesture.
- */
- fun shouldFilterGesture(startPosition: Offset): Boolean {
- check(isActive) { "Must be activated to use!" }
-
- return exclusionRegion?.contains(startPosition.x.roundToInt(), startPosition.y.roundToInt())
- ?: false
- }
-
- @AssistedFactory
- interface Factory {
- fun create(displayId: Int): SceneContainerGestureFilter
- }
-}
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 f505385..889380a 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
@@ -19,7 +19,6 @@
import android.view.MotionEvent
import android.view.View
import androidx.compose.runtime.getValue
-import androidx.compose.ui.geometry.Offset
import com.android.app.tracing.coroutines.launch
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.DefaultEdgeDetector
@@ -61,10 +60,8 @@
shadeInteractor: ShadeInteractor,
private val splitEdgeDetector: SplitEdgeDetector,
private val logger: SceneLogger,
- gestureFilterFactory: SceneContainerGestureFilter.Factory,
hapticsViewModelFactory: SceneContainerHapticsViewModel.Factory,
@Assisted view: View,
- @Assisted displayId: Int,
@Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
) : ExclusiveActivatable() {
@@ -92,8 +89,6 @@
},
)
- private val gestureFilter: SceneContainerGestureFilter = gestureFilterFactory.create(displayId)
-
override suspend fun onActivated(): Nothing {
try {
// Sends a MotionEventHandler to the owner of the view-model so they can report
@@ -112,7 +107,6 @@
coroutineScope {
launch { hydrator.activate() }
- launch { gestureFilter.activate() }
launch("SceneContainerHapticsViewModel") { hapticsViewModel.activate() }
}
awaitCancellation()
@@ -262,17 +256,6 @@
}
}
- /**
- * Returns `true` if a drag gesture starting at [startPosition] should be filtered out (e.g.
- * ignored, `false` otherwise.
- *
- * Invoke this and pass in the position of the `ACTION_DOWN` pointer event that began the
- * gesture.
- */
- fun shouldFilterGesture(startPosition: Offset): Boolean {
- return gestureFilter.shouldFilterGesture(startPosition)
- }
-
/** Defines interface for classes that can handle externally-reported [MotionEvent]s. */
interface MotionEventHandler {
/** Notifies that a [MotionEvent] has occurred. */
@@ -289,7 +272,6 @@
interface Factory {
fun create(
view: View,
- displayId: Int,
motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
): SceneContainerViewModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
index b5d45a4..fb7c34f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
@@ -15,17 +15,17 @@
/** [ScreenshotData] represents the current state of a single screenshot being acquired. */
data class ScreenshotData(
- @ScreenshotType var type: Int,
- @ScreenshotSource var source: Int,
+ @ScreenshotType val type: Int,
+ @ScreenshotSource val source: Int,
/** UserHandle for the owner of the app being screenshotted, if known. */
- var userHandle: UserHandle?,
+ val userHandle: UserHandle?,
/** ComponentName of the top-most app in the screenshot. */
- var topComponent: ComponentName?,
+ val topComponent: ComponentName?,
var screenBounds: Rect?,
- var taskId: Int,
+ val taskId: Int,
var insets: Insets,
var bitmap: Bitmap?,
- var displayId: Int,
+ val displayId: Int,
) {
val packageNameString
get() = topComponent?.packageName ?: ""
@@ -50,16 +50,21 @@
)
@VisibleForTesting
- fun forTesting() =
+ fun forTesting(
+ userHandle: UserHandle? = null,
+ source: Int = ScreenshotSource.SCREENSHOT_KEY_CHORD,
+ topComponent: ComponentName? = null,
+ bitmap: Bitmap? = null,
+ ) =
ScreenshotData(
type = WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
- source = ScreenshotSource.SCREENSHOT_KEY_CHORD,
- userHandle = null,
- topComponent = null,
+ source = source,
+ userHandle = userHandle,
+ topComponent = topComponent,
screenBounds = null,
taskId = 0,
insets = Insets.NONE,
- bitmap = null,
+ bitmap = bitmap,
displayId = Display.DEFAULT_DISPLAY,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 38608d0..ab8a953 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -202,10 +202,10 @@
// Return the single display to be screenshot based upon the request.
private suspend fun getDisplayToScreenshot(screenshotRequest: ScreenshotRequest): Display {
return when (screenshotRequest.source) {
- // TODO(b/367394043): Overview requests should use a display ID provided in
- // ScreenshotRequest.
ScreenshotSource.SCREENSHOT_OVERVIEW ->
- displayRepository.getDisplay(Display.DEFAULT_DISPLAY)
+ // Show on the display where overview was shown if available.
+ displayRepository.getDisplay(screenshotRequest.displayId)
+ ?: displayRepository.getDisplay(Display.DEFAULT_DISPLAY)
?: error("Can't find default display")
// Key chord and vendor gesture occur on the device itself, so screenshot the device's
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 4f47536..f83548d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -994,7 +994,9 @@
// be dropped, causing the shade expansion to fail silently. Since the shade doesn't open,
// it doesn't become visible, and the bounds will never update. Therefore, we must detect
// the incorrect bounds here and force the update so that touches are routed correctly.
- if (SceneContainerFlag.isEnabled() && mWindowRootView.getVisibility() == View.INVISIBLE) {
+ if (SceneContainerFlag.isEnabled()
+ && mWindowRootView != null
+ && mWindowRootView.getVisibility() == View.INVISIBLE) {
Rect bounds = newConfig.windowConfiguration.getBounds();
if (mWindowRootView.getWidth() != bounds.width()) {
mLogger.logConfigChangeWidthAdjust(mWindowRootView.getWidth(), bounds.width());
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
index 65b6231..e5f6846 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
@@ -30,41 +30,47 @@
fun singleShadeActions(
requireTwoPointersForTopEdgeForQs: Boolean = false
): Array<Pair<UserAction, UserActionResult>> {
+ val shadeUserActionResult = UserActionResult(Scenes.Shade, isIrreversible = true)
+ val qsSceneUserActionResult = UserActionResult(Scenes.QuickSettings, isIrreversible = true)
return arrayOf(
// Swiping down, not from the edge, always goes to shade.
- Swipe.Down to Scenes.Shade,
- swipeDown(pointerCount = 2) to Scenes.Shade,
+ Swipe.Down to shadeUserActionResult,
+ swipeDown(pointerCount = 2) to shadeUserActionResult,
// Swiping down from the top edge.
swipeDownFromTop(pointerCount = 1) to
if (requireTwoPointersForTopEdgeForQs) {
- Scenes.Shade
+ shadeUserActionResult
} else {
- Scenes.QuickSettings
+ qsSceneUserActionResult
},
- swipeDownFromTop(pointerCount = 2) to Scenes.QuickSettings,
+ swipeDownFromTop(pointerCount = 2) to qsSceneUserActionResult,
)
}
/** Returns collection of [UserAction] to [UserActionResult] pairs for opening the split shade. */
fun splitShadeActions(): Array<Pair<UserAction, UserActionResult>> {
- val splitShadeSceneKey = UserActionResult(Scenes.Shade, ToSplitShade)
+ val shadeUserActionResult = UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true)
return arrayOf(
// Swiping down, not from the edge, always goes to shade.
- Swipe.Down to splitShadeSceneKey,
- swipeDown(pointerCount = 2) to splitShadeSceneKey,
+ Swipe.Down to shadeUserActionResult,
+ swipeDown(pointerCount = 2) to shadeUserActionResult,
// Swiping down from the top edge goes to QS.
- swipeDownFromTop(pointerCount = 1) to splitShadeSceneKey,
- swipeDownFromTop(pointerCount = 2) to splitShadeSceneKey,
+ swipeDownFromTop(pointerCount = 1) to shadeUserActionResult,
+ swipeDownFromTop(pointerCount = 2) to shadeUserActionResult,
)
}
/** Returns collection of [UserAction] to [UserActionResult] pairs for opening the dual shade. */
fun dualShadeActions(): Array<Pair<UserAction, UserActionResult>> {
+ val notifShadeUserActionResult =
+ UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
+ val qsShadeuserActionResult =
+ UserActionResult.ShowOverlay(Overlays.QuickSettingsShade, isIrreversible = true)
return arrayOf(
- Swipe.Down to Overlays.NotificationsShade,
+ Swipe.Down to notifShadeUserActionResult,
Swipe(direction = SwipeDirection.Down, fromSource = SceneContainerEdge.TopRight) to
- Overlays.QuickSettingsShade,
+ qsShadeuserActionResult,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt
index cc6e8c2..3113dc4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt
@@ -32,6 +32,7 @@
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
/**
* Models the UI state for the user actions that the user can perform to navigate to other scenes.
@@ -50,7 +51,9 @@
combine(
shadeInteractor.shadeMode,
qsSceneAdapter.isCustomizerShowing,
- sceneBackInteractor.backScene.map { it ?: SceneFamilies.Home },
+ sceneBackInteractor.backScene
+ .filter { it != Scenes.Shade }
+ .map { it ?: SceneFamilies.Home },
) { shadeMode, isCustomizerShowing, backScene ->
buildMap<UserAction, UserActionResult> {
if (!isCustomizerShowing) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 7244f8a..e47952f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -62,11 +62,13 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
@@ -286,6 +288,8 @@
protected ContentObserver mLockscreenSettingsObserver;
protected ContentObserver mSettingsObserver;
+ private final Lazy<DeviceUnlockedInteractor> mDeviceUnlockedInteractorLazy;
+
@Inject
public NotificationLockscreenUserManagerImpl(Context context,
BroadcastDispatcher broadcastDispatcher,
@@ -305,7 +309,8 @@
SecureSettings secureSettings,
DumpManager dumpManager,
LockPatternUtils lockPatternUtils,
- FeatureFlagsClassic featureFlags) {
+ FeatureFlagsClassic featureFlags,
+ Lazy<DeviceUnlockedInteractor> deviceUnlockedInteractorLazy) {
mContext = context;
mMainExecutor = mainExecutor;
mBackgroundExecutor = backgroundExecutor;
@@ -325,6 +330,7 @@
mSecureSettings = secureSettings;
mKeyguardStateController = keyguardStateController;
mFeatureFlags = featureFlags;
+ mDeviceUnlockedInteractorLazy = deviceUnlockedInteractorLazy;
mLockScreenUris.add(SHOW_LOCKSCREEN);
mLockScreenUris.add(SHOW_PRIVATE_LOCKSCREEN);
@@ -748,8 +754,13 @@
// camera on the keyguard has a state of SHADE but the keyguard is still showing.
final boolean showingKeyguard = mState != StatusBarState.SHADE
|| mKeyguardStateController.isShowing();
- final boolean devicePublic = showingKeyguard && mKeyguardStateController.isMethodSecure();
-
+ final boolean devicePublic;
+ if (SceneContainerFlag.isEnabled()) {
+ devicePublic = !mDeviceUnlockedInteractorLazy.get()
+ .getDeviceUnlockStatus().getValue().isUnlocked();
+ } else {
+ devicePublic = showingKeyguard && mKeyguardStateController.isMethodSecure();
+ }
// Look for public mode users. Users are considered public in either case of:
// - device keyguard is shown in secure mode;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 5d14be8..73ad0e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -272,7 +272,7 @@
* Updates the {@link StatusBarState} and notifies registered listeners, if needed.
*/
private void updateStateAndNotifyListeners(int state) {
- if (state != mUpcomingState) {
+ if (state != mUpcomingState && !SceneContainerFlag.isEnabled()) {
Log.d(TAG, "setState: requested state " + StatusBarState.toString(state)
+ "!= upcomingState: " + StatusBarState.toString(mUpcomingState) + ". "
+ "This usually means the status bar state transition was interrupted before "
@@ -728,20 +728,23 @@
// doesn't work well for clients of this class (like remote input) that expect the device to
// be fully and properly unlocked when the state changes to SHADE.
//
- // Therefore, we calculate the device to be in a locked-ish state (KEYGUARD or SHADE_LOCKED,
+ // Therefore, we consider the device to be in a keyguardish state (KEYGUARD or SHADE_LOCKED,
// but not SHADE) if *any* of these are still true:
// 1. deviceUnlockStatus.isUnlocked is false.
- // 2. We are on (currentScene equals) a locked-ish scene (Lockscreen, Bouncer, or Communal).
- // 3. We are over (backStack contains) a locked-ish scene (Lockscreen or Communal).
+ // 2. currentScene is a keyguardish scene (Lockscreen, Bouncer, or Communal).
+ // 3. backStack contains a keyguardish scene (Lockscreen or Communal).
+
+ final boolean onKeyguardish = onLockscreen || onBouncer || onCommunal;
+ final boolean overKeyguardish = overLockscreen || overCommunal;
if (isOccluded) {
// Occlusion is special; even though the device is still technically on the lockscreen,
// the UI behaves as if it is unlocked.
newState = StatusBarState.SHADE;
- } else if (onLockscreen || onBouncer || onCommunal || overLockscreen || overCommunal) {
- // We get here if we are on or over a locked-ish scene, even if isUnlocked is true; we
+ } else if (onKeyguardish || overKeyguardish) {
+ // We get here if we are on or over a keyguardish scene, even if isUnlocked is true; we
// want to return SHADE_LOCKED or KEYGUARD until we are also neither on nor over a
- // locked-ish scene.
+ // keyguardish scene.
if (onShade || onQuickSettings || overShade || overlaidShade || overlaidQuickSettings) {
newState = StatusBarState.SHADE_LOCKED;
} else {
@@ -751,7 +754,7 @@
newState = StatusBarState.SHADE;
} else if (onShade || onQuickSettings) {
// We get here if deviceUnlockStatus.isUnlocked is false but we are no longer on or over
- // a locked-ish scene; we want to return SHADE_LOCKED until isUnlocked is also true.
+ // a keyguardish scene; we want to return SHADE_LOCKED until isUnlocked is also true.
newState = StatusBarState.SHADE_LOCKED;
} else {
throw new IllegalArgumentException(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index e3c47a4..321593b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -596,7 +596,8 @@
// NetworkCapabilities, but we need to convert it into TRANSPORT_WIFI in order to
// distinguish it from VCN over Cellular.
if (transportTypes[i] == NetworkCapabilities.TRANSPORT_CELLULAR
- && Utils.tryGetWifiInfoForVcn(networkCapabilities) != null) {
+ && Utils.tryGetWifiInfoForVcn(mConnectivityManager, networkCapabilities)
+ != null) {
transportTypes[i] = NetworkCapabilities.TRANSPORT_WIFI;
break;
}
@@ -1112,7 +1113,9 @@
continue;
}
if (transportType == NetworkCapabilities.TRANSPORT_CELLULAR
- && Utils.tryGetWifiInfoForVcn(mLastDefaultNetworkCapabilities) != null) {
+ && Utils.tryGetWifiInfoForVcn(
+ mConnectivityManager, mLastDefaultNetworkCapabilities)
+ != null) {
mConnectedTransports.set(NetworkCapabilities.TRANSPORT_WIFI);
if (mLastDefaultNetworkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
mValidatedTransports.set(NetworkCapabilities.TRANSPORT_WIFI);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 97add30..a24f267 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -91,7 +91,6 @@
constructor(
private val context: Context,
private val featureFlags: FeatureFlags,
- private val smartspaceManager: SmartspaceManager?,
private val activityStarter: ActivityStarter,
private val falsingManager: FalsingManager,
private val systemClock: SystemClock,
@@ -124,6 +123,7 @@
private const val MAX_RECENT_SMARTSPACE_DATA_FOR_DUMP = 5
}
+ private var userSmartspaceManager: SmartspaceManager? = null
private var session: SmartspaceSession? = null
private val datePlugin: BcSmartspaceDataPlugin? = optionalDatePlugin.orElse(null)
private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null)
@@ -461,7 +461,11 @@
}
private fun connectSession() {
- if (smartspaceManager == null) return
+ if (userSmartspaceManager == null) {
+ userSmartspaceManager =
+ userTracker.userContext.getSystemService(SmartspaceManager::class.java)
+ }
+ if (userSmartspaceManager == null) return
if (datePlugin == null && weatherPlugin == null && plugin == null) return
if (session != null || smartspaceViews.isEmpty()) {
return
@@ -474,12 +478,14 @@
return
}
- val newSession = smartspaceManager.createSmartspaceSession(
- SmartspaceConfig.Builder(
- context, BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD).build())
+ val newSession = userSmartspaceManager?.createSmartspaceSession(
+ SmartspaceConfig.Builder(
+ userTracker.userContext, BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD
+ ).build()
+ )
Log.d(TAG, "Starting smartspace session for " +
BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
- newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+ newSession?.addOnTargetsAvailableListener(uiExecutor, sessionListener)
this.session = newSession
deviceProvisionedController.removeCallback(deviceProvisionedListener)
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 5f4f72f..0474344 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -594,7 +594,9 @@
private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener =
(extractor, which) -> updateTheme();
private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor;
- private final GlanceableHubContainerController mGlanceableHubContainerController;
+
+ // Only use before the scene container. Null if scene container is enabled.
+ @Nullable private final GlanceableHubContainerController mGlanceableHubContainerController;
private final EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
@@ -807,7 +809,11 @@
mFingerprintManager = fingerprintManager;
mActivityStarter = activityStarter;
mBrightnessMirrorShowingInteractor = brightnessMirrorShowingInteractor;
- mGlanceableHubContainerController = glanceableHubContainerController;
+ if (!SceneContainerFlag.isEnabled()) {
+ mGlanceableHubContainerController = glanceableHubContainerController;
+ } else {
+ mGlanceableHubContainerController = null;
+ }
mEmergencyGestureIntentFactory = emergencyGestureIntentFactory;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
@@ -2972,7 +2978,9 @@
@Override
public void handleCommunalHubTouch(MotionEvent event) {
- mGlanceableHubContainerController.onTouchEvent(event);
+ if (mGlanceableHubContainerController != null) {
+ mGlanceableHubContainerController.onTouchEvent(event);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 8c03538..f8eae36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -411,7 +411,8 @@
mainActiveMode.getIcon().key().resPackage(),
mainActiveMode.getIcon().key().resId(),
mainActiveMode.getIcon().drawable(),
- mainActiveMode.getName(),
+ mResources.getString(R.string.active_mode_content_description,
+ mainActiveMode.getName()),
StatusBarIcon.Shape.FIXED_SPACE);
}
if (visible != mZenVisible) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index b1754fd..200f080 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -34,11 +34,15 @@
import androidx.annotation.Nullable;
+import com.android.compose.animation.scene.ObservableTransitionState;
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.ActionClickLogger;
import com.android.systemui.statusbar.CommandQueue;
@@ -52,6 +56,9 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.kotlin.JavaAdapter;
+
+import dagger.Lazy;
import java.util.concurrent.Executor;
@@ -80,6 +87,8 @@
private final ActionClickLogger mActionClickLogger;
private int mDisabled2;
protected BroadcastReceiver mChallengeReceiver = new ChallengeReceiver();
+ private final Lazy<DeviceUnlockedInteractor> mDeviceUnlockedInteractorLazy;
+ private final Lazy<SceneInteractor> mSceneInteractorLazy;
/**
*/
@@ -95,7 +104,10 @@
ShadeController shadeController,
CommandQueue commandQueue,
ActionClickLogger clickLogger,
- @Main Executor executor) {
+ @Main Executor executor,
+ Lazy<DeviceUnlockedInteractor> deviceUnlockedInteractorLazy,
+ Lazy<SceneInteractor> sceneInteractorLazy,
+ JavaAdapter javaAdapter) {
mContext = context;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mShadeController = shadeController;
@@ -113,20 +125,28 @@
mActionClickLogger = clickLogger;
mActivityIntentHelper = new ActivityIntentHelper(mContext);
mGroupExpansionManager = groupExpansionManager;
+ mDeviceUnlockedInteractorLazy = deviceUnlockedInteractorLazy;
+ mSceneInteractorLazy = sceneInteractorLazy;
+
+ if (SceneContainerFlag.isEnabled()) {
+ javaAdapter.alwaysCollectFlow(
+ mDeviceUnlockedInteractorLazy.get().getDeviceUnlockStatus(),
+ deviceUnlockStatus -> onStateChanged(mStatusBarStateController.getState()));
+ javaAdapter.alwaysCollectFlow(
+ mSceneInteractorLazy.get().getTransitionState(),
+ deviceUnlockStatus -> onStateChanged(mStatusBarStateController.getState()));
+ }
}
@Override
public void onStateChanged(int state) {
- boolean hasPendingRemoteInput = mPendingRemoteInputView != null;
- if (state == StatusBarState.SHADE
- && (mStatusBarStateController.leaveOpenOnKeyguardHide() || hasPendingRemoteInput)) {
- if (!mStatusBarStateController.isKeyguardRequested()
- && mKeyguardStateController.isUnlocked()) {
- if (hasPendingRemoteInput) {
- mExecutor.execute(mPendingRemoteInputView::callOnClick);
- }
- mPendingRemoteInputView = null;
- }
+ if (mPendingRemoteInputView == null) {
+ return;
+ }
+
+ if (state == StatusBarState.SHADE && canRetryPendingRemoteInput()) {
+ mExecutor.execute(mPendingRemoteInputView::callOnClick);
+ mPendingRemoteInputView = null;
}
}
@@ -320,6 +340,23 @@
}
}
+ /**
+ * Returns {@code true} if it is safe to retry a pending remote input. The exact criteria for
+ * this vary depending whether the scene container is enabled.
+ */
+ private boolean canRetryPendingRemoteInput() {
+ if (SceneContainerFlag.isEnabled()) {
+ final boolean isUnlocked = mDeviceUnlockedInteractorLazy.get()
+ .getDeviceUnlockStatus().getValue().isUnlocked();
+ final boolean isIdle = mSceneInteractorLazy.get()
+ .getTransitionState().getValue() instanceof ObservableTransitionState.Idle;
+ return isUnlocked && isIdle;
+ } else {
+ return mKeyguardStateController.isUnlocked()
+ && !mStatusBarStateController.isKeyguardRequested();
+ }
+ }
+
protected class ChallengeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
index da928a3..3cf2066 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
@@ -32,6 +32,7 @@
import androidx.annotation.NonNull;
import com.android.internal.camera.flags.Flags;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.ListenerSet;
import java.util.Set;
@@ -41,14 +42,17 @@
private static final int[] SENSORS = new int[] {CAMERA, MICROPHONE};
private final @NonNull SensorPrivacyManager mSensorPrivacyManager;
+ private final @NonNull UserTracker mUserTracker;
private final SparseBooleanArray mSoftwareToggleState = new SparseBooleanArray();
private final SparseBooleanArray mHardwareToggleState = new SparseBooleanArray();
private Boolean mRequiresAuthentication;
private final ListenerSet<Callback> mCallbacks = new ListenerSet<>();
public IndividualSensorPrivacyControllerImpl(
- @NonNull SensorPrivacyManager sensorPrivacyManager) {
+ @NonNull SensorPrivacyManager sensorPrivacyManager,
+ @NonNull UserTracker userTracker) {
mSensorPrivacyManager = sensorPrivacyManager;
+ mUserTracker = userTracker;
}
@Override
@@ -94,12 +98,14 @@
@Override
public void setSensorBlocked(@Source int source, @Sensor int sensor, boolean blocked) {
- mSensorPrivacyManager.setSensorPrivacyForProfileGroup(source, sensor, blocked);
+ mSensorPrivacyManager.setSensorPrivacyForProfileGroup(source, sensor, blocked,
+ mUserTracker.getUserId());
}
@Override
public void suppressSensorPrivacyReminders(int sensor, boolean suppress) {
- mSensorPrivacyManager.suppressSensorPrivacyReminders(sensor, suppress);
+ mSensorPrivacyManager.suppressSensorPrivacyReminders(sensor, suppress,
+ mUserTracker.getUserId());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
index 6764839c..4f595ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
@@ -76,7 +76,7 @@
// can be manually toggled on
mode.rule.isEnabled -> mode.isActive || mode.rule.isManualInvocationAllowed
// Mode was created as disabled, or disabled by the app that owns it ->
- // will be shown with a "Set up" text
+ // will be shown with a "Not set" text
!mode.rule.isEnabled -> mode.status == ZenMode.Status.DISABLED_BY_OTHER
else -> false
}
@@ -120,7 +120,7 @@
},
onLongClick = { openSettings(mode) },
onLongClickLabel =
- context.resources.getString(R.string.accessibility_long_click_tile)
+ context.resources.getString(R.string.accessibility_long_click_tile),
)
}
}
@@ -137,10 +137,10 @@
/**
* Returns a description of the mode, which is:
- * * a prompt to set up the mode if it is not enabled
- * * if it cannot be manually activated, text that says so
- * * otherwise, the trigger description of the mode if it exists...
- * * ...or null if it doesn't
+ * * a prompt to set up the mode if it is not enabled
+ * * if it cannot be manually activated, text that says so
+ * * otherwise, the trigger description of the mode if it exists...
+ * * ...or null if it doesn't
*
* This description is used directly for the content description of a mode tile for screen
* readers, and for the tile subtext will be augmented with the current status of the mode.
@@ -174,7 +174,7 @@
context,
R.style.Theme_SystemUI_Dialog,
/* cancelIsNeutral= */ true,
- zenDialogMetricsLogger
+ zenDialogMetricsLogger,
)
.createDialog()
SystemUIDialog.applyFlags(dialog)
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 7c055c8..7f90242 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -135,3 +135,15 @@
): Flow<R> {
return combine(flow, flow2, flow3, flow4, flow5, transform)
}
+
+fun <T1, T2, T3, T4, T5, T6, R> combineFlows(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ flow5: Flow<T5>,
+ flow6: Flow<T6>,
+ transform: (T1, T2, T3, T4, T5, T6) -> R,
+): Flow<R> {
+ return combine(flow, flow2, flow3, flow4, flow5, flow6, transform)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 079c72f..1f92bc1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -37,11 +37,8 @@
import android.media.AudioSystem;
import android.media.IAudioService;
import android.media.IVolumeController;
-import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
-import android.media.RoutingSessionInfo;
import android.media.VolumePolicy;
-import android.media.session.MediaController;
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.MediaSession.Token;
import android.net.Uri;
@@ -88,7 +85,6 @@
import java.io.PrintWriter;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
@@ -217,7 +213,7 @@
VolumeDialogControllerImpl.class.getSimpleName());
mWorker = new W(mWorkerLooper);
mRouter2Manager = MediaRouter2Manager.getInstance(mContext);
- mMediaSessionsCallbacksW = new MediaSessionsCallbacks(mContext);
+ mMediaSessionsCallbacksW = new MediaSessionsCallbacks();
mMediaSessions = createMediaSessions(mContext, mWorkerLooper, mMediaSessionsCallbacksW);
mAudioSharingInteractor = audioSharingInteractor;
mJavaAdapter = javaAdapter;
@@ -1360,16 +1356,9 @@
private final HashMap<Token, Integer> mRemoteStreams = new HashMap<>();
private int mNextStream = DYNAMIC_STREAM_REMOTE_START_INDEX;
- private final boolean mVolumeAdjustmentForRemoteGroupSessions;
-
- public MediaSessionsCallbacks(Context context) {
- mVolumeAdjustmentForRemoteGroupSessions = context.getResources().getBoolean(
- com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
- }
@Override
public void onRemoteUpdate(Token token, String name, PlaybackInfo pi) {
- if (showForSession(token)) {
addStream(token, "onRemoteUpdate");
int stream = 0;
@@ -1396,12 +1385,10 @@
Log.d(TAG, "onRemoteUpdate: " + name + ": " + ss.level + " of " + ss.levelMax);
mCallbacks.onStateChanged(mState);
}
- }
}
@Override
public void onRemoteVolumeChanged(Token token, int flags) {
- if (showForSession(token)) {
addStream(token, "onRemoteVolumeChanged");
int stream = 0;
synchronized (mRemoteStreams) {
@@ -1420,27 +1407,27 @@
if (showUI) {
onShowRequestedW(Events.SHOW_REASON_REMOTE_VOLUME_CHANGED);
}
- }
}
@Override
public void onRemoteRemoved(Token token) {
- if (showForSession(token)) {
- int stream = 0;
- synchronized (mRemoteStreams) {
- if (!mRemoteStreams.containsKey(token)) {
- Log.d(TAG, "onRemoteRemoved: stream doesn't exist, "
- + "aborting remote removed for token:" + token.toString());
- return;
- }
- stream = mRemoteStreams.get(token);
+ int stream;
+ synchronized (mRemoteStreams) {
+ if (!mRemoteStreams.containsKey(token)) {
+ Log.d(
+ TAG,
+ "onRemoteRemoved: stream doesn't exist, "
+ + "aborting remote removed for token:"
+ + token.toString());
+ return;
}
- mState.states.remove(stream);
- if (mState.activeStream == stream) {
- updateActiveStreamW(-1);
- }
- mCallbacks.onStateChanged(mState);
+ stream = mRemoteStreams.get(token);
}
+ mState.states.remove(stream);
+ if (mState.activeStream == stream) {
+ updateActiveStreamW(-1);
+ }
+ mCallbacks.onStateChanged(mState);
}
public void setStreamVolume(int stream, int level) {
@@ -1449,39 +1436,7 @@
Log.w(TAG, "setStreamVolume: No token found for stream: " + stream);
return;
}
- if (showForSession(token)) {
- mMediaSessions.setVolume(token, level);
- }
- }
-
- private boolean showForSession(Token token) {
- if (mVolumeAdjustmentForRemoteGroupSessions) {
- if (DEBUG) {
- Log.d(TAG, "Volume adjustment for remote group sessions allowed,"
- + " showForSession: true");
- }
- return true;
- }
- MediaController ctr = new MediaController(mContext, token);
- String packageName = ctr.getPackageName();
- List<RoutingSessionInfo> sessions =
- mRouter2Manager.getRoutingSessions(packageName);
- if (DEBUG) {
- Log.d(TAG, "Found " + sessions.size() + " routing sessions for package name "
- + packageName);
- }
- for (RoutingSessionInfo session : sessions) {
- if (DEBUG) {
- Log.d(TAG, "Found routingSessionInfo: " + session);
- }
- if (!session.isSystemSession()
- && session.getVolumeHandling() != MediaRoute2Info.PLAYBACK_VOLUME_FIXED) {
- return true;
- }
- }
-
- Log.d(TAG, "No routing session for " + packageName);
- return false;
+ mMediaSessions.setVolume(token, level);
}
private Token findToken(int stream) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index ebb9ce9..ed8de69 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -23,6 +23,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.CoreStartable;
+import com.android.systemui.Flags;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.plugins.VolumeDialog;
@@ -40,6 +41,8 @@
import com.android.systemui.volume.VolumeDialogImpl;
import com.android.systemui.volume.VolumePanelDialogReceiver;
import com.android.systemui.volume.VolumeUI;
+import com.android.systemui.volume.dialog.VolumeDialogPlugin;
+import com.android.systemui.volume.dialog.dagger.VolumeDialogPluginComponent;
import com.android.systemui.volume.domain.interactor.VolumeDialogInteractor;
import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor;
import com.android.systemui.volume.panel.dagger.VolumePanelComponent;
@@ -66,7 +69,8 @@
SpatializerModule.class,
},
subcomponents = {
- VolumePanelComponent.class
+ VolumePanelComponent.class,
+ VolumeDialogPluginComponent.class,
}
)
public interface VolumeModule {
@@ -101,6 +105,7 @@
/** */
@Provides
static VolumeDialog provideVolumeDialog(
+ Lazy<VolumeDialogPlugin> volumeDialogProvider,
Context context,
VolumeDialogController volumeDialogController,
AccessibilityManagerWrapper accessibilityManagerWrapper,
@@ -118,29 +123,33 @@
VibratorHelper vibratorHelper,
SystemClock systemClock,
VolumeDialogInteractor interactor) {
- VolumeDialogImpl impl = new VolumeDialogImpl(
- context,
- volumeDialogController,
- accessibilityManagerWrapper,
- deviceProvisionedController,
- configurationController,
- mediaOutputDialogManager,
- interactionJankMonitor,
- volumePanelNavigationInteractor,
- volumeNavigator,
- true, /* should listen for jank */
- csdFactory,
- devicePostureController,
- Looper.getMainLooper(),
- volumePanelFlag,
- dumpManager,
- secureSettings,
- vibratorHelper,
- systemClock,
- interactor);
- impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
- impl.setAutomute(true);
- impl.setSilentMode(false);
- return impl;
+ if (Flags.volumeRedesign()) {
+ return volumeDialogProvider.get();
+ } else {
+ VolumeDialogImpl impl = new VolumeDialogImpl(
+ context,
+ volumeDialogController,
+ accessibilityManagerWrapper,
+ deviceProvisionedController,
+ configurationController,
+ mediaOutputDialogManager,
+ interactionJankMonitor,
+ volumePanelNavigationInteractor,
+ volumeNavigator,
+ true, /* should listen for jank */
+ csdFactory,
+ devicePostureController,
+ Looper.getMainLooper(),
+ volumePanelFlag,
+ dumpManager,
+ secureSettings,
+ vibratorHelper,
+ systemClock,
+ interactor);
+ impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
+ impl.setAutomute(true);
+ impl.setSilentMode(false);
+ return impl;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialog.kt
new file mode 100644
index 0000000..869b3c6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialog.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import android.view.ContextThemeWrapper
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+class NewVolumeDialog @Inject constructor(@Application context: Context) :
+ Dialog(ContextThemeWrapper(context, R.style.volume_dialog_theme)) {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.volume_dialog)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialogPlugin.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialogPlugin.kt
new file mode 100644
index 0000000..b93714a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialogPlugin.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog
+
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.plugins.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent
+import com.android.systemui.volume.dialog.dagger.VolumeDialogPluginComponent
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+class NewVolumeDialogPlugin
+@Inject
+constructor(
+ @Application private val applicationCoroutineScope: CoroutineScope,
+ private val volumeDialogPluginComponentFactory: VolumeDialogPluginComponent.Factory,
+) : VolumeDialog {
+
+ private var volumeDialogPluginComponent: VolumeDialogPluginComponent? = null
+ private var job: Job? = null
+
+ override fun init(windowType: Int, callback: VolumeDialog.Callback?) {
+ job =
+ applicationCoroutineScope.launch {
+ coroutineScope {
+ volumeDialogPluginComponent = volumeDialogPluginComponentFactory.create(this)
+ }
+ }
+ }
+
+ private fun showDialog() {
+ val volumeDialogPluginComponent =
+ volumeDialogPluginComponent ?: error("Creating dialog before init was called")
+ volumeDialogPluginComponent.coroutineScope().launch {
+ coroutineScope {
+ val volumeDialogComponent: VolumeDialogComponent =
+ volumeDialogPluginComponent.volumeDialogComponentFactory().create(this)
+ with(volumeDialogComponent.volumeDialog()) {
+ setOnDismissListener { volumeDialogComponent.coroutineScope().cancel() }
+ show()
+ }
+ }
+ }
+ }
+
+ override fun destroy() {
+ job?.cancel()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
new file mode 100644
index 0000000..74e823e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import android.view.ContextThemeWrapper
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+class VolumeDialog @Inject constructor(@Application context: Context) :
+ Dialog(ContextThemeWrapper(context, R.style.volume_dialog_theme)) {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.volume_dialog)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt
new file mode 100644
index 0000000..a2e81d9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog
+
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.plugins.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent
+import com.android.systemui.volume.dialog.dagger.VolumeDialogPluginComponent
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+class VolumeDialogPlugin
+@Inject
+constructor(
+ @Application private val applicationCoroutineScope: CoroutineScope,
+ private val volumeDialogPluginComponentFactory: VolumeDialogPluginComponent.Factory,
+) : VolumeDialog {
+
+ private var volumeDialogPluginComponent: VolumeDialogPluginComponent? = null
+ private var job: Job? = null
+
+ override fun init(windowType: Int, callback: VolumeDialog.Callback?) {
+ job =
+ applicationCoroutineScope.launch {
+ coroutineScope {
+ volumeDialogPluginComponent = volumeDialogPluginComponentFactory.create(this)
+ }
+ }
+ }
+
+ private fun showDialog() {
+ val volumeDialogPluginComponent =
+ volumeDialogPluginComponent ?: error("Creating dialog before init was called")
+ volumeDialogPluginComponent.coroutineScope().launch {
+ coroutineScope {
+ val volumeDialogComponent: VolumeDialogComponent =
+ volumeDialogPluginComponent.volumeDialogComponentFactory().create(this)
+ with(volumeDialogComponent.volumeDialog()) {
+ setOnDismissListener { volumeDialogComponent.coroutineScope().cancel() }
+ show()
+ }
+ }
+ }
+ }
+
+ override fun destroy() {
+ job?.cancel()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt
new file mode 100644
index 0000000..f7ad320
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.dagger
+
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import dagger.BindsInstance
+import dagger.Subcomponent
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * Core Volume Dialog dagger component. It's managed by
+ * [com.android.systemui.volume.dialog.VolumeDialogPlugin] and lives alongside it.
+ */
+@VolumeDialogScope
+@Subcomponent(modules = [])
+interface VolumeDialogComponent {
+
+ /**
+ * Provides a coroutine scope to use inside [VolumeDialogScope].
+ * [com.android.systemui.volume.dialog.VolumeDialogPlugin] manages the lifecycle of this scope.
+ * It's cancelled when the dialog is disposed. This helps to free occupied resources when volume
+ * dialog is not shown.
+ */
+ @VolumeDialog fun coroutineScope(): CoroutineScope
+
+ @VolumeDialogScope fun volumeDialog(): com.android.systemui.volume.dialog.VolumeDialog
+
+ @Subcomponent.Factory
+ interface Factory {
+
+ fun create(@BindsInstance @VolumeDialog scope: CoroutineScope): VolumeDialogComponent
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogPluginComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogPluginComponent.kt
new file mode 100644
index 0000000..82612a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogPluginComponent.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.dagger
+
+import com.android.systemui.volume.dialog.dagger.module.VolumeDialogPluginModule
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
+import dagger.BindsInstance
+import dagger.Subcomponent
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * Volume Dialog plugin dagger component. It's managed by
+ * [com.android.systemui.volume.dialog.VolumeDialogPlugin] and lives alongside it.
+ */
+@VolumeDialogPluginScope
+@Subcomponent(modules = [VolumeDialogPluginModule::class])
+interface VolumeDialogPluginComponent {
+
+ /**
+ * Provides a coroutine scope to use inside [VolumeDialogPluginScope].
+ * [com.android.systemui.volume.dialog.VolumeDialogPlugin] manages the lifecycle of this scope.
+ * It's cancelled when the dialog is disposed. This helps to free occupied resources when volume
+ * dialog is not shown.
+ */
+ @VolumeDialogPlugin fun coroutineScope(): CoroutineScope
+
+ fun volumeDialogComponentFactory(): VolumeDialogComponent.Factory
+
+ @Subcomponent.Factory
+ interface Factory {
+
+ fun create(
+ @BindsInstance @VolumeDialogPlugin scope: CoroutineScope
+ ): VolumeDialogPluginComponent
+ }
+}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogPluginModule.kt
similarity index 71%
copy from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt
copy to packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogPluginModule.kt
index e52a6e1..3fdf86a 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogPluginModule.kt
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.util
+package com.android.systemui.volume.dialog.dagger.module
-/** Holds a [newValue] emitted from a `TFlow`, along with the [previousValue] emitted value. */
-data class WithPrev<out S, out T : S>(val previousValue: S, val newValue: T)
+import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent
+import dagger.Module
+
+@Module(subcomponents = [VolumeDialogComponent::class]) interface VolumeDialogPluginModule
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialog.kt
similarity index 70%
copy from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt
copy to packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialog.kt
index e52a6e1..34bddb4 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialog.kt
@@ -14,7 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.util
+package com.android.systemui.volume.dialog.dagger.scope
-/** Holds a [newValue] emitted from a `TFlow`, along with the [previousValue] emitted value. */
-data class WithPrev<out S, out T : S>(val previousValue: S, val newValue: T)
+import javax.inject.Qualifier
+
+/**
+ * Volume Dialog qualifier.
+ *
+ * @see VolumeDialogScope
+ */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class VolumeDialog
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogPlugin.kt
similarity index 69%
copy from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt
copy to packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogPlugin.kt
index e52a6e1..1038c30 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogPlugin.kt
@@ -14,7 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.util
+package com.android.systemui.volume.dialog.dagger.scope
-/** Holds a [newValue] emitted from a `TFlow`, along with the [previousValue] emitted value. */
-data class WithPrev<out S, out T : S>(val previousValue: S, val newValue: T)
+import javax.inject.Qualifier
+
+/**
+ * Volume Dialog plugin qualifier.
+ *
+ * @see VolumeDialogPluginScope
+ */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class VolumeDialogPlugin
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractorKosmos.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogPluginScope.kt
similarity index 60%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractorKosmos.kt
rename to packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogPluginScope.kt
index 3e46c3f..6c5f672 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractorKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogPluginScope.kt
@@ -14,12 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.scene.domain.interactor
+package com.android.systemui.volume.dialog.dagger.scope
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.scene.data.repository.systemGestureExclusionRepository
+import javax.inject.Scope
-val Kosmos.systemGestureExclusionInteractor by Fixture {
- SystemGestureExclusionInteractor(repository = systemGestureExclusionRepository)
-}
+/**
+ * Volume Dialog plugin dependency injection scope. This scope is created alongside Volume Dialog
+ * plugin is initialized and destroyed alongside it. This is effectively almost similar
+ * to @Application now.
+ */
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+@Scope
+annotation class VolumeDialogPluginScope
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogScope.kt
similarity index 65%
copy from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt
copy to packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogScope.kt
index e52a6e1..52caa6a 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogScope.kt
@@ -14,7 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.util
+package com.android.systemui.volume.dialog.dagger.scope
-/** Holds a [newValue] emitted from a `TFlow`, along with the [previousValue] emitted value. */
-data class WithPrev<out S, out T : S>(val previousValue: S, val newValue: T)
+import javax.inject.Scope
+
+/**
+ * Volume Panel dependency injection scope. This scope is created alongside Volume Panel and
+ * destroyed when it's lo longer present.
+ */
+@MustBeDocumented @Retention(AnnotationRetention.RUNTIME) @Scope annotation class VolumeDialogScope
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
new file mode 100644
index 0000000..ec7c6ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.domain.interactor
+
+import android.annotation.SuppressLint
+import android.os.Handler
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.plugins.VolumeDialogController
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogStateModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.shareIn
+
+private const val BUFFER_CAPACITY = 16
+
+/**
+ * Exposes [VolumeDialogController] callback events in the [event].
+ *
+ * @see VolumeDialogController.Callbacks
+ */
+@VolumeDialog
+class VolumeDialogCallbacksInteractor
+@Inject
+constructor(
+ private val volumeDialogController: VolumeDialogController,
+ @VolumeDialog private val coroutineScope: CoroutineScope,
+ @Background private val bgHandler: Handler,
+) {
+
+ @SuppressLint("SharedFlowCreation") // event-but needed
+ val event: Flow<VolumeDialogEventModel> =
+ callbackFlow {
+ val producer = VolumeDialogEventModelProducer(this)
+ volumeDialogController.addCallback(producer, bgHandler)
+ awaitClose { volumeDialogController.removeCallback(producer) }
+ }
+ .buffer(BUFFER_CAPACITY)
+ .shareIn(replay = 0, scope = coroutineScope, started = SharingStarted.WhileSubscribed())
+
+ private class VolumeDialogEventModelProducer(
+ private val scope: ProducerScope<VolumeDialogEventModel>
+ ) : VolumeDialogController.Callbacks {
+ override fun onShowRequested(reason: Int, keyguardLocked: Boolean, lockTaskModeState: Int) {
+ scope.trySend(
+ VolumeDialogEventModel.ShowRequested(
+ reason = reason,
+ keyguardLocked = keyguardLocked,
+ lockTaskModeState = lockTaskModeState,
+ )
+ )
+ }
+
+ override fun onDismissRequested(reason: Int) {
+ scope.trySend(VolumeDialogEventModel.DismissRequested(reason))
+ }
+
+ override fun onStateChanged(state: VolumeDialogController.State?) {
+ if (state != null) {
+ scope.trySend(VolumeDialogEventModel.StateChanged(VolumeDialogStateModel(state)))
+ }
+ }
+
+ override fun onLayoutDirectionChanged(layoutDirection: Int) {
+ scope.trySend(VolumeDialogEventModel.LayoutDirectionChanged(layoutDirection))
+ }
+
+ // Configuration change is never emitted by the VolumeDialogControllerImpl now.
+ override fun onConfigurationChanged() = Unit
+
+ override fun onShowVibrateHint() {
+ scope.trySend(VolumeDialogEventModel.ShowVibrateHint)
+ }
+
+ override fun onShowSilentHint() {
+ scope.trySend(VolumeDialogEventModel.ShowSilentHint)
+ }
+
+ override fun onScreenOff() {
+ scope.trySend(VolumeDialogEventModel.ScreenOff)
+ }
+
+ override fun onShowSafetyWarning(flags: Int) {
+ scope.trySend(VolumeDialogEventModel.ShowSafetyWarning(flags))
+ }
+
+ override fun onAccessibilityModeChanged(showA11yStream: Boolean) {
+ scope.trySend(VolumeDialogEventModel.AccessibilityModeChanged(showA11yStream))
+ }
+
+ // Captions button is remove from the Volume Dialog
+ override fun onCaptionComponentStateChanged(
+ isComponentEnabled: Boolean,
+ fromTooltip: Boolean,
+ ) = Unit
+
+ // Captions button is remove from the Volume Dialog
+ override fun onCaptionEnabledStateChanged(isEnabled: Boolean, checkBeforeSwitch: Boolean) =
+ Unit
+
+ override fun onShowCsdWarning(csdWarning: Int, durationMs: Int) {
+ scope.trySend(
+ VolumeDialogEventModel.ShowCsdWarning(
+ csdWarning = csdWarning,
+ durationMs = durationMs,
+ )
+ )
+ }
+
+ override fun onVolumeChangedFromKey() {
+ scope.trySend(VolumeDialogEventModel.VolumeChangedFromKey)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt
new file mode 100644
index 0000000..dd51108
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.domain.interactor
+
+import com.android.systemui.plugins.VolumeDialogController
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogStateModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Exposes [VolumeDialogController.getState] in the [volumeDialogState].
+ *
+ * @see [VolumeDialogController]
+ */
+@VolumeDialog
+class VolumeDialogStateInteractor
+@Inject
+constructor(
+ volumeDialogCallbacksInteractor: VolumeDialogCallbacksInteractor,
+ private val volumeDialogController: VolumeDialogController,
+ @VolumeDialog private val coroutineScope: CoroutineScope,
+) {
+
+ val volumeDialogState: Flow<VolumeDialogStateModel> =
+ volumeDialogCallbacksInteractor.event
+ .onStart { volumeDialogController.getState() }
+ .filterIsInstance(VolumeDialogEventModel.StateChanged::class)
+ .map { it.state }
+ .stateIn(scope = coroutineScope, started = SharingStarted.Eagerly, initialValue = null)
+ .filterNotNull()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt
new file mode 100644
index 0000000..ca0310e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.domain.model
+
+import android.media.AudioManager
+
+/**
+ * Models VolumeDialogController callback events.
+ *
+ * @see VolumeDialogController.Callbacks
+ */
+sealed interface VolumeDialogEventModel {
+
+ data class ShowRequested(
+ val reason: Int,
+ val keyguardLocked: Boolean,
+ val lockTaskModeState: Int,
+ ) : VolumeDialogEventModel
+
+ data class DismissRequested(val reason: Int) : VolumeDialogEventModel
+
+ data class StateChanged(val state: VolumeDialogStateModel) : VolumeDialogEventModel
+
+ data class LayoutDirectionChanged(val layoutDirection: Int) : VolumeDialogEventModel
+
+ data object ShowVibrateHint : VolumeDialogEventModel
+
+ data object ShowSilentHint : VolumeDialogEventModel
+
+ data object ScreenOff : VolumeDialogEventModel
+
+ data class ShowSafetyWarning(val flags: Int) : VolumeDialogEventModel
+
+ data class AccessibilityModeChanged(val showA11yStream: Boolean) : VolumeDialogEventModel
+
+ data class ShowCsdWarning(@AudioManager.CsdWarning val csdWarning: Int, val durationMs: Int) :
+ VolumeDialogEventModel
+
+ data object VolumeChangedFromKey : VolumeDialogEventModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStateModel.kt
new file mode 100644
index 0000000..f1443e3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStateModel.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.domain.model
+
+import android.content.ComponentName
+import android.util.SparseArray
+import androidx.core.util.keyIterator
+import com.android.systemui.plugins.VolumeDialogController
+
+/** Models a state of the Volume Dialog. */
+data class VolumeDialogStateModel(
+ val states: Map<Int, VolumeDialogStreamStateModel>,
+ val ringerModeInternal: Int = 0,
+ val ringerModeExternal: Int = 0,
+ val zenMode: Int = 0,
+ val effectsSuppressor: ComponentName? = null,
+ val effectsSuppressorName: String? = null,
+ val activeStream: Int = NO_ACTIVE_STREAM,
+ val disallowAlarms: Boolean = false,
+ val disallowMedia: Boolean = false,
+ val disallowSystem: Boolean = false,
+ val disallowRinger: Boolean = false,
+) {
+
+ constructor(
+ legacyState: VolumeDialogController.State
+ ) : this(
+ states = legacyState.states.mapToMap { VolumeDialogStreamStateModel(it) },
+ ringerModeInternal = legacyState.ringerModeInternal,
+ ringerModeExternal = legacyState.ringerModeExternal,
+ zenMode = legacyState.zenMode,
+ effectsSuppressor = legacyState.effectsSuppressor,
+ effectsSuppressorName = legacyState.effectsSuppressorName,
+ activeStream = legacyState.activeStream,
+ disallowAlarms = legacyState.disallowAlarms,
+ disallowMedia = legacyState.disallowMedia,
+ disallowSystem = legacyState.disallowSystem,
+ disallowRinger = legacyState.disallowRinger,
+ )
+
+ companion object {
+ const val NO_ACTIVE_STREAM: Int = -1
+ }
+}
+
+private fun <INPUT, OUTPUT> SparseArray<INPUT>.mapToMap(map: (INPUT) -> OUTPUT): Map<Int, OUTPUT> {
+ val resultMap = mutableMapOf<Int, OUTPUT>()
+ for (key in keyIterator()) {
+ val mappedValue: OUTPUT = map(get(key)!!)
+ resultMap[key] = mappedValue
+ }
+ return resultMap
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamStateModel.kt
new file mode 100644
index 0000000..a9d367d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamStateModel.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.domain.model
+
+import android.annotation.IntegerRes
+import com.android.systemui.plugins.VolumeDialogController
+
+/** Models a state of an audio stream of the Volume Dialog. */
+data class VolumeDialogStreamStateModel(
+ val isDynamic: Boolean = false,
+ val level: Int = 0,
+ val levelMin: Int = 0,
+ val levelMax: Int = 0,
+ val muted: Boolean = false,
+ val muteSupported: Boolean = false,
+ @IntegerRes val name: Int = 0,
+ val remoteLabel: String? = null,
+ val routedToBluetooth: Boolean = false,
+) {
+ constructor(
+ legacyState: VolumeDialogController.StreamState
+ ) : this(
+ isDynamic = legacyState.dynamic,
+ level = legacyState.level,
+ levelMin = legacyState.levelMin,
+ levelMax = legacyState.levelMax,
+ muted = legacyState.muted,
+ muteSupported = legacyState.muteSupported,
+ name = legacyState.name,
+ remoteLabel = legacyState.remoteLabel,
+ routedToBluetooth = legacyState.routedToBluetooth,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
new file mode 100644
index 0000000..700225d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ui.binder
+
+import android.view.View
+import com.android.systemui.lifecycle.WindowLifecycleState
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.lifecycle.setSnapshotBinding
+import com.android.systemui.lifecycle.viewModel
+import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.awaitCancellation
+
+class VolumeDialogViewBinder
+@Inject
+constructor(private val volumeDialogViewModelFactory: VolumeDialogViewModel.Factory) {
+
+ suspend fun bind(view: View) {
+ view.repeatWhenAttached {
+ view.viewModel(
+ traceName = "VolumeDialogViewBinder",
+ minWindowLifecycleState = WindowLifecycleState.ATTACHED,
+ factory = { volumeDialogViewModelFactory.create() },
+ ) { viewModel ->
+ view.setSnapshotBinding {}
+
+ awaitCancellation()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
new file mode 100644
index 0000000..f9e91ae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ui.viewmodel
+
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+class VolumeDialogViewModel @AssistedInject constructor() : ExclusiveActivatable() {
+
+ override suspend fun onActivated(): Nothing {
+ TODO("Not yet implemented")
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): VolumeDialogViewModel
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index 5fc1971..8075d11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -104,6 +104,8 @@
@Mock
private Animator mAnimator;
+ @Mock
+ private Animator mEndAnimator;
private ArgumentCaptor<Animator.AnimatorListener> mAnimatorListenerCaptor =
ArgumentCaptor.forClass(Animator.AnimatorListener.class);
@@ -123,7 +125,7 @@
MockitoAnnotations.initMocks(this);
when(mClipboardOverlayView.getEnterAnimation()).thenReturn(mAnimator);
- when(mClipboardOverlayView.getExitAnimation()).thenReturn(mAnimator);
+ when(mClipboardOverlayView.getExitAnimation()).thenReturn(mEndAnimator);
when(mClipboardOverlayView.getFadeOutAnimation()).thenReturn(mAnimator);
when(mClipboardOverlayWindow.getWindowInsets()).thenReturn(
getImeInsets(new Rect(0, 0, 0, 0)));
@@ -318,11 +320,11 @@
mOverlayController.setClipData(mSampleClipData, "");
mCallbacks.onShareButtonTapped();
- verify(mAnimator).addListener(mAnimatorListenerCaptor.capture());
- mAnimatorListenerCaptor.getValue().onAnimationEnd(mAnimator);
+ verify(mEndAnimator).addListener(mAnimatorListenerCaptor.capture());
+ mAnimatorListenerCaptor.getValue().onAnimationEnd(mEndAnimator);
verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "");
- verify(mClipboardOverlayView, times(1)).getFadeOutAnimation();
+ verify(mClipboardOverlayView, times(1)).getExitAnimation();
}
@Test
@@ -343,8 +345,8 @@
initController();
mCallbacks.onDismissButtonTapped();
- verify(mAnimator).addListener(mAnimatorListenerCaptor.capture());
- mAnimatorListenerCaptor.getValue().onAnimationEnd(mAnimator);
+ verify(mEndAnimator).addListener(mAnimatorListenerCaptor.capture());
+ mAnimatorListenerCaptor.getValue().onAnimationEnd(mEndAnimator);
// package name is null since we haven't actually set a source for this test
verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, null);
@@ -403,14 +405,18 @@
mOverlayController.setClipData(mSampleClipData, "first.package");
mCallbacks.onShareButtonTapped();
+ verify(mEndAnimator).addListener(mAnimatorListenerCaptor.capture());
+ mAnimatorListenerCaptor.getValue().onAnimationEnd(mEndAnimator);
mOverlayController.setClipData(mSampleClipData, "second.package");
mCallbacks.onShareButtonTapped();
+ verify(mEndAnimator, times(2)).addListener(mAnimatorListenerCaptor.capture());
+ mAnimatorListenerCaptor.getValue().onAnimationEnd(mEndAnimator);
- verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "first.package");
- verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "second.package");
verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_SHOWN_EXPANDED, 0, "first.package");
+ verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "first.package");
verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_SHOWN_EXPANDED, 0, "second.package");
+ verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "second.package");
verifyNoMoreInteractions(mUiEventLogger);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
index ad25502..7d5a334 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
@@ -148,6 +148,31 @@
)
}
+ @Test
+ fun migrate3To4_addSpanYColumn_defaultValuePopulated() {
+ val databaseV3 = migrationTestHelper.createDatabase(DATABASE_NAME, version = 3)
+
+ val fakeWidgetsV3 =
+ listOf(
+ FakeCommunalWidgetItemV3(1, "test_widget_1", 11, 0),
+ FakeCommunalWidgetItemV3(2, "test_widget_2", 12, 10),
+ FakeCommunalWidgetItemV3(3, "test_widget_3", 13, 0),
+ )
+ databaseV3.insertWidgetsV3(fakeWidgetsV3)
+
+ databaseV3.verifyWidgetsV3(fakeWidgetsV3)
+
+ val databaseV4 =
+ migrationTestHelper.runMigrationsAndValidate(
+ name = DATABASE_NAME,
+ version = 4,
+ validateDroppedTables = false,
+ CommunalDatabase.MIGRATION_3_4,
+ )
+
+ databaseV4.verifyWidgetsV4(fakeWidgetsV3.map { it.getV4() })
+ }
+
private fun SupportSQLiteDatabase.insertWidgetsV1(widgets: List<FakeCommunalWidgetItemV1>) {
widgets.forEach { widget ->
execSQL(
@@ -157,6 +182,22 @@
}
}
+ private fun SupportSQLiteDatabase.insertWidgetsV3(widgets: List<FakeCommunalWidgetItemV3>) {
+ widgets.forEach { widget ->
+ execSQL(
+ "INSERT INTO communal_widget_table(" +
+ "widget_id, " +
+ "component_name, " +
+ "item_id, " +
+ "user_serial_number) " +
+ "VALUES(${widget.widgetId}, " +
+ "'${widget.componentName}', " +
+ "${widget.itemId}, " +
+ "${widget.userSerialNumber})"
+ )
+ }
+ }
+
private fun SupportSQLiteDatabase.verifyWidgetsV1(widgets: List<FakeCommunalWidgetItemV1>) {
val cursor = query("SELECT * FROM communal_widget_table")
assertThat(cursor.moveToFirst()).isTrue()
@@ -193,6 +234,42 @@
assertThat(cursor.isAfterLast).isTrue()
}
+ private fun SupportSQLiteDatabase.verifyWidgetsV3(widgets: List<FakeCommunalWidgetItemV3>) {
+ val cursor = query("SELECT * FROM communal_widget_table")
+ assertThat(cursor.moveToFirst()).isTrue()
+
+ widgets.forEach { widget ->
+ assertThat(cursor.getInt(cursor.getColumnIndex("widget_id"))).isEqualTo(widget.widgetId)
+ assertThat(cursor.getString(cursor.getColumnIndex("component_name")))
+ .isEqualTo(widget.componentName)
+ assertThat(cursor.getInt(cursor.getColumnIndex("item_id"))).isEqualTo(widget.itemId)
+ assertThat(cursor.getInt(cursor.getColumnIndex("user_serial_number")))
+ .isEqualTo(widget.userSerialNumber)
+
+ cursor.moveToNext()
+ }
+ assertThat(cursor.isAfterLast).isTrue()
+ }
+
+ private fun SupportSQLiteDatabase.verifyWidgetsV4(widgets: List<FakeCommunalWidgetItemV4>) {
+ val cursor = query("SELECT * FROM communal_widget_table")
+ assertThat(cursor.moveToFirst()).isTrue()
+
+ widgets.forEach { widget ->
+ assertThat(cursor.getInt(cursor.getColumnIndex("widget_id"))).isEqualTo(widget.widgetId)
+ assertThat(cursor.getString(cursor.getColumnIndex("component_name")))
+ .isEqualTo(widget.componentName)
+ assertThat(cursor.getInt(cursor.getColumnIndex("item_id"))).isEqualTo(widget.itemId)
+ assertThat(cursor.getInt(cursor.getColumnIndex("user_serial_number")))
+ .isEqualTo(widget.userSerialNumber)
+ assertThat(cursor.getInt(cursor.getColumnIndex("span_y"))).isEqualTo(widget.spanY)
+
+ cursor.moveToNext()
+ }
+
+ assertThat(cursor.isAfterLast).isTrue()
+ }
+
private fun SupportSQLiteDatabase.insertRanks(ranks: List<FakeCommunalItemRank>) {
ranks.forEach { rank ->
execSQL("INSERT INTO communal_item_rank_table(rank) VALUES(${rank.rank})")
@@ -238,10 +315,27 @@
val userSerialNumber: Int,
)
- private data class FakeCommunalItemRank(
- val rank: Int,
+ private fun FakeCommunalWidgetItemV3.getV4(): FakeCommunalWidgetItemV4 {
+ return FakeCommunalWidgetItemV4(widgetId, componentName, itemId, userSerialNumber, 3)
+ }
+
+ private data class FakeCommunalWidgetItemV3(
+ val widgetId: Int,
+ val componentName: String,
+ val itemId: Int,
+ val userSerialNumber: Int,
)
+ private data class FakeCommunalWidgetItemV4(
+ val widgetId: Int,
+ val componentName: String,
+ val itemId: Int,
+ val userSerialNumber: Int,
+ val spanY: Int,
+ )
+
+ private data class FakeCommunalItemRank(val rank: Int)
+
companion object {
private const val DATABASE_NAME = "communal_db"
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
index 2f8f45c..0b9c06f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
@@ -901,7 +901,7 @@
// 1st time is onStart(), 2nd time is getActiveAutoSwitchNonDdsSubId()
verify(mTelephonyManager, times(2)).registerTelephonyCallback(any(), any());
- assertThat(mInternetDialogController.mSubIdTelephonyCallbackMap.size() == 2);
+ assertThat(mInternetDialogController.mSubIdTelephonyCallbackMap.size()).isEqualTo(2);
// Adds non DDS subId again
doReturn(SUB_ID2).when(info).getSubscriptionId();
@@ -912,7 +912,7 @@
// Does not add due to cached subInfo in mSubIdTelephonyCallbackMap.
verify(mTelephonyManager, times(2)).registerTelephonyCallback(any(), any());
- assertThat(mInternetDialogController.mSubIdTelephonyCallbackMap.size() == 2);
+ assertThat(mInternetDialogController.mSubIdTelephonyCallbackMap.size()).isEqualTo(2);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
index 148a2e5..52266ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
@@ -29,7 +29,6 @@
import java.util.UUID
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.kotlin.any
@@ -47,16 +46,11 @@
private val uiEventLogger = mock<UiEventLogger>()
private val actionsCallback = mock<ScreenshotActionsController.ActionsCallback>()
- private val request = ScreenshotData.forTesting()
+ private val request = ScreenshotData.forTesting(userHandle = UserHandle.OWNER)
private val validResult = ScreenshotSavedResult(Uri.EMPTY, Process.myUserHandle(), 0)
private lateinit var actionsProvider: ScreenshotActionsProvider
- @Before
- fun setUp() {
- request.userHandle = UserHandle.OWNER
- }
-
@Test
fun previewActionAccessed_beforeScreenshotCompleted_doesNothing() {
actionsProvider = createActionsProvider()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
index 15da77d..4000d6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
@@ -47,7 +47,7 @@
lateinit var screenshotView: ViewGroup
val userHandle = UserHandle.of(5)
- val screenshotData = ScreenshotData.forTesting()
+ val screenshotData = ScreenshotData.forTesting(userHandle = userHandle)
val appName = "app name"
lateinit var workProfileData: WorkProfileMessageController.WorkProfileFirstRunData
@@ -61,7 +61,7 @@
workProfileMessageController,
profileMessageController,
screenshotDetectionController,
- TestScope(UnconfinedTestDispatcher())
+ TestScope(UnconfinedTestDispatcher()),
)
screenshotView = ConstraintLayout(mContext)
workProfileData = WorkProfileMessageController.WorkProfileFirstRunData(appName, icon)
@@ -83,8 +83,6 @@
container.addView(detectionNoticeView)
messageContainer.setView(screenshotView)
-
- screenshotData.userHandle = userHandle
}
@Test
@@ -92,7 +90,7 @@
val profileData =
ProfileMessageController.ProfileFirstRunData(
LabeledIcon(appName, icon),
- ProfileMessageController.FirstRunProfile.PRIVATE
+ ProfileMessageController.FirstRunProfile.PRIVATE,
)
whenever(profileMessageController.onScreenshotTaken(eq(userHandle))).thenReturn(profileData)
messageContainer.onScreenshotTaken(screenshotData)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
index 1538c72..c5070286 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
@@ -61,8 +61,8 @@
@Test
fun testMaybeNotifyOfScreenshot_ignoresOverview() {
- val data = ScreenshotData.forTesting()
- data.source = WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW
+ val data =
+ ScreenshotData.forTesting(source = WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW)
val list = controller.maybeNotifyOfScreenshot(data)
@@ -72,8 +72,8 @@
@Test
fun testMaybeNotifyOfScreenshot_emptySet() {
- val data = ScreenshotData.forTesting()
- data.source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+ val data =
+ ScreenshotData.forTesting(source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD)
whenever(windowManager.notifyScreenshotListeners(eq(Display.DEFAULT_DISPLAY)))
.thenReturn(listOf())
@@ -85,8 +85,8 @@
@Test
fun testMaybeNotifyOfScreenshot_oneApp() {
- val data = ScreenshotData.forTesting()
- data.source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+ val data =
+ ScreenshotData.forTesting(source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD)
val component = ComponentName("package1", "class1")
val appName = "app name"
@@ -95,7 +95,7 @@
whenever(
packageManager.getActivityInfo(
eq(component),
- any(PackageManager.ComponentInfoFlags::class.java)
+ any(PackageManager.ComponentInfoFlags::class.java),
)
)
.thenReturn(activityInfo)
@@ -112,8 +112,8 @@
@Test
fun testMaybeNotifyOfScreenshot_multipleApps() {
- val data = ScreenshotData.forTesting()
- data.source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+ val data =
+ ScreenshotData.forTesting(source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD)
val component1 = ComponentName("package1", "class1")
val component2 = ComponentName("package2", "class2")
@@ -129,21 +129,21 @@
whenever(
packageManager.getActivityInfo(
eq(component1),
- any(PackageManager.ComponentInfoFlags::class.java)
+ any(PackageManager.ComponentInfoFlags::class.java),
)
)
.thenReturn(activityInfo1)
whenever(
packageManager.getActivityInfo(
eq(component2),
- any(PackageManager.ComponentInfoFlags::class.java)
+ any(PackageManager.ComponentInfoFlags::class.java),
)
)
.thenReturn(activityInfo2)
whenever(
packageManager.getActivityInfo(
eq(component3),
- any(PackageManager.ComponentInfoFlags::class.java)
+ any(PackageManager.ComponentInfoFlags::class.java),
)
)
.thenReturn(activityInfo3)
@@ -165,11 +165,13 @@
private fun includesFlagBits(@PackageManager.ComponentInfoFlagsBits mask: Int) =
ComponentInfoFlagMatcher(mask, mask)
+
private fun excludesFlagBits(@PackageManager.ComponentInfoFlagsBits mask: Int) =
ComponentInfoFlagMatcher(mask, 0)
private class ComponentInfoFlagMatcher(
- @PackageManager.ComponentInfoFlagsBits val mask: Int, val value: Int
+ @PackageManager.ComponentInfoFlagsBits val mask: Int,
+ val value: Int,
) : ArgumentMatcher<PackageManager.ComponentInfoFlags> {
override fun matches(flags: PackageManager.ComponentInfoFlags?): Boolean {
return flags != null && (mask.toLong() and flags.value) == value.toLong()
@@ -182,26 +184,28 @@
@Test
fun testMaybeNotifyOfScreenshot_disabledApp() {
- val data = ScreenshotData.forTesting()
- data.source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+ val data =
+ ScreenshotData.forTesting(source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD)
val component = ComponentName("package1", "class1")
val appName = "app name"
val activityInfo = mock(ActivityInfo::class.java)
whenever(
- packageManager.getActivityInfo(
- eq(component),
- argThat(includesFlagBits(MATCH_DISABLED_COMPONENTS or MATCH_ANY_USER))
+ packageManager.getActivityInfo(
+ eq(component),
+ argThat(includesFlagBits(MATCH_DISABLED_COMPONENTS or MATCH_ANY_USER)),
+ )
)
- ).thenReturn(activityInfo)
+ .thenReturn(activityInfo)
whenever(
- packageManager.getActivityInfo(
- eq(component),
- argThat(excludesFlagBits(MATCH_DISABLED_COMPONENTS))
+ packageManager.getActivityInfo(
+ eq(component),
+ argThat(excludesFlagBits(MATCH_DISABLED_COMPONENTS)),
+ )
)
- ).thenThrow(PackageManager.NameNotFoundException::class.java)
+ .thenThrow(PackageManager.NameNotFoundException::class.java)
whenever(windowManager.notifyScreenshotListeners(eq(Display.DEFAULT_DISPLAY)))
.thenReturn(listOf(component))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index 15705fb..0bea560 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -257,6 +257,58 @@
}
@Test
+ @EnableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
+ fun executeScreenshots_fromOverview_honorsDisplay() =
+ testScope.runTest {
+ val displayId = 1
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = displayId))
+ val onSaved = { _: Uri? -> }
+ screenshotExecutor.executeScreenshots(
+ createScreenshotRequest(
+ displayId = displayId,
+ source = WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW,
+ ),
+ onSaved,
+ callback,
+ )
+
+ val dataCaptor = ArgumentCaptor<ScreenshotData>()
+
+ verify(controller).handleScreenshot(dataCaptor.capture(), any(), any())
+
+ assertThat(dataCaptor.value.displayId).isEqualTo(displayId)
+
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
+ fun executeScreenshots_fromOverviewInvalidDisplay_usesDefault() =
+ testScope.runTest {
+ setDisplays(
+ display(TYPE_INTERNAL, id = Display.DEFAULT_DISPLAY),
+ display(TYPE_EXTERNAL, id = 1),
+ )
+ val onSaved = { _: Uri? -> }
+ screenshotExecutor.executeScreenshots(
+ createScreenshotRequest(
+ displayId = 5,
+ source = WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW,
+ ),
+ onSaved,
+ callback,
+ )
+
+ val dataCaptor = ArgumentCaptor<ScreenshotData>()
+
+ verify(controller).handleScreenshot(dataCaptor.capture(), any(), any())
+
+ assertThat(dataCaptor.value.displayId).isEqualTo(Display.DEFAULT_DISPLAY)
+
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
fun onDestroy_propagatedToControllers() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
@@ -527,9 +579,14 @@
runCurrent()
}
- private fun createScreenshotRequest(type: Int = WindowManager.TAKE_SCREENSHOT_FULLSCREEN) =
- ScreenshotRequest.Builder(type, WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER)
+ private fun createScreenshotRequest(
+ type: Int = WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
+ source: Int = WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER,
+ displayId: Int = Display.DEFAULT_DISPLAY,
+ ) =
+ ScreenshotRequest.Builder(type, source)
.setTopComponent(topComponent)
+ .setDisplayId(displayId)
.also {
if (type == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
it.setBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt
new file mode 100644
index 0000000..040a9e9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.clocks.FontTextStyle
+import com.android.systemui.shared.clocks.LogUtil
+import com.android.systemui.shared.clocks.view.SimpleDigitalClockTextView
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class SimpleDigitalClockTextViewTest : SysuiTestCase() {
+ private val messageBuffer = LogUtil.DEBUG_MESSAGE_BUFFER
+ private lateinit var underTest: SimpleDigitalClockTextView
+ private val defaultLargeClockTextSize = 500F
+ private val smallerTextSize = 300F
+ private val largerTextSize = 800F
+ private val firstMeasureTextSize = 100F
+
+ @Before
+ fun setup() {
+ underTest = SimpleDigitalClockTextView(context, messageBuffer)
+ underTest.textStyle = FontTextStyle()
+ underTest.aodStyle = FontTextStyle()
+ underTest.text = "0"
+ underTest.applyTextSize(defaultLargeClockTextSize)
+ }
+
+ @Test
+ fun applySmallerConstrainedTextSize_applyConstrainedTextSize() {
+ underTest.applyTextSize(smallerTextSize, constrainedByHeight = true)
+ assertEquals(smallerTextSize, underTest.textSize * underTest.fontSizeAdjustFactor)
+ }
+
+ @Test
+ fun applyLargerConstrainedTextSize_applyUnconstrainedTextSize() {
+ underTest.applyTextSize(largerTextSize, constrainedByHeight = true)
+ assertEquals(defaultLargeClockTextSize, underTest.textSize)
+ }
+
+ @Test
+ fun applyFirstMeasureConstrainedTextSize_getConstrainedTextSize() {
+ underTest.applyTextSize(firstMeasureTextSize, constrainedByHeight = true)
+ underTest.applyTextSize(smallerTextSize, constrainedByHeight = true)
+ assertEquals(smallerTextSize, underTest.textSize * underTest.fontSizeAdjustFactor)
+ }
+
+ @Test
+ fun applySmallFirstMeasureConstrainedSizeAndLargerConstrainedTextSize_applyDefaultSize() {
+ underTest.applyTextSize(firstMeasureTextSize, constrainedByHeight = true)
+ underTest.applyTextSize(largerTextSize, constrainedByHeight = true)
+ assertEquals(defaultLargeClockTextSize, underTest.textSize)
+ }
+
+ @Test
+ fun applyFirstMeasureConstrainedTextSize_applyUnconstrainedTextSize() {
+ underTest.applyTextSize(firstMeasureTextSize, constrainedByHeight = true)
+ underTest.applyTextSize(defaultLargeClockTextSize)
+ assertEquals(defaultLargeClockTextSize, underTest.textSize)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index 98315d0c..83dbfa0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -95,6 +95,7 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
public class NetworkControllerBaseTest extends SysuiTestCase {
@@ -332,10 +333,15 @@
}
public void setConnectivityViaCallbackInNetworkControllerForVcn(
- int networkType, boolean validated, boolean isConnected, VcnTransportInfo info) {
+ int networkType,
+ boolean validated,
+ boolean isConnected,
+ VcnTransportInfo info,
+ Network underlyingNetwork) {
final NetworkCapabilities.Builder builder =
new NetworkCapabilities.Builder(mNetCapabilities);
- builder.setTransportInfo(info);
+ builder.setTransportInfo(info)
+ .setUnderlyingNetworks(Collections.singletonList(underlyingNetwork));
setConnectivityCommon(builder, networkType, validated, isConnected);
mDefaultCallbackInNetworkController.onCapabilitiesChanged(
mock(Network.class), builder.build());
@@ -385,10 +391,15 @@
}
public void setConnectivityViaCallbackInWifiTrackerForVcn(
- int networkType, boolean validated, boolean isConnected, VcnTransportInfo info) {
+ int networkType,
+ boolean validated,
+ boolean isConnected,
+ VcnTransportInfo info,
+ Network underlyingNetwork) {
final NetworkCapabilities.Builder builder =
new NetworkCapabilities.Builder(mNetCapabilities);
- builder.setTransportInfo(info);
+ builder.setTransportInfo(info)
+ .setUnderlyingNetworks(Collections.singletonList(underlyingNetwork));
setConnectivityCommon(builder, networkType, validated, isConnected);
if (networkType == NetworkCapabilities.TRANSPORT_CELLULAR) {
if (isConnected) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
index 6c80a97..6febb91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
@@ -18,6 +18,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static junit.framework.Assert.assertEquals;
@@ -250,6 +251,17 @@
assertEquals(testSsid, mNetworkController.mWifiSignalController.mCurrentState.ssid);
}
+ private Network newWifiNetwork(WifiInfo wifiInfo) {
+ final Network network = mock(Network.class);
+ final NetworkCapabilities wifiCaps =
+ new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .setTransportInfo(wifiInfo)
+ .build();
+ when(mMockCm.getNetworkCapabilities(network)).thenReturn(wifiCaps);
+ return network;
+ }
+
@Test
public void testVcnWithUnderlyingWifi() {
String testSsid = "Test VCN SSID";
@@ -266,11 +278,19 @@
setWifiLevelForVcn(testLevel);
setConnectivityViaCallbackInNetworkControllerForVcn(
- NetworkCapabilities.TRANSPORT_CELLULAR, true, true, mVcnTransportInfo);
+ NetworkCapabilities.TRANSPORT_CELLULAR,
+ true,
+ true,
+ mVcnTransportInfo,
+ newWifiNetwork(mWifiInfo));
verifyLastMobileDataIndicatorsForVcn(true, testLevel, TelephonyIcons.ICON_CWF, true);
setConnectivityViaCallbackInNetworkControllerForVcn(
- NetworkCapabilities.TRANSPORT_CELLULAR, false, true, mVcnTransportInfo);
+ NetworkCapabilities.TRANSPORT_CELLULAR,
+ false,
+ true,
+ mVcnTransportInfo,
+ newWifiNetwork(mWifiInfo));
verifyLastMobileDataIndicatorsForVcn(true, testLevel, TelephonyIcons.ICON_CWF, false);
}
}
@@ -391,13 +411,15 @@
}
protected void setWifiLevelForVcn(int level) {
- when(mVcnTransportInfo.getWifiInfo()).thenReturn(mWifiInfo);
- when(mVcnTransportInfo.makeCopy(anyLong())).thenReturn(mVcnTransportInfo);
when(mWifiInfo.getRssi()).thenReturn(calculateRssiForLevel(level));
when(mWifiInfo.isCarrierMerged()).thenReturn(true);
when(mWifiInfo.getSubscriptionId()).thenReturn(1);
setConnectivityViaCallbackInWifiTrackerForVcn(
- NetworkCapabilities.TRANSPORT_CELLULAR, false, true, mVcnTransportInfo);
+ NetworkCapabilities.TRANSPORT_CELLULAR,
+ false,
+ true,
+ mVcnTransportInfo,
+ newWifiNetwork(mWifiInfo));
}
private int calculateRssiForLevel(int level) {
@@ -409,13 +431,15 @@
}
protected void setWifiStateForVcn(boolean connected, String ssid) {
- when(mVcnTransportInfo.getWifiInfo()).thenReturn(mWifiInfo);
- when(mVcnTransportInfo.makeCopy(anyLong())).thenReturn(mVcnTransportInfo);
when(mWifiInfo.getSSID()).thenReturn(ssid);
when(mWifiInfo.isCarrierMerged()).thenReturn(true);
when(mWifiInfo.getSubscriptionId()).thenReturn(1);
setConnectivityViaCallbackInWifiTrackerForVcn(
- NetworkCapabilities.TRANSPORT_CELLULAR, false, connected, mVcnTransportInfo);
+ NetworkCapabilities.TRANSPORT_CELLULAR,
+ false,
+ connected,
+ mVcnTransportInfo,
+ newWifiNetwork(mWifiInfo));
}
protected void verifyLastQsDataDirection(boolean in, boolean out) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 7a8533e..fe287ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -23,6 +23,7 @@
import android.app.smartspace.SmartspaceTarget
import android.content.ComponentName
import android.content.ContentResolver
+import android.content.Context
import android.content.pm.UserInfo
import android.database.ContentObserver
import android.graphics.drawable.Drawable
@@ -207,6 +208,9 @@
private val userHandleManaged: UserHandle = UserHandle(2)
private val userHandleSecondary: UserHandle = UserHandle(3)
+ @Mock private lateinit var userContextPrimary: Context
+ @Mock private lateinit var userContextSecondary: Context
+
private val userList = listOf(
mockUserInfo(userHandlePrimary, isManagedProfile = false),
mockUserInfo(userHandleManaged, isManagedProfile = true),
@@ -234,7 +238,11 @@
`when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
`when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
- setActiveUser(userHandlePrimary)
+ `when`(userContextPrimary.getSystemService(SmartspaceManager::class.java)).thenReturn(
+ smartspaceManager
+ )
+
+ setActiveUser(userHandlePrimary, userContextPrimary)
setAllowPrivateNotifications(userHandlePrimary, true)
setAllowPrivateNotifications(userHandleManaged, true)
setAllowPrivateNotifications(userHandleSecondary, true)
@@ -252,7 +260,6 @@
controller = LockscreenSmartspaceController(
context,
featureFlags,
- smartspaceManager,
activityStarter,
falsingManager,
clock,
@@ -709,7 +716,8 @@
connectSession()
// WHEN the secondary user becomes the active user
- setActiveUser(userHandleSecondary)
+ // Note: it doesn't switch to the SmartspaceManager for userContextSecondary
+ setActiveUser(userHandleSecondary, userContextSecondary)
userListener.onUserChanged(userHandleSecondary.identifier, context)
// WHEN we receive a new list of targets
@@ -912,9 +920,10 @@
clearInvocations(smartspaceView)
}
- private fun setActiveUser(userHandle: UserHandle) {
+ private fun setActiveUser(userHandle: UserHandle, userContext: Context) {
`when`(userTracker.userId).thenReturn(userHandle.identifier)
`when`(userTracker.userHandle).thenReturn(userHandle)
+ `when`(userTracker.userContext).thenReturn(userContext)
}
private fun mockUserInfo(userHandle: UserHandle, isManagedProfile: Boolean): UserInfo {
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 2ed3473..8360042 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
@@ -160,11 +160,11 @@
testableLooper = TestableLooper.get(this)
context.orCreateTestableResources.addOverride(
com.android.internal.R.string.status_bar_alarm_clock,
- ALARM_SLOT
+ ALARM_SLOT,
)
context.orCreateTestableResources.addOverride(
com.android.internal.R.string.status_bar_managed_profile,
- MANAGED_PROFILE_SLOT
+ MANAGED_PROFILE_SLOT,
)
whenever(devicePolicyManager.resources).thenReturn(devicePolicyManagerResources)
whenever(devicePolicyManagerResources.getString(anyString(), any())).thenReturn("")
@@ -430,8 +430,8 @@
eq(mContext.packageName),
eq(android.R.drawable.ic_lock_lock),
any(), // non-null
- eq("Bedtime Mode"),
- eq(StatusBarIcon.Shape.FIXED_SPACE)
+ eq("Bedtime Mode is on"),
+ eq(StatusBarIcon.Shape.FIXED_SPACE),
)
zenModeRepository.deactivateMode("bedtime")
@@ -443,8 +443,8 @@
eq(null),
eq(android.R.drawable.ic_media_play),
any(), // non-null
- eq("Other Mode"),
- eq(StatusBarIcon.Shape.FIXED_SPACE)
+ eq("Other Mode is on"),
+ eq(StatusBarIcon.Shape.FIXED_SPACE),
)
zenModeRepository.deactivateMode("other")
@@ -538,7 +538,7 @@
privacyLogger,
fakeConnectedDisplayStateProvider,
kosmos.zenModeInteractor,
- JavaAdapter(testScope.backgroundScope)
+ JavaAdapter(testScope.backgroundScope),
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index c523819..81c40dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -36,7 +36,9 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.ActionClickLogger;
@@ -50,8 +52,11 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.FakeSystemClock;
+import dagger.Lazy;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -71,8 +76,14 @@
@Mock private SysuiStatusBarStateController mStatusBarStateController;
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Mock private ActivityStarter mActivityStarter;
+ @Mock private Lazy<DeviceUnlockedInteractor> mDeviceUnlockedInteractorLazy;
+ @Mock private Lazy<SceneInteractor> mSceneInteractorLazy;
+ @Mock private JavaAdapter mJavaAdapter;
private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+ @Mock private DeviceUnlockedInteractor mDeviceUnlockedInteractor;
+ @Mock private SceneInteractor mSceneInteractor;
+
private int mCurrentUserId = 0;
private StatusBarRemoteInputCallback mRemoteInputCallback;
@@ -90,7 +101,8 @@
mKeyguardStateController, mStatusBarStateController, mStatusBarKeyguardViewManager,
mActivityStarter, mShadeController,
new CommandQueue(mContext, new FakeDisplayTracker(mContext)),
- mock(ActionClickLogger.class), mFakeExecutor));
+ mock(ActionClickLogger.class), mFakeExecutor, mDeviceUnlockedInteractorLazy,
+ mSceneInteractorLazy, mJavaAdapter));
mRemoteInputCallback.mChallengeReceiver = mRemoteInputCallback.new ChallengeReceiver();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index f62beeb..beba0f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -133,10 +133,6 @@
when(mRingerModeInternalLiveData.getValue()).thenReturn(-1);
when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
when(mUserTracker.getUserContext()).thenReturn(mContext);
- // Enable group volume adjustments
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions,
- true);
mCallback = mock(VolumeDialogControllerImpl.C.class);
mThreadFactory.setLooper(TestableLooper.get(this).getLooper());
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index 5d7e7c7..1302faa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -31,7 +31,7 @@
provider: ComponentName,
user: UserHandle,
rank: Int?,
- configurator: WidgetConfigurator?
+ configurator: WidgetConfigurator?,
) {
coroutineScope.launch {
val id = nextWidgetId++
@@ -93,6 +93,22 @@
_communalWidgets.value = fakeDatabase.values.toList()
}
+ override fun updateWidgetSpanY(widgetId: Int, spanY: Int) {
+ coroutineScope.launch {
+ fakeDatabase[widgetId]?.let { widget ->
+ when (widget) {
+ is CommunalWidgetContentModel.Available -> {
+ fakeDatabase[widgetId] = widget.copy(spanY = spanY)
+ }
+ is CommunalWidgetContentModel.Pending -> {
+ fakeDatabase[widgetId] = widget.copy(spanY = spanY)
+ }
+ }
+ _communalWidgets.value = fakeDatabase.values.toList()
+ }
+ }
+ }
+
override fun restoreWidgets(oldToNewWidgetIdMap: Map<Int, Int>) {}
override fun abortRestoreWidgets() {}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeKeyboardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeKeyboardRepository.kt
index b37cac1..ba31683 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeKeyboardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeKeyboardRepository.kt
@@ -19,8 +19,10 @@
import com.android.systemui.keyboard.data.model.Keyboard
import com.android.systemui.keyboard.shared.model.BacklightModel
+import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.filterNotNull
class FakeKeyboardRepository : KeyboardRepository {
@@ -32,8 +34,14 @@
// filtering to make sure backlight doesn't have default initial value
override val backlight: Flow<BacklightModel> = _backlightState.filterNotNull()
- private val _newlyConnectedKeyboard: MutableStateFlow<Keyboard?> = MutableStateFlow(null)
- override val newlyConnectedKeyboard: Flow<Keyboard> = _newlyConnectedKeyboard.filterNotNull()
+ // implemented as channel because original implementation is modeling events: it doesn't hold
+ // state so it won't always emit once connected. And it's bad if some tests depend on that
+ // incorrect behaviour.
+ private val _newlyConnectedKeyboard: Channel<Keyboard> = Channel()
+ override val newlyConnectedKeyboard: Flow<Keyboard> = _newlyConnectedKeyboard.consumeAsFlow()
+
+ private val _connectedKeyboards: MutableStateFlow<Set<Keyboard>> = MutableStateFlow(setOf())
+ override val connectedKeyboards: Flow<Set<Keyboard>> = _connectedKeyboards
fun setBacklight(state: BacklightModel) {
_backlightState.value = state
@@ -43,7 +51,14 @@
_isAnyKeyboardConnected.value = connected
}
+ fun setConnectedKeyboards(keyboards: Set<Keyboard>) {
+ _connectedKeyboards.value = keyboards
+ _isAnyKeyboardConnected.value = keyboards.isNotEmpty()
+ }
+
fun setNewlyConnectedKeyboard(keyboard: Keyboard) {
- _newlyConnectedKeyboard.value = keyboard
+ _newlyConnectedKeyboard.trySend(keyboard)
+ _connectedKeyboards.value += keyboard
+ _isAnyKeyboardConnected.value = true
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
index f26bb83..805a710 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
@@ -35,6 +35,8 @@
private val _revealAmount: MutableStateFlow<Float> = MutableStateFlow(0.0f)
override val revealAmount: Flow<Float> = _revealAmount
+ val revealAnimatorRequests: MutableList<RevealAnimatorRequest> = arrayListOf()
+
override val isAnimating: Boolean
get() = false
@@ -44,5 +46,12 @@
} else {
_revealAmount.value = 0.0f
}
+
+ revealAnimatorRequests.add(RevealAnimatorRequest(reveal, duration))
}
+
+ data class RevealAnimatorRequest(
+ val reveal: Boolean,
+ val duration: Long
+ )
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index f842db4..020f0a6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -8,17 +8,14 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.domain.interactor.systemGestureExclusionInteractor
import com.android.systemui.scene.shared.logger.sceneLogger
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.FakeOverlay
-import com.android.systemui.scene.ui.viewmodel.SceneContainerGestureFilter
import com.android.systemui.scene.ui.viewmodel.SceneContainerHapticsViewModel
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector
-import com.android.systemui.settings.displayTracker
import com.android.systemui.shade.domain.interactor.shadeInteractor
import kotlinx.coroutines.flow.MutableStateFlow
import org.mockito.kotlin.mock
@@ -72,16 +69,15 @@
}
val Kosmos.sceneContainerViewModel by Fixture {
- sceneContainerViewModelFactory.create(mock<View>(), displayTracker.defaultDisplayId, {}).apply {
- setTransitionState(transitionState)
- }
+ sceneContainerViewModelFactory
+ .create(mock<View>()) {}
+ .apply { setTransitionState(transitionState) }
}
val Kosmos.sceneContainerViewModelFactory by Fixture {
object : SceneContainerViewModel.Factory {
override fun create(
view: View,
- displayId: Int,
motionEventHandlerReceiver: (SceneContainerViewModel.MotionEventHandler?) -> Unit,
): SceneContainerViewModel =
SceneContainerViewModel(
@@ -91,26 +87,13 @@
shadeInteractor = shadeInteractor,
splitEdgeDetector = splitEdgeDetector,
logger = sceneLogger,
- gestureFilterFactory = sceneContainerGestureFilterFactory,
hapticsViewModelFactory = sceneContainerHapticsViewModelFactory,
view = view,
- displayId = displayId,
motionEventHandlerReceiver = motionEventHandlerReceiver,
)
}
}
-val Kosmos.sceneContainerGestureFilterFactory by Fixture {
- object : SceneContainerGestureFilter.Factory {
- override fun create(displayId: Int): SceneContainerGestureFilter {
- return SceneContainerGestureFilter(
- interactor = systemGestureExclusionInteractor,
- displayId = displayId,
- )
- }
- }
-}
-
val Kosmos.sceneContainerHapticsViewModelFactory by Fixture {
object : SceneContainerHapticsViewModel.Factory {
override fun create(view: View): SceneContainerHapticsViewModel {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
deleted file mode 100644
index 15ed1b3..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.data.repository
-
-import android.view.windowManagerService
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-
-val Kosmos.systemGestureExclusionRepository by Fixture {
- SystemGestureExclusionRepository(windowManager = windowManagerService)
-}
diff --git a/packages/SystemUI/frp/Android.bp b/packages/SystemUI/utils/kairos/Android.bp
similarity index 88%
rename from packages/SystemUI/frp/Android.bp
rename to packages/SystemUI/utils/kairos/Android.bp
index c3381db..1442591 100644
--- a/packages/SystemUI/frp/Android.bp
+++ b/packages/SystemUI/utils/kairos/Android.bp
@@ -20,9 +20,9 @@
}
java_library {
- name: "kt-frp",
+ name: "kairos",
host_supported: true,
- kotlincflags: ["-opt-in=com.android.systemui.experimental.frp.ExperimentalFrpApi"],
+ kotlincflags: ["-opt-in=com.android.systemui.kairos.ExperimentalFrpApi"],
srcs: ["src/**/*.kt"],
static_libs: [
"kotlin-stdlib",
@@ -31,7 +31,7 @@
}
java_test {
- name: "kt-frp-test",
+ name: "kairos-test",
optimize: {
enabled: false,
},
@@ -39,7 +39,7 @@
"test/**/*.kt",
],
static_libs: [
- "kt-frp",
+ "kairos",
"junit",
"kotlin-stdlib",
"kotlin-test",
diff --git a/packages/SystemUI/frp/OWNERS b/packages/SystemUI/utils/kairos/OWNERS
similarity index 100%
rename from packages/SystemUI/frp/OWNERS
rename to packages/SystemUI/utils/kairos/OWNERS
diff --git a/packages/SystemUI/frp/README.md b/packages/SystemUI/utils/kairos/README.md
similarity index 89%
rename from packages/SystemUI/frp/README.md
rename to packages/SystemUI/utils/kairos/README.md
index 9c5bdb0..85f622c 100644
--- a/packages/SystemUI/frp/README.md
+++ b/packages/SystemUI/utils/kairos/README.md
@@ -1,4 +1,4 @@
-# kt-frp
+# Kairos
A functional reactive programming (FRP) library for Kotlin.
@@ -13,12 +13,12 @@
### Details for nerds
-`kt-frp` implements an applicative / monadic flavor of FRP, using a push-pull
+`Kairos` implements an applicative / monadic flavor of FRP, using a push-pull
methodology to allow for efficient updates.
"Real" functional reactive programming should be specified with denotational
semantics ([wikipedia](https://en.wikipedia.org/wiki/Denotational_semantics)):
-you can view the semantics for `kt-frp` [here](docs/semantics.md).
+you can view the semantics for `Kairos` [here](docs/semantics.md).
## Usage
@@ -61,4 +61,4 @@
## Resources
-- [Cheatsheet for those coming from Kotlin Flow](docs/flow-to-frp-cheatsheet.md)
+- [Cheatsheet for those coming from Kotlin Flow](docs/flow-to-kairos-cheatsheet.md)
diff --git a/packages/SystemUI/frp/docs/flow-to-frp-cheatsheet.md b/packages/SystemUI/utils/kairos/docs/flow-to-kairos-cheatsheet.md
similarity index 90%
rename from packages/SystemUI/frp/docs/flow-to-frp-cheatsheet.md
rename to packages/SystemUI/utils/kairos/docs/flow-to-kairos-cheatsheet.md
index e20f3e6..9f7fd02 100644
--- a/packages/SystemUI/frp/docs/flow-to-frp-cheatsheet.md
+++ b/packages/SystemUI/utils/kairos/docs/flow-to-kairos-cheatsheet.md
@@ -1,27 +1,27 @@
-# From Flows to FRP
+# From Flows to Kairos
## Key differences
-* FRP evaluates all events (`TFlow` emissions + observers) in a transaction.
+* Kairos evaluates all events (`TFlow` emissions + observers) in a transaction.
-* FRP splits `Flow` APIs into two distinct types: `TFlow` and `TState`
+* Kairos splits `Flow` APIs into two distinct types: `TFlow` and `TState`
* `TFlow` is roughly equivalent to `SharedFlow` w/ a replay cache that
- exists for the duration of the current FRP transaction and shared with
+ exists for the duration of the current Kairos transaction and shared with
`SharingStarted.WhileSubscribed()`
* `TState` is roughly equivalent to `StateFlow` shared with
`SharingStarted.Eagerly`, but the current value can only be queried within
- a FRP transaction, and the value is only updated at the end of the
+ a Kairos transaction, and the value is only updated at the end of the
transaction
-* FRP further divides `Flow` APIs based on how they internally use state:
+* Kairos further divides `Flow` APIs based on how they internally use state:
* **FrpTransactionScope:** APIs that internally query some state need to be
- performed within an FRP transaction
+ performed within an Kairos transaction
* this scope is available from the other scopes, and from most lambdas
- passed to other FRP APIs
+ passed to other Kairos APIs
* **FrpStateScope:** APIs that internally accumulate state in reaction to
events need to be performed within an FRP State scope (akin to a
@@ -126,8 +126,8 @@
developers generally append `.onStart { emit(initialValue) }` to the `Flows`
that don't immediately emit.
-FRP avoids this gotcha by forcing usage of `TState` for `combine`, thus ensuring
-that there is always a current value to be combined for each input.
+Kairos avoids this gotcha by forcing usage of `TState` for `combine`, thus
+ensuring that there is always a current value to be combined for each input.
## collect { … }
@@ -158,8 +158,8 @@
#### Explanation
To keep all state-reads consistent, the current value of a TState can only be
-queried within an FRP transaction, modeled with `FrpTransactionScope`. Note that
-both `FrpStateScope` and `FrpBuildScope` extend `FrpTransactionScope`.
+queried within a Kairos transaction, modeled with `FrpTransactionScope`. Note
+that both `FrpStateScope` and `FrpBuildScope` extend `FrpTransactionScope`.
### I want to sample a TFlow
@@ -198,7 +198,7 @@
#### Explanation
-Under FRP's rules, a `TFlow` may only emit up to once per transaction. This
+Under Kairos's rules, a `TFlow` may only emit up to once per transaction. This
means that if we are merging two or more `TFlows` that are emitting at the same
time (within the same transaction), the resulting merged `TFlow` must emit a
single value. The lambda argument allows the developer to decide what to do in
diff --git a/packages/SystemUI/frp/docs/semantics.md b/packages/SystemUI/utils/kairos/docs/semantics.md
similarity index 96%
rename from packages/SystemUI/frp/docs/semantics.md
rename to packages/SystemUI/utils/kairos/docs/semantics.md
index b533190..d43bb44 100644
--- a/packages/SystemUI/frp/docs/semantics.md
+++ b/packages/SystemUI/utils/kairos/docs/semantics.md
@@ -1,11 +1,11 @@
# FRP Semantics
-`kt-frp`'s pure API is based off of the following denotational semantics
+`Kairos`'s pure API is based off of the following denotational semantics
([wikipedia](https://en.wikipedia.org/wiki/Denotational_semantics)).
-The semantics model `kt-frp` types as time-varying values; by making `Time` a
+The semantics model `Kairos` types as time-varying values; by making `Time` a
first-class value, we can define a referentially-transparent API that allows us
-to reason about the behavior of the pure FRP combinators. This is
+to reason about the behavior of the pure `Kairos` combinators. This is
implementation-agnostic; we can compare the behavior of any implementation with
expected behavior denoted by these semantics to identify bugs.
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/Combinators.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Combinators.kt
similarity index 97%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/Combinators.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Combinators.kt
index 298c071..8bf3a43 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/Combinators.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Combinators.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp
+package com.android.systemui.kairos
-import com.android.systemui.experimental.frp.util.These
-import com.android.systemui.experimental.frp.util.just
-import com.android.systemui.experimental.frp.util.none
+import com.android.systemui.kairos.util.These
+import com.android.systemui.kairos.util.just
+import com.android.systemui.kairos.util.none
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.conflate
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpBuildScope.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpBuildScope.kt
similarity index 98%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpBuildScope.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpBuildScope.kt
index 6e4c9eb..4de6deb 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpBuildScope.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpBuildScope.kt
@@ -16,11 +16,11 @@
@file:OptIn(ExperimentalCoroutinesApi::class)
-package com.android.systemui.experimental.frp
+package com.android.systemui.kairos
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.just
-import com.android.systemui.experimental.frp.util.map
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.just
+import com.android.systemui.kairos.util.map
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.RestrictsSuspension
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpEffectScope.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpEffectScope.kt
similarity index 97%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpEffectScope.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpEffectScope.kt
index a8ec98f..be2eb43 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpEffectScope.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpEffectScope.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp
+package com.android.systemui.kairos
import kotlin.coroutines.RestrictsSuspension
import kotlinx.coroutines.CoroutineScope
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpNetwork.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpNetwork.kt
similarity index 94%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpNetwork.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpNetwork.kt
index acc76d9..b688eaf 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpNetwork.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpNetwork.kt
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp
+package com.android.systemui.kairos
-import com.android.systemui.experimental.frp.internal.BuildScopeImpl
-import com.android.systemui.experimental.frp.internal.Network
-import com.android.systemui.experimental.frp.internal.StateScopeImpl
-import com.android.systemui.experimental.frp.internal.util.awaitCancellationAndThen
-import com.android.systemui.experimental.frp.internal.util.childScope
+import com.android.systemui.kairos.internal.BuildScopeImpl
+import com.android.systemui.kairos.internal.Network
+import com.android.systemui.kairos.internal.StateScopeImpl
+import com.android.systemui.kairos.internal.util.awaitCancellationAndThen
+import com.android.systemui.kairos.internal.util.childScope
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.coroutineContext
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpScope.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpScope.kt
similarity index 97%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpScope.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpScope.kt
index a5a7977..ad6b2c8 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpScope.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpScope.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp
+package com.android.systemui.kairos
import kotlin.coroutines.RestrictsSuspension
import kotlin.coroutines.resume
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpStateScope.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpStateScope.kt
similarity index 97%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpStateScope.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpStateScope.kt
index 61336f4..c7ea680 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpStateScope.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpStateScope.kt
@@ -14,20 +14,20 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp
+package com.android.systemui.kairos
-import com.android.systemui.experimental.frp.combine as combinePure
-import com.android.systemui.experimental.frp.map as mapPure
-import com.android.systemui.experimental.frp.util.Just
-import com.android.systemui.experimental.frp.util.Left
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.Right
-import com.android.systemui.experimental.frp.util.WithPrev
-import com.android.systemui.experimental.frp.util.just
-import com.android.systemui.experimental.frp.util.map
-import com.android.systemui.experimental.frp.util.none
-import com.android.systemui.experimental.frp.util.partitionEithers
-import com.android.systemui.experimental.frp.util.zipWith
+import com.android.systemui.kairos.combine as combinePure
+import com.android.systemui.kairos.map as mapPure
+import com.android.systemui.kairos.util.Just
+import com.android.systemui.kairos.util.Left
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.Right
+import com.android.systemui.kairos.util.WithPrev
+import com.android.systemui.kairos.util.just
+import com.android.systemui.kairos.util.map
+import com.android.systemui.kairos.util.none
+import com.android.systemui.kairos.util.partitionEithers
+import com.android.systemui.kairos.util.zipWith
import kotlin.coroutines.RestrictsSuspension
typealias FrpStateful<R> = suspend FrpStateScope.() -> R
@@ -715,7 +715,7 @@
stateB: TState<B>,
transform: suspend FrpTransactionScope.(A, B) -> Z,
): TState<Z> =
- com.android.systemui.experimental.frp
+ com.android.systemui.kairos
.combine(stateA, stateB) { a, b -> transactionally { transform(a, b) } }
.sampleTransactionals()
@@ -733,7 +733,7 @@
stateD: TState<D>,
transform: suspend FrpTransactionScope.(A, B, C, D) -> Z,
): TState<Z> =
- com.android.systemui.experimental.frp
+ com.android.systemui.kairos
.combine(stateA, stateB, stateC, stateD) { a, b, c, d ->
transactionally { transform(a, b, c, d) }
}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpTransactionScope.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpTransactionScope.kt
similarity index 97%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpTransactionScope.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpTransactionScope.kt
index b0b9dbc..a7ae1d9 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpTransactionScope.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpTransactionScope.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp
+package com.android.systemui.kairos
import kotlin.coroutines.RestrictsSuspension
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/TFlow.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TFlow.kt
similarity index 90%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/TFlow.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TFlow.kt
index cca6c9a..7ba1aca 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/TFlow.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TFlow.kt
@@ -14,36 +14,36 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp
+package com.android.systemui.kairos
-import com.android.systemui.experimental.frp.internal.DemuxImpl
-import com.android.systemui.experimental.frp.internal.Init
-import com.android.systemui.experimental.frp.internal.InitScope
-import com.android.systemui.experimental.frp.internal.InputNode
-import com.android.systemui.experimental.frp.internal.Network
-import com.android.systemui.experimental.frp.internal.NoScope
-import com.android.systemui.experimental.frp.internal.TFlowImpl
-import com.android.systemui.experimental.frp.internal.activated
-import com.android.systemui.experimental.frp.internal.cached
-import com.android.systemui.experimental.frp.internal.constInit
-import com.android.systemui.experimental.frp.internal.filterNode
-import com.android.systemui.experimental.frp.internal.init
-import com.android.systemui.experimental.frp.internal.map
-import com.android.systemui.experimental.frp.internal.mapImpl
-import com.android.systemui.experimental.frp.internal.mapMaybeNode
-import com.android.systemui.experimental.frp.internal.mergeNodes
-import com.android.systemui.experimental.frp.internal.mergeNodesLeft
-import com.android.systemui.experimental.frp.internal.neverImpl
-import com.android.systemui.experimental.frp.internal.switchDeferredImplSingle
-import com.android.systemui.experimental.frp.internal.switchPromptImpl
-import com.android.systemui.experimental.frp.internal.util.hashString
-import com.android.systemui.experimental.frp.util.Either
-import com.android.systemui.experimental.frp.util.Left
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.Right
-import com.android.systemui.experimental.frp.util.just
-import com.android.systemui.experimental.frp.util.map
-import com.android.systemui.experimental.frp.util.toMaybe
+import com.android.systemui.kairos.internal.DemuxImpl
+import com.android.systemui.kairos.internal.Init
+import com.android.systemui.kairos.internal.InitScope
+import com.android.systemui.kairos.internal.InputNode
+import com.android.systemui.kairos.internal.Network
+import com.android.systemui.kairos.internal.NoScope
+import com.android.systemui.kairos.internal.TFlowImpl
+import com.android.systemui.kairos.internal.activated
+import com.android.systemui.kairos.internal.cached
+import com.android.systemui.kairos.internal.constInit
+import com.android.systemui.kairos.internal.filterNode
+import com.android.systemui.kairos.internal.init
+import com.android.systemui.kairos.internal.map
+import com.android.systemui.kairos.internal.mapImpl
+import com.android.systemui.kairos.internal.mapMaybeNode
+import com.android.systemui.kairos.internal.mergeNodes
+import com.android.systemui.kairos.internal.mergeNodesLeft
+import com.android.systemui.kairos.internal.neverImpl
+import com.android.systemui.kairos.internal.switchDeferredImplSingle
+import com.android.systemui.kairos.internal.switchPromptImpl
+import com.android.systemui.kairos.internal.util.hashString
+import com.android.systemui.kairos.util.Either
+import com.android.systemui.kairos.util.Left
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.Right
+import com.android.systemui.kairos.util.just
+import com.android.systemui.kairos.util.map
+import com.android.systemui.kairos.util.toMaybe
import java.util.concurrent.atomic.AtomicReference
import kotlin.reflect.KProperty
import kotlinx.coroutines.CompletableDeferred
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/TState.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TState.kt
similarity index 91%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/TState.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TState.kt
index a5ec503..a4c6956 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/TState.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TState.kt
@@ -14,29 +14,29 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp
+package com.android.systemui.kairos
-import com.android.systemui.experimental.frp.internal.DerivedMapCheap
-import com.android.systemui.experimental.frp.internal.Init
-import com.android.systemui.experimental.frp.internal.InitScope
-import com.android.systemui.experimental.frp.internal.Network
-import com.android.systemui.experimental.frp.internal.NoScope
-import com.android.systemui.experimental.frp.internal.Schedulable
-import com.android.systemui.experimental.frp.internal.TFlowImpl
-import com.android.systemui.experimental.frp.internal.TStateImpl
-import com.android.systemui.experimental.frp.internal.TStateSource
-import com.android.systemui.experimental.frp.internal.activated
-import com.android.systemui.experimental.frp.internal.cached
-import com.android.systemui.experimental.frp.internal.constInit
-import com.android.systemui.experimental.frp.internal.constS
-import com.android.systemui.experimental.frp.internal.filterNode
-import com.android.systemui.experimental.frp.internal.flatMap
-import com.android.systemui.experimental.frp.internal.init
-import com.android.systemui.experimental.frp.internal.map
-import com.android.systemui.experimental.frp.internal.mapCheap
-import com.android.systemui.experimental.frp.internal.mapImpl
-import com.android.systemui.experimental.frp.internal.util.hashString
-import com.android.systemui.experimental.frp.internal.zipStates
+import com.android.systemui.kairos.internal.DerivedMapCheap
+import com.android.systemui.kairos.internal.Init
+import com.android.systemui.kairos.internal.InitScope
+import com.android.systemui.kairos.internal.Network
+import com.android.systemui.kairos.internal.NoScope
+import com.android.systemui.kairos.internal.Schedulable
+import com.android.systemui.kairos.internal.TFlowImpl
+import com.android.systemui.kairos.internal.TStateImpl
+import com.android.systemui.kairos.internal.TStateSource
+import com.android.systemui.kairos.internal.activated
+import com.android.systemui.kairos.internal.cached
+import com.android.systemui.kairos.internal.constInit
+import com.android.systemui.kairos.internal.constS
+import com.android.systemui.kairos.internal.filterNode
+import com.android.systemui.kairos.internal.flatMap
+import com.android.systemui.kairos.internal.init
+import com.android.systemui.kairos.internal.map
+import com.android.systemui.kairos.internal.mapCheap
+import com.android.systemui.kairos.internal.mapImpl
+import com.android.systemui.kairos.internal.util.hashString
+import com.android.systemui.kairos.internal.zipStates
import kotlin.reflect.KProperty
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/Transactional.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Transactional.kt
similarity index 85%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/Transactional.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Transactional.kt
index 0e7b420..6b1c8c8 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/Transactional.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Transactional.kt
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp
+package com.android.systemui.kairos
-import com.android.systemui.experimental.frp.internal.InitScope
-import com.android.systemui.experimental.frp.internal.NoScope
-import com.android.systemui.experimental.frp.internal.TransactionalImpl
-import com.android.systemui.experimental.frp.internal.init
-import com.android.systemui.experimental.frp.internal.transactionalImpl
-import com.android.systemui.experimental.frp.internal.util.hashString
+import com.android.systemui.kairos.internal.InitScope
+import com.android.systemui.kairos.internal.NoScope
+import com.android.systemui.kairos.internal.TransactionalImpl
+import com.android.systemui.kairos.internal.init
+import com.android.systemui.kairos.internal.transactionalImpl
+import com.android.systemui.kairos.internal.util.hashString
import kotlinx.coroutines.CompletableDeferred
/**
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/debug/Debug.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/debug/Debug.kt
similarity index 83%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/debug/Debug.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/debug/Debug.kt
index 8062341..4f302a1 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/debug/Debug.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/debug/Debug.kt
@@ -14,25 +14,25 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.debug
+package com.android.systemui.kairos.debug
-import com.android.systemui.experimental.frp.MutableTState
-import com.android.systemui.experimental.frp.TState
-import com.android.systemui.experimental.frp.TStateInit
-import com.android.systemui.experimental.frp.TStateLoop
-import com.android.systemui.experimental.frp.internal.DerivedFlatten
-import com.android.systemui.experimental.frp.internal.DerivedMap
-import com.android.systemui.experimental.frp.internal.DerivedMapCheap
-import com.android.systemui.experimental.frp.internal.DerivedZipped
-import com.android.systemui.experimental.frp.internal.Init
-import com.android.systemui.experimental.frp.internal.TStateDerived
-import com.android.systemui.experimental.frp.internal.TStateImpl
-import com.android.systemui.experimental.frp.internal.TStateSource
-import com.android.systemui.experimental.frp.util.Just
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.None
-import com.android.systemui.experimental.frp.util.none
-import com.android.systemui.experimental.frp.util.orElseGet
+import com.android.systemui.kairos.MutableTState
+import com.android.systemui.kairos.TState
+import com.android.systemui.kairos.TStateInit
+import com.android.systemui.kairos.TStateLoop
+import com.android.systemui.kairos.internal.DerivedFlatten
+import com.android.systemui.kairos.internal.DerivedMap
+import com.android.systemui.kairos.internal.DerivedMapCheap
+import com.android.systemui.kairos.internal.DerivedZipped
+import com.android.systemui.kairos.internal.Init
+import com.android.systemui.kairos.internal.TStateDerived
+import com.android.systemui.kairos.internal.TStateImpl
+import com.android.systemui.kairos.internal.TStateSource
+import com.android.systemui.kairos.util.Just
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.None
+import com.android.systemui.kairos.util.none
+import com.android.systemui.kairos.util.orElseGet
// object IdGen {
// private val counter = AtomicLong()
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/BuildScopeImpl.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/BuildScopeImpl.kt
similarity index 89%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/BuildScopeImpl.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/BuildScopeImpl.kt
index 127abd8..90f1aea 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/BuildScopeImpl.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/BuildScopeImpl.kt
@@ -14,34 +14,34 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.CoalescingMutableTFlow
-import com.android.systemui.experimental.frp.FrpBuildScope
-import com.android.systemui.experimental.frp.FrpCoalescingProducerScope
-import com.android.systemui.experimental.frp.FrpDeferredValue
-import com.android.systemui.experimental.frp.FrpEffectScope
-import com.android.systemui.experimental.frp.FrpNetwork
-import com.android.systemui.experimental.frp.FrpProducerScope
-import com.android.systemui.experimental.frp.FrpSpec
-import com.android.systemui.experimental.frp.FrpStateScope
-import com.android.systemui.experimental.frp.FrpTransactionScope
-import com.android.systemui.experimental.frp.GroupedTFlow
-import com.android.systemui.experimental.frp.LocalFrpNetwork
-import com.android.systemui.experimental.frp.MutableTFlow
-import com.android.systemui.experimental.frp.TFlow
-import com.android.systemui.experimental.frp.TFlowInit
-import com.android.systemui.experimental.frp.groupByKey
-import com.android.systemui.experimental.frp.init
-import com.android.systemui.experimental.frp.internal.util.childScope
-import com.android.systemui.experimental.frp.internal.util.launchOnCancel
-import com.android.systemui.experimental.frp.internal.util.mapValuesParallel
-import com.android.systemui.experimental.frp.launchEffect
-import com.android.systemui.experimental.frp.util.Just
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.None
-import com.android.systemui.experimental.frp.util.just
-import com.android.systemui.experimental.frp.util.map
+import com.android.systemui.kairos.CoalescingMutableTFlow
+import com.android.systemui.kairos.FrpBuildScope
+import com.android.systemui.kairos.FrpCoalescingProducerScope
+import com.android.systemui.kairos.FrpDeferredValue
+import com.android.systemui.kairos.FrpEffectScope
+import com.android.systemui.kairos.FrpNetwork
+import com.android.systemui.kairos.FrpProducerScope
+import com.android.systemui.kairos.FrpSpec
+import com.android.systemui.kairos.FrpStateScope
+import com.android.systemui.kairos.FrpTransactionScope
+import com.android.systemui.kairos.GroupedTFlow
+import com.android.systemui.kairos.LocalFrpNetwork
+import com.android.systemui.kairos.MutableTFlow
+import com.android.systemui.kairos.TFlow
+import com.android.systemui.kairos.TFlowInit
+import com.android.systemui.kairos.groupByKey
+import com.android.systemui.kairos.init
+import com.android.systemui.kairos.internal.util.childScope
+import com.android.systemui.kairos.internal.util.launchOnCancel
+import com.android.systemui.kairos.internal.util.mapValuesParallel
+import com.android.systemui.kairos.launchEffect
+import com.android.systemui.kairos.util.Just
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.None
+import com.android.systemui.kairos.util.just
+import com.android.systemui.kairos.util.map
import java.util.concurrent.atomic.AtomicReference
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/DeferScope.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/DeferScope.kt
similarity index 88%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/DeferScope.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/DeferScope.kt
index f72ba5f..f65307c 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/DeferScope.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/DeferScope.kt
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.internal.util.asyncImmediate
-import com.android.systemui.experimental.frp.internal.util.launchImmediate
+import com.android.systemui.kairos.internal.util.asyncImmediate
+import com.android.systemui.kairos.internal.util.launchImmediate
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Demux.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Demux.kt
similarity index 96%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Demux.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Demux.kt
index 418220f..e7b9952 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Demux.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Demux.kt
@@ -16,13 +16,13 @@
@file:Suppress("NOTHING_TO_INLINE")
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.internal.util.hashString
-import com.android.systemui.experimental.frp.util.Just
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.flatMap
-import com.android.systemui.experimental.frp.util.getMaybe
+import com.android.systemui.kairos.internal.util.hashString
+import com.android.systemui.kairos.util.Just
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.flatMap
+import com.android.systemui.kairos.util.getMaybe
import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.async
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/EvalScopeImpl.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/EvalScopeImpl.kt
similarity index 84%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/EvalScopeImpl.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/EvalScopeImpl.kt
index 38bc22f..815473f 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/EvalScopeImpl.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/EvalScopeImpl.kt
@@ -14,20 +14,20 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.FrpDeferredValue
-import com.android.systemui.experimental.frp.FrpTransactionScope
-import com.android.systemui.experimental.frp.TFlow
-import com.android.systemui.experimental.frp.TFlowInit
-import com.android.systemui.experimental.frp.TFlowLoop
-import com.android.systemui.experimental.frp.TState
-import com.android.systemui.experimental.frp.TStateInit
-import com.android.systemui.experimental.frp.Transactional
-import com.android.systemui.experimental.frp.emptyTFlow
-import com.android.systemui.experimental.frp.init
-import com.android.systemui.experimental.frp.mapCheap
-import com.android.systemui.experimental.frp.switch
+import com.android.systemui.kairos.FrpDeferredValue
+import com.android.systemui.kairos.FrpTransactionScope
+import com.android.systemui.kairos.TFlow
+import com.android.systemui.kairos.TFlowInit
+import com.android.systemui.kairos.TFlowLoop
+import com.android.systemui.kairos.TState
+import com.android.systemui.kairos.TStateInit
+import com.android.systemui.kairos.Transactional
+import com.android.systemui.kairos.emptyTFlow
+import com.android.systemui.kairos.init
+import com.android.systemui.kairos.mapCheap
+import com.android.systemui.kairos.switch
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/FilterNode.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/FilterNode.kt
similarity index 83%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/FilterNode.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/FilterNode.kt
index 4f2a769..bc06a36 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/FilterNode.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/FilterNode.kt
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.util.Just
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.just
-import com.android.systemui.experimental.frp.util.none
+import com.android.systemui.kairos.util.Just
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.just
+import com.android.systemui.kairos.util.none
internal inline fun <A, B> mapMaybeNode(
crossinline getPulse: suspend EvalScope.() -> TFlowImpl<A>,
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Graph.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt
similarity index 99%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Graph.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt
index 9425870..3aec319 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Graph.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.internal.util.Bag
+import com.android.systemui.kairos.internal.util.Bag
import java.util.TreeMap
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Init.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Init.kt
similarity index 89%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Init.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Init.kt
index efb7a09..57db9a4 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Init.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Init.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.just
-import com.android.systemui.experimental.frp.util.none
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.just
+import com.android.systemui.kairos.util.none
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Inputs.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Inputs.kt
similarity index 94%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Inputs.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Inputs.kt
index 85c87fe..8efaf79 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Inputs.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Inputs.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.internal.util.Key
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.just
+import com.android.systemui.kairos.internal.util.Key
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.just
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.sync.Mutex
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/InternalScopes.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/InternalScopes.kt
similarity index 81%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/InternalScopes.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/InternalScopes.kt
index b6cd906..af864e6 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/InternalScopes.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/InternalScopes.kt
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.FrpBuildScope
-import com.android.systemui.experimental.frp.FrpStateScope
-import com.android.systemui.experimental.frp.FrpTransactionScope
-import com.android.systemui.experimental.frp.TFlow
-import com.android.systemui.experimental.frp.internal.util.HeteroMap
-import com.android.systemui.experimental.frp.internal.util.Key
-import com.android.systemui.experimental.frp.util.Maybe
+import com.android.systemui.kairos.FrpBuildScope
+import com.android.systemui.kairos.FrpStateScope
+import com.android.systemui.kairos.FrpTransactionScope
+import com.android.systemui.kairos.TFlow
+import com.android.systemui.kairos.internal.util.HeteroMap
+import com.android.systemui.kairos.internal.util.Key
+import com.android.systemui.kairos.util.Maybe
internal interface InitScope {
val networkId: Any
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Mux.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Mux.kt
similarity index 97%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Mux.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Mux.kt
index e616d62..f7ff15f 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Mux.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Mux.kt
@@ -16,11 +16,11 @@
@file:Suppress("NOTHING_TO_INLINE")
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.internal.util.ConcurrentNullableHashMap
-import com.android.systemui.experimental.frp.internal.util.hashString
-import com.android.systemui.experimental.frp.util.Just
+import com.android.systemui.kairos.internal.util.ConcurrentNullableHashMap
+import com.android.systemui.kairos.internal.util.hashString
+import com.android.systemui.kairos.util.Just
import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.sync.Mutex
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/MuxDeferred.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt
similarity index 93%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/MuxDeferred.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt
index 6d43285..08bee85 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/MuxDeferred.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt
@@ -14,28 +14,28 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.internal.util.Key
-import com.android.systemui.experimental.frp.internal.util.associateByIndexTo
-import com.android.systemui.experimental.frp.internal.util.hashString
-import com.android.systemui.experimental.frp.internal.util.mapParallel
-import com.android.systemui.experimental.frp.internal.util.mapValuesNotNullParallelTo
-import com.android.systemui.experimental.frp.util.Just
-import com.android.systemui.experimental.frp.util.Left
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.None
-import com.android.systemui.experimental.frp.util.Right
-import com.android.systemui.experimental.frp.util.These
-import com.android.systemui.experimental.frp.util.flatMap
-import com.android.systemui.experimental.frp.util.getMaybe
-import com.android.systemui.experimental.frp.util.just
-import com.android.systemui.experimental.frp.util.maybeThat
-import com.android.systemui.experimental.frp.util.maybeThis
-import com.android.systemui.experimental.frp.util.merge
-import com.android.systemui.experimental.frp.util.orElseGet
-import com.android.systemui.experimental.frp.util.partitionEithers
-import com.android.systemui.experimental.frp.util.these
+import com.android.systemui.kairos.internal.util.Key
+import com.android.systemui.kairos.internal.util.associateByIndexTo
+import com.android.systemui.kairos.internal.util.hashString
+import com.android.systemui.kairos.internal.util.mapParallel
+import com.android.systemui.kairos.internal.util.mapValuesNotNullParallelTo
+import com.android.systemui.kairos.util.Just
+import com.android.systemui.kairos.util.Left
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.None
+import com.android.systemui.kairos.util.Right
+import com.android.systemui.kairos.util.These
+import com.android.systemui.kairos.util.flatMap
+import com.android.systemui.kairos.util.getMaybe
+import com.android.systemui.kairos.util.just
+import com.android.systemui.kairos.util.maybeThat
+import com.android.systemui.kairos.util.maybeThis
+import com.android.systemui.kairos.util.merge
+import com.android.systemui.kairos.util.orElseGet
+import com.android.systemui.kairos.util.partitionEithers
+import com.android.systemui.kairos.util.these
import java.util.TreeMap
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/MuxPrompt.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt
similarity index 95%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/MuxPrompt.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt
index ea0c150..cdfafa9 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/MuxPrompt.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt
@@ -14,20 +14,20 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.internal.util.Key
-import com.android.systemui.experimental.frp.internal.util.launchImmediate
-import com.android.systemui.experimental.frp.internal.util.mapParallel
-import com.android.systemui.experimental.frp.internal.util.mapValuesNotNullParallelTo
-import com.android.systemui.experimental.frp.util.Just
-import com.android.systemui.experimental.frp.util.Left
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.None
-import com.android.systemui.experimental.frp.util.Right
-import com.android.systemui.experimental.frp.util.filterJust
-import com.android.systemui.experimental.frp.util.map
-import com.android.systemui.experimental.frp.util.partitionEithers
+import com.android.systemui.kairos.internal.util.Key
+import com.android.systemui.kairos.internal.util.launchImmediate
+import com.android.systemui.kairos.internal.util.mapParallel
+import com.android.systemui.kairos.internal.util.mapValuesNotNullParallelTo
+import com.android.systemui.kairos.util.Just
+import com.android.systemui.kairos.util.Left
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.None
+import com.android.systemui.kairos.util.Right
+import com.android.systemui.kairos.util.filterJust
+import com.android.systemui.kairos.util.map
+import com.android.systemui.kairos.util.partitionEithers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Network.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Network.kt
similarity index 95%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Network.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Network.kt
index b5ffe75..f0df89d 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Network.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Network.kt
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.TState
-import com.android.systemui.experimental.frp.internal.util.HeteroMap
-import com.android.systemui.experimental.frp.util.Just
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.just
-import com.android.systemui.experimental.frp.util.none
+import com.android.systemui.kairos.TState
+import com.android.systemui.kairos.internal.util.HeteroMap
+import com.android.systemui.kairos.util.Just
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.just
+import com.android.systemui.kairos.util.none
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedDeque
import java.util.concurrent.ConcurrentLinkedQueue
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/NoScope.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/NoScope.kt
similarity index 93%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/NoScope.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/NoScope.kt
index 6375918..fbd9689 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/NoScope.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/NoScope.kt
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.FrpScope
+import com.android.systemui.kairos.FrpScope
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/NodeTypes.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/NodeTypes.kt
similarity index 95%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/NodeTypes.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/NodeTypes.kt
index e7f76a0..0002407 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/NodeTypes.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/NodeTypes.kt
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.util.Maybe
+import com.android.systemui.kairos.util.Maybe
/*
Dmux
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Output.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Output.kt
similarity index 94%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Output.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Output.kt
index e60dcca..a3af2d3 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Output.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Output.kt
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.util.Just
+import com.android.systemui.kairos.util.Just
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/PullNodes.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/PullNodes.kt
similarity index 91%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/PullNodes.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/PullNodes.kt
index b4656e0..dac98e0 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/PullNodes.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/PullNodes.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.internal.util.Key
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.map
+import com.android.systemui.kairos.internal.util.Key
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.map
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Scheduler.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Scheduler.kt
similarity index 98%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Scheduler.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Scheduler.kt
index 4fef865..872fb7a 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Scheduler.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Scheduler.kt
@@ -16,7 +16,7 @@
@file:OptIn(ExperimentalCoroutinesApi::class)
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.PriorityBlockingQueue
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/StateScopeImpl.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/StateScopeImpl.kt
similarity index 89%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/StateScopeImpl.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/StateScopeImpl.kt
index c1d1076..baf4101 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/StateScopeImpl.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/StateScopeImpl.kt
@@ -14,27 +14,27 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.FrpDeferredValue
-import com.android.systemui.experimental.frp.FrpStateScope
-import com.android.systemui.experimental.frp.FrpStateful
-import com.android.systemui.experimental.frp.FrpTransactionScope
-import com.android.systemui.experimental.frp.GroupedTFlow
-import com.android.systemui.experimental.frp.TFlow
-import com.android.systemui.experimental.frp.TFlowInit
-import com.android.systemui.experimental.frp.TFlowLoop
-import com.android.systemui.experimental.frp.TState
-import com.android.systemui.experimental.frp.TStateInit
-import com.android.systemui.experimental.frp.emptyTFlow
-import com.android.systemui.experimental.frp.groupByKey
-import com.android.systemui.experimental.frp.init
-import com.android.systemui.experimental.frp.internal.util.mapValuesParallel
-import com.android.systemui.experimental.frp.mapCheap
-import com.android.systemui.experimental.frp.merge
-import com.android.systemui.experimental.frp.switch
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.map
+import com.android.systemui.kairos.FrpDeferredValue
+import com.android.systemui.kairos.FrpStateScope
+import com.android.systemui.kairos.FrpStateful
+import com.android.systemui.kairos.FrpTransactionScope
+import com.android.systemui.kairos.GroupedTFlow
+import com.android.systemui.kairos.TFlow
+import com.android.systemui.kairos.TFlowInit
+import com.android.systemui.kairos.TFlowLoop
+import com.android.systemui.kairos.TState
+import com.android.systemui.kairos.TStateInit
+import com.android.systemui.kairos.emptyTFlow
+import com.android.systemui.kairos.groupByKey
+import com.android.systemui.kairos.init
+import com.android.systemui.kairos.internal.util.mapValuesParallel
+import com.android.systemui.kairos.mapCheap
+import com.android.systemui.kairos.merge
+import com.android.systemui.kairos.switch
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.map
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TFlowImpl.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TFlowImpl.kt
similarity index 94%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TFlowImpl.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TFlowImpl.kt
index 7997864..b904b48 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TFlowImpl.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TFlowImpl.kt
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.util.Maybe
+import com.android.systemui.kairos.util.Maybe
/* Initialized TFlow */
internal fun interface TFlowImpl<out A> {
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TStateImpl.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TStateImpl.kt
similarity index 95%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TStateImpl.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TStateImpl.kt
index d8b6dac..5cec05c 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TStateImpl.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TStateImpl.kt
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.internal.util.Key
-import com.android.systemui.experimental.frp.internal.util.associateByIndex
-import com.android.systemui.experimental.frp.internal.util.hashString
-import com.android.systemui.experimental.frp.internal.util.mapValuesParallel
-import com.android.systemui.experimental.frp.util.Just
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.just
-import com.android.systemui.experimental.frp.util.none
+import com.android.systemui.kairos.internal.util.Key
+import com.android.systemui.kairos.internal.util.associateByIndex
+import com.android.systemui.kairos.internal.util.hashString
+import com.android.systemui.kairos.internal.util.mapValuesParallel
+import com.android.systemui.kairos.util.Just
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.just
+import com.android.systemui.kairos.util.none
import java.util.concurrent.atomic.AtomicLong
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineStart
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TransactionalImpl.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TransactionalImpl.kt
similarity index 89%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TransactionalImpl.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TransactionalImpl.kt
index c3f80a1..8647bdd 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TransactionalImpl.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TransactionalImpl.kt
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.internal.util.Key
-import com.android.systemui.experimental.frp.internal.util.hashString
+import com.android.systemui.kairos.internal.util.Key
+import com.android.systemui.kairos.internal.util.hashString
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/Bag.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/Bag.kt
similarity index 96%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/Bag.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/Bag.kt
index cc5538e..4718519 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/Bag.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/Bag.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal.util
+package com.android.systemui.kairos.internal.util
internal class Bag<T> private constructor(private val intMap: MutableMap<T, Int>) :
Set<T> by intMap.keys {
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/ConcurrentNullableHashMap.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/ConcurrentNullableHashMap.kt
similarity index 95%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/ConcurrentNullableHashMap.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/ConcurrentNullableHashMap.kt
index 449aa19..6c8ae7c 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/ConcurrentNullableHashMap.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/ConcurrentNullableHashMap.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal.util
+package com.android.systemui.kairos.internal.util
import java.util.concurrent.ConcurrentHashMap
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/HeteroMap.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/HeteroMap.kt
similarity index 86%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/HeteroMap.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/HeteroMap.kt
index 14a567c..5cee2dd 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/HeteroMap.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/HeteroMap.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal.util
+package com.android.systemui.kairos.internal.util
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.None
-import com.android.systemui.experimental.frp.util.just
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.None
+import com.android.systemui.kairos.util.just
import java.util.concurrent.ConcurrentHashMap
internal interface Key<A>
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/MapUtils.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/MapUtils.kt
similarity index 97%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/MapUtils.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/MapUtils.kt
index 6f19a76..ebf9a66 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/MapUtils.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/MapUtils.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal.util
+package com.android.systemui.kairos.internal.util
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.async
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/Util.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/Util.kt
similarity index 97%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/Util.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/Util.kt
index 0a47429..6bb7f9f 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/Util.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/Util.kt
@@ -16,7 +16,7 @@
@file:OptIn(ExperimentalCoroutinesApi::class)
-package com.android.systemui.experimental.frp.internal.util
+package com.android.systemui.kairos.internal.util
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/Either.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/Either.kt
similarity index 98%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/Either.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/Either.kt
index dca8364..ad9f7d7 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/Either.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/Either.kt
@@ -16,7 +16,7 @@
@file:Suppress("NOTHING_TO_INLINE")
-package com.android.systemui.experimental.frp.util
+package com.android.systemui.kairos.util
/**
* Contains a value of two possibilities: `Left<A>` or `Right<B>`
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/Maybe.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/Maybe.kt
similarity index 99%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/Maybe.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/Maybe.kt
index 59c680e..c3cae38 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/Maybe.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/Maybe.kt
@@ -16,7 +16,7 @@
@file:Suppress("NOTHING_TO_INLINE", "SuspendCoroutine")
-package com.android.systemui.experimental.frp.util
+package com.android.systemui.kairos.util
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/These.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/These.kt
similarity index 98%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/These.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/These.kt
index 5404c07..aa95e0d 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/These.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/These.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.util
+package com.android.systemui.kairos.util
/** Contains at least one of two potential values. */
sealed class These<A, B> {
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/WithPrev.kt
similarity index 93%
rename from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt
rename to packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/WithPrev.kt
index e52a6e1..5cfaa3e 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/WithPrev.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.util
+package com.android.systemui.kairos.util
/** Holds a [newValue] emitted from a `TFlow`, along with the [previousValue] emitted value. */
data class WithPrev<out S, out T : S>(val previousValue: S, val newValue: T)
diff --git a/packages/SystemUI/frp/test/com/android/systemui/experimental/frp/FrpTests.kt b/packages/SystemUI/utils/kairos/test/com/android/systemui/kairos/KairosTests.kt
similarity index 98%
rename from packages/SystemUI/frp/test/com/android/systemui/experimental/frp/FrpTests.kt
rename to packages/SystemUI/utils/kairos/test/com/android/systemui/kairos/KairosTests.kt
index a58f499..165230b 100644
--- a/packages/SystemUI/frp/test/com/android/systemui/experimental/frp/FrpTests.kt
+++ b/packages/SystemUI/utils/kairos/test/com/android/systemui/kairos/KairosTests.kt
@@ -16,17 +16,17 @@
@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalFrpApi::class)
-package com.android.systemui.experimental.frp
+package com.android.systemui.kairos
-import com.android.systemui.experimental.frp.util.Either
-import com.android.systemui.experimental.frp.util.Left
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.None
-import com.android.systemui.experimental.frp.util.Right
-import com.android.systemui.experimental.frp.util.just
-import com.android.systemui.experimental.frp.util.map
-import com.android.systemui.experimental.frp.util.maybe
-import com.android.systemui.experimental.frp.util.none
+import com.android.systemui.kairos.util.Either
+import com.android.systemui.kairos.util.Left
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.None
+import com.android.systemui.kairos.util.Right
+import com.android.systemui.kairos.util.just
+import com.android.systemui.kairos.util.map
+import com.android.systemui.kairos.util.maybe
+import com.android.systemui.kairos.util.none
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit
@@ -55,7 +55,7 @@
import org.junit.Assert.assertTrue
import org.junit.Test
-class FrpTests {
+class KairosTests {
@Test
fun basic() = runFrpTest { network ->
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 994bdb5..6489905 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -206,7 +206,7 @@
// Inform that DND settings have changed on OS upgrade
// Package: android
- NOTE_ZEN_UPGRADE = 48;
+ NOTE_ZEN_UPGRADE = 48 [deprecated = true];
// Notification to suggest automatic battery saver.
// Package: android
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index 6b6b39d..a77ba62 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -47,7 +47,6 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.TypedValue;
-import android.view.Display;
import android.view.DisplayInfo;
import android.view.MagnificationSpec;
import android.view.View;
@@ -1637,9 +1636,10 @@
* <strong>if scale is >= {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}</strong>.
* We assume if the scale is < {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}, there
* will be no obvious magnification effect.
+ * Only the value of the default display is persisted in user's settings.
*/
public void persistScale(int displayId) {
- final float scale = getScale(Display.DEFAULT_DISPLAY);
+ final float scale = getScale(displayId);
if (scale < MagnificationConstants.PERSISTED_SCALE_MIN_VALUE) {
return;
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index c87d516..d0c3daf 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -205,6 +205,7 @@
.verifyCallerCanExecuteAppFunction(
callingUid,
callingPid,
+ targetUser,
requestInternal.getCallingPackage(),
targetPackageName,
requestInternal.getClientRequest().getFunctionIdentifier())
@@ -437,62 +438,17 @@
targetUser,
mServiceConfig.getExecuteAppFunctionCancellationTimeoutMillis(),
cancellationSignal,
- new RunServiceCallCallback<IAppFunctionService>() {
- @Override
- public void onServiceConnected(
- @NonNull IAppFunctionService service,
- @NonNull
- ServiceUsageCompleteListener
- serviceUsageCompleteListener) {
- try {
- service.executeAppFunction(
- requestInternal.getClientRequest(),
- cancellationCallback,
- new IExecuteAppFunctionCallback.Stub() {
- @Override
- public void onResult(
- ExecuteAppFunctionResponse response) {
- safeExecuteAppFunctionCallback.onResult(
- response);
- serviceUsageCompleteListener.onCompleted();
- }
- });
- } catch (Exception e) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse
- .RESULT_APP_UNKNOWN_ERROR,
- e.getMessage(),
- /* extras= */ null));
- serviceUsageCompleteListener.onCompleted();
- }
- }
-
- @Override
- public void onFailedToConnect() {
- Slog.e(TAG, "Failed to connect to service");
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
- "Failed to connect to AppFunctionService",
- /* extras= */ null));
- }
-
- @Override
- public void onCancelled() {
- // Do not forward the result back to the caller once it has been
- // canceled. The caller does not need a notification and should
- // proceed after initiating a cancellation.
- safeExecuteAppFunctionCallback.disable();
- }
- },
+ RunAppFunctionServiceCallback.create(
+ requestInternal,
+ cancellationCallback,
+ safeExecuteAppFunctionCallback),
callerBinder);
if (!bindServiceResult) {
Slog.e(TAG, "Failed to bind to the AppFunctionService");
safeExecuteAppFunctionCallback.onResult(
ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_TIMED_OUT,
+ ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
"Failed to bind the AppFunctionService.",
/* extras= */ null));
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
index 3592ed5..5393b93 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
@@ -64,6 +64,9 @@
* {@link Manifest.permission#EXECUTE_APP_FUNCTIONS} granted. In some cases, app functions can
* still opt-out of caller having {@link Manifest.permission#EXECUTE_APP_FUNCTIONS}.
*
+ * @param callingUid The calling uid.
+ * @param callingPid The calling pid.
+ * @param targetUser The user which the caller is requesting to execute as.
* @param callerPackageName The calling package (as previously validated).
* @param targetPackageName The package that owns the app function to execute.
* @param functionId The id of the app function to execute.
@@ -72,6 +75,7 @@
AndroidFuture<Boolean> verifyCallerCanExecuteAppFunction(
int callingUid,
int callingPid,
+ @NonNull UserHandle targetUser,
@NonNull String callerPackageName,
@NonNull String targetPackageName,
@NonNull String functionId);
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
index 8b6251a..e85a70d 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
@@ -93,6 +93,7 @@
public AndroidFuture<Boolean> verifyCallerCanExecuteAppFunction(
int callingUid,
int callingPid,
+ @NonNull UserHandle targetUser,
@NonNull String callerPackageName,
@NonNull String targetPackageName,
@NonNull String functionId) {
@@ -122,7 +123,10 @@
FutureAppSearchSession futureAppSearchSession =
new FutureAppSearchSessionImpl(
- mContext.getSystemService(AppSearchManager.class),
+ Objects.requireNonNull(
+ mContext
+ .createContextAsUser(targetUser, 0)
+ .getSystemService(AppSearchManager.class)),
THREAD_POOL_EXECUTOR,
new SearchContext.Builder(APP_FUNCTION_STATIC_METADATA_DB).build());
diff --git a/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
new file mode 100644
index 0000000..7820390
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.appfunctions;
+
+import android.annotation.NonNull;
+import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
+import android.app.appfunctions.ExecuteAppFunctionResponse;
+import android.app.appfunctions.IAppFunctionService;
+import android.app.appfunctions.ICancellationCallback;
+import android.app.appfunctions.IExecuteAppFunctionCallback;
+import android.app.appfunctions.SafeOneTimeExecuteAppFunctionCallback;
+import android.util.Slog;
+
+import com.android.server.appfunctions.RemoteServiceCaller.RunServiceCallCallback;
+import com.android.server.appfunctions.RemoteServiceCaller.ServiceUsageCompleteListener;
+
+
+/**
+ * A callback to forward a request to the {@link IAppFunctionService} and report back the result.
+ */
+public class RunAppFunctionServiceCallback implements RunServiceCallCallback<IAppFunctionService> {
+
+ private final ExecuteAppFunctionAidlRequest mRequestInternal;
+ private final SafeOneTimeExecuteAppFunctionCallback mSafeExecuteAppFunctionCallback;
+ private final ICancellationCallback mCancellationCallback;
+
+ private RunAppFunctionServiceCallback(
+ ExecuteAppFunctionAidlRequest requestInternal,
+ ICancellationCallback cancellationCallback,
+ SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) {
+ this.mRequestInternal = requestInternal;
+ this.mSafeExecuteAppFunctionCallback = safeExecuteAppFunctionCallback;
+ this.mCancellationCallback = cancellationCallback;
+ }
+
+ /**
+ * Creates a new instance of {@link RunAppFunctionServiceCallback}.
+ *
+ * @param requestInternal a request to send to the service.
+ * @param cancellationCallback a callback to forward cancellation signal to the service.
+ * @param safeExecuteAppFunctionCallback a callback to report back the result of the operation.
+ */
+ public static RunAppFunctionServiceCallback create(
+ ExecuteAppFunctionAidlRequest requestInternal,
+ ICancellationCallback cancellationCallback,
+ SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) {
+ return new RunAppFunctionServiceCallback(
+ requestInternal, cancellationCallback, safeExecuteAppFunctionCallback);
+ }
+
+ @Override
+ public void onServiceConnected(
+ @NonNull IAppFunctionService service,
+ @NonNull ServiceUsageCompleteListener serviceUsageCompleteListener) {
+ try {
+ service.executeAppFunction(
+ mRequestInternal.getClientRequest(),
+ mCancellationCallback,
+ new IExecuteAppFunctionCallback.Stub() {
+ @Override
+ public void onResult(ExecuteAppFunctionResponse response) {
+ mSafeExecuteAppFunctionCallback.onResult(response);
+ serviceUsageCompleteListener.onCompleted();
+ }
+ });
+ } catch (Exception e) {
+ mSafeExecuteAppFunctionCallback.onResult(
+ ExecuteAppFunctionResponse.newFailure(
+ ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
+ e.getMessage(),
+ /* extras= */ null));
+ serviceUsageCompleteListener.onCompleted();
+ }
+ }
+
+ @Override
+ public void onFailedToConnect() {
+ Slog.e("AppFunctionManagerServiceImpl", "Failed to connect to service");
+ mSafeExecuteAppFunctionCallback.onResult(
+ ExecuteAppFunctionResponse.newFailure(
+ ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
+ "Failed to connect to AppFunctionService",
+ /* extras= */ null));
+ }
+
+ @Override
+ public void onCancelled() {
+ mSafeExecuteAppFunctionCallback.disable();
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index cd2dd3a..81ae717 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -42,6 +42,7 @@
import android.app.admin.DevicePolicyManager;
import android.app.compat.CompatChanges;
import android.companion.AssociationInfo;
+import android.companion.AssociationRequest;
import android.companion.virtual.ActivityPolicyExemption;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.IVirtualDeviceActivityListener;
@@ -153,6 +154,9 @@
private static final String PERSISTENT_ID_PREFIX_CDM_ASSOCIATION = "companion:";
+ private static final List<String> DEVICE_PROFILES_ALLOWING_MIRROR_DISPLAYS = List.of(
+ AssociationRequest.DEVICE_PROFILE_APP_STREAMING);
+
/**
* Timeout until {@link #launchPendingIntent} stops waiting for an activity to be launched.
*/
@@ -498,6 +502,10 @@
return mAssociationInfo == null ? mParams.getName() : mAssociationInfo.getDisplayName();
}
+ String getDeviceProfile() {
+ return mAssociationInfo == null ? null : mAssociationInfo.getDeviceProfile();
+ }
+
/** Returns the public representation of the device. */
VirtualDevice getPublicVirtualDeviceObject() {
return mPublicVirtualDeviceObject;
@@ -1294,6 +1302,11 @@
return hasCustomAudioInputSupportInternal();
}
+ @Override
+ public boolean canCreateMirrorDisplays() {
+ return DEVICE_PROFILES_ALLOWING_MIRROR_DISPLAYS.contains(getDeviceProfile());
+ }
+
private boolean hasCustomAudioInputSupportInternal() {
if (!Flags.vdmPublicApis()) {
return false;
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 9060250..2acedd5 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -47,6 +47,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.EventLog;
import android.util.IndentingPrintWriter;
import android.util.LongArrayQueue;
import android.util.Slog;
@@ -200,6 +201,13 @@
// aborted.
private static final String METADATA_FILE = "/metadata/watchdog/mitigation_count.txt";
+ /**
+ * EventLog tags used when logging into the event log. Note the values must be sync with
+ * frameworks/base/services/core/java/com/android/server/EventLogTags.logtags to get correct
+ * name translation.
+ */
+ private static final int LOG_TAG_RESCUE_NOTE = 2900;
+
private static final Object sPackageWatchdogLock = new Object();
@GuardedBy("sPackageWatchdogLock")
private static PackageWatchdog sPackageWatchdog;
@@ -2024,7 +2032,7 @@
} else {
int count = getCount() + 1;
setCount(count);
- EventLogTags.writeRescueNote(Process.ROOT_UID, count, window);
+ EventLog.writeEvent(LOG_TAG_RESCUE_NOTE, Process.ROOT_UID, count, window);
if (Flags.recoverabilityDetection()) {
// After a reboot (e.g. by WARM_REBOOT or mainline rollback) we apply
// mitigations without waiting for DEFAULT_BOOT_LOOP_TRIGGER_COUNT.
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index ada1953..feb5775 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -42,6 +42,7 @@
import android.sysprop.CrashRecoveryProperties;
import android.text.TextUtils;
import android.util.ArraySet;
+import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
@@ -154,6 +155,14 @@
private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
| ApplicationInfo.FLAG_SYSTEM;
+ /**
+ * EventLog tags used when logging into the event log. Note the values must be sync with
+ * frameworks/base/services/core/java/com/android/server/EventLogTags.logtags to get correct
+ * name translation.
+ */
+ private static final int LOG_TAG_RESCUE_SUCCESS = 2902;
+ private static final int LOG_TAG_RESCUE_FAILURE = 2903;
+
/** Register the Rescue Party observer as a Package Watchdog health observer */
public static void registerHealthObserver(Context context) {
PackageWatchdog.getInstance(context).registerHealthObserver(
@@ -523,7 +532,7 @@
Slog.w(TAG, "Attempting rescue level " + levelToString(level));
try {
executeRescueLevelInternal(context, level, failedPackage);
- EventLogTags.writeRescueSuccess(level);
+ EventLog.writeEvent(LOG_TAG_RESCUE_SUCCESS, level);
String successMsg = "Finished rescue level " + levelToString(level);
if (!TextUtils.isEmpty(failedPackage)) {
successMsg += " for package " + failedPackage;
@@ -704,7 +713,7 @@
private static void logRescueException(int level, @Nullable String failedPackageName,
Throwable t) {
final String msg = getCompleteMessage(t);
- EventLogTags.writeRescueFailure(level, msg);
+ EventLog.writeEvent(LOG_TAG_RESCUE_FAILURE, level, msg);
String failureMsg = "Failed rescue level " + levelToString(level);
if (!TextUtils.isEmpty(failedPackageName)) {
failureMsg += " for package " + failedPackageName;
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index a459ea9..ce66dc3 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -114,6 +114,9 @@
"options": [
{
"include-filter": "android.os.storage.cts.StorageManagerTest"
+ },
+ {
+ "include-filter": "android.os.storage.cts.StorageStatsManagerTest"
}
]
}
@@ -173,15 +176,6 @@
"include-filter": "com.android.server.wm.BackgroundActivityStart*"
}
]
- },
- {
- "name": "CtsOsTestCases",
- "file_patterns": ["StorageManagerService\\.java"],
- "options": [
- {
- "include-filter": "android.os.storage.cts.StorageStatsManagerTest"
- }
- ]
}
]
}
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 71b6456..aca6d0b 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -1005,7 +1005,8 @@
case (int) AppsStartInfoProto.Package.USERS:
AppStartInfoContainer container =
new AppStartInfoContainer(mAppStartInfoHistoryListSize);
- int uid = container.readFromProto(proto, AppsStartInfoProto.Package.USERS);
+ int uid = container.readFromProto(proto, AppsStartInfoProto.Package.USERS,
+ pkgName);
synchronized (mLock) {
mData.put(pkgName, uid, container);
}
@@ -1403,7 +1404,7 @@
proto.end(token);
}
- int readFromProto(ProtoInputStream proto, long fieldId)
+ int readFromProto(ProtoInputStream proto, long fieldId, String packageName)
throws IOException, WireTypeMismatchException, ClassNotFoundException {
long token = proto.start(fieldId);
for (int next = proto.nextField();
@@ -1418,6 +1419,7 @@
// have a create time.
ApplicationStartInfo info = new ApplicationStartInfo(0);
info.readFromProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO);
+ info.setPackageName(packageName);
mInfos.add(info);
break;
case (int) AppsStartInfoProto.Package.User.MONITORING_ENABLED:
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 78a0a11..796de19 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -458,7 +458,13 @@
}
void setThreadPriority(int tid, int priority) {
- Process.setThreadPriority(tid, priority);
+ if (Flags.resetOnForkEnabled()) {
+ Process.setThreadScheduler(tid,
+ Process.SCHED_OTHER | Process.SCHED_RESET_ON_FORK,
+ priority);
+ } else {
+ Process.setThreadPriority(tid, priority);
+ }
}
}
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 4f6da3b..cc66378 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -214,3 +214,10 @@
description: "Defer submitting display events to frozen processes."
bug: "326315985"
}
+
+flag {
+ name: "reset_on_fork_enabled"
+ namespace: "system_performance"
+ description: "Set reset_on_fork flag."
+ bug: "370988407"
+}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 87504154..0475b94 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -384,11 +384,12 @@
/**
* Indicates if a Bluetooth SCO activation request owner is controlling
* the SCO audio state itself or not.
- * @param uid the UI of the SOC request owner app
+ * @param uid the UID of the SOC request owner app
* @return true if we should control SCO audio state, false otherwise
*/
private boolean shouldStartScoForUid(int uid) {
- return !(uid == Process.BLUETOOTH_UID || uid == Process.PHONE_UID);
+ return !(UserHandle.isSameApp(uid, Process.BLUETOOTH_UID)
+ || UserHandle.isSameApp(uid, Process.PHONE_UID));
}
@GuardedBy("mDeviceStateLock")
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 5fd12c2..09de894 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -385,11 +385,6 @@
|| !updatedDevice.getDeviceAddress().equals(ads.getDeviceAddress())) {
continue;
}
- if (mDeviceBroker.isSADevice(updatedDevice) == mDeviceBroker.isSADevice(ads)) {
- ads.setHasHeadTracker(updatedDevice.hasHeadTracker());
- ads.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
- ads.setSAEnabled(updatedDevice.isSAEnabled());
- }
ads.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
mDeviceBroker.postUpdatedAdiDeviceState(ads, false /*initSA*/);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 561030e..c37d471 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1583,8 +1583,11 @@
synchronized (mCachedAbsVolDrivingStreamsLock) {
mCachedAbsVolDrivingStreams.forEach((dev, stream) -> {
- mAudioSystem.setDeviceAbsoluteVolumeEnabled(dev, /*address=*/"", /*enabled=*/true,
- stream);
+ boolean enabled = true;
+ if (dev == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
+ enabled = mAvrcpAbsVolSupported;
+ }
+ mAudioSystem.setDeviceAbsoluteVolumeEnabled(dev, /*address=*/"", enabled, stream);
});
}
}
@@ -4881,7 +4884,7 @@
if (absDev == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
enabled = mAvrcpAbsVolSupported;
}
- if (stream != streamType) {
+ if (stream != streamType || !enabled) {
mAudioSystem.setDeviceAbsoluteVolumeEnabled(absDev, /*address=*/"",
enabled, streamType);
}
@@ -10097,9 +10100,6 @@
case MSG_INIT_SPATIALIZER:
onInitSpatializer();
- // the device inventory can only be synchronized after the
- // spatializer has been initialized
- mDeviceBroker.postSynchronizeAdiDevicesInInventory(null);
mAudioEventWakeLock.release();
break;
@@ -10383,10 +10383,10 @@
}
/*package*/ void setAvrcpAbsoluteVolumeSupported(boolean support) {
- mAvrcpAbsVolSupported = support;
- if (absVolumeIndexFix()) {
- int a2dpDev = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
- synchronized (mCachedAbsVolDrivingStreamsLock) {
+ synchronized (mCachedAbsVolDrivingStreamsLock) {
+ mAvrcpAbsVolSupported = support;
+ if (absVolumeIndexFix()) {
+ int a2dpDev = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
mCachedAbsVolDrivingStreams.compute(a2dpDev, (dev, stream) -> {
if (!mAvrcpAbsVolSupported) {
mAudioSystem.setDeviceAbsoluteVolumeEnabled(a2dpDev, /*address=*/
@@ -12499,6 +12499,12 @@
pw.println("\nLoudness alignment:");
mLoudnessCodecHelper.dump(pw);
+ pw.println("\nAbsolute voume devices:");
+ synchronized (mCachedAbsVolDrivingStreamsLock) {
+ mCachedAbsVolDrivingStreams.forEach((dev, stream) -> pw.println(
+ "Device type: 0x" + Integer.toHexString(dev) + ", driving stream " + stream));
+ }
+
mAudioSystem.dump(pw);
}
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index abfbddc..3afecf1 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -879,6 +879,14 @@
);
break;
+ case BiometricPrompt.DISMISSED_REASON_ERROR_NO_WM:
+ mClientReceiver.onError(
+ getEligibleModalities(),
+ BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+ 0 /* vendorCode */
+ );
+ break;
+
default:
Slog.w(TAG, "Unhandled reason: " + reason);
break;
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index de7bce7..8734136 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -253,9 +253,15 @@
// Check if any of the non-biometric and non-credential bits are set. If so, this is
// invalid.
- final int testBits = ~(Authenticators.DEVICE_CREDENTIAL
- | Authenticators.BIOMETRIC_MIN_STRENGTH
- | Authenticators.MANDATORY_BIOMETRICS);
+ final int testBits;
+ if (Flags.mandatoryBiometrics()) {
+ testBits = ~(Authenticators.DEVICE_CREDENTIAL
+ | Authenticators.BIOMETRIC_MIN_STRENGTH
+ | Authenticators.MANDATORY_BIOMETRICS);
+ } else {
+ testBits = ~(Authenticators.DEVICE_CREDENTIAL
+ | Authenticators.BIOMETRIC_MIN_STRENGTH);
+ }
if ((authenticators & testBits) != 0) {
Slog.e(BiometricService.TAG, "Non-biometric, non-credential bits found."
+ " Authenticators: " + authenticators);
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index af9c9ac..8d96ba9 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -377,6 +377,7 @@
* </point>
* </map>
* </luxToBrightnessMapping>
+ * <idleStylusTimeoutMillis>10000</idleStylusTimeoutMillis>
* </autoBrightness>
*
* <screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease>
@@ -708,6 +709,10 @@
private static final int KEEP_CURRENT_BRIGHTNESS = -1;
+ // The default value to 0 which will signify that the stylus usage immediately stopped
+ // after it was started. This will make the system behave as if the stylus was never used
+ private static final int DEFAULT_IDLE_STYLUS_TIMEOUT_MILLIS = 0;
+
private final Context mContext;
// The details of the ambient light sensor associated with this display.
@@ -754,6 +759,9 @@
@Nullable
private DisplayBrightnessMappingConfig mDisplayBrightnessMapping;
+ private int mIdleStylusTimeoutMillis =
+ DEFAULT_IDLE_STYLUS_TIMEOUT_MILLIS;
+
private float mBacklightMinimum = Float.NaN;
private float mBacklightMaximum = Float.NaN;
private float mBrightnessDefault = Float.NaN;
@@ -1730,6 +1738,7 @@
+ ", mDisplayBrightnessMapping= " + mDisplayBrightnessMapping
+ ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable
+ ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable
+ + ", mIdleStylusTimeoutMillis= " + mIdleStylusTimeoutMillis
+ "\n"
+ "mDefaultLowBlockingZoneRefreshRate= " + mDefaultLowBlockingZoneRefreshRate
+ ", mDefaultHighBlockingZoneRefreshRate= " + mDefaultHighBlockingZoneRefreshRate
@@ -2389,10 +2398,19 @@
loadAutoBrightnessDarkeningLightDebounceIdle(autoBrightness);
mDisplayBrightnessMapping = new DisplayBrightnessMappingConfig(mContext, mFlags,
autoBrightness, getBacklightToBrightnessSpline());
+ loadIdleStylusTimeoutMillis(autoBrightness);
loadEnableAutoBrightness(autoBrightness);
}
/**
+ * Gets the timeout post the stylus usage after which the automatic brightness will be enabled
+ * again
+ */
+ public int getIdleStylusTimeoutMillis() {
+ return mIdleStylusTimeoutMillis;
+ }
+
+ /**
* Loads the auto-brightness brightening light debounce. Internally, this takes care of loading
* the value from the display config, and if not present, falls back to config.xml.
*/
@@ -2923,6 +2941,16 @@
return levels;
}
+ private void loadIdleStylusTimeoutMillis(AutoBrightness autoBrightness) {
+ if (autoBrightness == null) {
+ return;
+ }
+ BigInteger idleStylusTimeoutMillis = autoBrightness.getIdleStylusTimeoutMillis();
+ if (idleStylusTimeoutMillis != null) {
+ mIdleStylusTimeoutMillis = idleStylusTimeoutMillis.intValue();
+ }
+ }
+
private void loadEnableAutoBrightness(AutoBrightness autobrightness) {
// mDdcAutoBrightnessAvailable is initialised to true, so that we fallback to using the
// config.xml values if the autobrightness tag is not defined in the ddc file.
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 4152ec9..bb503aa 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1661,33 +1661,49 @@
return false;
}
+ private boolean hasVideoOutputPermission(String func) {
+ return checkCallingPermission(CAPTURE_VIDEO_OUTPUT, func)
+ || hasSecureVideoOutputPermission(func);
+ }
+
+ private boolean hasSecureVideoOutputPermission(String func) {
+ return checkCallingPermission(CAPTURE_SECURE_VIDEO_OUTPUT, func);
+ }
+
+ private boolean canCreateMirrorDisplays(IVirtualDevice virtualDevice) {
+ if (virtualDevice == null) {
+ return false;
+ }
+ try {
+ return virtualDevice.canCreateMirrorDisplays();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to query virtual device for permissions", e);
+ return false;
+ }
+ }
+
private boolean canProjectVideo(IMediaProjection projection) {
- if (projection != null) {
- try {
- if (projection.canProjectVideo()) {
- return true;
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to query projection service for permissions", e);
- }
+ if (projection == null) {
+ return false;
}
- if (checkCallingPermission(CAPTURE_VIDEO_OUTPUT, "canProjectVideo()")) {
- return true;
+ try {
+ return projection.canProjectVideo();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to query projection service for permissions", e);
+ return false;
}
- return canProjectSecureVideo(projection);
}
private boolean canProjectSecureVideo(IMediaProjection projection) {
- if (projection != null) {
- try {
- if (projection.canProjectSecureVideo()) {
- return true;
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to query projection service for permissions", e);
- }
+ if (projection == null) {
+ return false;
}
- return checkCallingPermission(CAPTURE_SECURE_VIDEO_OUTPUT, "canProjectSecureVideo()");
+ try {
+ return projection.canProjectSecureVideo();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to query projection service for permissions", e);
+ return false;
+ }
}
private boolean checkCallingPermission(String permission, String func) {
@@ -1793,7 +1809,8 @@
&& (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
// Only a valid media projection or a virtual device can create a mirror virtual
// display.
- if (!canProjectVideo(projection) && virtualDevice == null) {
+ if (!canProjectVideo(projection) && !canCreateMirrorDisplays(virtualDevice)
+ && !hasVideoOutputPermission("createVirtualDisplayInternal")) {
throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or "
+ "CAPTURE_SECURE_VIDEO_OUTPUT permission, or an appropriate "
+ "MediaProjection token in order to create a screen sharing virtual "
@@ -1803,7 +1820,8 @@
}
}
if (callingUid != Process.SYSTEM_UID && (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
- if (!canProjectSecureVideo(projection)) {
+ if (!canProjectSecureVideo(projection)
+ && !hasSecureVideoOutputPermission("createVirtualDisplayInternal")) {
throw new SecurityException("Requires CAPTURE_SECURE_VIDEO_OUTPUT "
+ "or an appropriate MediaProjection token to create a "
+ "secure virtual display.");
@@ -2093,16 +2111,6 @@
}
}
- private void setVirtualDisplayStateInternal(IBinder appToken, boolean isOn) {
- synchronized (mSyncRoot) {
- if (mVirtualDisplayAdapter == null) {
- return;
- }
-
- mVirtualDisplayAdapter.setVirtualDisplayStateLocked(appToken, isOn);
- }
- }
-
private void setVirtualDisplayRotationInternal(IBinder appToken,
@Surface.Rotation int rotation) {
int displayId;
@@ -4615,16 +4623,6 @@
}
@Override // Binder call
- public void setVirtualDisplayState(IVirtualDisplayCallback callback, boolean isOn) {
- final long token = Binder.clearCallingIdentity();
- try {
- setVirtualDisplayStateInternal(callback.asBinder(), isOn);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override // Binder call
public void setVirtualDisplayRotation(IVirtualDisplayCallback callback,
@Surface.Rotation int rotation) {
if (!android.companion.virtualdevice.flags.Flags.virtualDisplayRotationApi()) {
@@ -5648,6 +5646,21 @@
public void onPresentation(int displayId, boolean isShown) {
mExternalDisplayPolicy.onPresentation(displayId, isShown);
}
+
+ @Override
+ public void stylusGestureStarted(long eventTime) {
+ if (mFlags.isBlockAutobrightnessChangesOnStylusUsage()) {
+ DisplayPowerController displayPowerController;
+ synchronized (mSyncRoot) {
+ displayPowerController = mDisplayPowerControllers.get(
+ Display.DEFAULT_DISPLAY);
+ }
+ // We assume that the stylus is being used on the default display. This should
+ // be changed to the displayId on which it is being used once we start getting this
+ // information from the input manager service
+ displayPowerController.stylusGestureStarted(eventTime);
+ }
+ }
}
class DesiredDisplayModeSpecsObserver
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 03fec011..8f07bb3 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -167,12 +167,11 @@
private static final int MSG_SET_DWBC_LOGGING_ENABLED = 16;
private static final int MSG_SET_BRIGHTNESS_FROM_OFFLOAD = 17;
private static final int MSG_OFFLOADING_SCREEN_ON_UNBLOCKED = 18;
-
-
+ private static final int MSG_SET_STYLUS_BEING_USED = 19;
+ private static final int MSG_SET_STYLUS_USE_ENDED = 20;
private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500;
-
// State machine constants for tracking initial brightness ramp skipping when enabled.
private static final int RAMP_STATE_SKIP_NONE = 0;
private static final int RAMP_STATE_SKIP_INITIAL = 1;
@@ -191,6 +190,10 @@
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80,
90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1200,
1400, 1600, 1800, 2000, 2250, 2500, 2750, 3000};
+
+ private static final int STYLUS_USAGE_DEBOUNCE_TIME = 1000;
+ private static final int NANO_SECONDS_TO_MILLI_SECONDS_RATIO = 1_000_000;
+
private static final int[] BRIGHTNESS_RANGE_INDEX = {
FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_UNKNOWN,
FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_0_1,
@@ -498,6 +501,11 @@
@GuardedBy("mLock")
private int mPendingOverrideDozeScreenStateLocked;
+ private long mLastStylusUsageEventTime = -1;
+
+ // The time of inactivity after which the stylus can be assumed to be no longer in use.
+ private long mIdleStylusTimeoutMillisConfig = 0;
+
/**
* Creates the display power controller.
*/
@@ -518,6 +526,7 @@
mSensorManager = sensorManager;
mHandler = new DisplayControllerHandler(handler.getLooper());
mDisplayDeviceConfig = mDisplayDevice.getDisplayDeviceConfig();
+ mIdleStylusTimeoutMillisConfig = mDisplayDeviceConfig.getIdleStylusTimeoutMillis();
mIsEnabled = logicalDisplay.isEnabledLocked();
mIsInTransition = logicalDisplay.isInTransitionLocked();
mIsDisplayInternal = displayDeviceInfo.type == Display.TYPE_INTERNAL;
@@ -893,6 +902,7 @@
mPhysicalDisplayName = displayName;
mDisplayStatsId = mUniqueDisplayId.hashCode();
mDisplayDeviceConfig = config;
+ mIdleStylusTimeoutMillisConfig = mDisplayDeviceConfig.getIdleStylusTimeoutMillis();
mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId;
loadFromDisplayDeviceConfig(token, info, hbmMetadata);
mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(config);
@@ -2971,6 +2981,18 @@
return mDisplayId == Display.DEFAULT_DISPLAY || mBootCompleted;
}
+ public void stylusGestureStarted(long eventTimeNanoSeconds) {
+ long eventTimeMs = eventTimeNanoSeconds / NANO_SECONDS_TO_MILLI_SECONDS_RATIO;
+ if (mLastStylusUsageEventTime == -1
+ || eventTimeMs > mLastStylusUsageEventTime + STYLUS_USAGE_DEBOUNCE_TIME) {
+ synchronized (mLock) {
+ // Add a message to notify the stylus usage has started
+ mHandler.sendEmptyMessageAtTime(MSG_SET_STYLUS_BEING_USED, mClock.uptimeMillis());
+ }
+ mLastStylusUsageEventTime = eventTimeMs;
+ }
+ }
+
private final class DisplayControllerHandler extends Handler {
DisplayControllerHandler(Looper looper) {
super(looper, null, true /*async*/);
@@ -3087,6 +3109,20 @@
updatePowerState();
}
break;
+ case MSG_SET_STYLUS_BEING_USED:
+ // Remove any MSG_SET_STYLUS_USE_ENDED message from the handler queue and
+ // post a delayed MSG_SET_STYLUS_USE_ENDED message to delay the stylus
+ // usage ended event processing
+ mHandler.removeMessages(MSG_SET_STYLUS_USE_ENDED);
+ Message message = mHandler.obtainMessage(MSG_SET_STYLUS_USE_ENDED);
+ mHandler.sendMessageAtTime(message,
+ mClock.uptimeMillis() + mIdleStylusTimeoutMillisConfig);
+ mDisplayBrightnessController.setStylusBeingUsed(true);
+ break;
+ case MSG_SET_STYLUS_USE_ENDED:
+ mDisplayBrightnessController.setStylusBeingUsed(false);
+ updatePowerState();
+ break;
}
}
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index bcb600d..06a9103 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -355,8 +355,8 @@
public SparseArray<int[]> getDisplayIdsByGroupIdLocked() {
SparseArray<int[]> displayIdsByGroupIds = new SparseArray<>();
for (int i = 0; i < mDisplayGroups.size(); i++) {
- int groupId = mDisplayGroups.get(i).getGroupId();
- displayIdsByGroupIds.put(groupId, getDisplayIdsForGroupLocked(groupId));
+ final int displayGroupId = mDisplayGroups.keyAt(i);
+ displayIdsByGroupIds.put(displayGroupId, getDisplayIdsForGroupLocked(displayGroupId));
}
return displayIdsByGroupIds;
}
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 9b02f4b..e77c5ec 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -207,13 +207,6 @@
return device;
}
- void setVirtualDisplayStateLocked(IBinder appToken, boolean isOn) {
- VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken);
- if (device != null) {
- device.setDisplayState(isOn);
- }
- }
-
DisplayDevice getDisplayDevice(IBinder appToken) {
return mVirtualDisplayDevices.get(appToken);
}
@@ -273,7 +266,6 @@
private boolean mStopped;
private int mPendingChanges;
private Display.Mode mMode;
- private boolean mIsDisplayOn;
private int mDisplayIdToMirror;
private boolean mIsWindowManagerMirroring;
private DisplayCutout mDisplayCutout;
@@ -299,9 +291,8 @@
mCallback = callback;
mProjection = projection;
mMediaProjectionCallback = mediaProjectionCallback;
- mDisplayState = Display.STATE_UNKNOWN;
+ mDisplayState = Display.STATE_ON;
mPendingChanges |= PENDING_SURFACE_CHANGE;
- mIsDisplayOn = surface != null;
mDisplayIdToMirror = virtualDisplayConfig.getDisplayIdToMirror();
mIsWindowManagerMirroring = virtualDisplayConfig.isWindowManagerMirroringEnabled();
}
@@ -394,6 +385,8 @@
float sdrBrightnessState, DisplayOffloadSessionImpl displayOffloadSession) {
if (state != mDisplayState) {
mDisplayState = state;
+ mInfo = null;
+ sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
if (state == Display.STATE_OFF) {
mCallback.dispatchDisplayPaused();
} else {
@@ -416,12 +409,13 @@
public void setSurfaceLocked(Surface surface) {
if (!mStopped && mSurface != surface) {
- if ((mSurface != null) != (surface != null)) {
+ if (mDisplayState == Display.STATE_ON
+ && ((mSurface == null) != (surface == null))) {
+ mInfo = null;
sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
}
sendTraversalRequestLocked();
mSurface = surface;
- mInfo = null;
mPendingChanges |= PENDING_SURFACE_CHANGE;
}
}
@@ -439,14 +433,6 @@
}
}
- void setDisplayState(boolean isOn) {
- if (mIsDisplayOn != isOn) {
- mIsDisplayOn = isOn;
- mInfo = null;
- sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
- }
- }
-
public void stopLocked() {
Slog.d(TAG, "Virtual Display: stopping device " + mName);
setSurfaceLocked(null);
@@ -567,7 +553,11 @@
mInfo.touch = ((mFlags & VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH) == 0) ?
DisplayDeviceInfo.TOUCH_NONE : DisplayDeviceInfo.TOUCH_VIRTUAL;
- mInfo.state = mIsDisplayOn ? Display.STATE_ON : Display.STATE_OFF;
+ if (mSurface == null) {
+ mInfo.state = Display.STATE_OFF;
+ } else {
+ mInfo.state = mDisplayState;
+ }
mInfo.ownerUid = mOwnerUid;
mInfo.ownerPackageName = mOwnerPackageName;
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 72a91d5..71fdaf3 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -501,6 +501,13 @@
return true;
}
+ /**
+ * Notifies if the stylus is currently being used or not.
+ */
+ public void setStylusBeingUsed(boolean isEnabled) {
+ // Todo(b/369977976) - Disable the auto-brightness strategy
+ }
+
@VisibleForTesting
static class Injector {
DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(Context context,
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index df66893..5284d1c 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -203,6 +203,10 @@
Flags.FLAG_NORMAL_BRIGHTNESS_FOR_DOZE_PARAMETER,
Flags::normalBrightnessForDozeParameter
);
+ private final FlagState mBlockAutobrightnessChangesOnStylusUsage = new FlagState(
+ Flags.FLAG_BLOCK_AUTOBRIGHTNESS_CHANGES_ON_STYLUS_USAGE,
+ Flags::blockAutobrightnessChangesOnStylusUsage
+ );
private final FlagState mEnableBatteryStatsForAllDisplays = new FlagState(
Flags.FLAG_ENABLE_BATTERY_STATS_FOR_ALL_DISPLAYS,
@@ -436,6 +440,13 @@
}
/**
+ * @return {@code true} if autobrightness is to be blocked when stylus is being used
+ */
+ public boolean isBlockAutobrightnessChangesOnStylusUsage() {
+ return mBlockAutobrightnessChangesOnStylusUsage.isEnabled();
+ }
+
+ /**
* dumps all flagstates
* @param pw printWriter
*/
@@ -479,6 +490,7 @@
pw.println(" " + mNormalBrightnessForDozeParameter);
pw.println(" " + mIdleScreenConfigInSubscribingLightSensor);
pw.println(" " + mEnableBatteryStatsForAllDisplays);
+ pw.println(" " + mBlockAutobrightnessChangesOnStylusUsage);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index e3ebe5b..252ed09 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -364,4 +364,12 @@
description: "Flag to enable battery stats for all displays."
bug: "366112793"
is_fixed_read_only: true
-}
\ No newline at end of file
+}
+
+flag {
+ name: "block_autobrightness_changes_on_stylus_usage"
+ namespace: "display_manager"
+ description: "Block the usage of ALS to control the display brightness when stylus is being used"
+ bug: "352411468"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index f045576..8acf583 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2694,6 +2694,9 @@
@SuppressWarnings("unused")
private void notifyStylusGestureStarted(int deviceId, long eventTime) {
mBatteryController.notifyStylusGestureStarted(deviceId, eventTime);
+ if (mDisplayManagerInternal != null) {
+ mDisplayManagerInternal.stylusGestureStarted(eventTime);
+ }
}
/**
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 0a9109b..d752429 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -16,7 +16,6 @@
package com.android.server.media;
-import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
import static android.media.VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
import static android.media.VolumeProvider.VOLUME_CONTROL_FIXED;
import static android.media.VolumeProvider.VOLUME_CONTROL_RELATIVE;
@@ -48,9 +47,7 @@
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.MediaMetadata;
-import android.media.MediaRouter2Manager;
import android.media.Rating;
-import android.media.RoutingSessionInfo;
import android.media.VolumeProvider;
import android.media.session.ISession;
import android.media.session.ISessionCallback;
@@ -186,7 +183,6 @@
private final MediaSessionService mService;
private final UriGrantsManagerInternal mUgmInternal;
private final Context mContext;
- private final boolean mVolumeAdjustmentForRemoteGroupSessions;
private final ForegroundServiceDelegationOptions mForegroundServiceDelegationOptions;
@@ -311,8 +307,6 @@
mAudioAttrs = DEFAULT_ATTRIBUTES;
mPolicies = policies;
mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
- mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
mForegroundServiceDelegationOptions = createForegroundServiceDelegationOptions();
@@ -659,49 +653,7 @@
}
return false;
}
- if (mVolumeAdjustmentForRemoteGroupSessions) {
- if (DEBUG) {
- Slog.d(
- TAG,
- "Volume adjustment for remote group sessions allowed so MediaSessionRecord"
- + " can handle volume key");
- }
- return true;
- }
- // See b/228021646 for details.
- MediaRouter2Manager mRouter2Manager = MediaRouter2Manager.getInstance(mContext);
- List<RoutingSessionInfo> sessions = mRouter2Manager.getRoutingSessions(mPackageName);
- boolean foundNonSystemSession = false;
- boolean remoteSessionAllowVolumeAdjustment = true;
- if (DEBUG) {
- Slog.d(
- TAG,
- "Found "
- + sessions.size()
- + " routing sessions for package name "
- + mPackageName);
- }
- for (RoutingSessionInfo session : sessions) {
- if (DEBUG) {
- Slog.d(TAG, "Found routingSessionInfo: " + session);
- }
- if (!session.isSystemSession()) {
- foundNonSystemSession = true;
- if (session.getVolumeHandling() == PLAYBACK_VOLUME_FIXED) {
- remoteSessionAllowVolumeAdjustment = false;
- }
- }
- }
- if (!foundNonSystemSession) {
- if (DEBUG) {
- Slog.d(
- TAG,
- "Package " + mPackageName
- + " has a remote media session but no associated routing session");
- }
- }
-
- return foundNonSystemSession && remoteSessionAllowVolumeAdjustment;
+ return true;
}
@Override
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 47f579d..e7e519e 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -42,6 +42,8 @@
import android.app.IProcessObserver;
import android.app.KeyguardManager;
import android.app.compat.CompatChanges;
+import android.app.role.RoleManager;
+import android.companion.AssociationRequest;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.ComponentName;
@@ -94,7 +96,7 @@
/**
* Manages MediaProjection sessions.
- *
+ * <p>
* The {@link MediaProjectionManagerService} manages the creation and lifetime of MediaProjections,
* as well as the capabilities they grant. Any service using MediaProjection tokens as permission
* grants <b>must</b> validate the token before use by calling {@link
@@ -137,6 +139,7 @@
private final PackageManager mPackageManager;
private final WindowManagerInternal mWmInternal;
private final KeyguardManager mKeyguardManager;
+ private final RoleManager mRoleManager;
private final MediaRouter mMediaRouter;
private final MediaRouterCallback mMediaRouterCallback;
@@ -173,6 +176,7 @@
mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
mKeyguardManager.addKeyguardLockedStateListener(
mContext.getMainExecutor(), this::onKeyguardLockedStateChanged);
+ mRoleManager = mContext.getSystemService(RoleManager.class);
Watchdog.getInstance().addMonitor(this);
}
@@ -182,6 +186,7 @@
* - be one of the bugreport allowlisted packages, or
* - hold the OP_PROJECT_MEDIA AppOp.
*/
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean canCaptureKeyguard() {
if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) {
return true;
@@ -193,6 +198,9 @@
if (mPackageManager.checkPermission(RECORD_SENSITIVE_CONTENT,
mProjectionGrant.packageName)
== PackageManager.PERMISSION_GRANTED) {
+ Slog.v(TAG,
+ "Allowing keyguard capture for package with RECORD_SENSITIVE_CONTENT "
+ + "permission");
return true;
}
if (AppOpsManager.MODE_ALLOWED == mAppOps.noteOpNoThrow(AppOpsManager.OP_PROJECT_MEDIA,
@@ -200,6 +208,13 @@
"recording lockscreen")) {
// Some tools use media projection by granting the OP_PROJECT_MEDIA app
// op via a shell command. Those tools can be granted keyguard capture
+ Slog.v(TAG,
+ "Allowing keyguard capture for package with OP_PROJECT_MEDIA AppOp ");
+ return true;
+ }
+ if (isProjectionAppHoldingAppStreamingRoleLocked()) {
+ Slog.v(TAG,
+ "Allowing keyguard capture for package holding app streaming role.");
return true;
}
return SystemConfig.getInstance().getBugreportWhitelistedPackages()
@@ -698,6 +713,20 @@
}
}
+ /**
+ * Application holding the app streaming role
+ * ({@value AssociationRequest#DEVICE_PROFILE_APP_STREAMING}) are allowed to record the
+ * lockscreen.
+ *
+ * @return true if the is held by the recording application.
+ */
+ @GuardedBy("mLock")
+ private boolean isProjectionAppHoldingAppStreamingRoleLocked() {
+ return mRoleManager.getRoleHoldersAsUser(AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
+ mContext.getUser())
+ .contains(mProjectionGrant.packageName);
+ }
+
private void dump(final PrintWriter pw) {
pw.println("MEDIA PROJECTION MANAGER (dumpsys media_projection)");
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index ff263d1..bdca555 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -37,7 +37,6 @@
import android.util.ArraySet;
import android.util.Slog;
-import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.util.NotificationMessagingUtil;
import java.io.PrintWriter;
@@ -173,13 +172,6 @@
maybeLogInterceptDecision(record, false, "criticalNotification");
return false;
}
- // Make an exception to policy for the notification saying that policy has changed
- if (NotificationManager.Policy.areAllVisualEffectsSuppressed(policy.suppressedVisualEffects)
- && "android".equals(record.getSbn().getPackageName())
- && SystemMessageProto.SystemMessage.NOTE_ZEN_UPGRADE == record.getSbn().getId()) {
- maybeLogInterceptDecision(record, false, "systemDndChangedNotification");
- return false;
- }
switch (zen) {
case Global.ZEN_MODE_NO_INTERRUPTIONS:
// #notevenalarms
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 626c3dd..ea211a9 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -54,10 +54,8 @@
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
import android.app.Flags;
-import android.app.Notification;
import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
-import android.app.PendingIntent;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
@@ -74,7 +72,6 @@
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
-import android.graphics.drawable.Icon;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
@@ -90,7 +87,6 @@
import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.provider.Settings;
import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.ConditionProviderService;
@@ -117,8 +113,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
@@ -309,7 +303,6 @@
mHandler.postMetricsTimer();
cleanUpZenRules();
mIsSystemServicesReady = true;
- showZenUpgradeNotification(mZenMode);
}
/**
@@ -485,7 +478,7 @@
populateZenRule(pkg, automaticZenRule, rule, origin, /* isNew= */ true);
rule = maybeRestoreRemovedRule(newConfig, pkg, rule, automaticZenRule, origin);
newConfig.automaticRules.put(rule.id, rule);
- maybeReplaceDefaultRule(newConfig, automaticZenRule);
+ maybeReplaceDefaultRule(newConfig, null, automaticZenRule);
if (setConfigLocked(newConfig, origin, reason, rule.component, true, callingUid)) {
return rule.id;
@@ -535,13 +528,24 @@
return ruleToRestore;
}
- private static void maybeReplaceDefaultRule(ZenModeConfig config, AutomaticZenRule addedRule) {
+ /**
+ * Possibly delete built-in rules if a more suitable rule is added or updated.
+ *
+ * <p>Today, this is done in one case: delete a disabled "Sleeping" rule if a Bedtime Mode is
+ * added (or an existing mode is turned into {@link AutomaticZenRule#TYPE_BEDTIME}, when
+ * upgrading). Because only the {@code config_systemWellbeing} package is allowed to use rules
+ * of this type, this will not trigger wantonly.
+ *
+ * @param oldRule If non-null, {@code rule} is updating {@code oldRule}. Otherwise,
+ * {@code rule} is being added.
+ */
+ private static void maybeReplaceDefaultRule(ZenModeConfig config, @Nullable ZenRule oldRule,
+ AutomaticZenRule rule) {
if (!Flags.modesApi()) {
return;
}
- if (addedRule.getType() == AutomaticZenRule.TYPE_BEDTIME) {
- // Delete a built-in disabled "Sleeping" rule when a BEDTIME rule is added; it may have
- // smarter triggers and it will prevent confusion about which one to use.
+ if (rule.getType() == AutomaticZenRule.TYPE_BEDTIME
+ && (oldRule == null || oldRule.type != rule.getType())) {
// Note: we must not verify canManageAutomaticZenRule here, since most likely they
// won't have the same owner (sleeping - system; bedtime - DWB).
ZenRule sleepingRule = config.automaticRules.get(
@@ -589,6 +593,10 @@
// condition) when no changes happen.
return true;
}
+
+ if (Flags.modesUi()) {
+ maybeReplaceDefaultRule(newConfig, oldRule, automaticZenRule);
+ }
return setConfigLocked(newConfig, origin, reason,
newRule.component, true, callingUid);
}
@@ -1584,8 +1592,6 @@
String reason, @Nullable String caller, int callingUid) {
setManualZenMode(zenMode, conditionId, origin, reason, caller, true /*setRingerMode*/,
callingUid);
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION, 0);
}
private void setManualZenMode(int zenMode, Uri conditionId, @ConfigOrigin int origin,
@@ -1783,17 +1789,6 @@
SystemZenRules.maybeUpgradeRules(mContext, config);
}
- // Resolve user id for settings.
- userId = userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM : userId;
- if (config.version < ZenModeConfig.XML_VERSION_ZEN_UPGRADE) {
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 1, userId);
- } else {
- // devices not restoring/upgrading already have updated zen settings
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.ZEN_SETTINGS_UPDATED, 1, userId);
- }
-
if (Flags.modesApi() && forRestore) {
// Note: forBackup doesn't write deletedRules, but just in case.
config.deletedRules.clear();
@@ -2062,7 +2057,6 @@
Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zen);
ZenLog.traceSetZenMode(Global.getInt(mContext.getContentResolver(), Global.ZEN_MODE, -1),
"updated setting");
- showZenUpgradeNotification(zen);
}
private int getPreviousRingerModeSetting() {
@@ -2117,12 +2111,6 @@
for (ZenRule automaticRule : mConfig.automaticRules.values()) {
if (automaticRule.isActive()) {
if (zenSeverity(automaticRule.zenMode) > zenSeverity(zen)) {
- // automatic rule triggered dnd and user hasn't seen update dnd dialog
- if (Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ZEN_SETTINGS_SUGGESTION_VIEWED, 1) == 0) {
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION, 1);
- }
zen = automaticRule.zenMode;
}
}
@@ -2702,62 +2690,6 @@
}
}
- private void showZenUpgradeNotification(int zen) {
- final boolean isWatch = mContext.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WATCH);
- final boolean showNotification = mIsSystemServicesReady
- && zen != Global.ZEN_MODE_OFF
- && !isWatch
- && Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0) != 0
- && Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ZEN_SETTINGS_UPDATED, 0) != 1;
-
- if (isWatch) {
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
- }
-
- if (showNotification) {
- mNotificationManager.notify(TAG, SystemMessage.NOTE_ZEN_UPGRADE,
- createZenUpgradeNotification());
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
- }
- }
-
- @VisibleForTesting
- protected Notification createZenUpgradeNotification() {
- final Bundle extras = new Bundle();
- extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
- mContext.getResources().getString(R.string.global_action_settings));
- int title = R.string.zen_upgrade_notification_title;
- int content = R.string.zen_upgrade_notification_content;
- int drawable = R.drawable.ic_zen_24dp;
- if (NotificationManager.Policy.areAllVisualEffectsSuppressed(
- getConsolidatedNotificationPolicy().suppressedVisualEffects)) {
- title = R.string.zen_upgrade_notification_visd_title;
- content = R.string.zen_upgrade_notification_visd_content;
- drawable = R.drawable.ic_dnd_block_notifications;
- }
-
- Intent onboardingIntent = new Intent(Settings.ZEN_MODE_ONBOARDING);
- onboardingIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- return new Notification.Builder(mContext, SystemNotificationChannels.DO_NOT_DISTURB)
- .setAutoCancel(true)
- .setSmallIcon(R.drawable.ic_settings_24dp)
- .setLargeIcon(Icon.createWithResource(mContext, drawable))
- .setContentTitle(mContext.getResources().getString(title))
- .setContentText(mContext.getResources().getString(content))
- .setContentIntent(PendingIntent.getActivity(mContext, 0, onboardingIntent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
- .setAutoCancel(true)
- .setLocalOnly(true)
- .addExtras(extras)
- .setStyle(new Notification.BigTextStyle())
- .build();
- }
-
private int drawableResNameToResId(String packageName, String resourceName) {
if (TextUtils.isEmpty(resourceName)) {
return 0;
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 89ced12..4665a72 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2208,10 +2208,10 @@
return true;
}
boolean permissionGranted = requireFullPermission ? hasPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid)
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL)
: (hasPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid)
- || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS, callingUid));
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS));
if (!permissionGranted) {
if (Process.isIsolatedUid(callingUid) && isKnownIsolatedComputeApp(callingUid)) {
return checkIsolatedOwnerHasPermission(callingUid, requireFullPermission);
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 1569fa0..02afdd6 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -80,6 +80,7 @@
import com.android.server.pm.PackageDexOptimizer.DexOptResult;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
+import com.android.server.pm.local.PackageManagerLocalImpl;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
@@ -819,10 +820,16 @@
final PackageSetting ps = installRequest.getScannedPackageSetting();
final String packageName = ps.getPackageName();
+ PackageSetting uncommittedPs = null;
+ if (Flags.improveInstallFreeze()) {
+ uncommittedPs = ps;
+ }
+
PackageManagerLocal packageManagerLocal =
LocalManagerRegistry.getManager(PackageManagerLocal.class);
try (PackageManagerLocal.FilteredSnapshot snapshot =
- packageManagerLocal.withFilteredSnapshot()) {
+ PackageManagerLocalImpl.withFilteredSnapshot(packageManagerLocal,
+ uncommittedPs)) {
boolean ignoreDexoptProfile =
(installRequest.getInstallFlags()
& PackageManager.INSTALL_IGNORE_DEXOPT_PROFILE)
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index aca65bf..83292b7 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -148,6 +148,7 @@
import android.util.ArraySet;
import android.util.EventLog;
import android.util.ExceptionUtils;
+import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -1014,13 +1015,17 @@
final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size());
try {
CriticalEventLog.getInstance().logInstallPackagesStarted();
-
if (prepareInstallPackages(requests)
&& scanInstallPackages(requests, createdAppId, versionInfos)) {
List<ReconciledPackage> reconciledPackages =
reconcileInstallPackages(requests, versionInfos);
- if (reconciledPackages != null
- && renameAndUpdatePaths(requests)
+ if (reconciledPackages == null) {
+ return;
+ }
+ if (Flags.improveInstallFreeze()) {
+ prepPerformDexoptIfNeeded(reconciledPackages);
+ }
+ if (renameAndUpdatePaths(requests)
&& commitInstallPackages(reconciledPackages)) {
success = true;
}
@@ -1031,6 +1036,75 @@
}
}
+ private int[] getNewUsers(InstallRequest installRequest, int[] allUsers)
+ throws PackageManagerException {
+ final int userId = installRequest.getUserId();
+ if (userId != UserHandle.USER_ALL && userId != UserHandle.USER_CURRENT
+ && !mPm.mUserManager.exists(userId)) {
+ throw new PackageManagerException(PackageManagerException.INTERNAL_ERROR_MISSING_USER,
+ "User " + userId + " doesn't exist or has been removed");
+ }
+
+ final IntArray newUserIds = new IntArray();
+ if (userId != UserHandle.USER_ALL) {
+ newUserIds.add(userId);
+ } else if (allUsers != null) {
+ final int[] installedForUsers = installRequest.getOriginUsers();
+ for (int currentUserId : allUsers) {
+ final boolean installedForCurrentUser = ArrayUtils.contains(
+ installedForUsers, currentUserId);
+ final boolean restrictedByPolicy =
+ mPm.isUserRestricted(currentUserId,
+ UserManager.DISALLOW_INSTALL_APPS)
+ || mPm.isUserRestricted(currentUserId,
+ UserManager.DISALLOW_DEBUGGING_FEATURES);
+ if (installedForCurrentUser || !restrictedByPolicy) {
+ newUserIds.add(currentUserId);
+ }
+ }
+ }
+
+ if (newUserIds.size() == 0) {
+ throw new PackageManagerException(PackageManagerException.INTERNAL_ERROR_MISSING_USER,
+ "User " + userId + " doesn't exist or has been removed");
+ } else {
+ return newUserIds.toArray();
+ }
+ }
+
+ private void prepPerformDexoptIfNeeded(List<ReconciledPackage> reconciledPackages) {
+ for (ReconciledPackage reconciledPkg : reconciledPackages) {
+ final InstallRequest request = reconciledPkg.mInstallRequest;
+ // prepare profiles
+ final PackageSetting ps = request.getScannedPackageSetting();
+ final PackageSetting oldPkgSetting = request.getScanRequestOldPackageSetting();
+ final int[] allUsers = mPm.mUserManager.getUserIds();
+ if (reconciledPkg.mCollectedSharedLibraryInfos != null
+ || (oldPkgSetting != null
+ && !oldPkgSetting.getSharedLibraryDependencies().isEmpty())) {
+ // Reconcile if the new package or the old package uses shared libraries.
+ // It is possible that the old package uses shared libraries but the new
+ // one doesn't.
+ mSharedLibraries.executeSharedLibrariesUpdate(request.getParsedPackage(), ps,
+ null, null, reconciledPkg.mCollectedSharedLibraryInfos, allUsers);
+ }
+ try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
+ final int[] newUsers = getNewUsers(request, allUsers);
+ // Hardcode previousAppId to 0 to disable any data migration (http://b/221088088)
+ mAppDataHelper.prepareAppDataPostCommitLIF(ps, 0, newUsers);
+ if (request.isClearCodeCache()) {
+ mAppDataHelper.clearAppDataLIF(ps.getPkg(), UserHandle.USER_ALL,
+ FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
+ | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
+ }
+ } catch (PackageManagerException e) {
+ request.setError(e.error, e.getMessage());
+ return;
+ }
+ DexOptHelper.performDexoptIfNeeded(request, mDexManager, mContext, null);
+ }
+ }
+
private boolean renameAndUpdatePaths(List<InstallRequest> requests) {
try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
for (InstallRequest request : requests) {
@@ -2655,20 +2729,22 @@
incrementalStorages.add(storage);
}
- // Hardcode previousAppId to 0 to disable any data migration (http://b/221088088)
- mAppDataHelper.prepareAppDataPostCommitLIF(ps, 0, installRequest.getNewUsers());
- if (installRequest.isClearCodeCache()) {
- mAppDataHelper.clearAppDataLIF(ps.getPkg(), UserHandle.USER_ALL,
- FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
- | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
- }
if (installRequest.isInstallReplace() && pkg != null) {
mDexManager.notifyPackageUpdated(packageName,
pkg.getBaseApkPath(), pkg.getSplitCodePaths());
}
+ if (!Flags.improveInstallFreeze()) {
+ // Hardcode previousAppId to 0 to disable any data migration (http://b/221088088)
+ mAppDataHelper.prepareAppDataPostCommitLIF(ps, 0, installRequest.getNewUsers());
+ if (installRequest.isClearCodeCache()) {
+ mAppDataHelper.clearAppDataLIF(ps.getPkg(), UserHandle.USER_ALL,
+ FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
+ | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
+ }
- DexOptHelper.performDexoptIfNeeded(installRequest, mDexManager, mContext,
- mPm.mInstallLock.getRawLock());
+ DexOptHelper.performDexoptIfNeeded(installRequest, mDexManager, mContext,
+ mPm.mInstallLock.getRawLock());
+ }
}
PackageManagerServiceUtils.waitForNativeBinariesExtractionForIncremental(
incrementalStorages);
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 8657de2..5653da0 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -716,7 +716,7 @@
visiblePackages.add(info.getActivityInfo().packageName);
}
final List<ApplicationInfo> installedPackages =
- mPackageManagerInternal.getInstalledApplicationsCrossUser(
+ mPackageManagerInternal.getInstalledApplications(
/* flags= */ 0, user.getIdentifier(), callingUid);
for (ApplicationInfo applicationInfo : installedPackages) {
if (!visiblePackages.contains(applicationInfo.packageName)) {
diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
index cf5de89..a28e3c1 100644
--- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
@@ -244,7 +244,7 @@
return;
}
int registerUid = registerUser.getUid();
- if (allowUids != null && registerUid != Process.SYSTEM_UID
+ if (allowUids != null && !UserHandle.isSameApp(registerUid, Process.SYSTEM_UID)
&& !ArrayUtils.contains(allowUids, registerUid)) {
if (DEBUG) {
Slog.w(TAG, "Skip invoke PackageMonitorCallback for " + intent.getAction()
diff --git a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
index 55afb17..c22e382 100644
--- a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
+++ b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
@@ -31,6 +31,7 @@
import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.SharedUserApi;
import com.android.server.pm.snapshot.PackageDataSnapshot;
@@ -71,8 +72,26 @@
@NonNull
@Override
public FilteredSnapshotImpl withFilteredSnapshot(int callingUid, @NonNull UserHandle user) {
+ return withFilteredSnapshot(callingUid, user, /* uncommittedPs= */ null);
+ }
+
+ /**
+ * Creates a {@link FilteredSnapshot} with a uncommitted {@link PackageState} that is used for
+ * dexopt in the art service to get the correct package state before the package is committed.
+ */
+ @NonNull
+ public static FilteredSnapshotImpl withFilteredSnapshot(PackageManagerLocal pm,
+ @NonNull PackageState uncommittedPs) {
+ return ((PackageManagerLocalImpl) pm).withFilteredSnapshot(Binder.getCallingUid(),
+ Binder.getCallingUserHandle(), uncommittedPs);
+ }
+
+ @NonNull
+ private FilteredSnapshotImpl withFilteredSnapshot(int callingUid, @NonNull UserHandle user,
+ @Nullable PackageState uncommittedPs) {
return new FilteredSnapshotImpl(callingUid, user,
- mService.snapshotComputer(false /*allowLiveComputer*/), null);
+ mService.snapshotComputer(/* allowLiveComputer= */ false),
+ /* parentSnapshot= */ null, uncommittedPs);
}
@Override
@@ -145,7 +164,8 @@
@Override
public FilteredSnapshot filtered(int callingUid, @NonNull UserHandle user) {
- return new FilteredSnapshotImpl(callingUid, user, mSnapshot, this);
+ return new FilteredSnapshotImpl(callingUid, user, mSnapshot, this,
+ /* uncommittedPs= */ null);
}
@SuppressWarnings("RedundantSuppression")
@@ -209,13 +229,18 @@
@Nullable
private final UnfilteredSnapshotImpl mParentSnapshot;
+ @Nullable
+ private final PackageState mUncommitPackageState;
+
private FilteredSnapshotImpl(int callingUid, @NonNull UserHandle user,
@NonNull PackageDataSnapshot snapshot,
- @Nullable UnfilteredSnapshotImpl parentSnapshot) {
+ @Nullable UnfilteredSnapshotImpl parentSnapshot,
+ @Nullable PackageState uncommittedPs) {
super(snapshot);
mCallingUid = callingUid;
mUserId = user.getIdentifier();
mParentSnapshot = parentSnapshot;
+ mUncommitPackageState = uncommittedPs;
}
@Override
@@ -237,6 +262,10 @@
@Override
public PackageState getPackageState(@NonNull String packageName) {
checkClosed();
+ if (mUncommitPackageState != null
+ && packageName.equals(mUncommitPackageState.getPackageName())) {
+ return mUncommitPackageState;
+ }
return mSnapshot.getPackageStateFiltered(packageName, mCallingUid, mUserId);
}
@@ -250,6 +279,11 @@
var filteredPackageStates = new ArrayMap<String, PackageState>();
for (int index = 0, size = packageStates.size(); index < size; index++) {
var packageState = packageStates.valueAt(index);
+ if (mUncommitPackageState != null
+ && packageState.getPackageName().equals(
+ mUncommitPackageState.getPackageName())) {
+ packageState = (PackageStateInternal) mUncommitPackageState;
+ }
if (!mSnapshot.shouldFilterApplication(packageState, mCallingUid, mUserId)) {
filteredPackageStates.put(packageStates.keyAt(index), packageState);
}
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index 027e69c..66ec53e 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -667,9 +667,11 @@
public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
List<KeyboardShortcutInfo> shortcuts = new ArrayList();
if (modifierShortcutManagerRefactor()) {
+ Context context = modifierShortcutManagerMultiuser()
+ ? mContext.createContextAsUser(mCurrentUser, 0) : mContext;
for (Bookmark b : mBookmarks.values()) {
KeyboardShortcutInfo info = shortcutInfoFromIntent(
- b.getShortcutChar(), b.getIntent(mContext), b.isShift());
+ b.getShortcutChar(), b.getIntent(context), b.isShift());
if (info != null) {
shortcuts.add(info);
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 197f007..68e781f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -373,6 +373,7 @@
//The config value can be overridden using Settings.Global.STEM_PRIMARY_BUTTON_DOUBLE_PRESS
static final int DOUBLE_PRESS_PRIMARY_NOTHING = 0;
static final int DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP = 1;
+ static final int DOUBLE_PRESS_PRIMARY_LAUNCH_DEFAULT_FITNESS_APP = 2;
// Must match: config_triplePressOnStemPrimaryBehavior in config.xml
// The config value can be overridden using Settings.Global.STEM_PRIMARY_BUTTON_TRIPLE_PRESS
@@ -1140,9 +1141,9 @@
+ mShortPressOnPowerBehavior);
if (count == 2) {
- powerMultiPressAction(eventTime, interactive, mDoublePressOnPowerBehavior);
+ powerMultiPressAction(displayId, eventTime, interactive, mDoublePressOnPowerBehavior);
} else if (count == 3) {
- powerMultiPressAction(eventTime, interactive, mTriplePressOnPowerBehavior);
+ powerMultiPressAction(displayId, eventTime, interactive, mTriplePressOnPowerBehavior);
} else if (count > 3 && count <= getMaxMultiPressPowerCount()) {
Slog.d(TAG, "No behavior defined for power press count " + count);
} else if (count == 1 && shouldHandleShortPressPowerAction(interactive, eventTime)) {
@@ -1306,7 +1307,8 @@
}
}
- private void powerMultiPressAction(long eventTime, boolean interactive, int behavior) {
+ private void powerMultiPressAction(int displayId, long eventTime, boolean interactive,
+ int behavior) {
switch (behavior) {
case MULTI_PRESS_POWER_NOTHING:
break;
@@ -1321,7 +1323,7 @@
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.THEATER_MODE_ON, 0);
if (!interactive) {
- wakeUpFromWakeKey(eventTime, KEYCODE_POWER, /* isDown= */ false);
+ wakeUpFromWakeKey(displayId, eventTime, KEYCODE_POWER, /* isDown= */ false);
}
} else {
Slog.i(TAG, "Toggling theater mode on.");
@@ -1337,7 +1339,7 @@
case MULTI_PRESS_POWER_BRIGHTNESS_BOOST:
Slog.i(TAG, "Starting brightness boost.");
if (!interactive) {
- wakeUpFromWakeKey(eventTime, KEYCODE_POWER, /* isDown= */ false);
+ wakeUpFromWakeKey(displayId, eventTime, KEYCODE_POWER, /* isDown= */ false);
}
mPowerManager.boostScreenBrightness(eventTime);
break;
@@ -1596,6 +1598,12 @@
performStemPrimaryDoublePressSwitchToRecentTask();
}
break;
+ case DOUBLE_PRESS_PRIMARY_LAUNCH_DEFAULT_FITNESS_APP:
+ final int stemPrimaryKeyDeviceId = INVALID_INPUT_DEVICE_ID;
+ handleKeyGestureInKeyGestureController(
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS,
+ stemPrimaryKeyDeviceId, KEYCODE_STEM_PRIMARY, /* metaState= */ 0);
+ break;
}
}
@@ -5539,7 +5547,7 @@
if (mRequestedOrSleepingDefaultDisplay) {
mCameraGestureTriggeredDuringGoingToSleep = true;
// Wake device up early to prevent display doing redundant turning off/on stuff.
- mWindowWakeUpPolicy.wakeUpFromPowerKeyCameraGesture();
+ mWindowWakeUpPolicy.wakeUpFromPowerKeyCameraGesture(event.getDisplayId());
}
return true;
}
@@ -5637,8 +5645,8 @@
public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
long whenNanos, int policyFlags) {
if ((policyFlags & FLAG_WAKE) != 0) {
- if (mWindowWakeUpPolicy.wakeUpFromMotion(
- whenNanos / 1000000, source, action == MotionEvent.ACTION_DOWN)) {
+ if (mWindowWakeUpPolicy.wakeUpFromMotion(displayId, whenNanos / 1000000, source,
+ action == MotionEvent.ACTION_DOWN)) {
// Woke up. Pass motion events to user.
return ACTION_PASS_TO_USER;
}
@@ -5652,8 +5660,8 @@
// there will be no dream to intercept the touch and wake into ambient. The device should
// wake up in this case.
if (isTheaterModeEnabled() && (policyFlags & FLAG_WAKE) != 0) {
- if (mWindowWakeUpPolicy.wakeUpFromMotion(
- whenNanos / 1000000, source, action == MotionEvent.ACTION_DOWN)) {
+ if (mWindowWakeUpPolicy.wakeUpFromMotion(displayId, whenNanos / 1000000, source,
+ action == MotionEvent.ACTION_DOWN)) {
// Woke up. Pass motion events to user.
return ACTION_PASS_TO_USER;
}
@@ -5995,13 +6003,14 @@
return;
}
wakeUpFromWakeKey(
+ event.getDisplayId(),
event.getEventTime(),
event.getKeyCode(),
event.getAction() == KeyEvent.ACTION_DOWN);
}
- private void wakeUpFromWakeKey(long eventTime, int keyCode, boolean isDown) {
- if (mWindowWakeUpPolicy.wakeUpFromKey(eventTime, keyCode, isDown)) {
+ private void wakeUpFromWakeKey(int displayId, long eventTime, int keyCode, boolean isDown) {
+ if (mWindowWakeUpPolicy.wakeUpFromKey(displayId, eventTime, keyCode, isDown)) {
final boolean keyCanLaunchHome = keyCode == KEYCODE_HOME || keyCode == KEYCODE_POWER;
// Start HOME with "reason" extra if sleeping for more than mWakeUpToLastStateTimeout
if (shouldWakeUpWithHomeIntent() && keyCanLaunchHome) {
@@ -7244,6 +7253,8 @@
return "DOUBLE_PRESS_PRIMARY_NOTHING";
case DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP:
return "DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP";
+ case DOUBLE_PRESS_PRIMARY_LAUNCH_DEFAULT_FITNESS_APP:
+ return "DOUBLE_PRESS_PRIMARY_LAUNCH_DEFAULT_FITNESS_APP";
default:
return Integer.toString(behavior);
}
diff --git a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
index af1ad13..966d84f4 100644
--- a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
@@ -25,6 +25,7 @@
import static android.view.KeyEvent.KEYCODE_POWER;
import static com.android.server.policy.Flags.supportInputWakeupDelegate;
+import static com.android.server.power.feature.flags.Flags.perDisplayWakeByTouch;
import android.annotation.Nullable;
import android.content.Context;
@@ -107,13 +108,14 @@
/**
* Wakes up from a key event.
*
+ * @param displayId the id of the display to wake.
* @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
* @param keyCode the {@link android.view.KeyEvent} key code of the key event.
* @param isDown {@code true} if the event's action is {@link KeyEvent#ACTION_DOWN}.
* @return {@code true} if the policy allows the requested wake up and the request has been
* executed; {@code false} otherwise.
*/
- boolean wakeUpFromKey(long eventTime, int keyCode, boolean isDown) {
+ boolean wakeUpFromKey(int displayId, long eventTime, int keyCode, boolean isDown) {
final boolean wakeAllowedDuringTheaterMode =
keyCode == KEYCODE_POWER
? mAllowTheaterModeWakeFromPowerKey
@@ -127,6 +129,7 @@
return true;
}
wakeUp(
+ displayId,
eventTime,
keyCode == KEYCODE_POWER ? WAKE_REASON_POWER_BUTTON : WAKE_REASON_WAKE_KEY,
keyCode == KEYCODE_POWER ? "POWER" : "KEY");
@@ -136,12 +139,13 @@
/**
* Wakes up from a motion event.
*
+ * @param displayId the id of the display to wake.
* @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
* @param isDown {@code true} if the event's action is {@link MotionEvent#ACTION_DOWN}.
* @return {@code true} if the policy allows the requested wake up and the request has been
* executed; {@code false} otherwise.
*/
- boolean wakeUpFromMotion(long eventTime, int source, boolean isDown) {
+ boolean wakeUpFromMotion(int displayId, long eventTime, int source, boolean isDown) {
if (!canWakeUp(mAllowTheaterModeWakeFromMotion)) {
if (DEBUG) Slog.d(TAG, "Unable to wake up from motion.");
return false;
@@ -150,7 +154,7 @@
&& mInputWakeUpDelegate.wakeUpFromMotion(eventTime, source, isDown)) {
return true;
}
- wakeUp(eventTime, WAKE_REASON_WAKE_MOTION, "MOTION");
+ wakeUp(displayId, eventTime, WAKE_REASON_WAKE_MOTION, "MOTION");
return true;
}
@@ -166,7 +170,7 @@
if (DEBUG) Slog.d(TAG, "Unable to wake up from camera cover.");
return false;
}
- wakeUp(eventTime, WAKE_REASON_CAMERA_LAUNCH, "CAMERA_COVER");
+ wakeUp(Display.DEFAULT_DISPLAY, eventTime, WAKE_REASON_CAMERA_LAUNCH, "CAMERA_COVER");
return true;
}
@@ -181,22 +185,24 @@
if (DEBUG) Slog.d(TAG, "Unable to wake up from lid.");
return false;
}
- wakeUp(mClock.uptimeMillis(), WAKE_REASON_LID, "LID");
+ wakeUp(Display.DEFAULT_DISPLAY, mClock.uptimeMillis(), WAKE_REASON_LID, "LID");
return true;
}
/**
* Wakes up to prevent sleeping when opening camera through power button.
*
+ * @param displayId the id of the display to wake.
* @return {@code true} if the policy allows the requested wake up and the request has been
* executed; {@code false} otherwise.
*/
- boolean wakeUpFromPowerKeyCameraGesture() {
+ boolean wakeUpFromPowerKeyCameraGesture(int displayId) {
if (!canWakeUp(mAllowTheaterModeWakeFromPowerKey)) {
if (DEBUG) Slog.d(TAG, "Unable to wake up from power key camera gesture.");
return false;
}
- wakeUp(mClock.uptimeMillis(), WAKE_REASON_CAMERA_LAUNCH, "CAMERA_GESTURE_PREVENT_LOCK");
+ wakeUp(displayId, mClock.uptimeMillis(), WAKE_REASON_CAMERA_LAUNCH,
+ "CAMERA_GESTURE_PREVENT_LOCK");
return true;
}
@@ -211,7 +217,7 @@
if (DEBUG) Slog.d(TAG, "Unable to wake up from gesture.");
return false;
}
- wakeUp(mClock.uptimeMillis(), WAKE_REASON_GESTURE, "GESTURE");
+ wakeUp(Display.DEFAULT_DISPLAY, mClock.uptimeMillis(), WAKE_REASON_GESTURE, "GESTURE");
return true;
}
@@ -234,7 +240,11 @@
}
/** Wakes up {@link PowerManager}. */
- private void wakeUp(long wakeTime, @WakeReason int reason, String details) {
- mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details);
+ private void wakeUp(int displayId, long wakeTime, @WakeReason int reason, String details) {
+ if (perDisplayWakeByTouch()) {
+ mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details, displayId);
+ } else {
+ mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details);
+ }
}
}
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 1346a29..c969eff 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -21,6 +21,7 @@
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.server.power.hint.Flags.adpfSessionTag;
import static com.android.server.power.hint.Flags.powerhintThreadCleanup;
+import static com.android.server.power.hint.Flags.resetOnForkEnabled;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1057,6 +1058,11 @@
Slogf.w(TAG, errMsg);
throw new SecurityException(errMsg);
}
+ if (resetOnForkEnabled()){
+ for (int tid : tids) {
+ Process.setThreadScheduler(tid, Process.SCHED_RESET_ON_FORK, 0);
+ }
+ }
if (adpfSessionTag() && tag == SessionTag.APP) {
// If the category of the app is a game,
@@ -1282,11 +1288,9 @@
boolean updateHintAllowedByProcState(boolean allowed) {
synchronized (this) {
if (allowed && !mUpdateAllowedByProcState && !mShouldForcePause) {
- Slogf.e(TAG, "ADPF IS GETTING RESUMED? UID: " + mUid + " TAG: " + mTag);
resume();
}
if (!allowed && mUpdateAllowedByProcState) {
- Slogf.e(TAG, "ADPF IS GETTING PAUSED? UID: " + mUid + " TAG: " + mTag);
pause();
}
mUpdateAllowedByProcState = allowed;
@@ -1449,6 +1453,11 @@
Slogf.w(TAG, errMsg);
throw new SecurityException(errMsg);
}
+ if (resetOnForkEnabled()){
+ for (int tid : tids) {
+ Process.setThreadScheduler(tid, Process.SCHED_RESET_ON_FORK, 0);
+ }
+ }
if (powerhintThreadCleanup()) {
synchronized (mNonIsolatedTidsLock) {
for (int i = nonIsolated.size() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/power/hint/flags.aconfig b/services/core/java/com/android/server/power/hint/flags.aconfig
index 55afa05..e56b68c 100644
--- a/services/core/java/com/android/server/power/hint/flags.aconfig
+++ b/services/core/java/com/android/server/power/hint/flags.aconfig
@@ -14,3 +14,10 @@
description: "Feature flag for adding session tag to hint session atom"
bug: "345011125"
}
+
+flag {
+ name: "reset_on_fork_enabled"
+ namespace: "game"
+ description: "Set reset_on_fork flag."
+ bug: "370988407"
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 5372abe..2972771 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -230,6 +230,7 @@
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
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;
@@ -286,6 +287,9 @@
import android.app.servertransaction.TopResumedActivityChangeItem;
import android.app.servertransaction.TransferSplashScreenViewStateItem;
import android.app.usage.UsageEvents.Event;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.Overridable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -465,6 +469,11 @@
// finished destroying itself.
private static final int DESTROY_TIMEOUT = 10 * 1000;
+ @ChangeId
+ @Overridable
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ static final long UNIVERSAL_RESIZABLE_BY_DEFAULT = 357141415;
+
final ActivityTaskManagerService mAtmService;
final ActivityCallerState mCallerState;
@NonNull
@@ -2635,8 +2644,10 @@
if (finishing || !mHandleExitSplashScreen || mStartingSurface == null
|| mStartingWindow == null
|| mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH
- // skip copy splash screen to client if it was resized
- || (mStartingData != null && mStartingData.mResizedFromTransfer)
+ // Skip copy splash screen to client if it was resized, or the starting data already
+ // requested to be removed after transaction commit.
+ || (mStartingData != null && (mStartingData.mResizedFromTransfer
+ || mStartingData.mRemoveAfterTransaction != AFTER_TRANSACTION_IDLE))
|| isRelaunching()) {
return false;
}
@@ -3179,11 +3190,20 @@
* will be ignored.
*/
boolean isUniversalResizeable() {
- return mWmService.mConstants.mIgnoreActivityOrientationRequest
- && info.applicationInfo.category != ApplicationInfo.CATEGORY_GAME
- // If the user preference respects aspect ratio, then it becomes non-resizable.
- && !mAppCompatController.getAppCompatOverrides().getAppCompatAspectRatioOverrides()
- .shouldApplyUserMinAspectRatioOverride();
+ if (info.applicationInfo.category == ApplicationInfo.CATEGORY_GAME) {
+ return false;
+ }
+ final boolean compatEnabled = Flags.universalResizableByDefault()
+ && mDisplayContent != null && mDisplayContent.getConfiguration()
+ .smallestScreenWidthDp >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP
+ && mDisplayContent.getIgnoreOrientationRequest()
+ && info.isChangeEnabled(UNIVERSAL_RESIZABLE_BY_DEFAULT);
+ if (!compatEnabled && !mWmService.mConstants.mIgnoreActivityOrientationRequest) {
+ return false;
+ }
+ // If the user preference respects aspect ratio, then it becomes non-resizable.
+ return !mAppCompatController.getAppCompatOverrides().getAppCompatAspectRatioOverrides()
+ .shouldApplyUserMinAspectRatioOverride();
}
boolean isResizeable() {
@@ -8179,7 +8199,7 @@
@ActivityInfo.ScreenOrientation
protected int getOverrideOrientation() {
int candidateOrientation = super.getOverrideOrientation();
- if (isUniversalResizeable() && ActivityInfo.isFixedOrientation(candidateOrientation)) {
+ if (ActivityInfo.isFixedOrientation(candidateOrientation) && isUniversalResizeable()) {
candidateOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
}
return mAppCompatController.getOrientationPolicy()
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 49380d4..87fa62a 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2759,10 +2759,7 @@
mInTask = null;
// Launch ResolverActivity in the source task, so that it stays in the task bounds
// when in freeform workspace.
- // Also put noDisplay activities in the source task. These by itself can be placed
- // in any task/root-task, however it could launch other activities like
- // ResolverActivity, and we want those to stay in the original task.
- if ((mStartActivity.isResolverOrDelegateActivity() || mStartActivity.noDisplay)
+ if (mStartActivity.isResolverOrDelegateActivity()
&& mSourceRecord != null && mSourceRecord.inFreeformWindowingMode()) {
mAddingToTask = true;
}
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index a74b006..4824c16 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -21,6 +21,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Rect;
import android.util.Log;
import android.view.Surface;
@@ -128,7 +129,7 @@
/**
* Set the parameters to prepare the dim to be relative parented to the dimming container
*/
- void prepareReparent(@NonNull WindowContainer<?> geometryParent,
+ void prepareReparent(@Nullable WindowContainer<?> geometryParent,
@NonNull WindowState relativeParent) {
mAnimationHelper.setRequestedRelativeParent(relativeParent);
mAnimationHelper.setRequestedGeometryParent(geometryParent);
@@ -221,7 +222,7 @@
* @param dimmingContainer The container that is dimming. The dim layer will be rel-z
* parented below it
*/
- public void adjustPosition(@NonNull WindowContainer<?> geometryParent,
+ public void adjustPosition(@Nullable WindowContainer<?> geometryParent,
@NonNull WindowState dimmingContainer) {
if (mDimState != null) {
mDimState.prepareReparent(geometryParent, dimmingContainer);
diff --git a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
index 298edae..3999e03 100644
--- a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
+++ b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
@@ -108,7 +108,7 @@
}
// Sets the requested layer to reparent the dim to without applying it immediately
- void setRequestedGeometryParent(WindowContainer<?> geometryParent) {
+ void setRequestedGeometryParent(@Nullable WindowContainer<?> geometryParent) {
if (geometryParent != null) {
mRequestedProperties.mGeometryParent = geometryParent;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 0878912..66f9230 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5215,7 +5215,7 @@
// but not window manager visible (!isVisibleNow()), it can still be the parent of the
// dim, but can not create a new surface or continue a dim alone.
Dimmer dimmer;
- WindowContainer<?> geometryParent = task;
+ WindowContainer<?> geometryParent = null;
if (Flags.useTasksDimOnly()) {
geometryParent = getDimParent();
dimmer = getDimController();
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index a07facf..776de2e 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -712,6 +712,12 @@
minOccurs="0" maxOccurs="unbounded">
<xs:annotation name="final"/>
</xs:element>
+ <!-- The time after which the stylus is to be assumed to be not under use. This will
+ enable the logic of changing the brightness with ambient light changes -->
+ <xs:element name="idleStylusTimeoutMillis" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
</xs:sequence>
</xs:complexType>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 5309263..110a5a2 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -8,12 +8,14 @@
method public final java.math.BigInteger getDarkeningLightDebounceIdleMillis();
method public final java.math.BigInteger getDarkeningLightDebounceMillis();
method public boolean getEnabled();
+ method public final java.math.BigInteger getIdleStylusTimeoutMillis();
method public final java.util.List<com.android.server.display.config.LuxToBrightnessMapping> getLuxToBrightnessMapping();
method public final void setBrighteningLightDebounceIdleMillis(java.math.BigInteger);
method public final void setBrighteningLightDebounceMillis(java.math.BigInteger);
method public final void setDarkeningLightDebounceIdleMillis(java.math.BigInteger);
method public final void setDarkeningLightDebounceMillis(java.math.BigInteger);
method public void setEnabled(boolean);
+ method public final void setIdleStylusTimeoutMillis(java.math.BigInteger);
}
public enum AutoBrightnessModeName {
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index 7d5532f..5c4716d 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -57,7 +57,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
-import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.doReturn
@@ -384,10 +383,6 @@
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)) {
PackageManager.PERMISSION_GRANTED
}
- whenever(this.checkPermission(
- eq(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL), anyInt(), anyInt())) {
- PackageManager.PERMISSION_GRANTED
- }
}
val mockSharedLibrariesImpl: SharedLibrariesImpl = mock {
whenever(this.snapshot()) { this@mock }
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
index e33ca77..70a2d48 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
+++ b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
@@ -45,7 +45,7 @@
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
+import com.android.bedstead.multiuser.annotations.EnsureHasSecondaryUser;
import com.android.bedstead.nene.users.UserReference;
import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.TestUtils;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 8e1be9a..3976ea4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -157,6 +157,7 @@
.getIdleScreenRefreshRateTimeoutLuxThresholdPoint());
assertNull(mDisplayDeviceConfig.getTempSensor().name);
assertTrue(mDisplayDeviceConfig.isAutoBrightnessAvailable());
+ assertEquals(0, mDisplayDeviceConfig.getIdleStylusTimeoutMillis());
}
@Test
@@ -253,6 +254,7 @@
.getLux().intValue());
assertEquals(800, idleScreenRefreshRateTimeoutLuxThresholdPoints.get(1)
.getTimeout().intValue());
+ assertEquals(1000, mDisplayDeviceConfig.getIdleStylusTimeoutMillis());
}
@Test
@@ -1479,6 +1481,7 @@
+ "</point>\n"
+ "</map>\n"
+ "</luxToBrightnessMapping>\n"
+ + "<idleStylusTimeoutMillis>1000</idleStylusTimeoutMillis>\n"
+ "</autoBrightness>\n"
+ getPowerThrottlingConfig()
+ "<highBrightnessMode enabled=\"true\">\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 342c87a..6093a67 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -1309,6 +1309,38 @@
}
/**
+ * Tests that it's not allowed to create an auto-mirror virtual display without
+ * CAPTURE_VIDEO_OUTPUT permission or a virtual device that can mirror displays
+ */
+ @Test
+ public void createAutoMirrorDisplay_withoutPermissionOrAllowedVirtualDevice_throwsException()
+ throws Exception {
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ registerDefaultDisplays(displayManager);
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+ when(virtualDevice.getDeviceId()).thenReturn(1);
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(false);
+ when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+ when(mContext.checkCallingPermission(CAPTURE_VIDEO_OUTPUT)).thenReturn(
+ PackageManager.PERMISSION_DENIED);
+
+ final VirtualDisplayConfig.Builder builder =
+ new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+ .setFlags(VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR)
+ .setUniqueId("uniqueId --- mirror display");
+ assertThrows(SecurityException.class, () -> {
+ localService.createVirtualDisplay(
+ builder.build(),
+ mMockAppToken /* callback */,
+ virtualDevice /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
+ });
+ }
+
+ /**
* Tests that the virtual display is added to the default display group when created with
* VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR using a virtual device.
*/
@@ -1320,6 +1352,7 @@
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(virtualDevice.getDeviceId()).thenReturn(1);
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
// Create an auto-mirror virtual display using a virtual device.
@@ -1352,6 +1385,7 @@
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(virtualDevice.getDeviceId()).thenReturn(1);
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
// Create an auto-mirror virtual display using a virtual device.
@@ -1418,6 +1452,7 @@
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(virtualDevice.getDeviceId()).thenReturn(1);
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
when(mContext.checkCallingPermission(ADD_ALWAYS_UNLOCKED_DISPLAY))
.thenReturn(PackageManager.PERMISSION_GRANTED);
@@ -1453,6 +1488,7 @@
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(virtualDevice.getDeviceId()).thenReturn(1);
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
// Create an auto-mirror virtual display using a virtual device.
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
index e863f15..e678acc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -39,6 +39,7 @@
import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.Parcel;
import android.os.Process;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
@@ -580,6 +581,50 @@
assertTrue(mAppStartInfoTracker.mMonotonicClock.monotonicTime() >= originalMonotonicTime);
}
+ /**
+ * Test to confirm that parcel read and write implementations match, correctly loading records
+ * with the same values and leaving no data unread.
+ */
+ @Test
+ public void testParcelReadWriteMatch() throws Exception {
+ // Create a start info records with all fields set.
+ ApplicationStartInfo startInfo = new ApplicationStartInfo(1234L);
+ startInfo.setPid(123);
+ startInfo.setRealUid(987);
+ startInfo.setPackageUid(654);
+ startInfo.setDefiningUid(321);
+ startInfo.setReason(ApplicationStartInfo.START_REASON_LAUNCHER);
+ startInfo.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN);
+ startInfo.setStartType(ApplicationStartInfo.START_TYPE_WARM);
+ startInfo.setLaunchMode(ApplicationStartInfo.LAUNCH_MODE_SINGLE_TOP);
+ startInfo.setPackageName(APP_1_PACKAGE_NAME);
+ startInfo.setProcessName(APP_1_PROCESS_NAME);
+ startInfo.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_LAUNCH, 999L);
+ startInfo.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME, 888L);
+ startInfo.setForceStopped(true);
+ startInfo.setStartComponent(ApplicationStartInfo.START_COMPONENT_OTHER);
+ startInfo.setIntent(buildIntent(COMPONENT));
+
+ // Write the start info to a parcel.
+ Parcel parcel = Parcel.obtain();
+ startInfo.writeToParcel(parcel, 0 /* flags */);
+
+ // Set the data position back to 0 so it's ready to be read.
+ parcel.setDataPosition(0);
+
+ // Now load the record from the parcel.
+ ApplicationStartInfo startInfoFromParcel = new ApplicationStartInfo(parcel);
+
+ // Make sure there is no unread data remaining in the parcel, and confirm that the loaded
+ // start info object is equal to the one it was written from. Check dataAvail first as if
+ // that check fails then the next check will fail too, but knowing the status of this check
+ // will tell us that we're missing a read or write. Check the objects are equals second as
+ // if the avail check passes and equals fails, then we know we're reading all the data just
+ // not to the correct fields.
+ assertEquals(0, parcel.dataAvail());
+ assertTrue(startInfo.equals(startInfoFromParcel));
+ }
+
private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
try {
Field field = clazz.getDeclaredField(fieldName);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
index 24e7242..54ee2a3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
@@ -33,6 +33,7 @@
import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.Process;
+import android.os.UserHandle;
import android.util.SparseArray;
import org.junit.After;
@@ -340,6 +341,24 @@
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
}
+ @Test
+ public void registerPackageMonitor_callbackNotInAllowListSystemUidSecondUser_callbackIsCalled()
+ throws Exception {
+ IRemoteCallback callback = createMockPackageMonitorCallback();
+ int userId = 10;
+ int fakeAppId = 12345;
+ SparseArray<int[]> broadcastAllowList = new SparseArray<>();
+ broadcastAllowList.put(userId, new int[]{UserHandle.getUid(userId, fakeAppId)});
+
+ mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, userId,
+ UserHandle.getUid(userId, Process.SYSTEM_UID));
+ mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
+ FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{userId},
+ null /* instantUserIds */, broadcastAllowList, mHandler, null /* filterExtras */);
+
+ verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
+ }
+
private IRemoteCallback createMockPackageMonitorCallback() {
return spy(new IRemoteCallback.Stub() {
@Override
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index cbe6700..6ede334 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -230,6 +230,20 @@
}
java_library {
+ name: "servicestests-utils-ravenwood",
+ srcs: [
+ "utils/**/*.java",
+ "utils/**/*.kt",
+ "utils-mockito/**/*.kt",
+ ],
+ libs: [
+ "android.test.runner.stubs.system",
+ "junit",
+ "mockito-ravenwood-prebuilt",
+ ],
+}
+
+java_library {
name: "mockito-test-utils",
srcs: [
"utils-mockito/**/*.kt",
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index 1426d5d..c4b4afd 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -69,6 +69,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
@@ -92,6 +93,8 @@
import org.testng.Assert;
import java.util.Locale;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
@RunWith(AndroidJUnit4.class)
public class FullScreenMagnificationControllerTest {
@@ -1440,19 +1443,42 @@
@Test
public void persistScale_setValueWhenScaleIsOne_nothingChanged() {
+ register(TEST_DISPLAY);
final float persistedScale =
mFullScreenMagnificationController.getPersistedScale(TEST_DISPLAY);
PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
- mFullScreenMagnificationController.setScale(DISPLAY_0, 1.0f, pivotPoint.x, pivotPoint.y,
+ mFullScreenMagnificationController.setScale(TEST_DISPLAY, 1.0f, pivotPoint.x, pivotPoint.y,
false, SERVICE_ID_1);
mFullScreenMagnificationController.persistScale(TEST_DISPLAY);
+ // persistScale may post a task to a background thread. Let's wait for it completes.
+ waitForBackgroundThread();
Assert.assertEquals(mFullScreenMagnificationController.getPersistedScale(TEST_DISPLAY),
persistedScale);
}
@Test
+ public void persistScale_setValuesOnMultipleDisplays() {
+ register(DISPLAY_0);
+ register(DISPLAY_1);
+ final PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ mFullScreenMagnificationController.setScale(DISPLAY_0, 3.0f, pivotPoint.x, pivotPoint.y,
+ false, SERVICE_ID_1);
+ mFullScreenMagnificationController.persistScale(DISPLAY_0);
+ mFullScreenMagnificationController.setScale(DISPLAY_1, 4.0f, pivotPoint.x, pivotPoint.y,
+ false, SERVICE_ID_1);
+ mFullScreenMagnificationController.persistScale(DISPLAY_1);
+
+ // persistScale may post a task to a background thread. Let's wait for it completes.
+ waitForBackgroundThread();
+ Assert.assertEquals(mFullScreenMagnificationController.getPersistedScale(DISPLAY_0),
+ 3.0f);
+ Assert.assertEquals(mFullScreenMagnificationController.getPersistedScale(DISPLAY_1),
+ 4.0f);
+ }
+
+ @Test
public void testOnContextChanged_alwaysOnFeatureDisabled_resetMagnification() {
setScaleToMagnifying();
@@ -1494,6 +1520,15 @@
);
}
+ private static void waitForBackgroundThread() {
+ final CompletableFuture<Void> future = new CompletableFuture<>();
+ BackgroundThread.getHandler().post(() -> future.complete(null));
+ try {
+ future.get();
+ } catch (InterruptedException | ExecutionException ignore) {
+ }
+ }
+
private void setScaleToMagnifying() {
register(DISPLAY_0);
float scale = 2.0f;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
index 87fe6cf..6aa8a32 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
@@ -300,7 +300,8 @@
mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 2.5f, NaN, NaN);
- mMagnificationConnectionManager.setScale(TEST_DISPLAY, 10.0f);
+ mMagnificationConnectionManager.setScale(TEST_DISPLAY,
+ MagnificationScaleProvider.MAX_SCALE * 2.f);
assertEquals(mMagnificationConnectionManager.getScale(TEST_DISPLAY),
MagnificationScaleProvider.MAX_SCALE);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
index efc2d97..1bea371 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
@@ -217,7 +217,13 @@
doNothing().when(mContext).enforceCallingOrSelfPermission(
eq(SET_BIOMETRIC_DIALOG_ADVANCED), any());
- assertTrue(Utils.isValidAuthenticatorConfig(mContext, Authenticators.MANDATORY_BIOMETRICS));
+ if (Flags.mandatoryBiometrics()) {
+ assertTrue(Utils.isValidAuthenticatorConfig(mContext,
+ Authenticators.MANDATORY_BIOMETRICS));
+ } else {
+ assertFalse(Utils.isValidAuthenticatorConfig(mContext,
+ Authenticators.MANDATORY_BIOMETRICS));
+ }
// The rest of the bits are not allowed to integrate with the public APIs
for (int i = 8; i < 32; i++) {
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index ee63d5d..425bb15 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -33,6 +33,7 @@
import static android.view.Display.INVALID_DISPLAY;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -51,11 +52,15 @@
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
+import android.Manifest;
import android.annotation.SuppressLint;
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions.LaunchCookie;
import android.app.AppOpsManager;
+import android.app.Instrumentation;
import android.app.KeyguardManager;
+import android.app.role.RoleManager;
+import android.companion.AssociationRequest;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -68,6 +73,7 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.Looper;
+import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.test.TestLooper;
@@ -88,6 +94,7 @@
import com.android.server.wm.WindowManagerInternal;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -98,6 +105,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -312,7 +320,6 @@
assertThat(mService.getActiveProjectionInfo()).isNotNull();
}
- @SuppressLint("MissingPermission")
@EnableFlags(android.companion.virtualdevice.flags
.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
@Test
@@ -335,6 +342,36 @@
assertThat(mService.getActiveProjectionInfo()).isNotNull();
}
+ @EnableFlags(android.companion.virtualdevice.flags
+ .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+ @Test
+ public void testCreateProjection_keyguardLocked_RoleHeld() {
+ runWithRole(AssociationRequest.DEVICE_PROFILE_APP_STREAMING, () -> {
+ try {
+ mAppInfo.privateFlags |= PRIVATE_FLAG_PRIVILEGED;
+ doReturn(mAppInfo).when(mPackageManager).getApplicationInfoAsUser(anyString(),
+ any(ApplicationInfoFlags.class), any(UserHandle.class));
+ MediaProjectionManagerService.MediaProjection projection =
+ mService.createProjectionInternal(Process.myUid(),
+ mContext.getPackageName(),
+ TYPE_MIRRORING, /* isPermanentGrant= */ false, UserHandle.CURRENT);
+ doReturn(true).when(mKeyguardManager).isKeyguardLocked();
+ doReturn(PackageManager.PERMISSION_DENIED).when(
+ mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, projection.packageName);
+
+ projection.start(mIMediaProjectionCallback);
+ projection.notifyVirtualDisplayCreated(10);
+
+ // The projection was started because it was allowed to capture the keyguard.
+ assertWithMessage("Failed to run projection")
+ .that(mService.getActiveProjectionInfo()).isNotNull();
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
@Test
public void testCreateProjection_attemptReuse_noPriorProjectionGrant()
throws NameNotFoundException {
@@ -1202,6 +1239,47 @@
return mService.getProjectionInternal(UID, PACKAGE_NAME);
}
+ /**
+ * Run the provided block giving the current context's package the provided role.
+ */
+ @SuppressWarnings("SameParameterValue")
+ private void runWithRole(String role, Runnable block) {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ String packageName = mContext.getPackageName();
+ UserHandle user = instrumentation.getTargetContext().getUser();
+ RoleManager roleManager = Objects.requireNonNull(
+ mContext.getSystemService(RoleManager.class));
+ try {
+ CountDownLatch latch = new CountDownLatch(1);
+ instrumentation.getUiAutomation().adoptShellPermissionIdentity(
+ Manifest.permission.MANAGE_ROLE_HOLDERS,
+ Manifest.permission.BYPASS_ROLE_QUALIFICATION);
+
+ roleManager.setBypassingRoleQualification(true);
+ roleManager.addRoleHolderAsUser(role, packageName, /* flags = */ 0, user,
+ mContext.getMainExecutor(), success -> {
+ if (success) {
+ latch.countDown();
+ } else {
+ Assert.fail("Couldn't set role for test (failure) " + role);
+ }
+ });
+ assertWithMessage("Couldn't set role for test (timeout) : " + role)
+ .that(latch.await(1, TimeUnit.SECONDS)).isTrue();
+ block.run();
+
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } finally {
+ roleManager.removeRoleHolderAsUser(role, packageName, 0, user,
+ mContext.getMainExecutor(), (aBool) -> {
+ });
+ roleManager.setBypassingRoleQualification(false);
+ instrumentation.getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+ }
+
private static class FakeIMediaProjectionCallback extends IMediaProjectionCallback.Stub {
CountDownLatch mLatch = new CountDownLatch(1);
@Override
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
index cf58d9b..c6cc941 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
@@ -70,6 +70,7 @@
import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
import platform.test.runner.parameterized.Parameters;
+
@SmallTest
@RunWith(ParameterizedAndroidJunit4.class)
@TestableLooper.RunWithLooper
@@ -147,6 +148,147 @@
}
@Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void testRuleDiff_toStringNoChangeAddRemove() throws Exception {
+ // Start with two identical rules
+ ZenModeConfig.ZenRule r1 = makeRule();
+ ZenModeConfig.ZenRule r2 = makeRule();
+
+ ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2);
+ assertThat(d.toString()).isEqualTo("ZenRuleDiff{no changes}");
+
+ d = new ZenModeDiff.RuleDiff(r1, null);
+ assertThat(d.toString()).isEqualTo("ZenRuleDiff{removed}");
+
+ d = new ZenModeDiff.RuleDiff(null, r2);
+ assertThat(d.toString()).isEqualTo("ZenRuleDiff{added}");
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void testRuleDiff_toString() throws Exception {
+ // Start with two identical rules
+ ZenModeConfig.ZenRule r1 = makeRule();
+ ZenModeConfig.ZenRule r2 = makeRule();
+
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(
+ ZenModeConfig.ZenRule.class, getZenRuleExemptFields(), false);
+ generateFieldDiffs(r1, r2, fieldsForDiff, expectedFrom, expectedTo);
+
+ ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2);
+ assertThat(d.toString()).isEqualTo("ZenRuleDiff{"
+ + "enabled:true->false, "
+ + "conditionOverride:2->1, "
+ + "name:string1->string2, "
+ + "zenMode:2->1, "
+ + "conditionId:null->, "
+ + "condition:null->Condition["
+ + "state=STATE_TRUE,"
+ + "id=hello:,"
+ + "summary=,"
+ + "line1=,"
+ + "line2=,"
+ + "icon=-1,"
+ + "source=SOURCE_UNKNOWN,"
+ + "flags=2], "
+ + "component:null->ComponentInfo{b/b}, "
+ + "configurationActivity:null->ComponentInfo{a/a}, "
+ + "id:string1->string2, "
+ + "creationTime:200->100, "
+ + "enabler:string1->string2, "
+ + "zenPolicy:ZenPolicyDiff{"
+ + "mPriorityCategories_Reminders:1->2, "
+ + "mPriorityCategories_Events:1->2, "
+ + "mPriorityCategories_Messages:1->2, "
+ + "mPriorityCategories_Calls:1->2, "
+ + "mPriorityCategories_RepeatCallers:1->2, "
+ + "mPriorityCategories_Alarms:1->2, "
+ + "mPriorityCategories_Media:1->2, "
+ + "mPriorityCategories_System:1->2, "
+ + "mPriorityCategories_Conversations:1->2, "
+ + "mVisualEffects_FullScreenIntent:1->2, "
+ + "mVisualEffects_Lights:1->2, "
+ + "mVisualEffects_Peek:1->2, "
+ + "mVisualEffects_StatusBar:1->2, "
+ + "mVisualEffects_Badge:1->2, "
+ + "mVisualEffects_Ambient:1->2, "
+ + "mVisualEffects_NotificationList:1->2, "
+ + "mPriorityMessages:2->1, "
+ + "mPriorityCalls:2->1, "
+ + "mConversationSenders:2->1, "
+ + "mAllowChannels:2->1}, "
+ + "modified:true->false, "
+ + "pkg:string1->string2, "
+ + "zenDeviceEffects:ZenDeviceEffectsDiff{"
+ + "mGrayscale:true->false, "
+ + "mSuppressAmbientDisplay:true->false, "
+ + "mDimWallpaper:true->false, "
+ + "mNightMode:true->false, "
+ + "mDisableAutoBrightness:true->false, "
+ + "mDisableTapToWake:true->false, "
+ + "mDisableTiltToWake:true->false, "
+ + "mDisableTouch:true->false, "
+ + "mMinimizeRadioUsage:true->false, "
+ + "mMaximizeDoze:true->false, "
+ + "mExtraEffects:[effect1]->[effect2]}, "
+ + "triggerDescription:string1->string2, "
+ + "type:2->1, "
+ + "allowManualInvocation:true->false, "
+ + "iconResName:string1->string2, "
+ + "legacySuppressedEffects:2->1}");
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void testRuleDiff_toStringNullStartPolicy() throws Exception {
+ // Start with two identical rules
+ ZenModeConfig.ZenRule r1 = makeRule();
+ ZenModeConfig.ZenRule r2 = makeRule();
+
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(
+ ZenModeConfig.ZenRule.class, getZenRuleExemptFields(), false);
+ generateFieldDiffs(r1, r2, fieldsForDiff, expectedFrom, expectedTo);
+
+ // Create a ZenRule with ZenDeviceEffects and ZenPolicy as null.
+ r1.zenPolicy = null;
+ r1.zenDeviceEffects = null;
+ ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2);
+ assertThat(d.toString()).isEqualTo("ZenRuleDiff{"
+ + "enabled:true->false, "
+ + "conditionOverride:2->1, "
+ + "name:string1->string2, "
+ + "zenMode:2->1, "
+ + "conditionId:null->, "
+ + "condition:null->Condition["
+ + "state=STATE_TRUE,"
+ + "id=hello:,"
+ + "summary=,"
+ + "line1=,"
+ + "line2=,"
+ + "icon=-1,"
+ + "source=SOURCE_UNKNOWN,"
+ + "flags=2], "
+ + "component:null->ComponentInfo{b/b}, "
+ + "configurationActivity:null->ComponentInfo{a/a}, "
+ + "id:string1->string2, "
+ + "creationTime:200->100, "
+ + "enabler:string1->string2, "
+ + "zenPolicy:ZenPolicyDiff{added}, "
+ + "modified:true->false, "
+ + "pkg:string1->string2, "
+ + "zenDeviceEffects:ZenDeviceEffectsDiff{added}, "
+ + "triggerDescription:string1->string2, "
+ + "type:2->1, "
+ + "allowManualInvocation:true->false, "
+ + "iconResName:string1->string2, "
+ + "legacySuppressedEffects:2->1}");
+ }
+
+ @Test
public void testDeviceEffectsDiff_addRemoveSame() {
// Test add, remove, and both sides same
ZenDeviceEffects effects = new ZenDeviceEffects.Builder().build();
@@ -216,12 +358,18 @@
generateFieldDiffs(effects1, effects2, fieldsForDiff, expectedFrom, expectedTo);
d = new ZenModeDiff.DeviceEffectsDiff(effects1, effects2);
- assertThat(d.toString()).isEqualTo("ZenDeviceEffectsDiff{mNightMode:true->false, "
- + "mDisableTapToWake:true->false, mDisableAutoBrightness:true->false, "
- + "mSuppressAmbientDisplay:true->false, mDisableTiltToWake:true->false, "
- + "mGrayscale:true->false, mDisableTouch:true->false, mMaximizeDoze:true->false, "
- + "mMinimizeRadioUsage:true->false, mExtraEffects:null->[], "
- + "mDimWallpaper:true->false}");
+ assertThat(d.toString()).isEqualTo("ZenDeviceEffectsDiff{"
+ + "mGrayscale:true->false, "
+ + "mSuppressAmbientDisplay:true->false, "
+ + "mDimWallpaper:true->false, "
+ + "mNightMode:true->false, "
+ + "mDisableAutoBrightness:true->false, "
+ + "mDisableTapToWake:true->false, "
+ + "mDisableTiltToWake:true->false, "
+ + "mDisableTouch:true->false, "
+ + "mMinimizeRadioUsage:true->false, "
+ + "mMaximizeDoze:true->false, "
+ + "mExtraEffects:[effect1]->[effect2]}");
}
@@ -307,17 +455,27 @@
generateFieldDiffsForZenPolicy(policy1, policy2, fieldsForDiff, expectedFrom, expectedTo);
d = new ZenModeDiff.PolicyDiff(policy1, policy2);
- assertThat(d.toString()).isEqualTo("ZenPolicyDiff{mPriorityCalls:2->1, "
- + "mVisualEffects_StatusBar:1->2, mPriorityCategories_RepeatCallers:1->2, "
- + "mPriorityCategories_Calls:1->2, mPriorityCategories_Media:1->2, "
- + "mConversationSenders:2->1, mPriorityCategories_Reminders:1->2, "
- + "mVisualEffects_Badge:1->2, mPriorityCategories_Messages:1->2, "
- + "mAllowChannels:2->1, mPriorityMessages:2->1, "
- + "mVisualEffects_NotificationList:1->2, mVisualEffects_FullScreenIntent:1->2, "
- + "mPriorityCategories_Alarms:1->2, mVisualEffects_Lights:1->2, "
- + "mPriorityCategories_Events:1->2, mVisualEffects_Ambient:1->2, "
- + "mPriorityCategories_System:1->2, mPriorityCategories_Conversations:1->2, "
- + "mVisualEffects_Peek:1->2}");
+ assertThat(d.toString()).isEqualTo("ZenPolicyDiff{"
+ + "mPriorityCategories_Reminders:1->2, "
+ + "mPriorityCategories_Events:1->2, "
+ + "mPriorityCategories_Messages:1->2, "
+ + "mPriorityCategories_Calls:1->2, "
+ + "mPriorityCategories_RepeatCallers:1->2, "
+ + "mPriorityCategories_Alarms:1->2, "
+ + "mPriorityCategories_Media:1->2, "
+ + "mPriorityCategories_System:1->2, "
+ + "mPriorityCategories_Conversations:1->2, "
+ + "mVisualEffects_FullScreenIntent:1->2, "
+ + "mVisualEffects_Lights:1->2, "
+ + "mVisualEffects_Peek:1->2, "
+ + "mVisualEffects_StatusBar:1->2, "
+ + "mVisualEffects_Badge:1->2, "
+ + "mVisualEffects_Ambient:1->2, "
+ + "mVisualEffects_NotificationList:1->2, "
+ + "mPriorityMessages:2->1, "
+ + "mPriorityCalls:2->1, "
+ + "mConversationSenders:2->1, "
+ + "mAllowChannels:2->1}");
}
private static Set<String> getZenRuleExemptFields() {
@@ -701,6 +859,44 @@
expectedA.put(f.getName(), "string1");
f.set(b, "string2");
expectedB.put(f.getName(), "string2");
+ } else if (Set.class.equals(t)) {
+ Set<String> aSet = Set.of("effect1");
+ Set<String> bSet = Set.of("effect2");
+ f.set(a, aSet);
+ expectedA.put(f.getName(), aSet);
+ f.set(b, bSet);
+ expectedB.put(f.getName(), bSet);
+ } else if (ZenDeviceEffects.class.equals(t)) {
+ // Recurse into generating field diffs for ZenDeviceEffects.
+ ZenDeviceEffects effects1 = new ZenDeviceEffects.Builder().build();
+ ZenDeviceEffects effects2 = new ZenDeviceEffects.Builder().build();
+ // maps mapping field name -> expected output value as we set diffs
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(
+ ZenDeviceEffects.class, Collections.emptySet() /*no exempt fields*/, true);
+ generateFieldDiffs(effects1, effects2, fieldsForDiff, expectedFrom, expectedTo);
+ f.set(a, effects1);
+ expectedA.put(f.getName(), effects1);
+ f.set(b, effects2);
+ expectedB.put(f.getName(), effects2);
+ } else if (ZenPolicy.class.equals(t)) {
+ // Recurse into generating field diffs for ZenPolicy.
+ ZenPolicy policy1 = new ZenPolicy.Builder().build();
+ ZenPolicy policy2 = new ZenPolicy.Builder().build();
+ // maps mapping field name -> expected output value as we set diffs
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(ZenPolicy.class,
+ Collections.emptySet(), false);
+ generateFieldDiffsForZenPolicy(policy1, policy2, fieldsForDiff, expectedFrom,
+ expectedTo);
+ f.set(a, policy1);
+ expectedA.put(f.getName(), policy1);
+ f.set(b, policy2);
+ expectedB.put(f.getName(), policy2);
} else {
// catch-all for other types: have the field be "added"
f.set(a, null);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
index b997f5d..a49f5a8 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
@@ -26,8 +26,6 @@
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS;
import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
-import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
import static android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS;
import static android.provider.Settings.Global.ZEN_MODE_OFF;
@@ -57,7 +55,6 @@
import androidx.test.filters.SmallTest;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.util.NotificationMessagingUtil;
import com.android.server.UiServiceTestCase;
@@ -188,49 +185,6 @@
}
@Test
- public void testSuppressDNDInfo_yes_VisEffectsAllowed() {
- NotificationRecord r = getNotificationRecord();
- when(r.getSbn().getPackageName()).thenReturn("android");
- when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE);
- Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects()
- - SUPPRESSED_EFFECT_STATUS_BAR, 0);
-
- assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
- }
-
- @Test
- public void testSuppressDNDInfo_yes_WrongId() {
- NotificationRecord r = getNotificationRecord();
- when(r.getSbn().getPackageName()).thenReturn("android");
- when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ACCOUNT_CREDENTIAL_PERMISSION);
- Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects(), 0);
-
- assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
- }
-
- @Test
- public void testSuppressDNDInfo_yes_WrongPackage() {
- NotificationRecord r = getNotificationRecord();
- when(r.getSbn().getPackageName()).thenReturn("android2");
- when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE);
- Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects(), 0);
-
- assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
- }
-
- @Test
- public void testSuppressDNDInfo_no() {
- NotificationRecord r = getNotificationRecord();
- when(r.getSbn().getPackageName()).thenReturn("android");
- when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE);
- Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects(), 0);
-
- assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
- assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_ALARMS, policy, r));
- assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_NO_INTERRUPTIONS, policy, r));
- }
-
- @Test
public void testSuppressAnything_yes_ZenModeOff() {
NotificationRecord r = getNotificationRecord();
when(r.getSbn().getPackageName()).thenReturn("bananas");
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 d4cba8d..294027b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -169,7 +169,6 @@
import com.android.internal.R;
import com.android.internal.config.sysui.TestableFlagResolver;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.os.AtomsProto;
@@ -747,54 +746,6 @@
}
@Test
- public void testZenUpgradeNotification() {
- /**
- * Commit a485ec65b5ba947d69158ad90905abf3310655cf disabled DND status change
- * notification on watches. So, assume that the device is not watch.
- */
- when(mContext.getPackageManager()).thenReturn(mPackageManager);
- when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(false);
-
- // shows zen upgrade notification if stored settings says to shows,
- // zen has not been updated, boot is completed
- // and we're setting zen mode on
- Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 1);
- Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 0);
- mZenModeHelper.mIsSystemServicesReady = true;
- mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
- mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-
- verify(mNotificationManager, times(1)).notify(eq(ZenModeHelper.TAG),
- eq(SystemMessage.NOTE_ZEN_UPGRADE), any());
- assertEquals(0, Settings.Secure.getInt(mContentResolver,
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, -1));
- }
-
- @Test
- public void testNoZenUpgradeNotification() {
- // doesn't show upgrade notification if stored settings says don't show
- Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
- Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 0);
- mZenModeHelper.mIsSystemServicesReady = true;
- mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-
- verify(mNotificationManager, never()).notify(eq(ZenModeHelper.TAG),
- eq(SystemMessage.NOTE_ZEN_UPGRADE), any());
- }
-
- @Test
- public void testNoZenUpgradeNotificationZenUpdated() {
- // doesn't show upgrade notification since zen was already updated
- Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
- Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 1);
- mZenModeHelper.mIsSystemServicesReady = true;
- mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-
- verify(mNotificationManager, never()).notify(eq(ZenModeHelper.TAG),
- eq(SystemMessage.NOTE_ZEN_UPGRADE), any());
- }
-
- @Test
public void testZenSetInternalRinger_AllPriorityNotificationSoundsMuted() {
AudioManagerInternal mAudioManager = mock(AudioManagerInternal.class);
mZenModeHelper.mAudioManager = mAudioManager;
@@ -3032,6 +2983,33 @@
}
@Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void updateAutomaticZenRule_withTypeBedtime_replacesDisabledSleeping() {
+ ZenRule sleepingRule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID);
+ sleepingRule.enabled = false;
+ sleepingRule.userModifiedFields = 0;
+ sleepingRule.name = "ZZZZZZZ...";
+ mZenModeHelper.mConfig.automaticRules.clear();
+ mZenModeHelper.mConfig.automaticRules.put(sleepingRule.id, sleepingRule);
+
+ AutomaticZenRule futureBedtime = new AutomaticZenRule.Builder("Bedtime (?)", CONDITION_ID)
+ .build();
+ String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule(mPkg, futureBedtime,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.mConfig.automaticRules.keySet())
+ .containsExactly(sleepingRule.id, bedtimeRuleId);
+
+ AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime (!)", CONDITION_ID)
+ .setType(TYPE_BEDTIME)
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(bedtimeRuleId, bedtime, ORIGIN_APP, "reason",
+ CUSTOM_PKG_UID);
+
+ assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(bedtimeRuleId);
+ }
+
+ @Test
@EnableFlags(FLAG_MODES_API)
public void testSetManualZenMode() {
setupZenConfig();
diff --git a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
index 9b92ff4..3ea3235 100644
--- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
@@ -23,6 +23,7 @@
import static android.view.KeyEvent.KEYCODE_STEM_PRIMARY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.server.policy.PhoneWindowManager.DOUBLE_PRESS_PRIMARY_LAUNCH_DEFAULT_FITNESS_APP;
import static com.android.server.policy.PhoneWindowManager.DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT;
import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS;
@@ -32,6 +33,7 @@
import android.app.ActivityManager.RecentTaskInfo;
import android.app.ActivityTaskManager.RootTaskInfo;
import android.content.ComponentName;
+import android.hardware.input.KeyGestureEvent;
import android.os.RemoteException;
import android.provider.Settings;
import android.view.Display;
@@ -236,6 +238,19 @@
}
@Test
+ public void stemDoubleKey_behaviorIsLaunchFitness_gestureEventFired() {
+ overrideBehavior(
+ STEM_PRIMARY_BUTTON_DOUBLE_PRESS, DOUBLE_PRESS_PRIMARY_LAUNCH_DEFAULT_FITNESS_APP);
+ setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+
+ sendKey(KEYCODE_STEM_PRIMARY);
+ sendKey(KEYCODE_STEM_PRIMARY);
+
+ mPhoneWindowManager.assertKeyGestureEventSentToKeyGestureController(
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS);
+ }
+
+ @Test
public void stemTripleKey_EarlyShortPress_AllAppsThenBackToOriginalThenToggleA11y()
throws RemoteException {
overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 1aa9087..3dc893a 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -700,7 +700,7 @@
void assertPowerWakeUp() {
mTestLooper.dispatchAll();
verify(mWindowWakeUpPolicy)
- .wakeUpFromKey(anyLong(), eq(KeyEvent.KEYCODE_POWER), anyBoolean());
+ .wakeUpFromKey(anyInt(), anyLong(), eq(KeyEvent.KEYCODE_POWER), anyBoolean());
}
void assertNoPowerSleep() {
@@ -919,4 +919,9 @@
mTestLooper.dispatchAll();
Assert.assertEquals(expectEnabled, mIsTalkBackEnabled);
}
+
+ void assertKeyGestureEventSentToKeyGestureController(int gestureType) {
+ verify(mInputManagerInternal)
+ .handleKeyGestureInKeyGestureController(anyInt(), any(), anyInt(), eq(gestureType));
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
index 7322e5a..4119ad3 100644
--- a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
@@ -35,6 +35,7 @@
import static com.android.internal.R.bool.config_allowTheaterModeWakeFromLidSwitch;
import static com.android.internal.R.bool.config_allowTheaterModeWakeFromGesture;
import static com.android.server.policy.Flags.FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE;
+import static com.android.server.power.feature.flags.Flags.FLAG_PER_DISPLAY_WAKE_BY_TOUCH;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -43,6 +44,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -52,6 +54,8 @@
import android.content.ContextWrapper;
import android.content.res.Resources;
import android.os.PowerManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.view.Display;
@@ -125,6 +129,7 @@
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testMotionWakeUpDelegation_wakePowerManagerIfDelegateDoesNotHandleWake() {
setTheaterModeEnabled(false);
mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
@@ -136,7 +141,8 @@
// Verify the policy wake up call succeeds because of the call on the delegate, and not
// because of a PowerManager wake up.
- assertThat(mPolicy.wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true)).isTrue();
+ assertThat(mPolicy.wakeUpFromMotion(
+ mDefaultDisplay.getDisplayId(), 200, SOURCE_TOUCHSCREEN, true)).isTrue();
verify(mInputWakeUpDelegate).wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true);
verifyNoPowerManagerWakeUp();
@@ -144,12 +150,14 @@
// Verify the policy wake up call succeeds because of the PowerManager wake up, since the
// delegate would not handle the wake up request.
- assertThat(mPolicy.wakeUpFromMotion(300, SOURCE_ROTARY_ENCODER, false)).isTrue();
+ assertThat(mPolicy.wakeUpFromMotion(
+ mDefaultDisplay.getDisplayId(), 300, SOURCE_ROTARY_ENCODER, false)).isTrue();
verify(mInputWakeUpDelegate).wakeUpFromMotion(300, SOURCE_ROTARY_ENCODER, false);
verify(mPowerManager).wakeUp(300, WAKE_REASON_WAKE_MOTION, "android.policy:MOTION");
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testKeyWakeUpDelegation_wakePowerManagerIfDelegateDoesNotHandleWake() {
setTheaterModeEnabled(false);
mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
@@ -161,7 +169,8 @@
// Verify the policy wake up call succeeds because of the call on the delegate, and not
// because of a PowerManager wake up.
- assertThat(mPolicy.wakeUpFromKey(200, KEYCODE_POWER, true)).isTrue();
+ assertThat(mPolicy.wakeUpFromKey(
+ mDefaultDisplay.getDisplayId(), 200, KEYCODE_POWER, true)).isTrue();
verify(mInputWakeUpDelegate).wakeUpFromKey(200, KEYCODE_POWER, true);
verifyNoPowerManagerWakeUp();
@@ -169,7 +178,8 @@
// Verify the policy wake up call succeeds because of the PowerManager wake up, since the
// delegate would not handle the wake up request.
- assertThat(mPolicy.wakeUpFromKey(300, KEYCODE_STEM_PRIMARY, false)).isTrue();
+ assertThat(mPolicy.wakeUpFromKey(
+ mDefaultDisplay.getDisplayId(), 300, KEYCODE_STEM_PRIMARY, false)).isTrue();
verify(mInputWakeUpDelegate).wakeUpFromKey(300, KEYCODE_STEM_PRIMARY, false);
verify(mPowerManager).wakeUp(300, WAKE_REASON_WAKE_KEY, "android.policy:KEY");
}
@@ -186,7 +196,8 @@
.setInputWakeUpDelegate(mInputWakeUpDelegate);
// Check that the wake up does not happen because the theater mode policy check fails.
- assertThat(mPolicy.wakeUpFromKey(200, KEYCODE_POWER, true)).isFalse();
+ assertThat(mPolicy.wakeUpFromKey(
+ mDefaultDisplay.getDisplayId(), 200, KEYCODE_POWER, true)).isFalse();
verify(mInputWakeUpDelegate, never()).wakeUpFromKey(anyLong(), anyInt(), anyBoolean());
}
@@ -201,11 +212,13 @@
.setInputWakeUpDelegate(mInputWakeUpDelegate);
// Check that the wake up does not happen because the theater mode policy check fails.
- assertThat(mPolicy.wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true)).isFalse();
+ assertThat(mPolicy.wakeUpFromMotion(
+ mDefaultDisplay.getDisplayId(), 200, SOURCE_TOUCHSCREEN, true)).isFalse();
verify(mInputWakeUpDelegate, never()).wakeUpFromMotion(anyLong(), anyInt(), anyBoolean());
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testTheaterModeChecksNotAppliedWhenScreenIsOn() {
mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
setDefaultDisplayState(Display.STATE_ON);
@@ -213,30 +226,69 @@
setBooleanRes(config_allowTheaterModeWakeFromMotion, false);
mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
- mPolicy.wakeUpFromMotion(200L, SOURCE_TOUCHSCREEN, true);
+ mPolicy.wakeUpFromMotion(mDefaultDisplay.getDisplayId(), 200L, SOURCE_TOUCHSCREEN, true);
verify(mPowerManager).wakeUp(200L, WAKE_REASON_WAKE_MOTION, "android.policy:MOTION");
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromMotion() {
runPowerManagerUpChecks(
- () -> mPolicy.wakeUpFromMotion(mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, true),
+ () -> mPolicy.wakeUpFromMotion(mDefaultDisplay.getDisplayId(),
+ mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, true),
config_allowTheaterModeWakeFromMotion,
WAKE_REASON_WAKE_MOTION,
"android.policy:MOTION");
}
@Test
+ @EnableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
+ public void testWakeUpFromMotion_perDisplayWakeByTouchEnabled() {
+ setTheaterModeEnabled(false);
+ final int displayId = 555;
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+
+ boolean displayWokeUp = mPolicy.wakeUpFromMotion(
+ displayId, mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, /* isDown= */ true);
+
+ // Verify that display is woken up
+ assertThat(displayWokeUp).isTrue();
+ verify(mPowerManager).wakeUp(anyLong(), eq(WAKE_REASON_WAKE_MOTION),
+ eq("android.policy:MOTION"), eq(displayId));
+ }
+
+ @Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
+ public void testWakeUpFromMotion_perDisplayWakeByTouchDisabled() {
+ setTheaterModeEnabled(false);
+ final int displayId = 555;
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+
+ boolean displayWokeUp = mPolicy.wakeUpFromMotion(
+ displayId, mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, /* isDown= */ true);
+
+ // Verify that power is woken up and display isn't woken up individually
+ assertThat(displayWokeUp).isTrue();
+ verify(mPowerManager).wakeUp(
+ anyLong(), eq(WAKE_REASON_WAKE_MOTION), eq("android.policy:MOTION"));
+ verify(mPowerManager, never()).wakeUp(anyLong(), eq(WAKE_REASON_WAKE_MOTION),
+ eq("android.policy:MOTION"), eq(displayId));
+ }
+
+ @Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromKey_nonPowerKey() {
runPowerManagerUpChecks(
- () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_HOME, true),
+ () -> mPolicy.wakeUpFromKey(
+ mDefaultDisplay.getDisplayId(), mClock.uptimeMillis(), KEYCODE_HOME, true),
config_allowTheaterModeWakeFromKey,
WAKE_REASON_WAKE_KEY,
"android.policy:KEY");
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromKey_powerKey() {
// Disable the resource affecting all wake keys because it affects power key as well.
// That way, power key wake during theater mode will solely be controlled by
@@ -245,7 +297,8 @@
// Test with power key
runPowerManagerUpChecks(
- () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_POWER, true),
+ () -> mPolicy.wakeUpFromKey(
+ mDefaultDisplay.getDisplayId(), mClock.uptimeMillis(), KEYCODE_POWER, true),
config_allowTheaterModeWakeFromPowerKey,
WAKE_REASON_POWER_BUTTON,
"android.policy:POWER");
@@ -254,13 +307,15 @@
// even if the power-key specific theater mode config is disabled.
setBooleanRes(config_allowTheaterModeWakeFromPowerKey, false);
runPowerManagerUpChecks(
- () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_POWER, false),
+ () -> mPolicy.wakeUpFromKey(mDefaultDisplay.getDisplayId(), mClock.uptimeMillis(),
+ KEYCODE_POWER, false),
config_allowTheaterModeWakeFromKey,
WAKE_REASON_POWER_BUTTON,
"android.policy:POWER");
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromLid() {
runPowerManagerUpChecks(
() -> mPolicy.wakeUpFromLid(),
@@ -270,6 +325,7 @@
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromWakeGesture() {
runPowerManagerUpChecks(
() -> mPolicy.wakeUpFromWakeGesture(),
@@ -279,6 +335,7 @@
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testwakeUpFromCameraCover() {
runPowerManagerUpChecks(
() -> mPolicy.wakeUpFromCameraCover(mClock.uptimeMillis()),
@@ -288,6 +345,7 @@
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromPowerKeyCameraGesture() {
// Disable the resource affecting all wake keys because it affects power key as well.
// That way, power key wake during theater mode will solely be controlled by
@@ -295,7 +353,7 @@
setBooleanRes(config_allowTheaterModeWakeFromKey, false);
runPowerManagerUpChecks(
- () -> mPolicy.wakeUpFromPowerKeyCameraGesture(),
+ () -> mPolicy.wakeUpFromPowerKeyCameraGesture(mDefaultDisplay.getDisplayId()),
config_allowTheaterModeWakeFromPowerKey,
WAKE_REASON_CAMERA_LAUNCH,
"android.policy:CAMERA_GESTURE_PREVENT_LOCK");
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index adc969c..72f4fa91 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4881,6 +4881,25 @@
assertNotEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOverrideOrientation());
}
+
+ @Test
+ @EnableCompatChanges({ActivityRecord.UNIVERSAL_RESIZABLE_BY_DEFAULT})
+ public void testUniversalResizeableByDefault() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT);
+ mDisplayContent.setIgnoreOrientationRequest(false);
+ setUpApp(mDisplayContent);
+ assertFalse(mActivity.isUniversalResizeable());
+
+ mDisplayContent.setIgnoreOrientationRequest(true);
+ final int swDp = mDisplayContent.getConfiguration().smallestScreenWidthDp;
+ if (swDp < WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP) {
+ final int height = 100 + (int) (mDisplayContent.getDisplayMetrics().density
+ * WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP);
+ resizeDisplay(mDisplayContent, 100 + height, height);
+ }
+ assertTrue(mActivity.isUniversalResizeable());
+ }
+
@Test
public void testClearSizeCompat_resetOverrideConfig() {
final int origDensity = 480;
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ff302f6..ff966ae 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2298,13 +2298,9 @@
*
* See {@link #getImei(int)} for details on the required permissions and behavior
* when the caller does not hold sufficient permissions.
- *
- * @throws UnsupportedOperationException If the device does not have
- * {@link PackageManager#FEATURE_TELEPHONY_GSM}.
*/
@SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM)
public String getImei() {
return getImei(getSlotIndex());
}
@@ -2343,13 +2339,9 @@
* </ul>
*
* @param slotIndex of which IMEI is returned
- *
- * @throws UnsupportedOperationException If the device does not have
- * {@link PackageManager#FEATURE_TELEPHONY_GSM}.
*/
@SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM)
public String getImei(int slotIndex) {
ITelephony telephony = getITelephony();
if (telephony == null) return null;
@@ -2366,11 +2358,7 @@
/**
* Returns the Type Allocation Code from the IMEI. Return null if Type Allocation Code is not
* available.
- *
- * @throws UnsupportedOperationException If the device does not have
- * {@link PackageManager#FEATURE_TELEPHONY_GSM}.
*/
- @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM)
@Nullable
public String getTypeAllocationCode() {
return getTypeAllocationCode(getSlotIndex());
@@ -2381,11 +2369,7 @@
* available.
*
* @param slotIndex of which Type Allocation Code is returned
- *
- * @throws UnsupportedOperationException If the device does not have
- * {@link PackageManager#FEATURE_TELEPHONY_GSM}.
*/
- @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM)
@Nullable
public String getTypeAllocationCode(int slotIndex) {
ITelephony telephony = getITelephony();
@@ -19378,12 +19362,9 @@
* </ul>
*
* @return Primary IMEI of type string
- * @throws UnsupportedOperationException If the device does not have
- * {@link PackageManager#FEATURE_TELEPHONY_GSM}.
* @throws SecurityException if the caller does not have the required permission/privileges
*/
@NonNull
- @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM)
public String getPrimaryImei() {
try {
ITelephony telephony = getITelephony();
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index bd5c759..49ca6f3 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -249,6 +249,13 @@
public static final String KEY_PROVISION_SATELLITE_TOKENS = "provision_satellite";
/**
+ * Bundle key to get the response from
+ * {@link #deprovisionSatellite(List, Executor, OutcomeReceiver)}.
+ * @hide
+ */
+ public static final String KEY_DEPROVISION_SATELLITE_TOKENS = "deprovision_satellite";
+
+ /**
* The request was successfully processed.
*/
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -2791,6 +2798,61 @@
}
}
+ /**
+ * Deliver the list of deprovisioned satellite subscriber infos.
+ *
+ * @param list The list of deprovisioned satellite subscriber infos.
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback object to which the result will be delivered.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ public void deprovisionSatellite(@NonNull List<SatelliteSubscriberInfo> list,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ ResultReceiver receiver = new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == SATELLITE_RESULT_SUCCESS) {
+ if (resultData.containsKey(KEY_DEPROVISION_SATELLITE_TOKENS)) {
+ boolean isUpdated =
+ resultData.getBoolean(KEY_DEPROVISION_SATELLITE_TOKENS);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onResult(isUpdated)));
+ } else {
+ loge("KEY_DEPROVISION_SATELLITE_TOKENS does not exist.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(
+ SATELLITE_RESULT_REQUEST_FAILED))));
+ }
+ } else {
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(resultCode))));
+ }
+ }
+ };
+ telephony.deprovisionSatellite(list, receiver);
+ } else {
+ loge("deprovisionSatellite() invalid telephony");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+ }
+ } catch (RemoteException ex) {
+ loge("deprovisionSatellite() RemoteException: " + ex);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+ }
+ }
+
@Nullable
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 3161d17..61f0146 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3444,4 +3444,15 @@
*/
boolean overrideCarrierRoamingNtnEligibilityChanged(
in boolean status, in boolean resetRequired);
+
+ /**
+ * Deliver the list of deprovisioned satellite subscriber infos.
+ *
+ * @param list The list of deprovisioned satellite subscriber infos.
+ * @param result The result receiver that returns whether deliver success or fail.
+ * @hide
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void deprovisionSatellite(in List<SatelliteSubscriberInfo> list, in ResultReceiver result);
}
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java
index 9d56a92..8ecddaa 100644
--- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java
@@ -16,6 +16,8 @@
package com.android.internal.protolog;
+import static org.junit.Assert.assertThrows;
+
import android.platform.test.annotations.Presubmit;
import com.android.internal.protolog.common.IProtoLogGroup;
@@ -44,8 +46,29 @@
.containsExactly(TEST_GROUP_1, TEST_GROUP_2);
}
+ @Test
+ public void throwOnRegisteringDuplicateGroup() {
+ final var assertion = assertThrows(RuntimeException.class,
+ () -> ProtoLog.init(TEST_GROUP_1, TEST_GROUP_1, TEST_GROUP_2));
+
+ Truth.assertThat(assertion).hasMessageThat().contains("" + TEST_GROUP_1.getId());
+ Truth.assertThat(assertion).hasMessageThat().contains("duplicate");
+ }
+
+ @Test
+ public void throwOnRegisteringGroupsWithIdCollisions() {
+ final var assertion = assertThrows(RuntimeException.class,
+ () -> ProtoLog.init(TEST_GROUP_1, TEST_GROUP_WITH_COLLISION, TEST_GROUP_2));
+
+ Truth.assertThat(assertion).hasMessageThat()
+ .contains("" + TEST_GROUP_WITH_COLLISION.getId());
+ Truth.assertThat(assertion).hasMessageThat().contains("collision");
+ }
+
private static final IProtoLogGroup TEST_GROUP_1 = new ProtoLogGroup("TEST_TAG_1", 1);
private static final IProtoLogGroup TEST_GROUP_2 = new ProtoLogGroup("TEST_TAG_2", 2);
+ private static final IProtoLogGroup TEST_GROUP_WITH_COLLISION =
+ new ProtoLogGroup("TEST_TAG_WITH_COLLISION", 1);
private static class ProtoLogGroup implements IProtoLogGroup {
private final boolean mEnabled;
diff --git a/tests/vcn/java/android/net/vcn/VcnTransportInfoTest.java b/tests/vcn/java/android/net/vcn/VcnTransportInfoTest.java
index 81814b6..7bc9970 100644
--- a/tests/vcn/java/android/net/vcn/VcnTransportInfoTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnTransportInfoTest.java
@@ -25,6 +25,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
import android.net.NetworkCapabilities;
import android.net.wifi.WifiConfiguration;
@@ -39,6 +40,7 @@
private static final int SUB_ID = 1;
private static final int NETWORK_ID = 5;
private static final int MIN_UDP_PORT_4500_NAT_TIMEOUT = 120;
+ private static final int MIN_UDP_PORT_4500_NAT_TIMEOUT_INVALID = 119;
private static final WifiInfo WIFI_INFO =
new WifiInfo.Builder().setNetworkId(NETWORK_ID).build();
@@ -48,6 +50,27 @@
new VcnTransportInfo(WIFI_INFO, MIN_UDP_PORT_4500_NAT_TIMEOUT);
@Test
+ public void testBuilder() {
+ final VcnTransportInfo transportInfo =
+ new VcnTransportInfo.Builder()
+ .setMinUdpPort4500NatTimeoutSeconds(MIN_UDP_PORT_4500_NAT_TIMEOUT)
+ .build();
+
+ assertEquals(
+ MIN_UDP_PORT_4500_NAT_TIMEOUT, transportInfo.getMinUdpPort4500NatTimeoutSeconds());
+ }
+
+ @Test
+ public void testBuilder_withInvalidNatTimeout() {
+ try {
+ new VcnTransportInfo.Builder()
+ .setMinUdpPort4500NatTimeoutSeconds(MIN_UDP_PORT_4500_NAT_TIMEOUT_INVALID);
+ fail("Expected to fail due to invalid NAT timeout");
+ } catch (Exception expected) {
+ }
+ }
+
+ @Test
public void testGetWifiInfo() {
assertEquals(WIFI_INFO, WIFI_UNDERLYING_INFO.getWifiInfo());
diff --git a/tests/vcn/java/android/net/vcn/VcnUtilsTest.java b/tests/vcn/java/android/net/vcn/VcnUtilsTest.java
new file mode 100644
index 0000000..3ce6c8f
--- /dev/null
+++ b/tests/vcn/java/android/net/vcn/VcnUtilsTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2021 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.net.vcn;
+
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.wifi.WifiInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+public class VcnUtilsTest {
+ private static final int SUB_ID = 1;
+
+ private static final WifiInfo WIFI_INFO = new WifiInfo.Builder().build();
+ private static final TelephonyNetworkSpecifier TEL_NETWORK_SPECIFIER =
+ new TelephonyNetworkSpecifier.Builder().setSubscriptionId(SUB_ID).build();
+ private static final VcnTransportInfo VCN_TRANSPORT_INFO =
+ new VcnTransportInfo.Builder().build();
+
+ private ConnectivityManager mMockConnectivityManager;
+ private Network mMockWifiNetwork;
+ private Network mMockCellNetwork;
+
+ private NetworkCapabilities mVcnCapsWithUnderlyingWifi;
+ private NetworkCapabilities mVcnCapsWithUnderlyingCell;
+
+ @Before
+ public void setUp() {
+ mMockConnectivityManager = mock(ConnectivityManager.class);
+
+ mMockWifiNetwork = mock(Network.class);
+ mVcnCapsWithUnderlyingWifi = newVcnCaps(VCN_TRANSPORT_INFO, mMockWifiNetwork);
+ final NetworkCapabilities wifiCaps =
+ new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .setTransportInfo(WIFI_INFO)
+ .build();
+ when(mMockConnectivityManager.getNetworkCapabilities(mMockWifiNetwork))
+ .thenReturn(wifiCaps);
+
+ mMockCellNetwork = mock(Network.class);
+ mVcnCapsWithUnderlyingCell = newVcnCaps(VCN_TRANSPORT_INFO, mMockCellNetwork);
+ final NetworkCapabilities cellCaps =
+ new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .setNetworkSpecifier(TEL_NETWORK_SPECIFIER)
+ .build();
+ when(mMockConnectivityManager.getNetworkCapabilities(mMockCellNetwork))
+ .thenReturn(cellCaps);
+ }
+
+ private static NetworkCapabilities newVcnCaps(
+ VcnTransportInfo vcnTransportInfo, Network underlyingNetwork) {
+ return new NetworkCapabilities.Builder()
+ .setTransportInfo(vcnTransportInfo)
+ .setUnderlyingNetworks(Collections.singletonList(underlyingNetwork))
+ .build();
+ }
+
+ @Test
+ public void getWifiInfoFromVcnCaps() {
+ assertEquals(
+ WIFI_INFO,
+ VcnUtils.getWifiInfoFromVcnCaps(
+ mMockConnectivityManager, mVcnCapsWithUnderlyingWifi));
+ }
+
+ @Test
+ public void getWifiInfoFromVcnCaps_onVcnWithUnderlyingCell() {
+ assertNull(
+ VcnUtils.getWifiInfoFromVcnCaps(
+ mMockConnectivityManager, mVcnCapsWithUnderlyingCell));
+ }
+
+ @Test
+ public void getSubIdFromVcnCaps() {
+ assertEquals(
+ SUB_ID,
+ VcnUtils.getSubIdFromVcnCaps(mMockConnectivityManager, mVcnCapsWithUnderlyingCell));
+ }
+
+ @Test
+ public void getSubIdFromVcnCaps_onVcnWithUnderlyingWifi() {
+ assertEquals(
+ INVALID_SUBSCRIPTION_ID,
+ VcnUtils.getSubIdFromVcnCaps(mMockConnectivityManager, mVcnCapsWithUnderlyingWifi));
+ }
+
+ @Test
+ public void getSubIdFromVcnCaps_onNonVcnNetwork() {
+ assertEquals(
+ INVALID_SUBSCRIPTION_ID,
+ VcnUtils.getSubIdFromVcnCaps(
+ mMockConnectivityManager, new NetworkCapabilities.Builder().build()));
+ }
+
+ @Test
+ public void getSubIdFromVcnCaps_withMultipleUnderlyingNetworks() {
+ final NetworkCapabilities vcnCaps =
+ new NetworkCapabilities.Builder(mVcnCapsWithUnderlyingCell)
+ .setUnderlyingNetworks(
+ Arrays.asList(
+ new Network[] {mMockCellNetwork, mock(Network.class)}))
+ .build();
+ assertEquals(SUB_ID, VcnUtils.getSubIdFromVcnCaps(mMockConnectivityManager, vcnCaps));
+ }
+}
diff --git a/tools/aapt/StringPool.cpp b/tools/aapt/StringPool.cpp
index 1af8d6f..b2e48bd 100644
--- a/tools/aapt/StringPool.cpp
+++ b/tools/aapt/StringPool.cpp
@@ -40,7 +40,7 @@
void printStringPool(const ResStringPool* pool)
{
if (pool->getError() == NO_INIT) {
- printf("String pool is unitialized.\n");
+ printf("String pool is uninitialized.\n");
return;
} else if (pool->getError() != NO_ERROR) {
printf("String pool is corrupt/invalid.\n");
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 064b461..2527dcd 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -445,7 +445,7 @@
using namespace android;
if (pool->getError() == NO_INIT) {
- printer->Print("String pool is unitialized.\n");
+ printer->Print("String pool is uninitialized.\n");
return;
} else if (pool->getError() != NO_ERROR) {
printer->Print("String pool is corrupt/invalid.\n");