Merge "Inline several methods in IMMS#attachNewInputLocked()" into main
diff --git a/Android.bp b/Android.bp
index 2becf07..fb1fa3b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -115,7 +115,7 @@
":android.security.legacykeystore-java-source",
":android.security.maintenance-java-source",
":android.security.metrics-java-source",
- ":android.system.keystore2-V3-java-source",
+ ":android.system.keystore2-V4-java-source",
":android.hardware.cas-V1-java-source",
":credstore_aidl",
":dumpstate_aidl",
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index c4ffa34..fea2b7b 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -33,4 +33,4 @@
flag_hook = ${REPO_ROOT}/frameworks/base/packages/SystemUI/flag_check.py --msg=${PREUPLOAD_COMMIT_MESSAGE} --files=${PREUPLOAD_FILES} --project=${REPO_PROJECT}
[Tool Paths]
-ktfmt = ${REPO_ROOT}/prebuilts/build-tools/common/framework/ktfmt.jar
+ktfmt = ${REPO_ROOT}/external/ktfmt/ktfmt.sh
diff --git a/cmds/uiautomator/library/Android.bp b/cmds/uiautomator/library/Android.bp
index cffc078..cd1fb9a 100644
--- a/cmds/uiautomator/library/Android.bp
+++ b/cmds/uiautomator/library/Android.bp
@@ -35,7 +35,7 @@
],
installable: false,
flags: [
- "-stubpackages com.android.uiautomator.core:com.android.uiautomator.testrunner",
+ "--stub-packages com.android.uiautomator.core:com.android.uiautomator.testrunner",
],
check_api: {
diff --git a/core/api/current.txt b/core/api/current.txt
index 9d506b4..831cf01 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -15974,6 +15974,7 @@
enum_constant public static final android.graphics.ColorSpace.Named LINEAR_EXTENDED_SRGB;
enum_constant public static final android.graphics.ColorSpace.Named LINEAR_SRGB;
enum_constant public static final android.graphics.ColorSpace.Named NTSC_1953;
+ enum_constant @FlaggedApi("com.android.graphics.flags.ok_lab_colorspace") public static final android.graphics.ColorSpace.Named OK_LAB;
enum_constant public static final android.graphics.ColorSpace.Named PRO_PHOTO_RGB;
enum_constant public static final android.graphics.ColorSpace.Named SMPTE_C;
enum_constant public static final android.graphics.ColorSpace.Named SRGB;
@@ -47132,6 +47133,8 @@
method public int describeContents();
method public int getAttributeFlags();
method @NonNull public java.util.Set<java.lang.String> getFeatureTags();
+ method @FlaggedApi("com.android.internal.telephony.flags.emergency_registration_state") public boolean getFlagRegistrationTypeEmergency();
+ method @FlaggedApi("com.android.internal.telephony.flags.emergency_registration_state") public boolean getFlagVirtualRegistrationForEmergencyCall();
method @Nullable public android.telephony.ims.SipDetails getSipDetails();
method public int getTransportType();
method public void writeToParcel(@NonNull android.os.Parcel, int);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 1c247a6..807fa48 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -7555,6 +7555,12 @@
* the activity to be restarted). Otherwise, this will be used the next
* time the activity is visible.
*
+ * <aside class="note"><b>Note:</b> Device manufacturers can configure devices to override
+ * (ignore) calls to this method to improve the layout of orientation-restricted apps. See
+ * <a href="{@docRoot}guide/practices/device-compatibility-mode">
+ * Device compatibility mode</a>.
+ * </aside>
+ *
* @param requestedOrientation An orientation constant as used in
* {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}.
*/
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 15b13dc..ffb920b 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -757,6 +757,15 @@
void addStartInfoTimestamp(int key, long timestampNs, int userId);
/**
+ * Reports view related timestamps to be added to the calling apps most
+ * recent {@link ApplicationStartInfo}.
+ *
+ * @param renderThreadDrawStartTimeNs Clock monotonic time in nanoseconds of RenderThread draw start
+ * @param framePresentedTimeNs Clock monotonic time in nanoseconds of frame presented
+ */
+ oneway void reportStartInfoViewTimestamps(long renderThreadDrawStartTimeNs, long framePresentedTimeNs);
+
+ /**
* Return a list of {@link ApplicationExitInfo} records.
*
* <p class="note"> Note: System stores these historical information in a ring buffer, older
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index cf06416..5bc0ddc 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -42,6 +42,7 @@
import android.service.notification.ZenPolicy;
import android.app.AutomaticZenRule;
import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenDeviceEffects;
/** {@hide} */
interface INotificationManager
@@ -227,6 +228,7 @@
int getRuleInstanceCount(in ComponentName owner);
int getAutomaticZenRuleState(String id);
void setAutomaticZenRuleState(String id, in Condition condition);
+ void setManualZenRuleDeviceEffects(in ZenDeviceEffects effects);
byte[] getBackupPayload(int user);
void applyRestore(in byte[] payload, int user);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index fc3bb02..a1fa404 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2239,9 +2239,6 @@
private void visitUris(@NonNull Consumer<Uri> visitor) {
visitIconUri(visitor, getIcon());
- if (actionIntent != null) {
- actionIntent.visitUris(visitor);
- }
}
@Override
@@ -2957,21 +2954,6 @@
}
}
- // allPendingIntents should contain all associated intents after parcelling, but it may also
- // contain intents added by the app to extras for their own purposes. We only care about
- // checking the intents known and used by system_server, to avoid the confused deputy issue.
- List<PendingIntent> pendingIntents = Arrays.asList(contentIntent, deleteIntent,
- fullScreenIntent);
- for (PendingIntent intent : pendingIntents) {
- if (intent != null) {
- intent.visitUris(visitor);
- }
- }
-
- if (mBubbleMetadata != null) {
- mBubbleMetadata.visitUris(visitor);
- }
-
if (extras != null) {
visitIconUri(visitor, extras.getParcelable(EXTRA_LARGE_ICON_BIG, Icon.class));
visitIconUri(visitor, extras.getParcelable(EXTRA_PICTURE_ICON, Icon.class));
@@ -3043,28 +3025,15 @@
callPerson.visitUris(visitor);
}
visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON, Icon.class));
+ }
- // Extras for MediaStyle.
- PendingIntent deviceIntent = extras.getParcelable(EXTRA_MEDIA_REMOTE_INTENT,
- PendingIntent.class);
- if (deviceIntent != null) {
- deviceIntent.visitUris(visitor);
- }
+ if (mBubbleMetadata != null) {
+ visitIconUri(visitor, mBubbleMetadata.getIcon());
+ }
- if (extras.containsKey(WearableExtender.EXTRA_WEARABLE_EXTENSIONS)) {
- WearableExtender extender = new WearableExtender(this);
- extender.visitUris(visitor);
- }
-
- if (extras.containsKey(TvExtender.EXTRA_TV_EXTENDER)) {
- TvExtender extender = new TvExtender(this);
- extender.visitUris(visitor);
- }
-
- if (extras.containsKey(CarExtender.EXTRA_CAR_EXTENDER)) {
- CarExtender extender = new CarExtender(this);
- extender.visitUris(visitor);
- }
+ if (extras != null && extras.containsKey(WearableExtender.EXTRA_WEARABLE_EXTENSIONS)) {
+ WearableExtender extender = new WearableExtender(this);
+ extender.visitUris(visitor);
}
}
@@ -11459,16 +11428,6 @@
}
}
- private void visitUris(@NonNull Consumer<Uri> visitor) {
- visitIconUri(visitor, getIcon());
- if (mPendingIntent != null) {
- mPendingIntent.visitUris(visitor);
- }
- if (mDeleteIntent != null) {
- mDeleteIntent.visitUris(visitor);
- }
- }
-
/**
* Builder to construct a {@link BubbleMetadata} object.
*/
@@ -12667,9 +12626,6 @@
}
private void visitUris(@NonNull Consumer<Uri> visitor) {
- if (mDisplayIntent != null) {
- mDisplayIntent.visitUris(visitor);
- }
for (Action action : mActions) {
action.visitUris(visitor);
}
@@ -12829,12 +12785,6 @@
return mUnreadConversation;
}
- private void visitUris(@NonNull Consumer<Uri> visitor) {
- if (mUnreadConversation != null) {
- mUnreadConversation.visitUris(visitor);
- }
- }
-
/**
* A class which holds the unread messages from a conversation.
*/
@@ -12986,16 +12936,7 @@
onRead,
participants, b.getLong(KEY_TIMESTAMP));
}
-
- private void visitUris(@NonNull Consumer<Uri> visitor) {
- if (mReadPendingIntent != null) {
- mReadPendingIntent.visitUris(visitor);
- }
- if (mReplyPendingIntent != null) {
- mReplyPendingIntent.visitUris(visitor);
- }
- }
- }
+ };
/**
* Builder class for {@link CarExtender.UnreadConversation} objects.
@@ -13318,15 +13259,6 @@
public boolean isSuppressShowOverApps() {
return mSuppressShowOverApps;
}
-
- private void visitUris(@NonNull Consumer<Uri> visitor) {
- if (mContentIntent != null) {
- mContentIntent.visitUris(visitor);
- }
- if (mDeleteIntent != null) {
- mDeleteIntent.visitUris(visitor);
- }
- }
}
/**
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index e4310c1..bd80dc1 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -57,6 +57,7 @@
import android.service.notification.Adjustment;
import android.service.notification.Condition;
import android.service.notification.StatusBarNotification;
+import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenPolicy;
import android.util.Log;
@@ -1828,6 +1829,18 @@
throw e.rethrowFromSystemServer();
}
}
+ /**
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_UI)
+ public void setManualZenRuleDeviceEffects(@NonNull ZenDeviceEffects effects) {
+ INotificationManager service = getService();
+ try {
+ service.setManualZenRuleDeviceEffects(effects);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
/**
* For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above, the
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 1ac08ac..3714e5d 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -44,8 +44,6 @@
import android.content.pm.PackageManager.ResolveInfoFlagsBits;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
-import android.net.Uri;
-import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -71,7 +69,6 @@
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
-import java.util.function.Consumer;
/**
* A description of an Intent and target action to perform with it. Instances
@@ -1471,21 +1468,6 @@
return sb.toString();
}
- /**
- * See {@link Intent#visitUris(Consumer)}.
- *
- * @hide
- */
- public void visitUris(@NonNull Consumer<Uri> visitor) {
- if (android.app.Flags.visitRiskyUris()) {
- Intent intent = Binder.withCleanCallingIdentity(this::getIntent);
-
- if (intent != null) {
- intent.visitUris(visitor);
- }
- }
- }
-
/** @hide */
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
diff --git a/core/java/android/app/Person.java b/core/java/android/app/Person.java
index 041866001..96f6f4e 100644
--- a/core/java/android/app/Person.java
+++ b/core/java/android/app/Person.java
@@ -189,7 +189,7 @@
*/
public void visitUris(@NonNull Consumer<Uri> visitor) {
visitor.accept(getIconUri());
- if (Flags.visitRiskyUris()) {
+ if (Flags.visitPersonUri()) {
if (mUri != null && !mUri.isEmpty()) {
visitor.accept(Uri.parse(mUri));
}
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 73ac263..f751a23 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -46,10 +46,13 @@
}
flag {
- name: "visit_risky_uris"
+ name: "visit_person_uri"
namespace: "systemui"
- description: "Guards the security fix that ensures all URIs in intents and Person.java are valid"
+ description: "Guards the security fix that ensures all URIs Person.java are valid"
bug: "281044385"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
# vvv Prototypes for using app icons in notifications vvv
diff --git a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
index 470b1ec..1977a39 100644
--- a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
+++ b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
@@ -24,6 +24,8 @@
import android.os.Bundle;
import android.app.ondeviceintelligence.Feature;
import android.app.ondeviceintelligence.FeatureDetails;
+ import android.app.ondeviceintelligence.InferenceInfo;
+ import java.util.List;
import android.app.ondeviceintelligence.IDownloadCallback;
import android.app.ondeviceintelligence.IListFeaturesCallback;
import android.app.ondeviceintelligence.IFeatureCallback;
@@ -72,4 +74,6 @@
in IStreamingResponseCallback streamingCallback) = 8;
String getRemoteServicePackageName() = 9;
+
+ List<InferenceInfo> getLatestInferenceInfo(long startTimeEpochMillis) = 10;
}
diff --git a/core/java/android/app/ondeviceintelligence/InferenceInfo.aidl b/core/java/android/app/ondeviceintelligence/InferenceInfo.aidl
new file mode 100644
index 0000000..6d70fc4
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/InferenceInfo.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+/**
+ * @hide
+ */
+parcelable InferenceInfo;
diff --git a/core/java/android/app/ondeviceintelligence/InferenceInfo.java b/core/java/android/app/ondeviceintelligence/InferenceInfo.java
new file mode 100644
index 0000000..5557a81
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/InferenceInfo.java
@@ -0,0 +1,211 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class represents the information related to an inference event to track the resource usage
+ * as a function of inference time.
+ *
+ * @hide
+ */
+public class InferenceInfo implements Parcelable {
+
+ /**
+ * Uid for the caller app.
+ */
+ private final int uid;
+
+ /**
+ * Inference start time (milliseconds from the epoch time).
+ */
+ private final long startTimeMs;
+
+ /**
+ * Inference end time (milliseconds from the epoch time).
+ */
+ private final long endTimeMs;
+
+ /**
+ * Suspended time in milliseconds.
+ */
+ private final long suspendedTimeMs;
+
+ /**
+ * Constructs an InferenceInfo object with the specified parameters.
+ *
+ * @param uid Uid for the caller app.
+ * @param startTimeMs Inference start time (milliseconds from the epoch time).
+ * @param endTimeMs Inference end time (milliseconds from the epoch time).
+ * @param suspendedTimeMs Suspended time in milliseconds.
+ */
+ public InferenceInfo(int uid, long startTimeMs, long endTimeMs,
+ long suspendedTimeMs) {
+ this.uid = uid;
+ this.startTimeMs = startTimeMs;
+ this.endTimeMs = endTimeMs;
+ this.suspendedTimeMs = suspendedTimeMs;
+ }
+
+ /**
+ * Constructs an InferenceInfo object from a Parcel.
+ *
+ * @param in The Parcel to read the object's data from.
+ */
+ protected InferenceInfo(Parcel in) {
+ uid = in.readInt();
+ startTimeMs = in.readLong();
+ endTimeMs = in.readLong();
+ suspendedTimeMs = in.readLong();
+ }
+
+
+ /**
+ * Writes the object's data to the provided Parcel.
+ *
+ * @param dest The Parcel to write the object's data to.
+ * @param flags Additional flags about how the object should be written.
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(uid);
+ dest.writeLong(startTimeMs);
+ dest.writeLong(endTimeMs);
+ dest.writeLong(suspendedTimeMs);
+ }
+
+ /**
+ * Returns the UID for the caller app.
+ *
+ * @return the UID for the caller app.
+ */
+ public int getUid() {
+ return uid;
+ }
+
+ /**
+ * Returns the inference start time in milliseconds from the epoch time.
+ *
+ * @return the inference start time in milliseconds from the epoch time.
+ */
+ public long getStartTimeMs() {
+ return startTimeMs;
+ }
+
+ /**
+ * Returns the inference end time in milliseconds from the epoch time.
+ *
+ * @return the inference end time in milliseconds from the epoch time.
+ */
+ public long getEndTimeMs() {
+ return endTimeMs;
+ }
+
+ /**
+ * Returns the suspended time in milliseconds.
+ *
+ * @return the suspended time in milliseconds.
+ */
+ public long getSuspendedTimeMs() {
+ return suspendedTimeMs;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+
+ public static final @android.annotation.NonNull Parcelable.Creator<InferenceInfo> CREATOR
+ = new Parcelable.Creator<InferenceInfo>() {
+ @Override
+ public InferenceInfo[] newArray(int size) {
+ return new InferenceInfo[size];
+ }
+
+ @Override
+ public InferenceInfo createFromParcel(@android.annotation.NonNull Parcel in) {
+ return new InferenceInfo(in);
+ }
+ };
+
+ /**
+ * Builder class for creating instances of {@link InferenceInfo}.
+ */
+ public static class Builder {
+ private int uid;
+ private long startTimeMs;
+ private long endTimeMs;
+ private long suspendedTimeMs;
+
+ /**
+ * Sets the UID for the caller app.
+ *
+ * @param uid the UID for the caller app.
+ * @return the Builder instance.
+ */
+ public Builder setUid(int uid) {
+ this.uid = uid;
+ return this;
+ }
+
+ /**
+ * Sets the inference start time in milliseconds from the epoch time.
+ *
+ * @param startTimeMs the inference start time in milliseconds from the epoch time.
+ * @return the Builder instance.
+ */
+ public Builder setStartTimeMs(long startTimeMs) {
+ this.startTimeMs = startTimeMs;
+ return this;
+ }
+
+ /**
+ * Sets the inference end time in milliseconds from the epoch time.
+ *
+ * @param endTimeMs the inference end time in milliseconds from the epoch time.
+ * @return the Builder instance.
+ */
+ public Builder setEndTimeMs(long endTimeMs) {
+ this.endTimeMs = endTimeMs;
+ return this;
+ }
+
+ /**
+ * Sets the suspended time in milliseconds.
+ *
+ * @param suspendedTimeMs the suspended time in milliseconds.
+ * @return the Builder instance.
+ */
+ public Builder setSuspendedTimeMs(long suspendedTimeMs) {
+ this.suspendedTimeMs = suspendedTimeMs;
+ return this;
+ }
+
+ /**
+ * Builds and returns an instance of {@link InferenceInfo}.
+ *
+ * @return an instance of {@link InferenceInfo}.
+ */
+ public InferenceInfo build() {
+ return new InferenceInfo(uid, startTimeMs, endTimeMs,
+ suspendedTimeMs);
+ }
+ }
+}
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
index a37f51b..937a9cd 100644
--- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
+++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
@@ -496,6 +496,24 @@
}
}
+ /**
+ * This is primarily intended to be used to attribute/blame on-device intelligence power usage,
+ * via the configured remote implementation, to its actual caller.
+ *
+ * @param startTimeEpochMillis epoch millis used to filter the InferenceInfo events.
+ * @return InferenceInfo events since the passed in startTimeEpochMillis.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public List<InferenceInfo> getLatestInferenceInfo(long startTimeEpochMillis) {
+ try {
+ return mService.getLatestInferenceInfo(startTimeEpochMillis);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/** Request inference with provided Bundle and Params. */
public static final int REQUEST_TYPE_INFERENCE = 0;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 02d62a2..2e60cb0 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -106,7 +106,6 @@
import java.util.Objects;
import java.util.Set;
import java.util.TimeZone;
-import java.util.function.Consumer;
/**
* An intent is an abstract description of an operation to be performed. It
@@ -8330,27 +8329,6 @@
}
}
- /**
- * Note all {@link Uri} that are referenced internally, with the expectation that Uri permission
- * grants will need to be issued to ensure the recipient of this object is able to render its
- * contents.
- * See b/281044385 for more context and examples about what happens when this isn't done
- * correctly.
- *
- * @hide
- */
- public void visitUris(@NonNull Consumer<Uri> visitor) {
- if (android.app.Flags.visitRiskyUris()) {
- visitor.accept(mData);
- if (mSelector != null) {
- mSelector.visitUris(visitor);
- }
- if (mOriginalIntent != null) {
- mOriginalIntent.visitUris(visitor);
- }
- }
- }
-
public static Intent getIntentOld(String uri) throws URISyntaxException {
Intent intent = getIntentOld(uri, 0);
intent.mLocalFlags |= LOCAL_FLAG_FROM_URI;
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 86d061c..e895d7b 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -823,6 +823,16 @@
}
/**
+ * Returns the number of actions in the filter, or {@code 0} if there are no actions.
+ * <p> This method provides a safe alternative to {@link #countActions()}, which
+ * may throw an exception if there are no actions.
+ * @hide
+ */
+ public final int safeCountActions() {
+ return mActions == null ? 0 : mActions.size();
+ }
+
+ /**
* Return an action in the filter.
*/
public final String getAction(int index) {
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 93cc71b..df27f9d 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -696,14 +696,11 @@
* Otherwise it'll return the same list as {@link UserManager#getUserProfiles()} would.
*
* <p>To get hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
- * caller should have either:</p>
- * <ul>
- * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
- * permission</li>
- * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
- * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
- * </ul>
+ * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+ * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
*/
+ // Alternatively, a system app can access this api for private profile if they've been granted
+ // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
@SuppressLint("RequiresPermission")
@RequiresPermission(conditional = true,
anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -765,19 +762,16 @@
* </ul>
*
* <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
- * caller should have either:</p>
- * <ul>
- * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
- * permission</li>
- * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
- * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
- * </ul>
+ * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+ * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
*
* @param packageName The specific package to query. If null, it checks all installed packages
* in the profile.
* @param user The UserHandle of the profile.
* @return List of launchable activities. Can be an empty list but will not be null.
*/
+ // Alternatively, a system app can access this api for private profile if they've been granted
+ // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
@SuppressLint("RequiresPermission")
@RequiresPermission(conditional = true,
anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -821,18 +815,15 @@
* to distinguish it from other users (eg, badges).
*
* <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
- * caller should have either:</p>
- * <ul>
- * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
- * permission</li>
- * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
- * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
- * </ul>
+ * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+ * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
*
* @param userHandle user handle of the user for which LauncherUserInfo is requested.
* @return the {@link LauncherUserInfo} object related to the user specified, null in case
* the user is inaccessible.
*/
+ // Alternatively, a system app can access this api for private profile if they've been granted
+ // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
@Nullable
@SuppressLint("RequiresPermission")
@FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
@@ -874,13 +865,8 @@
* </p>
*
* <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
- * caller should have either:</p>
- * <ul>
- * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
- * permission</li>
- * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
- * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
- * </ul>
+ * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+ * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
*
* @param packageName the package for which intent sender to launch App Market Activity is
* required.
@@ -888,6 +874,8 @@
* @return {@link IntentSender} object which launches the App Market Activity, null in case
* there is no such activity.
*/
+ // Alternatively, a system app can access this api for private profile if they've been granted
+ // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
@Nullable
@SuppressLint("RequiresPermission")
@FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
@@ -914,18 +902,15 @@
* user at creation.
*
* <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
- * caller should have either:</p>
- * <ul>
- * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
- * permission</li>
- * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
- * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
- * </ul>
+ * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+ * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
*
* @param userHandle the user for which installed system packages are required.
* @return {@link List} of {@link String}, representing the package name of the installed
* package. Can be empty but not null.
*/
+ // Alternatively, a system app can access this api for private profile if they've been granted
+ // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
@FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
@NonNull
@SuppressLint("RequiresPermission")
@@ -952,6 +937,8 @@
* successful, null otherwise.
* @hide
*/
+ // Alternatively, a system app can access this api for private profile if they've been granted
+ // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
@Nullable
@FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
@RequiresPermission(conditional = true,
@@ -969,18 +956,15 @@
* returns null.
*
* <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
- * caller should have either:</p>
- * <ul>
- * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
- * permission</li>
- * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
- * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
- * </ul>
+ * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+ * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
*
* @param intent The intent to find a match for.
* @param user The profile to look in for a match.
* @return An activity info object if there is a match.
*/
+ // Alternatively, a system app can access this api for private profile if they've been granted
+ // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
@SuppressLint("RequiresPermission")
@RequiresPermission(conditional = true,
anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -1034,19 +1018,16 @@
* Starts a Main activity in the specified profile.
*
* <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
- * caller should have either:</p>
- * <ul>
- * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
- * permission</li>
- * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
- * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
- * </ul>
+ * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+ * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
*
* @param component The ComponentName of the activity to launch
* @param user The UserHandle of the profile
* @param sourceBounds The Rect containing the source bounds of the clicked icon
* @param opts Options to pass to startActivity
*/
+ // Alternatively, a system app can access this api for private profile if they've been granted
+ // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
@SuppressLint("RequiresPermission")
@RequiresPermission(conditional = true,
anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -1088,19 +1069,16 @@
* package in the specified profile.
*
* <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
- * caller should have either:</p>
- * <ul>
- * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
- * permission</li>
- * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
- * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
- * </ul>
+ * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+ * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
*
* @param component The ComponentName of the package to launch settings for.
* @param user The UserHandle of the profile
* @param sourceBounds The Rect containing the source bounds of the clicked icon
* @param opts Options to pass to startActivity
*/
+ // Alternatively, a system app can access this api for private profile if they've been granted
+ // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
@SuppressLint("RequiresPermission")
@RequiresPermission(conditional = true,
anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -1216,19 +1194,16 @@
* Checks if the package is installed and enabled for a profile.
*
* <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
- * caller should have either:</p>
- * <ul>
- * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
- * permission</li>
- * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
- * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
- * </ul>
+ * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+ * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
*
* @param packageName The package to check.
* @param user The UserHandle of the profile.
*
* @return true if the package exists and is enabled.
*/
+ // Alternatively, a system app can access this api for private profile if they've been granted
+ // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
@SuppressLint("RequiresPermission")
@RequiresPermission(conditional = true,
anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -1250,13 +1225,8 @@
* app and the launcher.
*
* <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
- * caller should have either:</p>
- * <ul>
- * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
- * permission</li>
- * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
- * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
- * </ul>
+ * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+ * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
*
* <p>Note: This just returns whatever extras were provided to the system, <em>which might
* even be {@code null}.</em>
@@ -1269,6 +1239,8 @@
* @see Callback#onPackagesSuspended(String[], UserHandle, Bundle)
* @see PackageManager#isPackageSuspended()
*/
+ // Alternatively, a system app can access this api for private profile if they've been granted
+ // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
@SuppressLint("RequiresPermission")
@RequiresPermission(conditional = true,
anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -1287,18 +1259,15 @@
* {@code PackageManager.setDistractingPackageRestrictions(String[], int)}.
*
* <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
- * caller should have either:</p>
- * <ul>
- * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
- * permission</li>
- * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
- * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
- * </ul>
+ * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+ * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
*
* @param packageName The package for which to check.
* @param user the {@link UserHandle} of the profile.
* @return
*/
+ // Alternatively, a system app can access this api for private profile if they've been granted
+ // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
@SuppressLint("RequiresPermission")
@RequiresPermission(conditional = true,
anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -1317,13 +1286,8 @@
* Returns {@link ApplicationInfo} about an application installed for a specific user profile.
*
* <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
- * caller should have either:</p>
- * <ul>
- * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
- * permission</li>
- * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
- * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
- * </ul>
+ * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+ * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
*
* @param packageName The package name of the application
* @param flags Additional option flags {@link PackageManager#getApplicationInfo}
@@ -1333,6 +1297,8 @@
* {@code null} if the package isn't installed for the given profile, or the profile
* isn't enabled.
*/
+ // Alternatively, a system app can access this api for private profile if they've been granted
+ // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
@SuppressLint("RequiresPermission")
@RequiresPermission(conditional = true,
anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -1386,19 +1352,16 @@
* throw a {@link SecurityException} unless the caller has the same UID as the target app's.
*
* <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
- * caller should have either:</p>
- * <ul>
- * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
- * permission</li>
- * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
- * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
- * </ul>
+ * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+ * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
*
* @param component The activity to check.
* @param user The UserHandle of the profile.
*
* @return true if the activity exists and is enabled.
*/
+ // Alternatively, a system app can access this api for private profile if they've been granted
+ // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
@SuppressLint("RequiresPermission")
@RequiresPermission(conditional = true,
anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -1961,16 +1924,13 @@
* Registers a callback for changes to packages in this user and managed profiles.
*
* <p>To receive callbacks for hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
- * caller should have either:</p>
- * <ul>
- * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
- * permission</li>
- * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
- * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
- * </ul>
+ * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+ * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
*
* @param callback The callback to register.
*/
+ // Alternatively, a system app can access this api for private profile if they've been granted
+ // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
@SuppressLint("RequiresPermission")
@RequiresPermission(conditional = true,
anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -1982,17 +1942,14 @@
* Registers a callback for changes to packages in this user and managed profiles.
*
* <p>To receive callbacks for hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
- * caller should have either:</p>
- * <ul>
- * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
- * permission</li>
- * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
- * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
- * </ul>
+ * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+ * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
*
* @param callback The callback to register.
* @param handler that should be used to post callbacks on, may be null.
*/
+ // Alternatively, a system app can access this api for private profile if they've been granted
+ // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
@SuppressLint("RequiresPermission")
@RequiresPermission(conditional = true,
anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -2447,13 +2404,8 @@
* the session owner to retrieve these details.
*
* <p>To receive callbacks for hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
- * caller should have either:</p>
- * <ul>
- * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
- * permission</li>
- * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
- * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
- * </ul>
+ * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+ * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
*
* @see PackageInstaller#getAllSessions()
*/
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index e2159f7..37a2df8 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -200,7 +200,10 @@
throw new IllegalStateException(
"Exclusively one of logo resource or logo bitmap can be set");
}
- mPromptInfo.setLogo(logoRes, convertDrawableToBitmap(mContext.getDrawable(logoRes)));
+ if (logoRes != 0) {
+ mPromptInfo.setLogo(logoRes,
+ convertDrawableToBitmap(mContext.getDrawable(logoRes)));
+ }
return this;
}
@@ -219,11 +222,11 @@
@RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
@NonNull
public BiometricPrompt.Builder setLogoBitmap(@NonNull Bitmap logoBitmap) {
- if (mPromptInfo.getLogoRes() != -1) {
+ if (mPromptInfo.getLogoRes() != 0) {
throw new IllegalStateException(
"Exclusively one of logo resource or logo bitmap can be set");
}
- mPromptInfo.setLogo(-1, logoBitmap);
+ mPromptInfo.setLogo(0, logoBitmap);
return this;
}
@@ -832,7 +835,7 @@
* Gets the drawable resource of the logo for the prompt, as set by
* {@link Builder#setLogoRes(int)}. Currently for system applications use only.
*
- * @return The drawable resource of the logo, or -1 if the prompt has no logo resource set.
+ * @return The drawable resource of the logo, or 0 if the prompt has no logo resource set.
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
@RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index f4a3c87..ba9f30d 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -33,7 +33,7 @@
*/
public class PromptInfo implements Parcelable {
- @DrawableRes private int mLogoRes = -1;
+ @DrawableRes private int mLogoRes;
@Nullable private Bitmap mLogoBitmap;
@Nullable private String mLogoDescription;
@NonNull private CharSequence mTitle;
@@ -187,7 +187,7 @@
* this permission.
*/
public boolean requiresAdvancedPermission() {
- if (mLogoRes != -1) {
+ if (mLogoRes != 0) {
return true;
} else if (mLogoBitmap != null) {
return true;
@@ -221,7 +221,7 @@
/**
* Sets logo res and bitmap
*
- * @param logoRes The logo res set by the app; Or -1 if the app sets bitmap directly.
+ * @param logoRes The logo res set by the app; Or 0 if the app sets bitmap directly.
* @param logoBitmap The bitmap from logoRes if the app sets logoRes; Or the bitmap set by the
* app directly.
*/
@@ -351,7 +351,7 @@
public Bitmap getLogoBitmap() {
// If mLogoRes has been set, return null since mLogoBitmap is from the res, but not from
// the app directly.
- return mLogoRes == -1 ? mLogoBitmap : null;
+ return mLogoRes == 0 ? mLogoBitmap : null;
}
public String getLogoDescription() {
diff --git a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
index 967fc42..0f944cf 100644
--- a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
@@ -28,4 +28,11 @@
namespace: "preload_safety"
description: "Enables signal API with staking"
bug: "287498482"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "enable_usb_sysfs_midi_identification"
+ namespace: "system_sw_usb"
+ description: "Enable identifying midi device using USB sysfs"
+ bug: "333778731"
+}
diff --git a/core/java/android/service/notification/ZenAdapters.java b/core/java/android/service/notification/ZenAdapters.java
index b249815..a122b71 100644
--- a/core/java/android/service/notification/ZenAdapters.java
+++ b/core/java/android/service/notification/ZenAdapters.java
@@ -33,7 +33,7 @@
.allowAlarms(policy.allowAlarms())
.allowCalls(
policy.allowCalls()
- ? notificationPolicySendersToZenPolicyPeopleType(
+ ? prioritySendersToPeopleType(
policy.allowCallsFrom())
: ZenPolicy.PEOPLE_TYPE_NONE)
.allowConversations(
@@ -45,7 +45,7 @@
.allowMedia(policy.allowMedia())
.allowMessages(
policy.allowMessages()
- ? notificationPolicySendersToZenPolicyPeopleType(
+ ? prioritySendersToPeopleType(
policy.allowMessagesFrom())
: ZenPolicy.PEOPLE_TYPE_NONE)
.allowReminders(policy.allowReminders())
@@ -71,7 +71,7 @@
/** Maps {@link ZenPolicy.PeopleType} enum to {@link Policy.PrioritySenders}. */
@Policy.PrioritySenders
- public static int zenPolicyPeopleTypeToNotificationPolicySenders(
+ public static int peopleTypeToPrioritySenders(
@ZenPolicy.PeopleType int zpPeopleType, @Policy.PrioritySenders int defaultResult) {
switch (zpPeopleType) {
case ZenPolicy.PEOPLE_TYPE_ANYONE:
@@ -87,7 +87,7 @@
/** Maps {@link Policy.PrioritySenders} enum to {@link ZenPolicy.PeopleType}. */
@ZenPolicy.PeopleType
- public static int notificationPolicySendersToZenPolicyPeopleType(
+ public static int prioritySendersToPeopleType(
@Policy.PrioritySenders int npPrioritySenders) {
switch (npPrioritySenders) {
case Policy.PRIORITY_SENDERS_ANY:
diff --git a/core/java/android/service/notification/ZenDeviceEffects.aidl b/core/java/android/service/notification/ZenDeviceEffects.aidl
new file mode 100644
index 0000000..e635493
--- /dev/null
+++ b/core/java/android/service/notification/ZenDeviceEffects.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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.service.notification;
+
+parcelable ZenDeviceEffects;
+
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 610a317..7a0c016 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -24,9 +24,26 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
-import static android.service.notification.ZenAdapters.notificationPolicySendersToZenPolicyPeopleType;
+import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+import static android.service.notification.ZenAdapters.peopleTypeToPrioritySenders;
+import static android.service.notification.ZenAdapters.prioritySendersToPeopleType;
import static android.service.notification.ZenAdapters.zenPolicyConversationSendersToNotificationPolicy;
-import static android.service.notification.ZenAdapters.zenPolicyPeopleTypeToNotificationPolicySenders;
+import static android.service.notification.ZenModeConfig.EventInfo.REPLY_YES_OR_MAYBE;
+import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
+import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_ALARMS;
+import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_CALLS;
+import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_EVENTS;
+import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_MEDIA;
+import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_MESSAGES;
+import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REMINDERS;
+import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REPEAT_CALLERS;
+import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_SYSTEM;
+import static android.service.notification.ZenPolicy.STATE_ALLOW;
+import static android.service.notification.ZenPolicy.STATE_DISALLOW;
+import static android.service.notification.ZenPolicy.VISUAL_EFFECT_AMBIENT;
+import static android.service.notification.ZenPolicy.VISUAL_EFFECT_FULL_SCREEN_INTENT;
+import static android.service.notification.ZenPolicy.VISUAL_EFFECT_LIGHTS;
+import static android.service.notification.ZenPolicy.VISUAL_EFFECT_PEEK;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -58,6 +75,7 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -320,31 +338,107 @@
@UnsupportedAppUsage
public ZenModeConfig() {
+ if (Flags.modesUi()) {
+ ensureManualZenRule();
+ }
}
public ZenModeConfig(Parcel source) {
- allowCalls = source.readInt() == 1;
- allowRepeatCallers = source.readInt() == 1;
- allowMessages = source.readInt() == 1;
- allowReminders = source.readInt() == 1;
- allowEvents = source.readInt() == 1;
- allowCallsFrom = source.readInt();
- allowMessagesFrom = source.readInt();
+ if (!Flags.modesUi()) {
+ allowCalls = source.readInt() == 1;
+ allowRepeatCallers = source.readInt() == 1;
+ allowMessages = source.readInt() == 1;
+ allowReminders = source.readInt() == 1;
+ allowEvents = source.readInt() == 1;
+ allowCallsFrom = source.readInt();
+ allowMessagesFrom = source.readInt();
+ }
user = source.readInt();
manualRule = source.readParcelable(null, ZenRule.class);
readRulesFromParcel(automaticRules, source);
if (Flags.modesApi()) {
readRulesFromParcel(deletedRules, source);
}
- allowAlarms = source.readInt() == 1;
- allowMedia = source.readInt() == 1;
- allowSystem = source.readInt() == 1;
- suppressedVisualEffects = source.readInt();
+ if (!Flags.modesUi()) {
+ allowAlarms = source.readInt() == 1;
+ allowMedia = source.readInt() == 1;
+ allowSystem = source.readInt() == 1;
+ suppressedVisualEffects = source.readInt();
+ }
areChannelsBypassingDnd = source.readInt() == 1;
- allowConversations = source.readBoolean();
- allowConversationsFrom = source.readInt();
- if (Flags.modesApi()) {
- allowPriorityChannels = source.readBoolean();
+ if (!Flags.modesUi()) {
+ allowConversations = source.readBoolean();
+ allowConversationsFrom = source.readInt();
+ if (Flags.modesApi()) {
+ allowPriorityChannels = source.readBoolean();
+ }
+ }
+ }
+
+ public static ZenPolicy getDefaultZenPolicy() {
+ ZenPolicy policy = new ZenPolicy.Builder()
+ .allowAlarms(true)
+ .allowMedia(true)
+ .allowSystem(false)
+ .allowCalls(PEOPLE_TYPE_STARRED)
+ .allowMessages(PEOPLE_TYPE_STARRED)
+ .allowReminders(false)
+ .allowEvents(false)
+ .allowRepeatCallers(true)
+ .allowConversations(CONVERSATION_SENDERS_IMPORTANT)
+ .showAllVisualEffects()
+ .showVisualEffect(VISUAL_EFFECT_FULL_SCREEN_INTENT, false)
+ .showVisualEffect(VISUAL_EFFECT_LIGHTS, false)
+ .showVisualEffect(VISUAL_EFFECT_PEEK, false)
+ .showVisualEffect(VISUAL_EFFECT_AMBIENT, false)
+ .allowPriorityChannels(true)
+ .build();
+ return policy;
+ }
+
+ public static ZenModeConfig getDefaultConfig() {
+ ZenModeConfig config = new ZenModeConfig();
+
+ EventInfo eventInfo = new EventInfo();
+ eventInfo.reply = REPLY_YES_OR_MAYBE;
+ ZenRule events = new ZenRule();
+ events.id = EVENTS_DEFAULT_RULE_ID;
+ events.conditionId = toEventConditionId(eventInfo);
+ events.component = ComponentName.unflattenFromString(
+ "android/com.android.server.notification.EventConditionProvider");
+ events.enabled = false;
+ events.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ events.pkg = "android";
+ config.automaticRules.put(EVENTS_DEFAULT_RULE_ID, events);
+
+ ScheduleInfo scheduleInfo = new ScheduleInfo();
+ scheduleInfo.days = new int[] {1, 2, 3, 4, 5, 6, 7};
+ scheduleInfo.startHour = 22;
+ scheduleInfo.endHour = 7;
+ scheduleInfo.exitAtAlarm = true;
+ ZenRule sleeping = new ZenRule();
+ sleeping.id = EVERY_NIGHT_DEFAULT_RULE_ID;
+ sleeping.conditionId = toScheduleConditionId(scheduleInfo);
+ sleeping.component = ComponentName.unflattenFromString(
+ "android/com.android.server.notification.ScheduleConditionProvider");
+ sleeping.enabled = false;
+ sleeping.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ sleeping.pkg = "android";
+ config.automaticRules.put(EVERY_NIGHT_DEFAULT_RULE_ID, sleeping);
+
+ return config;
+ }
+
+ void ensureManualZenRule() {
+ if (manualRule == null) {
+ final ZenRule newRule = new ZenRule();
+ newRule.type = AutomaticZenRule.TYPE_OTHER;
+ newRule.enabled = true;
+ newRule.conditionId = Uri.EMPTY;
+ newRule.allowManualInvocation = true;
+ newRule.zenPolicy = getDefaultZenPolicy();
+ newRule.pkg = "android";
+ manualRule = newRule;
}
}
@@ -363,28 +457,34 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(allowCalls ? 1 : 0);
- dest.writeInt(allowRepeatCallers ? 1 : 0);
- dest.writeInt(allowMessages ? 1 : 0);
- dest.writeInt(allowReminders ? 1 : 0);
- dest.writeInt(allowEvents ? 1 : 0);
- dest.writeInt(allowCallsFrom);
- dest.writeInt(allowMessagesFrom);
+ if (!Flags.modesUi()) {
+ dest.writeInt(allowCalls ? 1 : 0);
+ dest.writeInt(allowRepeatCallers ? 1 : 0);
+ dest.writeInt(allowMessages ? 1 : 0);
+ dest.writeInt(allowReminders ? 1 : 0);
+ dest.writeInt(allowEvents ? 1 : 0);
+ dest.writeInt(allowCallsFrom);
+ dest.writeInt(allowMessagesFrom);
+ }
dest.writeInt(user);
dest.writeParcelable(manualRule, 0);
writeRulesToParcel(automaticRules, dest);
if (Flags.modesApi()) {
writeRulesToParcel(deletedRules, dest);
}
- dest.writeInt(allowAlarms ? 1 : 0);
- dest.writeInt(allowMedia ? 1 : 0);
- dest.writeInt(allowSystem ? 1 : 0);
- dest.writeInt(suppressedVisualEffects);
+ if (!Flags.modesUi()) {
+ dest.writeInt(allowAlarms ? 1 : 0);
+ dest.writeInt(allowMedia ? 1 : 0);
+ dest.writeInt(allowSystem ? 1 : 0);
+ dest.writeInt(suppressedVisualEffects);
+ }
dest.writeInt(areChannelsBypassingDnd ? 1 : 0);
- dest.writeBoolean(allowConversations);
- dest.writeInt(allowConversationsFrom);
- if (Flags.modesApi()) {
- dest.writeBoolean(allowPriorityChannels);
+ if (!Flags.modesUi()) {
+ dest.writeBoolean(allowConversations);
+ dest.writeInt(allowConversationsFrom);
+ if (Flags.modesApi()) {
+ dest.writeBoolean(allowPriorityChannels);
+ }
}
}
@@ -408,35 +508,251 @@
@Override
public String toString() {
StringBuilder sb = new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
- .append("user=").append(user)
- .append(",allowAlarms=").append(allowAlarms)
- .append(",allowMedia=").append(allowMedia)
- .append(",allowSystem=").append(allowSystem)
- .append(",allowReminders=").append(allowReminders)
- .append(",allowEvents=").append(allowEvents)
- .append(",allowCalls=").append(allowCalls)
- .append(",allowRepeatCallers=").append(allowRepeatCallers)
- .append(",allowMessages=").append(allowMessages)
- .append(",allowConversations=").append(allowConversations)
- .append(",allowCallsFrom=").append(sourceToString(allowCallsFrom))
- .append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom))
- .append(",allowConvFrom=").append(ZenPolicy.conversationTypeToString
- (allowConversationsFrom))
- .append(",suppressedVisualEffects=").append(suppressedVisualEffects);
+ .append("user=").append(user);
+ if (!Flags.modesUi()) {
+ sb.append(",allowAlarms=").append(allowAlarms)
+ .append(",allowMedia=").append(allowMedia)
+ .append(",allowSystem=").append(allowSystem)
+ .append(",allowReminders=").append(allowReminders)
+ .append(",allowEvents=").append(allowEvents)
+ .append(",allowCalls=").append(allowCalls)
+ .append(",allowRepeatCallers=").append(allowRepeatCallers)
+ .append(",allowMessages=").append(allowMessages)
+ .append(",allowConversations=").append(allowConversations)
+ .append(",allowCallsFrom=").append(sourceToString(allowCallsFrom))
+ .append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom))
+ .append(",allowConvFrom=").append(ZenPolicy.conversationTypeToString
+ (allowConversationsFrom))
+ .append("\nsuppressedVisualEffects=").append(suppressedVisualEffects);
+ }
if (Flags.modesApi()) {
- sb.append(",hasPriorityChannels=").append(areChannelsBypassingDnd);
+ sb.append("\nhasPriorityChannels=").append(areChannelsBypassingDnd);
sb.append(",allowPriorityChannels=").append(allowPriorityChannels);
} else {
- sb.append(",areChannelsBypassingDnd=").append(areChannelsBypassingDnd);
+ sb.append("\nareChannelsBypassingDnd=").append(areChannelsBypassingDnd);
}
- sb.append(",\nautomaticRules=").append(rulesToString(automaticRules))
- .append(",\nmanualRule=").append(manualRule);
+ sb.append(",\nautomaticRules=").append(rulesToString(automaticRules));
+ sb.append(",\nmanualRule=").append(manualRule);
if (Flags.modesApi()) {
sb.append(",\ndeletedRules=").append(rulesToString(deletedRules));
}
return sb.append(']').toString();
}
+ public boolean isAllowPriorityChannels() {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ }
+ return allowPriorityChannels;
+ }
+
+ public void setAllowPriorityChannels(boolean allowPriorityChannels) {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ } else {
+ this.allowPriorityChannels = allowPriorityChannels;
+ }
+ }
+
+ public int getSuppressedVisualEffects() {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ } else {
+ return this.suppressedVisualEffects;
+ }
+ }
+
+ public void setSuppressedVisualEffects(int suppressedVisualEffects) {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ } else {
+ this.suppressedVisualEffects = suppressedVisualEffects;
+ }
+ }
+
+ public @ZenPolicy.ConversationSenders int getAllowConversationsFrom() {
+ if (Flags.modesUi()) {
+ return manualRule.zenPolicy.getPriorityConversationSenders();
+ }
+ return allowConversationsFrom;
+ }
+
+ public void setAllowConversationsFrom(
+ @ZenPolicy.ConversationSenders int allowConversationsFrom) {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ } else {
+ this.allowConversationsFrom = allowConversationsFrom;
+ }
+ }
+
+ public void setAllowConversations(boolean allowConversations) {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ } else {
+ this.allowConversations = allowConversations;
+ }
+ }
+
+ public boolean isAllowConversations() {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ }
+ return allowConversations;
+ }
+
+ public @Policy.PrioritySenders int getAllowMessagesFrom() {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ }
+ return allowMessagesFrom;
+ }
+
+ public void setAllowMessagesFrom(@Policy.PrioritySenders int allowMessagesFrom) {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ } else {
+ this.allowMessagesFrom = allowMessagesFrom;
+ }
+ }
+
+ public void setAllowMessages(boolean allowMessages) {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ }
+ this.allowMessages = allowMessages;
+ }
+
+ public @Policy.PrioritySenders int getAllowCallsFrom() {
+ if (Flags.modesUi()) {
+ return peopleTypeToPrioritySenders(
+ manualRule.zenPolicy.getPriorityCallSenders(), DEFAULT_CALLS_SOURCE);
+ }
+ return allowCallsFrom;
+ }
+
+ public void setAllowCallsFrom(@Policy.PrioritySenders int allowCallsFrom) {
+ if (Flags.modesUi()) {
+ manualRule.zenPolicy = new ZenPolicy.Builder(manualRule.zenPolicy)
+ .allowCalls(prioritySendersToPeopleType(allowCallsFrom))
+ .build();
+ } else {
+ this.allowCallsFrom = allowCallsFrom;
+ }
+ }
+
+ public void setAllowCalls(boolean allowCalls) {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ }
+ this.allowCalls = allowCalls;
+ }
+
+ public boolean isAllowEvents() {
+ if (Flags.modesUi()) {
+ return manualRule.zenPolicy.isCategoryAllowed(
+ ZenPolicy.PRIORITY_CATEGORY_EVENTS, false);
+ }
+ return allowEvents;
+ }
+
+ public void setAllowEvents(boolean allowEvents) {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ } else {
+ this.allowEvents = allowEvents;
+ }
+ }
+
+ public boolean isAllowReminders() {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ }
+ return allowReminders;
+ }
+
+ public void setAllowReminders(boolean allowReminders) {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ } else {
+ this.allowReminders = allowReminders;
+ }
+ }
+
+ public boolean isAllowMessages() {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ }
+ return allowMessages;
+ }
+
+ public boolean isAllowRepeatCallers() {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ }
+ return allowRepeatCallers;
+ }
+
+ public void setAllowRepeatCallers(boolean allowRepeatCallers) {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ } else {
+ this.allowRepeatCallers = allowRepeatCallers;
+ }
+ }
+
+ public boolean isAllowSystem() {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ }
+ return allowSystem;
+ }
+
+ public void setAllowSystem(boolean allowSystem) {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ } else {
+ this.allowSystem = allowSystem;
+ }
+ }
+
+ public boolean isAllowMedia() {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ }
+ return allowMedia;
+ }
+
+ public void setAllowMedia(boolean allowMedia) {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ } else {
+ this.allowMedia = allowMedia;
+ }
+ }
+
+ public boolean isAllowAlarms() {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ }
+ return allowAlarms;
+ }
+
+ public void setAllowAlarms(boolean allowAlarms) {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ } else {
+ this.allowAlarms = allowAlarms;
+ }
+ }
+
+ public boolean isAllowCalls() {
+ if (Flags.modesUi()) {
+ throw new IllegalStateException("can't be used with modesUI flag");
+ }
+ return allowCalls;
+ }
+
private static String rulesToString(ArrayMap<String, ZenRule> ruleList) {
if (ruleList.isEmpty()) {
return "{}";
@@ -512,6 +828,8 @@
if (!(o instanceof ZenModeConfig)) return false;
if (o == this) return true;
final ZenModeConfig other = (ZenModeConfig) o;
+ // The policy fields that live on config are compared directly because the fields will
+ // contain data until MODES_UI is rolled out/cleaned up.
boolean eq = other.allowAlarms == allowAlarms
&& other.allowMedia == allowMedia
&& other.allowSystem == allowSystem
@@ -539,6 +857,8 @@
@Override
public int hashCode() {
+ // The policy fields that live on config are compared directly because the fields will
+ // contain data until MODES_UI is rolled out/cleaned up.
if (Flags.modesApi()) {
return Objects.hash(allowAlarms, allowMedia, allowSystem, allowCalls,
allowRepeatCallers, allowMessages,
@@ -619,9 +939,14 @@
rt.version = safeInt(parser, ZEN_ATT_VERSION, getCurrentXmlVersion());
rt.user = safeInt(parser, ZEN_ATT_USER, rt.user);
boolean readSuppressedEffects = false;
+ boolean readManualRule = false;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
tag = parser.getName();
if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) {
+ if (Flags.modesUi() && !readManualRule) {
+ // migrate from fields on config into manual rule
+ rt.manualRule.zenPolicy = rt.toZenPolicy();
+ }
return rt;
}
if (type == XmlPullParser.START_TAG) {
@@ -693,6 +1018,9 @@
DEFAULT_SUPPRESSED_VISUAL_EFFECTS);
} else if (MANUAL_TAG.equals(tag)) {
rt.manualRule = readRuleXml(parser);
+ if (rt.manualRule != null) {
+ readManualRule = true;
+ }
} else if (AUTOMATIC_TAG.equals(tag)
|| (Flags.modesApi() && AUTOMATIC_DELETED_TAG.equals(tag))) {
final String id = parser.getAttributeValue(null, RULE_ATT_ID);
@@ -742,6 +1070,9 @@
? Integer.toString(xmlVersion) : Integer.toString(version));
out.attributeInt(null, ZEN_ATT_USER, user);
out.startTag(null, ALLOW_TAG);
+ // From MODES_UI these fields are only read if the flag has transitioned from off to on
+ // However, we will continue to write these fields until the flag is cleaned up so it's
+ // possible to turn the flag off without losing user data
out.attributeBoolean(null, ALLOW_ATT_CALLS, allowCalls);
out.attributeBoolean(null, ALLOW_ATT_REPEAT_CALLERS, allowRepeatCallers);
out.attributeBoolean(null, ALLOW_ATT_MESSAGES, allowMessages);
@@ -816,11 +1147,11 @@
rt.enabler = parser.getAttributeValue(null, RULE_ATT_ENABLER);
rt.condition = readConditionXml(parser);
- if (!Flags.modesApi() && rt.zenMode != Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+ if (!Flags.modesApi() && rt.zenMode != ZEN_MODE_IMPORTANT_INTERRUPTIONS
&& Condition.isValidId(rt.conditionId, SYSTEM_AUTHORITY)) {
// all default rules and user created rules updated to zenMode important interruptions
Slog.i(TAG, "Updating zenMode of automatic rule " + rt.name);
- rt.zenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ rt.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
}
rt.modified = safeBoolean(parser, RULE_ATT_MODIFIED, false);
rt.zenPolicy = readZenPolicyXml(parser);
@@ -952,7 +1283,7 @@
if (Flags.modesApi()) {
final int channels = safeInt(parser, ALLOW_ATT_CHANNELS, ZenPolicy.STATE_UNSET);
if (channels != ZenPolicy.STATE_UNSET) {
- builder.allowPriorityChannels(channels == ZenPolicy.STATE_ALLOW);
+ builder.allowPriorityChannels(channels == STATE_ALLOW);
policySet = true;
}
}
@@ -966,7 +1297,7 @@
policySet = true;
}
if (repeatCallers != ZenPolicy.STATE_UNSET) {
- builder.allowRepeatCallers(repeatCallers == ZenPolicy.STATE_ALLOW);
+ builder.allowRepeatCallers(repeatCallers == STATE_ALLOW);
policySet = true;
}
if (conversations != ZenPolicy.CONVERSATION_SENDERS_UNSET) {
@@ -974,23 +1305,23 @@
policySet = true;
}
if (alarms != ZenPolicy.STATE_UNSET) {
- builder.allowAlarms(alarms == ZenPolicy.STATE_ALLOW);
+ builder.allowAlarms(alarms == STATE_ALLOW);
policySet = true;
}
if (media != ZenPolicy.STATE_UNSET) {
- builder.allowMedia(media == ZenPolicy.STATE_ALLOW);
+ builder.allowMedia(media == STATE_ALLOW);
policySet = true;
}
if (system != ZenPolicy.STATE_UNSET) {
- builder.allowSystem(system == ZenPolicy.STATE_ALLOW);
+ builder.allowSystem(system == STATE_ALLOW);
policySet = true;
}
if (events != ZenPolicy.STATE_UNSET) {
- builder.allowEvents(events == ZenPolicy.STATE_ALLOW);
+ builder.allowEvents(events == STATE_ALLOW);
policySet = true;
}
if (reminders != ZenPolicy.STATE_UNSET) {
- builder.allowReminders(reminders == ZenPolicy.STATE_ALLOW);
+ builder.allowReminders(reminders == STATE_ALLOW);
policySet = true;
}
@@ -1005,31 +1336,31 @@
ZenPolicy.STATE_UNSET);
if (fullScreenIntent != ZenPolicy.STATE_UNSET) {
- builder.showFullScreenIntent(fullScreenIntent == ZenPolicy.STATE_ALLOW);
+ builder.showFullScreenIntent(fullScreenIntent == STATE_ALLOW);
policySet = true;
}
if (lights != ZenPolicy.STATE_UNSET) {
- builder.showLights(lights == ZenPolicy.STATE_ALLOW);
+ builder.showLights(lights == STATE_ALLOW);
policySet = true;
}
if (peek != ZenPolicy.STATE_UNSET) {
- builder.showPeeking(peek == ZenPolicy.STATE_ALLOW);
+ builder.showPeeking(peek == STATE_ALLOW);
policySet = true;
}
if (statusBar != ZenPolicy.STATE_UNSET) {
- builder.showStatusBarIcons(statusBar == ZenPolicy.STATE_ALLOW);
+ builder.showStatusBarIcons(statusBar == STATE_ALLOW);
policySet = true;
}
if (badges != ZenPolicy.STATE_UNSET) {
- builder.showBadges(badges == ZenPolicy.STATE_ALLOW);
+ builder.showBadges(badges == STATE_ALLOW);
policySet = true;
}
if (ambient != ZenPolicy.STATE_UNSET) {
- builder.showInAmbientDisplay(ambient == ZenPolicy.STATE_ALLOW);
+ builder.showInAmbientDisplay(ambient == STATE_ALLOW);
policySet = true;
}
if (notificationList != ZenPolicy.STATE_UNSET) {
- builder.showInNotificationList(notificationList == ZenPolicy.STATE_ALLOW);
+ builder.showInNotificationList(notificationList == STATE_ALLOW);
policySet = true;
}
@@ -1266,17 +1597,22 @@
}
};
+ public ZenPolicy getZenPolicy() {
+ return Flags.modesUi() ? manualRule.zenPolicy : toZenPolicy();
+ }
+
/**
* Converts a ZenModeConfig to a ZenPolicy
*/
- public ZenPolicy toZenPolicy() {
+ @VisibleForTesting
+ ZenPolicy toZenPolicy() {
ZenPolicy.Builder builder = new ZenPolicy.Builder()
.allowCalls(allowCalls
- ? notificationPolicySendersToZenPolicyPeopleType(allowCallsFrom)
+ ? prioritySendersToPeopleType(allowCallsFrom)
: ZenPolicy.PEOPLE_TYPE_NONE)
.allowRepeatCallers(allowRepeatCallers)
.allowMessages(allowMessages
- ? notificationPolicySendersToZenPolicyPeopleType(allowMessagesFrom)
+ ? prioritySendersToPeopleType(allowMessagesFrom)
: ZenPolicy.PEOPLE_TYPE_NONE)
.allowReminders(allowReminders)
.allowEvents(allowEvents)
@@ -1336,7 +1672,7 @@
if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_MESSAGES,
isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_MESSAGES, defaultPolicy))) {
priorityCategories |= Policy.PRIORITY_CATEGORY_MESSAGES;
- messageSenders = zenPolicyPeopleTypeToNotificationPolicySenders(
+ messageSenders = peopleTypeToPrioritySenders(
zenPolicy.getPriorityMessageSenders(), messageSenders);
}
@@ -1352,7 +1688,7 @@
if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CALLS,
isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CALLS, defaultPolicy))) {
priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS;
- callSenders = zenPolicyPeopleTypeToNotificationPolicySenders(
+ callSenders = peopleTypeToPrioritySenders(
zenPolicy.getPriorityCallSenders(), callSenders);
}
@@ -1452,46 +1788,155 @@
return (policy.suppressedVisualEffects & visualEffect) == 0;
}
+ private boolean isVisualEffectAllowed(int suppressedVisualEffects, int visualEffect) {
+ return (suppressedVisualEffects & visualEffect) == 0;
+ }
+
public Policy toNotificationPolicy() {
int priorityCategories = 0;
int priorityCallSenders = Policy.PRIORITY_SENDERS_CONTACTS;
int priorityMessageSenders = Policy.PRIORITY_SENDERS_CONTACTS;
int priorityConversationSenders = Policy.CONVERSATION_SENDERS_IMPORTANT;
- if (allowConversations) {
- priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS;
- }
- if (allowCalls) {
- priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS;
- }
- if (allowMessages) {
- priorityCategories |= Policy.PRIORITY_CATEGORY_MESSAGES;
- }
- if (allowEvents) {
- priorityCategories |= Policy.PRIORITY_CATEGORY_EVENTS;
- }
- if (allowReminders) {
- priorityCategories |= Policy.PRIORITY_CATEGORY_REMINDERS;
- }
- if (allowRepeatCallers) {
- priorityCategories |= Policy.PRIORITY_CATEGORY_REPEAT_CALLERS;
- }
- if (allowAlarms) {
- priorityCategories |= Policy.PRIORITY_CATEGORY_ALARMS;
- }
- if (allowMedia) {
- priorityCategories |= Policy.PRIORITY_CATEGORY_MEDIA;
- }
- if (allowSystem) {
- priorityCategories |= Policy.PRIORITY_CATEGORY_SYSTEM;
- }
- priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders);
- priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders);
- priorityConversationSenders = zenPolicyConversationSendersToNotificationPolicy(
- allowConversationsFrom, priorityConversationSenders);
+ int state = 0;
+ int suppressedVisualEffects = 0;
- int state = areChannelsBypassingDnd ? Policy.STATE_CHANNELS_BYPASSING_DND : 0;
- if (Flags.modesApi()) {
- state = Policy.policyState(areChannelsBypassingDnd, allowPriorityChannels);
+ if (Flags.modesUi()) {
+ if (manualRule.zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_EVENTS, false)) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_EVENTS;
+ }
+ if (manualRule.zenPolicy.isCategoryAllowed(
+ ZenPolicy.PRIORITY_CATEGORY_REMINDERS, false)) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_REMINDERS;
+ }
+ if (manualRule.zenPolicy.isCategoryAllowed(
+ ZenPolicy.PRIORITY_CATEGORY_REPEAT_CALLERS, false)) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_REPEAT_CALLERS;
+ }
+ if (manualRule.zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_ALARMS, false)) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_ALARMS;
+ }
+ if (manualRule.zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_MEDIA, false)) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_MEDIA;
+ }
+ if (manualRule.zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_SYSTEM, false)) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_SYSTEM;
+ }
+
+ if (manualRule.zenPolicy.getPriorityCategoryConversations() == STATE_ALLOW) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS;
+ }
+ priorityConversationSenders = zenPolicyConversationSendersToNotificationPolicy(
+ manualRule.zenPolicy.getPriorityConversationSenders(),
+ CONVERSATION_SENDERS_NONE);
+ if (manualRule.zenPolicy.getPriorityCategoryCalls() == STATE_ALLOW) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS;
+ }
+ priorityCallSenders = peopleTypeToPrioritySenders(
+ manualRule.zenPolicy.getPriorityCallSenders(), DEFAULT_CALLS_SOURCE);
+ if (manualRule.zenPolicy.getPriorityCategoryMessages() == STATE_ALLOW) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_MESSAGES;
+ }
+ priorityMessageSenders = peopleTypeToPrioritySenders(
+ manualRule.zenPolicy.getPriorityMessageSenders(), DEFAULT_SOURCE);
+
+ state = Policy.policyState(areChannelsBypassingDnd,
+ manualRule.zenPolicy.getPriorityChannelsAllowed() != STATE_DISALLOW);
+
+ boolean suppressFullScreenIntent = !manualRule.zenPolicy.isVisualEffectAllowed(
+ ZenPolicy.VISUAL_EFFECT_FULL_SCREEN_INTENT,
+ isVisualEffectAllowed(DEFAULT_SUPPRESSED_VISUAL_EFFECTS,
+ ZenPolicy.VISUAL_EFFECT_FULL_SCREEN_INTENT));
+
+ boolean suppressLights = !manualRule.zenPolicy.isVisualEffectAllowed(
+ ZenPolicy.VISUAL_EFFECT_LIGHTS,
+ isVisualEffectAllowed(DEFAULT_SUPPRESSED_VISUAL_EFFECTS,
+ ZenPolicy.VISUAL_EFFECT_LIGHTS));
+
+ boolean suppressAmbient = !manualRule.zenPolicy.isVisualEffectAllowed(
+ ZenPolicy.VISUAL_EFFECT_AMBIENT,
+ isVisualEffectAllowed(DEFAULT_SUPPRESSED_VISUAL_EFFECTS,
+ ZenPolicy.VISUAL_EFFECT_AMBIENT));
+
+ if (suppressFullScreenIntent && suppressLights && suppressAmbient) {
+ suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
+ }
+
+ if (suppressFullScreenIntent) {
+ suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
+ }
+
+ if (suppressLights) {
+ suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_LIGHTS;
+ }
+
+ if (!manualRule.zenPolicy.isVisualEffectAllowed(ZenPolicy.VISUAL_EFFECT_PEEK,
+ isVisualEffectAllowed(DEFAULT_SUPPRESSED_VISUAL_EFFECTS,
+ ZenPolicy.VISUAL_EFFECT_PEEK))) {
+ suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_PEEK;
+ suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_SCREEN_ON;
+ }
+
+ if (!manualRule.zenPolicy.isVisualEffectAllowed(ZenPolicy.VISUAL_EFFECT_STATUS_BAR,
+ isVisualEffectAllowed(DEFAULT_SUPPRESSED_VISUAL_EFFECTS,
+ ZenPolicy.VISUAL_EFFECT_STATUS_BAR))) {
+ suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_STATUS_BAR;
+ }
+
+ if (!manualRule.zenPolicy.isVisualEffectAllowed(ZenPolicy.VISUAL_EFFECT_BADGE,
+ isVisualEffectAllowed(DEFAULT_SUPPRESSED_VISUAL_EFFECTS,
+ ZenPolicy.VISUAL_EFFECT_BADGE))) {
+ suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_BADGE;
+ }
+
+ if (suppressAmbient) {
+ suppressedVisualEffects |= SUPPRESSED_EFFECT_AMBIENT;
+ }
+
+ if (!manualRule.zenPolicy.isVisualEffectAllowed(
+ ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST,
+ isVisualEffectAllowed(DEFAULT_SUPPRESSED_VISUAL_EFFECTS,
+ ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST))) {
+ suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
+ }
+ } else {
+ if (isAllowConversations()) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS;
+ }
+ if (isAllowCalls()) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS;
+ }
+ if (isAllowMessages()) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_MESSAGES;
+ }
+ if (isAllowEvents()) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_EVENTS;
+ }
+ if (isAllowReminders()) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_REMINDERS;
+ }
+ if (isAllowRepeatCallers()) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_REPEAT_CALLERS;
+ }
+ if (isAllowAlarms()) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_ALARMS;
+ }
+ if (isAllowMedia()) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_MEDIA;
+ }
+ if (isAllowSystem()) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_SYSTEM;
+ }
+ priorityCallSenders = sourceToPrioritySenders(getAllowCallsFrom(), priorityCallSenders);
+ priorityMessageSenders = sourceToPrioritySenders(
+ getAllowMessagesFrom(), priorityMessageSenders);
+ priorityConversationSenders = zenPolicyConversationSendersToNotificationPolicy(
+ getAllowConversationsFrom(), priorityConversationSenders);
+
+ state = areChannelsBypassingDnd ? Policy.STATE_CHANNELS_BYPASSING_DND : 0;
+ if (Flags.modesApi()) {
+ state = Policy.policyState(areChannelsBypassingDnd, allowPriorityChannels);
+ }
+ suppressedVisualEffects = getSuppressedVisualEffects();
}
return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders,
@@ -1544,31 +1989,38 @@
public void applyNotificationPolicy(Policy policy) {
if (policy == null) return;
- allowAlarms = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_ALARMS) != 0;
- allowMedia = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MEDIA) != 0;
- allowSystem = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_SYSTEM) != 0;
- allowEvents = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_EVENTS) != 0;
- allowReminders = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REMINDERS) != 0;
- allowCalls = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_CALLS) != 0;
- allowMessages = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) != 0;
- allowRepeatCallers = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REPEAT_CALLERS)
- != 0;
- allowCallsFrom = normalizePrioritySenders(policy.priorityCallSenders, allowCallsFrom);
- allowMessagesFrom = normalizePrioritySenders(policy.priorityMessageSenders,
- allowMessagesFrom);
- if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) {
- suppressedVisualEffects = policy.suppressedVisualEffects;
+ if (Flags.modesUi()) {
+ manualRule.zenPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy);
+ } else {
+ setAllowAlarms((policy.priorityCategories & Policy.PRIORITY_CATEGORY_ALARMS) != 0);
+ allowMedia = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MEDIA) != 0;
+ allowSystem = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_SYSTEM) != 0;
+ allowEvents = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_EVENTS) != 0;
+ allowReminders = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REMINDERS) != 0;
+ allowCalls = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_CALLS) != 0;
+ allowMessages = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) != 0;
+ allowRepeatCallers =
+ (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REPEAT_CALLERS)
+ != 0;
+ allowCallsFrom = normalizePrioritySenders(policy.priorityCallSenders, allowCallsFrom);
+ allowMessagesFrom = normalizePrioritySenders(policy.priorityMessageSenders,
+ allowMessagesFrom);
+ if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) {
+ suppressedVisualEffects = policy.suppressedVisualEffects;
+ }
+ allowConversations = (policy.priorityCategories
+ & Policy.PRIORITY_CATEGORY_CONVERSATIONS) != 0;
+ allowConversationsFrom = normalizeConversationSenders(allowConversations,
+ policy.priorityConversationSenders,
+ allowConversationsFrom);
+ if (policy.state != Policy.STATE_UNSET) {
+ if (Flags.modesApi()) {
+ setAllowPriorityChannels(policy.allowPriorityChannels());
+ }
+ }
}
- allowConversations = (policy.priorityCategories
- & Policy.PRIORITY_CATEGORY_CONVERSATIONS) != 0;
- allowConversationsFrom = normalizeConversationSenders(allowConversations,
- policy.priorityConversationSenders,
- allowConversationsFrom);
if (policy.state != Policy.STATE_UNSET) {
areChannelsBypassingDnd = (policy.state & Policy.STATE_CHANNELS_BYPASSING_DND) != 0;
- if (Flags.modesApi()) {
- allowPriorityChannels = policy.allowPriorityChannels();
- }
}
}
@@ -1995,49 +2447,11 @@
return "";
}
- public static String getConditionSummary(Context context, ZenModeConfig config,
- int userHandle, boolean shortVersion) {
- return getConditionLine(context, config, userHandle, false /*useLine1*/, shortVersion);
- }
-
- private static String getConditionLine(Context context, ZenModeConfig config,
- int userHandle, boolean useLine1, boolean shortVersion) {
- if (config == null) return "";
- String summary = "";
- if (config.manualRule != null) {
- final Uri id = config.manualRule.conditionId;
- if (config.manualRule.enabler != null) {
- summary = getOwnerCaption(context, config.manualRule.enabler);
- } else {
- if (id == null) {
- summary = context.getString(com.android.internal.R.string.zen_mode_forever);
- } else {
- final long time = tryParseCountdownConditionId(id);
- Condition c = config.manualRule.condition;
- if (time > 0) {
- final long now = System.currentTimeMillis();
- final long span = time - now;
- c = toTimeCondition(context, time, Math.round(span / (float) MINUTES_MS),
- userHandle, shortVersion);
- }
- final String rt = c == null ? "" : useLine1 ? c.line1 : c.summary;
- summary = TextUtils.isEmpty(rt) ? "" : rt;
- }
- }
+ public boolean isManualActive() {
+ if (!Flags.modesUi()) {
+ return manualRule != null;
}
- for (ZenRule automaticRule : config.automaticRules.values()) {
- if (automaticRule.isAutomaticActive()) {
- if (summary.isEmpty()) {
- summary = automaticRule.name;
- } else {
- summary = context.getResources()
- .getString(R.string.zen_mode_rule_name_combination, summary,
- automaticRule.name);
- }
-
- }
- }
- return summary;
+ return manualRule != null && manualRule.isAutomaticActive();
}
public static class ZenRule implements Parcelable {
@@ -2401,7 +2815,7 @@
public static boolean isZenOverridingRinger(int zen, Policy consolidatedPolicy) {
return zen == Global.ZEN_MODE_NO_INTERRUPTIONS
|| zen == Global.ZEN_MODE_ALARMS
- || (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+ || (zen == ZEN_MODE_IMPORTANT_INTERRUPTIONS
&& ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(consolidatedPolicy));
}
@@ -2410,21 +2824,40 @@
* This includes notification, ringer and system sounds
*/
public static boolean areAllPriorityOnlyRingerSoundsMuted(ZenModeConfig config) {
- boolean areChannelsBypassingDnd = config.areChannelsBypassingDnd;
- if (Flags.modesApi()) {
- areChannelsBypassingDnd = config.areChannelsBypassingDnd
- && config.allowPriorityChannels;
+ if (Flags.modesUi()) {
+ final ZenPolicy policy = config.manualRule.zenPolicy;
+ return !policy.isCategoryAllowed(PRIORITY_CATEGORY_REMINDERS, false)
+ && !policy.isCategoryAllowed(PRIORITY_CATEGORY_CALLS, false)
+ && !policy.isCategoryAllowed(PRIORITY_CATEGORY_MESSAGES, false)
+ && !policy.isCategoryAllowed(PRIORITY_CATEGORY_EVENTS, false)
+ && !policy.isCategoryAllowed(PRIORITY_CATEGORY_REPEAT_CALLERS, false)
+ && !policy.isCategoryAllowed(PRIORITY_CATEGORY_SYSTEM, false)
+ && !(config.areChannelsBypassingDnd && policy.getPriorityChannelsAllowed()
+ == STATE_ALLOW);
+
+ } else {
+ boolean areChannelsBypassingDnd = config.areChannelsBypassingDnd;
+ if (Flags.modesApi()) {
+ areChannelsBypassingDnd = config.areChannelsBypassingDnd
+ && config.isAllowPriorityChannels();
+ }
+ return !config.isAllowReminders() && !config.isAllowCalls() && !config.isAllowMessages()
+ && !config.isAllowEvents() && !config.isAllowRepeatCallers()
+ && !areChannelsBypassingDnd && !config.isAllowSystem();
}
- return !config.allowReminders && !config.allowCalls && !config.allowMessages
- && !config.allowEvents && !config.allowRepeatCallers
- && !areChannelsBypassingDnd && !config.allowSystem;
}
/**
* Determines whether dnd mutes all sounds
*/
public static boolean areAllZenBehaviorSoundsMuted(ZenModeConfig config) {
- return !config.allowAlarms && !config.allowMedia
+ if (Flags.modesUi()) {
+ final ZenPolicy policy = config.manualRule.zenPolicy;
+ return !policy.isCategoryAllowed(PRIORITY_CATEGORY_ALARMS, false)
+ && !policy.isCategoryAllowed(PRIORITY_CATEGORY_MEDIA, false)
+ && areAllPriorityOnlyRingerSoundsMuted(config);
+ }
+ return !config.isAllowAlarms() && !config.isAllowMedia()
&& areAllPriorityOnlyRingerSoundsMuted(config);
}
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
index a77e076..f123a96 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
@@ -101,6 +101,11 @@
private static final String TAG = OnDeviceSandboxedInferenceService.class.getSimpleName();
/**
+ * @hide
+ */
+ public static final String INFERENCE_INFO_BUNDLE_KEY = "inference_info";
+
+ /**
* The {@link Intent} that must be declared as handled by the service. To be supported, the
* service must also require the
* {@link android.Manifest.permission#BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE}
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 57d1b8d..ccec89b 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -496,9 +496,10 @@
if (delegatorPackageName == null) {
delegatorPackageName = view.getContext().getOpPackageName();
}
+ WeakReference<View> viewRef = new WeakReference<>(view);
Consumer<Boolean> consumer = delegationAccepted -> {
if (delegationAccepted) {
- onDelegationAccepted(view);
+ onDelegationAccepted(viewRef.get());
}
};
mImm.acceptStylusHandwritingDelegation(view, delegatorPackageName, view::post, consumer);
@@ -509,6 +510,10 @@
mState.mHandled = true;
mState.mShouldInitHandwriting = false;
}
+ if (view == null) {
+ // can be null if view was detached and was GCed.
+ return;
+ }
if (view instanceof TextView) {
((TextView) view).hideHint();
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a26150c..a11bb78 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -177,6 +177,7 @@
import android.graphics.RenderNode;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
+import android.hardware.SyncFence;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.hardware.display.DisplayManagerGlobal;
@@ -219,6 +220,7 @@
import android.view.InputDevice.InputSourceClass;
import android.view.Surface.OutOfResourcesException;
import android.view.SurfaceControl.Transaction;
+import android.view.SurfaceControl.TransactionStats;
import android.view.View.AttachInfo;
import android.view.View.FocusDirection;
import android.view.View.MeasureSpec;
@@ -293,6 +295,7 @@
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* The top of a view hierarchy, implementing the needed protocol between View
@@ -1189,6 +1192,13 @@
private String mFpsTraceName;
private String mLargestViewTraceName;
+ private final boolean mAppStartInfoTimestampsFlagValue;
+ @GuardedBy("this")
+ private boolean mAppStartTimestampsSent = false;
+ private boolean mAppStartTrackingStarted = false;
+ private long mRenderThreadDrawStartTimeNs = -1;
+ private long mFirstFramePresentedTimeNs = -1;
+
private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
private static boolean sToolkitFrameRateFunctionEnablingReadOnlyFlagValue;
private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
@@ -1306,6 +1316,8 @@
} else {
mSensitiveContentProtectionService = null;
}
+
+ mAppStartInfoTimestampsFlagValue = android.app.Flags.appStartInfoTimestamps();
}
public static void addFirstDrawHandler(Runnable callback) {
@@ -2578,6 +2590,12 @@
notifySurfaceDestroyed();
}
destroySurface();
+
+ // Reset so they can be sent again for warm starts.
+ mAppStartTimestampsSent = false;
+ mAppStartTrackingStarted = false;
+ mRenderThreadDrawStartTimeNs = -1;
+ mFirstFramePresentedTimeNs = -1;
}
}
}
@@ -4376,6 +4394,30 @@
reportDrawFinished(t, seqId);
}
});
+
+ // Only trigger once per {@link ViewRootImpl} instance, so don't add listener if
+ // {link mTransactionCompletedTimeNs} has already been set.
+ if (mAppStartInfoTimestampsFlagValue && !mAppStartTrackingStarted) {
+ mAppStartTrackingStarted = true;
+ Transaction transaction = new Transaction();
+ transaction.addTransactionCompletedListener(mExecutor,
+ new Consumer<TransactionStats>() {
+ @Override
+ public void accept(TransactionStats transactionStats) {
+ SyncFence presentFence = transactionStats.getPresentFence();
+ if (presentFence.awaitForever()) {
+ if (mFirstFramePresentedTimeNs == -1) {
+ // Only trigger once per {@link ViewRootImpl} instance.
+ mFirstFramePresentedTimeNs = presentFence.getSignalTime();
+ maybeSendAppStartTimes();
+ }
+ }
+ presentFence.close();
+ }
+ });
+ applyTransactionOnDraw(transaction);
+ }
+
if (DEBUG_BLAST) {
Log.d(mTag, "Setup new sync=" + mWmsRequestSyncGroup.getName());
}
@@ -4383,6 +4425,45 @@
mWmsRequestSyncGroup.add(this, null /* runnable */);
}
+ private void maybeSendAppStartTimes() {
+ synchronized (this) {
+ if (mAppStartTimestampsSent) {
+ // Don't send timestamps more than once.
+ return;
+ }
+
+ // If we already have {@link mRenderThreadDrawStartTimeNs} then pass it through, if not
+ // post to main thread and check if we have it there.
+ if (mRenderThreadDrawStartTimeNs != -1) {
+ sendAppStartTimesLocked();
+ } else {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (ViewRootImpl.this) {
+ if (mRenderThreadDrawStartTimeNs == -1) {
+ return;
+ }
+ sendAppStartTimesLocked();
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @GuardedBy("this")
+ private void sendAppStartTimesLocked() {
+ try {
+ ActivityManager.getService().reportStartInfoViewTimestamps(
+ mRenderThreadDrawStartTimeNs, mFirstFramePresentedTimeNs);
+ mAppStartTimestampsSent = true;
+ } catch (RemoteException e) {
+ // Ignore, timestamps may be lost.
+ if (DBG) Log.d(TAG, "Exception attempting to report start timestamps.", e);
+ }
+ }
+
/**
* Helper used to notify the service to block projection when a sensitive
* view (the view displays sensitive content) is attached to the window.
@@ -5569,7 +5650,13 @@
registerCallbackForPendingTransactions();
}
+ long timeNs = SystemClock.uptimeNanos();
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
+
+ // Only trigger once per {@link ViewRootImpl} instance.
+ if (mAppStartInfoTimestampsFlagValue && mRenderThreadDrawStartTimeNs == -1) {
+ mRenderThreadDrawStartTimeNs = timeNs;
+ }
} else {
// If we get here with a disabled & requested hardware renderer, something went
// wrong (an invalidate posted right before we destroyed the hardware surface
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 42bf420..7b7ead4 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -963,7 +963,7 @@
* true:
* <ul>
* <li>Activity has requested orientation more than two times within one-second timer
- * <li>Activity is not letterboxed for fixed orientation
+ * <li>Activity is not letterboxed for fixed-orientation apps
* </ul>
*
* <p>Setting this property to {@code false} informs the system that the app must be
@@ -1055,22 +1055,22 @@
* for an app to inform the system that the app should be excluded from the camera compatibility
* force rotation treatment.
*
- * <p>The camera compatibility treatment aligns orientations of portrait app window and natural
- * orientation of the device and set opposite to natural orientation for a landscape app
- * window. Mismatch between them can lead to camera issues like sideways or stretched
+ * <p>The camera compatibility treatment aligns portrait app windows with the natural
+ * orientation of the device and landscape app windows opposite the device natural orientation.
+ * Mismatch between the orientations can lead to camera issues like a sideways or stretched
* viewfinder since this is one of the strongest assumptions that apps make when they implement
- * camera previews. Since app and natural display orientations aren't guaranteed to match, the
- * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to
+ * camera previews. Since app and device natural orientations aren't guaranteed to match, the
+ * rotation can cause letterboxing. The forced rotation is triggered as soon as an app opens the
* camera and is removed once camera is closed.
*
- * <p>The camera compatibility can be enabled by device manufacturers on displays that have the
- * ignore requested orientation display setting enabled (enables compatibility mode for fixed
- * orientation on Android 12 (API level 31) or higher; see
- * <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced
- * letterboxing</a> for more details).
+ * <p>Camera compatibility can be enabled by device manufacturers on displays that have the
+ * ignore requested orientation display setting enabled, which enables compatibility mode for
+ * fixed-orientation apps on Android 12 (API level 31) or higher. See
+ * <a href="{@docRoot}guide/practices/device-compatibility-mode">Device compatibility mode</a>
+ * for more details.
*
* <p>With this property set to {@code true} or unset, the system may apply the force rotation
- * treatment to fixed orientation activities. Device manufacturers can exclude packages from the
+ * treatment to fixed-orientation activities. Device manufacturers can exclude packages from the
* treatment using their discretion to improve display compatibility.
*
* <p>With this property set to {@code false}, the system will not apply the force rotation
@@ -1093,12 +1093,12 @@
* for an app to inform the system that the app should be excluded from the activity "refresh"
* after the camera compatibility force rotation treatment.
*
- * <p>The camera compatibility treatment aligns orientations of portrait app window and natural
- * orientation of the device and set opposite to natural orientation for a landscape app
- * window. Mismatch between them can lead to camera issues like sideways or stretched
+ * <p>The camera compatibility treatment aligns portrait app windows with the natural
+ * orientation of the device and landscape app windows opposite the device natural orientation.
+ * Mismatch between the orientations can lead to camera issues like a sideways or stretched
* viewfinder since this is one of the strongest assumptions that apps make when they implement
- * camera previews. Since app and natural display orientations aren't guaranteed to match, the
- * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to
+ * camera previews. Since app and device natural orientations aren't guaranteed to match, the
+ * rotation can cause letterboxing. The forced rotation is triggered as soon as an app opens the
* camera and is removed once camera is closed.
*
* <p>Force rotation is followed by the "Refresh" of the activity by going through "resumed ->
@@ -1109,10 +1109,10 @@
* rotation.
*
* <p>The camera compatibility can be enabled by device manufacturers on displays that have the
- * ignore requested orientation display setting enabled (enables compatibility mode for fixed
- * orientation on Android 12 (API level 31) or higher; see
- * <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced
- * letterboxing</a> for more details).
+ * ignore requested orientation display setting enabled, which enables compatibility mode for
+ * fixed-orientation apps on Android 12 (API level 31) or higher. See
+ * <a href="{@docRoot}guide/practices/device-compatibility-mode">Device compatibility mode</a>
+ * for more details.
*
* <p>With this property set to {@code true} or unset, the system may "refresh" activity after
* the force rotation treatment. Device manufacturers can exclude packages from the "refresh"
@@ -1140,12 +1140,11 @@
* "stopped -> resumed".
*
* <p>The camera compatibility treatment aligns orientations of portrait app window and natural
- * orientation of the device and set opposite to natural orientation for a landscape app
- * window. Mismatch between them can lead to camera issues like sideways or stretched
- * viewfinder since this is one of the strongest assumptions that apps make when they implement
- * camera previews. Since app and natural display orientations aren't guaranteed to match, the
- * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to
- * camera and is removed once camera is closed.
+ * orientation of the device. Mismatch between the orientations can lead to camera issues like a
+ * sideways or stretched viewfinder since this is one of the strongest assumptions that apps
+ * make when they implement camera previews. Since app and natural display orientations aren't
+ * guaranteed to match, the rotation can cause letterboxing. The forced rotation is triggered as
+ * soon as app opens the camera and is removed once camera is closed.
*
* <p>Force rotation is followed by the "Refresh" of the activity by going through "resumed ->
* ... -> stopped -> ... -> resumed" cycle (by default) or "resumed -> paused -> resumed" cycle
@@ -1154,10 +1153,10 @@
* to sideways or stretching issues persisting even after force rotation.
*
* <p>The camera compatibility can be enabled by device manufacturers on displays that have the
- * ignore requested orientation display setting enabled (enables compatibility mode for fixed
- * orientation on Android 12 (API level 31) or higher; see
- * <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced
- * letterboxing</a> for more details).
+ * ignore requested orientation display setting enabled, which enables compatibility mode for
+ * fixed-orientation apps on Android 12 (API level 31) or higher. See
+ * <a href="{@docRoot}guide/practices/device-compatibility-mode">Device compatibility mode</a>
+ * for more details.
*
* <p>Device manufacturers can override packages to "refresh" via "resumed -> paused -> resumed"
* cycle using their discretion to improve display compatibility.
@@ -1203,7 +1202,7 @@
* <p>With this property set to {@code true} or unset, device manufacturers can override
* orientation for the app using their discretion to improve display compatibility.
*
- * <p>With this property set to {@code false}, device manufactured per-app override for
+ * <p>With this property set to {@code false}, device manufacturer per-app override for
* orientation won't be applied.
*
* <p><b>Syntax:</b>
@@ -1227,15 +1226,15 @@
* <p>When this compat override is enabled and while display is fixed to the landscape natural
* orientation, the orientation requested by the activity will be still respected by bounds
* resolution logic. For instance, if an activity requests portrait orientation, then activity
- * will appear in the letterbox mode for fixed orientation with the display rotated to the
- * lanscape natural orientation.
+ * appears in letterbox mode for fixed-orientation apps with the display rotated to the lanscape
+ * natural orientation.
*
* <p>The treatment is disabled by default but device manufacturers can enable the treatment
* using their discretion to improve display compatibility on displays that have the ignore
- * orientation request display setting enabled by OEMs on the device (enables compatibility mode
- * for fixed orientation on Android 12 (API level 31) or higher; see
- * <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced
- * letterboxing</a> for more details).
+ * orientation request display setting enabled by OEMs on the device, which enables
+ * compatibility mode for fixed-orientation apps on Android 12 (API level 31) or higher. See
+ * <a href="{@docRoot}guide/practices/device-compatibility-mode">Device compatibility mode</a>
+ * for more details.
*
* <p>With this property set to {@code true} or unset, the system wiil use landscape display
* orientation when the following conditions are met:
@@ -1246,7 +1245,7 @@
* <li>Device manufacturer enabled the treatment.
* </ul>
*
- * <p>With this property set to {@code false}, device manufactured per-app override for
+ * <p>With this property set to {@code false}, device manufacturer per-app override for
* display orientation won't be applied.
*
* <p><b>Syntax:</b>
@@ -1344,13 +1343,11 @@
* see {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE} to
* disable the full-screen option only.
*
- * <p>The user override is intended to improve the app experience on devices
- * that have the ignore orientation request display setting enabled by OEMs
- * (enables compatibility mode for fixed orientation on Android 12 (API
- * level 31) or higher; see
- * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-compatibility-mode">
- * Large screen compatibility mode</a>
- * for more details).
+ * <p>The user override is intended to improve the app experience on devices that have the
+ * ignore orientation request display setting enabled by OEMs, which enables compatibility mode
+ * for fixed-orientation apps on Android 12 (API level 31) or higher. See
+ * <a href="{@docRoot}guide/practices/device-compatibility-mode">Device compatibility mode</a>
+ * for more details.
*
* <p>To opt out of the user aspect ratio compatibility override, add this property
* to your app manifest and set the value to {@code false}. Your app will be excluded
@@ -1383,13 +1380,11 @@
* <p>When users apply the full-screen compatibility override, the orientation
* of the activity is forced to {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER}.
*
- * <p>The user override is intended to improve the app experience on devices
- * that have the ignore orientation request display setting enabled by OEMs
- * (enables compatibility mode for fixed orientation on Android 12 (API
- * level 31) or higher; see
- * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-compatibility-mode">
- * Large screen compatibility mode</a>
- * for more details).
+ * <p>The user override is intended to improve the app experience on devices that have the
+ * ignore orientation request display setting enabled by OEMs, which enables compatibility mode
+ * for fixed-orientation apps on Android 12 (API level 31) or higher. See
+ * <a href="{@docRoot}guide/practices/device-compatibility-mode">Device compatibility mode</a>
+ * for more details.
*
* <p>To opt out of the full-screen option of the user aspect ratio compatibility
* override, add this property to your app manifest and set the value to {@code false}.
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 950dfee..199a69a 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -524,7 +524,7 @@
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_AUTOFILL,
DEVICE_CONFIG_INCLUDE_INVISIBLE_VIEW_GROUP_IN_ASSIST_STRUCTURE,
- false);
+ true);
}
/** @hide */
diff --git a/core/java/android/view/autofill/AutofillId.java b/core/java/android/view/autofill/AutofillId.java
index 15ba1a1..6b60858 100644
--- a/core/java/android/view/autofill/AutofillId.java
+++ b/core/java/android/view/autofill/AutofillId.java
@@ -61,6 +61,11 @@
}
/** @hide */
+ public AutofillId(@NonNull AutofillId hostId, int virtualChildId, int sessionId) {
+ this(FLAG_IS_VIRTUAL_INT | FLAG_HAS_SESSION, hostId.mViewId, virtualChildId, sessionId);
+ }
+
+ /** @hide */
@TestApi
public AutofillId(@NonNull AutofillId hostId, long virtualChildId, int sessionId) {
this(FLAG_IS_VIRTUAL_LONG | FLAG_HAS_SESSION, hostId.mViewId, virtualChildId, sessionId);
@@ -236,9 +241,9 @@
public String toString() {
final StringBuilder builder = new StringBuilder().append(mViewId);
if (isVirtualInt()) {
- builder.append(':').append(mVirtualIntId);
+ builder.append(":i").append(mVirtualIntId);
} else if (isVirtualLong()) {
- builder.append(':').append(mVirtualLongId);
+ builder.append(":l").append(mVirtualLongId);
}
if (hasSession()) {
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index ad513f1..0d4c556 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -193,6 +193,7 @@
@RequiresFeature(PackageManager.FEATURE_AUTOFILL)
public final class AutofillManager {
+ private static final boolean DBG = false;
private static final String TAG = "AutofillManager";
/**
@@ -2027,7 +2028,31 @@
if (!hasAutofillFeature()) {
return;
}
+ if (DBG) {
+ Log.v(TAG, "notifyValueChanged() called with virtualId:" + virtualId + " value:"
+ + value);
+ }
synchronized (mLock) {
+ if (mLastAutofilledData != null) {
+ AutofillId id = new AutofillId(view.getAutofillId(), virtualId, mSessionId);
+ if (mLastAutofilledData.containsKey(id)) {
+ if (Objects.equals(mLastAutofilledData.get(id), value)) {
+ // Indicates that the view was autofilled
+ if (sDebug) {
+ Log.v(TAG, "notifyValueChanged() virtual view autofilled successfully:"
+ + virtualId + " value:" + value);
+ }
+ try {
+ mService.setViewAutofilled(mSessionId, id, mContext.getUserId());
+ } catch (RemoteException e) {
+ // The failure could be a consequence of something going wrong on the
+ // server side. Do nothing here since it's just logging, but it's
+ // possible follow-up actions may fail.
+ Log.w(TAG, "RemoteException caught but ignored " + e);
+ }
+ }
+ }
+ }
if (!mEnabled || !isActiveLocked()) {
if (sVerbose) {
Log.v(TAG, "notifyValueChanged(" + view.getAutofillId() + ":" + virtualId
@@ -2985,16 +3010,34 @@
mLastAutofilledData.put(view.getAutofillId(), targetValue);
}
view.setAutofilled(true, hideHighlight);
+ if (sDebug) {
+ Log.d(TAG, "View " + view.getAutofillId() + " autofilled synchronously.");
+ }
try {
mService.setViewAutofilled(mSessionId, view.getAutofillId(), mContext.getUserId());
} catch (RemoteException e) {
// The failure could be a consequence of something going wrong on the server side.
// Do nothing here since it's just logging, but it's possible follow-up actions may
// fail.
+ Log.w(TAG, "Unable to log due to " + e);
+ }
+ } else {
+ if (sDebug) {
+ Log.d(TAG, "View " + view.getAutofillId() + " " + view.getClass().toString()
+ + " from " + view.getClass().getPackageName()
+ + " : didn't fill in synchronously. It may fill asynchronously.");
}
}
}
+ /**
+ * Returns String with text "null" if the object is null, or the actual string represented by
+ * the object.
+ */
+ private @NonNull String getString(Object obj) {
+ return obj == null ? "null" : obj.toString();
+ }
+
private void onGetCredentialException(int sessionId, AutofillId id, String errorType,
String errorMsg) {
synchronized (mLock) {
@@ -3114,6 +3157,10 @@
ArrayList<AutofillId> failedIds = new ArrayList<>();
+ if (mLastAutofilledData == null) {
+ mLastAutofilledData = new ParcelableMap(itemCount);
+ }
+
for (int i = 0; i < itemCount; i++) {
final AutofillId id = ids.get(i);
final AutofillValue value = values.get(i);
@@ -3126,6 +3173,9 @@
failedIds.add(id);
continue;
}
+ // Mark the view as to be autofilled with 'value'
+ mLastAutofilledData.put(id, value);
+
if (id.isVirtualInt()) {
if (virtualValues == null) {
// Most likely there will be just one view with virtual children.
@@ -3139,12 +3189,6 @@
}
valuesByParent.put(id.getVirtualChildIntId(), value);
} else {
- // Mark the view as to be autofilled with 'value'
- if (mLastAutofilledData == null) {
- mLastAutofilledData = new ParcelableMap(itemCount - i);
- }
- mLastAutofilledData.put(id, value);
-
view.autofill(value);
// Set as autofilled if the values match now, e.g. when the value was updated
@@ -4282,14 +4326,16 @@
if (mIsTrackedSaveView && mVisibleTrackedIds.isEmpty()) {
if (sVerbose) {
- Log.v(TAG, "No more visible ids. Invisible = " + mInvisibleTrackedIds);
+ Log.v(TAG, "No more visible tracked save ids. Invisible = "
+ + mInvisibleTrackedIds);
}
finishSessionLocked(/* commitReason= */ COMMIT_REASON_VIEW_CHANGED);
}
if (mVisibleDialogTrackedIds.isEmpty()) {
if (sVerbose) {
- Log.v(TAG, "No more visible ids. Invisible = " + mInvisibleDialogTrackedIds);
+ Log.v(TAG, "No more visible tracked fill dialog ids. Invisible = "
+ + mInvisibleDialogTrackedIds);
}
processNoVisibleTrackedAllViews();
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 2d4bb90..0c63e58 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -129,6 +129,7 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collections;
@@ -2630,13 +2631,17 @@
return false;
}
if (useDelegation) {
+ WeakReference<Executor> executorRef = new WeakReference<>(executor);
+ WeakReference<Consumer<Boolean>> callbackRef = new WeakReference<>(callback);
if (useCallback) {
IBooleanListener listener = new IBooleanListener.Stub() {
@Override
public void onResult(boolean value) {
- executor.execute(() -> {
- callback.accept(value);
- });
+ Executor executor = executorRef.get();
+ Consumer<Boolean> callback = callbackRef.get();
+ if (executor != null && callback != null) {
+ executor.execute(() -> callback.accept(value));
+ }
}
};
if (!IInputMethodManagerGlobalInvoker.acceptStylusHandwritingDelegationAsync(
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 15f9cff8..3c5623f 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1044,11 +1044,6 @@
public int getActionTag() {
return SET_PENDING_INTENT_TEMPLATE_TAG;
}
-
- @Override
- public void visitUris(@NonNull Consumer<Uri> visitor) {
- mPendingIntentTemplate.visitUris(visitor);
- }
}
/**
@@ -1528,11 +1523,6 @@
public int getActionTag() {
return SET_REMOTE_VIEW_ADAPTER_INTENT_TAG;
}
-
- @Override
- public void visitUris(@NonNull Consumer<Uri> visitor) {
- mIntent.visitUris(visitor);
- }
}
/**
@@ -1611,11 +1601,6 @@
public int getActionTag() {
return SET_ON_CLICK_RESPONSE_TAG;
}
-
- @Override
- public void visitUris(@NonNull Consumer<Uri> visitor) {
- mResponse.visitUris(visitor);
- }
}
/** Helper action to configure handwriting delegation via {@link PendingIntent}. */
@@ -1663,11 +1648,6 @@
public int getActionTag() {
return SET_ON_STYLUS_HANDWRITING_RESPONSE_TAG;
}
-
- @Override
- public void visitUris(@NonNull Consumer<Uri> visitor) {
- mPendingIntent.visitUris(visitor);
- }
}
/**
@@ -1738,11 +1718,6 @@
public int getActionTag() {
return SET_ON_CHECKED_CHANGE_RESPONSE_TAG;
}
-
- @Override
- public void visitUris(@NonNull Consumer<Uri> visitor) {
- mResponse.visitUris(visitor);
- }
}
/** @hide **/
@@ -2302,10 +2277,6 @@
final Icon icon = (Icon) getParameterValue(null);
if (icon != null) visitIconUri(icon, visitor);
break;
- case INTENT:
- final Intent intent = (Intent) getParameterValue(null);
- if (intent != null) intent.visitUris(visitor);
- break;
// TODO(b/281044385): Should we do anything about type BUNDLE?
}
}
@@ -7226,20 +7197,6 @@
mElementNames = parcel.createStringArrayList();
}
- /**
- * See {@link RemoteViews#visitUris(Consumer)}.
- *
- * @hide
- */
- public void visitUris(@NonNull Consumer<Uri> visitor) {
- if (mPendingIntent != null) {
- mPendingIntent.visitUris(visitor);
- }
- if (mFillIntent != null) {
- mFillIntent.visitUris(visitor);
- }
- }
-
private void handleViewInteraction(
View v,
InteractionHandler handler) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index fd3837f..f7e0ec8 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -14051,6 +14051,9 @@
@Override
public void autofill(AutofillValue value) {
+ if (android.view.autofill.Helper.sVerbose) {
+ Log.v(LOG_TAG, "autofill() called on textview for id:" + getAutofillId());
+ }
if (!isTextAutofillable()) {
Log.w(LOG_TAG, "cannot autofill non-editable TextView: " + this);
return;
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 87ede4a..0a4762d 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -180,3 +180,14 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "rear_display_disable_force_desktop_system_decorations"
+ description: "Block system decorations from being added to a rear display when desktop mode is forced"
+ bug: "346103150"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index c14a6c1..63ff598 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -1227,7 +1227,10 @@
requestApplyInsets();
}
}
- if (insets != null) {
+ if (insets != null && (consumedLeft > 0
+ || consumedTop > 0
+ || consumedRight > 0
+ || consumedBottom > 0)) {
insets = insets.inset(consumedLeft, consumedTop, consumedRight, consumedBottom);
}
}
diff --git a/core/res/res/drawable/ic_swipe_down.xml b/core/res/res/drawable/ic_swipe_down.xml
new file mode 100644
index 0000000..15712d6
--- /dev/null
+++ b/core/res/res/drawable/ic_swipe_down.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M180,600L40,460L82,418L152,488Q146,461 143,434Q140,407 140,380Q140,298 167,221Q194,144 245,80L288,123Q245,179 222.5,244.5Q200,310 200,380Q200,406 203,431.5Q206,457 213,482L278,418L320,460L180,600ZM658,833Q635,841 611.5,840.5Q588,840 566,829L304,707L322,667Q332,647 350,634.5Q368,622 390,620L458,615L346,308Q340,292 347,277.5Q354,263 370,257Q386,251 400.5,258Q415,265 421,281L569,688L469,695L600,756Q607,759 615,759.5Q623,760 630,758L787,701Q818,690 832,659.5Q846,629 835,598L780,448Q774,432 781,417.5Q788,403 804,397Q820,391 834.5,398Q849,405 855,421L910,571Q933,634 905.5,693.5Q878,753 815,776L658,833ZM568,568L514,417Q508,401 515,386.5Q522,372 538,366Q554,360 568.5,367Q583,374 589,390L644,540L568,568ZM681,527L640,414Q634,398 641,383.5Q648,369 664,363Q680,357 694.5,364Q709,371 715,387L756,499L681,527ZM689,605L689,605L689,605Q689,605 689,605Q689,605 689,605L689,605Q689,605 689,605Q689,605 689,605L689,605L689,605Z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/immersive_cling_bg_circ.xml b/core/res/res/drawable/immersive_cling_bg.xml
similarity index 67%
rename from core/res/res/drawable/immersive_cling_bg_circ.xml
rename to core/res/res/drawable/immersive_cling_bg.xml
index 4731bbd..de29c32 100644
--- a/core/res/res/drawable/immersive_cling_bg_circ.xml
+++ b/core/res/res/drawable/immersive_cling_bg.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2015 The Android Open Source Project
+ ~ 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.
@@ -15,12 +15,10 @@
~ limitations under the License
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval" >
-
- <solid android:color="@color/white" />
-
- <size
- android:height="56dp"
- android:width="56dp" />
-
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <corners
+ android:bottomLeftRadius="28dp"
+ android:bottomRightRadius="28dp"/>
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainer" />
</shape>
diff --git a/core/res/res/drawable/immersive_cling_btn_bg.xml b/core/res/res/drawable/immersive_cling_btn_bg.xml
new file mode 100644
index 0000000..df49e38
--- /dev/null
+++ b/core/res/res/drawable/immersive_cling_btn_bg.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android">
+ <ripple android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <solid android:color="@android:color/white" />
+ <corners android:radius="28dp" />
+ </shape>
+ </item>
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="28dp" />
+ <solid android:color="?android:attr/colorAccent" />
+ </shape>
+ </item>
+ </ripple>
+</inset>
\ No newline at end of file
diff --git a/core/res/res/drawable/immersive_cling_light_bg_circ.xml b/core/res/res/drawable/immersive_cling_light_bg_circ.xml
deleted file mode 100644
index df5d5ad..0000000
--- a/core/res/res/drawable/immersive_cling_light_bg_circ.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2015 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
- -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval" >
-
- <solid android:color="#80ffffff" />
-
- <size
- android:height="76dp"
- android:width="76dp" />
-
-</shape>
diff --git a/core/res/res/layout/immersive_mode_cling.xml b/core/res/res/layout/immersive_mode_cling.xml
index 9fd615d..2cde9e6 100644
--- a/core/res/res/layout/immersive_mode_cling.xml
+++ b/core/res/res/layout/immersive_mode_cling.xml
@@ -14,79 +14,67 @@
limitations under the License.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="?android:attr/colorAccent"
+ android:background="@android:drawable/immersive_cling_bg"
android:gravity="center_vertical"
- android:paddingBottom="24dp">
+ android:padding="24dp">
- <FrameLayout
- android:id="@+id/immersive_cling_chevron"
- android:layout_width="76dp"
- android:layout_height="76dp"
- android:layout_marginTop="-24dp"
- android:layout_centerHorizontal="true">
-
- <ImageView
- android:id="@+id/immersive_cling_back_bg_light"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scaleType="center"
- android:src="@drawable/immersive_cling_light_bg_circ" />
-
- <ImageView
- android:id="@+id/immersive_cling_back_bg"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scaleType="center"
- android:src="@drawable/immersive_cling_bg_circ" />
-
- <ImageView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:paddingTop="8dp"
- android:scaleType="center"
- android:src="@drawable/ic_expand_more_48dp"
- android:tint="?android:attr/colorAccent"/>
- </FrameLayout>
+ <!-- The top margin of this icon can be adjusted to push the content down to prevent overlapping
+ with the display cutout. -->
+ <ImageView
+ android:id="@+id/immersive_cling_icon"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_centerHorizontal="true"
+ android:scaleType="fitXY"
+ android:src="@drawable/ic_swipe_down"
+ android:tint="?android:attr/colorAccent"
+ android:tintMode="src_in" />
<TextView
android:id="@+id/immersive_cling_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_below="@id/immersive_cling_chevron"
- android:paddingEnd="48dp"
- android:paddingStart="48dp"
- android:paddingTop="40dp"
+ android:layout_below="@id/immersive_cling_icon"
+ android:layout_marginTop="20dp"
+ android:gravity="center_horizontal"
android:text="@string/immersive_cling_title"
- android:textColor="@android:color/white"
- android:textSize="24sp" />
+ android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:textSize="24sp"
+ android:fontFamily="google-sans" />
<TextView
android:id="@+id/immersive_cling_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/immersive_cling_title"
- android:paddingEnd="48dp"
- android:paddingStart="48dp"
- android:paddingTop="12.6dp"
+ android:paddingTop="14dp"
+ android:gravity="center_horizontal"
android:text="@string/immersive_cling_description"
- android:textColor="@android:color/white"
- android:textSize="16sp" />
+ android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
+ android:textSize="14sp"
+ android:fontFamily="google-sans" />
<Button
android:id="@+id/ok"
- style="@style/Widget.Material.Button.Borderless"
+ style="@style/Widget.Material.Button.Borderless.Colored"
+ android:background="@drawable/immersive_cling_btn_bg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_below="@+id/immersive_cling_description"
- android:layout_marginEnd="40dp"
- android:layout_marginTop="18dp"
- android:paddingEnd="8dp"
- android:paddingStart="8dp"
+ android:layout_marginTop="24dp"
+ android:paddingStart="18dp"
+ android:paddingEnd="18dp"
+ android:minWidth="48dp"
+ android:minHeight="48dp"
android:text="@string/immersive_cling_positive"
- android:textColor="@android:color/white"
- android:textSize="14sp" />
-
+ android:textColor="?androidprv:attr/materialColorOnPrimary"
+ android:textAllCaps="false"
+ android:textSize="14sp"
+ android:textFontWeight="500"
+ android:fontFamily="google-sans" />
</RelativeLayout>
diff --git a/core/res/res/values-land/dimens.xml b/core/res/res/values-land/dimens.xml
index f58c4b0..c0916bc 100644
--- a/core/res/res/values-land/dimens.xml
+++ b/core/res/res/values-land/dimens.xml
@@ -69,7 +69,7 @@
<dimen name="timepicker_left_side_width">250dip</dimen>
<!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
- <dimen name="immersive_mode_cling_width">380dp</dimen>
+ <dimen name="immersive_mode_cling_width">500dp</dimen>
<!-- Floating toolbar dimensions -->
<dimen name="floating_toolbar_preferred_width">544dp</dimen>
diff --git a/core/res/res/values-sw600dp/dimens.xml b/core/res/res/values-sw600dp/dimens.xml
index 4c70ea3..4aed94c 100644
--- a/core/res/res/values-sw600dp/dimens.xml
+++ b/core/res/res/values-sw600dp/dimens.xml
@@ -112,7 +112,7 @@
<dimen name="keyguard_muliuser_selector_margin">12dp</dimen>
<!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
- <dimen name="immersive_mode_cling_width">380dp</dimen>
+ <dimen name="immersive_mode_cling_width">600dp</dimen>
<dimen name="floating_toolbar_preferred_width">544dp</dimen>
diff --git a/core/res/res/values-w204dp-round-watch/dimens_material.xml b/core/res/res/values-w204dp-round-watch/dimens_material.xml
new file mode 100644
index 0000000..c07d5c4
--- /dev/null
+++ b/core/res/res/values-w204dp-round-watch/dimens_material.xml
@@ -0,0 +1,20 @@
+<?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>
+ <dimen name="screen_percentage_05">10.2dp</dimen>
+ <dimen name="screen_percentage_10">20.4dp</dimen>
+ <dimen name="screen_percentage_15">30.6dp</dimen>
+</resources>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 27b756d..f94c8ab 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -851,7 +851,12 @@
of the screen.
<p>This attribute is supported by the <a
href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a>
- element. -->
+ element.
+ <aside class="note"><b>Note:</b> Device manufacturers can configure devices to override
+ (ignore) this attribute to improve the layout of apps. See
+ <a href="{@docRoot}guide/practices/device-compatibility-mode">
+ Device compatibility mode</a>.
+ </aside> -->
<attr name="screenOrientation">
<!-- No preference specified: let the system decide the best
orientation. This will either be the orientation selected
@@ -1447,13 +1452,23 @@
no other apps in multi-window visible on screen (e.g. picture-in-picture) or on other
displays. Therefore, this flag cannot be used to assure an exclusive resource access.
- <p>NOTE: A task's root activity value is applied to all additional activities launched in
+ <p>A task's root activity value is applied to all additional activities launched in
the task. That is if the root activity of a task is resizeable then the system will treat
all other activities in the task as resizeable and will not if the root activity isn't
resizeable.
- <p>NOTE: The value of {@link android.R.attr#screenOrientation} is ignored for
- resizeable activities when in multi-window mode before Android 12. -->
+ <aside class="note"><b>Note:</b>
+ <ul>
+ <li>On Android 11 (API level 30) and lower, the value of
+ {@link android.R.attr#screenOrientation} is ignored for resizeable activities
+ in multi-window mode.
+ <li>Device manufacturers can configure devices to override (ignore) this attribute
+ to force apps to resize. The override does not affect the app's support for
+ multi-window mode. See
+ <a href="{@docRoot}guide/practices/device-compatibility-mode">
+ Device compatibility mode</a>.
+ </ul>
+ </aside> -->
<attr name="resizeableActivity" format="boolean" />
<!-- Indicates that the activity specifically supports the picture-in-picture form of
@@ -1470,27 +1485,37 @@
<!-- This value indicates the maximum aspect ratio the activity supports. If the app runs on a
device with a wider aspect ratio, the system automatically letterboxes the app, leaving
portions of the screen unused so the app can run at its specified maximum aspect ratio.
- <p>
- Maximum aspect ratio, expressed as (longer dimension / shorter dimension) in decimal
+ <p>Maximum aspect ratio, expressed as (longer dimension / shorter dimension) in decimal
form. For example, if the maximum aspect ratio is 7:3, set value to 2.33.
- <p>
- Value needs to be greater or equal to 1.0, otherwise it is ignored.
- <p>
- NOTE: This attribute is ignored if the activity has
- {@link android.R.attr#resizeableActivity} set to true. -->
+ <p>Value needs to be greater or equal to 1.0, otherwise it is ignored.
+ <aside class="note"><b>Note:</b>
+ <ul>
+ <li>This attribute is ignored if the activity has
+ {@link android.R.attr#resizeableActivity} set to {@code true}.
+ <li>Device manufacturers can configure devices to override (ignore) this attribute
+ to improve the layout of apps. See
+ <a href="{@docRoot}guide/practices/device-compatibility-mode">
+ Device compatibility mode</a>.
+ </ul>
+ </aside> -->
<attr name="maxAspectRatio" format="float" />
<!-- This value indicates the minimum aspect ratio the activity supports. If the app runs on a
device with a narrower aspect ratio, the system automatically letterboxes the app, leaving
portions of the screen unused so the app can run at its specified minimum aspect ratio.
- <p>
- Minimum aspect ratio, expressed as (longer dimension / shorter dimension) in decimal
- form. For example, if the minimum aspect ratio is 4:3, set value to 1.33.
- <p>
- Value needs to be greater or equal to 1.0, otherwise it is ignored.
- <p>
- NOTE: This attribute is ignored if the activity has
- {@link android.R.attr#resizeableActivity} set to true. -->
+ <p>Minimum aspect ratio, expressed as (longer dimension / shorter dimension) in decimal
+ form. For example, if the minimum aspect ratio is 4:3, set value to 1.33.
+ <p>Value needs to be greater or equal to 1.0, otherwise it is ignored.
+ <aside class="note"><b>Note:</b>
+ <ul>
+ <li>This attribute is ignored if the activity has
+ {@link android.R.attr#resizeableActivity} set to {@code true}.
+ <li>Device manufacturers can configure devices to override (ignore) this attribute
+ to improve the layout of apps. See
+ <a href="{@docRoot}guide/practices/device-compatibility-mode">
+ Device compatibility mode</a>.
+ </ul>
+ </aside> -->
<attr name="minAspectRatio" format="float" />
<!-- This value indicates how tasks rooted at this activity will behave in lockTask mode.
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index b547a7a..b6e8383 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5125,7 +5125,7 @@
<string name="immersive_cling_title">Viewing full screen</string>
<!-- Cling help message description when hiding the navigation bar entering immersive mode [CHAR LIMIT=none] -->
- <string name="immersive_cling_description">To exit, swipe down from the top.</string>
+ <string name="immersive_cling_description">To exit, swipe down from the top of your screen</string>
<!-- Cling help message confirmation button when hiding the navigation bar entering immersive mode [CHAR LIMIT=30] -->
<string name="immersive_cling_positive">Got it</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 639b746..c16bd24 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1619,6 +1619,7 @@
<java-symbol type="layout" name="restrictions_pin_challenge" />
<java-symbol type="layout" name="restrictions_pin_setup" />
<java-symbol type="layout" name="immersive_mode_cling" />
+ <java-symbol type="id" name="immersive_cling_icon" />
<java-symbol type="layout" name="user_switching_dialog" />
<java-symbol type="layout" name="common_tab_settings" />
<java-symbol type="layout" name="resolver_list_per_profile" />
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 15c9047..c8ea374 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -34,6 +34,9 @@
import android.appwidget.AppWidgetHostView;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.AsyncTask;
@@ -834,33 +837,6 @@
}
@Test
- public void visitUris_intents() {
- RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
-
- Uri fillIntentUri = Uri.parse("content://intent/fill");
- views.setOnCheckedChangeResponse(
- R.id.layout,
- RemoteViews.RemoteResponse.fromFillInIntent(new Intent("action", fillIntentUri)));
-
- Uri pendingIntentUri = Uri.parse("content://intent/pending");
- PendingIntent pendingIntent = getPendingIntentWithUri(pendingIntentUri);
- views.setOnClickResponse(
- R.id.layout,
- RemoteViews.RemoteResponse.fromPendingIntent(pendingIntent));
-
- Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
- views.visitUris(visitor);
- verify(visitor, times(1)).accept(eq(fillIntentUri));
- verify(visitor, times(1)).accept(eq(pendingIntentUri));
- }
-
- private PendingIntent getPendingIntentWithUri(Uri uri) {
- return PendingIntent.getActivity(mContext, 0,
- new Intent("action", uri),
- PendingIntent.FLAG_IMMUTABLE);
- }
-
- @Test
public void layoutInflaterFactory_nothingSet_returnsNull() {
final RemoteViews rv = new RemoteViews(mPackage, R.layout.remote_views_test);
assertNull(rv.getLayoutInflaterFactory());
diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig
index 0b9e72d..5ad97e6 100644
--- a/graphics/java/android/framework_graphics.aconfig
+++ b/graphics/java/android/framework_graphics.aconfig
@@ -24,3 +24,12 @@
description: "Return null when decode from URI fails in Icon.loadDrawable()"
bug: "335878768"
}
+
+flag {
+ name: "ok_lab_colorspace"
+ is_exported: true
+ is_fixed_read_only: true
+ namespace: "core_graphics"
+ description: "Add OkLab ColorSpace support"
+ bug: "344038816"
+}
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 4bc3ece..3752257 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -17,6 +17,7 @@
package android.graphics;
import android.annotation.AnyThread;
+import android.annotation.FlaggedApi;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -27,9 +28,13 @@
import android.hardware.DataSpace.ColorDataSpace;
import android.util.SparseIntArray;
+import com.android.graphics.flags.Flags;
+
import libcore.util.NativeAllocationRegistry;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
import java.util.function.DoubleUnaryOperator;
/**
@@ -230,7 +235,9 @@
-2392 / 128.0, 8192 / 1305.0, Rgb.TransferParameters.TYPE_PQish);
// See static initialization block next to #get(Named)
- private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length];
+ private static final HashMap<Integer, ColorSpace> sNamedColorSpaceMap =
+ new HashMap<>();
+
private static final SparseIntArray sDataToColorSpaces = new SparseIntArray();
@NonNull private final String mName;
@@ -745,7 +752,23 @@
* <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
* </table>
*/
- BT2020_PQ
+ BT2020_PQ,
+
+ /**
+ * <p>{@link ColorSpace.Lab Lab} color space OkLab standardized as
+ * OkLab</p>
+ * <table summary="Color space definition">
+ * <tr><th>Property</th><th colspan="4">Value</th></tr>
+ * <tr><td>Name</td><td colspan="4">Oklab</td></tr>
+ * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+ * <tr>
+ * <td>Range</td>
+ * <td colspan="4">\(L: `[0.0, 1.0]`, a: `[-2, 2]`, b: `[-2, 2]`\)</td>
+ * </tr>
+ * </table>
+ */
+ @FlaggedApi(Flags.FLAG_OK_LAB_COLORSPACE)
+ OK_LAB
// Update the initialization block next to #get(Named) when adding new values
}
@@ -1428,11 +1451,11 @@
*/
@NonNull
static ColorSpace get(@IntRange(from = MIN_ID, to = MAX_ID) int index) {
- if (index < 0 || index >= sNamedColorSpaces.length) {
- throw new IllegalArgumentException("Invalid ID, must be in the range [0.." +
- sNamedColorSpaces.length + ")");
+ ColorSpace colorspace = sNamedColorSpaceMap.get(index);
+ if (colorspace == null) {
+ throw new IllegalArgumentException("Invalid ID: " + index);
}
- return sNamedColorSpaces[index];
+ return colorspace;
}
/**
@@ -1485,12 +1508,20 @@
*
* <p>This method is thread-safe.</p>
*
+ * Note that in the Android W release and later, this can return the SRGB ColorSpace if
+ * the {@link ColorSpace.Named} parameter is not enabled in the corresponding release.
+ *
* @param name The name of the color space to get an instance of
- * @return A non-null {@link ColorSpace} instance
+ * @return A non-null {@link ColorSpace} instance. If the ColorSpace is not supported
+ * then the SRGB ColorSpace is returned.
*/
@NonNull
public static ColorSpace get(@NonNull Named name) {
- return sNamedColorSpaces[name.ordinal()];
+ ColorSpace colorSpace = sNamedColorSpaceMap.get(name.ordinal());
+ if (colorSpace == null) {
+ return sNamedColorSpaceMap.get(Named.SRGB.ordinal());
+ }
+ return colorSpace;
}
/**
@@ -1511,7 +1542,8 @@
@NonNull @Size(9) float[] toXYZD50,
@NonNull Rgb.TransferParameters function) {
- for (ColorSpace colorSpace : sNamedColorSpaces) {
+ Collection<ColorSpace> colorspaces = sNamedColorSpaceMap.values();
+ for (ColorSpace colorSpace : colorspaces) {
if (colorSpace.getModel() == Model.RGB) {
ColorSpace.Rgb rgb = (ColorSpace.Rgb) adapt(colorSpace, ILLUMINANT_D50_XYZ);
if (compare(toXYZD50, rgb.mTransform) &&
@@ -1525,25 +1557,25 @@
}
static {
- sNamedColorSpaces[Named.SRGB.ordinal()] = new ColorSpace.Rgb(
+ sNamedColorSpaceMap.put(Named.SRGB.ordinal(), new ColorSpace.Rgb(
"sRGB IEC61966-2.1",
SRGB_PRIMARIES,
ILLUMINANT_D65,
null,
SRGB_TRANSFER_PARAMETERS,
Named.SRGB.ordinal()
- );
+ ));
sDataToColorSpaces.put(DataSpace.DATASPACE_SRGB, Named.SRGB.ordinal());
- sNamedColorSpaces[Named.LINEAR_SRGB.ordinal()] = new ColorSpace.Rgb(
+ sNamedColorSpaceMap.put(Named.LINEAR_SRGB.ordinal(), new ColorSpace.Rgb(
"sRGB IEC61966-2.1 (Linear)",
SRGB_PRIMARIES,
ILLUMINANT_D65,
1.0,
0.0f, 1.0f,
Named.LINEAR_SRGB.ordinal()
- );
+ ));
sDataToColorSpaces.put(DataSpace.DATASPACE_SRGB_LINEAR, Named.LINEAR_SRGB.ordinal());
- sNamedColorSpaces[Named.EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
+ sNamedColorSpaceMap.put(Named.EXTENDED_SRGB.ordinal(), new ColorSpace.Rgb(
"scRGB-nl IEC 61966-2-2:2003",
SRGB_PRIMARIES,
ILLUMINANT_D65,
@@ -1553,112 +1585,113 @@
-0.799f, 2.399f,
SRGB_TRANSFER_PARAMETERS,
Named.EXTENDED_SRGB.ordinal()
- );
+ ));
sDataToColorSpaces.put(DataSpace.DATASPACE_SCRGB, Named.EXTENDED_SRGB.ordinal());
- sNamedColorSpaces[Named.LINEAR_EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
+ sNamedColorSpaceMap.put(Named.LINEAR_EXTENDED_SRGB.ordinal(), new ColorSpace.Rgb(
"scRGB IEC 61966-2-2:2003",
SRGB_PRIMARIES,
ILLUMINANT_D65,
1.0,
-0.5f, 7.499f,
Named.LINEAR_EXTENDED_SRGB.ordinal()
- );
+ ));
sDataToColorSpaces.put(
DataSpace.DATASPACE_SCRGB_LINEAR, Named.LINEAR_EXTENDED_SRGB.ordinal());
- sNamedColorSpaces[Named.BT709.ordinal()] = new ColorSpace.Rgb(
+ sNamedColorSpaceMap.put(Named.BT709.ordinal(), new ColorSpace.Rgb(
"Rec. ITU-R BT.709-5",
SRGB_PRIMARIES,
ILLUMINANT_D65,
null,
SMPTE_170M_TRANSFER_PARAMETERS,
Named.BT709.ordinal()
- );
+ ));
sDataToColorSpaces.put(DataSpace.DATASPACE_BT709, Named.BT709.ordinal());
- sNamedColorSpaces[Named.BT2020.ordinal()] = new ColorSpace.Rgb(
+ sNamedColorSpaceMap.put(Named.BT2020.ordinal(), new ColorSpace.Rgb(
"Rec. ITU-R BT.2020-1",
BT2020_PRIMARIES,
ILLUMINANT_D65,
null,
new Rgb.TransferParameters(1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145, 1 / 0.45),
Named.BT2020.ordinal()
- );
+ ));
+
sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020, Named.BT2020.ordinal());
- sNamedColorSpaces[Named.DCI_P3.ordinal()] = new ColorSpace.Rgb(
+ sNamedColorSpaceMap.put(Named.DCI_P3.ordinal(), new ColorSpace.Rgb(
"SMPTE RP 431-2-2007 DCI (P3)",
DCI_P3_PRIMARIES,
new float[] { 0.314f, 0.351f },
2.6,
0.0f, 1.0f,
Named.DCI_P3.ordinal()
- );
+ ));
sDataToColorSpaces.put(DataSpace.DATASPACE_DCI_P3, Named.DCI_P3.ordinal());
- sNamedColorSpaces[Named.DISPLAY_P3.ordinal()] = new ColorSpace.Rgb(
+ sNamedColorSpaceMap.put(Named.DISPLAY_P3.ordinal(), new ColorSpace.Rgb(
"Display P3",
DCI_P3_PRIMARIES,
ILLUMINANT_D65,
null,
SRGB_TRANSFER_PARAMETERS,
Named.DISPLAY_P3.ordinal()
- );
+ ));
sDataToColorSpaces.put(DataSpace.DATASPACE_DISPLAY_P3, Named.DISPLAY_P3.ordinal());
- sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb(
+ sNamedColorSpaceMap.put(Named.NTSC_1953.ordinal(), new ColorSpace.Rgb(
"NTSC (1953)",
NTSC_1953_PRIMARIES,
ILLUMINANT_C,
null,
SMPTE_170M_TRANSFER_PARAMETERS,
Named.NTSC_1953.ordinal()
- );
- sNamedColorSpaces[Named.SMPTE_C.ordinal()] = new ColorSpace.Rgb(
+ ));
+ sNamedColorSpaceMap.put(Named.SMPTE_C.ordinal(), new ColorSpace.Rgb(
"SMPTE-C RGB",
new float[] { 0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f },
ILLUMINANT_D65,
null,
SMPTE_170M_TRANSFER_PARAMETERS,
Named.SMPTE_C.ordinal()
- );
- sNamedColorSpaces[Named.ADOBE_RGB.ordinal()] = new ColorSpace.Rgb(
+ ));
+ sNamedColorSpaceMap.put(Named.ADOBE_RGB.ordinal(), new ColorSpace.Rgb(
"Adobe RGB (1998)",
new float[] { 0.64f, 0.33f, 0.21f, 0.71f, 0.15f, 0.06f },
ILLUMINANT_D65,
2.2,
0.0f, 1.0f,
Named.ADOBE_RGB.ordinal()
- );
+ ));
sDataToColorSpaces.put(DataSpace.DATASPACE_ADOBE_RGB, Named.ADOBE_RGB.ordinal());
- sNamedColorSpaces[Named.PRO_PHOTO_RGB.ordinal()] = new ColorSpace.Rgb(
+ sNamedColorSpaceMap.put(Named.PRO_PHOTO_RGB.ordinal(), new ColorSpace.Rgb(
"ROMM RGB ISO 22028-2:2013",
new float[] { 0.7347f, 0.2653f, 0.1596f, 0.8404f, 0.0366f, 0.0001f },
ILLUMINANT_D50,
null,
new Rgb.TransferParameters(1.0, 0.0, 1 / 16.0, 0.031248, 1.8),
Named.PRO_PHOTO_RGB.ordinal()
- );
- sNamedColorSpaces[Named.ACES.ordinal()] = new ColorSpace.Rgb(
+ ));
+ sNamedColorSpaceMap.put(Named.ACES.ordinal(), new ColorSpace.Rgb(
"SMPTE ST 2065-1:2012 ACES",
new float[] { 0.73470f, 0.26530f, 0.0f, 1.0f, 0.00010f, -0.0770f },
ILLUMINANT_D60,
1.0,
-65504.0f, 65504.0f,
Named.ACES.ordinal()
- );
- sNamedColorSpaces[Named.ACESCG.ordinal()] = new ColorSpace.Rgb(
+ ));
+ sNamedColorSpaceMap.put(Named.ACESCG.ordinal(), new ColorSpace.Rgb(
"Academy S-2014-004 ACEScg",
new float[] { 0.713f, 0.293f, 0.165f, 0.830f, 0.128f, 0.044f },
ILLUMINANT_D60,
1.0,
-65504.0f, 65504.0f,
Named.ACESCG.ordinal()
- );
- sNamedColorSpaces[Named.CIE_XYZ.ordinal()] = new Xyz(
+ ));
+ sNamedColorSpaceMap.put(Named.CIE_XYZ.ordinal(), new Xyz(
"Generic XYZ",
Named.CIE_XYZ.ordinal()
- );
- sNamedColorSpaces[Named.CIE_LAB.ordinal()] = new ColorSpace.Lab(
+ ));
+ sNamedColorSpaceMap.put(Named.CIE_LAB.ordinal(), new ColorSpace.Lab(
"Generic L*a*b*",
Named.CIE_LAB.ordinal()
- );
- sNamedColorSpaces[Named.BT2020_HLG.ordinal()] = new ColorSpace.Rgb(
+ ));
+ sNamedColorSpaceMap.put(Named.BT2020_HLG.ordinal(), new ColorSpace.Rgb(
"Hybrid Log Gamma encoding",
BT2020_PRIMARIES,
ILLUMINANT_D65,
@@ -1668,9 +1701,9 @@
0.0f, 1.0f,
BT2020_HLG_TRANSFER_PARAMETERS,
Named.BT2020_HLG.ordinal()
- );
+ ));
sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020_HLG, Named.BT2020_HLG.ordinal());
- sNamedColorSpaces[Named.BT2020_PQ.ordinal()] = new ColorSpace.Rgb(
+ sNamedColorSpaceMap.put(Named.BT2020_PQ.ordinal(), new ColorSpace.Rgb(
"Perceptual Quantizer encoding",
BT2020_PRIMARIES,
ILLUMINANT_D65,
@@ -1680,8 +1713,14 @@
0.0f, 1.0f,
BT2020_PQ_TRANSFER_PARAMETERS,
Named.BT2020_PQ.ordinal()
- );
+ ));
sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020_PQ, Named.BT2020_PQ.ordinal());
+ if (Flags.okLabColorspace()) {
+ sNamedColorSpaceMap.put(Named.OK_LAB.ordinal(), new ColorSpace.OkLab(
+ "Oklab",
+ Named.OK_LAB.ordinal()
+ ));
+ }
}
private static double transferHLGOETF(Rgb.TransferParameters params, double x) {
@@ -2142,10 +2181,103 @@
return v;
}
+ }
- private static float clamp(float x, float min, float max) {
- return x < min ? min : x > max ? max : x;
+ private static float clamp(float x, float min, float max) {
+ return x < min ? min : x > max ? max : x;
+ }
+
+ /**
+ * Implementation of the Oklab color space. Oklab uses a D65 white point.
+ */
+ @AnyThread
+ private static final class OkLab extends ColorSpace {
+
+ private OkLab(@NonNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id) {
+ super(name, Model.LAB, id);
}
+
+ @Override
+ public boolean isWideGamut() {
+ return true;
+ }
+
+ @Override
+ public float getMinValue(@IntRange(from = 0, to = 3) int component) {
+ return component == 0 ? 0.0f : -0.5f;
+ }
+
+ @Override
+ public float getMaxValue(@IntRange(from = 0, to = 3) int component) {
+ return component == 0 ? 1.0f : 0.5f;
+ }
+
+ @Override
+ public float[] toXyz(@NonNull @Size(min = 3) float[] v) {
+ v[0] = clamp(v[0], 0.0f, 1.0f);
+ v[1] = clamp(v[1], -0.5f, 0.5f);
+ v[2] = clamp(v[2], -0.5f, 0.5f);
+
+ mul3x3Float3(INVERSE_M2, v);
+ v[0] = v[0] * v[0] * v[0];
+ v[1] = v[1] * v[1] * v[1];
+ v[2] = v[2] * v[2] * v[2];
+
+ mul3x3Float3(INVERSE_M1, v);
+
+ return v;
+ }
+
+ @Override
+ public float[] fromXyz(@NonNull @Size(min = 3) float[] v) {
+ mul3x3Float3(M1, v);
+
+ v[0] = (float) Math.cbrt(v[0]);
+ v[1] = (float) Math.cbrt(v[1]);
+ v[2] = (float) Math.cbrt(v[2]);
+
+ mul3x3Float3(M2, v);
+ return v;
+ }
+
+ /**
+ * Temp array used as input to compute M1 below
+ */
+ private static final float[] M1TMP = {
+ 0.8189330101f, 0.0329845436f, 0.0482003018f,
+ 0.3618667424f, 0.9293118715f, 0.2643662691f,
+ -0.1288597137f, 0.0361456387f, 0.6338517070f
+ };
+
+ /**
+ * This is the matrix applied before the nonlinear transform for (D50) XYZ-to-Oklab.
+ * This combines the D50-to-D65 white point transform with the normal transform matrix
+ * because this is always done together in [fromXyz].
+ */
+ private static final float[] M1 = mul3x3(
+ M1TMP,
+ chromaticAdaptation(Adaptation.BRADFORD, ILLUMINANT_D50, ILLUMINANT_D65)
+ );
+
+ /**
+ * Matrix applied after the nonlinear transform.
+ */
+ private static final float[] M2 = {
+ 0.2104542553f, 1.9779984951f, 0.0259040371f,
+ 0.7936177850f, -2.4285922050f, 0.7827717662f,
+ -0.0040720468f, 0.4505937099f, -0.8086757660f
+ };
+
+ /**
+ * The inverse of the [M1] matrix, transforming back to XYZ (D50)
+ */
+ private static final float[] INVERSE_M1 = inverse3x3(M1);
+
+ /**
+ * The inverse of the [M2] matrix, doing the first linear transform in the
+ * Oklab-to-XYZ before doing the nonlinear transform.
+ */
+ private static final float[] INVERSE_M2 = inverse3x3(M2);
}
/**
diff --git a/keystore/java/android/security/KeyStoreException.java b/keystore/java/android/security/KeyStoreException.java
index 5825fac..eea5690 100644
--- a/keystore/java/android/security/KeyStoreException.java
+++ b/keystore/java/android/security/KeyStoreException.java
@@ -679,6 +679,9 @@
sErrorCodeToFailureInfo.put(ResponseCode.OUT_OF_KEYS_REQUIRES_SYSTEM_UPGRADE,
new PublicErrorInformation(IS_SYSTEM_ERROR | IS_TRANSIENT_ERROR,
ERROR_DEVICE_REQUIRES_UPGRADE_FOR_ATTESTATION));
+ sErrorCodeToFailureInfo.put(ResponseCode.GET_ATTESTATION_APPLICATION_ID_FAILED,
+ new PublicErrorInformation(IS_SYSTEM_ERROR | IS_TRANSIENT_ERROR,
+ ERROR_INTERNAL_SYSTEM_ERROR));
sErrorCodeToFailureInfo.put(ResponseCode.OUT_OF_KEYS_PENDING_INTERNET_CONNECTIVITY,
new PublicErrorInformation(IS_SYSTEM_ERROR | IS_TRANSIENT_ERROR,
ERROR_ATTESTATION_KEYS_UNAVAILABLE));
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index 94c281f..290fefa 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -1394,10 +1394,16 @@
primaryBounds = mProperties.mIsReversedLayout ? boundsBottom : boundsTop;
secondaryBounds = mProperties.mIsReversedLayout ? boundsTop : boundsBottom;
}
- t.setWindowCrop(mPrimaryVeil, primaryBounds.width(), primaryBounds.height());
- t.setWindowCrop(mSecondaryVeil, secondaryBounds.width(), secondaryBounds.height());
- t.setPosition(mPrimaryVeil, primaryBounds.left, primaryBounds.top);
- t.setPosition(mSecondaryVeil, secondaryBounds.left, secondaryBounds.top);
+ if (mPrimaryVeil != null) {
+ t.setWindowCrop(mPrimaryVeil, primaryBounds.width(), primaryBounds.height());
+ t.setPosition(mPrimaryVeil, primaryBounds.left, primaryBounds.top);
+ t.setVisibility(mPrimaryVeil, !primaryBounds.isEmpty());
+ }
+ if (mSecondaryVeil != null) {
+ t.setWindowCrop(mSecondaryVeil, secondaryBounds.width(), secondaryBounds.height());
+ t.setPosition(mSecondaryVeil, secondaryBounds.left, secondaryBounds.top);
+ t.setVisibility(mSecondaryVeil, !secondaryBounds.isEmpty());
+ }
}
private static float[] colorToFloatArray(@NonNull Color color) {
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 15f8c32..112eb61 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -111,3 +111,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "animate_bubble_size_change"
+ namespace: "multitasking"
+ description: "Turns on the animation for bubble bar icons size change"
+ bug: "335575529"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index f27f46c..89cddc3 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -459,6 +459,12 @@
start of this area. -->
<dimen name="desktop_mode_customizable_caption_margin_end">152dp</dimen>
+ <!-- The default minimum allowed window width when resizing a window in desktop mode. -->
+ <dimen name="desktop_mode_minimum_window_width">386dp</dimen>
+
+ <!-- The default minimum allowed window height when resizing a window in desktop mode. -->
+ <dimen name="desktop_mode_minimum_window_height">352dp</dimen>
+
<!-- The width of the maximize menu in desktop mode. -->
<dimen name="desktop_mode_maximize_menu_width">228dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 32873d9..81e7d1f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -602,7 +602,7 @@
List<Bubble> removedBubbles = filterAllBubbles(bubble ->
userId == bubble.getUser().getIdentifier());
for (Bubble b : removedBubbles) {
- doRemove(b.getKey(), Bubbles.DISMISS_USER_REMOVED);
+ doRemove(b.getKey(), Bubbles.DISMISS_USER_ACCOUNT_REMOVED);
}
if (!removedBubbles.isEmpty()) {
dispatchPendingChanges();
@@ -678,7 +678,7 @@
|| reason == Bubbles.DISMISS_SHORTCUT_REMOVED
|| reason == Bubbles.DISMISS_PACKAGE_REMOVED
|| reason == Bubbles.DISMISS_USER_CHANGED
- || reason == Bubbles.DISMISS_USER_REMOVED;
+ || reason == Bubbles.DISMISS_USER_ACCOUNT_REMOVED;
int indexToRemove = indexForKey(key);
if (indexToRemove == -1) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 1d053f9..82af88d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -61,7 +61,7 @@
DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE,
DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT,
DISMISS_OVERFLOW_MAX_REACHED, DISMISS_SHORTCUT_REMOVED, DISMISS_PACKAGE_REMOVED,
- DISMISS_NO_BUBBLE_UP, DISMISS_RELOAD_FROM_DISK, DISMISS_USER_REMOVED,
+ DISMISS_NO_BUBBLE_UP, DISMISS_RELOAD_FROM_DISK, DISMISS_USER_ACCOUNT_REMOVED,
DISMISS_SWITCH_TO_STACK})
@Target({FIELD, LOCAL_VARIABLE, PARAMETER})
@interface DismissReason {
@@ -82,7 +82,7 @@
int DISMISS_PACKAGE_REMOVED = 13;
int DISMISS_NO_BUBBLE_UP = 14;
int DISMISS_RELOAD_FROM_DISK = 15;
- int DISMISS_USER_REMOVED = 16;
+ int DISMISS_USER_ACCOUNT_REMOVED = 16;
int DISMISS_SWITCH_TO_STACK = 17;
/** Returns a binder that can be passed to an external process to manipulate Bubbles. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 2234041..c2242a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -336,6 +336,11 @@
setTouching();
mStartPos = touchPos;
mMoving = false;
+ // This triggers initialization of things like the resize veil in preparation for
+ // showing it when the user moves the divider past the slop, and has to be done
+ // before onStartDragging() which starts the jank interaction tracing
+ mSplitLayout.updateDividerBounds(mSplitLayout.getDividerPosition(),
+ false /* shouldUseParallaxEffect */);
mSplitLayout.onStartDragging();
break;
case MotionEvent.ACTION_MOVE:
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 3dcdc0b..4d597ca 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
@@ -99,10 +99,12 @@
private DragPositioningCallback mDragPositioningCallback;
private DragResizeInputListener mDragResizeListener;
private DragDetector mDragDetector;
-
+ private Runnable mCurrentViewHostRunnable = null;
private RelayoutParams mRelayoutParams = new RelayoutParams();
private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
new WindowDecoration.RelayoutResult<>();
+ private final Runnable mViewHostRunnable =
+ () -> updateViewHost(mRelayoutParams, null /* onDrawTransaction */, mResult);
private final Point mPositionInParent = new Point();
private HandleMenu mHandleMenu;
@@ -194,17 +196,88 @@
// position and crop are set.
final boolean shouldSetTaskPositionAndCrop = !DesktopModeStatus.isVeiledResizeEnabled()
&& mTaskDragResizer.isResizingOrAnimating();
- // Use |applyStartTransactionOnDraw| so that the transaction (that applies task crop) is
- // synced with the buffer transaction (that draws the View). Both will be shown on screen
- // at the same, whereas applying them independently causes flickering. See b/270202228.
- relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */,
- shouldSetTaskPositionAndCrop);
+ // For headers only (i.e. in freeform): use |applyStartTransactionOnDraw| so that the
+ // transaction (that applies task crop) is synced with the buffer transaction (that draws
+ // the View). Both will be shown on screen at the same, whereas applying them independently
+ // causes flickering. See b/270202228.
+ final boolean applyTransactionOnDraw =
+ taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+ relayout(taskInfo, t, t, applyTransactionOnDraw, shouldSetTaskPositionAndCrop);
+ if (!applyTransactionOnDraw) {
+ t.apply();
+ }
}
void relayout(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
Trace.beginSection("DesktopModeWindowDecoration#relayout");
+ if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ // The Task is in Freeform mode -> show its header in sync since it's an integral part
+ // of the window itself - a delayed header might cause bad UX.
+ relayoutInSync(taskInfo, startT, finishT, applyStartTransactionOnDraw,
+ shouldSetTaskPositionAndCrop);
+ } else {
+ // The Task is outside Freeform mode -> allow the handle view to be delayed since the
+ // handle is just a small addition to the window.
+ relayoutWithDelayedViewHost(taskInfo, startT, finishT, applyStartTransactionOnDraw,
+ shouldSetTaskPositionAndCrop);
+ }
+ Trace.endSection();
+ }
+
+ /** Run the whole relayout phase immediately without delay. */
+ private void relayoutInSync(ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
+ boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
+ // Clear the current ViewHost runnable as we will update the ViewHost here
+ clearCurrentViewHostRunnable();
+ updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT, applyStartTransactionOnDraw,
+ shouldSetTaskPositionAndCrop);
+ if (mResult.mRootView != null) {
+ updateViewHost(mRelayoutParams, startT, mResult);
+ }
+ }
+
+ /**
+ * Clear the current ViewHost runnable - to ensure it doesn't run once relayout params have been
+ * updated.
+ */
+ private void clearCurrentViewHostRunnable() {
+ if (mCurrentViewHostRunnable != null) {
+ mHandler.removeCallbacks(mCurrentViewHostRunnable);
+ mCurrentViewHostRunnable = null;
+ }
+ }
+
+ /**
+ * Relayout the window decoration but repost some of the work, to unblock the current callstack.
+ */
+ private void relayoutWithDelayedViewHost(ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
+ boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
+ if (applyStartTransactionOnDraw) {
+ throw new IllegalArgumentException(
+ "We cannot both sync viewhost ondraw and delay viewhost creation.");
+ }
+ // Clear the current ViewHost runnable as we will update the ViewHost here
+ clearCurrentViewHostRunnable();
+ updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT,
+ false /* applyStartTransactionOnDraw */, shouldSetTaskPositionAndCrop);
+ if (mResult.mRootView == null) {
+ // This means something blocks the window decor from showing, e.g. the task is hidden.
+ // Nothing is set up in this case including the decoration surface.
+ return;
+ }
+ // Store the current runnable so it can be removed if we start a new relayout.
+ mCurrentViewHostRunnable = mViewHostRunnable;
+ mHandler.post(mCurrentViewHostRunnable);
+ }
+
+ private void updateRelayoutParamsAndSurfaces(ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
+ boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
+ Trace.beginSection("DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces");
if (isHandleMenuActive()) {
mHandleMenu.relayout(startT);
}
@@ -216,8 +289,8 @@
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
- Trace.beginSection("DesktopModeWindowDecoration#relayout-inner");
- relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
+ Trace.beginSection("DesktopModeWindowDecoration#relayout-updateViewsAndSurfaces");
+ updateViewsAndSurfaces(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
Trace.endSection();
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
@@ -228,7 +301,7 @@
if (mResult.mRootView == null) {
// This means something blocks the window decor from showing, e.g. the task is hidden.
// Nothing is set up in this case including the decoration surface.
- Trace.endSection(); // DesktopModeWindowDecoration#relayout
+ Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces
return;
}
@@ -246,7 +319,7 @@
updateDragResizeListener(oldDecorationSurface);
updateMaximizeMenu(startT);
- Trace.endSection(); // DesktopModeWindowDecoration#relayout
+ Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces
}
private void updateDragResizeListener(SurfaceControl oldDecorationSurface) {
@@ -851,6 +924,7 @@
closeHandleMenu();
mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
disposeResizeVeil();
+ clearCurrentViewHostRunnable();
super.close();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index 82c399a..fe1c9c3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -22,12 +22,16 @@
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED;
+import android.content.Context;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.SurfaceControl;
+import com.android.window.flags.Flags;
+import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.shared.DesktopModeStatus;
/**
* Utility class that contains logic common to classes implementing {@link DragPositioningCallback}
@@ -35,11 +39,11 @@
* and applying that change to the task bounds when applicable.
*/
public class DragPositioningCallbackUtility {
-
/**
* Determine the delta between input's current point and the input start point.
- * @param inputX current input x coordinate
- * @param inputY current input y coordinate
+ *
+ * @param inputX current input x coordinate
+ * @param inputY current input y coordinate
* @param repositionStartPoint initial input coordinate
* @return delta between these two points
*/
@@ -52,13 +56,14 @@
/**
* Based on type of resize and delta provided, calculate the new bounds to display for this
* task.
- * @param ctrlType type of drag being performed
- * @param repositionTaskBounds the bounds the task is being repositioned to
+ *
+ * @param ctrlType type of drag being performed
+ * @param repositionTaskBounds the bounds the task is being repositioned to
* @param taskBoundsAtDragStart the bounds of the task on the first drag input event
- * @param stableBounds bounds that represent the resize limit of this task
- * @param delta difference between start input and current input in x/y coordinates
- * @param displayController task's display controller
- * @param windowDecoration window decoration of the task being dragged
+ * @param stableBounds bounds that represent the resize limit of this task
+ * @param delta difference between start input and current input in x/y
+ * coordinates
+ * @param windowDecoration window decoration of the task being dragged
* @return whether this method changed repositionTaskBounds
*/
static boolean changeBounds(int ctrlType, Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
@@ -142,8 +147,9 @@
/**
* If task bounds are outside of provided drag area, snap the bounds to be just inside the
* drag area.
+ *
* @param repositionTaskBounds bounds determined by task positioner
- * @param validDragArea the area that task must be positioned inside
+ * @param validDragArea the area that task must be positioned inside
* @return whether bounds were modified
*/
public static boolean snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) {
@@ -170,18 +176,38 @@
private static float getMinWidth(DisplayController displayController,
WindowDecoration windowDecoration) {
- return windowDecoration.mTaskInfo.minWidth < 0 ? getDefaultMinSize(displayController,
+ return windowDecoration.mTaskInfo.minWidth < 0 ? getDefaultMinWidth(displayController,
windowDecoration)
: windowDecoration.mTaskInfo.minWidth;
}
private static float getMinHeight(DisplayController displayController,
WindowDecoration windowDecoration) {
- return windowDecoration.mTaskInfo.minHeight < 0 ? getDefaultMinSize(displayController,
+ return windowDecoration.mTaskInfo.minHeight < 0 ? getDefaultMinHeight(displayController,
windowDecoration)
: windowDecoration.mTaskInfo.minHeight;
}
+ private static float getDefaultMinWidth(DisplayController displayController,
+ WindowDecoration windowDecoration) {
+ if (isSizeConstraintForDesktopModeEnabled(windowDecoration.mDecorWindowContext)) {
+ return WindowDecoration.loadDimensionPixelSize(
+ windowDecoration.mDecorWindowContext.getResources(),
+ R.dimen.desktop_mode_minimum_window_width);
+ }
+ return getDefaultMinSize(displayController, windowDecoration);
+ }
+
+ private static float getDefaultMinHeight(DisplayController displayController,
+ WindowDecoration windowDecoration) {
+ if (isSizeConstraintForDesktopModeEnabled(windowDecoration.mDecorWindowContext)) {
+ return WindowDecoration.loadDimensionPixelSize(
+ windowDecoration.mDecorWindowContext.getResources(),
+ R.dimen.desktop_mode_minimum_window_height);
+ }
+ return getDefaultMinSize(displayController, windowDecoration);
+ }
+
private static float getDefaultMinSize(DisplayController displayController,
WindowDecoration windowDecoration) {
float density = displayController.getDisplayLayout(windowDecoration.mTaskInfo.displayId)
@@ -189,9 +215,15 @@
return windowDecoration.mTaskInfo.defaultMinSize * density;
}
+ private static boolean isSizeConstraintForDesktopModeEnabled(Context context) {
+ return DesktopModeStatus.canEnterDesktopMode(context)
+ && Flags.enableDesktopWindowingSizeConstraints();
+ }
+
interface DragStartListener {
/**
* Inform the implementing class that a drag resize has started
+ *
* @param taskId id of this positioner's {@link WindowDecoration}
*/
void onDragStart(int taskId);
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 b9532dd..216990c 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
@@ -199,8 +199,16 @@
void relayout(RelayoutParams params, SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView,
RelayoutResult<T> outResult) {
- outResult.reset();
+ updateViewsAndSurfaces(params, startT, finishT, wct, rootView, outResult);
+ if (outResult.mRootView != null) {
+ updateViewHost(params, startT, outResult);
+ }
+ }
+ protected void updateViewsAndSurfaces(RelayoutParams params,
+ SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
+ WindowContainerTransaction wct, T rootView, RelayoutResult<T> outResult) {
+ outResult.reset();
if (params.mRunningTaskInfo != null) {
mTaskInfo = params.mRunningTaskInfo;
}
@@ -236,7 +244,6 @@
updateCaptionContainerSurface(startT, outResult);
updateCaptionInsets(params, wct, outResult, taskBounds);
updateTaskSurface(params, startT, finishT, outResult);
- updateViewHost(params, startT, outResult);
}
private void inflateIfNeeded(RelayoutParams params, WindowContainerTransaction wct,
@@ -410,8 +417,17 @@
}
}
- private void updateViewHost(RelayoutParams params, SurfaceControl.Transaction onDrawTransaction,
- RelayoutResult<T> outResult) {
+ /**
+ * Updates a {@link SurfaceControlViewHost} to connect the window decoration surfaces with our
+ * View hierarchy.
+ *
+ * @param params parameters to use from the last relayout
+ * @param onDrawTransaction a transaction to apply in sync with #onDraw
+ * @param outResult results to use from the last relayout
+ *
+ */
+ protected void updateViewHost(RelayoutParams params,
+ SurfaceControl.Transaction onDrawTransaction, RelayoutResult<T> outResult) {
Trace.beginSection("CaptionViewHostLayout");
if (mCaptionWindowManager == null) {
// Put caption under a container surface because ViewRootImpl sets the destination frame
@@ -433,6 +449,9 @@
mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay,
mCaptionWindowManager);
if (params.mApplyStartTransactionOnDraw) {
+ if (onDrawTransaction == null) {
+ throw new IllegalArgumentException("Trying to sync a null Transaction");
+ }
mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction);
}
mViewHost.setView(outResult.mRootView, lp);
@@ -440,6 +459,9 @@
} else {
Trace.beginSection("CaptionViewHostLayout-relayout");
if (params.mApplyStartTransactionOnDraw) {
+ if (onDrawTransaction == null) {
+ throw new IllegalArgumentException("Trying to sync a null Transaction");
+ }
mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction);
}
mViewHost.relayout(lp);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index f55c96c..93e4051 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -1170,9 +1170,9 @@
// Verify the update has the removals.
BubbleData.Update update = mUpdateCaptor.getValue();
assertThat(update.removedBubbles.get(0)).isEqualTo(
- Pair.create(mBubbleA2, Bubbles.DISMISS_USER_REMOVED));
+ Pair.create(mBubbleA2, Bubbles.DISMISS_USER_ACCOUNT_REMOVED));
assertThat(update.removedBubbles.get(1)).isEqualTo(
- Pair.create(mBubbleA1, Bubbles.DISMISS_USER_REMOVED));
+ Pair.create(mBubbleA1, Bubbles.DISMISS_USER_ACCOUNT_REMOVED));
// Verify no A bubbles in active or overflow.
assertBubbleListContains(mBubbleC1, mBubbleB3);
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 1b223cf..46c1589 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
@@ -23,12 +23,15 @@
import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlTransaction;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -37,7 +40,7 @@
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
-import android.content.res.Configuration;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Handler;
@@ -47,13 +50,19 @@
import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
+import android.view.AttachedSurfaceControl;
import android.view.Choreographer;
import android.view.Display;
+import android.view.GestureDetector;
+import android.view.InsetsState;
+import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
+import android.view.View;
import android.view.WindowManager;
import android.window.WindowContainerTransaction;
+import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
@@ -74,6 +83,7 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.quality.Strictness;
@@ -112,18 +122,25 @@
@Mock
private Supplier<SurfaceControl.Transaction> mMockTransactionSupplier;
@Mock
- private SurfaceControl.Transaction mMockTransaction;
- @Mock
private SurfaceControl mMockSurfaceControl;
@Mock
private SurfaceControlViewHost mMockSurfaceControlViewHost;
@Mock
+ private AttachedSurfaceControl mMockRootSurfaceControl;
+ @Mock
private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory;
@Mock
private TypedArray mMockRoundedCornersRadiusArray;
- private final Configuration mConfiguration = new Configuration();
+ @Mock
+ private TestTouchEventListener mMockTouchEventListener;
+ @Mock
+ private DesktopModeWindowDecoration.ExclusionRegionListener mMockExclusionRegionListener;
+ @Mock
+ private PackageManager mMockPackageManager;
+ private final InsetsState mInsetsState = new InsetsState();
+ private SurfaceControl.Transaction mMockTransaction;
private StaticMockitoSession mMockitoSession;
private TestableContext mTestableContext;
@@ -145,9 +162,17 @@
when(DesktopModeStatus.useDesktopOverrideDensity()).thenReturn(false);
doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory).create(
any(), any(), any());
+ when(mMockSurfaceControlViewHost.getRootSurfaceControl())
+ .thenReturn(mMockRootSurfaceControl);
+ mMockTransaction = createMockSurfaceControlTransaction();
doReturn(mMockTransaction).when(mMockTransactionSupplier).get();
mTestableContext = new TestableContext(mContext);
mTestableContext.ensureTestableResources();
+ mContext.setMockPackageManager(mMockPackageManager);
+ when(mMockPackageManager.getApplicationLabel(any())).thenReturn("applicationLabel");
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY);
+ doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
}
@After
@@ -341,6 +366,99 @@
assertThat(hasNoInputChannelFeature(relayoutParams)).isTrue();
}
+ @Test
+ public void relayout_fullscreenTask_appliesTransactionImmediately() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ spyWindowDecor.relayout(taskInfo);
+
+ verify(mMockTransaction).apply();
+ verify(mMockRootSurfaceControl, never()).applyTransactionOnDraw(any());
+ }
+
+ @Test
+ public void relayout_freeformTask_appliesTransactionOnDraw() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ // Make non-resizable to avoid dealing with input-permissions (MONITOR_INPUT)
+ taskInfo.isResizeable = false;
+
+ spyWindowDecor.relayout(taskInfo);
+
+ verify(mMockTransaction, never()).apply();
+ verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockTransaction);
+ }
+
+ @Test
+ public void relayout_fullscreenTask_doesNotCreateViewHostImmediately() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ spyWindowDecor.relayout(taskInfo);
+
+ verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any());
+ }
+
+ @Test
+ public void relayout_fullscreenTask_postsViewHostCreation() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
+ spyWindowDecor.relayout(taskInfo);
+
+ verify(mMockHandler).post(runnableArgument.capture());
+ runnableArgument.getValue().run();
+ verify(mMockSurfaceControlViewHostFactory).create(any(), any(), any());
+ }
+
+ @Test
+ public void relayout_freeformTask_createsViewHostImmediately() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ // Make non-resizable to avoid dealing with input-permissions (MONITOR_INPUT)
+ taskInfo.isResizeable = false;
+
+ spyWindowDecor.relayout(taskInfo);
+
+ verify(mMockSurfaceControlViewHostFactory).create(any(), any(), any());
+ verify(mMockHandler, never()).post(any());
+ }
+
+ @Test
+ public void relayout_removesExistingHandlerCallback() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
+ spyWindowDecor.relayout(taskInfo);
+ verify(mMockHandler).post(runnableArgument.capture());
+
+ spyWindowDecor.relayout(taskInfo);
+
+ verify(mMockHandler).removeCallbacks(runnableArgument.getValue());
+ }
+
+ @Test
+ public void close_removesExistingHandlerCallback() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
+ spyWindowDecor.relayout(taskInfo);
+ verify(mMockHandler).post(runnableArgument.capture());
+
+ spyWindowDecor.close();
+
+ verify(mMockHandler).removeCallbacks(runnableArgument.getValue());
+ }
+
private void fillRoundedCornersResources(int fillValue) {
when(mMockRoundedCornersRadiusArray.getDimensionPixelSize(anyInt(), anyInt()))
.thenReturn(fillValue);
@@ -361,12 +479,16 @@
private DesktopModeWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo) {
- return new DesktopModeWindowDecoration(mContext, mMockDisplayController,
- mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl,
+ DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext,
+ mMockDisplayController, mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl,
mMockHandler, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
SurfaceControl.Builder::new, mMockTransactionSupplier,
WindowContainerTransaction::new, SurfaceControl::new,
mMockSurfaceControlViewHostFactory);
+ windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener,
+ mMockTouchEventListener, mMockTouchEventListener);
+ windowDecor.setExclusionRegionListener(mMockExclusionRegionListener);
+ return windowDecor;
}
private ActivityManager.RunningTaskInfo createTaskInfo(boolean visible) {
@@ -391,4 +513,32 @@
return (params.mInputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL)
!= 0;
}
+
+ private static class TestTouchEventListener extends GestureDetector.SimpleOnGestureListener
+ implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
+ View.OnGenericMotionListener, DragDetector.MotionEventHandler {
+
+ @Override
+ public void onClick(View v) {}
+
+ @Override
+ public boolean onGenericMotion(View v, MotionEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ return false;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean handleMotionEvent(@Nullable View v, MotionEvent ev) {
+ return false;
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index e6fabcf..f750e6b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -16,14 +16,20 @@
package com.android.wm.shell.windowdecor
import android.app.ActivityManager
+import android.content.Context
+import android.content.res.Resources
import android.graphics.PointF
import android.graphics.Rect
import android.os.IBinder
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.view.Display
import android.window.WindowContainerToken
+import com.android.window.flags.Flags
+import com.android.wm.shell.R
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.shared.DesktopModeStatus
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
@@ -33,8 +39,8 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.`when` as whenever
import org.mockito.Mockito.any
+import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
/**
@@ -57,6 +63,10 @@
private lateinit var mockDisplayLayout: DisplayLayout
@Mock
private lateinit var mockDisplay: Display
+ @Mock
+ private lateinit var mockContext: Context
+ @Mock
+ private lateinit var mockResources: Resources
@Before
fun setup() {
@@ -69,16 +79,15 @@
(i.arguments.first() as Rect).set(STABLE_BOUNDS)
}
- mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
- taskId = TASK_ID
- token = taskToken
- minWidth = MIN_WIDTH
- minHeight = MIN_HEIGHT
- defaultMinSize = DEFAULT_MIN
- displayId = DISPLAY_ID
- configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
- }
+ initializeTaskInfo()
mockWindowDecoration.mDisplay = mockDisplay
+ mockWindowDecoration.mDecorWindowContext = mockContext
+ whenever(mockContext.getResources()).thenReturn(mockResources)
+ whenever(mockWindowDecoration.mDecorWindowContext.resources).thenReturn(mockResources)
+ whenever(mockResources.getDimensionPixelSize(R.dimen.desktop_mode_minimum_window_width))
+ .thenReturn(DESKTOP_MODE_MIN_WIDTH)
+ whenever(mockResources.getDimensionPixelSize(R.dimen.desktop_mode_minimum_window_height))
+ .thenReturn(DESKTOP_MODE_MIN_HEIGHT)
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
}
@@ -93,8 +102,8 @@
val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
- repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
- mockDisplayController, mockWindowDecoration)
+ repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+ mockWindowDecoration)
assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
@@ -113,8 +122,8 @@
val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
- repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
- mockDisplayController, mockWindowDecoration)
+ repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+ mockWindowDecoration)
assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top + 5)
@@ -127,14 +136,14 @@
val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
val repositionTaskBounds = Rect(STARTING_BOUNDS)
- // Resize to width of 95px and width of -5px with minimum of 10px
+ // Resize to width of 95px and height of -5px with minimum of 10px
val newX = STARTING_BOUNDS.right.toFloat() - 5
val newY = STARTING_BOUNDS.top.toFloat() + 105
val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
- repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
- mockDisplayController, mockWindowDecoration)
+ repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+ mockWindowDecoration)
assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
@@ -153,8 +162,8 @@
val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
- repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
- mockDisplayController, mockWindowDecoration)
+ repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+ mockWindowDecoration)
assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top + 80)
assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right - 80)
@@ -172,8 +181,8 @@
val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
- repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
- mockDisplayController, mockWindowDecoration)
+ repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+ mockWindowDecoration)
assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right)
@@ -196,14 +205,13 @@
assertThat(repositionTaskBounds.left).isEqualTo(validDragArea.left)
assertThat(repositionTaskBounds.top).isEqualTo(validDragArea.bottom)
assertThat(repositionTaskBounds.right)
- .isEqualTo(validDragArea.left + STARTING_BOUNDS.width())
+ .isEqualTo(validDragArea.left + STARTING_BOUNDS.width())
assertThat(repositionTaskBounds.bottom)
- .isEqualTo(validDragArea.bottom + STARTING_BOUNDS.height())
+ .isEqualTo(validDragArea.bottom + STARTING_BOUNDS.height())
}
@Test
fun testChangeBounds_toDisallowedBounds_freezesAtLimit() {
- var hasMoved = false
val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(),
STARTING_BOUNDS.bottom.toFloat())
val repositionTaskBounds = Rect(STARTING_BOUNDS)
@@ -212,26 +220,127 @@
var newY = STARTING_BOUNDS.bottom.toFloat() + 10
var delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
assertTrue(DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
- repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
- mockDisplayController, mockWindowDecoration))
- hasMoved = true
+ repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+ mockWindowDecoration))
// Resize width to 120px, height to disallowed area which should not result in a change.
newX += 10
newY = DISALLOWED_RESIZE_AREA.top.toFloat()
delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
assertTrue(DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
- repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
- mockDisplayController, mockWindowDecoration))
+ repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+ mockWindowDecoration))
assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right + 20)
assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom + 10)
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS)
+ fun taskMinWidthHeightUndefined_changeBoundsInDesktopModeLessThanMin_shouldNotChangeBounds() {
+ whenever(DesktopModeStatus.canEnterDesktopMode(mockContext)).thenReturn(true)
+ initializeTaskInfo(taskMinWidth = -1, taskMinHeight = -1)
+ val startingPoint =
+ PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat())
+ val repositionTaskBounds = Rect(STARTING_BOUNDS)
+ // Shrink height and width to 1px. The default allowed width and height are defined in
+ // R.dimen.desktop_mode_minimum_window_width and R.dimen.desktop_mode_minimum_window_height
+ val newX = STARTING_BOUNDS.right.toFloat() - 99
+ val newY = STARTING_BOUNDS.bottom.toFloat() - 99
+ val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+ DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+ repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+ mockWindowDecoration)
+ assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+ assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+ assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right)
+ assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS)
+ fun taskMinWidthHeightUndefined_changeBoundsInDesktopModeAllowedSize_shouldChangeBounds() {
+ whenever(DesktopModeStatus.canEnterDesktopMode(mockContext)).thenReturn(true)
+ initializeTaskInfo(taskMinWidth = -1, taskMinHeight = -1)
+ val startingPoint =
+ PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat())
+ val repositionTaskBounds = Rect(STARTING_BOUNDS)
+ // Shrink height and width to 20px. The default allowed width and height are defined in
+ // R.dimen.desktop_mode_minimum_window_width and R.dimen.desktop_mode_minimum_window_height
+ val newX = STARTING_BOUNDS.right.toFloat() - 80
+ val newY = STARTING_BOUNDS.bottom.toFloat() - 80
+ val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+ DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+ repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+ mockWindowDecoration)
+ assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+ assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+ assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right - 80)
+ assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom - 80)
+ }
+
+ @Test
+ fun taskMinWidthHeightUndefined_changeBoundsLessThanDefaultMinSize_shouldNotChangeBounds() {
+ initializeTaskInfo(taskMinWidth = -1, taskMinHeight = -1)
+ val startingPoint =
+ PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat())
+ val repositionTaskBounds = Rect(STARTING_BOUNDS)
+ // Shrink height and width to 1px. The default allowed width and height are defined in the
+ // defaultMinSize of the TaskInfo.
+ val newX = STARTING_BOUNDS.right.toFloat() - 99
+ val newY = STARTING_BOUNDS.bottom.toFloat() - 99
+ val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+ DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+ repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+ mockWindowDecoration)
+ assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+ assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+ assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right)
+ assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom)
+ }
+
+ @Test
+ fun taskMinWidthHeightUndefined_changeBoundsToAnAllowedSize_shouldChangeBounds() {
+ initializeTaskInfo(taskMinWidth = -1, taskMinHeight = -1)
+ val startingPoint =
+ PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat())
+ val repositionTaskBounds = Rect(STARTING_BOUNDS)
+ // Shrink height and width to 50px. The default allowed width and height are defined in the
+ // defaultMinSize of the TaskInfo.
+ val newX = STARTING_BOUNDS.right.toFloat() - 50
+ val newY = STARTING_BOUNDS.bottom.toFloat() - 50
+ val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+ DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+ repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+ mockWindowDecoration)
+ assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+ assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+ assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right - 50)
+ assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom - 50)
+ }
+
+ private fun initializeTaskInfo(taskMinWidth: Int = MIN_WIDTH, taskMinHeight: Int = MIN_HEIGHT) {
+ mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
+ taskId = TASK_ID
+ token = taskToken
+ minWidth = taskMinWidth
+ minHeight = taskMinHeight
+ defaultMinSize = DEFAULT_MIN
+ displayId = DISPLAY_ID
+ configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
+ }
+ }
+
companion object {
private const val TASK_ID = 5
private const val MIN_WIDTH = 10
private const val MIN_HEIGHT = 10
+ private const val DESKTOP_MODE_MIN_WIDTH = 20
+ private const val DESKTOP_MODE_MIN_HEIGHT = 20
private const val DENSITY_DPI = 20
private const val DEFAULT_MIN = 40
private const val DISPLAY_ID = 1
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index 9174556..6667504 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -2,6 +2,8 @@
import android.app.ActivityManager
import android.app.WindowConfiguration
+import android.content.Context
+import android.content.res.Resources
import android.graphics.Point
import android.graphics.Rect
import android.os.IBinder
@@ -17,6 +19,7 @@
import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING
import androidx.test.filters.SmallTest
+import com.android.wm.shell.R
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
@@ -83,7 +86,10 @@
private lateinit var mockTransaction: SurfaceControl.Transaction
@Mock
private lateinit var mockTransitionBinder: IBinder
-
+ @Mock
+ private lateinit var mockContext: Context
+ @Mock
+ private lateinit var mockResources: Resources
private lateinit var taskPositioner: FluidResizeTaskPositioner
@Before
@@ -119,6 +125,12 @@
}
`when`(mockWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
mockWindowDecoration.mDisplay = mockDisplay
+ mockWindowDecoration.mDecorWindowContext = mockContext
+ whenever(mockWindowDecoration.mDecorWindowContext.resources).thenReturn(mockResources)
+ whenever(mockResources.getDimensionPixelSize(R.dimen.desktop_mode_minimum_window_width))
+ .thenReturn(DESKTOP_MODE_MIN_WIDTH)
+ whenever(mockResources.getDimensionPixelSize(R.dimen.desktop_mode_minimum_window_height))
+ .thenReturn(DESKTOP_MODE_MIN_HEIGHT)
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
whenever(mockTransitions.startTransition(anyInt(), any(), any()))
.doReturn(mockTransitionBinder)
@@ -788,6 +800,8 @@
private const val TASK_ID = 5
private const val MIN_WIDTH = 10
private const val MIN_HEIGHT = 10
+ private const val DESKTOP_MODE_MIN_WIDTH = 20
+ private const val DESKTOP_MODE_MIN_HEIGHT = 20
private const val DENSITY_DPI = 20
private const val DEFAULT_MIN = 40
private const val DISPLAY_ID = 1
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 e73069a..f3603e1 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
@@ -32,6 +32,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
@@ -828,6 +829,36 @@
eq(mMockTaskSurface), anyInt(), anyInt());
}
+ @Test
+ public void updateViewHost_applyTransactionOnDrawIsTrue_surfaceControlIsUpdated() {
+ final TestWindowDecoration windowDecor = createWindowDecoration(
+ new TestRunningTaskInfoBuilder().build());
+ mRelayoutParams.mApplyStartTransactionOnDraw = true;
+
+ windowDecor.updateViewHost(mRelayoutParams, mMockSurfaceControlStartT, mRelayoutResult);
+
+ verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT);
+ }
+
+ @Test
+ public void updateViewHost_nullDrawTransaction_applyTransactionOnDrawIsTrue_throwsException() {
+ final TestWindowDecoration windowDecor = createWindowDecoration(
+ new TestRunningTaskInfoBuilder().build());
+ mRelayoutParams.mApplyStartTransactionOnDraw = true;
+
+ assertThrows(IllegalArgumentException.class,
+ () -> windowDecor.updateViewHost(
+ mRelayoutParams, null /* onDrawTransaction */, mRelayoutResult));
+ }
+
+ @Test
+ public void updateViewHost_nullDrawTransaction_applyTransactionOnDrawIsFalse_doesNotThrow() {
+ final TestWindowDecoration windowDecor = createWindowDecoration(
+ new TestRunningTaskInfoBuilder().build());
+ mRelayoutParams.mApplyStartTransactionOnDraw = false;
+
+ windowDecor.updateViewHost(mRelayoutParams, null /* onDrawTransaction */, mRelayoutResult);
+ }
private TestWindowDecoration createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo) {
return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
diff --git a/packages/CrashRecovery/aconfig/flags.aconfig b/packages/CrashRecovery/aconfig/flags.aconfig
index 8cdef38..80412321 100644
--- a/packages/CrashRecovery/aconfig/flags.aconfig
+++ b/packages/CrashRecovery/aconfig/flags.aconfig
@@ -12,15 +12,22 @@
flag {
name: "enable_crashrecovery"
is_exported: true
- namespace: "crashrecovery"
+ namespace: "modularization"
description: "Enables various dependencies of crashrecovery module"
bug: "289203818"
}
flag {
name: "allow_rescue_party_flag_resets"
- namespace: "crashrecovery"
+ namespace: "modularization"
description: "Enables rescue party flag resets"
bug: "287618292"
is_fixed_read_only: true
}
+
+flag {
+ name: "reenable_settings_resets"
+ namespace: "modularization"
+ description: "Re-enables settings resets only, deletes flag resets"
+ bug: "333847376"
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
index f65a1b7..c48e7e4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
@@ -71,7 +71,7 @@
},
scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = .32f),
shape = EntryShape.TopRoundedCorner,
- windowInsets = WindowInsets.navigationBars,
+ contentWindowInsets = { WindowInsets.navigationBars },
dragHandle = null,
// Never take over the full screen. We always want to leave some top scrim space
// for exiting and viewing the underlying app to help a user gain context.
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 652e62c..5728c8c 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -18,6 +18,7 @@
import android.content.Intent
import android.os.Bundle
+import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
@@ -33,6 +34,7 @@
@OptIn(ExperimentalHorologistApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
+ Log.d(TAG, "onCreate, intent: $intent")
super.onCreate(savedInstanceState)
setTheme(android.R.style.Theme_DeviceDefault)
setContent {
@@ -47,6 +49,7 @@
}
override fun onNewIntent(intent: Intent) {
+ Log.d(TAG, "onNewIntent, intent: $intent")
super.onNewIntent(intent)
setIntent(intent)
viewModel.updateRequest(intent)
diff --git a/packages/InputDevices/res/raw/keyboard_layout_serbian_and_montenegrin_cyrillic.kcm b/packages/InputDevices/res/raw/keyboard_layout_serbian_and_montenegrin_cyrillic.kcm
new file mode 100644
index 0000000..6fa54f9
--- /dev/null
+++ b/packages/InputDevices/res/raw/keyboard_layout_serbian_and_montenegrin_cyrillic.kcm
@@ -0,0 +1,320 @@
+# Copyright 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.
+
+#
+# Serbian and Montenegrin (Cyrillic) keyboard layout.
+#
+
+type OVERLAY
+
+map key 86 PLUS
+
+### ROW 1
+
+key GRAVE {
+ label: '`'
+ base: '`'
+ shift: '~'
+}
+
+key 1 {
+ label: '1'
+ base: '1'
+ shift: '!'
+}
+
+key 2 {
+ label: '2'
+ base: '2'
+ shift: '\u0022'
+}
+
+key 3 {
+ label: '3'
+ base: '3'
+ shift: '#'
+}
+
+key 4 {
+ label: '4'
+ base: '4'
+ shift: '$'
+}
+
+key 5 {
+ label: '5'
+ base: '5'
+ shift: '%'
+}
+
+key 6 {
+ label: '6'
+ base: '6'
+ shift: '&'
+}
+
+key 7 {
+ label: '7'
+ base: '7'
+ shift: '/'
+}
+
+key 8 {
+ label: '8'
+ base: '8'
+ shift: '('
+}
+
+key 9 {
+ label: '9'
+ base: '9'
+ shift: ')'
+}
+
+key 0 {
+ label: '0'
+ base: '0'
+ shift: '='
+}
+
+key MINUS {
+ label: '\''
+ base: '\u030d'
+ shift: '?'
+}
+
+key EQUALS {
+ label: '+'
+ base: '+'
+ shift: '*'
+}
+
+### ROW 2
+
+key Q {
+ label: '\u0409'
+ base, capslock+shift: '\u0459'
+ shift, capslock: '\u0409'
+}
+
+key W {
+ label: '\u040a'
+ base, capslock+shift: '\u045a'
+ shift, capslock: '\u040a'
+}
+
+key E {
+ label: '\u0415'
+ base, capslock+shift: '\u0435'
+ shift, capslock: '\u0415'
+ ralt: '\u20ac'
+}
+
+key R {
+ label: '\u0420'
+ base, capslock+shift: '\u0440'
+ shift, capslock: '\u0420'
+}
+
+key T {
+ label: '\u0422'
+ base, capslock+shift: '\u0442'
+ shift, capslock: '\u0422'
+}
+
+key Y {
+ label: '\u0417'
+ base, capslock+shift: '\u0437'
+ shift, capslock: '\u0417'
+}
+
+key U {
+ label: '\u0423'
+ base, capslock+shift: '\u0443'
+ shift, capslock: '\u0423'
+}
+
+key I {
+ label: '\u0418'
+ base, capslock+shift: '\u0438'
+ shift, capslock: '\u0418'
+}
+
+key O {
+ label: '\u041e'
+ base, capslock+shift: '\u043e'
+ shift, capslock: '\u041e'
+}
+
+key P {
+ label: '\u041f'
+ base, capslock+shift: '\u043f'
+ shift, capslock: '\u041f'
+}
+
+key LEFT_BRACKET {
+ label: '\u0428'
+ base, capslock+shift: '\u0448'
+ shift, capslock: '\u0428'
+}
+
+key RIGHT_BRACKET {
+ label: '\u0402'
+ base, capslock+shift: '\u0452'
+ shift, capslock: '\u0402'
+}
+
+### ROW 3
+
+key A {
+ label: '\u0410'
+ base, capslock+shift: '\u0430'
+ shift, capslock: '\u0410'
+}
+
+key S {
+ label: '\u0421'
+ base, capslock+shift: '\u0441'
+ shift, capslock: '\u0421'
+}
+
+key D {
+ label: '\u0414'
+ base, capslock+shift: '\u0434'
+ shift, capslock: '\u0414'
+}
+
+key F {
+ label: '\u0424'
+ base, capslock+shift: '\u0444'
+ shift, capslock: '\u0424'
+}
+
+key G {
+ label: '\u0413'
+ base, capslock+shift: '\u0433'
+ shift, capslock: '\u0413'
+}
+
+key H {
+ label: '\u0425'
+ base, capslock+shift: '\u0445'
+ shift, capslock: '\u0425'
+}
+
+key J {
+ label: '\u0408'
+ base, capslock+shift: '\u0458'
+ shift, capslock: '\u0408'
+}
+
+key K {
+ label: '\u041a'
+ base, capslock+shift: '\u043a'
+ shift, capslock: '\u041a'
+}
+
+key L {
+ label: '\u041b'
+ base, capslock+shift: '\u043b'
+ shift, capslock: '\u041b'
+}
+
+key SEMICOLON {
+ label: '\u0427'
+ base, capslock+shift: '\u0447'
+ shift, capslock: '\u0427'
+}
+
+key APOSTROPHE {
+ label: '\u040b'
+ base, capslock+shift: '\u045b'
+ shift, capslock: '\u040b'
+}
+
+key BACKSLASH {
+ label: '\u0416'
+ base, capslock+shift: '\u0436'
+ shift, capslock: '\u0416'
+}
+
+### ROW 4
+
+key PLUS {
+ label: '<'
+ base: '<'
+ shift: '>'
+}
+
+key Z {
+ label: '\u0405'
+ base, capslock+shift: '\u0455'
+ shift, capslock: '\u0405'
+}
+
+key X {
+ label: '\u040f'
+ base, capslock+shift: '\u045f'
+ shift, capslock: '\u040f'
+}
+
+key C {
+ label: '\u0426'
+ base, capslock+shift: '\u0446'
+ shift, capslock: '\u0426'
+}
+
+key V {
+ label: '\u0412'
+ base, capslock+shift: '\u0432'
+ shift, capslock: '\u0412'
+}
+
+key B {
+ label: '\u0411'
+ base, capslock+shift: '\u0431'
+ shift, capslock: '\u0411'
+}
+
+key N {
+ label: '\u041d'
+ base, capslock+shift: '\u043d'
+ shift, capslock: '\u041d'
+}
+
+key M {
+ label: '\u041c'
+ base, capslock+shift: '\u043c'
+ shift, capslock: '\u041c'
+}
+
+key COMMA {
+ label: ','
+ base: ','
+ shift: ';'
+ ralt: '<'
+}
+
+key PERIOD {
+ label: '.'
+ base: '.'
+ shift: ':'
+ ralt: '>'
+}
+
+key SLASH {
+ label: '-'
+ base: '-'
+ shift: '_'
+}
\ No newline at end of file
diff --git a/packages/InputDevices/res/raw/keyboard_layout_serbian_latin.kcm b/packages/InputDevices/res/raw/keyboard_layout_serbian_and_montenegrin_latin.kcm
similarity index 99%
rename from packages/InputDevices/res/raw/keyboard_layout_serbian_latin.kcm
rename to packages/InputDevices/res/raw/keyboard_layout_serbian_and_montenegrin_latin.kcm
index 0ff1dea..8e4d7b1 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_serbian_latin.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_serbian_and_montenegrin_latin.kcm
@@ -13,7 +13,7 @@
# limitations under the License.
#
-# Serbian (Latin) keyboard layout.
+# Serbian and Montenegrin (Latin) keyboard layout.
#
type OVERLAY
diff --git a/packages/InputDevices/res/values/strings.xml b/packages/InputDevices/res/values/strings.xml
index be7d2c1..5a91125 100644
--- a/packages/InputDevices/res/values/strings.xml
+++ b/packages/InputDevices/res/values/strings.xml
@@ -158,4 +158,10 @@
<!-- Montenegrin (Latin) keyboard layout label. [CHAR LIMIT=35] -->
<string name="keyboard_layout_montenegrin_latin">Montenegrin (Latin)</string>
+
+ <!-- Serbian (Cyrillic) keyboard layout label. [CHAR LIMIT=35] -->
+ <string name="keyboard_layout_serbian_cyrillic">Serbian (Cyrillic)</string>
+
+ <!-- Montenegrin (Cyrillic) keyboard layout label. [CHAR LIMIT=35] -->
+ <string name="keyboard_layout_montenegrin_cyrillic">Montenegrin (Cyrillic)</string>
</resources>
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index 84e4b9e..9309489 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -336,14 +336,28 @@
<keyboard-layout
android:name="keyboard_layout_serbian_latin"
android:label="@string/keyboard_layout_serbian_latin"
- android:keyboardLayout="@raw/keyboard_layout_serbian_latin"
+ android:keyboardLayout="@raw/keyboard_layout_serbian_and_montenegrin_latin"
android:keyboardLocale="sr-Latn-RS"
android:keyboardLayoutType="qwertz" />
<keyboard-layout
android:name="keyboard_layout_montenegrin_latin"
android:label="@string/keyboard_layout_montenegrin_latin"
- android:keyboardLayout="@raw/keyboard_layout_serbian_latin"
+ android:keyboardLayout="@raw/keyboard_layout_serbian_and_montenegrin_latin"
android:keyboardLocale="cnr-Latn-ME"
android:keyboardLayoutType="qwertz" />
+
+ <keyboard-layout
+ android:name="keyboard_layout_serbian_cyrillic"
+ android:label="@string/keyboard_layout_serbian_cyrillic"
+ android:keyboardLayout="@raw/keyboard_layout_serbian_and_montenegrin_cyrillic"
+ android:keyboardLocale="sr-Cyrl-RS"
+ android:keyboardLayoutType="extended" />
+
+ <keyboard-layout
+ android:name="keyboard_layout_montenegrin_cyrillic"
+ android:label="@string/keyboard_layout_montenegrin_cyrillic"
+ android:keyboardLayout="@raw/keyboard_layout_serbian_and_montenegrin_cyrillic"
+ android:keyboardLocale="cnr-Cyrl-ME"
+ android:keyboardLayoutType="extended" />
</keyboard-layouts>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
index c96644c..03d52d0 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
@@ -28,6 +28,7 @@
import android.os.Bundle;
import android.util.Log;
import android.view.View;
+
import androidx.annotation.Nullable;
/**
@@ -84,22 +85,28 @@
int statusCode = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
boolean returnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
-
- if (returnResult) {
- int legacyStatus = intent.getIntExtra(PackageInstaller.EXTRA_LEGACY_STATUS,
+ int legacyStatus = intent.getIntExtra(PackageInstaller.EXTRA_LEGACY_STATUS,
PackageManager.INSTALL_FAILED_INTERNAL_ERROR);
+ // TODO (b/346655018): Use INSTALL_FAILED_ABORTED legacyCode in the condition
+ // statusCode can be STATUS_FAILURE_ABORTED if:
+ // 1. GPP blocks an install.
+ // 2. User denies ownership update explicitly.
+ // InstallFailed dialog must not be shown only when the user denies ownership update. We
+ // must show this dialog for all other install failures.
+ boolean userDenied = statusCode == PackageInstaller.STATUS_FAILURE_ABORTED
+ && legacyStatus != PackageManager.INSTALL_FAILED_VERIFICATION_TIMEOUT
+ && legacyStatus != PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE;
+
+ if (returnResult) {
// Return result if requested
Intent result = new Intent();
result.putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus);
setResult(Activity.RESULT_FIRST_USER, result);
finish();
- } else if (statusCode != PackageInstaller.STATUS_FAILURE_ABORTED) {
- // statusCode will be STATUS_FAILURE_ABORTED if the update-owner confirmation dialog was
- // dismissed by the user. We don't want to show a InstallFailed dialog in this case.
- // If the user denies install permission for normal installs, this dialog will never be
- // triggered as the status code is returned from PackageInstallerActivity.java
-
+ } else if (userDenied) {
+ finish();
+ } else {
// Set header icon and title
PackageUtil.AppSnippet as = intent.getParcelableExtra(
PackageInstallerActivity.EXTRA_APP_SNIPPET, PackageUtil.AppSnippet.class);
@@ -127,8 +134,6 @@
// Get status messages
setExplanationFromErrorCode(statusCode);
- } else {
- finish();
}
}
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 45667f5..232fa92 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@
allprojects {
extra["androidTop"] = androidTop
- extra["jetpackComposeVersion"] = "1.7.0-alpha08"
+ extra["jetpackComposeVersion"] = "1.7.0-SNAPSHOT"
}
subprojects {
diff --git a/packages/SettingsLib/Spa/settings.gradle.kts b/packages/SettingsLib/Spa/settings.gradle.kts
index e003c81..31f462e 100644
--- a/packages/SettingsLib/Spa/settings.gradle.kts
+++ b/packages/SettingsLib/Spa/settings.gradle.kts
@@ -36,6 +36,9 @@
}
mavenCentral()
maven {
+ url = uri("https://androidx.dev/snapshots/builds/11846308/artifacts/repository")
+ }
+ maven {
url = uri("https://jitpack.io")
content {
includeGroup("com.github.PhilJay")
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 4aa57b3..f98695e 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -57,13 +57,13 @@
api("androidx.slice:slice-builders:1.1.0-alpha02")
api("androidx.slice:slice-core:1.1.0-alpha02")
api("androidx.slice:slice-view:1.1.0-alpha02")
- api("androidx.compose.material3:material3:1.3.0-alpha06")
+ api("androidx.compose.material3:material3:1.3.0-SNAPSHOT")
api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
api("androidx.lifecycle:lifecycle-livedata-ktx")
api("androidx.lifecycle:lifecycle-runtime-compose")
- api("androidx.navigation:navigation-compose:2.8.0-alpha08")
+ api("androidx.navigation:navigation-compose:2.8.0-beta01")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
api("com.google.android.material:material:1.11.0")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
index 1a10bf0..2cf0d3b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
@@ -29,6 +29,7 @@
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextLinkStyles
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
@@ -98,7 +99,9 @@
) {
val url = LinkAnnotation.Url(
url = urlSpan.url,
- style = SpanStyle(color = urlSpanColor, textDecoration = TextDecoration.Underline),
+ styles = TextLinkStyles(
+ style = SpanStyle(color = urlSpanColor, textDecoration = TextDecoration.Underline),
+ ),
)
addLink(url, start, end)
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
index 612f9e5..4c1efd7 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
@@ -21,6 +21,7 @@
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextLinkStyles
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
@@ -47,9 +48,11 @@
AnnotatedString.Range(
item = LinkAnnotation.Url(
url = "https://www.android.com/",
- style = SpanStyle(
- color = MaterialTheme.colorScheme.primary,
- textDecoration = TextDecoration.Underline,
+ styles = TextLinkStyles(
+ style = SpanStyle(
+ color = MaterialTheme.colorScheme.primary,
+ textDecoration = TextDecoration.Underline,
+ ),
),
),
start = 31,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/NonWritableNamespacesForBackgroundUserPrefixes.java b/packages/SettingsProvider/src/com/android/providers/settings/NonWritableNamespacesForBackgroundUserPrefixes.java
new file mode 100644
index 0000000..5c5ca46
--- /dev/null
+++ b/packages/SettingsProvider/src/com/android/providers/settings/NonWritableNamespacesForBackgroundUserPrefixes.java
@@ -0,0 +1,39 @@
+/*
+ * 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.providers.settings;
+
+import android.util.ArraySet;
+
+import java.util.Arrays;
+import java.util.Set;
+
+/**
+ * Contains the list of prefixes for namespaces in which nothing can be written by background
+ * user.
+ *
+ * <p>
+ * The list in enforced is Auto devices only. To add to
+ * the list, create a change and tag the OWNER. In the change description, include a
+ * description of the flag's functionality, and a justification for why it needs to be
+ * denylisted.
+ */
+final class NonWritableNamespacesForBackgroundUserPrefixes {
+ public static final Set<String> DENYLIST =
+ new ArraySet<String>(Arrays.asList(
+ "game_overlay"
+ ));
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 15f8a7b..d54236e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2392,8 +2392,12 @@
== PackageManager.PERMISSION_GRANTED;
boolean isRoot = Binder.getCallingUid() == Process.ROOT_UID;
- if (isRoot || hasWritePermission) {
+ if (isRoot) {
return;
+ }
+
+ if (hasWritePermission) {
+ assertCallingUserDenyList(flags);
} else if (hasAllowlistPermission) {
for (String flag : flags) {
boolean namespaceAllowed = false;
@@ -2410,12 +2414,49 @@
+ "'; allowlist permission granted, but must add flag to the allowlist.");
}
}
+ assertCallingUserDenyList(flags);
} else {
throw new SecurityException("Permission denial to mutate flag, must have root, "
+ "WRITE_DEVICE_CONFIG, or WRITE_ALLOWLISTED_DEVICE_CONFIG");
}
}
+ // The check is added mainly for auto devices. On auto devices, it is possible that
+ // multiple users are visible simultaneously using visible background users.
+ // In such cases, it is desired that Non-current user (ex. visible background users) can
+ // only change settings for certain namespaces.
+ private void assertCallingUserDenyList(@NonNull Set<String> flags) {
+ if (!UserManager.isVisibleBackgroundUsersEnabled()) {
+ // enforce the deny list only on devices supporting visible background user.
+ return;
+ }
+
+ int callingUser = UserHandle.getCallingUserId();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ int currentUser = ActivityManager.getCurrentUser();
+ if (callingUser == currentUser) {
+ // enforce the deny list only if the caller is not current user. Currently only auto
+ // uses background visible user, and auto doesn't support profiles so profiles of
+ // current users is not checked here.
+ return;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+
+ for (String flag : flags) {
+ for (String denylistedPrefix :
+ NonWritableNamespacesForBackgroundUserPrefixes.DENYLIST) {
+ if (flag.startsWith(denylistedPrefix)) {
+ throw new SecurityException("Permission denial for flag '" + flag
+ + "' for background user " + callingUser + ". Namespace is added to "
+ + "denylist.");
+ }
+ }
+ }
+ }
+
private static void warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk(
int targetSdkVersion, String name) {
// If the app targets Lollipop MR1 or older SDK we warn, otherwise crash.
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index c61f996..1cbf67e 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1048,15 +1048,6 @@
bug: "343505271"
}
-flag {
- name: "glanceable_hub_animate_timer_activity_starts"
- namespace: "systemui"
- description: "Properly animates activity starts from live timers on the glanceable hub"
- bug: "345741071"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
flag {
name: "new_touchpad_gestures_tutorial"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index b1258ba..c4659cf 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -55,6 +55,7 @@
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
+import com.android.systemui.communal.ui.compose.Dimensions.SlideOffsetY
import com.android.systemui.communal.ui.compose.extensions.allowGestures
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.util.CommunalColors
@@ -76,6 +77,15 @@
override fun matches(key: ElementKey, scene: SceneKey) = true
}
+private object TransitionDuration {
+ const val BETWEEN_HUB_AND_EDIT_MODE_MS = 1000
+ const val EDIT_MODE_TO_HUB_CONTENT_MS = 167
+ const val EDIT_MODE_TO_HUB_GRID_DELAY_MS = 167
+ const val EDIT_MODE_TO_HUB_GRID_END_MS =
+ EDIT_MODE_TO_HUB_GRID_DELAY_MS + EDIT_MODE_TO_HUB_CONTENT_MS
+ const val HUB_TO_EDIT_MODE_CONTENT_MS = 250
+}
+
val sceneTransitions = transitions {
to(CommunalScenes.Communal, key = CommunalTransitionKeys.SimpleFade) {
spec = tween(durationMillis = 250)
@@ -97,6 +107,30 @@
}
timestampRange(startMillis = 167, endMillis = 334) { fade(Communal.Elements.Scrim) }
}
+ to(CommunalScenes.Blank, key = CommunalTransitionKeys.ToEditMode) {
+ spec = tween(durationMillis = TransitionDuration.BETWEEN_HUB_AND_EDIT_MODE_MS)
+ timestampRange(endMillis = TransitionDuration.HUB_TO_EDIT_MODE_CONTENT_MS) {
+ fade(Communal.Elements.Grid)
+ fade(Communal.Elements.IndicationArea)
+ fade(Communal.Elements.LockIcon)
+ }
+ fade(Communal.Elements.Scrim)
+ }
+ to(CommunalScenes.Communal, key = CommunalTransitionKeys.FromEditMode) {
+ spec = tween(durationMillis = TransitionDuration.BETWEEN_HUB_AND_EDIT_MODE_MS)
+ translate(Communal.Elements.Grid, y = SlideOffsetY)
+ timestampRange(endMillis = TransitionDuration.EDIT_MODE_TO_HUB_CONTENT_MS) {
+ fade(Communal.Elements.IndicationArea)
+ fade(Communal.Elements.LockIcon)
+ fade(Communal.Elements.Scrim)
+ }
+ timestampRange(
+ startMillis = TransitionDuration.EDIT_MODE_TO_HUB_GRID_DELAY_MS,
+ endMillis = TransitionDuration.EDIT_MODE_TO_HUB_GRID_END_MS
+ ) {
+ fade(Communal.Elements.Grid)
+ }
+ }
}
/**
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index b353b5a..18085ab 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -25,9 +25,9 @@
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler
import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
-import com.android.systemui.communal.widgets.WidgetInteractionHandler
import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
import com.android.systemui.keyguard.ui.composable.section.LockSection
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
@@ -38,7 +38,7 @@
@Inject
constructor(
private val viewModel: CommunalViewModel,
- private val interactionHandler: WidgetInteractionHandler,
+ private val interactionHandler: SmartspaceInteractionHandler,
private val dialogFactory: SystemUIDialogFactory,
private val lockSection: LockSection,
private val ambientStatusBarSection: AmbientStatusBarSection,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index f43064a..927890e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -33,6 +33,8 @@
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
@@ -128,11 +130,11 @@
import androidx.compose.ui.window.Popup
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.window.layout.WindowMetricsCalculator
+import com.android.compose.animation.Easings.Emphasized
import com.android.compose.modifiers.thenIf
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
import com.android.internal.R.dimen.system_app_widget_background_radius
-import com.android.systemui.Flags.glanceableHubAnimateTimerActivityStarts
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -176,6 +178,10 @@
derivedStateOf { selectedKey.value != null || reorderingWidgets }
}
val isEmptyState by viewModel.isEmptyState.collectAsStateWithLifecycle(initialValue = false)
+ val isCommunalContentVisible by
+ viewModel.isCommunalContentVisible.collectAsStateWithLifecycle(
+ initialValue = !viewModel.isEditMode
+ )
val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize)
val contentOffset = beforeContentPadding(contentPadding).toOffset()
@@ -248,47 +254,88 @@
viewModel = viewModel,
)
} else {
- CommunalHubLazyGrid(
- communalContent = communalContent,
- viewModel = viewModel,
- contentPadding = contentPadding,
- contentOffset = contentOffset,
- setGridCoordinates = { gridCoordinates = it },
- updateDragPositionForRemove = { offset ->
- isPointerWithinEnabledRemoveButton(
- removeEnabled = removeButtonEnabled,
- offset = gridCoordinates?.let { it.positionInWindow() + offset },
- containerToCheck = removeButtonCoordinates
+ val slideOffsetInPx =
+ with(LocalDensity.current) { Dimensions.SlideOffsetY.toPx().toInt() }
+ AnimatedVisibility(
+ visible = isCommunalContentVisible,
+ enter =
+ fadeIn(
+ animationSpec =
+ tween(durationMillis = 83, delayMillis = 83, easing = LinearEasing)
+ ) +
+ slideInVertically(
+ animationSpec = tween(durationMillis = 1000, easing = Emphasized),
+ initialOffsetY = { -slideOffsetInPx }
+ ),
+ exit =
+ fadeOut(
+ animationSpec = tween(durationMillis = 167, easing = LinearEasing)
+ ) +
+ slideOutVertically(
+ animationSpec = tween(durationMillis = 1000, easing = Emphasized),
+ targetOffsetY = { -slideOffsetInPx }
+ ),
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ Box {
+ CommunalHubLazyGrid(
+ communalContent = communalContent,
+ viewModel = viewModel,
+ contentPadding = contentPadding,
+ contentOffset = contentOffset,
+ setGridCoordinates = { gridCoordinates = it },
+ updateDragPositionForRemove = { offset ->
+ isPointerWithinEnabledRemoveButton(
+ removeEnabled = removeButtonEnabled,
+ offset =
+ gridCoordinates?.let { it.positionInWindow() + offset },
+ containerToCheck = removeButtonCoordinates
+ )
+ },
+ gridState = gridState,
+ contentListState = contentListState,
+ selectedKey = selectedKey,
+ widgetConfigurator = widgetConfigurator,
+ interactionHandler = interactionHandler,
)
- },
- gridState = gridState,
- contentListState = contentListState,
- selectedKey = selectedKey,
- widgetConfigurator = widgetConfigurator,
- interactionHandler = interactionHandler,
- )
+ }
+ }
}
}
- if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
- Toolbar(
- setToolbarSize = { toolbarSize = it },
- setRemoveButtonCoordinates = { removeButtonCoordinates = it },
- onEditDone = onEditDone,
- onOpenWidgetPicker = onOpenWidgetPicker,
- onRemoveClicked = {
- val index =
- selectedKey.value?.let { key ->
- contentListState.list.indexOfFirst { it.key == key }
+ if (onOpenWidgetPicker != null && onEditDone != null) {
+ AnimatedVisibility(
+ visible = viewModel.isEditMode && isCommunalContentVisible,
+ enter =
+ fadeIn(animationSpec = tween(durationMillis = 250, easing = LinearEasing)) +
+ slideInVertically(
+ animationSpec = tween(durationMillis = 1000, easing = Emphasized),
+ ),
+ exit =
+ fadeOut(animationSpec = tween(durationMillis = 167, easing = LinearEasing)) +
+ slideOutVertically(
+ animationSpec = tween(durationMillis = 1000, easing = Emphasized)
+ ),
+ ) {
+ Toolbar(
+ setToolbarSize = { toolbarSize = it },
+ setRemoveButtonCoordinates = { removeButtonCoordinates = it },
+ onEditDone = onEditDone,
+ onOpenWidgetPicker = onOpenWidgetPicker,
+ onRemoveClicked = {
+ val index =
+ selectedKey.value?.let { key ->
+ contentListState.list.indexOfFirst { it.key == key }
+ }
+ index?.let {
+ contentListState.onRemove(it)
+ contentListState.onSaveList()
+ viewModel.setSelectedKey(null)
}
- index?.let {
- contentListState.onRemove(it)
- contentListState.onSaveList()
- viewModel.setSelectedKey(null)
- }
- },
- removeEnabled = removeButtonEnabled
- )
+ },
+ removeEnabled = removeButtonEnabled
+ )
+ }
}
if (currentPopup == PopupType.CtaTile) {
PopupOnDismissCtaTile(viewModel::onHidePopup)
@@ -1161,9 +1208,7 @@
modifier = modifier,
factory = { context ->
SmartspaceAppWidgetHostView(context).apply {
- if (glanceableHubAnimateTimerActivityStarts()) {
- interactionHandler?.let { setInteractionHandler(it) }
- }
+ interactionHandler?.let { setInteractionHandler(it) }
updateAppWidget(model.remoteViews)
}
},
@@ -1331,6 +1376,7 @@
horizontal = ToolbarButtonPaddingHorizontal,
)
val IconSize = 40.dp
+ val SlideOffsetY = 30.dp
}
private object Colors {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index e61b2d0..cf14547 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -111,6 +111,25 @@
}
}
+ @Test
+ fun keyguardGoesAway_whenInEditMode_doesNotChangeScene() =
+ with(kosmos) {
+ testScope.runTest {
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ communalInteractor.setEditModeOpen(true)
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.GONE,
+ testScope = this
+ )
+ // Scene change will be handled in EditWidgetsActivity not here
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ }
+ }
+
@Ignore("Ignored until custom animations are implemented in b/322787129")
@Test
fun deviceDocked_forceCommunalScene() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index e42a67b..3d454a2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -52,6 +52,7 @@
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.EnableSceneContainer
@@ -121,6 +122,7 @@
private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository
private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter
private lateinit var sceneInteractor: SceneInteractor
+ private lateinit var communalSceneInteractor: CommunalSceneInteractor
private lateinit var userTracker: FakeUserTracker
private lateinit var activityStarter: ActivityStarter
private lateinit var userManager: UserManager
@@ -141,6 +143,7 @@
editWidgetsActivityStarter = kosmos.editWidgetsActivityStarter
communalPrefsRepository = kosmos.fakeCommunalPrefsRepository
sceneInteractor = kosmos.sceneInteractor
+ communalSceneInteractor = kosmos.communalSceneInteractor
userTracker = kosmos.fakeUserTracker
activityStarter = kosmos.activityStarter
userManager = kosmos.userManager
@@ -815,7 +818,11 @@
@Test
fun testShowWidgetEditorStartsActivity() =
testScope.runTest {
+ val editModeState by collectLastValue(communalSceneInteractor.editModeState)
+
underTest.showWidgetEditor()
+
+ assertThat(editModeState).isEqualTo(EditModeState.STARTING)
verify(editWidgetsActivityStarter).startActivity()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
index a0e7781..6e48b99 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
@@ -24,6 +24,7 @@
import com.android.systemui.communal.data.repository.communalSceneRepository
import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
@@ -82,6 +83,27 @@
}
@Test
+ fun snapToSceneForActivity() =
+ testScope.runTest {
+ val currentScene by collectLastValue(underTest.currentScene)
+ assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
+
+ underTest.snapToSceneForActivityStart(CommunalScenes.Communal)
+ assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
+ }
+
+ @Test
+ fun snapToSceneForActivity_willNotChangeScene_forEditModeActivity() =
+ testScope.runTest {
+ val currentScene by collectLastValue(underTest.currentScene)
+ assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
+
+ underTest.setEditModeState(EditModeState.STARTING)
+ underTest.snapToSceneForActivityStart(CommunalScenes.Communal)
+ assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
+ }
+
+ @Test
fun transitionProgress_fullProgress() =
testScope.runTest {
val transitionProgress by
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt
new file mode 100644
index 0000000..0cd3fb2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.communal.smartspace
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.RemoteViews.RemoteResponse
+import androidx.core.util.component1
+import androidx.core.util.component2
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
+import com.android.systemui.plugins.ActivityStarter
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.isNull
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.notNull
+import org.mockito.kotlin.refEq
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SmartspaceInteractionHandlerTest : SysuiTestCase() {
+ private val activityStarter = mock<ActivityStarter>()
+
+ private val testIntent =
+ PendingIntent.getActivity(
+ context,
+ /* requestCode = */ 0,
+ Intent("action"),
+ PendingIntent.FLAG_IMMUTABLE
+ )
+ private val testResponse = RemoteResponse.fromPendingIntent(testIntent)
+
+ private val underTest: SmartspaceInteractionHandler by lazy {
+ SmartspaceInteractionHandler(activityStarter)
+ }
+
+ @Test
+ fun launchAnimatorIsUsedForSmartspaceView() {
+ val parent = FrameLayout(context)
+ val view = SmartspaceAppWidgetHostView(context)
+ parent.addView(view)
+ val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view)
+
+ underTest.onInteraction(view, testIntent, testResponse)
+
+ // Verify that we pass in a non-null animation controller
+ verify(activityStarter)
+ .startPendingIntentWithoutDismissing(
+ /* intent = */ eq(testIntent),
+ /* dismissShade = */ eq(false),
+ /* intentSentUiThreadCallback = */ isNull(),
+ /* animationController = */ notNull(),
+ /* fillInIntent = */ refEq(fillInIntent),
+ /* extraOptions = */ refEq(activityOptions.toBundle()),
+ )
+ }
+
+ @Test
+ fun launchAnimatorIsNotUsedForRegularView() {
+ val parent = FrameLayout(context)
+ val view = View(context)
+ parent.addView(view)
+ val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view)
+
+ underTest.onInteraction(view, testIntent, testResponse)
+
+ // Verify null is used as the animation controller
+ verify(activityStarter)
+ .startPendingIntentWithoutDismissing(
+ /* intent = */ eq(testIntent),
+ /* dismissShade = */ eq(false),
+ /* intentSentUiThreadCallback = */ isNull(),
+ /* animationController = */ isNull(),
+ /* fillInIntent = */ refEq(fillInIntent),
+ /* extraOptions = */ refEq(activityOptions.toBundle()),
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
index f9d5073..0250c9d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
@@ -19,17 +19,23 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class CommunalTransitionViewModelTest : SysuiTestCase() {
@@ -49,13 +55,9 @@
fun testIsUmoOnCommunalDuringTransitionBetweenLockscreenAndGlanceableHub() =
testScope.runTest {
val isUmoOnCommunal by collectLastValue(underTest.isUmoOnCommunal)
- assertThat(isUmoOnCommunal).isNull()
+ runCurrent()
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GLANCEABLE_HUB,
- testScope
- )
+ enterCommunal(from = KeyguardState.LOCKSCREEN)
assertThat(isUmoOnCommunal).isTrue()
keyguardTransitionRepository.sendTransitionSteps(
@@ -70,13 +72,9 @@
fun testIsUmoOnCommunalDuringTransitionBetweenDreamingAndGlanceableHub() =
testScope.runTest {
val isUmoOnCommunal by collectLastValue(underTest.isUmoOnCommunal)
- assertThat(isUmoOnCommunal).isNull()
+ runCurrent()
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.DREAMING,
- to = KeyguardState.GLANCEABLE_HUB,
- testScope
- )
+ enterCommunal(from = KeyguardState.DREAMING)
assertThat(isUmoOnCommunal).isTrue()
keyguardTransitionRepository.sendTransitionSteps(
@@ -91,13 +89,9 @@
fun testIsUmoOnCommunalDuringTransitionBetweenOccludedAndGlanceableHub() =
testScope.runTest {
val isUmoOnCommunal by collectLastValue(underTest.isUmoOnCommunal)
- assertThat(isUmoOnCommunal).isNull()
+ runCurrent()
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.GLANCEABLE_HUB,
- testScope
- )
+ enterCommunal(from = KeyguardState.OCCLUDED)
assertThat(isUmoOnCommunal).isTrue()
keyguardTransitionRepository.sendTransitionSteps(
@@ -105,7 +99,33 @@
to = KeyguardState.OCCLUDED,
testScope
)
-
assertThat(isUmoOnCommunal).isFalse()
}
+
+ @Test
+ fun isUmoOnCommunal_noLongerVisible_returnsFalse() =
+ testScope.runTest {
+ val isUmoOnCommunal by collectLastValue(underTest.isUmoOnCommunal)
+ runCurrent()
+
+ enterCommunal(from = KeyguardState.LOCKSCREEN)
+ assertThat(isUmoOnCommunal).isTrue()
+
+ // Communal is no longer visible.
+ kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Blank)
+ runCurrent()
+
+ // isUmoOnCommunal returns false, even without any keyguard transition.
+ assertThat(isUmoOnCommunal).isFalse()
+ }
+
+ private suspend fun TestScope.enterCommunal(from: KeyguardState) {
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = from,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope
+ )
+ kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal)
+ runCurrent()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 84dbfd4..d5fe2a1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -38,14 +38,17 @@
import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
@@ -86,6 +89,7 @@
private lateinit var widgetRepository: FakeCommunalWidgetRepository
private lateinit var smartspaceRepository: FakeSmartspaceRepository
private lateinit var mediaRepository: FakeCommunalMediaRepository
+ private lateinit var communalSceneInteractor: CommunalSceneInteractor
private val testableResources = context.orCreateTestableResources
@@ -99,6 +103,7 @@
widgetRepository = kosmos.fakeCommunalWidgetRepository
smartspaceRepository = kosmos.fakeSmartspaceRepository
mediaRepository = kosmos.fakeCommunalMediaRepository
+ communalSceneInteractor = kosmos.communalSceneInteractor
kosmos.fakeUserTracker.set(
userInfos = listOf(MAIN_USER_INFO),
selectedUserIndex = 0,
@@ -107,9 +112,10 @@
underTest =
CommunalEditModeViewModel(
- kosmos.communalSceneInteractor,
+ communalSceneInteractor,
kosmos.communalInteractor,
kosmos.communalSettingsInteractor,
+ kosmos.keyguardTransitionInteractor,
mediaHost,
uiEventLogger,
logcatLogBuffer("CommunalEditModeViewModelTest"),
@@ -172,6 +178,22 @@
}
@Test
+ fun isCommunalContentVisible_isTrue_whenEditModeShowing() =
+ testScope.runTest {
+ val isCommunalContentVisible by collectLastValue(underTest.isCommunalContentVisible)
+ communalSceneInteractor.setEditModeState(EditModeState.SHOWING)
+ assertThat(isCommunalContentVisible).isEqualTo(true)
+ }
+
+ @Test
+ fun isCommunalContentVisible_isFalse_whenEditModeNotShowing() =
+ testScope.runTest {
+ val isCommunalContentVisible by collectLastValue(underTest.isCommunalContentVisible)
+ communalSceneInteractor.setEditModeState(null)
+ assertThat(isCommunalContentVisible).isEqualTo(false)
+ }
+
+ @Test
fun deleteWidget() =
testScope.runTest {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
index df7b291..7b7d03b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
@@ -63,34 +63,15 @@
underTest.onInteraction(view, testIntent, testResponse)
+ // Verify that we pass in a non-null animation controller
verify(activityStarter)
.startPendingIntentMaybeDismissingKeyguard(
- eq(testIntent),
- eq(false),
- isNull(),
- notNull(),
- refEq(fillInIntent),
- refEq(activityOptions.toBundle()),
- )
- }
-
- @Test
- fun launchAnimatorIsUsedForSmartspaceView() {
- val parent = FrameLayout(context)
- val view = SmartspaceAppWidgetHostView(context)
- parent.addView(view)
- val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view)
-
- underTest.onInteraction(view, testIntent, testResponse)
-
- verify(activityStarter)
- .startPendingIntentMaybeDismissingKeyguard(
- eq(testIntent),
- eq(false),
- isNull(),
- notNull(),
- refEq(fillInIntent),
- refEq(activityOptions.toBundle()),
+ /* intent = */ eq(testIntent),
+ /* dismissShade = */ eq(false),
+ /* intentSentUiThreadCallback = */ isNull(),
+ /* animationController = */ notNull(),
+ /* fillInIntent = */ refEq(fillInIntent),
+ /* extraOptions = */ refEq(activityOptions.toBundle()),
)
}
@@ -103,14 +84,15 @@
underTest.onInteraction(view, testIntent, testResponse)
+ // Verify null is used as the animation controller
verify(activityStarter)
.startPendingIntentMaybeDismissingKeyguard(
- eq(testIntent),
- eq(false),
- isNull(),
- isNull(),
- refEq(fillInIntent),
- refEq(activityOptions.toBundle()),
+ /* intent = */ eq(testIntent),
+ /* dismissShade = */ eq(false),
+ /* intentSentUiThreadCallback = */ isNull(),
+ /* animationController = */ isNull(),
+ /* fillInIntent = */ refEq(fillInIntent),
+ /* extraOptions = */ refEq(activityOptions.toBundle()),
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index f375ec7..5dac37a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -333,27 +333,16 @@
}
@Test
- fun isDreamingFromKeyguardUpdateMonitor() =
- TestScope(mainDispatcher).runTest {
- whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(false)
- var latest: Boolean? = null
- val job = underTest.isDreaming.onEach { latest = it }.launchIn(this)
+ fun isDreaming() =
+ testScope.runTest {
+ val isDreaming by collectLastValue(underTest.isDreaming)
+ assertThat(isDreaming).isFalse()
- runCurrent()
- assertThat(latest).isFalse()
+ underTest.setDreaming(true)
+ assertThat(isDreaming).isTrue()
- val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
- verify(keyguardUpdateMonitor).registerCallback(captor.capture())
-
- captor.value.onDreamingStateChanged(true)
- runCurrent()
- assertThat(latest).isTrue()
-
- captor.value.onDreamingStateChanged(false)
- runCurrent()
- assertThat(latest).isFalse()
-
- job.cancel()
+ underTest.setDreaming(false)
+ assertThat(isDreaming).isFalse()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
index a12b6f8..d20fec4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
@@ -39,6 +39,7 @@
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -166,4 +167,37 @@
to = KeyguardState.OCCLUDED
)
}
+
+ @Test
+ fun transitionToGone_whenOpeningGlanceableHubEditMode() =
+ testScope.runTest {
+ kosmos.fakeKeyguardBouncerRepository.setAlternateVisible(true)
+ runCurrent()
+
+ // On Glanceable hub and edit mode activity is started
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.ALTERNATE_BOUNCER,
+ testScope
+ )
+ reset(transitionRepository)
+
+ kosmos.communalInteractor.setEditModeOpen(true)
+ runCurrent()
+
+ // Auth and alternate bouncer is hidden
+ kosmos.fakeKeyguardBouncerRepository.setAlternateVisible(false)
+ advanceTimeBy(200) // advance past delay
+
+ // Then no transition should occur yet
+ assertThat(transitionRepository).noTransitionsStarted()
+
+ // When keyguard is going away
+ kosmos.fakeKeyguardRepository.setKeyguardGoingAway(true)
+ runCurrent()
+
+ // Then transition to GONE should occur
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.ALTERNATE_BOUNCER, to = KeyguardState.GONE)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelTest.kt
index 79671b8..bf3231e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelTest.kt
@@ -53,7 +53,7 @@
@Test
fun lockscreenAlpha() =
testScope.runTest {
- val viewState = ViewStateAccessor(alpha = { 0.6f })
+ val viewState = ViewStateAccessor()
val alpha by collectValues(underTest.lockscreenAlpha(viewState))
keyguardTransitionRepository.sendTransitionSteps(
@@ -62,11 +62,9 @@
testScope
)
- assertThat(alpha[0]).isEqualTo(0.6f)
- // Fades out just prior to halfway
+ // Remain at zero throughout
+ assertThat(alpha[0]).isEqualTo(0f)
assertThat(alpha[1]).isEqualTo(0f)
- // Must finish at 0
- assertThat(alpha[2]).isEqualTo(0f)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModelTest.kt
index 59a6ce7..80a9532 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModelTest.kt
@@ -21,6 +21,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -32,6 +33,7 @@
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
+import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
@@ -104,6 +106,24 @@
values.forEach { assertThat(it).isNull() }
}
+ @Test
+ fun notificationAlpha_fadesOut() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.notificationAlpha)
+
+ keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(alpha).isEqualTo(1f)
+
+ keyguardTransitionRepository.sendTransitionStep(step(0.25f))
+ assertThat(alpha).isIn(Range.open(.25f, .75f))
+
+ keyguardTransitionRepository.sendTransitionStep(step(1f))
+ assertThat(alpha).isEqualTo(0f)
+
+ keyguardTransitionRepository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(alpha).isEqualTo(1f)
+ }
+
private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
return TransitionStep(
from = KeyguardState.GONE,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index c4506f2..ca4434d2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -121,10 +121,13 @@
sceneInteractor.setTransitionState(transitionState)
val expandFraction by collectLastValue(scrollViewModel.expandFraction)
assertThat(expandFraction).isEqualTo(0f)
+
+ fakeSceneDataSource.changeScene(toScene = Scenes.Gone)
val isScrollable by collectLastValue(scrollViewModel.isScrollable)
assertThat(isScrollable).isFalse()
fakeSceneDataSource.pause()
+
sceneInteractor.changeScene(Scenes.Shade, "reason")
val transitionProgress = MutableStateFlow(0f)
transitionState.value =
@@ -159,8 +162,10 @@
sceneInteractor.setTransitionState(transitionState)
val expandFraction by collectLastValue(scrollViewModel.expandFraction)
assertThat(expandFraction).isEqualTo(1f)
+
+ fakeSceneDataSource.changeScene(toScene = Scenes.Lockscreen)
val isScrollable by collectLastValue(scrollViewModel.isScrollable)
- assertThat(isScrollable).isFalse()
+ assertThat(isScrollable).isTrue()
}
@Test
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
index de65931..7cf56aa 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -58,6 +58,19 @@
@Nullable ActivityTransitionAnimator.Controller animationController);
/**
+ * Similar to {@link #startPendingIntentMaybeDismissingKeyguard(PendingIntent, Runnable,
+ * ActivityTransitionAnimator.Controller)} but will always not dismiss the keyguard when
+ * launching activities. This should be avoided and other alternatives should be used.
+ */
+ void startPendingIntentWithoutDismissing(
+ PendingIntent intent,
+ boolean dismissShade,
+ Runnable intentSentUiThreadCallback,
+ @Nullable ActivityTransitionAnimator.Controller animationController,
+ @Nullable Intent fillInIntent,
+ @Nullable Bundle extraOptions);
+
+ /**
* Similar to {@link #startPendingIntentDismissingKeyguard}, except that it supports launching
* activities on top of the keyguard. If the activity supports {@code showOverLockscreen}, it
* will show over keyguard without first dimissing it. If it doesn't support it, calling this
diff --git a/packages/SystemUI/res/drawable/ic_swipe_down.xml b/packages/SystemUI/res/drawable/ic_swipe_down.xml
new file mode 100644
index 0000000..15712d6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_swipe_down.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M180,600L40,460L82,418L152,488Q146,461 143,434Q140,407 140,380Q140,298 167,221Q194,144 245,80L288,123Q245,179 222.5,244.5Q200,310 200,380Q200,406 203,431.5Q206,457 213,482L278,418L320,460L180,600ZM658,833Q635,841 611.5,840.5Q588,840 566,829L304,707L322,667Q332,647 350,634.5Q368,622 390,620L458,615L346,308Q340,292 347,277.5Q354,263 370,257Q386,251 400.5,258Q415,265 421,281L569,688L469,695L600,756Q607,759 615,759.5Q623,760 630,758L787,701Q818,690 832,659.5Q846,629 835,598L780,448Q774,432 781,417.5Q788,403 804,397Q820,391 834.5,398Q849,405 855,421L910,571Q933,634 905.5,693.5Q878,753 815,776L658,833ZM568,568L514,417Q508,401 515,386.5Q522,372 538,366Q554,360 568.5,367Q583,374 589,390L644,540L568,568ZM681,527L640,414Q634,398 641,383.5Q648,369 664,363Q680,357 694.5,364Q709,371 715,387L756,499L681,527ZM689,605L689,605L689,605Q689,605 689,605Q689,605 689,605L689,605Q689,605 689,605Q689,605 689,605L689,605L689,605Z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/immersive_cling_bg_circ.xml b/packages/SystemUI/res/drawable/immersive_cling_bg.xml
similarity index 67%
copy from core/res/res/drawable/immersive_cling_bg_circ.xml
copy to packages/SystemUI/res/drawable/immersive_cling_bg.xml
index 4731bbd..de29c32 100644
--- a/core/res/res/drawable/immersive_cling_bg_circ.xml
+++ b/packages/SystemUI/res/drawable/immersive_cling_bg.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2015 The Android Open Source Project
+ ~ 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.
@@ -15,12 +15,10 @@
~ limitations under the License
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval" >
-
- <solid android:color="@color/white" />
-
- <size
- android:height="56dp"
- android:width="56dp" />
-
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <corners
+ android:bottomLeftRadius="28dp"
+ android:bottomRightRadius="28dp"/>
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainer" />
</shape>
diff --git a/packages/SystemUI/res/drawable/immersive_cling_bg_circ.xml b/packages/SystemUI/res/drawable/immersive_cling_bg_circ.xml
deleted file mode 100644
index 32e88ab..0000000
--- a/packages/SystemUI/res/drawable/immersive_cling_bg_circ.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval" >
-
- <solid android:color="?android:attr/colorBackground" />
-
- <size
- android:height="56dp"
- android:width="56dp" />
-
-</shape>
diff --git a/packages/SystemUI/res/drawable/immersive_cling_btn_bg.xml b/packages/SystemUI/res/drawable/immersive_cling_btn_bg.xml
new file mode 100644
index 0000000..df49e38
--- /dev/null
+++ b/packages/SystemUI/res/drawable/immersive_cling_btn_bg.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android">
+ <ripple android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <solid android:color="@android:color/white" />
+ <corners android:radius="28dp" />
+ </shape>
+ </item>
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="28dp" />
+ <solid android:color="?android:attr/colorAccent" />
+ </shape>
+ </item>
+ </ripple>
+</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/immersive_cling_light_bg_circ.xml b/packages/SystemUI/res/drawable/immersive_cling_light_bg_circ.xml
deleted file mode 100644
index 12c3e23..0000000
--- a/packages/SystemUI/res/drawable/immersive_cling_light_bg_circ.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval" >
-
- <solid android:color="?android:attr/colorBackground" />
-
- <size
- android:height="76dp"
- android:width="76dp" />
-
-</shape>
diff --git a/packages/SystemUI/res/layout/immersive_mode_cling.xml b/packages/SystemUI/res/layout/immersive_mode_cling.xml
index e6529b9..20b7cd3 100644
--- a/packages/SystemUI/res/layout/immersive_mode_cling.xml
+++ b/packages/SystemUI/res/layout/immersive_mode_cling.xml
@@ -14,78 +14,67 @@
limitations under the License.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:theme="@android:style/Theme.DeviceDefault.DayNight"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/immersive_cling_bg"
+ android:gravity="center_vertical"
+ android:padding="24dp">
+
+ <!-- The top margin of this icon can be adjusted to push the content down to prevent overlapping
+ with the display cutout. -->
+ <ImageView
+ android:id="@+id/immersive_cling_icon"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_centerHorizontal="true"
+ android:scaleType="fitXY"
+ android:src="@drawable/ic_swipe_down"
+ android:tint="?android:attr/colorAccent"
+ android:tintMode="src_in" />
+
+ <TextView
+ android:id="@+id/immersive_cling_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:gravity="center_vertical"
- android:paddingBottom="24dp">
-
- <FrameLayout
- android:id="@+id/immersive_cling_chevron"
- android:layout_width="76dp"
- android:layout_height="76dp"
- android:layout_marginTop="-24dp"
- android:layout_centerHorizontal="true">
-
- <ImageView
- android:id="@+id/immersive_cling_back_bg_light"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scaleType="center"
- android:src="@drawable/immersive_cling_light_bg_circ" />
-
- <ImageView
- android:id="@+id/immersive_cling_back_bg"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scaleType="center"
- android:src="@drawable/immersive_cling_bg_circ" />
-
- <ImageView
- android:id="@+id/immersive_cling_ic_expand_more"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:paddingTop="8dp"
- android:scaleType="center"
- android:src="@drawable/ic_expand_more_48dp"/>
- </FrameLayout>
+ android:layout_below="@id/immersive_cling_icon"
+ android:layout_marginTop="20dp"
+ android:gravity="center_horizontal"
+ android:text="@string/immersive_cling_title"
+ android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:textSize="24sp"
+ android:fontFamily="google-sans" />
<TextView
- android:id="@+id/immersive_cling_title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@id/immersive_cling_chevron"
- android:paddingEnd="48dp"
- android:paddingStart="48dp"
- android:paddingTop="40dp"
- android:text="@string/immersive_cling_title"
- android:textColor="?android:attr/textColorPrimaryInverse"
- android:textSize="24sp" />
-
- <TextView
- android:id="@+id/immersive_cling_description"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@id/immersive_cling_title"
- android:paddingEnd="48dp"
- android:paddingStart="48dp"
- android:paddingTop="12.6dp"
- android:text="@string/immersive_cling_description"
- android:textColor="?android:attr/textColorPrimaryInverse"
- android:textSize="16sp" />
+ android:id="@+id/immersive_cling_description"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/immersive_cling_title"
+ android:paddingTop="14dp"
+ android:gravity="center_horizontal"
+ android:text="@string/immersive_cling_description"
+ android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
+ android:textSize="14sp"
+ android:fontFamily="google-sans" />
<Button
- android:id="@+id/ok"
- style="@android:style/Widget.Material.Button.Borderless"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:layout_below="@+id/immersive_cling_description"
- android:layout_marginEnd="40dp"
- android:layout_marginTop="18dp"
- android:paddingEnd="8dp"
- android:paddingStart="8dp"
- android:text="@string/immersive_cling_positive"
- android:textColor="?android:attr/textColorPrimaryInverse"
- android:textSize="14sp" />
-
+ android:id="@+id/ok"
+ style="@android:style/Widget.Material.Button.Borderless.Colored"
+ android:background="@drawable/immersive_cling_btn_bg"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:layout_below="@+id/immersive_cling_description"
+ android:layout_marginTop="24dp"
+ android:paddingStart="18dp"
+ android:paddingEnd="18dp"
+ android:minWidth="48dp"
+ android:minHeight="48dp"
+ android:text="@string/immersive_cling_positive"
+ android:textColor="?androidprv:attr/materialColorOnPrimary"
+ android:textAllCaps="false"
+ android:textSize="14sp"
+ android:textFontWeight="500"
+ android:fontFamily="google-sans" />
</RelativeLayout>
diff --git a/packages/SystemUI/res/raw/keep.xml b/packages/SystemUI/res/raw/keep_homecontrols_sq.xml
similarity index 100%
rename from packages/SystemUI/res/raw/keep.xml
rename to packages/SystemUI/res/raw/keep_homecontrols_sq.xml
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index aea79e8..235015b 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -34,7 +34,7 @@
<dimen name="volume_row_slider_height">128dp</dimen>
<!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
- <dimen name="immersive_mode_cling_width">380dp</dimen>
+ <dimen name="immersive_mode_cling_width">500dp</dimen>
<dimen name="controls_activity_view_top_offset">25dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 29e0dbe..27af334 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -56,7 +56,7 @@
<dimen name="navigation_key_padding">25dp</dimen>
<!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
- <dimen name="immersive_mode_cling_width">380dp</dimen>
+ <dimen name="immersive_mode_cling_width">600dp</dimen>
<!-- Keyboard shortcuts helper -->
<dimen name="ksh_layout_width">488dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index d979abb..e825fc5 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1122,6 +1122,8 @@
<dimen name="biometric_prompt_panel_max_width">640dp</dimen>
<dimen name="biometric_prompt_land_small_horizontal_guideline_padding">344dp</dimen>
<dimen name="biometric_prompt_two_pane_udfps_horizontal_guideline_padding">114dp</dimen>
+ <dimen name="biometric_prompt_two_pane_udfps_shorter_content_width">216dp</dimen>
+ <dimen name="biometric_prompt_two_pane_udfps_shorter_horizontal_guideline_padding">661dp</dimen>
<dimen name="biometric_prompt_two_pane_medium_horizontal_guideline_padding">640dp</dimen>
<dimen name="biometric_prompt_one_pane_medium_top_guideline_padding">119dp</dimen>
<dimen name="biometric_prompt_one_pane_medium_horizontal_guideline_padding">0dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index fe968a7..79be2b1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -330,6 +330,8 @@
<string name="share_to_app_stop_dialog_title">Stop sharing screen?</string>
<!-- Text telling a user that they will stop sharing their screen if they click the "Stop sharing" button [CHAR LIMIT=100] -->
<string name="share_to_app_stop_dialog_message">You will stop sharing your screen</string>
+ <!-- Text telling a user that they will stop sharing the contents of the specified [app_name] if they click the "Stop sharing" button. Note that the app name will appear in bold. [CHAR LIMIT=100] -->
+ <string name="share_to_app_stop_dialog_message_specific_app">You will stop sharing <b><xliff:g id="app_name" example="Photos App">%1$s</xliff:g></b></string>
<!-- Button to stop screen sharing [CHAR LIMIT=35] -->
<string name="share_to_app_stop_dialog_button">Stop sharing</string>
@@ -337,6 +339,8 @@
<string name="cast_to_other_device_stop_dialog_title">Stop casting screen?</string>
<!-- Text telling a user that they will stop casting their screen to a different device if they click the "Stop casting" button [CHAR LIMIT=100] -->
<string name="cast_to_other_device_stop_dialog_message">You will stop casting your screen</string>
+ <!-- Text telling a user that they will stop casting the contents of the specified [app_name] to a different device if they click the "Stop casting" button. Note that the app name will appear in bold. [CHAR LIMIT=100] -->
+ <string name="cast_to_other_device_stop_dialog_message_specific_app">You will stop casting <b><xliff:g id="app_name" example="Photos App">%1$s</xliff:g></b></string>
<!-- Button to stop screen casting to a different device [CHAR LIMIT=35] -->
<string name="cast_to_other_device_stop_dialog_button">Stop casting</string>
@@ -367,7 +371,7 @@
<!-- Cling help message title when hiding the navigation bar entering immersive mode [CHAR LIMIT=none] -->
<string name="immersive_cling_title">Viewing full screen</string>
<!-- Cling help message description when hiding the navigation bar entering immersive mode [CHAR LIMIT=none] -->
- <string name="immersive_cling_description">To exit, swipe down from the top.</string>
+ <string name="immersive_cling_description">To exit, swipe down from the top of your screen</string>
<!-- Cling help message confirmation button when hiding the navigation bar entering immersive mode [CHAR LIMIT=30] -->
<string name="immersive_cling_positive">Got it</string>
@@ -716,7 +720,7 @@
<!-- QuickSettings: Accessibility label to activate a device [CHAR LIMIT=NONE]-->
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate">activate</string>
<!-- QuickSettings: Bluetooth auto on tomorrow [CHAR LIMIT=NONE]-->
- <string name="turn_on_bluetooth_auto_tomorrow">Automatically turn on again tomorrow</string>
+ <string name="turn_on_bluetooth_auto_tomorrow">Automatically turn on tomorrow</string>
<!-- QuickSettings: Bluetooth auto on info text when disabled [CHAR LIMIT=NONE]-->
<string name="turn_on_bluetooth_auto_info_disabled">Features like Quick Share and Find My Device use Bluetooth</string>
<!-- QuickSettings: Bluetooth auto on info text when enabled [CHAR LIMIT=NONE]-->
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index b4d53d0..a87ee24 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -364,7 +364,7 @@
if (midGuideline != null) {
val left =
if (bounds.left >= 0) {
- bounds.left
+ abs(bounds.left)
} else {
view.width - abs(bounds.left)
}
@@ -372,7 +372,7 @@
if (bounds.right >= 0) {
view.width - abs(bounds.right)
} else {
- bounds.right
+ abs(bounds.right)
}
val mid = (left + right) / 2
mediumConstraintSet.setGuidelineBegin(midGuideline.id, mid)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 68a3f5d..7d494a5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -30,6 +30,7 @@
import android.hardware.biometrics.Flags.customBiometricPrompt
import android.hardware.biometrics.PromptContentView
import android.os.UserHandle
+import android.text.TextPaint
import android.util.Log
import android.util.RotationUtils
import android.view.HapticFeedbackConstants
@@ -52,6 +53,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
import com.android.systemui.res.R
+import com.android.systemui.util.kotlin.combine
import javax.inject.Inject
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
@@ -260,15 +262,15 @@
val position: Flow<PromptPosition> =
combine(
_forceLargeSize,
+ promptKind,
displayStateInteractor.isLargeScreen,
displayStateInteractor.currentRotation,
modalities
- ) { forceLarge, isLargeScreen, rotation, modalities ->
+ ) { forceLarge, promptKind, isLargeScreen, rotation, modalities ->
when {
forceLarge ||
isLargeScreen ||
- promptKind.value.isOnePaneNoSensorLandscapeBiometric() ->
- PromptPosition.Bottom
+ promptKind.isOnePaneNoSensorLandscapeBiometric() -> PromptPosition.Bottom
rotation == DisplayRotation.ROTATION_90 -> PromptPosition.Right
rotation == DisplayRotation.ROTATION_270 -> PromptPosition.Left
rotation == DisplayRotation.ROTATION_180 && modalities.hasUdfps ->
@@ -308,6 +310,10 @@
context.resources.getDimensionPixelSize(
R.dimen.biometric_prompt_two_pane_udfps_horizontal_guideline_padding
)
+ private val udfpsHorizontalShorterGuidelinePadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_two_pane_udfps_shorter_horizontal_guideline_padding
+ )
private val mediumTopGuidelinePadding =
context.resources.getDimensionPixelSize(
R.dimen.biometric_prompt_one_pane_medium_top_guideline_padding
@@ -449,47 +455,6 @@
}
}
- /**
- * Rect for positioning prompt guidelines (left, top, right, unused)
- *
- * Negative values are used to signify that guideline measuring should be flipped, measuring
- * from opposite side of the screen
- */
- val guidelineBounds: Flow<Rect> =
- combine(iconPosition, promptKind, size, position, modalities) {
- _,
- promptKind,
- size,
- position,
- modalities ->
- when (position) {
- PromptPosition.Bottom ->
- if (promptKind.isOnePaneNoSensorLandscapeBiometric()) {
- Rect(0, 0, 0, 0)
- } else {
- Rect(0, mediumTopGuidelinePadding, 0, 0)
- }
- PromptPosition.Right ->
- if (size.isSmall) {
- Rect(-smallHorizontalGuidelinePadding, 0, 0, 0)
- } else if (modalities.hasUdfps) {
- Rect(udfpsHorizontalGuidelinePadding, 0, 0, 0)
- } else {
- Rect(-mediumHorizontalGuidelinePadding, 0, 0, 0)
- }
- PromptPosition.Left ->
- if (size.isSmall) {
- Rect(0, 0, -smallHorizontalGuidelinePadding, 0)
- } else if (modalities.hasUdfps) {
- Rect(0, 0, udfpsHorizontalGuidelinePadding, 0)
- } else {
- Rect(0, 0, -mediumHorizontalGuidelinePadding, 0)
- }
- PromptPosition.Top -> Rect()
- }
- }
- .distinctUntilChanged()
-
/** Padding for prompt UI elements */
val promptPadding: Flow<Rect> =
combine(size, displayStateInteractor.currentRotation) { size, rotation ->
@@ -556,6 +521,81 @@
if (contentView == null) description else ""
}
+ private val hasOnlyOneLineTitle: Flow<Boolean> =
+ combine(title, subtitle, contentView, description) {
+ title,
+ subtitle,
+ contentView,
+ description ->
+ if (subtitle.isNotEmpty() || contentView != null || description.isNotEmpty()) {
+ false
+ } else {
+ val maxWidth =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_two_pane_udfps_shorter_content_width
+ )
+ val attributes =
+ context.obtainStyledAttributes(
+ R.style.TextAppearance_AuthCredential_Title,
+ intArrayOf(android.R.attr.textSize)
+ )
+ val paint = TextPaint()
+ paint.textSize = attributes.getDimensionPixelSize(0, 0).toFloat()
+ val textWidth = paint.measureText(title)
+ attributes.recycle()
+ textWidth / maxWidth <= 1
+ }
+ }
+
+ /**
+ * Rect for positioning prompt guidelines (left, top, right, unused)
+ *
+ * Negative values are used to signify that guideline measuring should be flipped, measuring
+ * from opposite side of the screen
+ */
+ val guidelineBounds: Flow<Rect> =
+ combine(iconPosition, promptKind, size, position, modalities, hasOnlyOneLineTitle) {
+ _,
+ promptKind,
+ size,
+ position,
+ modalities,
+ hasOnlyOneLineTitle ->
+ var left = 0
+ var top = 0
+ var right = 0
+ when (position) {
+ PromptPosition.Bottom -> {
+ val noSensorLandscape = promptKind.isOnePaneNoSensorLandscapeBiometric()
+ top = if (noSensorLandscape) 0 else mediumTopGuidelinePadding
+ }
+ PromptPosition.Right ->
+ left = getHorizontalPadding(size, modalities, hasOnlyOneLineTitle)
+ PromptPosition.Left ->
+ right = getHorizontalPadding(size, modalities, hasOnlyOneLineTitle)
+ PromptPosition.Top -> {}
+ }
+ Rect(left, top, right, 0)
+ }
+ .distinctUntilChanged()
+
+ private fun getHorizontalPadding(
+ size: PromptSize,
+ modalities: BiometricModalities,
+ hasOnlyOneLineTitle: Boolean
+ ) =
+ if (size.isSmall) {
+ -smallHorizontalGuidelinePadding
+ } else if (modalities.hasUdfps) {
+ if (hasOnlyOneLineTitle) {
+ -udfpsHorizontalShorterGuidelinePadding
+ } else {
+ udfpsHorizontalGuidelinePadding
+ }
+ } else {
+ -mediumHorizontalGuidelinePadding
+ }
+
/** If the indicator (help, error) message should be shown. */
val isIndicatorMessageVisible: Flow<Boolean> =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index d522c7e..88c3f9f6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -208,7 +208,10 @@
// doing any translation.
CommunalScenes.Communal
}
- to == KeyguardState.GONE -> CommunalScenes.Blank
+ // Transitioning to Blank scene when entering the edit mode will be handled separately
+ // with custom animations.
+ to == KeyguardState.GONE && !communalInteractor.editModeOpen.value ->
+ CommunalScenes.Blank
!docked && !KeyguardState.deviceIsAwakeInState(to) -> {
// If the user taps the screen and wakes the device within this timeout, we don't
// want to dismiss the hub
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 3e513f8..00678a8 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -38,6 +38,7 @@
import com.android.systemui.communal.shared.model.CommunalContentSize.THIRD
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.communal.widgets.WidgetConfigurator
@@ -307,6 +308,7 @@
preselectedKey: String? = null,
shouldOpenWidgetPickerOnStart: Boolean = false,
) {
+ communalSceneInteractor.setEditModeState(EditModeState.STARTING)
editWidgetsActivityStarter.startActivity(preselectedKey, shouldOpenWidgetPickerOnStart)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
index 0dab67c..20d8a2a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
@@ -22,14 +22,17 @@
import com.android.systemui.communal.data.repository.CommunalSceneRepository
import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
@@ -57,11 +60,32 @@
communalSceneRepository.snapToScene(newScene, delayMillis)
}
+ /** Immediately snaps to the new scene when activity is started. */
+ fun snapToSceneForActivityStart(newScene: SceneKey, delayMillis: Long = 0) {
+ // skip if we're starting edit mode activity, as it will be handled later by changeScene
+ // with transition key [CommunalTransitionKeys.ToEditMode].
+ if (_editModeState.value == EditModeState.STARTING) {
+ return
+ }
+ snapToScene(newScene, delayMillis)
+ }
+
/**
* Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene].
*/
val currentScene: Flow<SceneKey> = communalSceneRepository.currentScene
+ private val _editModeState = MutableStateFlow<EditModeState?>(null)
+ /**
+ * Current state for glanceable hub edit mode, used to chain the animations when transitioning
+ * between communal scene and the edit mode activity.
+ */
+ val editModeState = _editModeState.asStateFlow()
+
+ fun setEditModeState(value: EditModeState?) {
+ _editModeState.value = value
+ }
+
/** Transition state of the hub mode. */
val transitionState: StateFlow<ObservableTransitionState> =
communalSceneRepository.transitionState
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt
index 73cfb52..11fb233 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt
@@ -26,6 +26,10 @@
object CommunalTransitionKeys {
/** Fades the glanceable hub without any translation */
val SimpleFade = TransitionKey("SimpleFade")
+ /** Transition from the glanceable hub before entering edit mode */
+ val ToEditMode = TransitionKey("ToEditMode")
+ /** Transition to the glanceable hub after exiting edit mode */
+ val FromEditMode = TransitionKey("FromEditMode")
/** Immediately transitions without any delay */
val Immediately = TransitionKey("Immediately")
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/EditModeState.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/EditModeState.kt
new file mode 100644
index 0000000..ace9c2e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/EditModeState.kt
@@ -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 com.android.systemui.communal.shared.model
+
+/**
+ * Models the state of the edit mode activity. Used to chain the animation during the transition
+ * between the hub on communal scene, and the edit mode activity after unlocking the keyguard.
+ */
+enum class EditModeState(val value: Int) {
+ // starting activity after dismissing keyguard
+ STARTING(0),
+ // activity content is showing
+ SHOWING(1),
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
new file mode 100644
index 0000000..a88b777
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.communal.smartspace
+
+import android.app.ActivityOptions
+import android.app.PendingIntent
+import android.content.Intent
+import android.view.View
+import android.widget.RemoteViews
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.communal.util.InteractionHandlerDelegate
+import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
+import com.android.systemui.plugins.ActivityStarter
+import javax.inject.Inject
+
+/**
+ * Handles interactions on smartspace elements on the hub.
+ */
+class SmartspaceInteractionHandler @Inject constructor(
+ private val activityStarter: ActivityStarter,
+) : RemoteViews.InteractionHandler {
+ private val delegate = InteractionHandlerDelegate(
+ findViewToAnimate = { view -> view is SmartspaceAppWidgetHostView },
+ intentStarter = this::startIntent,
+ )
+
+ override fun onInteraction(
+ view: View,
+ pendingIntent: PendingIntent,
+ response: RemoteViews.RemoteResponse
+ ): Boolean = delegate.onInteraction(view, pendingIntent, response)
+
+ private fun startIntent(
+ pendingIntent: PendingIntent,
+ fillInIntent: Intent,
+ extraOptions: ActivityOptions,
+ animationController: ActivityTransitionAnimator.Controller?
+ ): Boolean {
+ activityStarter.startPendingIntentWithoutDismissing(
+ pendingIntent,
+ /* dismissShade = */ false,
+ /* intentSentUiThreadCallback = */ null,
+ animationController,
+ fillInIntent,
+ extraOptions.toBundle()
+ )
+ return true
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 8cd5603..cc90730 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -25,6 +25,7 @@
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.media.controls.ui.view.MediaHost
import kotlinx.coroutines.flow.Flow
@@ -34,12 +35,15 @@
/** The base view model for the communal hub. */
abstract class BaseCommunalViewModel(
- private val communalSceneInteractor: CommunalSceneInteractor,
+ val communalSceneInteractor: CommunalSceneInteractor,
private val communalInteractor: CommunalInteractor,
val mediaHost: MediaHost,
) {
val currentScene: Flow<SceneKey> = communalSceneInteractor.currentScene
+ /** Used to animate showing or hiding the communal content. */
+ open val isCommunalContentVisible: Flow<Boolean> = MutableStateFlow(false)
+
/** Whether communal hub should be focused by accessibility tools. */
open val isFocusable: Flow<Boolean> = MutableStateFlow(false)
@@ -63,6 +67,8 @@
communalSceneInteractor.changeScene(scene, transitionKey)
}
+ fun setEditModeState(state: EditModeState?) = communalSceneInteractor.setEditModeState(state)
+
/**
* Updates the transition state of the hub [SceneTransitionLayout].
*
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 5312aec..c0c5861 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -30,21 +30,27 @@
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.log.CommunalUiEvent
+import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.dagger.MediaModule
import com.android.systemui.res.R
+import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import javax.inject.Inject
import javax.inject.Named
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext
@@ -56,6 +62,7 @@
communalSceneInteractor: CommunalSceneInteractor,
private val communalInteractor: CommunalInteractor,
private val communalSettingsInteractor: CommunalSettingsInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
private val uiEventLogger: UiEventLogger,
@CommunalLog logBuffer: LogBuffer,
@@ -66,6 +73,20 @@
override val isEditMode = true
+ override val isCommunalContentVisible: Flow<Boolean> =
+ communalSceneInteractor.editModeState.map { it == EditModeState.SHOWING }
+
+ /**
+ * Emits when edit mode activity can show, after we've transitioned to [KeyguardState.GONE]
+ * and edit mode is open.
+ */
+ val canShowEditMode =
+ allOf(
+ keyguardTransitionInteractor.isFinishedInState(KeyguardState.GONE),
+ communalInteractor.editModeOpen
+ )
+ .filter { it }
+
// Only widgets are editable.
override val communalContent: Flow<List<CommunalContentModel>> =
communalInteractor.widgetContent.onEach { models ->
@@ -172,6 +193,11 @@
/** Sets whether edit mode is currently open */
fun setEditModeOpen(isOpen: Boolean) = communalInteractor.setEditModeOpen(isOpen)
+ /** Called when exiting the edit mode, before transitioning back to the communal scene. */
+ fun cleanupEditModeState() {
+ communalSceneInteractor.setEditModeState(null)
+ }
+
companion object {
private const val TAG = "CommunalEditModeViewModel"
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
index 7a20ebc..fce18a26 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
@@ -18,6 +18,7 @@
import android.graphics.Color
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -29,11 +30,11 @@
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
@@ -50,16 +51,18 @@
dreamToGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
glanceableHubToDreamTransitionViewModel: GlanceableHubToDreamingTransitionViewModel,
communalInteractor: CommunalInteractor,
- keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ communalSceneInteractor: CommunalSceneInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor
) {
// Show UMO on glanceable hub immediately on transition into glanceable hub
private val showUmoFromOccludedToGlanceableHub: Flow<Boolean> =
keyguardTransitionInteractor
- .transition(Edge.create(from = KeyguardState.OCCLUDED))
+ .transition(
+ Edge.create(from = KeyguardState.OCCLUDED, to = KeyguardState.GLANCEABLE_HUB)
+ )
.filter {
- it.to == KeyguardState.GLANCEABLE_HUB &&
- (it.transitionState == TransitionState.STARTED ||
- it.transitionState == TransitionState.CANCELED)
+ (it.transitionState == TransitionState.STARTED ||
+ it.transitionState == TransitionState.CANCELED)
}
.map { it.transitionState == TransitionState.STARTED }
@@ -82,7 +85,13 @@
* of UMO should be updated.
*/
val isUmoOnCommunal: Flow<Boolean> =
- merge(
+ allOf(
+ // Only show UMO on the hub if the hub is at least partially visible. This prevents
+ // the UMO from being missing on the lock screen when going from the hub to lock
+ // screen in some way other than through a direct transition, such as unlocking from
+ // the hub, then pressing power twice to go back to the lock screen.
+ communalSceneInteractor.isCommunalVisible,
+ merge(
lockscreenToGlanceableHubTransitionViewModel.showUmo,
glanceableHubToLockscreenTransitionViewModel.showUmo,
dreamToGlanceableHubTransitionViewModel.showUmo,
@@ -90,7 +99,7 @@
showUmoFromOccludedToGlanceableHub,
showUmoFromGlanceableHubToOccluded,
)
- .distinctUntilChanged()
+ )
/** Whether to show communal when exiting the occluded state. */
val showCommunalFromOccluded: Flow<Boolean> = communalInteractor.showCommunalFromOccluded
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index c6fa5a84..11247df 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -125,6 +125,8 @@
logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
}
+ override val isCommunalContentVisible: Flow<Boolean> = MutableStateFlow(true)
+
/**
* Freeze the content flow, when an activity is about to show, like starting a timer via voice:
* 1) in handheld mode, use the keyguard occluded state;
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt b/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
new file mode 100644
index 0000000..40b182d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.communal.util
+
+import android.app.ActivityOptions
+import android.app.PendingIntent
+import android.content.Intent
+import android.view.View
+import android.widget.RemoteViews
+import androidx.core.util.component1
+import androidx.core.util.component2
+import com.android.systemui.animation.ActivityTransitionAnimator
+
+
+/** A delegate that can be used to launch activities from [RemoteViews] */
+class InteractionHandlerDelegate(
+ private val findViewToAnimate: (View) -> Boolean,
+ private val intentStarter: IntentStarter,
+) : RemoteViews.InteractionHandler {
+
+ /**
+ * Responsible for starting the pending intent for launching activities.
+ */
+ fun interface IntentStarter {
+ fun startPendingIntent(
+ intent: PendingIntent,
+ fillInIntent: Intent,
+ activityOptions: ActivityOptions,
+ controller: ActivityTransitionAnimator.Controller?,
+ ): Boolean
+ }
+
+ override fun onInteraction(
+ view: View,
+ pendingIntent: PendingIntent,
+ response: RemoteViews.RemoteResponse
+ ): Boolean {
+ val launchOptions = response.getLaunchOptions(view)
+ return when {
+ pendingIntent.isActivity -> {
+ // Forward the fill-in intent and activity options retrieved from the response
+ // to populate the pending intent, so that list items can launch respective
+ // activities.
+ val hostView = getNearestParent(view)
+ val animationController =
+ hostView?.let(ActivityTransitionAnimator.Controller::fromView)
+ val (fillInIntent, activityOptions) = launchOptions
+ intentStarter.startPendingIntent(
+ pendingIntent,
+ fillInIntent,
+ activityOptions,
+ animationController
+ )
+ }
+
+ else -> RemoteViews.startPendingIntent(view, pendingIntent, launchOptions)
+ }
+ }
+
+ private fun getNearestParent(child: View): View? {
+ var view: Any? = child
+ while (view is View) {
+ if (findViewToAnimate(view)) return view
+ view = view.parent
+ }
+ return null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 50477b1..40df6cec 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -37,6 +37,7 @@
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
+import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.communal.ui.compose.CommunalHub
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent
@@ -107,6 +108,7 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ listenForTransitionAndChangeScene()
communalViewModel.setEditModeOpen(true)
@@ -138,6 +140,22 @@
}
}
+ // Handle scene change to show the activity and animate in its content
+ private fun listenForTransitionAndChangeScene() {
+ lifecycleScope.launch {
+ communalViewModel.canShowEditMode.collect {
+ communalViewModel.changeScene(
+ CommunalScenes.Blank,
+ CommunalTransitionKeys.ToEditMode
+ )
+ // wait till transitioned to Blank scene, then animate in communal content in
+ // edit mode
+ communalViewModel.currentScene.first { it == CommunalScenes.Blank }
+ communalViewModel.setEditModeState(EditModeState.SHOWING)
+ }
+ }
+ }
+
private fun onOpenWidgetPicker() {
lifecycleScope.launch {
communalViewModel.onOpenWidgetPicker(
@@ -150,9 +168,11 @@
private fun onEditDone() {
lifecycleScope.launch {
+ communalViewModel.cleanupEditModeState()
+
communalViewModel.changeScene(
CommunalScenes.Communal,
- CommunalTransitionKeys.SimpleFade
+ CommunalTransitionKeys.FromEditMode
)
// Wait for the current scene to be idle on communal.
@@ -192,6 +212,7 @@
override fun onDestroy() {
super.onDestroy()
+ communalViewModel.cleanupEditModeState()
communalViewModel.setEditModeOpen(false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
index e88a8b5..cbc6c97 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
@@ -18,15 +18,11 @@
import android.app.ActivityOptions
import android.app.PendingIntent
-import android.appwidget.AppWidgetHostView
import android.content.Intent
-import android.util.Pair
import android.view.View
import android.widget.RemoteViews
-import androidx.core.util.component1
-import androidx.core.util.component2
import com.android.systemui.animation.ActivityTransitionAnimator
-import com.android.systemui.common.ui.view.getNearestParent
+import com.android.systemui.communal.util.InteractionHandlerDelegate
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.ActivityStarter
import javax.inject.Inject
@@ -37,38 +33,32 @@
constructor(
private val activityStarter: ActivityStarter,
) : RemoteViews.InteractionHandler {
+
+ private val delegate = InteractionHandlerDelegate(
+ findViewToAnimate = { view -> view is CommunalAppWidgetHostView },
+ intentStarter = this::startIntent,
+ )
+
override fun onInteraction(
view: View,
pendingIntent: PendingIntent,
response: RemoteViews.RemoteResponse
- ): Boolean {
- val launchOptions = response.getLaunchOptions(view)
- return when {
- pendingIntent.isActivity ->
- // Forward the fill-in intent and activity options retrieved from the response
- // to populate the pending intent, so that list items can launch respective
- // activities.
- startActivity(view, pendingIntent, launchOptions)
- else -> RemoteViews.startPendingIntent(view, pendingIntent, launchOptions)
- }
- }
+ ): Boolean = delegate.onInteraction(view, pendingIntent, response)
- private fun startActivity(
- view: View,
+
+ private fun startIntent(
pendingIntent: PendingIntent,
- launchOptions: Pair<Intent, ActivityOptions>,
+ fillInIntent: Intent,
+ extraOptions: ActivityOptions,
+ controller: ActivityTransitionAnimator.Controller?
): Boolean {
- val hostView = view.getNearestParent<AppWidgetHostView>()
- val animationController = hostView?.let(ActivityTransitionAnimator.Controller::fromView)
- val (fillInIntent, activityOptions) = launchOptions
-
activityStarter.startPendingIntentMaybeDismissingKeyguard(
pendingIntent,
/* dismissShade = */ false,
/* intentSentUiThreadCallback = */ null,
- animationController,
+ controller,
fillInIntent,
- activityOptions.toBundle(),
+ extraOptions.toBundle(),
)
return true
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index f2a544e..209bc7a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -80,6 +80,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder;
import com.android.systemui.keyguard.ui.binder.WindowManagerLockscreenVisibilityViewBinder;
@@ -118,6 +119,7 @@
private final ShellTransitions mShellTransitions;
private final DisplayTracker mDisplayTracker;
private final PowerInteractor mPowerInteractor;
+ private final KeyguardInteractor mKeyguardInteractor;
private final Lazy<SceneInteractor> mSceneInteractorLazy;
private final Executor mMainExecutor;
@@ -338,6 +340,7 @@
WindowManagerOcclusionManager windowManagerOcclusionManager,
Lazy<SceneInteractor> sceneInteractorLazy,
@Main Executor mainExecutor,
+ KeyguardInteractor keyguardInteractor,
KeyguardEnabledInteractor keyguardEnabledInteractor) {
super();
mKeyguardViewMediator = keyguardViewMediator;
@@ -347,6 +350,7 @@
mDisplayTracker = displayTracker;
mFlags = featureFlags;
mPowerInteractor = powerInteractor;
+ mKeyguardInteractor = keyguardInteractor;
mSceneInteractorLazy = sceneInteractorLazy;
mMainExecutor = mainExecutor;
@@ -474,6 +478,7 @@
public void onDreamingStarted() {
trace("onDreamingStarted");
checkPermission();
+ mKeyguardInteractor.setDreaming(true);
mKeyguardViewMediator.onDreamingStarted();
}
@@ -481,6 +486,7 @@
public void onDreamingStopped() {
trace("onDreamingStopped");
checkPermission();
+ mKeyguardInteractor.setDreaming(false);
mKeyguardViewMediator.onDreamingStopped();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index f3a1843..2d60fcc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -176,6 +176,8 @@
import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -185,7 +187,6 @@
import java.util.concurrent.Executor;
import java.util.function.Consumer;
-import dagger.Lazy;
import kotlinx.coroutines.CoroutineDispatcher;
/**
@@ -3014,7 +3015,7 @@
&& mPM.isInteractive(), WakeAndUnlockUpdateReason.HIDE);
}
- if ((mShowing && !mOccluded) || mUnlockingAndWakingFromDream) {
+ if (mBootCompleted && ((mShowing && !mOccluded) || mUnlockingAndWakingFromDream)) {
if (mUnlockingAndWakingFromDream) {
Log.d(TAG, "hiding keyguard before waking from dream");
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index f15896b..d508b2b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -148,7 +148,7 @@
* Dozing/AOD is a specific type of dream, but it is also possible for other non-systemui dreams
* to be active, such as screensavers.
*/
- val isDreaming: Flow<Boolean>
+ val isDreaming: MutableStateFlow<Boolean>
/** Observable for whether the device is dreaming with an overlay, see [DreamOverlayService] */
val isDreamingWithOverlay: Flow<Boolean>
@@ -250,6 +250,9 @@
/** Sets the current amount of alpha that should be used for rendering the keyguard. */
fun setKeyguardAlpha(alpha: Float)
+ /** Whether the device is actively dreaming */
+ fun setDreaming(isDreaming: Boolean)
+
/**
* Returns whether the keyguard bottom area should be constrained to the top of the lock icon
*/
@@ -509,25 +512,7 @@
}
.distinctUntilChanged()
- override val isDreaming: Flow<Boolean> =
- conflatedCallbackFlow {
- val callback =
- object : KeyguardUpdateMonitorCallback() {
- override fun onDreamingStateChanged(isDreaming: Boolean) {
- trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming")
- }
- }
- keyguardUpdateMonitor.registerCallback(callback)
- trySendWithFailureLogging(
- keyguardUpdateMonitor.isDreaming,
- TAG,
- "initial isDreaming",
- )
-
- awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
- }
- .flowOn(mainDispatcher)
- .distinctUntilChanged()
+ override val isDreaming: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val linearDozeAmount: Flow<Float> = conflatedCallbackFlow {
val callback =
@@ -674,6 +659,10 @@
_keyguardAlpha.value = alpha
}
+ override fun setDreaming(isDreaming: Boolean) {
+ this.isDreaming.value = isDreaming
+ }
+
override fun isUdfpsSupported(): Boolean = keyguardUpdateMonitor.isUdfpsSupported
override fun setQuickSettingsVisible(isVisible: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 14eb972..49d00af 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -114,13 +114,26 @@
powerInteractor.isAwake,
keyguardInteractor.isAodAvailable,
communalInteractor.isIdleOnCommunal,
+ communalInteractor.editModeOpen,
keyguardInteractor.isKeyguardOccluded,
)
.filterRelevantKeyguardStateAnd {
(isAlternateBouncerShowing, isPrimaryBouncerShowing, _, _, _) ->
!isAlternateBouncerShowing && !isPrimaryBouncerShowing
}
- .collect { (_, _, isAwake, isAodAvailable, isIdleOnCommunal, isOccluded) ->
+ .collect {
+ (
+ _,
+ _,
+ isAwake,
+ isAodAvailable,
+ isIdleOnCommunal,
+ isCommunalEditMode,
+ isOccluded) ->
+ // When unlocking over glanceable hub to enter edit mode, transitioning directly
+ // to GONE prevents the lockscreen flash. Let listenForAlternateBouncerToGone
+ // handle it.
+ if (isCommunalEditMode) return@collect
val to =
if (!isAwake) {
if (isAodAvailable) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index f5e98f1..f3692bd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -29,6 +29,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample
+import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineDispatcher
@@ -65,6 +66,7 @@
override fun start() {
listenForDozingToAny()
+ listenForDozingToGoneViaBiometrics()
listenForWakeFromDozing()
listenForTransitionToCamera(scope, keyguardInteractor)
}
@@ -77,6 +79,35 @@
isKeyguardDismissible && !isKeyguardShowing
}
+ private fun listenForDozingToGoneViaBiometrics() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ return
+ }
+
+ // This is separate from `listenForDozingToAny` because any delay on wake and unlock will
+ // cause a noticeable issue with animations
+ scope.launch {
+ powerInteractor.isAwake
+ .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
+ .sample(
+ keyguardInteractor.biometricUnlockState,
+ ::Pair,
+ )
+ .collect {
+ (
+ _,
+ biometricUnlockState,
+ ) ->
+ if (isWakeAndUnlock(biometricUnlockState.mode)) {
+ startTransitionTo(
+ KeyguardState.GONE,
+ ownerReason = "biometric wake and unlock",
+ )
+ }
+ }
+ }
+ }
+
private fun listenForDozingToAny() {
if (KeyguardWmStateRefactor.isEnabled) {
return
@@ -87,7 +118,6 @@
.debounce(50L)
.filterRelevantKeyguardStateAnd { isAwake -> isAwake }
.sample(
- keyguardInteractor.biometricUnlockState,
keyguardInteractor.isKeyguardOccluded,
communalInteractor.isIdleOnCommunal,
canTransitionToGoneOnWake,
@@ -96,7 +126,6 @@
.collect {
(
_,
- biometricUnlockState,
occluded,
isIdleOnCommunal,
canTransitionToGoneOnWake,
@@ -104,8 +133,6 @@
startTransitionTo(
if (!deviceEntryRepository.isLockscreenEnabled()) {
KeyguardState.GONE
- } else if (isWakeAndUnlock(biometricUnlockState.mode)) {
- KeyguardState.GONE
} else if (canTransitionToGoneOnWake) {
KeyguardState.GONE
} else if (primaryBouncerShowing) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index a595eeb..ef96be0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -482,6 +482,10 @@
repository.topClippingBounds.value = top
}
+ fun setDreaming(isDreaming: Boolean) {
+ repository.setDreaming(isDreaming)
+ }
+
/** Temporary shim, until [KeyguardWmStateRefactor] is enabled */
fun showKeyguard() {
fromGoneTransitionInteractor.get().showKeyguard()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt
index 77ebfce..480f948 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt
@@ -16,7 +16,6 @@
package com.android.systemui.keyguard.ui.viewmodel
-import android.util.MathUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor.Companion.TO_GONE_DURATION
import com.android.systemui.keyguard.shared.model.Edge
@@ -50,12 +49,10 @@
)
fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
- var startAlpha = 1f
return transitionAnimation.sharedFlow(
duration = 200.milliseconds,
- onStart = { startAlpha = viewState.alpha() },
- onStep = { MathUtils.lerp(startAlpha, 0f, it) },
- onFinish = { 0f },
+ onStart = { 0f },
+ onStep = { 0f },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt
index 70c0032..c62e4f4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt
@@ -60,6 +60,14 @@
onFinish = { 1f },
)
+ val notificationAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ duration = 500.milliseconds,
+ onStep = { 1f - it },
+ // Needs to be 1f in order for HUNs to appear on AOD
+ onFinish = { 1f },
+ )
+
val deviceEntryBackgroundViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 987b370..53794d2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -106,7 +106,6 @@
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
@@ -697,7 +696,6 @@
.onStart { emit(Unit) }
.map { getMediaLockScreenSetting() }
.distinctUntilChanged()
- .flowOn(backgroundDispatcher)
.collectLatest {
allowMediaPlayerOnLockScreen = it
updateHostVisibility()
@@ -886,7 +884,8 @@
val previousVisibleIndex =
MediaPlayerData.playerKeys().indexOfFirst { key -> it == key }
mediaCarouselScrollHandler.scrollToPlayer(previousVisibleIndex, mediaIndex)
- } ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = mediaIndex)
+ }
+ ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = mediaIndex)
}
} else if (isRtl && mediaContent.childCount > 0) {
// In RTL, Scroll to the first player as it is the rightmost player in media carousel.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
index 2dfc920..abf258c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
@@ -24,6 +24,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.ViewRootImpl.CLIENT_IMMERSIVE_CONFIRMATION;
import static android.view.ViewRootImpl.CLIENT_TRANSIENT;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
@@ -36,12 +37,11 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.res.ColorStateList;
import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.database.ContentObserver;
import android.graphics.Insets;
import android.graphics.PixelFormat;
+import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.os.Binder;
import android.os.Bundle;
@@ -71,7 +71,7 @@
import android.view.animation.Interpolator;
import android.widget.Button;
import android.widget.FrameLayout;
-import android.widget.ImageView;
+import android.widget.RelativeLayout;
import com.android.systemui.CoreStartable;
import com.android.systemui.res.R;
@@ -263,6 +263,7 @@
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT);
lp.setFitInsetsTypes(lp.getFitInsetsTypes() & ~Type.statusBars());
+ lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
// Trusted overlay so touches outside the touchable area are allowed to pass through
lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
| WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
@@ -275,13 +276,20 @@
private FrameLayout.LayoutParams getBubbleLayoutParams() {
return new FrameLayout.LayoutParams(
- mSysUiContext.getResources().getDimensionPixelSize(
- R.dimen.immersive_mode_cling_width),
+ getClingWindowWidth(),
ViewGroup.LayoutParams.WRAP_CONTENT,
Gravity.CENTER_HORIZONTAL | Gravity.TOP);
}
/**
+ * Returns the width of the cling window.
+ */
+ private int getClingWindowWidth() {
+ return mSysUiContext.getResources().getDimensionPixelSize(
+ R.dimen.immersive_mode_cling_width);
+ }
+
+ /**
* @return the window token that's used by all ImmersiveModeConfirmation windows.
*/
IBinder getWindowToken() {
@@ -409,21 +417,6 @@
mClingLayout = (ViewGroup)
View.inflate(mSysUiContext, R.layout.immersive_mode_cling, null);
- TypedArray ta = mDisplayContext.obtainStyledAttributes(
- new int[]{android.R.attr.colorAccent});
- int colorAccent = ta.getColor(0, 0);
- ta.recycle();
- mClingLayout.setBackgroundColor(colorAccent);
- ImageView expandMore = mClingLayout.findViewById(R.id.immersive_cling_ic_expand_more);
- if (expandMore != null) {
- expandMore.setImageTintList(ColorStateList.valueOf(colorAccent));
- }
- ImageView lightBgCirc = mClingLayout.findViewById(R.id.immersive_cling_back_bg_light);
- if (lightBgCirc != null) {
- // Set transparency to 50%
- lightBgCirc.setImageAlpha(128);
- }
-
final Button ok = mClingLayout.findViewById(R.id.ok);
ok.setOnClickListener(new OnClickListener() {
@Override
@@ -482,6 +475,24 @@
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ // If the top display cutout overlaps with the full-width (windowWidth=-1)/centered
+ // dialog, then adjust the dialog contents by the cutout
+ final int width = getWidth();
+ final int windowWidth = getClingWindowWidth();
+ final Rect topDisplayCutout = insets.getDisplayCutout() != null
+ ? insets.getDisplayCutout().getBoundingRectTop()
+ : new Rect();
+ final boolean intersectsTopCutout = topDisplayCutout.intersects(
+ width - (windowWidth / 2), 0,
+ width + (windowWidth / 2), topDisplayCutout.bottom);
+ if (mClingWindow != null &&
+ (windowWidth < 0 || (width > 0 && intersectsTopCutout))) {
+ final View iconView = mClingWindow.findViewById(R.id.immersive_cling_icon);
+ RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)
+ iconView.getLayoutParams();
+ lp.topMargin = topDisplayCutout.bottom;
+ iconView.setLayoutParams(lp);
+ }
// we will be hiding the nav bar, so layout as if it's already hidden
return new WindowInsets.Builder(insets).setInsets(
Type.systemBars(), Insets.NONE).build();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
index 6611434..f6fbe38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
@@ -30,8 +30,8 @@
import com.android.systemui.statusbar.chips.domain.interactor.OngoingActivityChipInteractor.Companion.createDialogLaunchOnClickListener
import com.android.systemui.statusbar.chips.domain.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndCastToOtherDeviceDialogDelegate
+import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndShareToAppDialogDelegate
-import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.Utils
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -60,8 +60,8 @@
private val mediaProjectionRepository: MediaProjectionRepository,
private val packageManager: PackageManager,
private val systemClock: SystemClock,
- private val dialogFactory: SystemUIDialog.Factory,
private val dialogTransitionAnimator: DialogTransitionAnimator,
+ private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
) : OngoingActivityChipInteractor {
override val chip: StateFlow<OngoingActivityChipModel> =
mediaProjectionRepository.mediaProjectionState
@@ -70,9 +70,9 @@
is MediaProjectionState.NotProjecting -> OngoingActivityChipModel.Hidden
is MediaProjectionState.Projecting -> {
if (isProjectionToOtherDevice(state.hostPackage)) {
- createCastToOtherDeviceChip()
+ createCastToOtherDeviceChip(state)
} else {
- createShareToAppChip()
+ createShareToAppChip(state)
}
}
}
@@ -97,7 +97,9 @@
return Utils.isHeadlessRemoteDisplayProvider(packageManager, packageName)
}
- private fun createCastToOtherDeviceChip(): OngoingActivityChipModel.Shown {
+ private fun createCastToOtherDeviceChip(
+ state: MediaProjectionState.Projecting,
+ ): OngoingActivityChipModel.Shown {
return OngoingActivityChipModel.Shown(
icon =
Icon.Resource(
@@ -107,32 +109,39 @@
// TODO(b/332662551): Maybe use a MediaProjection API to fetch this time.
startTimeMs = systemClock.elapsedRealtime(),
createDialogLaunchOnClickListener(
- castToOtherDeviceDialogDelegate,
+ createCastToOtherDeviceDialogDelegate(state),
dialogTransitionAnimator,
),
)
}
- private val castToOtherDeviceDialogDelegate =
+ private fun createCastToOtherDeviceDialogDelegate(state: MediaProjectionState.Projecting) =
EndCastToOtherDeviceDialogDelegate(
- dialogFactory,
+ endMediaProjectionDialogHelper,
this@MediaProjectionChipInteractor,
+ state,
)
- private fun createShareToAppChip(): OngoingActivityChipModel.Shown {
+ private fun createShareToAppChip(
+ state: MediaProjectionState.Projecting,
+ ): OngoingActivityChipModel.Shown {
return OngoingActivityChipModel.Shown(
// TODO(b/332662551): Use the right content description.
icon = Icon.Resource(SHARE_TO_APP_ICON, contentDescription = null),
// TODO(b/332662551): Maybe use a MediaProjection API to fetch this time.
startTimeMs = systemClock.elapsedRealtime(),
- createDialogLaunchOnClickListener(shareToAppDialogDelegate, dialogTransitionAnimator),
+ createDialogLaunchOnClickListener(
+ createShareToAppDialogDelegate(state),
+ dialogTransitionAnimator
+ ),
)
}
- private val shareToAppDialogDelegate =
+ private fun createShareToAppDialogDelegate(state: MediaProjectionState.Projecting) =
EndShareToAppDialogDelegate(
- dialogFactory,
+ endMediaProjectionDialogHelper,
this@MediaProjectionChipInteractor,
+ state,
)
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegate.kt
index 33cec97..596fbf8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegate.kt
@@ -17,25 +17,33 @@
package com.android.systemui.statusbar.chips.mediaprojection.ui.view
import android.os.Bundle
+import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractor
import com.android.systemui.statusbar.phone.SystemUIDialog
/** A dialog that lets the user stop an ongoing cast-screen-to-other-device event. */
class EndCastToOtherDeviceDialogDelegate(
- private val systemUIDialogFactory: SystemUIDialog.Factory,
+ private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
private val interactor: MediaProjectionChipInteractor,
+ private val state: MediaProjectionState.Projecting,
) : SystemUIDialog.Delegate {
override fun createDialog(): SystemUIDialog {
- return systemUIDialogFactory.create(this)
+ return endMediaProjectionDialogHelper.createDialog(this)
}
override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
with(dialog) {
setIcon(MediaProjectionChipInteractor.CAST_TO_OTHER_DEVICE_ICON)
setTitle(R.string.cast_to_other_device_stop_dialog_title)
- // TODO(b/332662551): Use a different message if they're sharing just a single app.
- setMessage(R.string.cast_to_other_device_stop_dialog_message)
+ setMessage(
+ endMediaProjectionDialogHelper.getDialogMessage(
+ state,
+ genericMessageResId = R.string.cast_to_other_device_stop_dialog_message,
+ specificAppMessageResId =
+ R.string.cast_to_other_device_stop_dialog_message_specific_app,
+ )
+ )
// No custom on-click, because the dialog will automatically be dismissed when the
// button is clicked anyway.
setNegativeButton(R.string.close_dialog_button, /* onClick= */ null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt
new file mode 100644
index 0000000..347be02
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.statusbar.chips.mediaprojection.ui.view
+
+import android.annotation.StringRes
+import android.content.Context
+import android.content.pm.PackageManager
+import android.text.Html
+import android.text.Html.FROM_HTML_MODE_LEGACY
+import android.text.TextUtils
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.mediaprojection.data.model.MediaProjectionState
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import javax.inject.Inject
+
+/** Helper class for showing dialogs that let users end different types of media projections. */
+@SysUISingleton
+class EndMediaProjectionDialogHelper
+@Inject
+constructor(
+ private val dialogFactory: SystemUIDialog.Factory,
+ private val packageManager: PackageManager,
+ private val context: Context
+) {
+ /** Creates a new [SystemUIDialog] using the given delegate. */
+ fun createDialog(delegate: SystemUIDialog.Delegate): SystemUIDialog {
+ return dialogFactory.create(delegate)
+ }
+
+ /**
+ * Returns the message to show in the dialog based on the specific media projection state.
+ *
+ * @param genericMessageResId a res ID for a more generic "end projection" message
+ * @param specificAppMessageResId a res ID for an "end projection" message that also lets us
+ * specify which app is currently being projected.
+ */
+ fun getDialogMessage(
+ state: MediaProjectionState.Projecting,
+ @StringRes genericMessageResId: Int,
+ @StringRes specificAppMessageResId: Int,
+ ): CharSequence {
+ when (state) {
+ is MediaProjectionState.Projecting.EntireScreen ->
+ return context.getString(genericMessageResId)
+ is MediaProjectionState.Projecting.SingleTask -> {
+ val packageName =
+ state.task.baseIntent.component?.packageName
+ ?: return context.getString(genericMessageResId)
+ try {
+ val appInfo = packageManager.getApplicationInfo(packageName, 0)
+ val appName = appInfo.loadLabel(packageManager)
+ return getSpecificAppMessageText(specificAppMessageResId, appName)
+ } catch (e: PackageManager.NameNotFoundException) {
+ // TODO(b/332662551): Log this error.
+ return context.getString(genericMessageResId)
+ }
+ }
+ }
+ }
+
+ private fun getSpecificAppMessageText(
+ @StringRes specificAppMessageResId: Int,
+ appName: CharSequence,
+ ): CharSequence {
+ // https://developer.android.com/guide/topics/resources/string-resource#StylingWithHTML
+ val escapedAppName = TextUtils.htmlEncode(appName.toString())
+ val text = context.getString(specificAppMessageResId, escapedAppName)
+ return Html.fromHtml(text, FROM_HTML_MODE_LEGACY)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegate.kt
index 3a863b1..749a11f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegate.kt
@@ -17,25 +17,32 @@
package com.android.systemui.statusbar.chips.mediaprojection.ui.view
import android.os.Bundle
+import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractor
import com.android.systemui.statusbar.phone.SystemUIDialog
/** A dialog that lets the user stop an ongoing share-screen-to-app event. */
class EndShareToAppDialogDelegate(
- private val systemUIDialogFactory: SystemUIDialog.Factory,
+ private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
private val interactor: MediaProjectionChipInteractor,
+ private val state: MediaProjectionState.Projecting,
) : SystemUIDialog.Delegate {
override fun createDialog(): SystemUIDialog {
- return systemUIDialogFactory.create(this)
+ return endMediaProjectionDialogHelper.createDialog(this)
}
override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
with(dialog) {
setIcon(MediaProjectionChipInteractor.SHARE_TO_APP_ICON)
setTitle(R.string.share_to_app_stop_dialog_title)
- // TODO(b/332662551): Use a different message if they're sharing just a single app.
- setMessage(R.string.share_to_app_stop_dialog_message)
+ setMessage(
+ endMediaProjectionDialogHelper.getDialogMessage(
+ state,
+ genericMessageResId = R.string.share_to_app_stop_dialog_message,
+ specificAppMessageResId = R.string.share_to_app_stop_dialog_message_specific_app
+ )
+ )
// No custom on-click, because the dialog will automatically be dismissed when the
// button is clicked anyway.
setNegativeButton(R.string.close_dialog_button, /* onClick= */ null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
index 11636bd..f95e0fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
@@ -37,6 +37,7 @@
import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator
import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import com.android.systemui.statusbar.phone.ongoingcall.data.model.OngoingCallModel
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -224,8 +225,8 @@
modifiedStatusBarAttributes,
isTransientShown,
isInFullscreenMode,
- ongoingCallRepository.hasOngoingCall,
- ) { modifiedAttributes, isTransientShown, isInFullscreenMode, hasOngoingCall ->
+ ongoingCallRepository.ongoingCallState,
+ ) { modifiedAttributes, isTransientShown, isInFullscreenMode, ongoingCallState ->
if (modifiedAttributes == null) {
null
} else {
@@ -234,7 +235,7 @@
modifiedAttributes.appearance,
isTransientShown,
isInFullscreenMode,
- hasOngoingCall,
+ hasOngoingCall = ongoingCallState is OngoingCallModel.InCall,
)
StatusBarAppearance(
statusBarMode,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index b77321b..71a0b94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -3721,7 +3721,7 @@
protected boolean isInsideQsHeader(MotionEvent ev) {
if (SceneContainerFlag.isEnabled()) {
- return ev.getY() < mScrollViewFields.getScrimClippingShape().getBounds().getTop();
+ return ev.getY() < mScrollViewFields.getStackTop();
}
mQsHeader.getBoundsOnScreen(mQsHeaderBound);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index cf5a562..85835d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -40,6 +40,7 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
/** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */
@SysUISingleton
@@ -146,6 +147,7 @@
/** Receives the amount (px) that the stack should scroll due to internal expansion. */
val syntheticScrollConsumer: (Float) -> Unit = stackAppearanceInteractor::setSyntheticScroll
+
/**
* Receives whether the current touch gesture is overscroll as it has already been consumed by
* the stack.
@@ -154,10 +156,9 @@
stackAppearanceInteractor::setCurrentGestureOverscroll
/** Whether the notification stack is scrollable or not. */
- val isScrollable: Flow<Boolean> =
- sceneInteractor
- .isCurrentSceneInFamily(SceneFamilies.NotifShade)
- .dumpWhileCollecting("isScrollable")
+ val isScrollable: Flow<Boolean> = sceneInteractor.currentScene.map {
+ sceneInteractor.isSceneInFamily(it, SceneFamilies.NotifShade) || it == Scenes.Lockscreen
+ }.dumpWhileCollecting("isScrollable")
/** Whether the notification stack is displayed in doze mode. */
val isDozing: Flow<Boolean> by lazy {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index b13630f..1fc2821 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -486,7 +486,7 @@
dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
goneToAodTransitionViewModel.notificationAlpha,
goneToDreamingTransitionViewModel.lockscreenAlpha,
- goneToDozingTransitionViewModel.lockscreenAlpha,
+ goneToDozingTransitionViewModel.notificationAlpha,
goneToLockscreenTransitionViewModel.lockscreenAlpha,
lockscreenToDreamingTransitionViewModel.lockscreenAlpha,
lockscreenToGoneTransitionViewModel.notificationAlpha(viewState),
@@ -506,9 +506,7 @@
combineTransform(
isTransitioningToHiddenKeyguard,
alphaForShadeAndQsExpansion,
- ) {
- isTransitioningToHiddenKeyguard,
- alphaForShadeAndQsExpansion ->
+ ) { isTransitioningToHiddenKeyguard, alphaForShadeAndQsExpansion ->
if (!isTransitioningToHiddenKeyguard) {
emit(alphaForShadeAndQsExpansion)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 2ab7aa9..fae0a46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -85,6 +85,26 @@
)
}
+ override fun startPendingIntentWithoutDismissing(
+ intent: PendingIntent,
+ dismissShade: Boolean,
+ intentSentUiThreadCallback: Runnable?,
+ animationController: ActivityTransitionAnimator.Controller?,
+ fillInIntent: Intent?,
+ extraOptions: Bundle?
+ ) {
+ activityStarterInternal.startPendingIntentDismissingKeyguard(
+ intent = intent,
+ intentSentUiThreadCallback = intentSentUiThreadCallback,
+ animationController = animationController,
+ showOverLockscreen = true,
+ skipLockscreenChecks = true,
+ dismissShade = dismissShade,
+ fillInIntent = fillInIntent,
+ extraOptions = extraOptions,
+ )
+ }
+
override fun startPendingIntentMaybeDismissingKeyguard(
intent: PendingIntent,
intentSentUiThreadCallback: Runnable?,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt
index c9becb4..cff9f5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt
@@ -39,6 +39,7 @@
associatedView: View? = null,
animationController: ActivityTransitionAnimator.Controller? = null,
showOverLockscreen: Boolean = false,
+ skipLockscreenChecks: Boolean = false,
fillInIntent: Intent? = null,
extraOptions: Bundle? = null,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
index e580f64..dbb95e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
@@ -40,6 +40,7 @@
associatedView: View?,
animationController: ActivityTransitionAnimator.Controller?,
showOverLockscreen: Boolean,
+ skipLockscreenChecks: Boolean,
fillInIntent: Intent?,
extraOptions: Bundle?
) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index f83aed8..97b6f95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -538,6 +538,9 @@
// later to awaken.
}
mNotificationShadeWindowController.setNotificationShadeFocusable(false);
+ // Notify the interactor first, to prevent race conditions with the screen waking up
+ // that would show a flicker of the lockscreen on DOZING->GONE
+ mBiometricUnlockInteractor.setBiometricUnlockState(mode, biometricUnlockSource);
mKeyguardViewMediator.onWakeAndUnlocking(wakeInKeyguard);
Trace.endSection();
break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index e5fc4e2..e400ab6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -228,6 +228,7 @@
associatedView: View?,
animationController: ActivityTransitionAnimator.Controller?,
showOverLockscreen: Boolean,
+ skipLockscreenChecks: Boolean,
fillInIntent: Intent?,
extraOptions: Bundle?,
) {
@@ -246,10 +247,10 @@
val actuallyShowOverLockscreen =
showOverLockscreen &&
intent.isActivity &&
- activityIntentHelper.wouldPendingShowOverLockscreen(
+ (skipLockscreenChecks || activityIntentHelper.wouldPendingShowOverLockscreen(
intent,
lockScreenUserManager.currentUserId
- )
+ ))
val animate =
!willLaunchResolverActivity &&
@@ -469,7 +470,7 @@
shadeControllerLazy.get().collapseShadeForActivityStart()
}
if (communalHub()) {
- communalSceneInteractor.snapToScene(CommunalScenes.Blank)
+ communalSceneInteractor.snapToSceneForActivityStart(CommunalScenes.Blank)
}
return deferred
}
@@ -556,7 +557,7 @@
override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
super.onTransitionAnimationStart(isExpandingFullyAbove)
if (communalHub()) {
- communalSceneInteractor.snapToScene(
+ communalSceneInteractor.snapToSceneForActivityStart(
CommunalScenes.Blank,
ActivityTransitionAnimator.TIMINGS.totalDuration
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index a7d4ce3..d128057 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -29,35 +29,36 @@
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.CoreStartable
import com.android.systemui.Dumpable
-import com.android.systemui.res.R
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
+import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.phone.ongoingcall.data.model.OngoingCallModel
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
import com.android.systemui.statusbar.policy.CallbackController
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
import java.io.PrintWriter
import java.util.concurrent.Executor
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
-/**
- * A controller to handle the ongoing call chip in the collapsed status bar.
- */
+/** A controller to handle the ongoing call chip in the collapsed status bar. */
@SysUISingleton
-class OngoingCallController @Inject constructor(
+class OngoingCallController
+@Inject
+constructor(
@Application private val scope: CoroutineScope,
private val context: Context,
private val ongoingCallRepository: OngoingCallRepository,
@@ -79,54 +80,61 @@
private val mListeners: MutableList<OngoingCallListener> = mutableListOf()
private val uidObserver = CallAppUidObserver()
- private val notifListener = object : NotifCollectionListener {
- // Temporary workaround for b/178406514 for testing purposes.
- //
- // b/178406514 means that posting an incoming call notif then updating it to an ongoing call
- // notif does not work (SysUI never receives the update). This workaround allows us to
- // trigger the ongoing call chip when an ongoing call notif is *added* rather than
- // *updated*, allowing us to test the chip.
- //
- // TODO(b/183229367): Remove this function override when b/178406514 is fixed.
- override fun onEntryAdded(entry: NotificationEntry) {
- onEntryUpdated(entry, true)
- }
+ private val notifListener =
+ object : NotifCollectionListener {
+ // Temporary workaround for b/178406514 for testing purposes.
+ //
+ // b/178406514 means that posting an incoming call notif then updating it to an ongoing
+ // call notif does not work (SysUI never receives the update). This workaround allows us
+ // to trigger the ongoing call chip when an ongoing call notif is *added* rather than
+ // *updated*, allowing us to test the chip.
+ //
+ // TODO(b/183229367): Remove this function override when b/178406514 is fixed.
+ override fun onEntryAdded(entry: NotificationEntry) {
+ onEntryUpdated(entry, true)
+ }
- override fun onEntryUpdated(entry: NotificationEntry) {
- // We have a new call notification or our existing call notification has been updated.
- // TODO(b/183229367): This likely won't work if you take a call from one app then
- // switch to a call from another app.
- if (callNotificationInfo == null && isCallNotification(entry) ||
- (entry.sbn.key == callNotificationInfo?.key)) {
- val newOngoingCallInfo = CallNotificationInfo(
- entry.sbn.key,
- entry.sbn.notification.getWhen(),
- entry.sbn.notification.contentIntent,
- entry.sbn.uid,
- entry.sbn.notification.extras.getInt(
- Notification.EXTRA_CALL_TYPE, -1) == CALL_TYPE_ONGOING,
- statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false
- )
- if (newOngoingCallInfo == callNotificationInfo) {
- return
+ override fun onEntryUpdated(entry: NotificationEntry) {
+ // We have a new call notification or our existing call notification has been
+ // updated.
+ // TODO(b/183229367): This likely won't work if you take a call from one app then
+ // switch to a call from another app.
+ if (
+ callNotificationInfo == null && isCallNotification(entry) ||
+ (entry.sbn.key == callNotificationInfo?.key)
+ ) {
+ val newOngoingCallInfo =
+ CallNotificationInfo(
+ entry.sbn.key,
+ entry.sbn.notification.getWhen(),
+ entry.sbn.notification.contentIntent,
+ entry.sbn.uid,
+ entry.sbn.notification.extras.getInt(
+ Notification.EXTRA_CALL_TYPE,
+ -1
+ ) == CALL_TYPE_ONGOING,
+ statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false
+ )
+ if (newOngoingCallInfo == callNotificationInfo) {
+ return
+ }
+
+ callNotificationInfo = newOngoingCallInfo
+ if (newOngoingCallInfo.isOngoing) {
+ updateChip()
+ } else {
+ removeChip()
+ }
}
+ }
- callNotificationInfo = newOngoingCallInfo
- if (newOngoingCallInfo.isOngoing) {
- updateChip()
- } else {
+ override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+ if (entry.sbn.key == callNotificationInfo?.key) {
removeChip()
}
}
}
- override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
- if (entry.sbn.key == callNotificationInfo?.key) {
- removeChip()
- }
- }
- }
-
override fun start() {
dumpManager.registerDumpable(this)
notifCollection.addCollectionListener(notifListener)
@@ -169,8 +177,21 @@
*/
fun hasOngoingCall(): Boolean {
return callNotificationInfo?.isOngoing == true &&
- // When the user is in the phone app, don't show the chip.
- !uidObserver.isCallAppVisible
+ // When the user is in the phone app, don't show the chip.
+ !uidObserver.isCallAppVisible
+ }
+
+ /** Creates the right [OngoingCallModel] based on the call state. */
+ private fun getOngoingCallModel(): OngoingCallModel {
+ if (hasOngoingCall()) {
+ val currentInfo =
+ callNotificationInfo
+ // This shouldn't happen, but protect against it in case
+ ?: return OngoingCallModel.NoCall
+ return OngoingCallModel.InCall(currentInfo.callStartTime)
+ } else {
+ return OngoingCallModel.NoCall
+ }
}
override fun addCallback(listener: OngoingCallListener) {
@@ -182,9 +203,7 @@
}
override fun removeCallback(listener: OngoingCallListener) {
- synchronized(mListeners) {
- mListeners.remove(listener)
- }
+ synchronized(mListeners) { mListeners.remove(listener) }
}
private fun updateChip() {
@@ -196,8 +215,8 @@
if (currentChipView != null && timeView != null) {
if (currentCallNotificationInfo.hasValidStartTime()) {
timeView.setShouldHideText(false)
- timeView.base = currentCallNotificationInfo.callStartTime -
- systemClock.currentTimeMillis() +
+ timeView.base =
+ currentCallNotificationInfo.callStartTime - systemClock.currentTimeMillis() +
systemClock.elapsedRealtime()
timeView.start()
} else {
@@ -218,14 +237,19 @@
callNotificationInfo = null
if (DEBUG) {
- Log.w(TAG, "Ongoing call chip view could not be found; " +
- "Not displaying chip in status bar")
+ Log.w(
+ TAG,
+ "Ongoing call chip view could not be found; " +
+ "Not displaying chip in status bar"
+ )
}
}
}
private fun updateChipClickListener() {
- if (callNotificationInfo == null) { return }
+ if (callNotificationInfo == null) {
+ return
+ }
val currentChipView = chipView
val backgroundView =
currentChipView?.findViewById<View>(R.id.ongoing_activity_chip_background)
@@ -237,7 +261,8 @@
intent,
ActivityTransitionAnimator.Controller.fromView(
backgroundView,
- InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP)
+ InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
+ )
)
}
}
@@ -249,9 +274,11 @@
}
private fun updateGestureListening() {
- if (callNotificationInfo == null ||
- callNotificationInfo?.statusBarSwipedAway == true ||
- !isFullscreen) {
+ if (
+ callNotificationInfo == null ||
+ callNotificationInfo?.statusBarSwipedAway == true ||
+ !isFullscreen
+ ) {
swipeStatusBarAwayGestureHandler.removeOnGestureDetectedCallback(TAG)
} else {
swipeStatusBarAwayGestureHandler.addOnGestureDetectedCallback(TAG) { _ ->
@@ -270,30 +297,31 @@
}
/** Tear down anything related to the chip view to prevent leaks. */
- @VisibleForTesting
- fun tearDownChipView() = chipView?.getTimeView()?.stop()
+ @VisibleForTesting fun tearDownChipView() = chipView?.getTimeView()?.stop()
private fun View.getTimeView(): ChipChronometer? {
return this.findViewById(R.id.ongoing_activity_chip_time)
}
/**
- * If there's an active ongoing call, then we will force the status bar to always show, even if
- * the user is in immersive mode. However, we also want to give users the ability to swipe away
- * the status bar if they need to access the area under the status bar.
- *
- * This method updates the status bar window appropriately when the swipe away gesture is
- * detected.
- */
+ * If there's an active ongoing call, then we will force the status bar to always show, even if
+ * the user is in immersive mode. However, we also want to give users the ability to swipe away
+ * the status bar if they need to access the area under the status bar.
+ *
+ * This method updates the status bar window appropriately when the swipe away gesture is
+ * detected.
+ */
private fun onSwipeAwayGestureDetected() {
- if (DEBUG) { Log.d(TAG, "Swipe away gesture detected") }
+ if (DEBUG) {
+ Log.d(TAG, "Swipe away gesture detected")
+ }
callNotificationInfo = callNotificationInfo?.copy(statusBarSwipedAway = true)
statusBarWindowController.setOngoingProcessRequiresStatusBarVisible(false)
swipeStatusBarAwayGestureHandler.removeOnGestureDetectedCallback(TAG)
}
private fun sendStateChangeEvent() {
- ongoingCallRepository.setHasOngoingCall(hasOngoingCall())
+ ongoingCallRepository.setOngoingCallState(getOngoingCallModel())
mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
}
@@ -308,8 +336,8 @@
val statusBarSwipedAway: Boolean
) {
/**
- * Returns true if the notification information has a valid call start time.
- * See b/192379214.
+ * Returns true if the notification information has a valid call start time. See
+ * b/192379214.
*/
fun hasValidStartTime(): Boolean = callStartTime > 0
}
@@ -342,9 +370,10 @@
callAppUid = uid
try {
- isCallAppVisible = isProcessVisibleToUser(
- iActivityManager.getUidProcessState(uid, context.opPackageName)
- )
+ isCallAppVisible =
+ isProcessVisibleToUser(
+ iActivityManager.getUidProcessState(uid, context.opPackageName)
+ )
if (isRegistered) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/model/OngoingCallModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/model/OngoingCallModel.kt
new file mode 100644
index 0000000..aaa52a7b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/model/OngoingCallModel.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.statusbar.phone.ongoingcall.data.model
+
+/** Represents the state of any ongoing calls. */
+sealed interface OngoingCallModel {
+ /** There is no ongoing call. */
+ data object NoCall : OngoingCallModel
+
+ /**
+ * There *is* an ongoing call.
+ *
+ * @property startTimeMs the time that the phone call started, based on the notification's
+ * `when` field. Importantly, this time is relative to
+ * [com.android.systemui.util.time.SystemClock.currentTimeMillis], **not**
+ * [com.android.systemui.util.time.SystemClock.elapsedRealtime].
+ */
+ data class InCall(val startTimeMs: Long) : OngoingCallModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
index 886481e..554c474 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone.ongoingcall.data.repository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.phone.ongoingcall.data.model.OngoingCallModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -32,15 +33,15 @@
*/
@SysUISingleton
class OngoingCallRepository @Inject constructor() {
- private val _hasOngoingCall = MutableStateFlow(false)
- /** True if there's currently an ongoing call notification and false otherwise. */
- val hasOngoingCall: StateFlow<Boolean> = _hasOngoingCall.asStateFlow()
+ private val _ongoingCallState = MutableStateFlow<OngoingCallModel>(OngoingCallModel.NoCall)
+ /** The current ongoing call state. */
+ val ongoingCallState: StateFlow<OngoingCallModel> = _ongoingCallState.asStateFlow()
/**
- * Sets whether there's currently an ongoing call notification. Should only be set from
+ * Sets the current ongoing call state, based on notifications. Should only be set from
* [com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController].
*/
- fun setHasOngoingCall(hasOngoingCall: Boolean) {
- _hasOngoingCall.value = hasOngoingCall
+ fun setOngoingCallState(state: OngoingCallModel) {
+ _ongoingCallState.value = state
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
index f4e3eab..0871c86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
@@ -18,6 +18,7 @@
import android.os.PersistableBundle
import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
+import android.telephony.CarrierConfigManager.KEY_SHOW_5G_SLICE_ICON_BOOL
import android.telephony.CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.flow.MutableStateFlow
@@ -66,10 +67,16 @@
/** Flow tracking the [KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL] config */
val showOperatorNameInStatusBar: StateFlow<Boolean> = showOperatorName.config
+ private val showNetworkSlice =
+ BooleanCarrierConfig(KEY_SHOW_5G_SLICE_ICON_BOOL, defaultConfig)
+ /** Flow tracking the [KEY_SHOW_5G_SLICE_ICON_BOOL] config */
+ val allowNetworkSliceIndicator: StateFlow<Boolean> = showNetworkSlice.config
+
private val trackedConfigs =
listOf(
inflateSignalStrength,
showOperatorName,
+ showNetworkSlice,
)
/** Ingest a new carrier config, and switch all of the tracked keys over to the new values */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 425c58b..205205e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -48,6 +48,9 @@
/** Reflects the value from the carrier config INFLATE_SIGNAL_STRENGTH for this connection */
val inflateSignalStrength: StateFlow<Boolean>
+ /** Carrier config KEY_SHOW_5G_SLICE_ICON_BOOL for this connection */
+ val allowNetworkSliceIndicator: StateFlow<Boolean>
+
/**
* The table log buffer created for this connection. Will have the name "MobileConnectionLog
* [subId]"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
index 83d5f2b..3261b71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
@@ -79,6 +79,9 @@
)
.stateIn(scope, SharingStarted.WhileSubscribed(), _inflateSignalStrength.value)
+ // I don't see a reason why we would turn the config off for demo mode.
+ override val allowNetworkSliceIndicator = MutableStateFlow(true)
+
private val _isEmergencyOnly = MutableStateFlow(false)
override val isEmergencyOnly =
_isEmergencyOnly
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
index a532e62..2e47678 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -166,6 +166,7 @@
override val isRoaming = MutableStateFlow(false).asStateFlow()
override val carrierId = MutableStateFlow(INVALID_SUBSCRIPTION_ID).asStateFlow()
override val inflateSignalStrength = MutableStateFlow(false).asStateFlow()
+ override val allowNetworkSliceIndicator = MutableStateFlow(false).asStateFlow()
override val isEmergencyOnly = MutableStateFlow(false).asStateFlow()
override val operatorAlphaShort = MutableStateFlow(null).asStateFlow()
override val isInService = MutableStateFlow(true).asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index 41559b2..a5e47a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -308,6 +308,21 @@
activeRepo.value.inflateSignalStrength.value
)
+ override val allowNetworkSliceIndicator =
+ activeRepo
+ .flatMapLatest { it.allowNetworkSliceIndicator }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = "allowSlice",
+ initialValue = activeRepo.value.allowNetworkSliceIndicator.value,
+ )
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.allowNetworkSliceIndicator.value
+ )
+
override val numberOfLevels =
activeRepo
.flatMapLatest { it.numberOfLevels }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 6803a9d..9449659 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -310,6 +310,7 @@
.stateIn(scope, SharingStarted.WhileSubscribed(), UnknownNetworkType)
override val inflateSignalStrength = systemUiCarrierConfig.shouldInflateSignalStrength
+ override val allowNetworkSliceIndicator = systemUiCarrierConfig.allowNetworkSliceIndicator
override val numberOfLevels =
inflateSignalStrength
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index ed9e405..507759c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -253,7 +253,13 @@
)
override val showSliceAttribution: StateFlow<Boolean> =
- connectionRepository.hasPrioritizedNetworkCapabilities
+ combine(
+ connectionRepository.allowNetworkSliceIndicator,
+ connectionRepository.hasPrioritizedNetworkCapabilities,
+ ) { allowed, hasPrioritizedNetworkCapabilities ->
+ allowed && hasPrioritizedNetworkCapabilities
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val isNonTerrestrial: StateFlow<Boolean> =
if (Flags.carrierEnabledSatelliteFlag()) {
@@ -350,7 +356,8 @@
shownLevel.map {
SignalIconModel.Satellite(
level = it,
- icon = SatelliteIconModel.fromSignalStrength(it)
+ icon =
+ SatelliteIconModel.fromSignalStrength(it)
?: SatelliteIconModel.fromSignalStrength(0)!!
)
}
diff --git a/packages/SystemUI/tests/goldens/doubleClick_swapSide.json b/packages/SystemUI/tests/goldens/doubleClick_swapSide.json
index 044ddbc..c4998a5 100644
--- a/packages/SystemUI/tests/goldens/doubleClick_swapSide.json
+++ b/packages/SystemUI/tests/goldens/doubleClick_swapSide.json
@@ -38,7 +38,7 @@
"y": 96
},
{
- "x": 45.008995,
+ "x": 45.009,
"y": 96
},
{
@@ -133,7 +133,7 @@
"y": 96
},
{
- "x": 156.13857,
+ "x": 156.13858,
"y": 96
},
{
@@ -141,7 +141,7 @@
"y": 96
},
{
- "x": 64.81257,
+ "x": 64.81259,
"y": 96
},
{
@@ -149,11 +149,11 @@
"y": 96
},
{
- "x": 24.443243,
+ "x": 24.443266,
"y": 96
},
{
- "x": 14.680362,
+ "x": 14.680339,
"y": 96
},
{
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 7076954..cc7dec56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -26,6 +26,7 @@
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Point
+import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable
import android.hardware.biometrics.BiometricFingerprintConstants
import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT
@@ -87,9 +88,6 @@
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4
-import platform.test.runner.parameterized.Parameter
-import platform.test.runner.parameterized.Parameters
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -97,6 +95,8 @@
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
private const val USER_ID = 4
private const val REQUEST_ID = 4L
@@ -135,6 +135,27 @@
private val defaultLogoDescription = "Test Android App"
private val logoDescriptionFromApp = "Test Cake App"
private val packageNameForLogoWithOverrides = "should.use.overridden.logo"
+ /** Prompt panel size padding */
+ private val smallHorizontalGuidelinePadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_land_small_horizontal_guideline_padding
+ )
+ private val udfpsHorizontalGuidelinePadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_two_pane_udfps_horizontal_guideline_padding
+ )
+ private val udfpsHorizontalShorterGuidelinePadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_two_pane_udfps_shorter_horizontal_guideline_padding
+ )
+ private val mediumTopGuidelinePadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_one_pane_medium_top_guideline_padding
+ )
+ private val mediumHorizontalGuidelinePadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_two_pane_medium_horizontal_guideline_padding
+ )
private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
private lateinit var promptRepository: FakePromptRepository
@@ -1370,6 +1391,142 @@
}
@Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun position_bottom_rotation0() = runGenericTest {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0)
+ val position by collectLastValue(viewModel.position)
+ assertThat(position).isEqualTo(PromptPosition.Bottom)
+ } // TODO(b/335278136): Add test for no sensor landscape
+
+ @Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun position_bottom_forceLarge() = runGenericTest {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
+ viewModel.onSwitchToCredential()
+ val position by collectLastValue(viewModel.position)
+ assertThat(position).isEqualTo(PromptPosition.Bottom)
+ }
+
+ @Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun position_bottom_largeScreen() = runGenericTest {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
+ displayStateRepository.setIsLargeScreen(true)
+ val position by collectLastValue(viewModel.position)
+ assertThat(position).isEqualTo(PromptPosition.Bottom)
+ }
+
+ @Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun position_right_rotation90() = runGenericTest {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
+ val position by collectLastValue(viewModel.position)
+ assertThat(position).isEqualTo(PromptPosition.Right)
+ }
+
+ @Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun position_left_rotation270() = runGenericTest {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
+ val position by collectLastValue(viewModel.position)
+ assertThat(position).isEqualTo(PromptPosition.Left)
+ }
+
+ @Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun position_top_rotation180() = runGenericTest {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180)
+ val position by collectLastValue(viewModel.position)
+ if (testCase.modalities.hasUdfps) {
+ assertThat(position).isEqualTo(PromptPosition.Top)
+ } else {
+ assertThat(position).isEqualTo(PromptPosition.Bottom)
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun guideline_bottom() = runGenericTest {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0)
+ val guidelineBounds by collectLastValue(viewModel.guidelineBounds)
+ assertThat(guidelineBounds).isEqualTo(Rect(0, mediumTopGuidelinePadding, 0, 0))
+ } // TODO(b/335278136): Add test for no sensor landscape
+
+ @Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun guideline_right() = runGenericTest {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
+
+ val isSmall = testCase.shouldStartAsImplicitFlow
+ val guidelineBounds by collectLastValue(viewModel.guidelineBounds)
+
+ if (isSmall) {
+ assertThat(guidelineBounds).isEqualTo(Rect(-smallHorizontalGuidelinePadding, 0, 0, 0))
+ } else if (testCase.modalities.hasUdfps) {
+ assertThat(guidelineBounds).isEqualTo(Rect(udfpsHorizontalGuidelinePadding, 0, 0, 0))
+ } else {
+ assertThat(guidelineBounds).isEqualTo(Rect(-mediumHorizontalGuidelinePadding, 0, 0, 0))
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun guideline_right_onlyShortTitle() =
+ runGenericTest(subtitle = "") {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
+
+ val isSmall = testCase.shouldStartAsImplicitFlow
+ val guidelineBounds by collectLastValue(viewModel.guidelineBounds)
+
+ if (!isSmall && testCase.modalities.hasUdfps) {
+ assertThat(guidelineBounds)
+ .isEqualTo(Rect(-udfpsHorizontalShorterGuidelinePadding, 0, 0, 0))
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun guideline_left() = runGenericTest {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
+
+ val isSmall = testCase.shouldStartAsImplicitFlow
+ val guidelineBounds by collectLastValue(viewModel.guidelineBounds)
+
+ if (isSmall) {
+ assertThat(guidelineBounds).isEqualTo(Rect(0, 0, -smallHorizontalGuidelinePadding, 0))
+ } else if (testCase.modalities.hasUdfps) {
+ assertThat(guidelineBounds).isEqualTo(Rect(0, 0, udfpsHorizontalGuidelinePadding, 0))
+ } else {
+ assertThat(guidelineBounds).isEqualTo(Rect(0, 0, -mediumHorizontalGuidelinePadding, 0))
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun guideline_left_onlyShortTitle() =
+ runGenericTest(subtitle = "") {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
+
+ val isSmall = testCase.shouldStartAsImplicitFlow
+ val guidelineBounds by collectLastValue(viewModel.guidelineBounds)
+
+ if (!isSmall && testCase.modalities.hasUdfps) {
+ assertThat(guidelineBounds)
+ .isEqualTo(Rect(0, 0, -udfpsHorizontalShorterGuidelinePadding, 0))
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun guideline_top() = runGenericTest {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180)
+ val guidelineBounds by collectLastValue(viewModel.guidelineBounds)
+ if (testCase.modalities.hasUdfps) {
+ assertThat(guidelineBounds).isEqualTo(Rect(0, 0, 0, 0))
+ }
+ }
+
+ @Test
fun iconViewLoaded() = runGenericTest {
val isIconViewLoaded by collectLastValue(viewModel.isIconViewLoaded)
// TODO(b/328677869): Add test for noIcon logic.
@@ -1399,9 +1556,10 @@
private fun runGenericTest(
doNotStart: Boolean = false,
allowCredentialFallback: Boolean = false,
+ subtitle: String? = "s",
description: String? = null,
contentView: PromptContentView? = null,
- logoRes: Int = -1,
+ logoRes: Int = 0,
logoBitmap: Bitmap? = null,
logoDescription: String? = null,
packageName: String = OP_PACKAGE_NAME,
@@ -1437,11 +1595,11 @@
allowCredentialFallback = allowCredentialFallback,
fingerprint = testCase.fingerprint,
face = testCase.face,
+ subtitleFromApp = subtitle,
descriptionFromApp = description,
contentViewFromApp = contentView,
logoResFromApp = logoRes,
- logoBitmapFromApp =
- if (logoRes != -1) logoDrawableFromAppRes.toBitmap() else logoBitmap,
+ logoBitmapFromApp = if (logoRes != 0) logoDrawableFromAppRes.toBitmap() else logoBitmap,
logoDescriptionFromApp = logoDescription,
packageName = packageName,
)
@@ -1625,9 +1783,10 @@
face: FaceSensorPropertiesInternal? = null,
requireConfirmation: Boolean = false,
allowCredentialFallback: Boolean = false,
+ subtitleFromApp: String? = "s",
descriptionFromApp: String? = null,
contentViewFromApp: PromptContentView? = null,
- logoResFromApp: Int = -1,
+ logoResFromApp: Int = 0,
logoBitmapFromApp: Bitmap? = null,
logoDescriptionFromApp: String? = null,
packageName: String = OP_PACKAGE_NAME,
@@ -1636,7 +1795,7 @@
PromptInfo().apply {
logoDescription = logoDescriptionFromApp
title = "t"
- subtitle = "s"
+ subtitle = subtitleFromApp
description = descriptionFromApp
contentView = contentViewFromApp
authenticators = listOf(face, fingerprint).extractAuthenticatorTypes()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 6b1d39a..03afcb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -1240,6 +1240,7 @@
mViewMediator.start();
mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null);
+ mViewMediator.onBootCompleted();
}
private void captureKeyguardStateControllerCallback() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 89e0971..246cfbf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -569,13 +569,13 @@
// WHEN biometrics succeeds with wake and unlock mode
powerInteractor.setAwakeForTest()
keyguardRepository.setBiometricUnlockState(BiometricUnlockMode.WAKE_AND_UNLOCK)
- advanceTimeBy(60L)
+ runCurrent()
assertThat(transitionRepository)
.startedTransition(
to = KeyguardState.GONE,
from = KeyguardState.DOZING,
- ownerName = "FromDozingTransitionInteractor",
+ ownerName = "FromDozingTransitionInteractor(biometric wake and unlock)",
animatorAssertion = { it.isNotNull() }
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
index bba01bd..6c350cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
@@ -25,6 +25,8 @@
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardViewController
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel
import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
import com.android.systemui.dreams.DreamOverlayStateController
@@ -498,6 +500,8 @@
to = KeyguardState.GLANCEABLE_HUB,
testScope = testScope,
)
+ kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal)
+ runCurrent()
mediaHierarchyManager.qsExpansion = 0f
mediaHierarchyManager.setTransitionToFullShadeAmount(123f)
@@ -542,6 +546,8 @@
to = KeyguardState.GLANCEABLE_HUB,
testScope = testScope,
)
+ kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal)
+ runCurrent()
verify(mediaCarouselController)
.onDesiredLocationChanged(
eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB),
@@ -557,6 +563,8 @@
to = KeyguardState.LOCKSCREEN,
testScope = testScope,
)
+ kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Blank)
+ runCurrent()
verify(mediaCarouselController)
.onDesiredLocationChanged(
eq(MediaHierarchyManager.LOCATION_QQS),
@@ -579,6 +587,8 @@
to = KeyguardState.GLANCEABLE_HUB,
testScope = testScope,
)
+ kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal)
+ runCurrent()
verify(mediaCarouselController)
.onDesiredLocationChanged(
eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB),
@@ -600,6 +610,8 @@
to = KeyguardState.GLANCEABLE_HUB,
testScope = testScope,
)
+ kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal)
+ runCurrent()
verify(mediaCarouselController)
.onDesiredLocationChanged(
eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB),
@@ -635,6 +647,8 @@
to = KeyguardState.GLANCEABLE_HUB,
testScope = testScope,
)
+ kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal)
+ runCurrent()
// Mock the behavior for dreaming that pulling down shade will immediately set QS as
// expanded
expandQS()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
index a4505a9..327eec4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
@@ -55,7 +56,7 @@
@SmallTest
class MediaProjectionChipInteractorTest : SysuiTestCase() {
- private val kosmos = Kosmos()
+ private val kosmos = Kosmos().also { it.testCase = this }
private val testScope = kosmos.testScope
private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository
private val systemClock = kosmos.fakeSystemClock
@@ -178,7 +179,7 @@
}
@Test
- fun chip_castToOtherDevice_clickListenerShowsCastDialog() =
+ fun chip_castToOtherDevice_entireScreen_clickListenerShowsCastDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
mediaProjectionRepo.mediaProjectionState.value =
@@ -200,7 +201,33 @@
}
@Test
- fun chip_shareToApp_clickListenerShowsShareDialog() =
+ fun chip_castToOtherDevice_singleTask_clickListenerShowsCastDialog() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.SingleTask(
+ CAST_TO_OTHER_DEVICES_PACKAGE,
+ createTask(taskId = 1)
+ )
+
+ val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+
+ // Dialogs must be created on the main thread
+ context.mainExecutor.execute {
+ clickListener.onClick(chipView)
+ verify(kosmos.mockDialogTransitionAnimator)
+ .showFromView(
+ eq(mockCastDialog),
+ eq(chipBackgroundView),
+ eq(null),
+ anyBoolean(),
+ )
+ }
+ }
+
+ @Test
+ fun chip_shareToApp_entireScreen_clickListenerShowsShareDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
mediaProjectionRepo.mediaProjectionState.value =
@@ -221,6 +248,28 @@
}
}
+ @Test
+ fun chip_shareToApp_singleTask_clickListenerShowsShareDialog() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.SingleTask(NORMAL_PACKAGE, createTask(taskId = 1))
+
+ val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+
+ // Dialogs must be created on the main thread
+ context.mainExecutor.execute {
+ clickListener.onClick(chipView)
+ verify(kosmos.mockDialogTransitionAnimator)
+ .showFromView(
+ eq(mockShareDialog),
+ eq(chipBackgroundView),
+ eq(null),
+ anyBoolean(),
+ )
+ }
+ }
+
companion object {
const val CAST_TO_OTHER_DEVICES_PACKAGE = "other.devices.package"
const val NORMAL_PACKAGE = "some.normal.package"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegateTest.kt
index 9a2f545..7b676e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegateTest.kt
@@ -16,22 +16,28 @@
package com.android.systemui.statusbar.chips.mediaprojection.ui.view
+import android.content.ComponentName
import android.content.DialogInterface
+import android.content.Intent
+import android.content.packageManager
+import android.content.pm.ApplicationInfo
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.mediaProjectionChipInteractor
import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
+import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
@@ -41,22 +47,14 @@
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
class EndCastToOtherDeviceDialogDelegateTest : SysuiTestCase() {
- private val kosmos = Kosmos()
+ private val kosmos = Kosmos().also { it.testCase = this }
private val sysuiDialog = mock<SystemUIDialog>()
- private val sysuiDialogFactory = kosmos.mockSystemUIDialogFactory
- private val underTest =
- EndCastToOtherDeviceDialogDelegate(
- sysuiDialogFactory,
- kosmos.mediaProjectionChipInteractor,
- )
-
- @Before
- fun setUp() {
- whenever(sysuiDialogFactory.create(eq(underTest), eq(context))).thenReturn(sysuiDialog)
- }
+ private lateinit var underTest: EndCastToOtherDeviceDialogDelegate
@Test
fun icon() {
+ createAndSetDelegate(ENTIRE_SCREEN)
+
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
verify(sysuiDialog).setIcon(R.drawable.ic_cast_connected)
@@ -64,20 +62,52 @@
@Test
fun title() {
+ createAndSetDelegate(SINGLE_TASK)
+
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
verify(sysuiDialog).setTitle(R.string.cast_to_other_device_stop_dialog_title)
}
@Test
- fun message() {
+ fun message_entireScreen() {
+ createAndSetDelegate(ENTIRE_SCREEN)
+
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
- verify(sysuiDialog).setMessage(R.string.cast_to_other_device_stop_dialog_message)
+ verify(sysuiDialog)
+ .setMessage(context.getString(R.string.cast_to_other_device_stop_dialog_message))
+ }
+
+ @Test
+ fun message_singleTask() {
+ val baseIntent =
+ Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
+ val appInfo = mock<ApplicationInfo>()
+ whenever(appInfo.loadLabel(kosmos.packageManager)).thenReturn("Fake Package")
+ whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
+ .thenReturn(appInfo)
+
+ createAndSetDelegate(
+ MediaProjectionState.Projecting.SingleTask(
+ HOST_PACKAGE,
+ createTask(taskId = 1, baseIntent = baseIntent)
+ )
+ )
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ // It'd be nice to use R.string.cast_to_other_device_stop_dialog_message_specific_app
+ // directly, but it includes the <b> tags which aren't in the returned string.
+ val result = argumentCaptor<CharSequence>()
+ verify(sysuiDialog).setMessage(result.capture())
+ assertThat(result.firstValue.toString()).isEqualTo("You will stop casting Fake Package")
}
@Test
fun negativeButton() {
+ createAndSetDelegate(ENTIRE_SCREEN)
+
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
verify(sysuiDialog).setNegativeButton(R.string.close_dialog_button, null)
@@ -86,6 +116,8 @@
@Test
fun positiveButton() =
kosmos.testScope.runTest {
+ createAndSetDelegate(SINGLE_TASK)
+
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
val clickListener = argumentCaptor<DialogInterface.OnClickListener>()
@@ -105,4 +137,20 @@
assertThat(kosmos.fakeMediaProjectionRepository.stopProjectingInvoked).isTrue()
}
+
+ private fun createAndSetDelegate(state: MediaProjectionState.Projecting) {
+ underTest =
+ EndCastToOtherDeviceDialogDelegate(
+ kosmos.endMediaProjectionDialogHelper,
+ kosmos.mediaProjectionChipInteractor,
+ state,
+ )
+ }
+
+ companion object {
+ private const val HOST_PACKAGE = "fake.host.package"
+ private val ENTIRE_SCREEN = MediaProjectionState.Projecting.EntireScreen(HOST_PACKAGE)
+ private val SINGLE_TASK =
+ MediaProjectionState.Projecting.SingleTask(HOST_PACKAGE, createTask(taskId = 1))
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
new file mode 100644
index 0000000..bbd1109
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
@@ -0,0 +1,144 @@
+/*
+ * 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.statusbar.chips.mediaprojection.ui.view
+
+import android.content.ComponentName
+import android.content.Intent
+import android.content.packageManager
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.mediaprojection.data.model.MediaProjectionState
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+class EndMediaProjectionDialogHelperTest : SysuiTestCase() {
+ private val kosmos = Kosmos().also { it.testCase = this }
+
+ private val underTest = kosmos.endMediaProjectionDialogHelper
+
+ @Test
+ fun createDialog_usesDelegateAndFactory() {
+ val dialog = mock<SystemUIDialog>()
+ val delegate = SystemUIDialog.Delegate { dialog }
+ whenever(kosmos.mockSystemUIDialogFactory.create(eq(delegate))).thenReturn(dialog)
+
+ underTest.createDialog(delegate)
+
+ verify(kosmos.mockSystemUIDialogFactory).create(delegate)
+ }
+
+ @Test
+ fun getDialogMessage_entireScreen_isGenericMessage() {
+ val result =
+ underTest.getDialogMessage(
+ MediaProjectionState.Projecting.EntireScreen("host.package"),
+ R.string.accessibility_home,
+ R.string.cast_to_other_device_stop_dialog_message_specific_app
+ )
+
+ assertThat(result).isEqualTo(context.getString(R.string.accessibility_home))
+ }
+
+ @Test
+ fun getDialogMessage_singleTask_cannotFindPackage_isGenericMessage() {
+ val baseIntent =
+ Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
+ whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
+ .thenThrow(PackageManager.NameNotFoundException())
+
+ val projectionState =
+ MediaProjectionState.Projecting.SingleTask(
+ "host.package",
+ createTask(taskId = 1, baseIntent = baseIntent)
+ )
+
+ val result =
+ underTest.getDialogMessage(
+ projectionState,
+ R.string.accessibility_home,
+ R.string.cast_to_other_device_stop_dialog_message_specific_app
+ )
+
+ assertThat(result).isEqualTo(context.getString(R.string.accessibility_home))
+ }
+
+ @Test
+ fun getDialogMessage_singleTask_findsPackage_isSpecificMessageWithAppLabel() {
+ val baseIntent =
+ Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
+ val appInfo = mock<ApplicationInfo>()
+ whenever(appInfo.loadLabel(kosmos.packageManager)).thenReturn("Fake Package")
+ whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
+ .thenReturn(appInfo)
+
+ val projectionState =
+ MediaProjectionState.Projecting.SingleTask(
+ "host.package",
+ createTask(taskId = 1, baseIntent = baseIntent)
+ )
+
+ val result =
+ underTest.getDialogMessage(
+ projectionState,
+ R.string.accessibility_home,
+ R.string.cast_to_other_device_stop_dialog_message_specific_app
+ )
+
+ // It'd be nice to use the R.string resources directly, but they include the <b> tags which
+ // aren't in the returned string.
+ assertThat(result.toString()).isEqualTo("You will stop casting Fake Package")
+ }
+
+ @Test
+ fun getDialogMessage_appLabelHasSpecialCharacters_isEscaped() {
+ val baseIntent =
+ Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
+ val appInfo = mock<ApplicationInfo>()
+ whenever(appInfo.loadLabel(kosmos.packageManager)).thenReturn("Fake & Package <Here>")
+ whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
+ .thenReturn(appInfo)
+
+ val projectionState =
+ MediaProjectionState.Projecting.SingleTask(
+ "host.package",
+ createTask(taskId = 1, baseIntent = baseIntent)
+ )
+
+ val result =
+ underTest.getDialogMessage(
+ projectionState,
+ R.string.accessibility_home,
+ R.string.cast_to_other_device_stop_dialog_message_specific_app
+ )
+
+ assertThat(result.toString()).isEqualTo("You will stop casting Fake & Package <Here>")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegateTest.kt
index 1d6e866..4ddca52 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegateTest.kt
@@ -16,22 +16,28 @@
package com.android.systemui.statusbar.chips.mediaprojection.ui.view
+import android.content.ComponentName
import android.content.DialogInterface
+import android.content.Intent
+import android.content.packageManager
+import android.content.pm.ApplicationInfo
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.mediaProjectionChipInteractor
import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
+import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
@@ -41,22 +47,14 @@
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
class EndShareToAppDialogDelegateTest : SysuiTestCase() {
- private val kosmos = Kosmos()
+ private val kosmos = Kosmos().also { it.testCase = this }
private val sysuiDialog = mock<SystemUIDialog>()
- private val sysuiDialogFactory = kosmos.mockSystemUIDialogFactory
- private val underTest =
- EndShareToAppDialogDelegate(
- sysuiDialogFactory,
- kosmos.mediaProjectionChipInteractor,
- )
-
- @Before
- fun setUp() {
- whenever(sysuiDialogFactory.create(eq(underTest), eq(context))).thenReturn(sysuiDialog)
- }
+ private lateinit var underTest: EndShareToAppDialogDelegate
@Test
fun icon() {
+ createAndSetDelegate(SINGLE_TASK)
+
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
verify(sysuiDialog).setIcon(R.drawable.ic_screenshot_share)
@@ -64,20 +62,51 @@
@Test
fun title() {
+ createAndSetDelegate(ENTIRE_SCREEN)
+
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
verify(sysuiDialog).setTitle(R.string.share_to_app_stop_dialog_title)
}
@Test
- fun message() {
+ fun message_entireScreen() {
+ createAndSetDelegate(ENTIRE_SCREEN)
+
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
- verify(sysuiDialog).setMessage(R.string.share_to_app_stop_dialog_message)
+ verify(sysuiDialog).setMessage(context.getString(R.string.share_to_app_stop_dialog_message))
+ }
+
+ @Test
+ fun message_singleTask() {
+ val baseIntent =
+ Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
+ val appInfo = mock<ApplicationInfo>()
+ whenever(appInfo.loadLabel(kosmos.packageManager)).thenReturn("Fake Package")
+ whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
+ .thenReturn(appInfo)
+
+ createAndSetDelegate(
+ MediaProjectionState.Projecting.SingleTask(
+ HOST_PACKAGE,
+ createTask(taskId = 1, baseIntent = baseIntent)
+ )
+ )
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ // It'd be nice to use R.string.share_to_app_stop_dialog_message_specific_app directly, but
+ // it includes the <b> tags which aren't in the returned string.
+ val result = argumentCaptor<CharSequence>()
+ verify(sysuiDialog).setMessage(result.capture())
+ assertThat(result.firstValue.toString()).isEqualTo("You will stop sharing Fake Package")
}
@Test
fun negativeButton() {
+ createAndSetDelegate(SINGLE_TASK)
+
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
verify(sysuiDialog).setNegativeButton(R.string.close_dialog_button, null)
@@ -86,6 +115,8 @@
@Test
fun positiveButton() =
kosmos.testScope.runTest {
+ createAndSetDelegate(ENTIRE_SCREEN)
+
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
val clickListener = argumentCaptor<DialogInterface.OnClickListener>()
@@ -105,4 +136,20 @@
assertThat(kosmos.fakeMediaProjectionRepository.stopProjectingInvoked).isTrue()
}
+
+ private fun createAndSetDelegate(state: MediaProjectionState.Projecting) {
+ underTest =
+ EndShareToAppDialogDelegate(
+ kosmos.endMediaProjectionDialogHelper,
+ kosmos.mediaProjectionChipInteractor,
+ state,
+ )
+ }
+
+ companion object {
+ private const val HOST_PACKAGE = "fake.host.package"
+ private val ENTIRE_SCREEN = MediaProjectionState.Projecting.EntireScreen(HOST_PACKAGE)
+ private val SINGLE_TASK =
+ MediaProjectionState.Projecting.SingleTask(HOST_PACKAGE, createTask(taskId = 1))
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
index 6712963..65bf0bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
@@ -22,6 +22,7 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
@@ -39,8 +40,7 @@
@SmallTest
class OngoingActivityChipsViewModelTest : SysuiTestCase() {
-
- private val kosmos = Kosmos()
+ private val kosmos = Kosmos().also { it.testCase = this }
private val testScope = kosmos.testScope
private val screenRecordState = kosmos.screenRecordRepository.screenRecordState
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
index 057dcb2..6af14e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
@@ -35,6 +35,7 @@
import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator
import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import com.android.systemui.statusbar.phone.ongoingcall.data.model.OngoingCallModel
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -390,7 +391,7 @@
testScope.runTest {
val latest by collectLastValue(underTest.statusBarAppearance)
- ongoingCallRepository.setHasOngoingCall(true)
+ ongoingCallRepository.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 34))
onSystemBarAttributesChanged(
requestedVisibleTypes = WindowInsets.Type.navigationBars(),
)
@@ -403,7 +404,7 @@
testScope.runTest {
val latest by collectLastValue(underTest.statusBarAppearance)
- ongoingCallRepository.setHasOngoingCall(true)
+ ongoingCallRepository.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 789))
onSystemBarAttributesChanged(
requestedVisibleTypes = WindowInsets.Type.statusBars(),
appearance = APPEARANCE_OPAQUE_STATUS_BARS,
@@ -417,7 +418,7 @@
testScope.runTest {
val latest by collectLastValue(underTest.statusBarAppearance)
- ongoingCallRepository.setHasOngoingCall(false)
+ ongoingCallRepository.setOngoingCallState(OngoingCallModel.NoCall)
onSystemBarAttributesChanged(
requestedVisibleTypes = WindowInsets.Type.navigationBars(),
appearance = APPEARANCE_OPAQUE_STATUS_BARS,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index 4d6798b..feef943 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -32,16 +32,17 @@
import android.widget.LinearLayout
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository
import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
-import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository
+import com.android.systemui.statusbar.phone.ongoingcall.data.model.OngoingCallModel
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.util.concurrency.FakeExecutor
@@ -60,13 +61,13 @@
import org.mockito.ArgumentMatchers.anyString
import org.mockito.ArgumentMatchers.nullable
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.eq
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
private const val CALL_UID = 900
@@ -93,8 +94,8 @@
private lateinit var controller: OngoingCallController
private lateinit var notifCollectionListener: NotifCollectionListener
- @Mock private lateinit var mockSwipeStatusBarAwayGestureHandler:
- SwipeStatusBarAwayGestureHandler
+ @Mock
+ private lateinit var mockSwipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler
@Mock private lateinit var mockOngoingCallListener: OngoingCallListener
@Mock private lateinit var mockActivityStarter: ActivityStarter
@Mock private lateinit var mockIActivityManager: IActivityManager
@@ -112,21 +113,22 @@
MockitoAnnotations.initMocks(this)
val notificationCollection = mock(CommonNotifCollection::class.java)
- controller = OngoingCallController(
- testScope.backgroundScope,
- context,
- ongoingCallRepository,
- notificationCollection,
- clock,
- mockActivityStarter,
- mainExecutor,
- mockIActivityManager,
- OngoingCallLogger(uiEventLoggerFake),
- DumpManager(),
- mockStatusBarWindowController,
- mockSwipeStatusBarAwayGestureHandler,
- statusBarModeRepository,
- )
+ controller =
+ OngoingCallController(
+ testScope.backgroundScope,
+ context,
+ ongoingCallRepository,
+ notificationCollection,
+ clock,
+ mockActivityStarter,
+ mainExecutor,
+ mockIActivityManager,
+ OngoingCallLogger(uiEventLoggerFake),
+ DumpManager(),
+ mockStatusBarWindowController,
+ mockSwipeStatusBarAwayGestureHandler,
+ statusBarModeRepository,
+ )
controller.start()
controller.addCallback(mockOngoingCallListener)
controller.setChipView(chipView)
@@ -136,7 +138,7 @@
notifCollectionListener = collectionListenerCaptor.value!!
`when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
- .thenReturn(PROC_STATE_INVISIBLE)
+ .thenReturn(PROC_STATE_INVISIBLE)
}
@After
@@ -146,10 +148,14 @@
@Test
fun onEntryUpdated_isOngoingCallNotif_listenerAndRepoNotified() {
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ val notification = NotificationEntryBuilder(createOngoingCallNotifEntry())
+ notification.modifyNotification(context).setWhen(567)
+ notifCollectionListener.onEntryUpdated(notification.build())
verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
- assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue()
+ val repoState = ongoingCallRepository.ongoingCallState.value
+ assertThat(repoState).isInstanceOf(OngoingCallModel.InCall::class.java)
+ assertThat((repoState as OngoingCallModel.InCall).startTimeMs).isEqualTo(567)
}
@Test
@@ -164,7 +170,8 @@
notifCollectionListener.onEntryUpdated(createNotCallNotifEntry())
verify(mockOngoingCallListener, never()).onOngoingCallStateChanged(anyBoolean())
- assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse()
+ assertThat(ongoingCallRepository.ongoingCallState.value)
+ .isInstanceOf(OngoingCallModel.NoCall::class.java)
}
@Test
@@ -172,25 +179,27 @@
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
notifCollectionListener.onEntryUpdated(createScreeningCallNotifEntry())
- verify(mockOngoingCallListener, times(2))
- .onOngoingCallStateChanged(anyBoolean())
+ verify(mockOngoingCallListener, times(2)).onOngoingCallStateChanged(anyBoolean())
}
@Test
fun onEntryUpdated_ongoingCallNotifThenScreeningCallNotif_repoUpdated() {
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
- assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue()
+ assertThat(ongoingCallRepository.ongoingCallState.value)
+ .isInstanceOf(OngoingCallModel.InCall::class.java)
notifCollectionListener.onEntryUpdated(createScreeningCallNotifEntry())
- assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse()
+ assertThat(ongoingCallRepository.ongoingCallState.value)
+ .isInstanceOf(OngoingCallModel.NoCall::class.java)
}
/** Regression test for b/191472854. */
@Test
fun onEntryUpdated_notifHasNullContentIntent_noCrash() {
notifCollectionListener.onEntryUpdated(
- createCallNotifEntry(ongoingCallStyle, nullContentIntent = true))
+ createCallNotifEntry(ongoingCallStyle, nullContentIntent = true)
+ )
}
/** Regression test for b/192379214. */
@@ -202,12 +211,12 @@
notifCollectionListener.onEntryUpdated(notification.build())
chipView.measure(
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
)
assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
- .isEqualTo(0)
+ .isEqualTo(0)
}
@Test
@@ -218,12 +227,12 @@
notifCollectionListener.onEntryUpdated(notification.build())
chipView.measure(
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
)
assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
- .isGreaterThan(0)
+ .isGreaterThan(0)
}
@Test
@@ -233,12 +242,12 @@
notifCollectionListener.onEntryUpdated(notification.build())
chipView.measure(
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
)
assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
- .isGreaterThan(0)
+ .isGreaterThan(0)
}
/** Regression test for b/194731244. */
@@ -250,15 +259,14 @@
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
}
- verify(mockIActivityManager, times(1))
- .registerUidObserver(any(), any(), any(), any())
+ verify(mockIActivityManager, times(1)).registerUidObserver(any(), any(), any(), any())
}
/** Regression test for b/216248574. */
@Test
fun entryUpdated_getUidProcessStateThrowsException_noCrash() {
`when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
- .thenThrow(SecurityException())
+ .thenThrow(SecurityException())
// No assert required, just check no crash
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
@@ -267,9 +275,15 @@
/** Regression test for b/216248574. */
@Test
fun entryUpdated_registerUidObserverThrowsException_noCrash() {
- `when`(mockIActivityManager.registerUidObserver(
- any(), any(), any(), nullable(String::class.java)
- )).thenThrow(SecurityException())
+ `when`(
+ mockIActivityManager.registerUidObserver(
+ any(),
+ any(),
+ any(),
+ nullable(String::class.java),
+ )
+ )
+ .thenThrow(SecurityException())
// No assert required, just check no crash
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
@@ -281,9 +295,8 @@
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
val packageNameCaptor = ArgumentCaptor.forClass(String::class.java)
- verify(mockIActivityManager).registerUidObserver(
- any(), any(), any(), packageNameCaptor.capture()
- )
+ verify(mockIActivityManager)
+ .registerUidObserver(any(), any(), any(), packageNameCaptor.capture())
assertThat(packageNameCaptor.value).isNotNull()
}
@@ -313,11 +326,13 @@
fun onEntryRemoved_callNotifAddedThenRemoved_repoUpdated() {
val ongoingCallNotifEntry = createOngoingCallNotifEntry()
notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
- assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue()
+ assertThat(ongoingCallRepository.ongoingCallState.value)
+ .isInstanceOf(OngoingCallModel.InCall::class.java)
notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)
- assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse()
+ assertThat(ongoingCallRepository.ongoingCallState.value)
+ .isInstanceOf(OngoingCallModel.NoCall::class.java)
}
@Test
@@ -360,7 +375,8 @@
notifCollectionListener.onEntryRemoved(removedEntryBuilder.build(), REASON_USER_STOPPED)
- assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse()
+ assertThat(ongoingCallRepository.ongoingCallState.value)
+ .isInstanceOf(OngoingCallModel.NoCall::class.java)
}
@Test
@@ -379,7 +395,8 @@
notifCollectionListener.onEntryRemoved(createNotCallNotifEntry(), REASON_USER_STOPPED)
- assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue()
+ assertThat(ongoingCallRepository.ongoingCallState.value)
+ .isInstanceOf(OngoingCallModel.InCall::class.java)
}
@Test
@@ -404,7 +421,7 @@
@Test
fun hasOngoingCall_ongoingCallNotifSentAndCallAppNotVisible_returnsTrue() {
`when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
- .thenReturn(PROC_STATE_INVISIBLE)
+ .thenReturn(PROC_STATE_INVISIBLE)
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
@@ -414,7 +431,7 @@
@Test
fun hasOngoingCall_ongoingCallNotifSentButCallAppVisible_returnsFalse() {
`when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
- .thenReturn(PROC_STATE_VISIBLE)
+ .thenReturn(PROC_STATE_VISIBLE)
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
@@ -472,10 +489,8 @@
lateinit var newChipView: View
TestableLooper.get(this).runWithLooper {
- newChipView = LayoutInflater.from(mContext).inflate(
- R.layout.ongoing_activity_chip,
- null
- )
+ newChipView =
+ LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip, null)
}
// Change the chip view associated with the controller.
@@ -488,13 +503,13 @@
fun callProcessChangesToVisible_listenerNotified() {
// Start the call while the process is invisible.
`when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
- .thenReturn(PROC_STATE_INVISIBLE)
+ .thenReturn(PROC_STATE_INVISIBLE)
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
reset(mockOngoingCallListener)
val captor = ArgumentCaptor.forClass(IUidObserver.Stub::class.java)
- verify(mockIActivityManager).registerUidObserver(
- captor.capture(), any(), any(), nullable(String::class.java))
+ verify(mockIActivityManager)
+ .registerUidObserver(captor.capture(), any(), any(), nullable(String::class.java))
val uidObserver = captor.value
// Update the process to visible.
@@ -509,13 +524,13 @@
fun callProcessChangesToInvisible_listenerNotified() {
// Start the call while the process is visible.
`when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
- .thenReturn(PROC_STATE_VISIBLE)
+ .thenReturn(PROC_STATE_VISIBLE)
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
reset(mockOngoingCallListener)
val captor = ArgumentCaptor.forClass(IUidObserver.Stub::class.java)
- verify(mockIActivityManager).registerUidObserver(
- captor.capture(), any(), any(), nullable(String::class.java))
+ verify(mockIActivityManager)
+ .registerUidObserver(captor.capture(), any(), any(), nullable(String::class.java))
val uidObserver = captor.value
// Update the process to invisible.
@@ -534,7 +549,7 @@
assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
assertThat(uiEventLoggerFake.eventId(0))
- .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_CLICKED.id)
+ .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_CLICKED.id)
}
/** Regression test for b/212467440. */
@@ -556,8 +571,9 @@
assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
assertThat(uiEventLoggerFake.eventId(0))
- .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_VISIBLE.id)
+ .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_VISIBLE.id)
}
+
// Other tests for notifyChipVisibilityChanged are in [OngoingCallLogger], since
// [OngoingCallController.notifyChipVisibilityChanged] just delegates to that class.
@@ -621,8 +637,7 @@
statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = false
testScope.runCurrent()
- verify(mockSwipeStatusBarAwayGestureHandler)
- .removeOnGestureDetectedCallback(anyString())
+ verify(mockSwipeStatusBarAwayGestureHandler).removeOnGestureDetectedCallback(anyString())
}
@Test
@@ -635,8 +650,7 @@
notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)
- verify(mockSwipeStatusBarAwayGestureHandler)
- .removeOnGestureDetectedCallback(anyString())
+ verify(mockSwipeStatusBarAwayGestureHandler).removeOnGestureDetectedCallback(anyString())
}
// TODO(b/195839150): Add test
@@ -675,5 +689,9 @@
private val hangUpIntent = mock(PendingIntent::class.java)
private val ongoingCallStyle = Notification.CallStyle.forOngoingCall(person, hangUpIntent)
-private val screeningCallStyle = Notification.CallStyle.forScreeningCall(
- person, hangUpIntent, /* answerIntent= */ mock(PendingIntent::class.java))
\ No newline at end of file
+private val screeningCallStyle =
+ Notification.CallStyle.forScreeningCall(
+ person,
+ hangUpIntent,
+ /* answerIntent= */ mock(PendingIntent::class.java),
+ )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
index 56aa7d6..73a86a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
@@ -18,6 +18,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.ongoingcall.data.model.OngoingCallModel
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -27,12 +28,13 @@
@Test
fun hasOngoingCall_matchesSet() {
- underTest.setHasOngoingCall(true)
+ val inCallModel = OngoingCallModel.InCall(startTimeMs = 654)
+ underTest.setOngoingCallState(inCallModel)
- assertThat(underTest.hasOngoingCall.value).isTrue()
+ assertThat(underTest.ongoingCallState.value).isEqualTo(inCallModel)
- underTest.setHasOngoingCall(false)
+ underTest.setOngoingCallState(OngoingCallModel.NoCall)
- assertThat(underTest.hasOngoingCall.value).isFalse()
+ assertThat(underTest.ongoingCallState.value).isEqualTo(OngoingCallModel.NoCall)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt
index 95b132d..3de50c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt
@@ -19,6 +19,7 @@
import android.os.PersistableBundle
import android.telephony.CarrierConfigManager
import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
+import android.telephony.CarrierConfigManager.KEY_SHOW_5G_SLICE_ICON_BOOL
import android.telephony.CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -53,16 +54,19 @@
fun processNewConfig_updatesAllFlows() {
assertThat(underTest.shouldInflateSignalStrength.value).isFalse()
assertThat(underTest.showOperatorNameInStatusBar.value).isFalse()
+ assertThat(underTest.allowNetworkSliceIndicator.value).isTrue()
underTest.processNewCarrierConfig(
configWithOverrides(
KEY_INFLATE_SIGNAL_STRENGTH_BOOL to true,
KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL to true,
+ KEY_SHOW_5G_SLICE_ICON_BOOL to false,
)
)
assertThat(underTest.shouldInflateSignalStrength.value).isTrue()
assertThat(underTest.showOperatorNameInStatusBar.value).isTrue()
+ assertThat(underTest.allowNetworkSliceIndicator.value).isFalse()
}
@Test
@@ -79,12 +83,14 @@
configWithOverrides(
KEY_INFLATE_SIGNAL_STRENGTH_BOOL to true,
KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL to true,
+ KEY_SHOW_5G_SLICE_ICON_BOOL to true,
)
)
assertThat(underTest.isUsingDefault).isTrue()
assertThat(underTest.shouldInflateSignalStrength.value).isTrue()
assertThat(underTest.showOperatorNameInStatusBar.value).isTrue()
+ assertThat(underTest.allowNetworkSliceIndicator.value).isTrue()
// Process a new config with no keys
underTest.processNewCarrierConfig(PersistableBundle())
@@ -92,6 +98,7 @@
assertThat(underTest.isUsingDefault).isFalse()
assertThat(underTest.shouldInflateSignalStrength.value).isFalse()
assertThat(underTest.showOperatorNameInStatusBar.value).isFalse()
+ assertThat(underTest.allowNetworkSliceIndicator.value).isFalse()
}
companion object {
@@ -105,6 +112,7 @@
PersistableBundle().also {
it.putBoolean(CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
it.putBoolean(CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL, false)
+ it.putBoolean(CarrierConfigManager.KEY_SHOW_5G_SLICE_ICON_BOOL, true)
}
/** Override the default config with the given (key, value) pair */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 3695d8c..6d8bf55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -25,6 +25,7 @@
import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN
import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN
import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
+import android.telephony.CarrierConfigManager.KEY_SHOW_5G_SLICE_ICON_BOOL
import android.telephony.NetworkRegistrationInfo
import android.telephony.NetworkRegistrationInfo.DOMAIN_PS
import android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_DENIED
@@ -1044,6 +1045,24 @@
}
@Test
+ fun allowNetworkSliceIndicator_exposesCarrierConfigValue() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.allowNetworkSliceIndicator)
+
+ systemUiCarrierConfig.processNewCarrierConfig(
+ configWithOverride(KEY_SHOW_5G_SLICE_ICON_BOOL, true)
+ )
+
+ assertThat(latest).isTrue()
+
+ systemUiCarrierConfig.processNewCarrierConfig(
+ configWithOverride(KEY_SHOW_5G_SLICE_ICON_BOOL, false)
+ )
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
fun isAllowedDuringAirplaneMode_alwaysFalse() =
testScope.runTest {
val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index dfe8023..1488418 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -194,6 +194,50 @@
}
@Test
+ fun networkSlice_configOn_hasPrioritizedCaps_showsSlice() =
+ testScope.runTest {
+ connectionRepository.allowNetworkSliceIndicator.value = true
+ val latest by collectLastValue(underTest.showSliceAttribution)
+
+ connectionRepository.hasPrioritizedNetworkCapabilities.value = true
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun networkSlice_configOn_noPrioritizedCaps_noSlice() =
+ testScope.runTest {
+ connectionRepository.allowNetworkSliceIndicator.value = true
+ val latest by collectLastValue(underTest.showSliceAttribution)
+
+ connectionRepository.hasPrioritizedNetworkCapabilities.value = false
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun networkSlice_configOff_hasPrioritizedCaps_noSlice() =
+ testScope.runTest {
+ connectionRepository.allowNetworkSliceIndicator.value = false
+ val latest by collectLastValue(underTest.showSliceAttribution)
+
+ connectionRepository.hasPrioritizedNetworkCapabilities.value = true
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun networkSlice_configOff_noPrioritizedCaps_noSlice() =
+ testScope.runTest {
+ connectionRepository.allowNetworkSliceIndicator.value = false
+ val latest by collectLastValue(underTest.showSliceAttribution)
+
+ connectionRepository.hasPrioritizedNetworkCapabilities.value = false
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
fun iconGroup_three_g() =
testScope.runTest {
connectionRepository.resolvedNetworkType.value =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
index cdb2b88..b8299e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.assertLogsWtf
@@ -63,7 +64,10 @@
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
class CollapsedStatusBarViewModelImplTest : SysuiTestCase() {
- private val kosmos = Kosmos().apply { testDispatcher = UnconfinedTestDispatcher() }
+ private val kosmos = Kosmos().also {
+ it.testCase = this
+ it.testDispatcher = UnconfinedTestDispatcher()
+ }
private val testScope = kosmos.testScope
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt
index e3c218d..3f38408 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.communal.ui.viewmodel
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.util.communalColors
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.dreamingToGlanceableHubTransitionViewModel
@@ -37,6 +38,7 @@
dreamToGlanceableHubTransitionViewModel = dreamingToGlanceableHubTransitionViewModel,
glanceableHubToDreamTransitionViewModel = glanceableHubToDreamingTransitionViewModel,
communalInteractor = communalInteractor,
+ communalSceneInteractor = communalSceneInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
communalColors = communalColors,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 22b8c8db..2d100f0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -80,7 +80,7 @@
override val isAodAvailable: StateFlow<Boolean> = _isAodAvailable
private val _isDreaming = MutableStateFlow(false)
- override val isDreaming: Flow<Boolean> = _isDreaming
+ override val isDreaming: MutableStateFlow<Boolean> = _isDreaming
private val _isDreamingWithOverlay = MutableStateFlow(false)
override val isDreamingWithOverlay: Flow<Boolean> = _isDreamingWithOverlay
@@ -204,7 +204,7 @@
_isAodAvailable.value = value
}
- fun setDreaming(isDreaming: Boolean) {
+ override fun setDreaming(isDreaming: Boolean) {
_isDreaming.value = isDreaming
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorKosmos.kt
index 062b448..9d22811 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorKosmos.kt
@@ -21,7 +21,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
-import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
+import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper
import com.android.systemui.util.time.fakeSystemClock
val Kosmos.mediaProjectionChipInteractor: MediaProjectionChipInteractor by
@@ -31,7 +31,7 @@
mediaProjectionRepository = fakeMediaProjectionRepository,
packageManager = packageManager,
systemClock = fakeSystemClock,
- dialogFactory = mockSystemUIDialogFactory,
+ endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
dialogTransitionAnimator = mockDialogTransitionAnimator,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt
new file mode 100644
index 0000000..4f82662
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt
@@ -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 com.android.systemui.statusbar.chips.mediaprojection.ui.view
+
+import android.content.applicationContext
+import android.content.packageManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
+
+val Kosmos.endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper by
+ Kosmos.Fixture {
+ EndMediaProjectionDialogHelper(
+ dialogFactory = mockSystemUIDialogFactory,
+ packageManager = packageManager,
+ context = applicationContext,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index eb2d6c0..c3c3cce 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -32,6 +32,7 @@
) : MobileConnectionRepository {
override val carrierId = MutableStateFlow(UNKNOWN_CARRIER_ID)
override val inflateSignalStrength: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ override val allowNetworkSliceIndicator: MutableStateFlow<Boolean> = MutableStateFlow(true)
override val isEmergencyOnly = MutableStateFlow(false)
override val isRoaming = MutableStateFlow(false)
override val operatorAlphaShort: MutableStateFlow<String?> = MutableStateFlow(null)
diff --git a/proto/src/ondeviceintelligence/OWNERS b/proto/src/ondeviceintelligence/OWNERS
new file mode 100644
index 0000000..09774f7
--- /dev/null
+++ b/proto/src/ondeviceintelligence/OWNERS
@@ -0,0 +1 @@
+file:/core/java/android/app/ondeviceintelligence/OWNERS
diff --git a/proto/src/ondeviceintelligence/inference_info.proto b/proto/src/ondeviceintelligence/inference_info.proto
new file mode 100644
index 0000000..a6f4f4f
--- /dev/null
+++ b/proto/src/ondeviceintelligence/inference_info.proto
@@ -0,0 +1,34 @@
+/*
+ * Copyright 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.
+ */
+
+syntax = "proto2";
+
+package android.ondeviceintelligence;
+
+option java_package = "com.android.server.ondeviceintelligence";
+option java_multiple_files = true;
+
+
+message InferenceInfo {
+ // Uid for the caller app.
+ optional int32 uid = 1;
+ // Inference start time(milliseconds from the epoch time).
+ optional int64 start_time_ms = 2;
+ // Inference end time(milliseconds from the epoch time).
+ optional int64 end_time_ms = 3;
+ // Suspended time in milliseconds.
+ optional int64 suspended_time_ms = 4;
+}
\ No newline at end of file
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 6fc05b7..eae516e 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -2003,7 +2003,7 @@
final AutofillManagerServiceImpl service =
peekServiceForUserWithLocalBinderIdentityLocked(userId);
if (service != null) {
- service.setViewAutofilled(sessionId, getCallingUid(), id);
+ service.setViewAutofilledLocked(sessionId, getCallingUid(), id);
} else if (sVerbose) {
Slog.v(TAG, "setAutofillFailure(): no service for " + userId);
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 588266f..2bf319e 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -478,7 +478,7 @@
}
@GuardedBy("mLock")
- void setViewAutofilled(int sessionId, int uid, @NonNull AutofillId id) {
+ void setViewAutofilledLocked(int sessionId, int uid, @NonNull AutofillId id) {
if (!isEnabledLocked()) {
Slog.wtf(TAG, "Service not enabled");
return;
@@ -488,7 +488,7 @@
Slog.v(TAG, "setViewAutofilled(): no session for " + sessionId + "(" + uid + ")");
return;
}
- session.setViewAutofilled(id);
+ session.setViewAutofilledLocked(id);
}
@GuardedBy("mLock")
@@ -515,7 +515,10 @@
}
final boolean finished = saveResult.isRemoveSession();
- if (sVerbose) Slog.v(TAG, "finishSessionLocked(): session finished on save? " + finished);
+ if (sVerbose) {
+ Slog.v(TAG, "finishSessionLocked(): session finished? " + finished
+ + ", showing save UI? " + saveResult.isLogSaveShown());
+ }
if (finished) {
session.removeFromServiceLocked();
@@ -792,10 +795,9 @@
* Initializes the last fill selection after an autofill service returned a new
* {@link FillResponse}.
*/
- void setLastResponse(int sessionId, @NonNull FillResponse response) {
- synchronized (mLock) {
+ @GuardedBy("mLock")
+ void setLastResponseLocked(int sessionId, @NonNull FillResponse response) {
mEventHistory = new FillEventHistory(sessionId, response.getClientState());
- }
}
void setLastAugmentedAutofillResponse(int sessionId) {
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index aa76200..676abd1 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -641,6 +641,15 @@
}
/**
+ * Set how many views are filtered from fill because they are not in current session
+ */
+ public void maybeSetFilteredFillableViewsCount(int filteredViewsCount) {
+ mEventInternal.ifPresent(event -> {
+ event.mFilteredFillabaleViewCount = filteredViewsCount;
+ });
+ }
+
+ /**
* Set views_filled_failure_count using failure count as long as mEventInternal
* presents.
*/
@@ -675,7 +684,14 @@
} else if (autofillIds.contains(autofillId)) {
if (sVerbose) {
Slog.v(TAG, "Logging autofill for id:" + autofillId);
- event.mViewFillSuccessCount++;
+ }
+ event.mViewFillSuccessCount++;
+ autofillIds.remove(autofillId);
+ event.mAlreadyFilledAutofillIds.add(autofillId);
+ } else if (event.mAlreadyFilledAutofillIds.contains(autofillId)) {
+ if (sVerbose) {
+ Slog.v(TAG, "Successfully filled autofillId:" + autofillId
+ + " already processed ");
}
} else {
Slog.w(TAG, "Successfully filled autofillId:" + autofillId
@@ -728,6 +744,7 @@
+ " mAppPackageUid=" + mCallingAppUid
+ " mIsCredentialRequest=" + event.mIsCredentialRequest
+ " mWebviewRequestedCredential=" + event.mWebviewRequestedCredential
+ + " mFilteredFillabaleViewCount=" + event.mFilteredFillabaleViewCount
+ " mViewFillableTotalCount=" + event.mViewFillableTotalCount
+ " mViewFillFailureCount=" + event.mViewFillFailureCount
+ " mFocusedId=" + event.mFocusedId
@@ -784,6 +801,7 @@
mCallingAppUid,
event.mIsCredentialRequest,
event.mWebviewRequestedCredential,
+ event.mFilteredFillabaleViewCount,
event.mViewFillableTotalCount,
event.mViewFillFailureCount,
event.mFocusedId,
@@ -832,6 +850,7 @@
int mFieldClassificationRequestId = DEFAULT_VALUE_INT;
boolean mIsCredentialRequest = false;
boolean mWebviewRequestedCredential = false;
+ int mFilteredFillabaleViewCount = DEFAULT_VALUE_INT;
int mViewFillableTotalCount = DEFAULT_VALUE_INT;
int mViewFillFailureCount = DEFAULT_VALUE_INT;
int mFocusedId = DEFAULT_VALUE_INT;
@@ -850,6 +869,7 @@
int mViewFilledButUnexpectedCount = 0;
ArraySet<AutofillId> mAutofillIdsAttemptedAutofill;
+ ArraySet<AutofillId> mAlreadyFilledAutofillIds = new ArraySet<>();
PresentationStatsEventInternal() {}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index aa67ffe..b22f999 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1677,22 +1677,23 @@
final LogMaker requestLog;
- // Start a new FillResponse logger for the success case.
- mFillResponseEventLogger.startLogForNewResponse();
- mFillResponseEventLogger.maybeSetRequestId(requestId);
- mFillResponseEventLogger.maybeSetAppPackageUid(uid);
- mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SUCCESS);
- mFillResponseEventLogger.startResponseProcessingTime();
- // Time passed since session was created
- final long fillRequestReceivedRelativeTimestamp =
- SystemClock.elapsedRealtime() - mLatencyBaseTime;
- mPresentationStatsEventLogger.maybeSetFillResponseReceivedTimestampMs(
- (int) (fillRequestReceivedRelativeTimestamp));
- mFillResponseEventLogger.maybeSetLatencyFillResponseReceivedMillis(
- (int) (fillRequestReceivedRelativeTimestamp));
- mFillResponseEventLogger.maybeSetDetectionPreference(getDetectionPreferenceForLogging());
-
synchronized (mLock) {
+ // Start a new FillResponse logger for the success case.
+ mFillResponseEventLogger.startLogForNewResponse();
+ mFillResponseEventLogger.maybeSetRequestId(requestId);
+ mFillResponseEventLogger.maybeSetAppPackageUid(uid);
+ mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SUCCESS);
+ mFillResponseEventLogger.startResponseProcessingTime();
+ // Time passed since session was created
+ final long fillRequestReceivedRelativeTimestamp =
+ SystemClock.elapsedRealtime() - mLatencyBaseTime;
+ mPresentationStatsEventLogger.maybeSetFillResponseReceivedTimestampMs(
+ (int) (fillRequestReceivedRelativeTimestamp));
+ mFillResponseEventLogger.maybeSetLatencyFillResponseReceivedMillis(
+ (int) (fillRequestReceivedRelativeTimestamp));
+ mFillResponseEventLogger.maybeSetDetectionPreference(
+ getDetectionPreferenceForLogging());
+
if (mDestroyed) {
Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: "
+ id + " destroyed");
@@ -1744,11 +1745,9 @@
Slog.v(TAG, "Service requested to wait for delayed fill response.");
registerDelayedFillBroadcastLocked();
}
- }
- mService.setLastResponse(id, response);
+ mService.setLastResponseLocked(id, response);
- synchronized (mLock) {
if (mLogViewEntered) {
mLogViewEntered = false;
mService.logViewEntered(id, null);
@@ -1821,16 +1820,18 @@
}
int datasetCount = (datasetList == null) ? 0 : datasetList.size();
- mFillResponseEventLogger.maybeSetTotalDatasetsProvided(datasetCount);
- // It's possible that this maybe overwritten later on after PCC filtering.
- mFillResponseEventLogger.maybeSetAvailableCount(datasetCount);
+ synchronized (mLock) {
+ mFillResponseEventLogger.maybeSetTotalDatasetsProvided(datasetCount);
+ // It's possible that this maybe overwritten later on after PCC filtering.
+ mFillResponseEventLogger.maybeSetAvailableCount(datasetCount);
- // TODO(b/266379948): Ideally wait for PCC request to finish for a while more
- // (say 100ms) before proceeding further on.
+ // TODO(b/266379948): Ideally wait for PCC request to finish for a while more
+ // (say 100ms) before proceeding further on.
- processResponseLockedForPcc(response, response.getClientState(), requestFlags);
- mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis();
- mFillResponseEventLogger.logAndEndEvent();
+ processResponseLockedForPcc(response, response.getClientState(), requestFlags);
+ mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis();
+ mFillResponseEventLogger.logAndEndEvent();
+ }
}
@@ -2381,21 +2382,22 @@
@Nullable CharSequence message) {
boolean showMessage = !TextUtils.isEmpty(message);
- // Start a new FillResponse logger for the failure or timeout case.
- mFillResponseEventLogger.startLogForNewResponse();
- mFillResponseEventLogger.maybeSetRequestId(requestId);
- mFillResponseEventLogger.maybeSetAppPackageUid(uid);
- mFillResponseEventLogger.maybeSetAvailableCount(
- AVAILABLE_COUNT_WHEN_FILL_REQUEST_FAILED_OR_TIMEOUT);
- mFillResponseEventLogger.maybeSetTotalDatasetsProvided(
- AVAILABLE_COUNT_WHEN_FILL_REQUEST_FAILED_OR_TIMEOUT);
- mFillResponseEventLogger.maybeSetDetectionPreference(getDetectionPreferenceForLogging());
- final long fillRequestReceivedRelativeTimestamp =
- SystemClock.elapsedRealtime() - mLatencyBaseTime;
- mFillResponseEventLogger.maybeSetLatencyFillResponseReceivedMillis(
- (int)(fillRequestReceivedRelativeTimestamp));
-
synchronized (mLock) {
+ // Start a new FillResponse logger for the failure or timeout case.
+ mFillResponseEventLogger.startLogForNewResponse();
+ mFillResponseEventLogger.maybeSetRequestId(requestId);
+ mFillResponseEventLogger.maybeSetAppPackageUid(uid);
+ mFillResponseEventLogger.maybeSetAvailableCount(
+ AVAILABLE_COUNT_WHEN_FILL_REQUEST_FAILED_OR_TIMEOUT);
+ mFillResponseEventLogger.maybeSetTotalDatasetsProvided(
+ AVAILABLE_COUNT_WHEN_FILL_REQUEST_FAILED_OR_TIMEOUT);
+ mFillResponseEventLogger.maybeSetDetectionPreference(
+ getDetectionPreferenceForLogging());
+ final long fillRequestReceivedRelativeTimestamp =
+ SystemClock.elapsedRealtime() - mLatencyBaseTime;
+ mFillResponseEventLogger.maybeSetLatencyFillResponseReceivedMillis(
+ (int) (fillRequestReceivedRelativeTimestamp));
+
unregisterDelayedFillBroadcastLocked();
if (mDestroyed) {
Slog.w(TAG, "Call to Session#onFillRequestFailureOrTimeout(req=" + requestId
@@ -2635,8 +2637,8 @@
+ id + " destroyed");
return;
}
+ mSaveEventLogger.maybeSetLatencySaveRequestMillis();
}
- mSaveEventLogger.maybeSetLatencySaveRequestMillis();
mHandler.sendMessage(obtainMessage(
AutofillManagerServiceImpl::handleSessionSave,
mService, this));
@@ -3215,6 +3217,58 @@
return saveInfo == null ? 0 : saveInfo.getFlags();
}
+ static class SaveInfoStats {
+ public int saveInfoCount;
+ public int saveDataTypeCount;
+ }
+
+ /**
+ * Get statistic information of save info in current session. Specifically
+ * 1. how many save info the current session has.
+ * 2. How many distinct save data types current session has.
+ *
+ * @return SaveInfoStats returns the above two number in a SaveInfoStats object
+ */
+ @GuardedBy("mLock")
+ private SaveInfoStats getSaveInfoStatsLocked() {
+ SaveInfoStats retSaveInfoStats = new SaveInfoStats();
+ retSaveInfoStats.saveInfoCount = -1;
+ retSaveInfoStats.saveDataTypeCount = -1;
+
+ if (mContexts == null) {
+ if (sVerbose) {
+ Slog.v(TAG, "getSaveInfoStatsLocked(): mContexts is null");
+ }
+ } else if (mResponses == null) {
+ // Happens when the activity / session was finished before the service replied, or
+ // when the service cannot autofill it (and returned a null response).
+ if (sVerbose) {
+ Slog.v(TAG, "getSaveInfoStatsLocked(): mResponses is null");
+ }
+ return retSaveInfoStats;
+ } else {
+ int numSaveInfos = 0;
+ int numSaveDataTypes = 0;
+ ArraySet<Integer> saveDataTypeSeen = new ArraySet<>();
+ final int numResponses = mResponses.size();
+ for (int responseNum = 0; responseNum < numResponses; responseNum++) {
+ final FillResponse response = mResponses.valueAt(responseNum);
+ if (response != null && response.getSaveInfo() != null) {
+ numSaveInfos += 1;
+ int saveDataType = response.getSaveInfo().getType();
+ if (!saveDataTypeSeen.contains(saveDataType)) {
+ saveDataTypeSeen.add(saveDataType);
+ numSaveDataTypes += 1;
+ }
+ }
+ }
+ retSaveInfoStats.saveInfoCount = numSaveInfos;
+ retSaveInfoStats.saveDataTypeCount = numSaveDataTypes;
+ }
+
+ return retSaveInfoStats;
+ }
+
/**
* Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_CONTEXT_COMMITTED}
* when necessary.
@@ -3227,7 +3281,9 @@
mHandler.sendMessage(obtainMessage(Session::handleLogContextCommitted, this,
Event.NO_SAVE_UI_REASON_NONE,
COMMIT_REASON_UNKNOWN));
- logAllEvents(COMMIT_REASON_UNKNOWN);
+ synchronized (mLock) {
+ logAllEventsLocked(COMMIT_REASON_UNKNOWN);
+ }
}
/**
@@ -3251,6 +3307,11 @@
mSessionCommittedEventLogger.maybeSetCommitReason(commitReason);
mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount);
+ SaveInfoStats saveInfoStats = getSaveInfoStatsLocked();
+ mSessionCommittedEventLogger.maybeSetSaveInfoCount(saveInfoStats.saveInfoCount);
+ mSessionCommittedEventLogger.maybeSetSaveDataTypeCount(saveInfoStats.saveDataTypeCount);
+ mSessionCommittedEventLogger.maybeSetLastFillResponseHasSaveInfo(
+ getSaveInfoLocked() != null);
mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_NONE);
}
@@ -4571,7 +4632,7 @@
FillResponse response = viewState.getSecondaryResponse();
if (response != null) {
- logPresentationStatsOnViewEntered(response, isCredmanRequested);
+ logPresentationStatsOnViewEnteredLocked(response, isCredmanRequested);
}
// If the ViewState is ready to be displayed, onReady() will be called.
@@ -4662,7 +4723,7 @@
FillResponse response = viewState.getResponse();
if (response != null) {
- logPresentationStatsOnViewEntered(response, isCredmanRequested);
+ logPresentationStatsOnViewEnteredLocked(response, isCredmanRequested);
}
if (isSameViewEntered) {
@@ -4703,7 +4764,7 @@
}
@GuardedBy("mLock")
- private void logPresentationStatsOnViewEntered(FillResponse response,
+ private void logPresentationStatsOnViewEnteredLocked(FillResponse response,
boolean isCredmanRequested) {
mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId());
mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested);
@@ -5061,7 +5122,7 @@
getUiForShowing().showFillDialog(filledId, response, filterText,
mService.getServicePackageName(), mComponentName, serviceIcon, this,
- id, mCompatMode, mPresentationStatsEventLogger);
+ id, mCompatMode, mPresentationStatsEventLogger, mLock);
return true;
}
@@ -5380,9 +5441,10 @@
try {
if (sVerbose) {
- Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds
- + " triggerId: " + saveTriggerId + " saveOnFinish:" + saveOnFinish
- + " flags: " + flags + " hasSaveInfo: " + (saveInfo != null));
+ Slog.v(TAG, "updateTrackedIdsLocked(): trackedViews: " + trackedViews
+ + " fillableIds: " + fillableIds + " triggerId: " + saveTriggerId
+ + " saveOnFinish:" + saveOnFinish + " flags: " + flags
+ + " hasSaveInfo: " + (saveInfo != null));
}
mClient.setTrackedViews(id, toArray(trackedViews), mSaveOnAllViewsInvisible,
saveOnFinish, toArray(fillableIds), saveTriggerId);
@@ -5420,7 +5482,7 @@
* Sets the state of views that failed to autofill.
*/
@GuardedBy("mLock")
- void setViewAutofilled(@NonNull AutofillId id) {
+ void setViewAutofilledLocked(@NonNull AutofillId id) {
if (sVerbose) {
Slog.v(TAG, "View autofilled: " + id);
}
@@ -6603,6 +6665,8 @@
boolean waitingDatasetAuth = false;
boolean hideHighlight = (entryCount == 1
&& dataset.getFieldIds().get(0).equals(mCurrentViewId));
+ // Count how many views are filtered because they are not in current session
+ int numOfViewsFiltered = 0;
for (int i = 0; i < entryCount; i++) {
if (dataset.getFieldValues().get(i) == null) {
continue;
@@ -6615,6 +6679,7 @@
Slog.v(TAG, "Skipping filling view: " +
viewId + " as it isn't part of the current session: " + id);
}
+ numOfViewsFiltered += 1;
continue;
}
ids.add(viewId);
@@ -6628,6 +6693,8 @@
viewState.resetState(ViewState.STATE_WAITING_DATASET_AUTH);
}
}
+ mPresentationStatsEventLogger.maybeSetFilteredFillableViewsCount(
+ numOfViewsFiltered);
if (!ids.isEmpty()) {
if (waitingDatasetAuth) {
mUi.hideFillUi(this);
@@ -6663,7 +6730,7 @@
}
@GuardedBy("mLock")
- private void logAllEvents(@AutofillCommitReason int val) {
+ private void logAllEventsLocked(@AutofillCommitReason int val) {
if (sVerbose) {
Slog.v(TAG, "logAllEvents(" + id + "): commitReason: " + val);
}
@@ -6695,7 +6762,7 @@
if (sVerbose) {
Slog.v(TAG, "destroyLocked for session: " + id);
}
- logAllEvents(COMMIT_REASON_SESSION_DESTROYED);
+ logAllEventsLocked(COMMIT_REASON_SESSION_DESTROYED);
if (mDestroyed) {
return null;
diff --git a/services/autofill/java/com/android/server/autofill/SessionCommittedEventLogger.java b/services/autofill/java/com/android/server/autofill/SessionCommittedEventLogger.java
index 1be8548..8f3c880 100644
--- a/services/autofill/java/com/android/server/autofill/SessionCommittedEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/SessionCommittedEventLogger.java
@@ -94,6 +94,33 @@
}
/**
+ * Set how many save infos there are in current session as long as mEventInternal presents.
+ */
+ public void maybeSetSaveInfoCount(int saveInfoCount) {
+ mEventInternal.ifPresent(event -> {
+ event.mSaveInfoCount = saveInfoCount;
+ });
+ }
+
+ /**
+ * Set how many save data types there are in current session as long as mEventInternal presents.
+ */
+ public void maybeSetSaveDataTypeCount(int saveDataTypeCount) {
+ mEventInternal.ifPresent(event -> {
+ event.mSaveDataTypeCount = saveDataTypeCount;
+ });
+ }
+
+ /**
+ * Set whether last fill response in session has save info as long as mEventInternal presents.
+ */
+ public void maybeSetLastFillResponseHasSaveInfo(boolean lastFillResponseHasSaveInfo) {
+ mEventInternal.ifPresent(event -> {
+ event.mLastFillResponseHasSaveInfo = lastFillResponseHasSaveInfo;
+ });
+ }
+
+ /**
* Log an AUTOFILL_SESSION_COMMITTED event.
*/
public void logAndEndEvent() {
@@ -109,7 +136,10 @@
+ " mRequestCount=" + event.mRequestCount
+ " mCommitReason=" + event.mCommitReason
+ " mSessionDurationMillis=" + event.mSessionDurationMillis
- + " mServiceUid=" + event.mServiceUid);
+ + " mServiceUid=" + event.mServiceUid
+ + " mSaveInfoCount=" + event.mSaveInfoCount
+ + " mSaveDataTypeCount=" + event.mSaveDataTypeCount
+ + " mLastFillResponseHasSaveInfo=" + event.mLastFillResponseHasSaveInfo);
}
FrameworkStatsLog.write(
AUTOFILL_SESSION_COMMITTED,
@@ -118,7 +148,10 @@
event.mRequestCount,
event.mCommitReason,
event.mSessionDurationMillis,
- event.mServiceUid);
+ event.mServiceUid,
+ event.mSaveInfoCount,
+ event.mSaveDataTypeCount,
+ event.mLastFillResponseHasSaveInfo);
mEventInternal = Optional.empty();
}
@@ -127,6 +160,9 @@
int mRequestCount = 0;
int mCommitReason = COMMIT_REASON_UNKNOWN;
long mSessionDurationMillis = 0;
+ int mSaveInfoCount = -1;
+ int mSaveDataTypeCount = -1;
+ boolean mLastFillResponseHasSaveInfo = false;
int mServiceUid = -1;
SessionCommittedEventInternal() {
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 3b9c54f..8cc666b 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -425,7 +425,8 @@
@Nullable String filterText, @Nullable String servicePackageName,
@NonNull ComponentName componentName, @Nullable Drawable serviceIcon,
@NonNull AutoFillUiCallback callback, int sessionId, boolean compatMode,
- @Nullable PresentationStatsEventLogger mPresentationStatsEventLogger) {
+ @Nullable PresentationStatsEventLogger presentationStatsEventLogger,
+ @NonNull Object sessionLock) {
if (sVerbose) {
Slog.v(TAG, "showFillDialog for "
+ componentName.toShortString() + ": " + response);
@@ -467,9 +468,11 @@
@Override
public void onDatasetPicked(Dataset dataset) {
log(MetricsEvent.TYPE_ACTION);
- if (mPresentationStatsEventLogger != null) {
- mPresentationStatsEventLogger.maybeSetPositiveCtaButtonClicked(
- true);
+ synchronized (sessionLock) {
+ if (presentationStatsEventLogger != null) {
+ presentationStatsEventLogger.maybeSetPositiveCtaButtonClicked(
+ true);
+ }
}
hideFillDialogUiThread(callback);
if (mCallback != null) {
@@ -482,8 +485,10 @@
@Override
public void onDismissed() {
log(MetricsEvent.TYPE_DISMISS);
- if (mPresentationStatsEventLogger != null) {
- mPresentationStatsEventLogger.maybeSetDialogDismissed(true);
+ synchronized (sessionLock) {
+ if (presentationStatsEventLogger != null) {
+ presentationStatsEventLogger.maybeSetDialogDismissed(true);
+ }
}
hideFillDialogUiThread(callback);
callback.requestShowSoftInput(focusedId);
@@ -493,9 +498,11 @@
@Override
public void onCanceled() {
log(MetricsEvent.TYPE_CLOSE);
- if (mPresentationStatsEventLogger != null) {
- mPresentationStatsEventLogger.maybeSetNegativeCtaButtonClicked(
- true);
+ synchronized (sessionLock) {
+ if (presentationStatsEventLogger != null) {
+ presentationStatsEventLogger.maybeSetNegativeCtaButtonClicked(
+ true);
+ }
}
hideFillDialogUiThread(callback);
callback.requestShowSoftInput(focusedId);
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 966478e..eb03709 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -663,6 +663,7 @@
PackageHealthObserverImpact.USER_IMPACT_LEVEL_10,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_20,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_40,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_50,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_71,
@@ -678,6 +679,7 @@
/* Actions having medium user impact, user of a device will likely notice. */
int USER_IMPACT_LEVEL_20 = 20;
int USER_IMPACT_LEVEL_30 = 30;
+ int USER_IMPACT_LEVEL_40 = 40;
int USER_IMPACT_LEVEL_50 = 50;
int USER_IMPACT_LEVEL_70 = 70;
/* Action has high user impact, a last resort, user of a device will be very frustrated. */
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index 37c2d26..189b2495 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -673,7 +673,7 @@
case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET:
return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10;
case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_20;
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_40;
case RESCUE_LEVEL_WARM_REBOOT:
return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50;
case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8dad9ac..5ffab55 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -10237,6 +10237,19 @@
addStartInfoTimestampInternal(key, timestampNs, userId, callingUid);
}
+ @Override
+ public void reportStartInfoViewTimestamps(long renderThreadDrawStartTimeNs,
+ long framePresentedTimeNs) {
+ int callingUid = Binder.getCallingUid();
+ int userId = UserHandle.getUserId(callingUid);
+ addStartInfoTimestampInternal(
+ ApplicationStartInfo.START_TIMESTAMP_INITIAL_RENDERTHREAD_FRAME,
+ renderThreadDrawStartTimeNs, userId, callingUid);
+ addStartInfoTimestampInternal(
+ ApplicationStartInfo.START_TIMESTAMP_SURFACEFLINGER_COMPOSITION_COMPLETE,
+ framePresentedTimeNs, userId, callingUid);
+ }
+
private void addStartInfoTimestampInternal(int key, long timestampNs, int userId, int uid) {
mProcessList.getAppStartInfoTracker().addTimestampToStart(
Settings.getPackageNameForUid(mContext, uid),
@@ -14629,7 +14642,7 @@
final StringBuilder sb = new StringBuilder("registerReceiver: ");
sb.append(Binder.getCallingUid()); sb.append('/');
sb.append(receiverId == null ? "null" : receiverId); sb.append('/');
- final int actionsCount = filter.countActions();
+ final int actionsCount = filter.safeCountActions();
if (actionsCount > 0) {
for (int i = 0; i < actionsCount; ++i) {
sb.append(filter.getAction(i));
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 3042b2a..4a7ad31 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -1195,21 +1195,8 @@
// Records are sorted newest to oldest, grab record at index 0.
ApplicationStartInfo startInfo = mInfos.get(0);
- int startupState = startInfo.getStartupState();
- // If startup state is error then don't accept any further timestamps.
- if (startupState == ApplicationStartInfo.STARTUP_STATE_ERROR) {
- if (DEBUG) Slog.d(TAG, "Startup state is error, not accepting new timestamps.");
- return;
- }
-
- // If startup state is first frame drawn then only accept fully drawn timestamp.
- if (startupState == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN
- && key != ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN) {
- if (DEBUG) {
- Slog.d(TAG, "Startup state is first frame drawn and timestamp is not fully "
- + "drawn, not accepting new timestamps.");
- }
+ if (!isAddTimestampAllowed(startInfo, key, timestampNs)) {
return;
}
@@ -1222,6 +1209,55 @@
}
}
+ private boolean isAddTimestampAllowed(ApplicationStartInfo startInfo, int key,
+ long timestampNs) {
+ int startupState = startInfo.getStartupState();
+
+ // If startup state is error then don't accept any further timestamps.
+ if (startupState == ApplicationStartInfo.STARTUP_STATE_ERROR) {
+ if (DEBUG) Slog.d(TAG, "Startup state is error, not accepting new timestamps.");
+ return false;
+ }
+
+ Map<Integer, Long> timestamps = startInfo.getStartupTimestamps();
+
+ if (startupState == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN) {
+ switch (key) {
+ case ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN:
+ // Allowed, continue to confirm it's not already added.
+ break;
+ case ApplicationStartInfo.START_TIMESTAMP_INITIAL_RENDERTHREAD_FRAME:
+ Long firstFrameTimeNs = timestamps
+ .get(ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME);
+ if (firstFrameTimeNs == null) {
+ // This should never happen. State can't be first frame drawn if first
+ // frame timestamp was not provided.
+ return false;
+ }
+
+ if (timestampNs > firstFrameTimeNs) {
+ // Initial renderthread frame has to occur before first frame.
+ return false;
+ }
+
+ // Allowed, continue to confirm it's not already added.
+ break;
+ case ApplicationStartInfo.START_TIMESTAMP_SURFACEFLINGER_COMPOSITION_COMPLETE:
+ // Allowed, continue to confirm it's not already added.
+ break;
+ default:
+ return false;
+ }
+ }
+
+ if (timestamps.get(key) != null) {
+ // Timestamp should not occur more than once for a given start.
+ return false;
+ }
+
+ return true;
+ }
+
@GuardedBy("mLock")
void dumpLocked(PrintWriter pw, String prefix, SimpleDateFormat sdf) {
if (mMonitoringModeEnabled) {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index e8af82e..3b73e06 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -69,9 +69,7 @@
import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
-import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
-import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL;
import static android.media.audio.Flags.roForegroundAudioControl;
import static android.os.Process.THREAD_GROUP_BACKGROUND;
import static android.os.Process.THREAD_GROUP_DEFAULT;
@@ -2309,14 +2307,8 @@
!= 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0;
if (roForegroundAudioControl()) { // flag check
- // TODO revisit restriction of FOREGROUND_AUDIO_CONTROL when it can be
- // limited to specific FGS types
- //final int fgsAudioType = FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
- // | FOREGROUND_SERVICE_TYPE_CAMERA
- // | FOREGROUND_SERVICE_TYPE_MICROPHONE
- // | FOREGROUND_SERVICE_TYPE_PHONE_CALL;
- //capabilityFromFGS |= (psr.getForegroundServiceTypes() & fgsAudioType) != 0
- // ? PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL : 0;
+ // TODO(b/335373208) - revisit restriction of FOREGROUND_AUDIO_CONTROL
+ // when it can be limited to specific FGS types
capabilityFromFGS |= PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL;
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 3df5687..cbea7aa 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -142,6 +142,7 @@
"arc_next",
"art_mainline",
"art_performance",
+ "attack_tools",
"avic",
"biometrics",
"biometrics_framework",
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 039e7f4..bce1830 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -275,7 +275,7 @@
* Handle BluetoothHeadset intents where the action is one of
* {@link BluetoothHeadset#ACTION_ACTIVE_DEVICE_CHANGED} or
* {@link BluetoothHeadset#ACTION_AUDIO_STATE_CHANGED}.
- * @param intent
+ * @param intent the Intent received from BT service
*/
private void onReceiveBtEvent(@NonNull Intent intent) {
mBtHelper.onReceiveBtEvent(intent);
@@ -296,7 +296,7 @@
/**
* Turns speakerphone on/off
- * @param on
+ * @param on true to enable speakerphone
* @param eventSource for logging purposes
*/
/*package*/ void setSpeakerphoneOn(
@@ -310,21 +310,21 @@
on, BtHelper.SCO_MODE_UNDEFINED, eventSource, isPrivileged));
}
+ private static final long SET_COMMUNICATION_DEVICE_TIMEOUT_MS = 3000;
+
+ /** synchronization for setCommunicationDevice() and getCommunicationDevice */
+ private final Object mCommunicationDeviceLock = new Object();
+ @GuardedBy("mCommunicationDeviceLock")
+ private int mCommunicationDeviceUpdateCount = 0;
+
/**
* Select device for use for communication use cases.
* @param cb Client binder for death detection
* @param uid Client uid
* @param device Device selected or null to unselect.
* @param eventSource for logging purposes
+ * @return false if there is no device and no communication client
*/
-
- private static final long SET_COMMUNICATION_DEVICE_TIMEOUT_MS = 3000;
-
- /** synchronization for setCommunicationDevice() and getCommunicationDevice */
- private Object mCommunicationDeviceLock = new Object();
- @GuardedBy("mCommunicationDeviceLock")
- private int mCommunicationDeviceUpdateCount = 0;
-
/*package*/ boolean setCommunicationDevice(IBinder cb, int uid, AudioDeviceInfo device,
boolean isPrivileged, String eventSource) {
@@ -355,7 +355,6 @@
* Sets or resets the communication device for matching client. If no client matches and the
* request is to reset for a given device (deviceInfo.mOn == false), the method is a noop.
* @param deviceInfo information on the device and requester {@link #CommunicationDeviceInfo}
- * @return true if the communication device is set or reset
*/
@GuardedBy("mDeviceStateLock")
/*package*/ void onSetCommunicationDeviceForClient(CommunicationDeviceInfo deviceInfo) {
@@ -742,15 +741,6 @@
}
/**
- * Helper method on top of isDeviceRequestedForCommunication() indicating if
- * speakerphone ON is currently requested or not.
- * @return true if speakerphone ON requested, false otherwise.
- */
- private boolean isSpeakerphoneRequested() {
- return isDeviceRequestedForCommunication(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
- }
-
- /**
* Indicates if preferred route selection for communication is speakerphone.
* @return true if speakerphone is active, false otherwise.
*/
@@ -929,6 +919,12 @@
}
@Override
+ public int hashCode() {
+ // only hashing on the fields used in equals()
+ return Objects.hash(mProfile, mDevice);
+ }
+
+ @Override
public String toString() {
return "BtDeviceInfo: device=" + mDevice.toString()
+ " state=" + mState
@@ -990,7 +986,7 @@
/**
* will block on mDeviceStateLock, which is held during an A2DP (dis) connection
* not just a simple message post
- * @param info struct with the (dis)connection information
+ * @param data struct with the (dis)connection information
*/
/*package*/ void queueOnBluetoothActiveDeviceChanged(@NonNull BtDeviceChangedData data) {
if (data.mPreviousDevice != null
@@ -1379,6 +1375,7 @@
mCommDevDispatchers.getBroadcastItem(i)
.dispatchCommunicationDeviceChanged(portId);
} catch (RemoteException e) {
+ Log.e(TAG, "dispatchCommunicationDevice error", e);
}
}
mCommDevDispatchers.finishBroadcast();
@@ -1581,6 +1578,12 @@
}
@Override
+ public int hashCode() {
+ // only hashing on the fields used in equals()
+ return Objects.hash(mCb.hashCode(), mUid);
+ }
+
+ @Override
public String toString() {
return "CommunicationDeviceInfo mCb=" + mCb.toString()
+ " mUid=" + mUid
@@ -1600,9 +1603,9 @@
//@GuardedBy("mConnectedDevices")
/*package*/ void setBluetoothA2dpOnInt(boolean on, boolean fromA2dp, String source) {
// for logging only
- final String eventSource = new StringBuilder("setBluetoothA2dpOn(").append(on)
- .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
- .append(Binder.getCallingPid()).append(" src:").append(source).toString();
+ final String eventSource = "setBluetoothA2dpOn(" + on
+ + ") from u/pid:" + Binder.getCallingUid() + "/"
+ + Binder.getCallingPid() + " src:" + source;
mBluetoothA2dpEnabled.set(on);
onSetForceUse(
@@ -2203,10 +2206,6 @@
sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, delay);
}
- private void sendIMsg(int msg, int existingMsgPolicy, int arg, int delay) {
- sendIILMsg(msg, existingMsgPolicy, arg, 0, null, delay);
- }
-
private void sendMsgNoDelay(int msg, int existingMsgPolicy) {
sendIILMsg(msg, existingMsgPolicy, 0, 0, null, 0);
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 287c92f..ba7aee0 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -449,7 +449,7 @@
@Override
public DeviceInfo put(String key, DeviceInfo value) {
final DeviceInfo result = super.put(key, value);
- record("put", true /* connected */, key, value);
+ record("put", true /* connected */, value);
return result;
}
@@ -457,7 +457,7 @@
public DeviceInfo putIfAbsent(String key, DeviceInfo value) {
final DeviceInfo result = super.putIfAbsent(key, value);
if (result == null) {
- record("putIfAbsent", true /* connected */, key, value);
+ record("putIfAbsent", true /* connected */, value);
}
return result;
}
@@ -466,7 +466,7 @@
public DeviceInfo remove(Object key) {
final DeviceInfo result = super.remove(key);
if (result != null) {
- record("remove", false /* connected */, (String) key, result);
+ record("remove", false /* connected */, result);
}
return result;
}
@@ -475,7 +475,7 @@
public boolean remove(Object key, Object value) {
final boolean result = super.remove(key, value);
if (result) {
- record("remove", false /* connected */, (String) key, (DeviceInfo) value);
+ record("remove", false /* connected */, (DeviceInfo) value);
}
return result;
}
@@ -489,7 +489,7 @@
// putAll
// replace
// replaceAll
- private void record(String event, boolean connected, String key, DeviceInfo value) {
+ private void record(String event, boolean connected, DeviceInfo value) {
// DeviceInfo - int mDeviceType;
// DeviceInfo - int mDeviceCodecFormat;
new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE
@@ -662,7 +662,7 @@
/**
* A class just for packaging up a set of connection parameters.
*/
- /*package*/ class WiredDeviceConnectionState {
+ /*package*/ static class WiredDeviceConnectionState {
public final AudioDeviceAttributes mAttributes;
public final @AudioService.ConnectionState int mState;
public final String mCaller;
@@ -1054,7 +1054,9 @@
IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(n);
try {
obs.dispatchAudioRoutesChanged(routes);
- } catch (RemoteException e) { }
+ } catch (RemoteException e) {
+ Log.e(TAG, "onReportNewRoutes", e);
+ }
}
}
mRoutesObservers.finishBroadcast();
@@ -1835,7 +1837,8 @@
.set(MediaMetrics.Property.EVENT, "disconnectHearingAid")
.record();
if (toRemove.size() > 0) {
- final int delay = checkSendBecomingNoisyIntentInt(DEVICE_OUT_HEARING_AID,
+ /*final int delay = */
+ checkSendBecomingNoisyIntentInt(DEVICE_OUT_HEARING_AID,
AudioService.CONNECTION_STATE_DISCONNECTED, AudioSystem.DEVICE_NONE);
toRemove.stream().forEach(deviceAddress ->
// TODO delay not used?
@@ -2697,10 +2700,6 @@
private static final String CONNECT_INTENT_KEY_PORT_NAME = "portName";
private static final String CONNECT_INTENT_KEY_STATE = "state";
private static final String CONNECT_INTENT_KEY_ADDRESS = "address";
- private static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback";
- private static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture";
- private static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI";
- private static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class";
private void sendDeviceConnectionIntent(int device, int state, String address,
String deviceName) {
@@ -2863,6 +2862,7 @@
mPrefDevDispatchers.getBroadcastItem(i).dispatchPrefDevicesChanged(
strategy, devices);
} catch (RemoteException e) {
+ Log.e(TAG, "dispatchPreferredDevice ", e);
}
}
mPrefDevDispatchers.finishBroadcast();
@@ -2879,6 +2879,7 @@
mNonDefDevDispatchers.getBroadcastItem(i).dispatchNonDefDevicesChanged(
strategy, devices);
} catch (RemoteException e) {
+ Log.e(TAG, "dispatchNonDefaultDevice ", e);
}
}
mNonDefDevDispatchers.finishBroadcast();
@@ -2895,6 +2896,7 @@
mDevRoleCapturePresetDispatchers.getBroadcastItem(i).dispatchDevicesRoleChanged(
capturePreset, role, devices);
} catch (RemoteException e) {
+ Log.e(TAG, "dispatchDevicesRoleForCapturePreset ", e);
}
}
mDevRoleCapturePresetDispatchers.finishBroadcast();
@@ -2960,7 +2962,7 @@
/**
* Check if device is in the list of connected devices
- * @param device
+ * @param device the device to query
* @return true if connected
*/
@VisibleForTesting
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 1949e6f..7b5cff7 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -54,7 +54,6 @@
import com.android.internal.os.BackgroundThread;
import com.android.server.EventLogTags;
import com.android.server.display.brightness.BrightnessEvent;
-import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.display.config.HysteresisLevels;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -256,7 +255,6 @@
// Controls Brightness range (including High Brightness Mode).
private final BrightnessRangeController mBrightnessRangeController;
- private final BrightnessClamperController mBrightnessClamperController;
// Throttles (caps) maximum allowed brightness
private final BrightnessThrottler mBrightnessThrottler;
@@ -295,7 +293,6 @@
BrightnessRangeController brightnessModeController,
BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
int ambientLightHorizonLong, float userLux, float userNits,
- BrightnessClamperController brightnessClamperController,
DisplayManagerFlags displayManagerFlags) {
this(new Injector(), callbacks, looper, sensorManager, lightSensor,
brightnessMappingStrategyMap, lightSensorWarmUpTime, brightnessMin, brightnessMax,
@@ -306,7 +303,7 @@
screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
screenBrightnessThresholdsIdle, context, brightnessModeController,
brightnessThrottler, ambientLightHorizonShort, ambientLightHorizonLong, userLux,
- userNits, brightnessClamperController, displayManagerFlags
+ userNits, displayManagerFlags
);
}
@@ -325,7 +322,6 @@
BrightnessRangeController brightnessRangeController,
BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
int ambientLightHorizonLong, float userLux, float userNits,
- BrightnessClamperController brightnessClamperController,
DisplayManagerFlags displayManagerFlags) {
mInjector = injector;
mClock = injector.createClock(displayManagerFlags.offloadControlsDozeAutoBrightness());
@@ -370,7 +366,6 @@
mForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
mBrightnessRangeController = brightnessRangeController;
- mBrightnessClamperController = brightnessClamperController;
mBrightnessThrottler = brightnessThrottler;
mBrightnessMappingStrategyMap = brightnessMappingStrategyMap;
mDisplayManagerFlags = displayManagerFlags;
@@ -771,7 +766,6 @@
mAmbientBrightnessThresholds.getDarkeningThreshold(lux);
}
mBrightnessRangeController.onAmbientLuxChange(mAmbientLux);
- mBrightnessClamperController.onAmbientLuxChange(mAmbientLux);
// If the short term model was invalidated and the change is drastic enough, reset it.
mShortTermModel.maybeReset(mAmbientLux);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 195a516..65a729a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -587,7 +587,7 @@
mUniqueDisplayId,
mThermalBrightnessThrottlingDataId,
logicalDisplay.getPowerThrottlingDataIdLocked(),
- mDisplayDeviceConfig), mContext, flags);
+ mDisplayDeviceConfig), mContext, flags, mSensorManager);
// Seed the cached brightness
saveBrightnessInfo(getScreenBrightnessSetting());
mAutomaticBrightnessStrategy =
@@ -1422,7 +1422,6 @@
: AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
mBrightnessRangeController.setAutoBrightnessEnabled(autoBrightnessState);
- mBrightnessClamperController.setAutoBrightnessState(autoBrightnessState);
boolean updateScreenBrightnessSetting =
displayBrightnessState.shouldUpdateScreenBrightnessSetting();
@@ -1549,7 +1548,7 @@
// we broadcast this change through setting.
final float unthrottledBrightnessState = brightnessState;
DisplayBrightnessState clampedState = mBrightnessClamperController.clamp(mPowerRequest,
- brightnessState, slowChange);
+ brightnessState, slowChange, /* displayState= */ state);
brightnessState = clampedState.getBrightness();
slowChange = clampedState.isSlowChange();
@@ -2478,7 +2477,6 @@
public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux,
boolean slowChange) {
mBrightnessRangeController.onAmbientLuxChange(ambientLux);
- mBrightnessClamperController.onAmbientLuxChange(ambientLux);
if (nits == BrightnessMappingStrategy.INVALID_NITS) {
mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness, slowChange);
} else {
@@ -3194,7 +3192,7 @@
screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
screenBrightnessThresholdsIdle, context, brightnessModeController,
brightnessThrottler, ambientLightHorizonShort, ambientLightHorizonLong, userLux,
- userNits, brightnessClamperController, displayManagerFlags);
+ userNits, displayManagerFlags);
}
BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
@@ -3243,10 +3241,10 @@
BrightnessClamperController getBrightnessClamperController(Handler handler,
BrightnessClamperController.ClamperChangeListener clamperChangeListener,
BrightnessClamperController.DisplayDeviceData data, Context context,
- DisplayManagerFlags flags) {
+ DisplayManagerFlags flags, SensorManager sensorManager) {
return new BrightnessClamperController(handler, clamperChangeListener, data, context,
- flags);
+ flags, sensorManager);
}
DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 11ef577..101ad30 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -16,21 +16,30 @@
package com.android.server.display.brightness.clamper;
+import static android.view.Display.STATE_ON;
+
import static com.android.server.display.brightness.clamper.BrightnessClamper.Type;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
import android.hardware.display.BrightnessInfo;
import android.hardware.display.DisplayManagerInternal;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.PowerManager;
+import android.os.SystemClock;
import android.provider.DeviceConfig;
import android.provider.DeviceConfigInterface;
import android.util.IndentingPrintWriter;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.DisplayDeviceConfig;
@@ -41,20 +50,30 @@
import com.android.server.display.config.SensorData;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.display.feature.DisplayManagerFlags;
+import com.android.server.display.utils.AmbientFilter;
+import com.android.server.display.utils.AmbientFilterFactory;
+import com.android.server.display.utils.DebugUtils;
+import com.android.server.display.utils.SensorUtils;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
/**
* Clampers controller, all in DisplayControllerHandler
*/
public class BrightnessClamperController {
private static final String TAG = "BrightnessClamperController";
+ // To enable these logs, run:
+ // 'adb shell setprop persist.log.tag.BrightnessClamperController DEBUG && adb reboot'
+ private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
+ public static final float INVALID_LUX = -1f;
private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
private final Handler mHandler;
+ private final SensorManager mSensorManager;
private final ClamperChangeListener mClamperChangeListenerExternal;
private final Executor mExecutor;
private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers;
@@ -66,24 +85,55 @@
private float mCustomAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
@Nullable
private Type mClamperType = null;
- private int mAutoBrightnessState = -1;
+ private final SensorEventListener mLightSensorListener;
+ private Sensor mRegisteredLightSensor = null;
+ private Sensor mLightSensor;
+ private String mLightSensorType;
+ private String mLightSensorName;
+ private AmbientFilter mAmbientFilter;
+ private final DisplayDeviceConfig mDisplayDeviceConfig;
+ private final Resources mResources;
+ private final int mLightSensorRate;
+ private final Injector mInjector;
private boolean mClamperApplied = false;
public BrightnessClamperController(Handler handler,
ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context,
- DisplayManagerFlags flags) {
- this(new Injector(), handler, clamperChangeListener, data, context, flags);
+ DisplayManagerFlags flags, SensorManager sensorManager) {
+ this(null, handler, clamperChangeListener, data, context, flags, sensorManager);
}
@VisibleForTesting
BrightnessClamperController(Injector injector, Handler handler,
ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context,
- DisplayManagerFlags flags) {
- mDeviceConfigParameterProvider = injector.getDeviceConfigParameterProvider();
+ DisplayManagerFlags flags, SensorManager sensorManager) {
+ mInjector = injector == null ? new Injector() : injector;
+ mDeviceConfigParameterProvider = mInjector.getDeviceConfigParameterProvider();
mHandler = handler;
+ mSensorManager = sensorManager;
+ mDisplayDeviceConfig = data.mDisplayDeviceConfig;
+ mLightSensorListener = new SensorEventListener() {
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ long now = SystemClock.elapsedRealtime();
+ mAmbientFilter.addValue(TimeUnit.NANOSECONDS.toMillis(event.timestamp),
+ event.values[0]);
+ final float lux = mAmbientFilter.getEstimate(now);
+ mModifiers.forEach(mModifier -> mModifier.setAmbientLux(lux));
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ // unused
+ }
+ };
+
mClamperChangeListenerExternal = clamperChangeListener;
mExecutor = new HandlerExecutor(handler);
+ mResources = context.getResources();
+ mLightSensorRate = context.getResources().getInteger(
+ R.integer.config_autoBrightnessLightSensorRate);
Runnable clamperChangeRunnableInternal = this::recalculateBrightnessCap;
@@ -93,10 +143,10 @@
}
};
- mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags,
+ mClampers = mInjector.getClampers(handler, clamperChangeListenerInternal, data, flags,
context);
- mModifiers = injector.getModifiers(flags, context, handler, clamperChangeListener,
- data.mDisplayDeviceConfig);
+ mModifiers = mInjector.getModifiers(flags, context, handler, clamperChangeListener,
+ data.mDisplayDeviceConfig, mSensorManager);
mOnPropertiesChangedListener =
properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
start();
@@ -114,7 +164,7 @@
* Called in DisplayControllerHandler
*/
public DisplayBrightnessState clamp(DisplayManagerInternal.DisplayPowerRequest request,
- float brightnessValue, boolean slowChange) {
+ float brightnessValue, boolean slowChange, int displayState) {
float cappedBrightness = Math.min(brightnessValue, mBrightnessCap);
DisplayBrightnessState.Builder builder = DisplayBrightnessState.builder();
@@ -133,6 +183,12 @@
mClamperApplied = false;
}
+ if (displayState != STATE_ON) {
+ unregisterSensorListener();
+ } else {
+ maybeRegisterLightSensor();
+ }
+
for (int i = 0; i < mModifiers.size(); i++) {
mModifiers.get(i).apply(request, builder);
}
@@ -175,6 +231,8 @@
writer.println(" mBrightnessCap: " + mBrightnessCap);
writer.println(" mClamperType: " + mClamperType);
writer.println(" mClamperApplied: " + mClamperApplied);
+ writer.println(" mLightSensor=" + mLightSensor);
+ writer.println(" mRegisteredLightSensor=" + mRegisteredLightSensor);
IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
mClampers.forEach(clamper -> clamper.dump(ipw));
mModifiers.forEach(modifier -> modifier.dump(ipw));
@@ -191,26 +249,6 @@
mModifiers.forEach(BrightnessStateModifier::stop);
}
- /**
- * Notifies modifiers that ambient lux has changed.
- * @param ambientLux current lux, debounced
- */
- public void onAmbientLuxChange(float ambientLux) {
- mModifiers.forEach(modifier -> modifier.onAmbientLuxChange(ambientLux));
- }
-
- /**
- * Sets the autobrightness state for clampers that need to be aware of the state.
- * @param state autobrightness state
- */
- public void setAutoBrightnessState(int state) {
- if (state == mAutoBrightnessState) {
- return;
- }
- mModifiers.forEach(modifier -> modifier.setAutoBrightnessState(state));
- mAutoBrightnessState = state;
- recalculateBrightnessCap();
- }
// Called in DisplayControllerHandler
private void recalculateBrightnessCap() {
@@ -243,6 +281,10 @@
if (!mClampers.isEmpty()) {
mDeviceConfigParameterProvider.addOnPropertiesChangedListener(
mExecutor, mOnPropertiesChangedListener);
+ reloadLightSensorData(mDisplayDeviceConfig);
+ mLightSensor = mInjector.getLightSensor(
+ mSensorManager, mLightSensorType, mLightSensorName);
+ maybeRegisterLightSensor();
}
}
@@ -281,7 +323,7 @@
List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context,
Handler handler, ClamperChangeListener listener,
- DisplayDeviceConfig displayDeviceConfig) {
+ DisplayDeviceConfig displayDeviceConfig, SensorManager sensorManager) {
List<BrightnessStateModifier> modifiers = new ArrayList<>();
modifiers.add(new DisplayDimModifier(context));
modifiers.add(new BrightnessLowPowerModeModifier());
@@ -292,13 +334,19 @@
}
return modifiers;
}
+
+ Sensor getLightSensor(SensorManager sensorManager, String type, String name) {
+ return SensorUtils.findSensor(sensorManager, type,
+ name, Sensor.TYPE_LIGHT);
+ }
+
}
/**
* Config Data for clampers
*/
public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData,
- BrightnessPowerClamper.PowerData,
+ BrightnessPowerClamper.PowerData,
BrightnessWearBedtimeModeClamper.WearBedtimeModeData {
@NonNull
private final String mUniqueDisplayId;
@@ -368,4 +416,51 @@
return mDisplayDeviceConfig.getTempSensor();
}
}
+
+ private void maybeRegisterLightSensor() {
+ if (mModifiers.stream().noneMatch(BrightnessStateModifier::shouldListenToLightSensor)) {
+ return;
+ }
+
+ if (mRegisteredLightSensor == mLightSensor) {
+ return;
+ }
+
+ if (mRegisteredLightSensor != null) {
+ unregisterSensorListener();
+ }
+
+ mAmbientFilter = AmbientFilterFactory.createBrightnessFilter(TAG, mResources);
+ mSensorManager.registerListener(mLightSensorListener,
+ mLightSensor, mLightSensorRate * 1000, mHandler);
+ mRegisteredLightSensor = mLightSensor;
+
+ if (DEBUG) {
+ Slog.d(TAG, "maybeRegisterLightSensor");
+ }
+ }
+
+ private void unregisterSensorListener() {
+ mSensorManager.unregisterListener(mLightSensorListener);
+ mRegisteredLightSensor = null;
+ mModifiers.forEach(mModifier -> mModifier.setAmbientLux(INVALID_LUX)); // set lux to invalid
+ if (DEBUG) {
+ Slog.d(TAG, "unregisterSensorListener");
+ }
+ }
+
+ private void reloadLightSensorData(DisplayDeviceConfig displayDeviceConfig) {
+ // The displayDeviceConfig (ddc) contains display specific preferences. When loaded,
+ // it naturally falls back to the global config.xml.
+ if (displayDeviceConfig != null
+ && displayDeviceConfig.getAmbientLightSensor() != null) {
+ // This covers both the ddc and the config.xml fallback
+ mLightSensorType = displayDeviceConfig.getAmbientLightSensor().type;
+ mLightSensorName = displayDeviceConfig.getAmbientLightSensor().name;
+ } else if (mLightSensorName == null && mLightSensorType == null) {
+ mLightSensorType = mResources.getString(
+ com.android.internal.R.string.config_displayLightSensorType);
+ mLightSensorName = "";
+ }
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
index 7ba4a4d..951980a 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
@@ -16,7 +16,6 @@
package com.android.server.display.brightness.clamper;
-import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
import android.content.ContentResolver;
import android.content.Context;
@@ -30,6 +29,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.display.BrightnessMappingStrategy;
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.DisplayDeviceConfig;
import com.android.server.display.brightness.BrightnessReason;
@@ -56,7 +56,6 @@
private float mBrightnessLowerBound;
private float mMinNitsAllowed;
private boolean mIsActive;
- private boolean mAutoBrightnessEnabled;
private float mAmbientLux;
private final DisplayDeviceConfig mDisplayDeviceConfig;
@@ -87,15 +86,15 @@
mContentResolver, Settings.Secure.EVEN_DIMMER_MIN_NITS,
/* def= */ MIN_NITS_DEFAULT, userId);
- boolean isActive = isSettingEnabled() && mAutoBrightnessEnabled;
-
- float luxBasedNitsLowerBound = mDisplayDeviceConfig.getMinNitsFromLux(mAmbientLux);
+ boolean isActive = isSettingEnabled()
+ && mAmbientLux != BrightnessMappingStrategy.INVALID_LUX;
final int reason;
float minNitsAllowed = -1f; // undefined, if setting is off.
final float minBrightnessAllowed;
if (isActive) {
+ float luxBasedNitsLowerBound = mDisplayDeviceConfig.getMinNitsFromLux(mAmbientLux);
minNitsAllowed = Math.max(settingNitsLowerBound,
luxBasedNitsLowerBound);
minBrightnessAllowed = getBrightnessFromNits(minNitsAllowed);
@@ -127,6 +126,12 @@
}
@VisibleForTesting
+ public void setAmbientLux(float lux) {
+ mAmbientLux = lux;
+ recalculateLowerBound();
+ }
+
+ @VisibleForTesting
public boolean isActive() {
return mIsActive;
}
@@ -164,10 +169,10 @@
@Override
public void apply(DisplayManagerInternal.DisplayPowerRequest request,
DisplayBrightnessState.Builder stateBuilder) {
+
stateBuilder.setMinBrightness(mBrightnessLowerBound);
float boundedBrightness = Math.max(mBrightnessLowerBound, stateBuilder.getBrightness());
stateBuilder.setBrightness(boundedBrightness);
-
if (BrightnessSynchronizer.floatEquals(stateBuilder.getBrightness(),
mBrightnessLowerBound)) {
stateBuilder.getBrightnessReason().addModifier(mReason);
@@ -180,14 +185,8 @@
}
@Override
- public void onAmbientLuxChange(float ambientLux) {
- mAmbientLux = ambientLux;
- recalculateLowerBound();
- }
-
- @Override
- public void setAutoBrightnessState(int state) {
- mAutoBrightnessEnabled = state == AUTO_BRIGHTNESS_ENABLED;
+ public boolean shouldListenToLightSensor() {
+ return isSettingEnabled();
}
@Override
@@ -217,6 +216,7 @@
}
private final class SettingsObserver extends ContentObserver {
+
SettingsObserver(Handler handler) {
super(handler);
mContentResolver.registerContentObserver(
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifier.java
index b478952..5661ede 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifier.java
@@ -51,4 +51,14 @@
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
super.dump(ipw);
}
+
+ @Override
+ public boolean shouldListenToLightSensor() {
+ return false;
+ }
+
+ @Override
+ public void setAmbientLux(float lux) {
+ // unused
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
index db5a524d..be8fa5a 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
@@ -68,14 +68,4 @@
public void stop() {
// do nothing
}
-
- @Override
- public void onAmbientLuxChange(float ambientLux) {
- // do nothing
- }
-
- @Override
- public void setAutoBrightnessState(int state) {
- // do nothing
- }
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessStateModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessStateModifier.java
index 1606159c..fd40ce3 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessStateModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessStateModifier.java
@@ -44,14 +44,15 @@
void stop();
/**
- * Allows modifiers to react to ambient lux changes.
- * @param ambientLux current debounced lux.
+ *
+ * @return whether the brightness state modifier needs to listen to the ambient lux in order to
+ * calculate its bounds.
*/
- void onAmbientLuxChange(float ambientLux);
+ boolean shouldListenToLightSensor();
/**
- * Sets the autobrightness state for clampers that need to be aware of the state.
- * @param state autobrightness state
+ * Current ambient lux
+ * @param lux - ambient lux
*/
- void setAutoBrightnessState(int state);
+ void setAmbientLux(float lux);
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java b/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java
index 4ff7bdb..ab880bf 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java
@@ -76,4 +76,14 @@
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
super.dump(ipw);
}
+
+ @Override
+ public boolean shouldListenToLightSensor() {
+ return false;
+ }
+
+ @Override
+ public void setAmbientLux(float lux) {
+ // unused
+ }
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 9e8bf0e..69452310 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -195,6 +195,18 @@
}
/**
+ * Returns {@link InputMethodInfo} that is queried from {@link #getSelectedMethodId()}.
+ *
+ * @return {@link InputMethodInfo} whose IME ID is the same as {@link #getSelectedMethodId()}.
+ * {@code null} otherwise
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ InputMethodInfo getSelectedMethod() {
+ return InputMethodSettingsRepository.get(mUserId).getMethodMap().get(mSelectedMethodId);
+ }
+
+ /**
* The token we have made for the currently active input method, to
* identify it in the future.
*/
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index f5bec85..99036c6 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -486,10 +486,17 @@
@GuardedBy("ImfLock.class")
@NonNull
- InputMethodBindingController getInputMethodBindingController(@UserIdInt int userId) {
- return mUserDataRepository.getOrCreate(userId).mBindingController;
+ UserDataRepository.UserData getUserData(@UserIdInt int userId) {
+ return mUserDataRepository.getOrCreate(userId);
}
+ @GuardedBy("ImfLock.class")
+ @NonNull
+ InputMethodBindingController getInputMethodBindingController(@UserIdInt int userId) {
+ return getUserData(userId).mBindingController;
+ }
+
+
/**
* Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method.
* This is to be synchronized with the secure settings keyed with
@@ -1168,7 +1175,7 @@
final int userId = user.getUserIdentifier();
SecureSettingsWrapper.onUserStarting(userId);
synchronized (ImfLock.class) {
- mService.mUserDataRepository.getOrCreate(userId);
+ mService.getUserData(userId);
if (mService.mExperimentalConcurrentMultiUserModeEnabled) {
if (mService.mCurrentUserId != userId && mService.mSystemReady) {
mService.experimentalInitializeVisibleBackgroundUserLocked(userId);
@@ -1290,7 +1297,7 @@
bindingControllerForTesting != null ? bindingControllerForTesting
: bindingControllerFactory);
for (int id : mUserManagerInternal.getUserIds()) {
- mUserDataRepository.getOrCreate(id);
+ getUserData(id);
}
final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
@@ -2489,8 +2496,7 @@
@GuardedBy("ImfLock.class")
void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) {
- final var bindingController =
- mUserDataRepository.getOrCreate(mCurrentUserId).mBindingController;
+ final var bindingController = getInputMethodBindingController(mCurrentUserId);
bindingController.setSelectedMethodId(null);
// Callback before clean-up binding states.
@@ -3084,8 +3090,9 @@
throw getExceptionForUnknownImeId(id);
}
+ final var bindingController = getInputMethodBindingController(userId);
// See if we need to notify a subtype change within the same IME.
- if (id.equals(getSelectedMethodIdLocked())) {
+ if (id.equals(bindingController.getSelectedMethodId())) {
final int subtypeCount = info.getSubtypeCount();
if (subtypeCount <= 0) {
notifyInputMethodSubtypeChangedLocked(userId, info, null);
@@ -3120,7 +3127,6 @@
return;
}
- final var bindingController = getInputMethodBindingController(userId);
// Changing to a different IME.
if (bindingController.getDeviceIdToShowIme() != DEVICE_ID_DEFAULT
&& deviceId == DEVICE_ID_DEFAULT) {
@@ -3143,7 +3149,7 @@
// mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
// because mCurMethodId is stored as a history in
// setSelectedInputMethodAndSubtypeLocked().
- getInputMethodBindingController(userId).setSelectedMethodId(id);
+ bindingController.setSelectedMethodId(id);
if (mActivityManagerInternal.isSystemReady()) {
Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
@@ -4186,10 +4192,10 @@
@GuardedBy("ImfLock.class")
private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme) {
- final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ final int userId = mCurrentUserId;
+ final var currentImi = getInputMethodBindingController(userId).getSelectedMethod();
final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
- onlyCurrentIme, settings.getMethodMap().get(getSelectedMethodIdLocked()),
- mCurrentSubtype);
+ onlyCurrentIme, currentImi, mCurrentSubtype);
if (nextSubtype == null) {
return false;
}
@@ -4204,10 +4210,10 @@
if (!calledWithValidTokenLocked(token)) {
return false;
}
- final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ final int userId = mCurrentUserId;
+ final var currentImi = getInputMethodBindingController(userId).getSelectedMethod();
final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
- false /* onlyCurrentIme */,
- settings.getMethodMap().get(getSelectedMethodIdLocked()), mCurrentSubtype);
+ false /* onlyCurrentIme */, currentImi, mCurrentSubtype);
return nextSubtype != null;
}
}
@@ -4642,8 +4648,7 @@
if (mCurrentUserId != mSwitchingController.getUserId()) {
return;
}
- final InputMethodInfo imi = InputMethodSettingsRepository.get(mCurrentUserId)
- .getMethodMap().get(getSelectedMethodIdLocked());
+ final var imi = getInputMethodBindingController(mCurrentUserId).getSelectedMethod();
if (imi != null) {
mSwitchingController.onUserActionLocked(imi, mCurrentSubtype);
}
@@ -5900,27 +5905,36 @@
synchronized (ImfLock.class) {
final int uid = Binder.getCallingUid();
- if (getSelectedMethodIdLocked() == null) {
+ final int imeUserId = UserHandle.getUserId(uid);
+ if (imeUserId != mCurrentUserId) {
+ // Currently concurrent multi-user is not supported here due to the remaining
+ // dependency on mCurEditorInfo and mCurClient.
+ // TODO(b/341558132): Remove this early-exit once it becomes multi-user ready.
+ Slog.i(TAG, "Ignoring createInputContentUriToken due to user ID mismatch."
+ + " imeUserId=" + imeUserId + " mCurrentUserId=" + mCurrentUserId);
return null;
}
- if (getCurTokenLocked() != token) {
- Slog.e(TAG, "Ignoring createInputContentUriToken mCurToken=" + getCurTokenLocked()
- + " token=" + token);
+ final var bindingController = getInputMethodBindingController(imeUserId);
+ if (bindingController.getSelectedMethodId() == null) {
+ return null;
+ }
+ if (bindingController.getCurToken() != token) {
+ Slog.e(TAG, "Ignoring createInputContentUriToken mCurToken="
+ + bindingController.getCurToken() + " token=" + token);
return null;
}
// We cannot simply distinguish a bad IME that reports an arbitrary package name from
// an unfortunate IME whose internal state is already obsolete due to the asynchronous
// nature of our system. Let's compare it with our internal record.
- final var curPackageName = mCurEditorInfo != null
- ? mCurEditorInfo.packageName : null;
+ // TODO(b/341558132): Use "imeUserId" to query per-user "curEditorInfo"
+ final var curPackageName = mCurEditorInfo != null ? mCurEditorInfo.packageName : null;
if (!TextUtils.equals(curPackageName, packageName)) {
Slog.e(TAG, "Ignoring createInputContentUriToken mCurEditorInfo.packageName="
+ curPackageName + " packageName=" + packageName);
return null;
}
- // This user ID can never bee spoofed.
- final int imeUserId = UserHandle.getUserId(uid);
- // This user ID can never bee spoofed.
+ // This user ID can never be spoofed.
+ // TODO(b/341558132): Use "imeUserId" to query per-user "curClient"
final int appUserId = UserHandle.getUserId(mCurClient.mUid);
// This user ID may be invalid if "contentUri" embedded an invalid user ID.
final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri(contentUri,
@@ -6109,7 +6123,7 @@
p.println(" mStylusIds=" + (mStylusIds != null
? Arrays.toString(mStylusIds.toArray()) : ""));
p.println(" mSwitchingController:");
- mSwitchingController.dump(p);
+ mSwitchingController.dump(p, " ");
p.println(" mStartInputHistory:");
mStartInputHistory.dump(pw, " ");
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index 23f947c..770e3b2 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -465,11 +465,11 @@
return result;
}
- protected void dump(final Printer pw) {
- pw.println(" mSwitchingAwareRotationList:");
- mSwitchingAwareRotationList.dump(pw, " ");
- pw.println(" mSwitchingUnawareRotationList:");
- mSwitchingUnawareRotationList.dump(pw, " ");
+ protected void dump(@NonNull Printer pw, @NonNull String prefix) {
+ pw.println(prefix + "mSwitchingAwareRotationList:");
+ mSwitchingAwareRotationList.dump(pw, prefix + " ");
+ pw.println(prefix + "mSwitchingUnawareRotationList:");
+ mSwitchingUnawareRotationList.dump(pw, prefix + " ");
}
}
@@ -529,11 +529,11 @@
return mController.getNextInputMethod(onlyCurrentIme, imi, subtype);
}
- public void dump(final Printer pw) {
+ public void dump(@NonNull Printer pw, @NonNull String prefix) {
if (mController != null) {
- mController.dump(pw);
+ mController.dump(pw, prefix);
} else {
- pw.println(" mController=null");
+ pw.println(prefix + "mController=null");
}
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 60147ef..a8adc06 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -284,6 +284,7 @@
import android.service.notification.NotificationServiceDumpProto;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
+import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeProto;
import android.service.notification.ZenPolicy;
@@ -5508,6 +5509,14 @@
}
@Override
+ public void setManualZenRuleDeviceEffects(ZenDeviceEffects effects) throws RemoteException {
+ checkCallerIsSystem();
+
+ mZenModeHelper.setManualZenRuleDeviceEffects(effects, computeZenOrigin(true),
+ "Update manual mode non-policy settings", Binder.getCallingUid());
+ }
+
+ @Override
public boolean updateAutomaticZenRule(String id, AutomaticZenRule automaticZenRule,
boolean fromUser) throws RemoteException {
validateAutomaticZenRule(id, automaticZenRule);
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
index 86aa2d8..02b5f97 100644
--- a/services/core/java/com/android/server/notification/ZenModeConditions.java
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -68,16 +68,17 @@
public void evaluateConfig(ZenModeConfig config, ComponentName trigger,
boolean processSubscriptions) {
if (config == null) return;
- if (config.manualRule != null && config.manualRule.condition != null
+ if (!android.app.Flags.modesUi() && config.manualRule != null
+ && config.manualRule.condition != null
&& !config.manualRule.isTrueOrUnknown()) {
if (DEBUG) Log.d(TAG, "evaluateConfig: clearing manual rule");
config.manualRule = null;
}
final ArraySet<Uri> current = new ArraySet<>();
- evaluateRule(config.manualRule, current, null, processSubscriptions);
+ evaluateRule(config.manualRule, current, null, processSubscriptions, true);
for (ZenRule automaticRule : config.automaticRules.values()) {
if (automaticRule.component != null) {
- evaluateRule(automaticRule, current, trigger, processSubscriptions);
+ evaluateRule(automaticRule, current, trigger, processSubscriptions, false);
updateSnoozing(automaticRule);
}
}
@@ -131,7 +132,7 @@
// Only valid for CPS backed rules
private void evaluateRule(ZenRule rule, ArraySet<Uri> current, ComponentName trigger,
- boolean processSubscriptions) {
+ boolean processSubscriptions, boolean isManual) {
if (rule == null || rule.conditionId == null) return;
if (rule.configurationActivity != null) return;
final Uri id = rule.conditionId;
@@ -153,8 +154,10 @@
}
// empty rule? disable and bail early
if (rule.component == null && rule.enabler == null) {
- Log.w(TAG, "No component found for automatic rule: " + rule.conditionId);
- rule.enabled = false;
+ if (!android.app.Flags.modesUi() || (android.app.Flags.modesUi() && !isManual)) {
+ Log.w(TAG, "No component found for automatic rule: " + rule.conditionId);
+ rule.enabled = false;
+ }
return;
}
if (current != null) {
diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
index 418eacc..ec5d96d 100644
--- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java
+++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
@@ -18,8 +18,8 @@
import static android.app.NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND;
import static android.provider.Settings.Global.ZEN_MODE_OFF;
-import static android.service.notification.NotificationServiceProto.CHANNEL_POLICY_PRIORITY;
import static android.service.notification.NotificationServiceProto.CHANNEL_POLICY_NONE;
+import static android.service.notification.NotificationServiceProto.CHANNEL_POLICY_PRIORITY;
import static android.service.notification.NotificationServiceProto.RULE_TYPE_AUTOMATIC;
import static android.service.notification.NotificationServiceProto.RULE_TYPE_MANUAL;
import static android.service.notification.NotificationServiceProto.RULE_TYPE_UNKNOWN;
@@ -352,8 +352,10 @@
ZenModeDiff.RuleDiff manualDiff = diff.getManualRuleDiff();
if (manualDiff != null && manualDiff.hasDiff()) {
// a diff in the manual rule doesn't *necessarily* mean that it's responsible for
- // the change -- only if it's been added or removed.
- if (manualDiff.wasAdded() || manualDiff.wasRemoved()) {
+ // the change -- only if it's been added or removed or updated.
+ if (manualDiff.wasAdded() || manualDiff.wasRemoved()
+ || (Flags.modesUi()
+ && (manualDiff.becameActive() || manualDiff.becameInactive()))) {
return RULE_TYPE_MANUAL;
}
}
@@ -391,10 +393,8 @@
if (config == null) {
return rules;
}
-
- if (config.manualRule != null) {
- // If the manual rule is non-null, then it's active. We make a copy and set the rule
- // type so that the correct value gets logged.
+ if (config.isManualActive()) {
+ // We make a copy and set the rule type so that the correct value gets logged.
ZenRule rule = config.manualRule.copy();
rule.type = ACTIVE_RULE_TYPE_MANUAL;
rules.add(rule);
@@ -592,10 +592,10 @@
// This applies to both call and message senders, but not conversation senders,
// where they use the same enum values.
proto.write(DNDPolicyProto.ALLOW_CALLS_FROM,
- ZenAdapters.notificationPolicySendersToZenPolicyPeopleType(
+ ZenAdapters.prioritySendersToPeopleType(
mNewPolicy.allowCallsFrom()));
proto.write(DNDPolicyProto.ALLOW_MESSAGES_FROM,
- ZenAdapters.notificationPolicySendersToZenPolicyPeopleType(
+ ZenAdapters.prioritySendersToPeopleType(
mNewPolicy.allowMessagesFrom()));
proto.write(DNDPolicyProto.ALLOW_CONVERSATIONS_FROM,
mNewPolicy.allowConversationsFrom());
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 454bd20..267291c 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -24,6 +24,10 @@
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_REMOVED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_UNKNOWN;
import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
+import static android.service.notification.Condition.SOURCE_UNKNOWN;
+import static android.service.notification.Condition.SOURCE_USER_ACTION;
+import static android.service.notification.Condition.STATE_FALSE;
+import static android.service.notification.Condition.STATE_TRUE;
import static android.service.notification.NotificationServiceProto.ROOT_CONFIG;
import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP;
import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT;
@@ -216,7 +220,9 @@
mAppOps = context.getSystemService(AppOpsManager.class);
mNotificationManager = context.getSystemService(NotificationManager.class);
- mDefaultConfig = readDefaultConfig(mContext.getResources());
+ mDefaultConfig = Flags.modesUi()
+ ? ZenModeConfig.getDefaultConfig()
+ : readDefaultConfig(mContext.getResources());
updateDefaultConfigAutomaticRules();
if (Flags.modesApi()) {
updateDefaultAutomaticRulePolicies();
@@ -612,7 +618,7 @@
if (rule != null) {
Condition deactivated = new Condition(rule.conditionId,
mContext.getString(R.string.zen_mode_implicit_deactivated),
- Condition.STATE_FALSE);
+ STATE_FALSE);
setAutomaticZenRuleStateLocked(newConfig, Collections.singletonList(rule),
deactivated, UPDATE_ORIGIN_APP, callingUid);
}
@@ -627,8 +633,7 @@
// would apply if changing the global interruption filter. We only do this
// for newly created rules, as existing rules have a pre-existing policy
// (whether initialized here or set via app or user).
- rule.zenPolicy = mConfig.toZenPolicy();
-
+ rule.zenPolicy = mConfig.getZenPolicy().copy();
newConfig.automaticRules.put(rule.id, rule);
}
// If the user has changed the rule's *zenMode*, then don't let app overwrite it.
@@ -639,7 +644,7 @@
rule.snoozing = false;
rule.condition = new Condition(rule.conditionId,
mContext.getString(R.string.zen_mode_implicit_activated),
- Condition.STATE_TRUE);
+ STATE_TRUE);
setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP,
"applyGlobalZenModeAsImplicitZenRule", callingUid);
@@ -687,7 +692,7 @@
// would take effect if changing the global policy.
// Note that NotificationManager.Policy cannot have any unset priority
// categories, but *can* have unset visual effects, which is why we do this.
- newZenPolicy = mConfig.toZenPolicy().overwrittenWith(newZenPolicy);
+ newZenPolicy = mConfig.getZenPolicy().overwrittenWith(newZenPolicy);
}
updatePolicy(
rule,
@@ -878,7 +883,7 @@
if (rule == null || !canManageAutomaticZenRule(rule)) {
return Condition.STATE_UNKNOWN;
}
- return rule.condition != null ? rule.condition.state : Condition.STATE_FALSE;
+ return rule.condition != null ? rule.condition.state : STATE_FALSE;
}
}
@@ -929,7 +934,7 @@
Condition condition, @ConfigChangeOrigin int origin, int callingUid) {
if (rules == null || rules.isEmpty()) return;
- if (Flags.modesApi() && condition.source == Condition.SOURCE_USER_ACTION) {
+ if (Flags.modesApi() && condition.source == SOURCE_USER_ACTION) {
origin = UPDATE_ORIGIN_USER; // Although coming from app, it's actually a user action.
}
@@ -1285,7 +1290,7 @@
if (isNew) {
// Newly created rule with no provided policy; fill in with the default.
zenRule.zenPolicy =
- Flags.modesUi() ? mDefaultConfig.toZenPolicy() : mConfig.toZenPolicy();
+ Flags.modesUi() ? mDefaultConfig.getZenPolicy() : mConfig.getZenPolicy();
return true;
}
// Otherwise, a null policy means no policy changes, so we can stop here.
@@ -1296,7 +1301,7 @@
// fields in the bitmask should be marked as updated.
ZenPolicy oldPolicy = zenRule.zenPolicy != null
? zenRule.zenPolicy
- : (Flags.modesUi() ? mDefaultConfig.toZenPolicy() : mConfig.toZenPolicy());
+ : (Flags.modesUi() ? mDefaultConfig.getZenPolicy() : mConfig.getZenPolicy());
// If this is updating a rule rather than creating a new one, keep any fields from the
// old policy if they are unspecified in the new policy. For newly created rules, oldPolicy
@@ -1524,9 +1529,15 @@
+ " conditionId=" + conditionId + " reason=" + reason
+ " setRingerMode=" + setRingerMode);
newConfig = mConfig.copy();
- if (zenMode == Global.ZEN_MODE_OFF) {
- newConfig.manualRule = null;
- if (!Flags.modesUi() || origin != UPDATE_ORIGIN_USER) {
+ if (Flags.modesUi()) {
+ newConfig.manualRule.enabler = caller;
+ newConfig.manualRule.conditionId = conditionId != null ? conditionId : Uri.EMPTY;
+ newConfig.manualRule.pkg = PACKAGE_ANDROID;
+ newConfig.manualRule.zenMode = zenMode;
+ newConfig.manualRule.condition = new Condition(newConfig.manualRule.conditionId, "",
+ zenMode == Global.ZEN_MODE_OFF ? STATE_FALSE : STATE_TRUE,
+ origin == UPDATE_ORIGIN_USER ? SOURCE_USER_ACTION : SOURCE_UNKNOWN);
+ if (zenMode == Global.ZEN_MODE_OFF && origin != UPDATE_ORIGIN_USER) {
// User deactivation of DND means just turning off the manual DND rule.
// For API calls (different origin) keep old behavior of snoozing all rules.
for (ZenRule automaticRule : newConfig.automaticRules.values()) {
@@ -1536,20 +1547,51 @@
}
}
} else {
- final ZenRule newRule = new ZenRule();
- newRule.enabled = true;
- newRule.zenMode = zenMode;
- newRule.conditionId = conditionId;
- newRule.enabler = caller;
- if (Flags.modesApi()) {
- newRule.allowManualInvocation = true;
+ if (zenMode == Global.ZEN_MODE_OFF) {
+ newConfig.manualRule = null;
+ // User deactivation of DND means just turning off the manual DND rule.
+ // For API calls (different origin) keep old behavior of snoozing all rules.
+ for (ZenRule automaticRule : newConfig.automaticRules.values()) {
+ if (automaticRule.isAutomaticActive()) {
+ automaticRule.snoozing = true;
+ }
+ }
+
+ } else {
+ final ZenRule newRule = new ZenRule();
+ newRule.enabled = true;
+ newRule.zenMode = zenMode;
+ newRule.conditionId = conditionId;
+ newRule.enabler = caller;
+ if (Flags.modesApi()) {
+ newRule.allowManualInvocation = true;
+ }
+ newConfig.manualRule = newRule;
}
- newConfig.manualRule = newRule;
}
setConfigLocked(newConfig, origin, reason, null, setRingerMode, callingUid);
}
}
+ public void setManualZenRuleDeviceEffects(ZenDeviceEffects deviceEffects,
+ @ConfigChangeOrigin int origin, String reason, int callingUid) {
+ if (!Flags.modesUi()) {
+ return;
+ }
+ ZenModeConfig newConfig;
+ synchronized (mConfigLock) {
+ if (mConfig == null) return;
+ if (DEBUG) Log.d(TAG, "updateManualRule " + deviceEffects
+ + " reason=" + reason
+ + " callingUid=" + callingUid);
+ newConfig = mConfig.copy();
+
+ newConfig.manualRule.pkg = PACKAGE_ANDROID;
+ newConfig.manualRule.zenDeviceEffects = deviceEffects;
+ setConfigLocked(newConfig, origin, reason, null, true, callingUid);
+ }
+ }
+
void dump(ProtoOutputStream proto) {
proto.write(ZenModeProto.ZEN_MODE, mZenMode);
synchronized (mConfigLock) {
@@ -1558,7 +1600,7 @@
}
for (ZenRule rule : mConfig.automaticRules.values()) {
if (rule.enabled && rule.condition != null
- && rule.condition.state == Condition.STATE_TRUE
+ && rule.condition.state == STATE_TRUE
&& !rule.snoozing) {
rule.dumpDebug(proto, ZenModeProto.ENABLED_ACTIVE_CONDITIONS);
}
@@ -1592,33 +1634,7 @@
private static void dump(PrintWriter pw, String prefix, String var, ZenModeConfig config) {
pw.print(prefix); pw.print(var); pw.print('=');
- if (config == null) {
- pw.println(config);
- return;
- }
- pw.printf("allow(alarms=%b,media=%b,system=%b,calls=%b,callsFrom=%s,repeatCallers=%b,"
- + "messages=%b,messagesFrom=%s,conversations=%b,conversationsFrom=%s,"
- + "events=%b,reminders=%b",
- config.allowAlarms, config.allowMedia, config.allowSystem,
- config.allowCalls, ZenModeConfig.sourceToString(config.allowCallsFrom),
- config.allowRepeatCallers, config.allowMessages,
- ZenModeConfig.sourceToString(config.allowMessagesFrom),
- config.allowConversations,
- ZenPolicy.conversationTypeToString(config.allowConversationsFrom),
- config.allowEvents, config.allowReminders);
- if (Flags.modesApi()) {
- pw.printf(",priorityChannels=%b", config.allowPriorityChannels);
- }
- pw.printf(")\n");
- pw.print(prefix);
- pw.printf(" disallow(visualEffects=%s)\n", config.suppressedVisualEffects);
- pw.print(prefix); pw.print(" manualRule="); pw.println(config.manualRule);
- if (config.automaticRules.isEmpty()) return;
- final int N = config.automaticRules.size();
- for (int i = 0; i < N; i++) {
- pw.print(prefix); pw.print(i == 0 ? " automaticRules=" : " ");
- pw.println(config.automaticRules.valueAt(i));
- }
+ pw.println(config);
}
public void readXml(TypedXmlPullParser parser, boolean forRestore, int userId)
@@ -1629,7 +1645,9 @@
if (config != null) {
if (forRestore) {
config.user = userId;
- config.manualRule = null; // don't restore the manual rule
+ if (!Flags.modesUi()) {
+ config.manualRule = null; // don't restore the manual rule
+ }
}
// booleans to determine whether to reset the rules to the default rules
@@ -1653,7 +1671,7 @@
// rules with null ZenPolicies explicitly as a copy of the global policy.
if (Flags.modesApi() && config.version < ZenModeConfig.XML_VERSION_MODES_API) {
// Keep the manual ("global") policy that from config.
- ZenPolicy manualRulePolicy = config.toZenPolicy();
+ ZenPolicy manualRulePolicy = config.getZenPolicy();
if (automaticRule.zenPolicy == null) {
automaticRule.zenPolicy = manualRulePolicy;
} else {
@@ -1842,7 +1860,7 @@
*/
@VisibleForTesting
protected ZenPolicy getDefaultZenPolicy() {
- return mDefaultConfig.toZenPolicy();
+ return mDefaultConfig.getZenPolicy();
}
@GuardedBy("mConfigLock")
@@ -2008,7 +2026,7 @@
private int computeZenMode() {
synchronized (mConfigLock) {
if (mConfig == null) return Global.ZEN_MODE_OFF;
- if (mConfig.manualRule != null) return mConfig.manualRule.zenMode;
+ if (mConfig.isManualActive()) return mConfig.manualRule.zenMode;
int zen = Global.ZEN_MODE_OFF;
for (ZenRule automaticRule : mConfig.automaticRules.values()) {
if (automaticRule.isAutomaticActive()) {
@@ -2047,19 +2065,19 @@
if (Flags.modesApi()) {
if (useManualConfig) {
// manual rule is configured using the settings stored directly in mConfig
- policy.apply(mConfig.toZenPolicy());
+ policy.apply(mConfig.getZenPolicy());
} else {
// under modes_api flag, an active automatic rule with no specified policy
// inherits the device default settings as stored in mDefaultConfig. While the
// rule's policy fields should be set upon creation, this is a fallback to
// catch any that may have fallen through the cracks.
Log.wtf(TAG, "active automatic rule found with no specified policy: " + rule);
- policy.apply(
- Flags.modesUi() ? mDefaultConfig.toZenPolicy() : mConfig.toZenPolicy());
+ policy.apply(Flags.modesUi()
+ ? mDefaultConfig.getZenPolicy() : mConfig.getZenPolicy());
}
} else {
- // active rule with no specified policy inherits the global config settings
- policy.apply(mConfig.toZenPolicy());
+ // active rule with no specified policy inherits the manual rule config settings
+ policy.apply(mConfig.getZenPolicy());
}
}
}
@@ -2071,7 +2089,7 @@
if (mConfig == null) return;
ZenPolicy policy = new ZenPolicy();
ZenDeviceEffects.Builder deviceEffectsBuilder = new ZenDeviceEffects.Builder();
- if (mConfig.manualRule != null) {
+ if (mConfig.isManualActive()) {
applyCustomPolicy(policy, mConfig.manualRule, true);
if (Flags.modesApi()) {
deviceEffectsBuilder.add(mConfig.manualRule.zenDeviceEffects);
@@ -2154,7 +2172,7 @@
// Should be checked before calling, but just in case.
return;
}
- ZenPolicy defaultPolicy = mDefaultConfig.toZenPolicy();
+ ZenPolicy defaultPolicy = mDefaultConfig.getZenPolicy();
for (ZenRule rule : mDefaultConfig.automaticRules.values()) {
if (ZenModeConfig.DEFAULT_RULE_IDS.contains(rule.id) && rule.zenPolicy == null) {
rule.zenPolicy = defaultPolicy.copy();
@@ -2335,17 +2353,17 @@
final ZenModeConfig config = mConfigs.valueAt(i);
events.add(FrameworkStatsLog.buildStatsEvent(DND_MODE_RULE,
/* optional int32 user = 1 */ user,
- /* optional bool enabled = 2 */ config.manualRule != null,
+ /* optional bool enabled = 2 */ config.isManualActive(),
/* optional bool channels_bypassing = 3 */ config.areChannelsBypassingDnd,
/* optional LoggedZenMode zen_mode = 4 */ ROOT_CONFIG,
/* optional string id = 5 */ "", // empty for root config
/* optional int32 uid = 6 */ Process.SYSTEM_UID, // system owns root config
- /* optional DNDPolicyProto policy = 7 */ config.toZenPolicy().toProto(),
+ /* optional DNDPolicyProto policy = 7 */ config.getZenPolicy().toProto(),
/* optional int32 rule_modified_fields = 8 */ 0,
/* optional int32 policy_modified_fields = 9 */ 0,
/* optional int32 device_effects_modified_fields = 10 */ 0,
/* optional ActiveRuleType rule_type = 11 */ TYPE_UNKNOWN));
- if (config.manualRule != null) {
+ if (config.isManualActive()) {
ruleToProtoLocked(user, config.manualRule, true, events);
}
for (ZenRule rule : config.automaticRules.values()) {
diff --git a/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java b/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java
index 96ab2cc..7dd8f2f 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java
@@ -188,7 +188,8 @@
public static IStreamingResponseCallback wrapWithValidation(
IStreamingResponseCallback streamingResponseCallback,
Executor resourceClosingExecutor,
- AndroidFuture future) {
+ AndroidFuture future,
+ InferenceInfoStore inferenceInfoStore) {
return new IStreamingResponseCallback.Stub() {
@Override
public void onNewContent(Bundle processedResult) throws RemoteException {
@@ -207,6 +208,7 @@
sanitizeResponseParams(resultBundle);
streamingResponseCallback.onSuccess(resultBundle);
} finally {
+ inferenceInfoStore.addInferenceInfoFromBundle(resultBundle);
resourceClosingExecutor.execute(() -> tryCloseResource(resultBundle));
future.complete(null);
}
@@ -216,6 +218,7 @@
public void onFailure(int errorCode, String errorMessage,
PersistableBundle errorParams) throws RemoteException {
streamingResponseCallback.onFailure(errorCode, errorMessage, errorParams);
+ inferenceInfoStore.addInferenceInfoFromBundle(errorParams);
future.completeExceptionally(new TimeoutException());
}
@@ -245,7 +248,8 @@
public static IResponseCallback wrapWithValidation(IResponseCallback responseCallback,
Executor resourceClosingExecutor,
- AndroidFuture future) {
+ AndroidFuture future,
+ InferenceInfoStore inferenceInfoStore) {
return new IResponseCallback.Stub() {
@Override
public void onSuccess(Bundle resultBundle)
@@ -254,6 +258,7 @@
sanitizeResponseParams(resultBundle);
responseCallback.onSuccess(resultBundle);
} finally {
+ inferenceInfoStore.addInferenceInfoFromBundle(resultBundle);
resourceClosingExecutor.execute(() -> tryCloseResource(resultBundle));
future.complete(null);
}
@@ -263,6 +268,7 @@
public void onFailure(int errorCode, String errorMessage,
PersistableBundle errorParams) throws RemoteException {
responseCallback.onFailure(errorCode, errorMessage, errorParams);
+ inferenceInfoStore.addInferenceInfoFromBundle(errorParams);
future.completeExceptionally(new TimeoutException());
}
@@ -291,11 +297,13 @@
public static ITokenInfoCallback wrapWithValidation(ITokenInfoCallback responseCallback,
- AndroidFuture future) {
+ AndroidFuture future,
+ InferenceInfoStore inferenceInfoStore) {
return new ITokenInfoCallback.Stub() {
@Override
public void onSuccess(TokenInfo tokenInfo) throws RemoteException {
responseCallback.onSuccess(tokenInfo);
+ inferenceInfoStore.addInferenceInfoFromBundle(tokenInfo.getInfoParams());
future.complete(null);
}
@@ -303,6 +311,7 @@
public void onFailure(int errorCode, String errorMessage, PersistableBundle errorParams)
throws RemoteException {
responseCallback.onFailure(errorCode, errorMessage, errorParams);
+ inferenceInfoStore.addInferenceInfoFromBundle(errorParams);
future.completeExceptionally(new TimeoutException());
}
};
diff --git a/services/core/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java b/services/core/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java
new file mode 100644
index 0000000..6578853
--- /dev/null
+++ b/services/core/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java
@@ -0,0 +1,99 @@
+/*
+ * 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.ondeviceintelligence;
+
+import android.app.ondeviceintelligence.InferenceInfo;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
+import android.util.Slog;
+
+import java.io.IOException;
+import java.util.Base64;
+import java.util.Comparator;
+import java.util.List;
+import java.util.TreeSet;
+
+public class InferenceInfoStore {
+ private static final String TAG = "InferenceInfoStore";
+ private final TreeSet<InferenceInfo> inferenceInfos;
+ private final long maxAgeMs;
+
+ public InferenceInfoStore(long maxAgeMs) {
+ this.maxAgeMs = maxAgeMs;
+ this.inferenceInfos = new TreeSet<>(
+ Comparator.comparingLong(InferenceInfo::getStartTimeMs));
+ }
+
+ public List<InferenceInfo> getLatestInferenceInfo(long startTimeEpochMillis) {
+ return inferenceInfos.stream().filter(
+ info -> info.getStartTimeMs() > startTimeEpochMillis).toList();
+ }
+
+ public void addInferenceInfoFromBundle(PersistableBundle pb) {
+ if (!pb.containsKey(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY)) {
+ return;
+ }
+
+ try {
+ String infoBytesBase64String = pb.getString(
+ OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY);
+ if (infoBytesBase64String != null) {
+ byte[] infoBytes = Base64.getDecoder().decode(infoBytesBase64String);
+ com.android.server.ondeviceintelligence.nano.InferenceInfo inferenceInfo =
+ com.android.server.ondeviceintelligence.nano.InferenceInfo.parseFrom(
+ infoBytes);
+ add(inferenceInfo);
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to parse InferenceInfo from the received bytes.");
+ }
+ }
+
+ public void addInferenceInfoFromBundle(Bundle b) {
+ if (!b.containsKey(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY)) {
+ return;
+ }
+
+ try {
+ byte[] infoBytes = b.getByteArray(
+ OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY);
+ if (infoBytes != null) {
+ com.android.server.ondeviceintelligence.nano.InferenceInfo inferenceInfo =
+ com.android.server.ondeviceintelligence.nano.InferenceInfo.parseFrom(
+ infoBytes);
+ add(inferenceInfo);
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to parse InferenceInfo from the received bytes.");
+ }
+ }
+
+ private synchronized void add(com.android.server.ondeviceintelligence.nano.InferenceInfo info) {
+ while (System.currentTimeMillis() - inferenceInfos.first().getStartTimeMs() > maxAgeMs) {
+ inferenceInfos.pollFirst();
+ }
+ inferenceInfos.add(toInferenceInfo(info));
+ }
+
+ private static InferenceInfo toInferenceInfo(
+ com.android.server.ondeviceintelligence.nano.InferenceInfo info) {
+ return new InferenceInfo.Builder().setUid(info.uid).setStartTimeMs(
+ info.startTimeMs).setEndTimeMs(info.endTimeMs).setSuspendedTimeMs(
+ info.suspendedTimeMs).build();
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
index 07af8d0..1450dc0 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
@@ -17,5 +17,10 @@
package com.android.server.ondeviceintelligence;
public interface OnDeviceIntelligenceManagerInternal {
+ /**
+ * Gets the uid for the process that is currently hosting the
+ * {@link android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService} registered on
+ * the device.
+ */
int getInferenceServiceUid();
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index 59964e0..9ef2e12 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -44,6 +44,7 @@
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.IStreamingResponseCallback;
import android.app.ondeviceintelligence.ITokenInfoCallback;
+import android.app.ondeviceintelligence.InferenceInfo;
import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
import android.content.ComponentName;
import android.content.Context;
@@ -127,6 +128,7 @@
private static final String NAMESPACE_ON_DEVICE_INTELLIGENCE = "ondeviceintelligence";
private static final String SYSTEM_PACKAGE = "android";
+ private static final long MAX_AGE_MS = TimeUnit.HOURS.toMillis(3);
private final Executor resourceClosingExecutor = Executors.newCachedThreadPool();
@@ -138,7 +140,7 @@
private final Context mContext;
protected final Object mLock = new Object();
-
+ private final InferenceInfoStore mInferenceInfoStore;
private RemoteOnDeviceSandboxedInferenceService mRemoteInferenceService;
private RemoteOnDeviceIntelligenceService mRemoteOnDeviceIntelligenceService;
volatile boolean mIsServiceEnabled;
@@ -170,6 +172,7 @@
super(context);
mContext = context;
mTemporaryServiceNames = new String[0];
+ mInferenceInfoStore = new InferenceInfoStore(MAX_AGE_MS);
}
@Override
@@ -191,6 +194,16 @@
mIsServiceEnabled = isServiceEnabled();
}
+
+ //connect to remote services(if available) during boot phase.
+ if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
+ try {
+ ensureRemoteInferenceServiceInitialized();
+ ensureRemoteIntelligenceServiceInitialized();
+ } catch (Exception e) {
+ Slog.w(TAG, "Couldn't pre-start remote ondeviceintelligence services.", e);
+ }
+ }
}
private void onDeviceConfigChange(@NonNull Set<String> keys) {
@@ -213,10 +226,18 @@
}
@Override
+ public List<InferenceInfo> getLatestInferenceInfo(long startTimeEpochMillis) {
+ mContext.enforceCallingPermission(
+ Manifest.permission.DUMP, TAG);
+ return OnDeviceIntelligenceManagerService.this.getLatestInferenceInfo(
+ startTimeEpochMillis);
+ }
+
+ @Override
public void getVersion(RemoteCallback remoteCallback) {
Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getVersion");
Objects.requireNonNull(remoteCallback);
- mContext.enforceCallingOrSelfPermission(
+ mContext.enforceCallingPermission(
Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
if (!mIsServiceEnabled) {
Slog.w(TAG, "Service not available");
@@ -241,7 +262,7 @@
throws RemoteException {
Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
Objects.requireNonNull(featureCallback);
- mContext.enforceCallingOrSelfPermission(
+ mContext.enforceCallingPermission(
Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
if (!mIsServiceEnabled) {
Slog.w(TAG, "Service not available");
@@ -279,7 +300,7 @@
throws RemoteException {
Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
Objects.requireNonNull(listFeaturesCallback);
- mContext.enforceCallingOrSelfPermission(
+ mContext.enforceCallingPermission(
Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
if (!mIsServiceEnabled) {
Slog.w(TAG, "Service not available");
@@ -323,7 +344,7 @@
Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatureStatus");
Objects.requireNonNull(feature);
Objects.requireNonNull(featureDetailsCallback);
- mContext.enforceCallingOrSelfPermission(
+ mContext.enforceCallingPermission(
Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
if (!mIsServiceEnabled) {
Slog.w(TAG, "Service not available");
@@ -367,7 +388,7 @@
Slog.i(TAG, "OnDeviceIntelligenceManagerInternal requestFeatureDownload");
Objects.requireNonNull(feature);
Objects.requireNonNull(downloadCallback);
- mContext.enforceCallingOrSelfPermission(
+ mContext.enforceCallingPermission(
Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
if (!mIsServiceEnabled) {
Slog.w(TAG, "Service not available");
@@ -407,7 +428,7 @@
sanitizeInferenceParams(request);
Objects.requireNonNull(tokenInfoCallback);
- mContext.enforceCallingOrSelfPermission(
+ mContext.enforceCallingPermission(
Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
if (!mIsServiceEnabled) {
Slog.w(TAG, "Service not available");
@@ -424,7 +445,8 @@
service.requestTokenInfo(callerUid, feature,
request,
wrapCancellationFuture(cancellationSignalFuture),
- wrapWithValidation(tokenInfoCallback, future));
+ wrapWithValidation(tokenInfoCallback, future,
+ mInferenceInfoStore));
return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
});
result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request),
@@ -450,7 +472,7 @@
Objects.requireNonNull(feature);
sanitizeInferenceParams(request);
Objects.requireNonNull(responseCallback);
- mContext.enforceCallingOrSelfPermission(
+ mContext.enforceCallingPermission(
Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
if (!mIsServiceEnabled) {
Slog.w(TAG, "Service not available");
@@ -470,7 +492,8 @@
wrapCancellationFuture(cancellationSignalFuture),
wrapProcessingFuture(processingSignalFuture),
wrapWithValidation(responseCallback,
- resourceClosingExecutor, future));
+ resourceClosingExecutor, future,
+ mInferenceInfoStore));
return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
});
result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request),
@@ -495,7 +518,7 @@
Objects.requireNonNull(feature);
sanitizeInferenceParams(request);
Objects.requireNonNull(streamingCallback);
- mContext.enforceCallingOrSelfPermission(
+ mContext.enforceCallingPermission(
Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
if (!mIsServiceEnabled) {
Slog.w(TAG, "Service not available");
@@ -515,7 +538,8 @@
wrapCancellationFuture(cancellationSignalFuture),
wrapProcessingFuture(processingSignalFuture),
wrapWithValidation(streamingCallback,
- resourceClosingExecutor, future));
+ resourceClosingExecutor, future,
+ mInferenceInfoStore));
return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
});
result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request),
@@ -836,6 +860,10 @@
&& (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
}
+ private List<InferenceInfo> getLatestInferenceInfo(long startTimeEpochMillis) {
+ return mInferenceInfoStore.getLatestInferenceInfo(startTimeEpochMillis);
+ }
+
@Nullable
public String getRemoteConfiguredPackageName() {
try {
@@ -1056,7 +1084,7 @@
}
private void setRemoteInferenceServiceUid(int remoteInferenceServiceUid) {
- synchronized (mLock){
+ synchronized (mLock) {
this.remoteInferenceServiceUid = remoteInferenceServiceUid;
}
}
diff --git a/services/core/java/com/android/server/pm/KillAppBlocker.java b/services/core/java/com/android/server/pm/KillAppBlocker.java
index e2901c3..7f5ad9d 100644
--- a/services/core/java/com/android/server/pm/KillAppBlocker.java
+++ b/services/core/java/com/android/server/pm/KillAppBlocker.java
@@ -83,13 +83,13 @@
}
}
- void waitAppProcessGone(ActivityManagerInternal mAmi, Computer snapshot,
+ void waitAppProcessGone(ActivityManagerInternal ami, Computer snapshot,
UserManagerService userManager, String packageName) {
if (!mRegistered) {
return;
}
synchronized (this) {
- if (mAmi != null) {
+ if (ami != null) {
int[] users = userManager.getUserIds();
for (int i = 0; i < users.length; i++) {
@@ -97,12 +97,16 @@
final int uid = snapshot.getPackageUidInternal(
packageName, MATCH_ALL, userId, Process.SYSTEM_UID);
if (uid != INVALID_UID) {
- if (mAmi.getUidProcessState(uid) != PROCESS_STATE_NONEXISTENT) {
+ if (ami.getUidProcessState(uid) != PROCESS_STATE_NONEXISTENT) {
mActiveUids.add(uid);
}
}
}
}
+ if (mActiveUids.size() == 0) {
+ // no active uid
+ return;
+ }
}
try {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 78d8002..1cd77ff 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -320,10 +320,10 @@
private final Handler mHandler;
- @GuardedBy("itself")
+ @GuardedBy("mServiceLock")
private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1);
- @GuardedBy("itself")
+ @GuardedBy("mServiceLock")
private final ArrayList<LauncherApps.ShortcutChangeCallback> mShortcutChangeCallbacks =
new ArrayList<>(1);
@@ -1847,9 +1847,7 @@
return;
}
- synchronized (mListeners) {
- copy = new ArrayList<>(mListeners);
- }
+ copy = new ArrayList<>(mListeners);
}
// Note onShortcutChanged() needs to be called with the system service permissions.
for (int i = copy.size() - 1; i >= 0; i--) {
@@ -1874,9 +1872,8 @@
if (!isUserUnlockedL(userId)) {
return;
}
- synchronized (mShortcutChangeCallbacks) {
- copy = new ArrayList<>(mShortcutChangeCallbacks);
- }
+
+ copy = new ArrayList<>(mShortcutChangeCallbacks);
}
for (int i = copy.size() - 1; i >= 0; i--) {
if (!CollectionUtils.isEmpty(changedList)) {
@@ -3432,7 +3429,7 @@
@Override
public void addListener(@NonNull ShortcutChangeListener listener) {
- synchronized (mListeners) {
+ synchronized (mServiceLock) {
mListeners.add(Objects.requireNonNull(listener));
}
}
@@ -3440,7 +3437,7 @@
@Override
public void addShortcutChangeCallback(
@NonNull LauncherApps.ShortcutChangeCallback callback) {
- synchronized (mShortcutChangeCallbacks) {
+ synchronized (mServiceLock) {
mShortcutChangeCallbacks.add(Objects.requireNonNull(callback));
}
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 12e5180..4e224a3 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2882,6 +2882,8 @@
}
ContentResolver resolver = mContext.getContentResolver();
boolean updateRotation = false;
+ boolean updateKidsModeSettings = false;
+ final boolean kidsModeEnabled;
synchronized (mLock) {
mEndcallBehavior = Settings.System.getIntForUser(resolver,
Settings.System.END_BUTTON_BEHAVIOR,
@@ -2995,20 +2997,23 @@
Secure.STYLUS_BUTTONS_ENABLED, 1, UserHandle.USER_CURRENT) == 1;
mInputManagerInternal.setStylusButtonMotionEventsEnabled(mStylusButtonsEnabled);
- final boolean kidsModeEnabled = Settings.Secure.getIntForUser(resolver,
+ kidsModeEnabled = Settings.Secure.getIntForUser(resolver,
Settings.Secure.NAV_BAR_KIDS_MODE, 0, UserHandle.USER_CURRENT) == 1;
if (mKidsModeEnabled != kidsModeEnabled) {
mKidsModeEnabled = kidsModeEnabled;
- updateKidsModeSettings();
+ updateKidsModeSettings = true;
}
}
+ if (updateKidsModeSettings) {
+ updateKidsModeSettings(kidsModeEnabled);
+ }
if (updateRotation) {
updateRotation(true);
}
}
- private void updateKidsModeSettings() {
- if (mKidsModeEnabled) {
+ private void updateKidsModeSettings(boolean kidsModeEnabled) {
+ if (kidsModeEnabled) {
// Needed since many Kids apps aren't optimised to support both orientations and it
// will be hard for kids to understand the app compat mode.
// TODO(229961548): Remove ignoreOrientationRequest exception for Kids Mode once
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 80c262a..11b9e77 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -17,7 +17,6 @@
package com.android.server.power;
import android.annotation.Nullable;
-import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
@@ -198,10 +197,9 @@
FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector,
Executor backgroundExecutor, PowerManagerFlags powerManagerFlags, Injector injector) {
mContext = context;
- mInjector = (injector == null) ? new RealInjector() : injector;
mFlags = powerManagerFlags;
mBatteryStats = batteryStats;
- mAppOps = mInjector.getAppOpsManager(context);
+ mAppOps = mContext.getSystemService(AppOpsManager.class);
mSuspendBlocker = suspendBlocker;
mPolicy = policy;
mFaceDownDetector = faceDownDetector;
@@ -232,6 +230,7 @@
mShowWirelessChargingAnimationConfig = context.getResources().getBoolean(
com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim);
+ mInjector = (injector == null) ? new RealInjector() : injector;
mWakeLockLog = mInjector.getWakeLockLog(context);
// Initialize interactive state for battery stats.
try {
@@ -265,7 +264,6 @@
/**
* Called when a wake lock is acquired.
*/
- @SuppressLint("AndroidFrameworkRequiresPermission")
public void onWakeLockAcquired(int flags, String tag, String packageName,
int ownerUid, int ownerPid, WorkSource workSource, String historyTag,
IWakeLockCallback callback) {
@@ -275,28 +273,27 @@
+ ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
+ ", workSource=" + workSource);
}
- notifyWakeLockListener(callback, tag, true, ownerUid, ownerPid, flags, workSource,
- packageName, historyTag);
- if (!mFlags.improveWakelockLatency()) {
- final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
- if (monitorType >= 0) {
- try {
- final boolean unimportantForLogging = ownerUid == Process.SYSTEM_UID
- && (flags & PowerManager.UNIMPORTANT_FOR_LOGGING) != 0;
- if (workSource != null) {
- mBatteryStats.noteStartWakelockFromSource(workSource, ownerPid, tag,
- historyTag, monitorType, unimportantForLogging);
- } else {
- mBatteryStats.noteStartWakelock(ownerUid, ownerPid, tag, historyTag,
- monitorType, unimportantForLogging);
- // XXX need to deal with disabled operations.
- mAppOps.startOpNoThrow(AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName,
- false, null, null);
- }
- } catch (RemoteException ex) {
- // Ignore
+ notifyWakeLockListener(callback, tag, true, ownerUid, flags);
+ final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
+ if (monitorType >= 0) {
+ try {
+ final boolean unimportantForLogging = ownerUid == Process.SYSTEM_UID
+ && (flags & PowerManager.UNIMPORTANT_FOR_LOGGING) != 0;
+ if (workSource != null) {
+ mBatteryStats.noteStartWakelockFromSource(workSource, ownerPid, tag,
+ historyTag, monitorType, unimportantForLogging);
+ } else {
+ mBatteryStats.noteStartWakelock(ownerUid, ownerPid, tag, historyTag,
+ monitorType, unimportantForLogging);
+ // XXX need to deal with disabled operations.
+ mAppOps.startOpNoThrow(AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName);
}
+ } catch (RemoteException ex) {
+ // Ignore
}
+ }
+
+ if (!mFlags.improveWakelockLatency()) {
mWakeLockLog.onWakeLockAcquired(tag, ownerUid, flags, /*eventTime=*/ -1);
}
mWakefulnessSessionObserver.onWakeLockAcquired(flags);
@@ -407,7 +404,6 @@
/**
* Called when a wake lock is released.
*/
- @SuppressLint("AndroidFrameworkRequiresPermission")
public void onWakeLockReleased(int flags, String tag, String packageName,
int ownerUid, int ownerPid, WorkSource workSource, String historyTag,
IWakeLockCallback callback, int releaseReason) {
@@ -417,24 +413,23 @@
+ ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
+ ", workSource=" + workSource);
}
- notifyWakeLockListener(callback, tag, false, ownerUid, ownerPid, flags, workSource,
- packageName, historyTag);
- if (!mFlags.improveWakelockLatency()) {
- final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
- if (monitorType >= 0) {
- try {
- if (workSource != null) {
- mBatteryStats.noteStopWakelockFromSource(workSource, ownerPid, tag,
- historyTag, monitorType);
- } else {
- mBatteryStats.noteStopWakelock(ownerUid, ownerPid, tag,
- historyTag, monitorType);
- mAppOps.finishOp(AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName, null);
- }
- } catch (RemoteException ex) {
- // Ignore
+ notifyWakeLockListener(callback, tag, false, ownerUid, flags);
+ final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
+ if (monitorType >= 0) {
+ try {
+ if (workSource != null) {
+ mBatteryStats.noteStopWakelockFromSource(workSource, ownerPid, tag,
+ historyTag, monitorType);
+ } else {
+ mBatteryStats.noteStopWakelock(ownerUid, ownerPid, tag,
+ historyTag, monitorType);
+ mAppOps.finishOp(AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName);
}
+ } catch (RemoteException ex) {
+ // Ignore
}
+ }
+ if (!mFlags.improveWakelockLatency()) {
mWakeLockLog.onWakeLockReleased(tag, ownerUid, /*eventTime=*/ -1);
}
mWakefulnessSessionObserver.onWakeLockReleased(flags, releaseReason);
@@ -1054,75 +1049,24 @@
}
private void notifyWakeLockListener(IWakeLockCallback callback, String tag, boolean isEnabled,
- int ownerUid, int ownerPid, int flags, WorkSource workSource, String packageName,
- String historyTag) {
- if (mFlags.improveWakelockLatency()) {
- if (callback != null) {
- long currentTime = mInjector.currentTimeMillis();
- mHandler.post(() -> {
- try {
+ int ownerUid, int flags) {
+ if (callback != null) {
+ long currentTime = mInjector.currentTimeMillis();
+ mHandler.post(() -> {
+ try {
+ if (mFlags.improveWakelockLatency()) {
if (isEnabled) {
- notifyWakelockAcquisition(tag, ownerUid, ownerPid, flags,
- workSource, packageName, historyTag, currentTime);
+ mWakeLockLog.onWakeLockAcquired(tag, ownerUid, flags, currentTime);
} else {
- notifyWakelockRelease(tag, ownerUid, ownerPid, flags,
- workSource, packageName, historyTag, currentTime);
+ mWakeLockLog.onWakeLockReleased(tag, ownerUid, currentTime);
}
- callback.onStateChanged(isEnabled);
- } catch (RemoteException e) {
- Slog.e(TAG, "Wakelock.mCallback [" + tag + "] is already dead.", e);
}
- });
- }
- }
-
- }
-
- @SuppressLint("AndroidFrameworkRequiresPermission")
- private void notifyWakelockAcquisition(String tag, int ownerUid, int ownerPid, int flags,
- WorkSource workSource, String packageName, String historyTag, long currentTime) {
- final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
- if (monitorType >= 0) {
- try {
- final boolean unimportantForLogging = ownerUid == Process.SYSTEM_UID
- && (flags & PowerManager.UNIMPORTANT_FOR_LOGGING) != 0;
- if (workSource != null) {
- mBatteryStats.noteStartWakelockFromSource(workSource, ownerPid, tag,
- historyTag, monitorType, unimportantForLogging);
- } else {
- mBatteryStats.noteStartWakelock(ownerUid, ownerPid, tag, historyTag,
- monitorType, unimportantForLogging);
- // XXX need to deal with disabled operations.
- mAppOps.startOpNoThrow(
- AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName,
- false, null, null);
+ callback.onStateChanged(isEnabled);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Wakelock.mCallback [" + tag + "] is already dead.", e);
}
- } catch (RemoteException ex) {
- // Do Nothing
- }
+ });
}
- mWakeLockLog.onWakeLockAcquired(tag, ownerUid, flags, currentTime);
- }
-
- @SuppressLint("AndroidFrameworkRequiresPermission")
- private void notifyWakelockRelease(String tag, int ownerUid, int ownerPid, int flags,
- WorkSource workSource, String packageName, String historyTag, long currentTime) {
- final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
- if (monitorType >= 0) {
- try {
- if (workSource != null) {
- mBatteryStats.noteStopWakelockFromSource(workSource, ownerPid, tag,
- historyTag, monitorType);
- } else {
- mBatteryStats.noteStopWakelock(ownerUid, ownerPid, tag,
- historyTag, monitorType);
- mAppOps.finishOp(AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName, null);
- }
- } catch (RemoteException ex) {
- // Ignore
- }
- }
- mWakeLockLog.onWakeLockReleased(tag, ownerUid, currentTime);
}
private final class NotifierHandler extends Handler {
@@ -1170,11 +1114,6 @@
* Gets the WakeLockLog object
*/
WakeLockLog getWakeLockLog(Context context);
-
- /**
- * Gets the AppOpsManager system service
- */
- AppOpsManager getAppOpsManager(Context context);
}
static class RealInjector implements Injector {
@@ -1187,10 +1126,5 @@
public WakeLockLog getWakeLockLog(Context context) {
return new WakeLockLog(context);
}
-
- @Override
- public AppOpsManager getAppOpsManager(Context context) {
- return context.getSystemService(AppOpsManager.class);
- }
}
}
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 df502eb..06595ac 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -19,6 +19,7 @@
import static android.os.Flags.adpfUseFmqChannel;
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 android.annotation.NonNull;
@@ -28,6 +29,8 @@
import android.app.StatsManager;
import android.app.UidObserver;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.hardware.power.ChannelConfig;
import android.hardware.power.IPower;
import android.hardware.power.SessionConfig;
@@ -130,6 +133,7 @@
private final IPower mPowerHal;
private int mPowerHalVersion;
+ private final PackageManager mPackageManager;
private static final String PROPERTY_SF_ENABLE_CPU_HINT = "debug.sf.enable_adpf_cpu_hint";
private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager";
@@ -151,6 +155,11 @@
mCleanUpHandler = null;
mNonIsolatedTids = null;
}
+ if (adpfSessionTag()) {
+ mPackageManager = mContext.getPackageManager();
+ } else {
+ mPackageManager = null;
+ }
mActiveSessions = new ArrayMap<>();
mChannelMap = new ArrayMap<>();
mNativeWrapper = injector.createNativeWrapper();
@@ -819,6 +828,25 @@
throw new SecurityException(errMsg);
}
+ if (adpfSessionTag() && tag == SessionTag.APP) {
+ // If the category of the app is a game,
+ // we change the session tag to SessionTag.GAME
+ // as it was not previously classified
+ switch (getUidApplicationCategory(callingUid)) {
+ case ApplicationInfo.CATEGORY_GAME:
+ tag = SessionTag.GAME;
+ break;
+ case ApplicationInfo.CATEGORY_UNDEFINED:
+ // We use CATEGORY_UNDEFINED to filter the case when
+ // PackageManager.NameNotFoundException is caught,
+ // which should not happen.
+ tag = SessionTag.APP;
+ break;
+ default:
+ tag = SessionTag.APP;
+ }
+ }
+
Long halSessionPtr = null;
if (mConfigCreationSupport.get()) {
try {
@@ -857,7 +885,10 @@
}
}
- logPerformanceHintSessionAtom(callingUid, halSessionPtr, durationNanos, tids);
+ final long sessionId = config != null ? config.id : halSessionPtr;
+ logPerformanceHintSessionAtom(
+ callingUid, sessionId, durationNanos, tids, tag);
+
synchronized (mLock) {
AppHintSession hs = new AppHintSession(callingUid, callingTgid, tids, token,
halSessionPtr, durationNanos);
@@ -944,9 +975,20 @@
}
private void logPerformanceHintSessionAtom(int uid, long sessionId,
- long targetDuration, int[] tids) {
+ long targetDuration, int[] tids, @SessionTag int sessionTag) {
FrameworkStatsLog.write(FrameworkStatsLog.PERFORMANCE_HINT_SESSION_REPORTED, uid,
- sessionId, targetDuration, tids.length);
+ sessionId, targetDuration, tids.length, sessionTag);
+ }
+
+ private int getUidApplicationCategory(int uid) {
+ try {
+ final String packageName = mPackageManager.getNameForUid(uid);
+ final ApplicationInfo applicationInfo =
+ mPackageManager.getApplicationInfo(packageName, PackageManager.MATCH_ALL);
+ return applicationInfo.category;
+ } catch (PackageManager.NameNotFoundException e) {
+ return ApplicationInfo.CATEGORY_UNDEFINED;
+ }
}
}
diff --git a/services/core/java/com/android/server/power/hint/TEST_MAPPING b/services/core/java/com/android/server/power/hint/TEST_MAPPING
index ce6277d..34c25c6 100644
--- a/services/core/java/com/android/server/power/hint/TEST_MAPPING
+++ b/services/core/java/com/android/server/power/hint/TEST_MAPPING
@@ -10,16 +10,18 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
- },
+ }
+ ],
+ "postsubmit": [
{
"name": "CtsStatsdAtomHostTestCases",
"options": [
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"},
- {"include-filter": "android.cts.statsdatom.powermanager"}
+ {"include-filter": "android.cts.statsdatom.performancehintmanager"}
],
"file_patterns": [
- "(/|^)ThermalManagerService.java"
+ "(/|^)HintManagerService.java"
]
}
]
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 0997744..55afa05 100644
--- a/services/core/java/com/android/server/power/hint/flags.aconfig
+++ b/services/core/java/com/android/server/power/hint/flags.aconfig
@@ -7,3 +7,10 @@
description: "Feature flag for auto PowerHintSession dead thread cleanup"
bug: "296160319"
}
+
+flag {
+ name: "adpf_session_tag"
+ namespace: "game"
+ description: "Feature flag for adding session tag to hint session atom"
+ bug: "345011125"
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 15f4c8c..5159fc4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2983,7 +2983,7 @@
void removeStartingWindowAnimation(boolean prepareAnimation) {
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
- if (task != null) {
+ if (mStartingData != null && task != null) {
task.mSharedStartingData = null;
}
if (mStartingWindow == null) {
@@ -8699,7 +8699,11 @@
rotation = mDisplayContent.getRotation();
}
if (!mOptOutEdgeToEdge && (!mResolveConfigHint.mUseOverrideInsetsForConfig
- || getCompatDisplayInsets() != null || isFloating(parentWindowingMode)
+ || getCompatDisplayInsets() != null
+ || (isFloating(parentWindowingMode)
+ // Check the windowing mode of activity as well in case it is switching
+ // between PiP and fullscreen.
+ && isFloating(inOutConfig.windowConfiguration.getWindowingMode()))
|| rotation == ROTATION_UNDEFINED)) {
// If the insets configuration decoupled logic is not enabled for the app, or the app
// already has a compat override, or the context doesn't contain enough info to
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a3a6b51..87ee5d8 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5719,15 +5719,21 @@
* @see Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS
*/
boolean supportsSystemDecorations() {
+ boolean forceDesktopModeOnDisplay = forceDesktopMode();
+
+ if (com.android.window.flags.Flags.rearDisplayDisableForceDesktopSystemDecorations()) {
+ // System decorations should not be forced on a rear display due to security policies.
+ forceDesktopModeOnDisplay =
+ forceDesktopModeOnDisplay && ((mDisplay.getFlags() & Display.FLAG_REAR) == 0);
+ }
+
return (mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(this)
|| (mDisplay.getFlags() & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0
- || forceDesktopMode())
+ || forceDesktopModeOnDisplay)
// VR virtual display will be used to run and render 2D app within a VR experience.
&& mDisplayId != mWmService.mVr2dDisplayId
// Do not show system decorations on untrusted virtual display.
- && isTrusted()
- // No system decoration on rear display.
- && (mDisplay.getFlags() & Display.FLAG_REAR) == 0;
+ && isTrusted();
}
/**
diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
index b2ba9d1..bf99ccd 100644
--- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
@@ -20,6 +20,7 @@
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.ViewRootImpl.CLIENT_TRANSIENT;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
@@ -35,6 +36,7 @@
import android.content.IntentFilter;
import android.graphics.Insets;
import android.graphics.PixelFormat;
+import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.os.Binder;
import android.os.Bundle;
@@ -60,6 +62,7 @@
import android.view.animation.Interpolator;
import android.widget.Button;
import android.widget.FrameLayout;
+import android.widget.RelativeLayout;
import com.android.internal.R;
@@ -233,6 +236,7 @@
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT);
lp.setFitInsetsTypes(lp.getFitInsetsTypes() & ~Type.statusBars());
+ lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
// Trusted overlay so touches outside the touchable area are allowed to pass through
lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
| WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
@@ -245,13 +249,20 @@
private FrameLayout.LayoutParams getBubbleLayoutParams() {
return new FrameLayout.LayoutParams(
- mContext.getResources().getDimensionPixelSize(
- R.dimen.immersive_mode_cling_width),
+ getClingWindowWidth(),
ViewGroup.LayoutParams.WRAP_CONTENT,
Gravity.CENTER_HORIZONTAL | Gravity.TOP);
}
/**
+ * Returns the width of the cling window.
+ */
+ private int getClingWindowWidth() {
+ return mContext.getResources().getDimensionPixelSize(
+ R.dimen.immersive_mode_cling_width);
+ }
+
+ /**
* @return the window token that's used by all ImmersiveModeConfirmation windows.
*/
IBinder getWindowToken() {
@@ -387,6 +398,24 @@
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ // If the top display cutout overlaps with the full-width (windowWidth=-1)/centered
+ // dialog, then adjust the dialog contents by the cutout
+ final int width = getWidth();
+ final int windowWidth = getClingWindowWidth();
+ final Rect topDisplayCutout = insets.getDisplayCutout() != null
+ ? insets.getDisplayCutout().getBoundingRectTop()
+ : new Rect();
+ final boolean intersectsTopCutout = topDisplayCutout.intersects(
+ width - (windowWidth / 2), 0,
+ width + (windowWidth / 2), topDisplayCutout.bottom);
+ if (mClingWindow != null &&
+ (windowWidth < 0 || (width > 0 && intersectsTopCutout))) {
+ final View iconView = mClingWindow.findViewById(R.id.immersive_cling_icon);
+ RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)
+ iconView.getLayoutParams();
+ lp.topMargin = topDisplayCutout.bottom;
+ iconView.setLayoutParams(lp);
+ }
// we will be hiding the nav bar, so layout as if it's already hidden
return new WindowInsets.Builder(insets).setInsets(
Type.systemBars(), Insets.NONE).build();
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 3c556bf..6288a42 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -424,7 +424,9 @@
// Use task bounds to calculating rounded corners if the task is not floating.
final InsetsState state = copyState ? new InsetsState(originalState)
: originalState;
- state.setRoundedCornerFrame(task.getBounds());
+ state.setRoundedCornerFrame(token.isFixedRotationTransforming()
+ ? token.getFixedRotationTransformDisplayBounds()
+ : task.getBounds());
return state;
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index d3efcb6..2a458c42 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -52,7 +52,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.display.config.HysteresisLevels;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.testutils.OffsettableClock;
@@ -102,8 +101,6 @@
@Mock Handler mNoOpHandler;
@Mock BrightnessRangeController mBrightnessRangeController;
@Mock
- BrightnessClamperController mBrightnessClamperController;
- @Mock
DisplayManagerFlags mDisplayManagerFlags;
@Mock BrightnessThrottler mBrightnessThrottler;
@@ -181,7 +178,7 @@
mContext, mBrightnessRangeController, mBrightnessThrottler,
useHorizon ? AMBIENT_LIGHT_HORIZON_SHORT : 1,
useHorizon ? AMBIENT_LIGHT_HORIZON_LONG : 10000, userLux, userNits,
- mBrightnessClamperController, mDisplayManagerFlags
+ mDisplayManagerFlags
);
when(mBrightnessRangeController.getCurrentBrightnessMax()).thenReturn(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index efa224f..95f0b65 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1415,7 +1415,7 @@
when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
- when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+ when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean(), anyInt())).thenAnswer(
invocation -> DisplayBrightnessState.builder()
.setIsSlowChange(invocation.getArgument(2))
.setBrightness(invocation.getArgument(1))
@@ -1439,7 +1439,7 @@
when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
- when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+ when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean(), anyInt())).thenAnswer(
invocation -> DisplayBrightnessState.builder()
.setIsSlowChange(invocation.getArgument(2))
.setBrightness(invocation.getArgument(1))
@@ -2094,7 +2094,7 @@
BrightnessClamperController clamperController = mock(BrightnessClamperController.class);
when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
- when(clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+ when(clamperController.clamp(any(), anyFloat(), anyBoolean(), anyInt())).thenAnswer(
invocation -> DisplayBrightnessState.builder()
.setIsSlowChange(invocation.getArgument(2))
.setBrightness(invocation.getArgument(1))
@@ -2328,7 +2328,7 @@
BrightnessClamperController getBrightnessClamperController(Handler handler,
BrightnessClamperController.ClamperChangeListener clamperChangeListener,
BrightnessClamperController.DisplayDeviceData data, Context context,
- DisplayManagerFlags flags) {
+ DisplayManagerFlags flags, SensorManager sensorManager) {
return mClamperController;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index 5487bc5..69043f5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -16,14 +16,20 @@
package com.android.server.display.brightness.clamper;
+import static android.view.Display.STATE_ON;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
import android.hardware.display.BrightnessInfo;
import android.hardware.display.DisplayManagerInternal;
import android.os.Handler;
@@ -34,11 +40,15 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.TestUtils;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.display.feature.DisplayManagerFlags;
+import com.android.server.testutils.OffsettableClock;
import com.android.server.testutils.TestHandler;
import org.junit.Before;
@@ -54,13 +64,16 @@
public class BrightnessClamperControllerTest {
private static final float FLOAT_TOLERANCE = 0.001f;
- private final TestHandler mTestHandler = new TestHandler(null);
+ private final OffsettableClock mClock = new OffsettableClock();
+ private final TestHandler mTestHandler = new TestHandler(null, mClock);
@Rule
public final TestableContext mMockContext = new TestableContext(
InstrumentationRegistry.getInstrumentation().getContext());
@Mock
private BrightnessClamperController.ClamperChangeListener mMockExternalListener;
+ @Mock
+ private SensorManager mSensorManager;
@Mock
private BrightnessClamperController.DisplayDeviceData mMockDisplayDeviceData;
@@ -74,15 +87,25 @@
private BrightnessModifier mMockModifier;
@Mock
private DisplayManagerInternal.DisplayPowerRequest mMockRequest;
+
+ Sensor mLightSensor;
+
@Mock
private DeviceConfig.Properties mMockProperties;
private BrightnessClamperController mClamperController;
private TestInjector mTestInjector;
+ @Rule
+ public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
mTestInjector = new TestInjector(List.of(mMockClamper), List.of(mMockModifier));
+ when(mSensorManager.getDefaultSensor(anyInt())).thenReturn(mLightSensor);
+ when(mMockModifier.shouldListenToLightSensor()).thenReturn(true);
+
mClamperController = createBrightnessClamperController();
}
@@ -132,7 +155,7 @@
public void testClamp_AppliesModifier() {
float initialBrightness = 0.2f;
boolean initialSlowChange = true;
- mClamperController.clamp(mMockRequest, initialBrightness, initialSlowChange);
+ mClamperController.clamp(mMockRequest, initialBrightness, initialSlowChange, STATE_ON);
verify(mMockModifier).apply(eq(mMockRequest), any());
}
@@ -151,7 +174,7 @@
mTestHandler.flush();
DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness,
- initialSlowChange);
+ initialSlowChange, STATE_ON);
assertEquals(initialBrightness, state.getBrightness(), FLOAT_TOLERANCE);
assertEquals(PowerManager.BRIGHTNESS_MAX, state.getMaxBrightness(), FLOAT_TOLERANCE);
@@ -175,7 +198,7 @@
mTestHandler.flush();
DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness,
- initialSlowChange);
+ initialSlowChange, STATE_ON);
assertEquals(clampedBrightness, state.getBrightness(), FLOAT_TOLERANCE);
assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
@@ -199,7 +222,7 @@
mTestHandler.flush();
DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness,
- initialSlowChange);
+ initialSlowChange, STATE_ON);
assertEquals(initialBrightness, state.getBrightness(), FLOAT_TOLERANCE);
assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
@@ -223,10 +246,10 @@
mTestHandler.flush();
// first call of clamp method
mClamperController.clamp(mMockRequest, initialBrightness,
- initialSlowChange);
+ initialSlowChange, STATE_ON);
// immediately second call of clamp method
DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness,
- initialSlowChange);
+ initialSlowChange, STATE_ON);
assertEquals(clampedBrightness, state.getBrightness(), FLOAT_TOLERANCE);
assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
@@ -237,6 +260,31 @@
}
@Test
+ public void testAmbientLuxChanges() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor = ArgumentCaptor.forClass(
+ SensorEventListener.class);
+
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ anyInt(), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL))).thenReturn(List.of(mLightSensor));
+
+ float initialBrightness = 0.8f;
+ boolean initialSlowChange = true;
+
+ DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness,
+ initialSlowChange, STATE_ON);
+ assertEquals(initialBrightness, state.getBrightness(), FLOAT_TOLERANCE);
+
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 50, mClock.now()));
+ verify(mMockModifier).setAmbientLux(50);
+
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 300, mClock.now()));
+ verify(mMockModifier).setAmbientLux(300);
+ }
+
+ @Test
public void testStop() {
mClamperController.stop();
verify(mMockModifier).stop();
@@ -245,7 +293,7 @@
private BrightnessClamperController createBrightnessClamperController() {
return new BrightnessClamperController(mTestInjector, mTestHandler, mMockExternalListener,
- mMockDisplayDeviceData, mMockContext, mFlags);
+ mMockDisplayDeviceData, mMockContext, mFlags, mSensorManager);
}
private class TestInjector extends BrightnessClamperController.Injector {
@@ -282,7 +330,7 @@
@Override
List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context,
Handler handler, BrightnessClamperController.ClamperChangeListener listener,
- DisplayDeviceConfig displayDeviceConfig) {
+ DisplayDeviceConfig displayDeviceConfig, SensorManager sensorManager) {
return mModifiers;
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
index 21e066d..d672435 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
@@ -20,8 +20,6 @@
import android.provider.Settings
import android.testing.TestableContext
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED
-import com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
import com.android.server.display.DisplayDeviceConfig
import com.android.server.display.brightness.BrightnessReason
import com.android.server.display.feature.flags.Flags
@@ -54,10 +52,12 @@
@Before
fun setUp() {
modifier =
- BrightnessLowLuxModifier(testHandler,
+ BrightnessLowLuxModifier(
+ testHandler,
mockClamperChangeListener,
context,
- mockDisplayDeviceConfig)
+ mockDisplayDeviceConfig
+ )
// values below transition point (even dimmer range)
// nits: 0.1 -> backlight 0.02 -> brightness -> 0.1
@@ -66,7 +66,7 @@
whenever(mockDisplayDeviceConfig.getBrightnessFromBacklight(/* backlight = */ 0.02f))
.thenReturn(LOW_LUX_BRIGHTNESS)
- // values above transition point (noraml range)
+ // values above transition point (normal range)
// nits: 10 -> backlight 0.2 -> brightness -> 0.3
whenever(mockDisplayDeviceConfig.getBacklightFromNits(/* nits= */ 2f))
.thenReturn(0.15f)
@@ -95,12 +95,12 @@
// test transition point ensures brightness doesn't drop when setting is off.
Settings.Secure.putIntForUser(context.contentResolver,
Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, USER_ID)
- modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
modifier.recalculateLowerBound()
testHandler.flush()
assertThat(modifier.brightnessLowerBound).isEqualTo(TRANSITION_POINT)
assertThat(modifier.brightnessReason).isEqualTo(0) // no reason - ie off
- modifier.onAmbientLuxChange(3000.0f)
+ modifier.setAmbientLux(3000f)
+
testHandler.flush()
assertThat(modifier.isActive).isFalse()
assertThat(modifier.brightnessLowerBound).isEqualTo(TRANSITION_POINT)
@@ -115,8 +115,8 @@
Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID)
Settings.Secure.putFloatForUser(context.contentResolver,
Settings.Secure.EVEN_DIMMER_MIN_NITS, 0.1f, USER_ID)
- modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
- modifier.onAmbientLuxChange(400.0f)
+ modifier.setAmbientLux(400f)
+
testHandler.flush()
assertThat(modifier.isActive).isTrue()
@@ -133,7 +133,6 @@
Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID)
Settings.Secure.putFloatForUser(context.contentResolver,
Settings.Secure.EVEN_DIMMER_MIN_NITS, 10.0f, USER_ID)
- modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
modifier.recalculateLowerBound()
testHandler.flush()
@@ -152,8 +151,8 @@
Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID) // on
Settings.Secure.putFloatForUser(context.contentResolver,
Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, USER_ID)
- modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
- modifier.onAmbientLuxChange(400.0f)
+ modifier.setAmbientLux(400f)
+
testHandler.flush()
assertThat(modifier.isActive).isTrue()
@@ -180,8 +179,8 @@
Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, USER_ID) // off
Settings.Secure.putFloatForUser(context.contentResolver,
Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, USER_ID)
- modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
- modifier.onAmbientLuxChange(400.0f)
+ modifier.setAmbientLux(400f)
+
testHandler.flush()
assertThat(modifier.isActive).isFalse()
@@ -203,15 +202,14 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
- fun testDisabledWhenAutobrightnessIsOff() {
+ fun testEnabledEvenWhenAutobrightnessIsOff() {
// test that high lux prevents low brightness range.
Settings.Secure.putIntForUser(context.contentResolver,
Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID) // on
Settings.Secure.putFloatForUser(context.contentResolver,
Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, USER_ID)
- modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
- modifier.onAmbientLuxChange(400.0f)
+ modifier.setAmbientLux(400f)
testHandler.flush()
assertThat(modifier.isActive).isTrue()
@@ -219,15 +217,13 @@
assertThat(modifier.brightnessReason).isEqualTo(BrightnessReason.MODIFIER_MIN_LUX)
assertThat(modifier.brightnessLowerBound).isEqualTo(LOW_LUX_BRIGHTNESS)
-
- modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_DISABLED)
- modifier.onAmbientLuxChange(400.0f)
+ modifier.setAmbientLux(400f)
testHandler.flush()
- assertThat(modifier.isActive).isFalse()
+ assertThat(modifier.isActive).isTrue()
// Test restriction from lux setting
- assertThat(modifier.brightnessReason).isEqualTo(0)
- assertThat(modifier.brightnessLowerBound).isEqualTo(TRANSITION_POINT)
+ assertThat(modifier.brightnessReason).isEqualTo(BrightnessReason.MODIFIER_MIN_LUX)
+ assertThat(modifier.brightnessLowerBound).isEqualTo(LOW_LUX_BRIGHTNESS)
}
}
diff --git a/services/tests/ondeviceintelligencetests/OWNERS b/services/tests/ondeviceintelligencetests/OWNERS
new file mode 100644
index 0000000..09774f7
--- /dev/null
+++ b/services/tests/ondeviceintelligencetests/OWNERS
@@ -0,0 +1 @@
+file:/core/java/android/app/ondeviceintelligence/OWNERS
diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
index ce2bb95..4460c6a 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
@@ -31,7 +31,6 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
-import android.app.AppOpsManager;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.SensorManager;
@@ -42,6 +41,7 @@
import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.VibrationAttributes;
import android.os.Vibrator;
import android.os.test.TestLooper;
@@ -82,12 +82,8 @@
@Mock private StatusBarManagerInternal mStatusBarManagerInternal;
@Mock private WakeLockLog mWakeLockLog;
- @Mock private IBatteryStats mBatteryStats;
-
@Mock private PowerManagerFlags mPowerManagerFlags;
- @Mock private AppOpsManager mAppOpsManager;
-
private PowerManagerService mService;
private Context mContextSpy;
private Resources mResourcesSpy;
@@ -234,7 +230,7 @@
public void testOnWakeLockListener_RemoteException_NoRethrow() {
when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(true);
createNotifier();
- clearInvocations(mWakeLockLog, mBatteryStats, mAppOpsManager);
+
IWakeLockCallback exceptingCallback = new IWakeLockCallback.Stub() {
@Override public void onStateChanged(boolean enabled) throws RemoteException {
throw new RemoteException("Just testing");
@@ -249,7 +245,6 @@
verifyZeroInteractions(mWakeLockLog);
mTestLooper.dispatchAll();
verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, 1);
-
mNotifier.onWakeLockAcquired(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
"my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
exceptingCallback);
@@ -282,115 +277,6 @@
verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, -1);
}
-
- @Test
- public void testOnWakeLockListener_FullWakeLock_ProcessesOnHandler() throws RemoteException {
- when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(true);
- createNotifier();
-
- IWakeLockCallback exceptingCallback = new IWakeLockCallback.Stub() {
- @Override public void onStateChanged(boolean enabled) throws RemoteException {
- throw new RemoteException("Just testing");
- }
- };
- clearInvocations(mWakeLockLog, mBatteryStats, mAppOpsManager);
-
- final int uid = 1234;
- final int pid = 5678;
-
- // Release the wakelock
- mNotifier.onWakeLockReleased(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag",
- "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
- exceptingCallback);
-
- // No interaction because we expect that to happen in async
- verifyZeroInteractions(mWakeLockLog, mBatteryStats, mAppOpsManager);
-
- // Progressing the looper, and validating all the interactions
- mTestLooper.dispatchAll();
- verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, 1);
- verify(mBatteryStats).noteStopWakelock(uid, pid, "wakelockTag", /* historyTag= */ null,
- BatteryStats.WAKE_TYPE_FULL);
- verify(mAppOpsManager).finishOp(AppOpsManager.OP_WAKE_LOCK, uid,
- "my.package.name", null);
-
- clearInvocations(mWakeLockLog, mBatteryStats, mAppOpsManager);
-
- // Acquire the wakelock
- mNotifier.onWakeLockAcquired(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag",
- "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
- exceptingCallback);
-
- // No interaction because we expect that to happen in async
- verifyNoMoreInteractions(mWakeLockLog, mBatteryStats, mAppOpsManager);
-
- // Progressing the looper, and validating all the interactions
- mTestLooper.dispatchAll();
- verify(mWakeLockLog).onWakeLockAcquired("wakelockTag", uid,
- PowerManager.SCREEN_BRIGHT_WAKE_LOCK, 1);
- verify(mBatteryStats).noteStartWakelock(uid, pid, "wakelockTag", /* historyTag= */ null,
- BatteryStats.WAKE_TYPE_FULL, false);
- verify(mAppOpsManager).startOpNoThrow(AppOpsManager.OP_WAKE_LOCK, uid,
- "my.package.name", false, null, null);
-
- // Test with improveWakelockLatency flag false, hence the wakelock log will run on the same
- // thread
- clearInvocations(mWakeLockLog, mBatteryStats, mAppOpsManager);
- when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(false);
-
- mNotifier.onWakeLockAcquired(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag",
- "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
- exceptingCallback);
- verify(mWakeLockLog).onWakeLockAcquired("wakelockTag", uid,
- PowerManager.SCREEN_BRIGHT_WAKE_LOCK, -1);
-
- mNotifier.onWakeLockReleased(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag",
- "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
- exceptingCallback);
- verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, -1);
- }
-
- @Test
- public void testOnWakeLockListener_FullWakeLock_ProcessesInSync() throws RemoteException {
- createNotifier();
-
- IWakeLockCallback exceptingCallback = new IWakeLockCallback.Stub() {
- @Override public void onStateChanged(boolean enabled) throws RemoteException {
- throw new RemoteException("Just testing");
- }
- };
- clearInvocations(mWakeLockLog, mBatteryStats, mAppOpsManager);
-
- final int uid = 1234;
- final int pid = 5678;
-
- // Release the wakelock
- mNotifier.onWakeLockReleased(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag",
- "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
- exceptingCallback);
-
- verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, -1);
- verify(mBatteryStats).noteStopWakelock(uid, pid, "wakelockTag", /* historyTag= */ null,
- BatteryStats.WAKE_TYPE_FULL);
- verify(mAppOpsManager).finishOp(AppOpsManager.OP_WAKE_LOCK, uid,
- "my.package.name", null);
-
- clearInvocations(mWakeLockLog, mBatteryStats, mAppOpsManager);
-
- // Acquire the wakelock
- mNotifier.onWakeLockAcquired(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag",
- "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
- exceptingCallback);
-
- mTestLooper.dispatchAll();
- verify(mWakeLockLog).onWakeLockAcquired("wakelockTag", uid,
- PowerManager.SCREEN_BRIGHT_WAKE_LOCK, -1);
- verify(mBatteryStats).noteStartWakelock(uid, pid, "wakelockTag", /* historyTag= */ null,
- BatteryStats.WAKE_TYPE_FULL, false);
- verify(mAppOpsManager).startOpNoThrow(AppOpsManager.OP_WAKE_LOCK, uid,
- "my.package.name", false, null, null);
- }
-
private final PowerManagerService.Injector mInjector = new PowerManagerService.Injector() {
@Override
Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
@@ -479,17 +365,13 @@
public WakeLockLog getWakeLockLog(Context context) {
return mWakeLockLog;
}
-
- @Override
- public AppOpsManager getAppOpsManager(Context context) {
- return mAppOpsManager;
- }
};
mNotifier = new Notifier(
mTestLooper.getLooper(),
mContextSpy,
- mBatteryStats,
+ IBatteryStats.Stub.asInterface(ServiceManager.getService(
+ BatteryStats.SERVICE_NAME)),
mInjector.createSuspendBlocker(mService, "testBlocker"),
null,
null,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 15c9bfb..f07e5bc 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -511,7 +511,8 @@
TestableNotificationManagerService.StrongAuthTrackerFake mStrongAuthTracker;
TestableFlagResolver mTestFlagResolver = new TestableFlagResolver();
- @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
1 << 30);
@Mock
@@ -626,7 +627,7 @@
when(mPackageManagerClient.getPackageUidAsUser(any(), anyInt())).thenReturn(mUid);
when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenAnswer(
(Answer<Boolean>) invocation -> {
- // TODO: b/317957802 - This is overly broad and basically makes ANY
+ // TODO: b/317957802 - This is overly broad and basically makes ANY
// isSameApp() check pass, requiring Mockito.reset() for meaningful
// tests! Make it more precise.
Object[] args = invocation.getArguments();
@@ -6892,22 +6893,15 @@
verify(visitor, times(1)).accept(eq(personIcon3.getUri()));
}
- private PendingIntent getPendingIntentWithUri(Uri uri) {
- return PendingIntent.getActivity(mContext, 0,
- new Intent("action", uri),
- PendingIntent.FLAG_IMMUTABLE);
- }
-
@Test
- @EnableFlags({android.app.Flags.FLAG_VISIT_RISKY_URIS})
- public void testVisitUris_callStyle_ongoingCall() {
+ public void testVisitUris_callStyle() {
Icon personIcon = Icon.createWithContentUri("content://media/person");
Icon verificationIcon = Icon.createWithContentUri("content://media/verification");
Person callingPerson = new Person.Builder().setName("Someone")
.setIcon(personIcon)
.build();
- Uri hangUpUri = Uri.parse("content://intent/hangup");
- PendingIntent hangUpIntent = getPendingIntentWithUri(hangUpUri);
+ PendingIntent hangUpIntent = PendingIntent.getActivity(mContext, 0, new Intent(),
+ PendingIntent.FLAG_IMMUTABLE);
Notification n = new Notification.Builder(mContext, "a")
.setStyle(Notification.CallStyle.forOngoingCall(callingPerson, hangUpIntent)
.setVerificationIcon(verificationIcon))
@@ -6920,42 +6914,10 @@
verify(visitor, times(1)).accept(eq(personIcon.getUri()));
verify(visitor, times(1)).accept(eq(verificationIcon.getUri()));
- verify(visitor, times(1)).accept(eq(hangUpUri));
hangUpIntent.cancel();
}
@Test
- @EnableFlags({android.app.Flags.FLAG_VISIT_RISKY_URIS})
- public void testVisitUris_callStyle_incomingCall() {
- Icon personIcon = Icon.createWithContentUri("content://media/person");
- Icon verificationIcon = Icon.createWithContentUri("content://media/verification");
- Person callingPerson = new Person.Builder().setName("Someone")
- .setIcon(personIcon)
- .build();
- Uri answerUri = Uri.parse("content://intent/answer");
- PendingIntent answerIntent = getPendingIntentWithUri(answerUri);
- Uri declineUri = Uri.parse("content://intent/decline");
- PendingIntent declineIntent = getPendingIntentWithUri(declineUri);
- Notification n = new Notification.Builder(mContext, "a")
- .setStyle(Notification.CallStyle.forIncomingCall(callingPerson, declineIntent,
- answerIntent)
- .setVerificationIcon(verificationIcon))
- .setContentTitle("Calling...")
- .setSmallIcon(android.R.drawable.sym_def_app_icon)
- .build();
-
- Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
- n.visitUris(visitor);
-
- verify(visitor, times(1)).accept(eq(personIcon.getUri()));
- verify(visitor, times(1)).accept(eq(verificationIcon.getUri()));
- verify(visitor, times(1)).accept(eq(answerIntent.getIntent().getData()));
- verify(visitor, times(1)).accept(eq(declineUri));
- answerIntent.cancel();
- declineIntent.cancel();
- }
-
- @Test
public void testVisitUris_styleExtrasWithoutStyle() {
Notification.Builder notification = new Notification.Builder(mContext, "a")
.setSmallIcon(android.R.drawable.sym_def_app_icon);
@@ -7001,87 +6963,23 @@
}
@Test
- @EnableFlags({android.app.Flags.FLAG_VISIT_RISKY_URIS})
public void testVisitUris_wearableExtender() {
Icon actionIcon = Icon.createWithContentUri("content://media/action");
Icon wearActionIcon = Icon.createWithContentUri("content://media/wearAction");
- Uri displayIntentUri = Uri.parse("content://intent/display");
- PendingIntent displayIntent = getPendingIntentWithUri(displayIntentUri);
- Uri actionIntentUri = Uri.parse("content://intent/action");
- PendingIntent actionIntent = getPendingIntentWithUri(actionIntentUri);
- Uri wearActionIntentUri = Uri.parse("content://intent/wear");
- PendingIntent wearActionIntent = getPendingIntentWithUri(wearActionIntentUri);
+ PendingIntent intent = PendingIntent.getActivity(mContext, 0, new Intent(),
+ PendingIntent.FLAG_IMMUTABLE);
Notification n = new Notification.Builder(mContext, "a")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
- .addAction(
- new Notification.Action.Builder(actionIcon, "Hey!", actionIntent).build())
- .extend(new Notification.WearableExtender()
- .setDisplayIntent(displayIntent)
- .addAction(new Notification.Action.Builder(wearActionIcon, "Wear!",
- wearActionIntent)
- .build()))
+ .addAction(new Notification.Action.Builder(actionIcon, "Hey!", intent).build())
+ .extend(new Notification.WearableExtender().addAction(
+ new Notification.Action.Builder(wearActionIcon, "Wear!", intent).build()))
.build();
Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
n.visitUris(visitor);
verify(visitor).accept(eq(actionIcon.getUri()));
- verify(visitor, times(1)).accept(eq(actionIntentUri));
verify(visitor).accept(eq(wearActionIcon.getUri()));
- verify(visitor, times(1)).accept(eq(wearActionIntentUri));
- displayIntent.cancel();
- actionIntent.cancel();
- wearActionIntent.cancel();
- }
-
- @Test
- @EnableFlags({android.app.Flags.FLAG_VISIT_RISKY_URIS})
- public void testVisitUris_tvExtender() {
- Uri contentIntentUri = Uri.parse("content://intent/content");
- PendingIntent contentIntent = getPendingIntentWithUri(contentIntentUri);
- Uri deleteIntentUri = Uri.parse("content://intent/delete");
- PendingIntent deleteIntent = getPendingIntentWithUri(deleteIntentUri);
- Notification n = new Notification.Builder(mContext, "a")
- .extend(
- new Notification.TvExtender()
- .setContentIntent(contentIntent)
- .setDeleteIntent(deleteIntent))
- .build();
-
- Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
- n.visitUris(visitor);
-
- verify(visitor, times(1)).accept(eq(contentIntentUri));
- verify(visitor, times(1)).accept(eq(deleteIntentUri));
- contentIntent.cancel();
- deleteIntent.cancel();
- }
-
- @Test
- @EnableFlags({android.app.Flags.FLAG_VISIT_RISKY_URIS})
- public void testVisitUris_carExtender() {
- final String testParticipant = "testParticipant";
- Uri readPendingIntentUri = Uri.parse("content://intent/read");
- PendingIntent readPendingIntent = getPendingIntentWithUri(readPendingIntentUri);
- Uri replyPendingIntentUri = Uri.parse("content://intent/reply");
- PendingIntent replyPendingIntent = getPendingIntentWithUri(replyPendingIntentUri);
- final RemoteInput testRemoteInput = new RemoteInput.Builder("key").build();
-
- Notification n = new Notification.Builder(mContext, "a")
- .extend(new Notification.CarExtender().setUnreadConversation(
- new Notification.CarExtender.Builder(testParticipant)
- .setReadPendingIntent(readPendingIntent)
- .setReplyAction(replyPendingIntent, testRemoteInput)
- .build()))
- .build();
-
- Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
- n.visitUris(visitor);
-
- verify(visitor, times(1)).accept(eq(readPendingIntentUri));
- verify(visitor, times(1)).accept(eq(replyPendingIntentUri));
- readPendingIntent.cancel();
- replyPendingIntent.cancel();
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
index 863cda4..594d6f2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -34,6 +34,7 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.Log;
import android.view.LayoutInflater;
@@ -87,6 +88,7 @@
import javax.annotation.Nullable;
@RunWith(AndroidJUnit4.class)
+@EnableFlags(Flags.FLAG_VISIT_PERSON_URI)
public class NotificationVisitUrisTest extends UiServiceTestCase {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -153,10 +155,6 @@
.put(Notification.Action.Builder.class, "extend")
// Overwrites icon supplied to constructor.
.put(Notification.BubbleMetadata.Builder.class, "setIcon")
- // Overwrites intent supplied to constructor.
- .put(Notification.BubbleMetadata.Builder.class, "setIntent")
- // Overwrites intent supplied to constructor.
- .put(Notification.BubbleMetadata.Builder.class, "setDeleteIntent")
// Discards previously-added actions.
.put(RemoteViews.class, "mergeRemoteViews")
.build();
@@ -172,7 +170,6 @@
@Before
public void setUp() {
mContext = InstrumentationRegistry.getInstrumentation().getContext();
- mSetFlagsRule.enableFlags(Flags.FLAG_VISIT_RISKY_URIS);
}
@After
@@ -700,14 +697,13 @@
}
if (clazz == Intent.class) {
- return new Intent("action", generateUri(where.plus(Intent.class)));
+ return new Intent("action");
}
if (clazz == PendingIntent.class) {
- // PendingIntent can have an Intent with a Uri.
- Uri intentUri = generateUri(where.plus(PendingIntent.class));
- return PendingIntent.getActivity(mContext, 0,
- new Intent("action", intentUri),
+ // PendingIntent can have an Intent with a Uri but those are inaccessible and
+ // not inspected.
+ return PendingIntent.getActivity(mContext, 0, new Intent("action"),
PendingIntent.FLAG_IMMUTABLE);
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index abfb95c..9352c12 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -17,7 +17,16 @@
package com.android.server.notification;
import static android.app.AutomaticZenRule.TYPE_BEDTIME;
+import static android.app.Flags.FLAG_MODES_UI;
+import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+import static android.provider.Settings.Global.ZEN_MODE_OFF;
+import static android.service.notification.Condition.SOURCE_USER_ACTION;
+import static android.service.notification.Condition.STATE_FALSE;
+import static android.service.notification.Condition.STATE_TRUE;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
+import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_NONE;
+import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
+import static android.service.notification.ZenPolicy.STATE_ALLOW;
import static com.google.common.truth.Truth.assertThat;
@@ -33,6 +42,9 @@
import android.content.ComponentName;
import android.net.Uri;
import android.os.Parcel;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.service.notification.Condition;
@@ -43,7 +55,6 @@
import android.util.Xml;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -63,9 +74,13 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Instant;
+import java.util.List;
+
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
public class ZenModeConfigTest extends UiServiceTestCase {
private final String NAME = "name";
@@ -91,6 +106,16 @@
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return FlagsParameterization.allCombinationsOf(
+ FLAG_MODES_UI);
+ }
+
+ public ZenModeConfigTest(FlagsParameterization flags) {
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public final void setUp() {
mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
@@ -101,13 +126,44 @@
ZenModeConfig config = getMutedRingerConfig();
assertTrue(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
- config.allowReminders = true;
+ if (Flags.modesUi()) {
+ config.manualRule.zenPolicy = new ZenPolicy.Builder(config.manualRule.zenPolicy)
+ .allowReminders(true)
+ .build();
+ } else {
+ config.setAllowReminders(true);
+ }
assertFalse(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
- config.allowReminders = false;
+ if (Flags.modesUi()) {
+ config.manualRule.zenPolicy = new ZenPolicy.Builder(config.manualRule.zenPolicy)
+ .allowReminders(false)
+ .build();
+ } else {
+ config.setAllowReminders(false);
+ }
+ assertTrue(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
config.areChannelsBypassingDnd = true;
+ assertTrue(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
+
+ if (Flags.modesUi()) {
+ config.manualRule.zenPolicy = new ZenPolicy.Builder(config.manualRule.zenPolicy)
+ .allowPriorityChannels(true)
+ .build();
+ } else {
+ config.setAllowPriorityChannels(true);
+ }
+
assertFalse(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
+
config.areChannelsBypassingDnd = false;
+ if (Flags.modesUi()) {
+ config.manualRule.zenPolicy = new ZenPolicy.Builder(config.manualRule.zenPolicy)
+ .allowPriorityChannels(false)
+ .build();
+ } else {
+ config.setAllowPriorityChannels(false);
+ }
assertTrue(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
}
@@ -122,6 +178,8 @@
@Test
public void testZenPolicyToNotificationPolicy_classic() {
ZenModeConfig config = getMutedAllConfig();
+ // this shouldn't usually be directly set, but since it's a test that involved the default
+ // policy, calling setNotificationPolicy as a precondition may obscure issues
config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_BADGE;
// Explicitly allow conversations from priority senders to make sure that goes through
@@ -154,8 +212,9 @@
@Test
public void testZenPolicyToNotificationPolicy() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
ZenModeConfig config = getMutedAllConfig();
+ // this shouldn't usually be directly set, but since it's a test that involved the default
+ // policy, calling setNotificationPolicy as a precondition may obscure issues
config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_BADGE;
// Explicitly allow conversations from priority senders to make sure that goes through
@@ -194,14 +253,16 @@
@Test
public void testZenPolicyToNotificationPolicy_unsetChannelsTakesDefault() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
ZenModeConfig config = new ZenModeConfig();
ZenPolicy zenPolicy = new ZenPolicy.Builder().build();
// When allowChannels is not set to anything in the ZenPolicy builder, make sure it takes
// the default value from the zen mode config.
Policy policy = config.toNotificationPolicy(zenPolicy);
- assertEquals(config.allowPriorityChannels, policy.allowPriorityChannels());
+ assertEquals(Flags.modesUi()
+ ? config.manualRule.zenPolicy.getPriorityChannelsAllowed() == STATE_ALLOW
+ : config.isAllowPriorityChannels(),
+ policy.allowPriorityChannels());
}
@Test
@@ -219,18 +280,22 @@
.build();
ZenModeConfig config = getMutedAllConfig();
- config.allowAlarms = true;
- config.allowReminders = true;
- config.allowEvents = true;
- config.allowCalls = true;
- config.allowCallsFrom = Policy.PRIORITY_SENDERS_CONTACTS;
- config.allowMessages = true;
- config.allowMessagesFrom = Policy.PRIORITY_SENDERS_STARRED;
- config.allowConversations = false;
- config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_BADGE;
- config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_LIGHTS;
- config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_AMBIENT;
- ZenPolicy actual = config.toZenPolicy();
+ if (Flags.modesUi()) {
+ config.manualRule.zenPolicy = expected.copy();
+ } else {
+ config.setAllowAlarms(true);
+ config.setAllowReminders(true);
+ config.setAllowEvents(true);
+ config.setAllowCalls(true);
+ config.setAllowCallsFrom(Policy.PRIORITY_SENDERS_CONTACTS);
+ config.setAllowMessages(true);
+ config.setAllowMessagesFrom(Policy.PRIORITY_SENDERS_STARRED);
+ config.setAllowConversationsFrom(CONVERSATION_SENDERS_NONE);
+ config.setSuppressedVisualEffects(config.getSuppressedVisualEffects()
+ | Policy.SUPPRESSED_EFFECT_BADGE | Policy.SUPPRESSED_EFFECT_LIGHTS
+ | Policy.SUPPRESSED_EFFECT_AMBIENT);
+ }
+ ZenPolicy actual = config.getZenPolicy();
assertEquals(expected.getVisualEffectBadge(), actual.getVisualEffectBadge());
assertEquals(expected.getPriorityCategoryAlarms(), actual.getPriorityCategoryAlarms());
@@ -247,7 +312,6 @@
@Test
public void testZenConfigToZenPolicy() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
ZenPolicy expected = new ZenPolicy.Builder()
.allowAlarms(true)
.allowReminders(true)
@@ -262,19 +326,24 @@
.build();
ZenModeConfig config = getMutedAllConfig();
- config.allowAlarms = true;
- config.allowReminders = true;
- config.allowEvents = true;
- config.allowCalls = true;
- config.allowCallsFrom = Policy.PRIORITY_SENDERS_CONTACTS;
- config.allowMessages = true;
- config.allowMessagesFrom = Policy.PRIORITY_SENDERS_STARRED;
- config.allowConversations = false;
- config.allowPriorityChannels = false;
- config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_BADGE;
- config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_LIGHTS;
- config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_AMBIENT;
- ZenPolicy actual = config.toZenPolicy();
+ if (Flags.modesUi()) {
+ config.manualRule.zenPolicy = expected.copy();
+ } else {
+ config.setAllowAlarms(true);
+ config.setAllowReminders(true);
+
+ config.setAllowEvents(true);
+ config.setAllowCalls(true);
+ config.setAllowCallsFrom(Policy.PRIORITY_SENDERS_CONTACTS);
+ config.setAllowMessages(true);
+ config.setAllowMessagesFrom(Policy.PRIORITY_SENDERS_STARRED);
+ config.setAllowConversationsFrom(CONVERSATION_SENDERS_NONE);
+ config.setAllowPriorityChannels(false);
+ config.setSuppressedVisualEffects(config.getSuppressedVisualEffects()
+ | Policy.SUPPRESSED_EFFECT_BADGE | Policy.SUPPRESSED_EFFECT_LIGHTS
+ | Policy.SUPPRESSED_EFFECT_AMBIENT);
+ }
+ ZenPolicy actual = config.getZenPolicy();
assertEquals(expected.getVisualEffectBadge(), actual.getVisualEffectBadge());
assertEquals(expected.getPriorityCategoryAlarms(), actual.getPriorityCategoryAlarms());
@@ -296,20 +365,64 @@
assertTrue(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
assertTrue(ZenModeConfig.areAllZenBehaviorSoundsMuted(config));
- config.allowReminders = true;
+ if (Flags.modesUi()) {
+ config.manualRule.zenPolicy = new ZenPolicy.Builder(config.manualRule.zenPolicy)
+ .allowReminders(true)
+ .build();
+ } else {
+ config.setAllowReminders(true);
+ }
assertFalse(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
assertFalse(ZenModeConfig.areAllZenBehaviorSoundsMuted(config));
- config.allowReminders = false;
+
+ if (Flags.modesUi()) {
+ config.manualRule.zenPolicy = new ZenPolicy.Builder(config.manualRule.zenPolicy)
+ .allowReminders(false)
+ .build();
+ } else {
+ config.setAllowReminders(false);
+ }
+
+ assertTrue(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
+ assertTrue(ZenModeConfig.areAllZenBehaviorSoundsMuted(config));
config.areChannelsBypassingDnd = true;
+ if (Flags.modesUi()) {
+ config.manualRule.zenPolicy = new ZenPolicy.Builder(config.manualRule.zenPolicy)
+ .allowPriorityChannels(true)
+ .build();
+ } else {
+ config.setAllowPriorityChannels(true);
+ }
+
assertFalse(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
assertFalse(ZenModeConfig.areAllZenBehaviorSoundsMuted(config));
- config.areChannelsBypassingDnd = false;
- config.allowAlarms = true;
+ config.areChannelsBypassingDnd = false;
+ if (Flags.modesUi()) {
+ config.manualRule.zenPolicy = new ZenPolicy.Builder(config.manualRule.zenPolicy)
+ .allowPriorityChannels(false)
+ .build();
+ } else {
+ config.setAllowPriorityChannels(false);
+ }
+
+ if (Flags.modesUi()) {
+ config.manualRule.zenPolicy = new ZenPolicy.Builder(config.manualRule.zenPolicy)
+ .allowAlarms(true)
+ .build();
+ } else {
+ config.setAllowAlarms(true);
+ }
assertTrue(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
assertFalse(ZenModeConfig.areAllZenBehaviorSoundsMuted(config));
- config.allowAlarms = false;
+ if (Flags.modesUi()) {
+ config.manualRule.zenPolicy = new ZenPolicy.Builder(config.manualRule.zenPolicy)
+ .allowAlarms(false)
+ .build();
+ } else {
+ config.setAllowAlarms(false);
+ }
assertTrue(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
assertTrue(ZenModeConfig.areAllZenBehaviorSoundsMuted(config));
@@ -375,8 +488,6 @@
@Test
public void testWriteToParcel() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
-
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
rule.configurationActivity = CONFIG_ACTIVITY;
rule.component = OWNER;
@@ -473,8 +584,6 @@
@Test
public void testRuleXml() throws Exception {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
-
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
rule.configurationActivity = CONFIG_ACTIVITY;
rule.component = OWNER;
@@ -719,8 +828,6 @@
@Test
public void testZenPolicyXml() throws Exception {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
-
ZenPolicy policy = new ZenPolicy.Builder()
.allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS)
.allowMessages(ZenPolicy.PEOPLE_TYPE_NONE)
@@ -770,63 +877,173 @@
fromXml.getVisualEffectNotificationList());
}
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ public void testisManualActive_stateTrue() {
+ ZenModeConfig config = getMutedAllConfig();
+ final ZenModeConfig.ZenRule newRule = new ZenModeConfig.ZenRule();
+ newRule.type = AutomaticZenRule.TYPE_OTHER;
+ newRule.enabled = true;
+ newRule.conditionId = Uri.EMPTY;
+ newRule.allowManualInvocation = true;
+ config.manualRule = newRule;
+ config.manualRule.pkg = "android";
+ config.manualRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ config.manualRule.condition = new Condition(Uri.EMPTY, "", STATE_TRUE, SOURCE_USER_ACTION);
+
+ assertThat(config.isManualActive()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ public void testisManualActive_stateFalse() {
+ ZenModeConfig config = getMutedAllConfig();
+ final ZenModeConfig.ZenRule newRule = new ZenModeConfig.ZenRule();
+ newRule.type = AutomaticZenRule.TYPE_OTHER;
+ newRule.enabled = true;
+ newRule.conditionId = Uri.EMPTY;
+ newRule.allowManualInvocation = true;
+ config.manualRule = newRule;
+ config.manualRule.pkg = "android";
+ config.manualRule.zenMode = ZEN_MODE_OFF;
+ config.manualRule.condition = new Condition(Uri.EMPTY, "", STATE_FALSE, SOURCE_USER_ACTION);
+
+ assertThat(config.isManualActive()).isFalse();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ public void testisManualActive_noState() {
+ ZenModeConfig config = getMutedAllConfig();
+ final ZenModeConfig.ZenRule newRule = new ZenModeConfig.ZenRule();
+ newRule.type = AutomaticZenRule.TYPE_OTHER;
+ newRule.enabled = true;
+ newRule.conditionId = Uri.EMPTY;
+ newRule.allowManualInvocation = true;
+ config.manualRule = newRule;
+ config.manualRule.pkg = "android";
+ config.manualRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+
+ assertThat(config.isManualActive()).isTrue();
+ }
+
+ @Test
+ public void testisManualActive_noRule() {
+ ZenModeConfig config = getMutedAllConfig();
+
+ assertThat(config.isManualActive()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ public void testRuleXml_manual_upgrade() throws Exception {
+ ZenModeConfig config = getMutedAllConfig();
+ final ZenModeConfig.ZenRule newRule = new ZenModeConfig.ZenRule();
+ newRule.type = AutomaticZenRule.TYPE_OTHER;
+ newRule.enabled = true;
+ newRule.conditionId = Uri.EMPTY;
+ newRule.allowManualInvocation = true;
+ newRule.pkg = "android";
+ newRule.zenMode = ZEN_MODE_OFF;
+ config.manualRule = newRule;
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ writeRuleXml(newRule, baos);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ZenModeConfig.ZenRule fromXml = readRuleXml(bais);
+
+ assertThat(fromXml.zenPolicy).isEqualTo(config.getZenPolicy());
+ }
+
private ZenModeConfig getMutedRingerConfig() {
ZenModeConfig config = new ZenModeConfig();
- // Allow alarms, media
- config.allowAlarms = true;
- config.allowMedia = true;
- // All sounds that respect the ringer are not allowed
- config.allowSystem = false;
- config.allowCalls = false;
- config.allowRepeatCallers = false;
- config.allowMessages = false;
- config.allowReminders = false;
- config.allowEvents = false;
+ if (Flags.modesUi()) {
+ config.manualRule.zenPolicy = new ZenPolicy.Builder()
+ .disallowAllSounds()
+ .allowAlarms(true)
+ .allowMedia(true)
+ .allowPriorityChannels(false)
+ .showAllVisualEffects()
+ .build();
+ } else {
+ // Allow alarms, media
+ config.setAllowAlarms(true);
+ config.setAllowMedia(true);
+
+ // All sounds that respect the ringer are not allowed
+ config.setAllowSystem(false);
+ config.setAllowCalls(false);
+ config.setAllowRepeatCallers(false);
+ config.setAllowMessages(false);
+ config.setAllowReminders(false);
+ config.setAllowEvents(false);
+ config.setSuppressedVisualEffects(0);
+ config.setAllowPriorityChannels(false);
+ }
config.areChannelsBypassingDnd = false;
- config.suppressedVisualEffects = 0;
-
return config;
}
private ZenModeConfig getCustomConfig() {
ZenModeConfig config = new ZenModeConfig();
- // Some sounds allowed
- config.allowAlarms = true;
- config.allowMedia = false;
- config.allowSystem = false;
- config.allowCalls = true;
- config.allowRepeatCallers = true;
- config.allowMessages = false;
- config.allowReminders = false;
- config.allowEvents = false;
- config.areChannelsBypassingDnd = false;
- config.allowCallsFrom = ZenModeConfig.SOURCE_ANYONE;
- config.allowMessagesFrom = ZenModeConfig.SOURCE_ANYONE;
- config.allowConversations = true;
- config.allowConversationsFrom = CONVERSATION_SENDERS_IMPORTANT;
- config.suppressedVisualEffects = 0;
+ if (Flags.modesUi()) {
+ config.manualRule.zenPolicy = new ZenPolicy.Builder()
+ .disallowAllSounds()
+ .allowAlarms(true)
+ .allowCalls(PEOPLE_TYPE_ANYONE)
+ .allowRepeatCallers(true)
+ .allowConversations(CONVERSATION_SENDERS_IMPORTANT)
+ .allowPriorityChannels(true)
+ .showAllVisualEffects()
+ .build();
+ } else {
+ // Some sounds allowed
+ config.setAllowAlarms(true);
+ config.setAllowMedia(false);
+ config.setAllowSystem(false);
+ config.setAllowCalls(true);
+ config.setAllowRepeatCallers(true);
+ config.setAllowMessages(false);
+ config.setAllowReminders(false);
+ config.setAllowEvents(false);
+ config.setAllowCallsFrom(ZenModeConfig.SOURCE_ANYONE);
+ config.setAllowMessagesFrom(ZenModeConfig.SOURCE_ANYONE);
+ config.setAllowConversations(true);
+ config.setAllowConversationsFrom(CONVERSATION_SENDERS_IMPORTANT);
+ config.setSuppressedVisualEffects(0);
+ config.setAllowPriorityChannels(true);
+ }
+ config.areChannelsBypassingDnd = false;
return config;
}
private ZenModeConfig getMutedAllConfig() {
ZenModeConfig config = new ZenModeConfig();
- // No sounds allowed
- config.allowAlarms = false;
- config.allowMedia = false;
- config.allowSystem = false;
- config.allowCalls = false;
- config.allowRepeatCallers = false;
- config.allowMessages = false;
- config.allowReminders = false;
- config.allowEvents = false;
- config.areChannelsBypassingDnd = false;
- config.allowConversations = false;
- config.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_NONE;
- config.suppressedVisualEffects = 0;
+ if (Flags.modesUi()) {
+ config.manualRule.zenPolicy = new ZenPolicy.Builder()
+ .disallowAllSounds()
+ .showAllVisualEffects()
+ .allowPriorityChannels(false)
+ .build();
+ } else {
+ // No sounds allowed
+ config.setAllowAlarms(false);
+ config.setAllowMedia(false);
+ config.setAllowSystem(false);
+ config.setAllowCalls(false);
+ config.setAllowRepeatCallers(false);
+ config.setAllowMessages(false);
+ config.setAllowReminders(false);
+ config.setAllowEvents(false);
+ config.setAllowConversations(false);
+ config.setAllowConversationsFrom(CONVERSATION_SENDERS_NONE);
+ config.setSuppressedVisualEffects(0);
+ }
+ config.areChannelsBypassingDnd = false;
return config;
}
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 2e64645..26a13cb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
@@ -16,6 +16,8 @@
package com.android.server.notification;
+import static android.app.Flags.FLAG_MODES_UI;
+
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
@@ -27,8 +29,10 @@
import android.app.AutomaticZenRule;
import android.app.Flags;
+import android.app.NotificationManager;
import android.content.ComponentName;
import android.net.Uri;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.service.notification.Condition;
@@ -37,7 +41,6 @@
import android.service.notification.ZenModeDiff;
import android.service.notification.ZenModeDiff.RuleDiff;
import android.service.notification.ZenPolicy;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArrayMap;
@@ -60,8 +63,11 @@
import java.util.Optional;
import java.util.Set;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(ParameterizedAndroidJunit4.class)
@TestableLooper.RunWithLooper
public class ZenModeDiffTest extends UiServiceTestCase {
// Base set of exempt fields independent of fields that are enabled/disabled via flags.
@@ -91,6 +97,16 @@
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return FlagsParameterization.allCombinationsOf(
+ FLAG_MODES_UI);
+ }
+
+ public ZenModeDiffTest(FlagsParameterization flags) {
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Test
public void testRuleDiff_addRemoveSame() {
// Test add, remove, and both sides same
@@ -220,21 +236,35 @@
ZenModeConfig c2 = new ZenModeConfig();
// set c1 and c2 to have some different senders
- c1.allowMessagesFrom = ZenModeConfig.SOURCE_STAR;
- c2.allowMessagesFrom = ZenModeConfig.SOURCE_CONTACT;
- c1.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
- c2.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_NONE;
+ NotificationManager.Policy c1Policy = c1.toNotificationPolicy();
+ c1.applyNotificationPolicy(new NotificationManager.Policy(
+ c1Policy.priorityCategories, c1Policy.priorityCallSenders,
+ c1Policy.PRIORITY_SENDERS_STARRED, c1Policy.suppressedVisualEffects,
+ c1Policy.state, ZenPolicy.CONVERSATION_SENDERS_IMPORTANT));
+
+ NotificationManager.Policy c2Policy = c1.toNotificationPolicy();
+ c2.applyNotificationPolicy(new NotificationManager.Policy(
+ c2Policy.priorityCategories, c2Policy.priorityCallSenders,
+ c2Policy.PRIORITY_SENDERS_CONTACTS, c2Policy.suppressedVisualEffects,
+ c2Policy.state, ZenPolicy.CONVERSATION_SENDERS_NONE));
ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2);
assertTrue(d.hasDiff());
- // Diff in top-level fields
- assertTrue(d.getDiffForField("allowMessagesFrom").hasDiff());
- assertTrue(d.getDiffForField("allowConversationsFrom").hasDiff());
+ if (!Flags.modesUi()) {
+ // Diff in top-level fields
+ assertTrue(d.getDiffForField("allowMessagesFrom").hasDiff());
+ assertTrue(d.getDiffForField("allowConversationsFrom").hasDiff());
- // Bonus testing of stringification of people senders and conversation senders
- assertTrue(d.toString().contains("allowMessagesFrom:stars->contacts"));
- assertTrue(d.toString().contains("allowConversationsFrom:important->none"));
+ // Bonus testing of stringification of people senders and conversation senders
+ assertTrue(d.toString().contains("allowMessagesFrom:stars->contacts"));
+ assertTrue(d.toString().contains("allowConversationsFrom:important->none"));
+ } else {
+ RuleDiff r = d.getManualRuleDiff();
+ assertNotNull(r);
+ ZenModeDiff.FieldDiff p = r.getDiffForField(RuleDiff.FIELD_ZEN_POLICY);
+ assertNotNull(p);
+ }
}
@Test
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 72ace84..201b286 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -18,6 +18,8 @@
import static android.app.AutomaticZenRule.TYPE_BEDTIME;
import static android.app.AutomaticZenRule.TYPE_IMMERSIVE;
+import static android.app.Flags.FLAG_MODES_API;
+import static android.app.Flags.FLAG_MODES_UI;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DEACTIVATED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DISABLED;
@@ -41,11 +43,9 @@
import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS;
import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_STARRED;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
+import static android.app.NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND;
+import static android.app.NotificationManager.Policy.STATE_PRIORITY_CHANNELS_BLOCKED;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
@@ -64,6 +64,11 @@
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
+import static android.service.notification.ZenPolicy.VISUAL_EFFECT_AMBIENT;
+import static android.service.notification.ZenPolicy.VISUAL_EFFECT_BADGE;
+import static android.service.notification.ZenPolicy.VISUAL_EFFECT_FULL_SCREEN_INTENT;
+import static android.service.notification.ZenPolicy.VISUAL_EFFECT_LIGHTS;
+import static android.service.notification.ZenPolicy.VISUAL_EFFECT_PEEK;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.LOG_DND_STATE_EVENTS;
import static com.android.os.dnd.DNDProtoEnums.PEOPLE_STARRED;
@@ -75,6 +80,7 @@
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -131,6 +137,7 @@
import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.provider.Settings.Global;
@@ -140,7 +147,6 @@
import android.service.notification.ZenAdapters;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
import android.service.notification.ZenModeConfig.ScheduleInfo;
import android.service.notification.ZenModeConfig.ZenRule;
import android.service.notification.ZenModeDiff;
@@ -172,8 +178,6 @@
import com.google.common.truth.Correspondence;
import com.google.common.util.concurrent.SettableFuture;
import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.testing.junit.testparameterinjector.TestParameter;
-import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import org.junit.Before;
import org.junit.Rule;
@@ -202,9 +206,12 @@
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
-@RunWith(TestParameterInjector.class)
+@RunWith(ParameterizedAndroidJunit4.class)
@TestableLooper.RunWithLooper
public class ZenModeHelperTest extends UiServiceTestCase {
@@ -268,6 +275,16 @@
ZenModeEventLoggerFake mZenModeEventLogger;
private String mPkg;
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return FlagsParameterization.progressionOf(FLAG_MODES_API,
+ FLAG_MODES_UI);
+ }
+
+ public ZenModeHelperTest(FlagsParameterization flags) {
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setUp() throws PackageManager.NameNotFoundException {
MockitoAnnotations.initMocks(this);
@@ -677,7 +694,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void testTotalSilence_consolidatedPolicyDisallowsAll() {
// Start with zen mode off just to make sure global/manual mode isn't doing anything.
mZenModeHelper.mZenMode = ZEN_MODE_OFF;
@@ -711,7 +728,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void testAlarmsOnly_consolidatedPolicyOnlyAllowsAlarmsAndMedia() {
// Start with zen mode off just to make sure global/manual mode isn't doing anything.
mZenModeHelper.mZenMode = ZEN_MODE_OFF;
@@ -802,15 +819,11 @@
// 1. Current ringer is normal
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
// Set zen to priority-only with all notification sounds muted (so ringer will be muted)
+ Policy totalSilence = new Policy(0,0,0);
+ mZenModeHelper.setNotificationPolicy(totalSilence, UPDATE_ORIGIN_APP, 1);
mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- mZenModeHelper.mConfig.allowReminders = false;
- mZenModeHelper.mConfig.allowCalls = false;
- mZenModeHelper.mConfig.allowMessages = false;
- mZenModeHelper.mConfig.allowEvents = false;
- mZenModeHelper.mConfig.allowRepeatCallers = false;
- mZenModeHelper.mConfig.allowConversations = false;
- // 2. apply priority only zen - verify ringer is unchanged
+ // 2. verify ringer is unchanged
mZenModeHelper.applyZenToRingerMode();
verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_SILENT,
mZenModeHelper.TAG);
@@ -844,9 +857,8 @@
public void testRingerAffectedStreamsPriorityOnly() {
// in priority only mode:
// ringtone, notification and system streams are affected by ringer mode
- mZenModeHelper.mConfig.allowAlarms = true;
- mZenModeHelper.mConfig.allowReminders = true;
- mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
+ UPDATE_ORIGIN_APP, "test", "caller", 1);
ZenModeHelper.RingerModeDelegate ringerModeDelegateRingerMuted =
mZenModeHelper.new RingerModeDelegate();
@@ -862,13 +874,9 @@
// even when ringer is muted (since all ringer sounds cannot bypass DND),
// system stream is still affected by ringer mode
- mZenModeHelper.mConfig.allowSystem = false;
- mZenModeHelper.mConfig.allowReminders = false;
- mZenModeHelper.mConfig.allowCalls = false;
- mZenModeHelper.mConfig.allowMessages = false;
- mZenModeHelper.mConfig.allowEvents = false;
- mZenModeHelper.mConfig.allowRepeatCallers = false;
- mZenModeHelper.mConfig.allowConversations = false;
+ mZenModeHelper.setNotificationPolicy(new Policy(0,0,0), UPDATE_ORIGIN_APP, 1);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
+ UPDATE_ORIGIN_APP, "test", "caller", 1);
ZenModeHelper.RingerModeDelegate ringerModeDelegateRingerNotMuted =
mZenModeHelper.new RingerModeDelegate();
@@ -885,7 +893,7 @@
}
@Test
- public void testZenSetInternalRinger_NotAllPriorityNotificationSoundsMuted_StartNormal() {
+ public void applyZenToRingerMode_ZEN_MODE_IMPORTANT_INTERRUPTIONS() {
AudioManagerInternal mAudioManager = mock(AudioManagerInternal.class);
mZenModeHelper.mAudioManager = mAudioManager;
Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_RINGER_LEVEL,
@@ -894,7 +902,6 @@
// 1. Current ringer is normal
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- mZenModeHelper.mConfig.allowReminders = true;
// 2. apply priority only zen - verify ringer is normal
mZenModeHelper.applyZenToRingerMode();
@@ -919,7 +926,6 @@
// 1. Current ringer is silent
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- mZenModeHelper.mConfig.allowReminders = true;
// 2. apply priority only zen - verify ringer is silent
mZenModeHelper.applyZenToRingerMode();
@@ -945,7 +951,6 @@
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
// Set zen to priority-only with all notification sounds muted (so ringer will be muted)
mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- mZenModeHelper.mConfig.allowReminders = true;
// 2. apply priority only zen - verify zen will still be normal
mZenModeHelper.applyZenToRingerMode();
@@ -977,11 +982,9 @@
// apply zen off multiple times - verify ringer is not set to normal
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
- mZenModeHelper.mZenMode = ZEN_MODE_OFF;
- mZenModeHelper.mConfig = null; // will evaluate config to zen mode off
for (int i = 0; i < 3; i++) {
- // if zen doesn't change, zen should not reapply itself to the ringer
- mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, Uri.EMPTY,
+ UPDATE_ORIGIN_APP, "test", "caller", 1);
}
verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
mZenModeHelper.TAG);
@@ -991,8 +994,6 @@
public void testSilentRingerSavedOnZenOff_startsZenOn() {
AudioManagerInternal mAudioManager = mock(AudioManagerInternal.class);
mZenModeHelper.mAudioManager = mAudioManager;
- mZenModeHelper.mZenMode = ZEN_MODE_OFF;
- mZenModeHelper.mConfig = new ZenModeConfig();
// previously set silent ringer
ZenModeHelper.RingerModeDelegate ringerModeDelegate =
@@ -1003,12 +1004,12 @@
assertEquals(AudioManager.RINGER_MODE_SILENT, Global.getInt(mContext.getContentResolver(),
Global.ZEN_MODE_RINGER_LEVEL, AudioManager.RINGER_MODE_NORMAL));
- // apply zen off multiple times - verify ringer is not set to normal
+ // apply zen on multiple times - verify ringer is not set to normal
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
- mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
for (int i = 0; i < 3; i++) {
// if zen doesn't change, zen should not reapply itself to the ringer
- mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, Uri.EMPTY,
+ UPDATE_ORIGIN_APP, "test", "caller", 1);
}
verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
mZenModeHelper.TAG);
@@ -1018,8 +1019,6 @@
public void testVibrateRingerSavedOnZenOff_startsZenOn() {
AudioManagerInternal mAudioManager = mock(AudioManagerInternal.class);
mZenModeHelper.mAudioManager = mAudioManager;
- mZenModeHelper.mZenMode = ZEN_MODE_OFF;
- mZenModeHelper.mConfig = new ZenModeConfig();
// previously set silent ringer
ZenModeHelper.RingerModeDelegate ringerModeDelegate =
@@ -1032,10 +1031,10 @@
// apply zen off multiple times - verify ringer is not set to normal
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
- mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
for (int i = 0; i < 3; i++) {
// if zen doesn't change, zen should not reapply itself to the ringer
- mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, Uri.EMPTY,
+ UPDATE_ORIGIN_APP, "test", "caller", 1);
}
verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
mZenModeHelper.TAG);
@@ -1066,22 +1065,18 @@
@Test
public void testParcelConfig() {
- mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- mZenModeHelper.mConfig.allowAlarms = false;
- mZenModeHelper.mConfig.allowMedia = false;
- mZenModeHelper.mConfig.allowSystem = false;
- mZenModeHelper.mConfig.allowReminders = true;
- mZenModeHelper.mConfig.allowCalls = true;
- mZenModeHelper.mConfig.allowMessages = true;
- mZenModeHelper.mConfig.allowEvents = true;
- mZenModeHelper.mConfig.allowRepeatCallers = true;
- mZenModeHelper.mConfig.allowConversations = true;
- mZenModeHelper.mConfig.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_ANYONE;
- mZenModeHelper.mConfig.suppressedVisualEffects = SUPPRESSED_EFFECT_BADGE;
- mZenModeHelper.mConfig.manualRule = new ZenModeConfig.ZenRule();
- mZenModeHelper.mConfig.manualRule.component = new ComponentName("a", "a");
- mZenModeHelper.mConfig.manualRule.enabled = true;
- mZenModeHelper.mConfig.manualRule.snoozing = true;
+ mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_EVENTS
+ | PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_REPEAT_CALLERS
+ | PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED,
+ PRIORITY_SENDERS_STARRED, 0, CONVERSATION_SENDERS_ANYONE), UPDATE_ORIGIN_UNKNOWN,
+ 1);
+ mZenModeHelper.setManualZenRuleDeviceEffects(new ZenDeviceEffects.Builder()
+ .setShouldDimWallpaper(true)
+ .setShouldDisplayGrayscale(true)
+ .setShouldUseNightMode(true)
+ .build(), UPDATE_ORIGIN_UNKNOWN, "test", 1);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
+ UPDATE_ORIGIN_UNKNOWN, "test", "me", 1);
ZenModeConfig actual = mZenModeHelper.mConfig.copy();
@@ -1090,24 +1085,17 @@
@Test
public void testWriteXml() throws Exception {
- mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- mZenModeHelper.mConfig.allowAlarms = false;
- mZenModeHelper.mConfig.allowMedia = false;
- mZenModeHelper.mConfig.allowSystem = false;
- mZenModeHelper.mConfig.allowReminders = true;
- mZenModeHelper.mConfig.allowCalls = true;
- mZenModeHelper.mConfig.allowMessages = true;
- mZenModeHelper.mConfig.allowEvents = true;
- mZenModeHelper.mConfig.allowRepeatCallers = true;
- mZenModeHelper.mConfig.allowConversations = true;
- mZenModeHelper.mConfig.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_ANYONE;
- mZenModeHelper.mConfig.suppressedVisualEffects = SUPPRESSED_EFFECT_BADGE;
- mZenModeHelper.mConfig.manualRule = new ZenModeConfig.ZenRule();
- mZenModeHelper.mConfig.manualRule.zenMode =
- ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- mZenModeHelper.mConfig.manualRule.component = new ComponentName("a", "a");
- mZenModeHelper.mConfig.manualRule.pkg = "a";
- mZenModeHelper.mConfig.manualRule.enabled = true;
+ mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_EVENTS
+ | PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_REPEAT_CALLERS
+ | PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED,
+ PRIORITY_SENDERS_STARRED, SUPPRESSED_EFFECT_BADGE, CONVERSATION_SENDERS_ANYONE),
+ UPDATE_ORIGIN_UNKNOWN, 1);
+ mZenModeHelper.setManualZenRuleDeviceEffects(new ZenDeviceEffects.Builder()
+ .setShouldDimWallpaper(true)
+ .setShouldDisplayGrayscale(true)
+ .build(), UPDATE_ORIGIN_UNKNOWN, "test", 1);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
+ UPDATE_ORIGIN_UNKNOWN, "test", "me", 1);
ZenModeConfig expected = mZenModeHelper.mConfig.copy();
if (Flags.modesUi()) {
@@ -1127,10 +1115,10 @@
@Test
public void testProto() throws InvalidProtocolBufferException {
- mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- // existence of manual rule means it should be in output
- mZenModeHelper.mConfig.manualRule = new ZenModeConfig.ZenRule();
- mZenModeHelper.mConfig.manualRule.pkg = "android"; // system
+ mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ Flags.modesApi() ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ null, "test", CUSTOM_PKG_UID);
+
mZenModeHelper.mConfig.automaticRules = new ArrayMap<>(); // no automatic rules
List<String> ids = new ArrayList<>();
@@ -1151,7 +1139,6 @@
assertTrue(cfg.getEnabled());
assertFalse(cfg.getChannelsBypassing());
}
- assertEquals(Process.SYSTEM_UID, cfg.getUid());
String name = cfg.getId();
assertTrue("unexpected rule id", ids.contains(name));
ids.remove(name);
@@ -1255,10 +1242,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void testProtoWithAutoRuleCustomPolicy() throws Exception {
- // allowChannels is only valid under modes_api.
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
-
setupZenConfig();
// clear any automatic rules just to make sure
mZenModeHelper.mConfig.automaticRules = new ArrayMap<>();
@@ -1299,7 +1284,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void testProtoWithAutoRuleWithModifiedFields() throws Exception {
setupZenConfig();
mZenModeHelper.mConfig.automaticRules = new ArrayMap<>();
@@ -1384,8 +1369,8 @@
public void testProtoWithManualRule() throws Exception {
setupZenConfig();
mZenModeHelper.mConfig.automaticRules = getCustomAutomaticRules();
- mZenModeHelper.mConfig.manualRule = new ZenModeConfig.ZenRule();
- mZenModeHelper.mConfig.manualRule.enabled = true;
+ mZenModeHelper.setManualZenMode(INTERRUPTION_FILTER_PRIORITY, Uri.EMPTY, UPDATE_ORIGIN_APP,
+ "test", "me", 1);
List<StatsEvent> events = new LinkedList<>();
mZenModeHelper.pullRules(events);
@@ -1408,15 +1393,15 @@
// Setup configs for user 10 and 11.
setupZenConfig();
ZenModeConfig config10 = mZenModeHelper.mConfig.copy();
+ Policy policy = new Policy(PRIORITY_CATEGORY_MEDIA | PRIORITY_CATEGORY_ALARMS, 0, 0);
+ config10.applyNotificationPolicy(policy);
config10.user = 10;
- config10.allowAlarms = true;
- config10.allowMedia = true;
mZenModeHelper.setConfig(config10, null, UPDATE_ORIGIN_INIT, "writeXml",
Process.SYSTEM_UID);
ZenModeConfig config11 = mZenModeHelper.mConfig.copy();
config11.user = 11;
- config11.allowAlarms = false;
- config11.allowMedia = false;
+ policy = new Policy(0, 0, 0);
+ config11.applyNotificationPolicy(policy);
mZenModeHelper.setConfig(config11, null, UPDATE_ORIGIN_INIT, "writeXml",
Process.SYSTEM_UID);
@@ -1583,8 +1568,9 @@
}
@Test
- public void testReadXmlRulesNotOverriden() throws Exception {
+ public void testReadXmlRulesNotOverridden() throws Exception {
setupZenConfig();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
// automatic zen rule is enabled on upgrade so rules should not be overriden to default
ArrayMap<String, ZenModeConfig.ZenRule> enabledAutoRule = new ArrayMap<>();
@@ -1607,7 +1593,7 @@
mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
assertTrue(mZenModeHelper.mConfig.automaticRules.containsKey("customRule"));
- setupZenConfigMaintained();
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
}
@Test
@@ -1626,7 +1612,7 @@
parser.nextTag();
mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
- assertEquals(0, mZenModeHelper.mConfig.suppressedVisualEffects);
+ assertTrue(mZenModeHelper.mConfig.getZenPolicy().shouldShowAllVisualEffects());
xml = "<zen version=\"6\" user=\"0\">\n"
+ "<allow calls=\"false\" repeatCallers=\"false\" messages=\"true\" "
@@ -1642,7 +1628,7 @@
parser.nextTag();
mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
- assertEquals(0, mZenModeHelper.mConfig.suppressedVisualEffects);
+ assertTrue(mZenModeHelper.mConfig.getZenPolicy().shouldShowAllVisualEffects());
}
@Test
@@ -1661,7 +1647,7 @@
parser.nextTag();
mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
- assertEquals(0, mZenModeHelper.mConfig.suppressedVisualEffects);
+ assertTrue(mZenModeHelper.mConfig.getZenPolicy().shouldShowAllVisualEffects());
}
@Test
@@ -1680,11 +1666,16 @@
parser.nextTag();
mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
- assertEquals(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT
- | SUPPRESSED_EFFECT_LIGHTS
- | SUPPRESSED_EFFECT_AMBIENT
- | SUPPRESSED_EFFECT_PEEK,
- mZenModeHelper.mConfig.suppressedVisualEffects);
+ assertThat(mZenModeHelper.mConfig.getZenPolicy()
+ .isVisualEffectAllowed(VISUAL_EFFECT_FULL_SCREEN_INTENT, true)).isFalse();
+ assertThat(mZenModeHelper.mConfig.getZenPolicy()
+ .isVisualEffectAllowed(VISUAL_EFFECT_LIGHTS, true)).isFalse();
+ assertThat(mZenModeHelper.mConfig.getZenPolicy()
+ .isVisualEffectAllowed(VISUAL_EFFECT_PEEK, true)).isFalse();
+ assertThat(mZenModeHelper.mConfig.getZenPolicy()
+ .isVisualEffectAllowed(VISUAL_EFFECT_AMBIENT, true)).isFalse();
+ assertThat(mZenModeHelper.mConfig.getZenPolicy()
+ .isVisualEffectAllowed(VISUAL_EFFECT_BADGE, true)).isTrue();
xml = "<zen version=\"6\" user=\"0\">\n"
+ "<allow calls=\"false\" repeatCallers=\"false\" messages=\"true\" "
@@ -1700,7 +1691,10 @@
parser.nextTag();
mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
- assertEquals(SUPPRESSED_EFFECT_PEEK, mZenModeHelper.mConfig.suppressedVisualEffects);
+ assertThat(mZenModeHelper.mConfig.getZenPolicy()
+ .isVisualEffectAllowed(VISUAL_EFFECT_PEEK, true)).isFalse();
+ assertThat(mZenModeHelper.mConfig.getZenPolicy()
+ .isVisualEffectAllowed(VISUAL_EFFECT_AMBIENT, true)).isTrue();
xml = "<zen version=\"6\" user=\"0\">\n"
+ "<allow calls=\"false\" repeatCallers=\"false\" messages=\"true\" "
@@ -1716,18 +1710,23 @@
parser.nextTag();
mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
- assertEquals(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT
- | SUPPRESSED_EFFECT_LIGHTS
- | SUPPRESSED_EFFECT_AMBIENT,
- mZenModeHelper.mConfig.suppressedVisualEffects);
+ assertThat(mZenModeHelper.mConfig.getZenPolicy()
+ .isVisualEffectAllowed(VISUAL_EFFECT_FULL_SCREEN_INTENT, true)).isFalse();
+ assertThat(mZenModeHelper.mConfig.getZenPolicy()
+ .isVisualEffectAllowed(VISUAL_EFFECT_LIGHTS, true)).isFalse();
+ assertThat(mZenModeHelper.mConfig.getZenPolicy()
+ .isVisualEffectAllowed(VISUAL_EFFECT_AMBIENT, true)).isFalse();
+ assertThat(mZenModeHelper.mConfig.getZenPolicy()
+ .isVisualEffectAllowed(VISUAL_EFFECT_BADGE, true)).isTrue();
}
@Test
public void testReadXmlResetDefaultRules() throws Exception {
setupZenConfig();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
// no enabled automatic zen rules and no default rules
- // so rules should be overriden by default rules
+ // so rules should be overridden by default rules
mZenModeHelper.mConfig.automaticRules = new ArrayMap<>();
// set previous version
@@ -1745,13 +1744,14 @@
assertTrue(rules.containsKey(defaultId));
}
- setupZenConfigMaintained();
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
}
@Test
public void testReadXmlAllDisabledRulesResetDefaultRules() throws Exception {
setupZenConfig();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
// all automatic zen rules are disabled on upgrade (and default rules don't already exist)
// so rules should be overriden by default rules
@@ -1782,12 +1782,13 @@
}
assertFalse(rules.containsKey("customRule"));
- setupZenConfigMaintained();
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
}
@Test
public void testReadXmlOnlyOneDefaultRuleExists() throws Exception {
setupZenConfig();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
// all automatic zen rules are disabled on upgrade and only one default rule exists
// so rules should be overriden to the default rules
@@ -1834,12 +1835,13 @@
}
assertFalse(rules.containsKey("customRule"));
- setupZenConfigMaintained();
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
}
@Test
public void testReadXmlDefaultRulesExist() throws Exception {
setupZenConfig();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
// Default rules exist so rules should not be overridden by defaults
ArrayMap<String, ZenModeConfig.ZenRule> automaticRules = new ArrayMap<>();
@@ -1897,13 +1899,13 @@
// check default rules
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
- assertTrue(rules.size() != 0);
+ assertEquals(3, rules.size());
for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
assertTrue(rules.containsKey(defaultId));
}
assertTrue(rules.containsKey("customRule"));
- setupZenConfigMaintained();
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
List<StatsEvent> events = new LinkedList<>();
mZenModeHelper.pullRules(events);
@@ -1911,11 +1913,12 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void testReadXml_onModesApi_noUpgrade() throws Exception {
// When reading XML for something that is already on the modes API system, make sure no
// rules' policies get changed.
setupZenConfig();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
// Shared for rules
ArrayMap<String, ZenModeConfig.ZenRule> enabledAutoRules = new ArrayMap<>();
@@ -1947,7 +1950,7 @@
mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
// basic check: global config maintained
- setupZenConfigMaintained();
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
// Find our automatic rules.
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
@@ -1958,12 +1961,13 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void testReadXml_upgradeToModesApi_makesCustomPolicies() throws Exception {
// When reading in an XML file written from a pre-modes-API version, confirm that we create
// a custom policy matching the global config for any automatic rule with no specified
// policy.
setupZenConfig();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
ArrayMap<String, ZenModeConfig.ZenRule> enabledAutoRule = new ArrayMap<>();
ZenModeConfig.ZenRule customRule = new ZenModeConfig.ZenRule();
@@ -1985,7 +1989,7 @@
mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
// basic check: global config maintained
- setupZenConfigMaintained();
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
// Find our automatic rule and check that it has a policy set now
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
@@ -2009,12 +2013,13 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void testReadXml_upgradeToModesApi_fillsInCustomPolicies() throws Exception {
// When reading in an XML file written from a pre-modes-API version, confirm that for an
// underspecified ZenPolicy, we fill in all of the gaps with things from the global config
// in order to maintain consistency of behavior.
setupZenConfig();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
ArrayMap<String, ZenModeConfig.ZenRule> enabledAutoRule = new ArrayMap<>();
ZenModeConfig.ZenRule customRule = new ZenModeConfig.ZenRule();
@@ -2041,7 +2046,7 @@
mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
// basic check: global config maintained
- setupZenConfigMaintained();
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
// Find our automatic rule and check that it has a policy set now
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
@@ -2068,7 +2073,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void testReadXml_upgradeToModesApi_existingDefaultRulesGetCustomPolicy()
throws Exception {
setupZenConfig();
@@ -2230,7 +2235,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void testDefaultRulesFromConfig_modesApi_getPolicies() {
// After mZenModeHelper was created, set some things in the policy so it's changed from
// default.
@@ -2389,7 +2394,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void testAddAutomaticZenRule_modesApi_fillsInDefaultValues() {
// When a new automatic zen rule is added with only some fields filled in, ensure that
// all unset fields are filled in with device defaults.
@@ -2436,19 +2441,19 @@
assertThat(rule2InConfig.zenPolicy.getPriorityMessageSenders())
.isEqualTo(PEOPLE_TYPE_CONTACTS);
assertThat(rule2InConfig.zenPolicy.getVisualEffectFullScreenIntent())
- .isEqualTo(ZenPolicy.STATE_ALLOW);
+ .isEqualTo(STATE_ALLOW);
// the rest of rule 2's settings should be the device defaults
assertThat(rule2InConfig.zenPolicy.getPriorityConversationSenders())
.isEqualTo(CONVERSATION_SENDERS_IMPORTANT);
assertThat(rule2InConfig.zenPolicy.getPriorityCategorySystem())
- .isEqualTo(ZenPolicy.STATE_DISALLOW);
+ .isEqualTo(STATE_DISALLOW);
assertThat(rule2InConfig.zenPolicy.getPriorityCategoryAlarms())
- .isEqualTo(ZenPolicy.STATE_ALLOW);
+ .isEqualTo(STATE_ALLOW);
assertThat(rule2InConfig.zenPolicy.getVisualEffectPeek())
- .isEqualTo(ZenPolicy.STATE_DISALLOW);
+ .isEqualTo(STATE_DISALLOW);
assertThat(rule2InConfig.zenPolicy.getVisualEffectNotificationList())
- .isEqualTo(ZenPolicy.STATE_ALLOW);
+ .isEqualTo(STATE_ALLOW);
}
@Test
@@ -2590,9 +2595,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void addAutomaticZenRule_fromApp_ignoresHiddenEffects() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
-
ZenDeviceEffects zde = new ZenDeviceEffects.Builder()
.setShouldDisplayGrayscale(true)
.setShouldSuppressAmbientDisplay(true)
@@ -2624,9 +2628,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void addAutomaticZenRule_fromSystem_respectsHiddenEffects() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
-
ZenDeviceEffects zde = new ZenDeviceEffects.Builder()
.setShouldDisplayGrayscale(true)
.setShouldSuppressAmbientDisplay(true)
@@ -2652,9 +2655,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void addAutomaticZenRule_fromUser_respectsHiddenEffects() throws Exception {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
-
ZenDeviceEffects zde = new ZenDeviceEffects.Builder()
.setShouldDisplayGrayscale(true)
.setShouldSuppressAmbientDisplay(true)
@@ -2682,8 +2684,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_fromApp_preservesPreviousHiddenEffects() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
ZenDeviceEffects original = new ZenDeviceEffects.Builder()
.setShouldDisableTapToWake(true)
.addExtraEffect("extra")
@@ -2717,8 +2719,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_fromSystem_updatesHiddenEffects() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
ZenDeviceEffects original = new ZenDeviceEffects.Builder()
.setShouldDisableTapToWake(true)
.build();
@@ -2744,8 +2746,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_fromUser_updatesHiddenEffects() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
ZenDeviceEffects original = new ZenDeviceEffects.Builder()
.setShouldDisableTapToWake(true)
.build();
@@ -2775,7 +2777,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_nullPolicy_doesNothing() {
// Test that when updateAutomaticZenRule is called with a null policy, nothing changes
// about the existing policy.
@@ -2796,11 +2798,11 @@
AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(savedRule.getZenPolicy().getPriorityCategoryCalls())
- .isEqualTo(ZenPolicy.STATE_DISALLOW);
+ .isEqualTo(STATE_DISALLOW);
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_overwritesExistingPolicy() {
// Test that when updating an automatic zen rule with an existing policy, the newly set
// fields overwrite those from the previous policy, but unset fields in the new policy
@@ -2826,18 +2828,18 @@
AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(savedRule.getZenPolicy().getPriorityCategoryCalls())
- .isEqualTo(ZenPolicy.STATE_ALLOW); // from update
+ .isEqualTo(STATE_ALLOW); // from update
assertThat(savedRule.getZenPolicy().getPriorityCallSenders())
.isEqualTo(ZenPolicy.PEOPLE_TYPE_CONTACTS); // from update
assertThat(savedRule.getZenPolicy().getPriorityCategoryAlarms())
- .isEqualTo(ZenPolicy.STATE_DISALLOW); // from original
+ .isEqualTo(STATE_DISALLOW); // from original
assertThat(savedRule.getZenPolicy().getPriorityCategoryReminders())
- .isEqualTo(ZenPolicy.STATE_ALLOW); // from original
+ .isEqualTo(STATE_ALLOW); // from original
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void addAutomaticZenRule_withTypeBedtime_replacesDisabledSleeping() {
ZenRule sleepingRule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS,
ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID);
@@ -2857,7 +2859,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void addAutomaticZenRule_withTypeBedtime_keepsEnabledSleeping() {
ZenRule sleepingRule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS,
ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID);
@@ -2878,7 +2880,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void addAutomaticZenRule_withTypeBedtime_keepsCustomizedSleeping() {
ZenRule sleepingRule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS,
ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID);
@@ -2899,8 +2901,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void testSetManualZenMode() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
setupZenConfig();
// note that caller=null because that's how it comes in from NMS.setZenMode
@@ -2919,68 +2921,83 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
- @DisableFlags(Flags.FLAG_MODES_UI)
- public void setManualZenMode_off_snoozesActiveRules(@TestParameter ChangeOrigin setZenOrigin) {
- // Start with an active rule and an inactive rule.
- mZenModeHelper.mConfig.automaticRules.clear();
- AutomaticZenRule activeRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .build();
- String activeRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- activeRule, UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(activeRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
- CUSTOM_PKG_UID);
- AutomaticZenRule inactiveRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .build();
- String inactiveRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- inactiveRule, UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ @EnableFlags(FLAG_MODES_API)
+ @DisableFlags(FLAG_MODES_UI)
+ public void setManualZenMode_off_snoozesActiveRules() {
+ for (ChangeOrigin origin : ChangeOrigin.values()) {
+ // Start with an active rule and an inactive rule.
+ mZenModeHelper.mConfig.automaticRules.clear();
+ AutomaticZenRule activeRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .build();
+ String activeRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ activeRule, UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(activeRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
+ AutomaticZenRule inactiveRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .build();
+ String inactiveRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ inactiveRule, UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ assertWithMessage("Failure for origin " + origin.name())
+ .that(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
- // User turns DND off.
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, setZenOrigin.value(),
- "snoozing", "systemui", Process.SYSTEM_UID);
- assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
- assertThat(mZenModeHelper.mConfig.automaticRules.get(activeRuleId).snoozing).isTrue();
- assertThat(mZenModeHelper.mConfig.automaticRules.get(inactiveRuleId).snoozing).isFalse();
+ // User turns DND off.
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, origin.value(),
+ "snoozing", "systemui", Process.SYSTEM_UID);
+ assertWithMessage("Failure for origin " + origin.name())
+ .that(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+ assertWithMessage("Failure for origin " + origin.name())
+ .that(mZenModeHelper.mConfig.automaticRules.get(activeRuleId).snoozing)
+ .isTrue();
+ assertWithMessage("Failure for origin " + origin.name())
+ .that(mZenModeHelper.mConfig.automaticRules.get(inactiveRuleId).snoozing)
+ .isFalse();
+ }
}
@Test
- @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
- public void setManualZenMode_off_doesNotSnoozeRulesIfFromUser(
- @TestParameter ChangeOrigin setZenOrigin) {
- // Start with an active rule and an inactive rule
- mZenModeHelper.mConfig.automaticRules.clear();
- AutomaticZenRule activeRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .build();
- String activeRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- activeRule, UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(activeRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
- CUSTOM_PKG_UID);
- AutomaticZenRule inactiveRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .build();
- String inactiveRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- inactiveRule, UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void setManualZenMode_off_doesNotSnoozeRulesIfFromUser() {
+ for (ChangeOrigin origin : ChangeOrigin.values()) {
+ // Start with an active rule and an inactive rule
+ mZenModeHelper.mConfig.automaticRules.clear();
+ AutomaticZenRule activeRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .build();
+ String activeRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ activeRule, UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(activeRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
+ AutomaticZenRule inactiveRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .build();
+ String inactiveRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ inactiveRule, UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-
- // User turns DND off.
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, setZenOrigin.value(),
- "snoozing", "systemui", Process.SYSTEM_UID);
- ZenModeConfig config = mZenModeHelper.mConfig;
- if (setZenOrigin == ChangeOrigin.ORIGIN_USER) {
- // Other rule was unaffected.
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
- assertThat(config.automaticRules.get(activeRuleId).snoozing).isFalse();
- assertThat(config.automaticRules.get(inactiveRuleId).snoozing).isFalse();
- } else {
- assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
- assertThat(config.automaticRules.get(activeRuleId).snoozing).isTrue();
- assertThat(config.automaticRules.get(inactiveRuleId).snoozing).isFalse();
+
+ // User turns DND off.
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, origin.value(),
+ "snoozing", "systemui", Process.SYSTEM_UID);
+ ZenModeConfig config = mZenModeHelper.mConfig;
+ if (origin == ChangeOrigin.ORIGIN_USER) {
+ // Other rule was unaffected.
+ assertWithMessage("Failure for origin " + origin.name()).that(
+ mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ assertWithMessage("Failure for origin " + origin.name()).that(
+ config.automaticRules.get(activeRuleId).snoozing).isFalse();
+ assertWithMessage("Failure for origin " + origin.name()).that(
+ config.automaticRules.get(inactiveRuleId).snoozing).isFalse();
+ } else {
+ assertWithMessage("Failure for origin " + origin.name()).that(
+ mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+ assertWithMessage("Failure for origin " + origin.name()).that(
+ config.automaticRules.get(activeRuleId).snoozing).isTrue();
+ assertWithMessage("Failure for origin " + origin.name()).that(
+ config.automaticRules.get(inactiveRuleId).snoozing).isFalse();
+ }
}
}
@@ -3002,45 +3019,17 @@
assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode);
}
- private enum ModesFlag {
- MODES_UI(2, /* originForUserActionInSystemUi= */ UPDATE_ORIGIN_USER),
- MODES_API(1, /* originForUserActionInSystemUi= */ UPDATE_ORIGIN_USER),
- DISABLED(0, /* originForUserActionInSystemUi= */ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI);
-
- private final int mFlagsEnabled;
- @ConfigChangeOrigin
- private final int mOriginForUserActionInSystemUi;
-
- ModesFlag(int flagsEnabled, @ConfigChangeOrigin int originForUserActionInSystemUi) {
- this.mFlagsEnabled = flagsEnabled;
- this.mOriginForUserActionInSystemUi = originForUserActionInSystemUi;
- }
-
- void applyFlags(SetFlagsRule setFlagsRule) {
- if (mFlagsEnabled >= 1) {
- setFlagsRule.enableFlags(Flags.FLAG_MODES_API);
- } else {
- setFlagsRule.disableFlags(Flags.FLAG_MODES_API);
- }
- if (mFlagsEnabled >= 2) {
- setFlagsRule.enableFlags(Flags.FLAG_MODES_UI);
- } else {
- setFlagsRule.disableFlags(Flags.FLAG_MODES_UI);
- }
- }
- }
-
@Test
- public void testZenModeEventLog_setManualZenMode(@TestParameter ModesFlag modesFlag)
+ public void testZenModeEventLog_setManualZenMode()
throws IllegalArgumentException {
- modesFlag.applyFlags(mSetFlagsRule);
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
// Turn zen mode on (to important_interruptions)
// Need to additionally call the looper in order to finish the post-apply-config process
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- modesFlag.mOriginForUserActionInSystemUi, "", null, Process.SYSTEM_UID);
+ Flags.modesApi() ? UPDATE_ORIGIN_USER: UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null,
+ Process.SYSTEM_UID);
// Now turn zen mode off, but via a different package UID -- this should get registered as
// "not an action by the user" because some other app is changing zen mode
@@ -3068,7 +3057,7 @@
assertEquals(DNDProtoEnums.MANUAL_RULE, mZenModeEventLogger.getChangedRuleType(0));
assertEquals(1, mZenModeEventLogger.getNumRulesActive(0));
assertThat(mZenModeEventLogger.getFromSystemOrSystemUi(0)).isEqualTo(
- modesFlag == ModesFlag.DISABLED);
+ !(Flags.modesUi() || Flags.modesApi()));
assertTrue(mZenModeEventLogger.getIsUserAction(0));
assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(0));
checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(0));
@@ -3097,9 +3086,8 @@
}
@Test
- public void testZenModeEventLog_automaticRules(@TestParameter ModesFlag modesFlag)
+ public void testZenModeEventLog_automaticRules()
throws IllegalArgumentException {
- modesFlag.applyFlags(mSetFlagsRule);
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
@@ -3116,15 +3104,14 @@
// Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
- Process.SYSTEM_UID);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// Event 2: "User" turns off the automatic rule (sets it to not enabled)
zenRule.setEnabled(false);
mZenModeHelper.updateAutomaticZenRule(id, zenRule,
- modesFlag.mOriginForUserActionInSystemUi, "", Process.SYSTEM_UID);
+ Flags.modesApi() ? UPDATE_ORIGIN_USER: UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
+ Process.SYSTEM_UID);
- // Add a new system rule
AutomaticZenRule systemRule = new AutomaticZenRule("systemRule",
null,
new ComponentName("android", "ScheduleConditionProvider"),
@@ -3132,7 +3119,8 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String systemId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), systemRule,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ Flags.modesApi() ? UPDATE_ORIGIN_USER: UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test",
+ Process.SYSTEM_UID);
// Event 3: turn on the system rule
mZenModeHelper.setAutomaticZenRuleState(systemId,
@@ -3140,9 +3128,9 @@
UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// Event 4: "User" deletes the rule
- mZenModeHelper.removeAutomaticZenRule(systemId, modesFlag.mOriginForUserActionInSystemUi,
- "", Process.SYSTEM_UID);
-
+ mZenModeHelper.removeAutomaticZenRule(systemId,
+ Flags.modesApi() ? UPDATE_ORIGIN_USER: UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
+ Process.SYSTEM_UID);
// In total, this represents 4 events
assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -3201,7 +3189,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void testZenModeEventLog_automaticRuleActivatedFromAppByAppAndUser()
throws IllegalArgumentException {
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
@@ -3288,22 +3276,19 @@
}
@Test
- public void testZenModeEventLog_policyChanges(@TestParameter ModesFlag modesFlag)
+ public void testZenModeEventLog_policyChanges()
throws IllegalArgumentException {
- modesFlag.applyFlags(mSetFlagsRule);
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
// First just turn zen mode on
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- modesFlag.mOriginForUserActionInSystemUi, "", null, Process.SYSTEM_UID);
+ UPDATE_ORIGIN_USER, "", null, Process.SYSTEM_UID);
// Now change the policy slightly; want to confirm that this'll be reflected in the logs
ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
- newConfig.allowAlarms = true;
- newConfig.allowRepeatCallers = false;
- mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
- modesFlag.mOriginForUserActionInSystemUi, Process.SYSTEM_UID);
+ mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_ALARMS, 0, 0),
+ UPDATE_ORIGIN_USER, Process.SYSTEM_UID);
// Turn zen mode off; we want to make sure policy changes do not get logged when zen mode
// is off.
@@ -3311,10 +3296,8 @@
null, Process.SYSTEM_UID);
// Change the policy again
- newConfig.allowMessages = false;
- newConfig.allowRepeatCallers = true;
- mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
- modesFlag.mOriginForUserActionInSystemUi, Process.SYSTEM_UID);
+ mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0),
+ UPDATE_ORIGIN_USER, Process.SYSTEM_UID);
// Total events: we only expect ones for turning on, changing policy, and turning off
assertEquals(3, mZenModeEventLogger.numLoggedChanges());
@@ -3347,9 +3330,7 @@
}
@Test
- public void testZenModeEventLog_ruleCounts(@TestParameter ModesFlag modesFlag)
- throws IllegalArgumentException {
- modesFlag.applyFlags(mSetFlagsRule);
+ public void testZenModeEventLog_ruleCounts() throws IllegalArgumentException {
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
@@ -3374,14 +3355,12 @@
// Rule 3, has stricter settings than the default settings
ZenModeConfig ruleConfig = mZenModeHelper.mConfig.copy();
- ruleConfig.allowReminders = false;
- ruleConfig.allowCalls = false;
- ruleConfig.allowMessages = false;
+ ruleConfig.applyNotificationPolicy(new Policy(0, 0, 0));
AutomaticZenRule zenRule3 = new AutomaticZenRule("name3",
null,
new ComponentName("android", "ScheduleConditionProvider"),
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
- ruleConfig.toZenPolicy(),
+ ruleConfig.getZenPolicy(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id3 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule3,
UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
@@ -3452,10 +3431,8 @@
}
@Test
- public void testZenModeEventLog_noLogWithNoConfigChange(
- @TestParameter ModesFlag modesFlag) throws IllegalArgumentException {
+ public void testZenModeEventLog_noLogWithNoConfigChange() throws IllegalArgumentException {
// If evaluateZenMode is called independently of a config change, don't log.
- modesFlag.applyFlags(mSetFlagsRule);
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
@@ -3472,17 +3449,16 @@
}
@Test
- public void testZenModeEventLog_reassignUid(@TestParameter ModesFlag modesFlag)
+ public void testZenModeEventLog_reassignUid()
throws IllegalArgumentException {
// Test that, only in specific cases, we reassign the calling UID to one associated with
// the automatic rule owner.
- modesFlag.applyFlags(mSetFlagsRule);
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
// Explicitly set up all rules with the same policy as the manual rule so there will be
// no policy changes in this test case.
- ZenPolicy manualRulePolicy = mZenModeHelper.mConfig.toZenPolicy();
+ ZenPolicy manualRulePolicy = mZenModeHelper.mConfig.getZenPolicy();
// Rule 1, owned by a package
AutomaticZenRule zenRule = new AutomaticZenRule("name",
@@ -3502,7 +3478,7 @@
manualRulePolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
- modesFlag.mOriginForUserActionInSystemUi, "test", Process.SYSTEM_UID);
+ UPDATE_ORIGIN_USER, "test", Process.SYSTEM_UID);
// Turn on rule 1; call looks like it's from the system. Because setting a condition is
// typically an automatic (non-user-initiated) action, expect the calling UID to be
@@ -3521,7 +3497,7 @@
// from the system-provided one.
zenRule.setEnabled(false);
mZenModeHelper.updateAutomaticZenRule(id, zenRule,
- modesFlag.mOriginForUserActionInSystemUi, "", Process.SYSTEM_UID);
+ UPDATE_ORIGIN_USER, "", Process.SYSTEM_UID);
// Add a manual rule. Any manual rule changes should not get calling uids reassigned.
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP,
@@ -3578,10 +3554,8 @@
}
@Test
- public void testZenModeEventLog_channelsBypassingChanges(
- @TestParameter ModesFlag modesFlag) {
+ public void testZenModeEventLog_channelsBypassingChanges() {
// Verify that the right thing happens when the canBypassDnd value changes.
- modesFlag.applyFlags(mSetFlagsRule);
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
@@ -3589,21 +3563,25 @@
// as a user action, and *should* get its UID reassigned.
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", CUSTOM_PKG_NAME, Process.SYSTEM_UID);
+ assertEquals(1, mZenModeEventLogger.numLoggedChanges());
// Now change apps bypassing to true
ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
newConfig.areChannelsBypassingDnd = true;
mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ assertEquals(2, mZenModeEventLogger.numLoggedChanges());
// and then back to false, all without changing anything else
newConfig.areChannelsBypassingDnd = false;
mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ assertEquals(3, mZenModeEventLogger.numLoggedChanges());
// Turn off manual mode, call from a package: don't reset UID even though enabler is set
mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "",
CUSTOM_PKG_NAME, 12345);
+ assertEquals(4, mZenModeEventLogger.numLoggedChanges());
// And likewise when turning it back on again
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP,
@@ -3642,11 +3620,11 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void testZenModeEventLog_policyAllowChannels() {
// when modes_api flag is on, ensure that any change in allow_channels gets logged,
// even when there are no other changes.
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
// Default zen config has allow channels = priority (aka on)
setupZenConfig();
@@ -3658,8 +3636,12 @@
// Now change only the channels part of the policy; want to confirm that this'll be
// reflected in the logs
ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
- newConfig.allowPriorityChannels = false;
- mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
+ Policy oldPolicy = newConfig.toNotificationPolicy();
+ Policy newPolicy = new Policy(oldPolicy.priorityCategories, oldPolicy.priorityCallSenders,
+ oldPolicy.priorityMessageSenders, oldPolicy.suppressedVisualEffects,
+ STATE_PRIORITY_CHANNELS_BLOCKED,
+ oldPolicy.priorityConversationSenders);
+ mZenModeHelper.setNotificationPolicy(newPolicy,
UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// Total events: one for turning on, one for changing policy
@@ -3688,7 +3670,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void testZenModeEventLog_ruleWithInterruptionFilterAll_notLoggedAsDndChange() {
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
@@ -3730,7 +3712,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void testZenModeEventLog_activeRuleTypes() {
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
@@ -3748,7 +3730,7 @@
// Create immersive rule
AutomaticZenRule immersive = new AutomaticZenRule.Builder("Immersed", CONDITION_ID)
.setType(TYPE_IMMERSIVE)
- .setZenPolicy(mZenModeHelper.mConfig.toZenPolicy()) // same as the manual rule
+ .setZenPolicy(mZenModeHelper.mConfig.getZenPolicy()) // same as the manual rule
.build();
String immersiveId = mZenModeHelper.addAutomaticZenRule(mPkg, immersive, UPDATE_ORIGIN_APP,
"reason", CUSTOM_PKG_UID);
@@ -3819,10 +3801,9 @@
}
@Test
- @DisableFlags(Flags.FLAG_MODES_API)
+ @DisableFlags(FLAG_MODES_API)
public void testUpdateConsolidatedPolicy_preModesApiDefaultRulesOnly_takesGlobalDefault() {
setupZenConfig();
-
// When there's one automatic rule active and it doesn't specify a policy, test that the
// resulting consolidated policy is one that matches the default rule settings.
AutomaticZenRule zenRule = new AutomaticZenRule("name",
@@ -3839,6 +3820,9 @@
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ assertEquals(mZenModeHelper.getNotificationPolicy(),
+ mZenModeHelper.getConsolidatedNotificationPolicy());
+
// inspect the consolidated policy. Based on setupZenConfig() values.
assertFalse(mZenModeHelper.mConsolidatedPolicy.allowAlarms());
assertFalse(mZenModeHelper.mConsolidatedPolicy.allowMedia());
@@ -3853,9 +3837,7 @@
}
@Test
- public void testUpdateConsolidatedPolicy_modesApiDefaultRulesOnly_takesDefault(
- @TestParameter({"MODES_UI", "MODES_API"}) ModesFlag modesFlag) {
- modesFlag.applyFlags(mSetFlagsRule);
+ public void testUpdateConsolidatedPolicy_modesApiDefaultRulesOnly_takesDefault() {
setupZenConfig();
// When there's one automatic rule active and it doesn't specify a policy, test that the
@@ -3876,13 +3858,13 @@
// inspect the consolidated policy, which should match the device default settings.
assertThat(ZenAdapters.notificationPolicyToZenPolicy(mZenModeHelper.mConsolidatedPolicy))
- .isEqualTo(modesFlag == ModesFlag.MODES_UI
+ .isEqualTo(Flags.modesUi()
? mZenModeHelper.getDefaultZenPolicy()
- : mZenModeHelper.mConfig.toZenPolicy());
+ : mZenModeHelper.mConfig.getZenPolicy());
}
@Test
- @DisableFlags(Flags.FLAG_MODES_API)
+ @DisableFlags(FLAG_MODES_API)
public void testUpdateConsolidatedPolicy_preModesApiCustomPolicyOnly_fillInWithGlobal() {
setupZenConfig();
@@ -3928,9 +3910,8 @@
}
@Test
- public void testUpdateConsolidatedPolicy_modesApiCustomPolicyOnly_fillInWithDefault(
- @TestParameter({"MODES_UI", "MODES_API"}) ModesFlag modesFlag) {
- modesFlag.applyFlags(mSetFlagsRule);
+ @EnableFlags(FLAG_MODES_API)
+ public void testUpdateConsolidatedPolicy_modesApiCustomPolicyOnly_fillInWithDefault() {
setupZenConfig();
// when there's only one automatic rule active and it has a custom policy, make sure that's
@@ -3962,12 +3943,12 @@
// policy for every field specified, and take default values (from either device default
// policy or manual rule) for unspecified things
assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isEqualTo(
- modesFlag == ModesFlag.MODES_UI ? true : false); // default
+ Flags.modesUi() ? true : false); // default
assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isEqualTo(
- modesFlag == ModesFlag.MODES_UI ? true : false); // default
+ Flags.modesUi() ? true : false); // default
assertThat(mZenModeHelper.mConsolidatedPolicy.allowSystem()).isTrue(); // custom
assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isEqualTo(
- modesFlag == ModesFlag.MODES_UI ? false : true); // default
+ Flags.modesUi() ? false : true); // default
assertThat(mZenModeHelper.mConsolidatedPolicy.allowCalls()).isFalse(); // custom
assertThat(mZenModeHelper.mConsolidatedPolicy.allowMessages()).isTrue(); // default
assertThat(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers()).isFalse(); // custom
@@ -3976,7 +3957,7 @@
}
@Test
- @DisableFlags(Flags.FLAG_MODES_API)
+ @DisableFlags(FLAG_MODES_API)
public void testUpdateConsolidatedPolicy_preModesApiDefaultAndCustomActive_mergesWithGlobal() {
setupZenConfig();
@@ -4037,9 +4018,8 @@
}
@Test
- public void testUpdateConsolidatedPolicy_modesApiDefaultAndCustomActive_mergesWithDefault(
- @TestParameter({"MODES_UI", "MODES_API"}) ModesFlag modesFlag) {
- modesFlag.applyFlags(mSetFlagsRule);
+ @EnableFlags(FLAG_MODES_API)
+ public void testUpdateConsolidatedPolicy_modesApiDefaultAndCustomActive_mergesWithDefault() {
setupZenConfig();
// when there are two rules active, one inheriting the default policy and one setting its
@@ -4088,10 +4068,10 @@
// restrictive option of each of the two
assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isFalse(); // custom stricter
assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isEqualTo(
- modesFlag == ModesFlag.MODES_UI ? true : false); // default
+ Flags.modesUi() ? true : false); // default
assertThat(mZenModeHelper.mConsolidatedPolicy.allowSystem()).isFalse(); // default stricter
assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isEqualTo(
- modesFlag == ModesFlag.MODES_UI ? false : true); // default
+ Flags.modesUi() ? false : true); // default
assertThat(mZenModeHelper.mConsolidatedPolicy.allowCalls()).isFalse(); // custom stricter
assertThat(mZenModeHelper.mConsolidatedPolicy.allowMessages()).isTrue(); // default
assertThat(mZenModeHelper.mConsolidatedPolicy.allowConversations()).isTrue(); // default
@@ -4099,12 +4079,12 @@
.isFalse(); // custom stricter
assertThat(mZenModeHelper.mConsolidatedPolicy.showBadges()).isFalse(); // custom stricter
assertThat(mZenModeHelper.mConsolidatedPolicy.showPeeking()).isEqualTo(
- modesFlag == ModesFlag.MODES_UI ? false : true); // default
+ Flags.modesUi() ? false : true); // default
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void testUpdateConsolidatedPolicy_allowChannels() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
setupZenConfig();
// one rule, custom policy, allows channels
@@ -4153,9 +4133,8 @@
}
@Test
- public void testUpdateConsolidatedPolicy_ignoresActiveRulesWithInterruptionFilterAll(
- @TestParameter({"MODES_UI", "MODES_API"}) ModesFlag modesFlag) {
- modesFlag.applyFlags(mSetFlagsRule);
+ @EnableFlags(FLAG_MODES_API)
+ public void testUpdateConsolidatedPolicy_ignoresActiveRulesWithInterruptionFilterAll() {
setupZenConfig();
// Rules with INTERRUPTION_FILTER_ALL are skipped when calculating consolidated policy.
@@ -4193,11 +4172,11 @@
// Consolidated Policy should be default + rule1.
assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isEqualTo(
- modesFlag == ModesFlag.MODES_UI ? true : false); // default
+ Flags.modesUi() ? true : false); // default
assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isTrue(); // priority rule
assertThat(mZenModeHelper.mConsolidatedPolicy.allowSystem()).isTrue(); // priority rule
assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isEqualTo(
- modesFlag == ModesFlag.MODES_UI ? false : true); // default
+ Flags.modesUi() ? false : true); // default
assertThat(mZenModeHelper.mConsolidatedPolicy.allowCalls()).isTrue(); // default
assertThat(mZenModeHelper.mConsolidatedPolicy.allowMessages()).isTrue(); // default
assertThat(mZenModeHelper.mConsolidatedPolicy.allowConversations()).isTrue(); // default
@@ -4205,8 +4184,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void zenRuleToAutomaticZenRule_allFields() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(
new String[]{OWNER.getPackageName()});
@@ -4249,8 +4228,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void automaticZenRuleToZenRule_allFields() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(
new String[]{OWNER.getPackageName()});
@@ -4291,7 +4270,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_fromApp_updatesNameUnlessUserModified() {
// Add a starting rule with the name OriginalName.
AutomaticZenRule azrBase = new AutomaticZenRule.Builder("OriginalName", CONDITION_ID)
@@ -4348,7 +4327,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_fromUser_updatesBitmaskAndValue() {
// Adds a starting rule with empty zen policies and device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
@@ -4385,7 +4364,7 @@
assertThat(rule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
assertThat(rule.getIconResId()).isEqualTo(ICON_RES_ID);
assertThat(rule.getZenPolicy().getPriorityChannelsAllowed()).isEqualTo(
- ZenPolicy.STATE_DISALLOW);
+ STATE_DISALLOW);
assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
@@ -4401,7 +4380,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_fromSystemUi_updatesValues() {
// Adds a starting rule with empty zen policies and device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
@@ -4441,7 +4420,7 @@
// UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI should change the value but NOT update the bitmask.
assertThat(rule.getIconResId()).isEqualTo(ICON_RES_ID);
assertThat(rule.getZenPolicy().getPriorityCategoryReminders())
- .isEqualTo(ZenPolicy.STATE_ALLOW);
+ .isEqualTo(STATE_ALLOW);
assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -4451,7 +4430,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_fromApp_updatesValuesIfRuleNotUserModified() {
// Adds a starting rule with empty zen policies and device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
@@ -4490,7 +4469,7 @@
assertThat(storedRule.zenMode).isEqualTo(ZEN_MODE_ALARMS);
assertThat(storedRule.zenPolicy.getPriorityCategoryReminders())
- .isEqualTo(ZenPolicy.STATE_ALLOW);
+ .isEqualTo(STATE_ALLOW);
assertThat(storedRule.zenDeviceEffects.shouldDisplayGrayscale()).isTrue();
assertThat(storedRule.userModifiedFields).isEqualTo(0);
assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(0);
@@ -4514,7 +4493,7 @@
// so the rule is not changed, and neither is the bitmask.
assertThat(ruleUser.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALL);
assertThat(ruleUser.getZenPolicy().getPriorityCategoryReminders())
- .isEqualTo(ZenPolicy.STATE_DISALLOW);
+ .isEqualTo(STATE_DISALLOW);
assertThat(ruleUser.getDeviceEffects().shouldDisplayGrayscale()).isFalse();
storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleIdUser);
@@ -4525,7 +4504,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void addAutomaticZenRule_updatesValues() {
// Adds a starting rule with empty zen policies and device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
@@ -4543,7 +4522,7 @@
// The values are modified but the bitmask is not.
assertThat(rule.getZenPolicy().getPriorityCategoryReminders())
- .isEqualTo(ZenPolicy.STATE_ALLOW);
+ .isEqualTo(STATE_ALLOW);
assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -4551,7 +4530,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_nullDeviceEffectsUpdate() {
// Adds a starting rule with empty zen policies and device effects
ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
@@ -4578,7 +4557,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_nullPolicyUpdate() {
// Adds a starting rule with set zen policy and empty device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
@@ -4607,7 +4586,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void automaticZenRuleToZenRule_nullToNonNullPolicyUpdate() {
when(mContext.checkCallingPermission(anyString()))
.thenReturn(PackageManager.PERMISSION_GRANTED);
@@ -4654,7 +4633,7 @@
// New ZenPolicy differs from the default config
assertThat(rule.getZenPolicy()).isNotNull();
assertThat(rule.getZenPolicy().getPriorityChannelsAllowed()).isEqualTo(
- ZenPolicy.STATE_DISALLOW);
+ STATE_DISALLOW);
ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(storedRule.canBeUpdatedByApp()).isFalse();
@@ -4671,7 +4650,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void automaticZenRuleToZenRule_nullToNonNullDeviceEffectsUpdate() {
// Adds a starting rule with empty zen policies and device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
@@ -4775,8 +4754,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void testUpdateAutomaticRule_activated_triggersBroadcast() throws Exception {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
setupZenConfig();
// Add a new automatic zen rule that's enabled
@@ -4815,8 +4794,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void testUpdateAutomaticRule_deactivatedByUser_triggersBroadcast() throws Exception {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
setupZenConfig();
// Add a new automatic zen rule that's enabled
@@ -4860,8 +4839,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void testUpdateAutomaticRule_deactivatedByApp_triggersBroadcast() throws Exception {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
setupZenConfig();
// Add a new automatic zen rule that's enabled
@@ -4937,7 +4916,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_ruleChanged_deactivatesRule() {
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", CONDITION_ID)
@@ -4961,7 +4940,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_ruleNotChanged_doesNotDeactivateRule() {
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", CONDITION_ID)
@@ -4984,7 +4963,7 @@
}
@Test
- @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
public void updateAutomaticZenRule_ruleChangedByUser_doesNotDeactivateRule() {
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", CONDITION_ID)
@@ -5009,7 +4988,7 @@
}
@Test
- @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
public void updateAutomaticZenRule_ruleDisabledByUser_doesNotReactivateOnReenable() {
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", CONDITION_ID)
@@ -5034,7 +5013,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void removeAutomaticZenRule_propagatesOriginToEffectsApplier() {
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
reset(mDeviceEffectsApplier);
@@ -5057,8 +5036,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void testDeviceEffects_applied() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
@@ -5077,8 +5056,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void testDeviceEffects_onDeactivateRule_applied() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
@@ -5096,8 +5075,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void testDeviceEffects_changeToConsolidatedEffects_applied() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
@@ -5136,8 +5115,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void testDeviceEffects_noChangeToConsolidatedEffects_notApplied() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
@@ -5161,9 +5140,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void testDeviceEffects_activeBeforeApplierProvided_appliedWhenProvided() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
-
ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
String ruleId = addRuleWithEffects(zde);
verify(mDeviceEffectsApplier, never()).apply(any(), anyInt());
@@ -5178,8 +5156,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void testDeviceEffects_onUserSwitch_appliedImmediately() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
@@ -5214,9 +5192,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void removeAndAddAutomaticZenRule_wasCustomized_isRestored() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
-
// Start with a rule.
mZenModeHelper.mConfig.automaticRules.clear();
mTestClock.setNowMillis(1000);
@@ -5257,7 +5234,7 @@
assertThat(newRuleId).isEqualTo(ruleId);
assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
assertThat(finalRule.getZenPolicy().getPriorityCategoryRepeatCallers()).isEqualTo(
- ZenPolicy.STATE_ALLOW);
+ STATE_ALLOW);
ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(storedRule.userModifiedFields).isEqualTo(
@@ -5270,9 +5247,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void removeAndAddAutomaticZenRule_wasNotCustomized_isNotRestored() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
-
// Start with a single rule.
mZenModeHelper.mConfig.automaticRules.clear();
mTestClock.setNowMillis(1000);
@@ -5303,9 +5279,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void removeAndAddAutomaticZenRule_recreatedButNotByApp_isNotRestored() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
-
// Start with a single rule.
mZenModeHelper.mConfig.automaticRules.clear();
mTestClock.setNowMillis(1000);
@@ -5345,16 +5320,15 @@
assertThat(newRuleId).isNotEqualTo(ruleId);
assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
assertThat(finalRule.getZenPolicy().getPriorityCategoryRepeatCallers()).isEqualTo(
- ZenPolicy.STATE_DISALLOW);
+ STATE_DISALLOW);
// Also, we discarded the "deleted rule" since we're not interested in recreating it.
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void removeAndAddAutomaticZenRule_removedByUser_isNotRestored() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
-
// Start with a single rule.
mZenModeHelper.mConfig.automaticRules.clear();
mTestClock.setNowMillis(1000);
@@ -5394,8 +5368,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void removeAutomaticZenRule_preservedForRestoringByPackageAndConditionId() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mContext.getTestablePermissions().setPermission(Manifest.permission.MANAGE_NOTIFICATIONS,
PERMISSION_GRANTED); // So that canManageAZR passes although packages don't match.
mZenModeHelper.mConfig.automaticRules.clear();
@@ -5434,8 +5408,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void removeAllZenRules_preservedForRestoring() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.mConfig.automaticRules.clear();
mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
@@ -5456,8 +5430,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void removeAllZenRules_fromSystem_deletesPreservedRulesToo() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.mConfig.automaticRules.clear();
// Start with deleted rules from 2 different packages.
@@ -5476,7 +5450,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void removeAndAddAutomaticZenRule_wasActive_isRestoredAsInactive() {
// Start with a rule.
mZenModeHelper.mConfig.automaticRules.clear();
@@ -5525,7 +5499,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void removeAndAddAutomaticZenRule_wasSnoozed_isRestoredAsInactive() {
// Start with a rule.
mZenModeHelper.mConfig.automaticRules.clear();
@@ -5579,8 +5553,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void testRuleCleanup() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
Instant now = Instant.ofEpochMilli(1701796461000L);
Instant yesterday = now.minus(1, ChronoUnit.DAYS);
Instant aWeekAgo = now.minus(7, ChronoUnit.DAYS);
@@ -5637,7 +5611,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void getAutomaticZenRuleState_ownedRule_returnsRuleState() {
String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
@@ -5662,7 +5636,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void getAutomaticZenRuleState_notOwnedRule_returnsStateUnknown() {
// Assume existence of a system-owned rule that is currently ACTIVE.
ZenRule systemRule = newZenRule("android", Instant.now(), null);
@@ -5678,7 +5652,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void setAutomaticZenRuleState_idForNotOwnedRule_ignored() {
// Assume existence of an other-package-owned rule that is currently ACTIVE.
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
@@ -5699,7 +5673,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void setAutomaticZenRuleState_conditionForNotOwnedRule_ignored() {
// Assume existence of an other-package-owned rule that is currently ACTIVE.
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
@@ -5720,7 +5694,7 @@
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void testCallbacks_policy() throws Exception {
setupZenConfig();
assertThat(mZenModeHelper.getNotificationPolicy().allowReminders()).isTrue();
@@ -5740,10 +5714,9 @@
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void testCallbacks_consolidatedPolicy() throws Exception {
- setupZenConfig();
- assertThat(mZenModeHelper.getConsolidatedNotificationPolicy().allowAlarms()).isTrue();
+ assertThat(mZenModeHelper.getConsolidatedNotificationPolicy().allowMedia()).isTrue();
SettableFuture<Policy> futureConsolidatedPolicy = SettableFuture.create();
mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
@Override
@@ -5762,12 +5735,12 @@
new Condition(CONDITION_ID, "", STATE_TRUE), UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
Policy callbackPolicy = futureConsolidatedPolicy.get(1, TimeUnit.SECONDS);
- assertThat(callbackPolicy.allowAlarms()).isFalse();
+ assertThat(callbackPolicy.allowMedia()).isFalse();
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void applyGlobalZenModeAsImplicitZenRule_createsImplicitRuleAndActivatesIt() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.mConfig.automaticRules.clear();
mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
@@ -5777,13 +5750,13 @@
.comparingElementsUsing(IGNORE_METADATA)
.containsExactly(
expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
- mZenModeHelper.mConfig.toZenPolicy(), // copy of global config
+ mZenModeHelper.mConfig.getZenPolicy(), // copy of global config
true));
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void applyGlobalZenModeAsImplicitZenRule_updatesImplicitRuleAndActivatesIt() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.mConfig.automaticRules.clear();
mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
@@ -5798,12 +5771,12 @@
.comparingElementsUsing(IGNORE_METADATA)
.containsExactly(
expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_ALARMS,
- mZenModeHelper.mConfig.toZenPolicy(), // copy of global config
+ mZenModeHelper.mConfig.getZenPolicy(), // copy of global config
true));
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void applyGlobalZenModeAsImplicitZenRule_ruleCustomized_doesNotUpdateRule() {
mZenModeHelper.mConfig.automaticRules.clear();
String pkg = mContext.getPackageName();
@@ -5835,7 +5808,7 @@
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void applyGlobalZenModeAsImplicitZenRule_ruleCustomizedButNotFilter_updatesRule() {
mZenModeHelper.mConfig.automaticRules.clear();
String pkg = mContext.getPackageName();
@@ -5866,8 +5839,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void applyGlobalZenModeAsImplicitZenRule_modeOff_deactivatesImplicitRule() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.mConfig.automaticRules.clear();
mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(mPkg, CUSTOM_PKG_UID,
ZEN_MODE_IMPORTANT_INTERRUPTIONS);
@@ -5883,8 +5856,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void applyGlobalZenModeAsImplicitZenRule_modeOffButNoPreviousRule_ignored() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.mConfig.automaticRules.clear();
mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
@@ -5894,8 +5867,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void applyGlobalZenModeAsImplicitZenRule_update_unsnoozesRule() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.mConfig.automaticRules.clear();
mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
@@ -5915,8 +5888,8 @@
}
@Test
+ @DisableFlags(FLAG_MODES_API)
public void applyGlobalZenModeAsImplicitZenRule_flagOff_ignored() {
- mSetFlagsRule.disableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.mConfig.automaticRules.clear();
withoutWtfCrash(
@@ -5928,8 +5901,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void applyGlobalPolicyAsImplicitZenRule_createsImplicitRule() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.mConfig.automaticRules.clear();
Policy policy = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
@@ -5952,8 +5925,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void applyGlobalPolicyAsImplicitZenRule_updatesImplicitRule() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.mConfig.automaticRules.clear();
Policy original = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
@@ -5983,7 +5956,7 @@
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void applyGlobalPolicyAsImplicitZenRule_ruleCustomized_doesNotUpdateRule() {
mZenModeHelper.mConfig.automaticRules.clear();
String pkg = mContext.getPackageName();
@@ -5995,7 +5968,7 @@
// Store this for checking later.
ZenPolicy originalEffectiveZenPolicy = new ZenPolicy.Builder(
- mZenModeHelper.mConfig.toZenPolicy()).allowMedia(true).build();
+ mZenModeHelper.mConfig.getZenPolicy()).allowMedia(true).build();
// From user, update that rule's policy.
AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
@@ -6023,7 +5996,7 @@
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ @EnableFlags(FLAG_MODES_API)
public void applyGlobalPolicyAsImplicitZenRule_ruleCustomizedButNotZenPolicy_updatesRule() {
mZenModeHelper.mConfig.automaticRules.clear();
String pkg = mContext.getPackageName();
@@ -6035,7 +6008,7 @@
// Store this for checking later.
ZenPolicy originalEffectiveZenPolicy = new ZenPolicy.Builder(
- mZenModeHelper.mConfig.toZenPolicy()).allowMedia(true).build();
+ mZenModeHelper.mConfig.getZenPolicy()).allowMedia(true).build();
// From user, update something in that rule, but not the ZenPolicy.
AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
@@ -6060,8 +6033,8 @@
}
@Test
+ @DisableFlags(FLAG_MODES_API)
public void applyGlobalPolicyAsImplicitZenRule_flagOff_ignored() {
- mSetFlagsRule.disableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.mConfig.automaticRules.clear();
withoutWtfCrash(
@@ -6072,8 +6045,8 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void getNotificationPolicyFromImplicitZenRule_returnsSetPolicy() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
Policy writtenPolicy = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
Policy.getAllSuppressedVisualEffects(), STATE_FALSE,
@@ -6088,32 +6061,30 @@
}
@Test
+ @EnableFlags(FLAG_MODES_API)
+ @DisableFlags(FLAG_MODES_UI)
public void getNotificationPolicyFromImplicitZenRule_ruleWithoutPolicy_copiesGlobalPolicy() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
- mZenModeHelper.mConfig.allowCalls = true;
- mZenModeHelper.mConfig.allowConversations = false;
-
// Implicit rule will get the global policy at the time of rule creation.
mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- ZEN_MODE_ALARMS);
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS);
- // If the policy then changes afterwards, we should keep the snapshotted version.
- mZenModeHelper.mConfig.allowCalls = false;
-
+ // If the policy then changes afterwards, it should inherit updates because user cannot
+ // edit the policy in the UI.
+ mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_ALARMS, 0, 0),
+ UPDATE_ORIGIN_APP, 1);
Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
CUSTOM_PKG_NAME);
assertThat(readPolicy).isNotNull();
- assertThat(readPolicy.allowCalls()).isTrue();
- assertThat(readPolicy.allowConversations()).isFalse();
+ assertThat(readPolicy.allowCalls()).isFalse();
+ assertThat(readPolicy.allowAlarms()).isTrue();
}
@Test
+ @EnableFlags(FLAG_MODES_API)
public void getNotificationPolicyFromImplicitZenRule_noImplicitRule_returnsGlobalPolicy() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
-
- mZenModeHelper.mConfig.allowCalls = true;
- mZenModeHelper.mConfig.allowConversations = false;
+ Policy policy = new Policy(PRIORITY_CATEGORY_CALLS, PRIORITY_SENDERS_STARRED, 0);
+ mZenModeHelper.setNotificationPolicy(policy, UPDATE_ORIGIN_USER, 1);
Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
CUSTOM_PKG_NAME);
@@ -6124,8 +6095,8 @@
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
- @DisableFlags(Flags.FLAG_MODES_UI)
+ @EnableFlags(FLAG_MODES_API)
+ @DisableFlags(FLAG_MODES_UI)
public void setNotificationPolicy_updatesRulePolicies_ifRulePolicyIsDefaultOrGlobalPolicy() {
ZenPolicy defaultZenPolicy = mZenModeHelper.getDefaultZenPolicy();
Policy previousManualPolicy = mZenModeHelper.mConfig.toNotificationPolicy();
@@ -6185,6 +6156,35 @@
assertThat(storedRule.getIconResId()).isEqualTo(0);
}
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void setManualZenRuleDeviceEffects_noPreexistingMode() {
+ ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
+ .setShouldDimWallpaper(true)
+ .build();
+ mZenModeHelper.setManualZenRuleDeviceEffects(effects, UPDATE_ORIGIN_USER, "settings", 1000);
+
+ assertThat(mZenModeHelper.getConfig().manualRule).isNotNull();
+ assertThat(mZenModeHelper.getConfig().isManualActive()).isFalse();
+ assertThat(mZenModeHelper.getConfig().manualRule.zenDeviceEffects).isEqualTo(effects);
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void setManualZenRuleDeviceEffects_preexistingMode() {
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, Uri.EMPTY, UPDATE_ORIGIN_USER,
+ "create manual rule", "settings", 1000);
+
+ ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
+ .setShouldDimWallpaper(true)
+ .build();
+ mZenModeHelper.setManualZenRuleDeviceEffects(effects, UPDATE_ORIGIN_USER, "settings", 1000);
+
+ assertThat(mZenModeHelper.getConfig().manualRule).isNotNull();
+ assertThat(mZenModeHelper.getConfig().isManualActive()).isFalse();
+ assertThat(mZenModeHelper.getConfig().manualRule.zenDeviceEffects).isEqualTo(effects);
+ }
+
private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode,
@Nullable ZenPolicy zenPolicy) {
ZenRule rule = new ZenRule();
@@ -6246,34 +6246,19 @@
// TODO: b/310620812 - Update setup methods to include allowChannels() when MODES_API is inlined
private void setupZenConfig() {
- mZenModeHelper.mZenMode = ZEN_MODE_OFF;
- mZenModeHelper.mConfig.allowAlarms = false;
- mZenModeHelper.mConfig.allowMedia = false;
- mZenModeHelper.mConfig.allowSystem = false;
- mZenModeHelper.mConfig.allowReminders = true;
- mZenModeHelper.mConfig.allowCalls = true;
- mZenModeHelper.mConfig.allowCallsFrom = PRIORITY_SENDERS_STARRED;
- mZenModeHelper.mConfig.allowMessages = true;
- mZenModeHelper.mConfig.allowConversations = true;
- mZenModeHelper.mConfig.allowEvents = true;
- mZenModeHelper.mConfig.allowRepeatCallers = true;
- mZenModeHelper.mConfig.suppressedVisualEffects = SUPPRESSED_EFFECT_BADGE;
- mZenModeHelper.mConfig.manualRule = null;
- }
-
- private void setupZenConfigMaintained() {
- // config is still the same as when it was setup (setupZenConfig)
- assertFalse(mZenModeHelper.mConfig.allowAlarms);
- assertFalse(mZenModeHelper.mConfig.allowMedia);
- assertFalse(mZenModeHelper.mConfig.allowSystem);
- assertTrue(mZenModeHelper.mConfig.allowReminders);
- assertTrue(mZenModeHelper.mConfig.allowCalls);
- assertEquals(PRIORITY_SENDERS_STARRED, mZenModeHelper.mConfig.allowCallsFrom);
- assertTrue(mZenModeHelper.mConfig.allowMessages);
- assertTrue(mZenModeHelper.mConfig.allowConversations);
- assertTrue(mZenModeHelper.mConfig.allowEvents);
- assertTrue(mZenModeHelper.mConfig.allowRepeatCallers);
- assertEquals(SUPPRESSED_EFFECT_BADGE, mZenModeHelper.mConfig.suppressedVisualEffects);
+ Policy customPolicy = new Policy(PRIORITY_CATEGORY_REMINDERS
+ | PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_MESSAGES
+ | PRIORITY_CATEGORY_EVENTS | PRIORITY_CATEGORY_REPEAT_CALLERS
+ | PRIORITY_CATEGORY_CONVERSATIONS,
+ PRIORITY_SENDERS_STARRED,
+ PRIORITY_SENDERS_STARRED,
+ SUPPRESSED_EFFECT_BADGE,
+ 0,
+ CONVERSATION_SENDERS_IMPORTANT);
+ mZenModeHelper.setNotificationPolicy(customPolicy, UPDATE_ORIGIN_UNKNOWN, 1);
+ if (!Flags.modesUi()) {
+ mZenModeHelper.mConfig.manualRule = null;
+ }
}
private void checkDndProtoMatchesSetupZenConfig(DNDPolicyProto dndProto) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java b/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java
index 8cf2776..3e87f1f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java
@@ -125,7 +125,7 @@
processDuration < PRE_TASK_DELAY_MS);
assertTrue("Target didn't call callback enough times.",
- mFactory.waitForAllExpectedItemsProcessed(TIMEOUT_ALLOWANCE));
+ mListener.waitForAllExpectedCallbackDone(TIMEOUT_ALLOWANCE));
// Once before processing this item, once after that.
assertEquals(2, mListener.mProbablyDoneResults.size());
// The last one must be called with probably done being true.
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index e8bac66..175a09d 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -80,6 +80,7 @@
import android.service.usb.UsbHandlerProto;
import android.util.Pair;
import android.util.Slog;
+import android.text.TextUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
@@ -110,6 +111,8 @@
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* UsbDeviceManager manages USB state in device mode.
@@ -135,6 +138,11 @@
*/
private static final String NORMAL_BOOT = "normal";
+ /**
+ * UDC controller for the ConfigFS USB Gadgets.
+ */
+ private static final String USB_CONTROLLER_NAME_PROPERTY = "sys.usb.controller";
+
private static final String USB_STATE_MATCH =
"DEVPATH=/devices/virtual/android_usb/android0";
private static final String ACCESSORY_START_MATCH =
@@ -932,6 +940,42 @@
sEventLogger.enqueue(new EventLogger.StringEvent("USB intent: " + intent));
}
+ private void getMidiCardDevice() throws FileNotFoundException {
+ String controllerName = getSystemProperty(USB_CONTROLLER_NAME_PROPERTY, "");
+ if (TextUtils.isEmpty(controllerName)) {
+ throw new FileNotFoundException("controller name not found");
+ }
+
+ File soundDir = new File("/sys/class/udc/" + controllerName + "/gadget/sound");
+ if (!soundDir.exists()) {
+ throw new FileNotFoundException("sound device not found");
+ }
+
+ // There should be exactly one sound card
+ File[] cardDirs = FileUtils.listFilesOrEmpty(soundDir,
+ (dir, file) -> file.startsWith("card"));
+ if (cardDirs.length != 1) {
+ throw new FileNotFoundException("sound card not match");
+ }
+
+ // There should be exactly one midi device
+ File[] midis = FileUtils.listFilesOrEmpty(cardDirs[0],
+ (dir, file) -> file.startsWith("midi"));
+ if (midis.length != 1) {
+ throw new FileNotFoundException("MIDI device not match");
+ }
+
+ Pattern pattern = Pattern.compile("midiC(\\d+)D(\\d+)");
+ Matcher matcher = pattern.matcher(midis[0].getName());
+ if (matcher.matches()) {
+ mMidiCard = Integer.parseInt(matcher.group(1));
+ mMidiDevice = Integer.parseInt(matcher.group(2));
+ Slog.i(TAG, "Found MIDI card " + mMidiCard + " device " + mMidiDevice);
+ } else {
+ throw new FileNotFoundException("MIDI name not match");
+ }
+ }
+
private void updateUsbFunctions() {
updateMidiFunction();
updateMtpFunction();
@@ -941,17 +985,26 @@
boolean enabled = (mCurrentFunctions & UsbManager.FUNCTION_MIDI) != 0;
if (enabled != mMidiEnabled) {
if (enabled) {
- Scanner scanner = null;
- try {
- scanner = new Scanner(new File(MIDI_ALSA_PATH));
- mMidiCard = scanner.nextInt();
- mMidiDevice = scanner.nextInt();
- } catch (FileNotFoundException e) {
- Slog.e(TAG, "could not open MIDI file", e);
- enabled = false;
- } finally {
- if (scanner != null) {
- scanner.close();
+ if (android.hardware.usb.flags.Flags.enableUsbSysfsMidiIdentification()) {
+ try {
+ getMidiCardDevice();
+ } catch (FileNotFoundException e) {
+ Slog.e(TAG, "could not identify MIDI device", e);
+ enabled = false;
+ }
+ } else {
+ Scanner scanner = null;
+ try {
+ scanner = new Scanner(new File(MIDI_ALSA_PATH));
+ mMidiCard = scanner.nextInt();
+ mMidiDevice = scanner.nextInt();
+ } catch (FileNotFoundException e) {
+ Slog.e(TAG, "could not open MIDI file", e);
+ enabled = false;
+ } finally {
+ if (scanner != null) {
+ scanner.close();
+ }
}
}
}
diff --git a/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
index c35242d..5f9a524 100644
--- a/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
+++ b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
@@ -242,6 +242,16 @@
&& Arrays.equals(mPolicyRuleFlags, that.mPolicyRuleFlags);
}
+ @Override
+ public int hashCode() {
+ int result = Arrays.hashCode(mPolicyRules);
+ result = 31 * result + Arrays.hashCode(mPolicyRuleFlags);
+ for (int i = 0; i < mCarrierIds.length; i++) {
+ result = 31 * result + Arrays.hashCode(mCarrierIds[i]);
+ }
+ return result;
+ }
+
private EuiccRulesAuthTable(Parcel source) {
mPolicyRules = source.createIntArray();
int len = mPolicyRules.length;
diff --git a/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java
index f548dba..c7af661 100644
--- a/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java
+++ b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java
@@ -256,6 +256,26 @@
}
/**
+ * Get the attribute flag ATTR_REGISTRATION_TYPE_EMERGENCY.
+ * @return {@code true} if the ATTR_REGISTRATION_TYPE_EMERGENCY attribute has been set, or
+ * {@code false} if it has not been set.
+ */
+ @FlaggedApi(Flags.FLAG_EMERGENCY_REGISTRATION_STATE)
+ public boolean getFlagRegistrationTypeEmergency() {
+ return (mImsAttributeFlags & ATTR_REGISTRATION_TYPE_EMERGENCY) != 0;
+ }
+
+ /**
+ * Get the attribute flag ATTR_VIRTUAL_FOR_ANONYMOUS_EMERGENCY_CALL.
+ * @return {@code true} if the ATTR_VIRTUAL_FOR_ANONYMOUS_EMERGENCY_CALL attribute has been set,
+ * or {@code false} if it has not been set.
+ */
+ @FlaggedApi(Flags.FLAG_EMERGENCY_REGISTRATION_STATE)
+ public boolean getFlagVirtualRegistrationForEmergencyCall() {
+ return (mImsAttributeFlags & ATTR_VIRTUAL_FOR_ANONYMOUS_EMERGENCY_CALL) != 0;
+ }
+
+ /**
* Gets the Set of feature tags associated with the current IMS registration, if the IMS
* service supports supplying this information.
* <p>
diff --git a/tests/TouchLatency/app/src/main/res/values/styles.xml b/tests/TouchLatency/app/src/main/res/values/styles.xml
index fa352cf..5058331 100644
--- a/tests/TouchLatency/app/src/main/res/values/styles.xml
+++ b/tests/TouchLatency/app/src/main/res/values/styles.xml
@@ -18,7 +18,7 @@
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
<!-- Customize your theme here. -->
- <item name="android:windowLayoutInDisplayCutoutMode">default</item>
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
</resources>