Merge "Ensure group summary (and any notifications added directly by NMS) have the correct token" into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5393475..9e2872f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -11471,6 +11471,19 @@
}
+package android.os.vibrator.persistence {
+
+ @FlaggedApi("android.os.vibrator.vibration_xml_apis") public final class ParsedVibration {
+ method @FlaggedApi("android.os.vibrator.vibration_xml_apis") @Nullable public android.os.VibrationEffect resolve(@NonNull android.os.Vibrator);
+ }
+
+ @FlaggedApi("android.os.vibrator.vibration_xml_apis") public final class VibrationXmlParser {
+ method @FlaggedApi("android.os.vibrator.vibration_xml_apis") @NonNull public static android.os.vibrator.persistence.ParsedVibration parse(@NonNull java.io.InputStream) throws java.io.IOException;
+ method @FlaggedApi("android.os.vibrator.vibration_xml_apis") @NonNull public static android.os.VibrationEffect parseVibrationEffect(@NonNull java.io.InputStream) throws java.io.IOException;
+ }
+
+}
+
package android.permission {
public final class AdminPermissionControlParams implements android.os.Parcelable {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index d899511..1352465 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2761,21 +2761,24 @@
package android.os.vibrator.persistence {
- public class ParsedVibration {
- method @NonNull public java.util.List<android.os.VibrationEffect> getVibrationEffects();
- method @Nullable public android.os.VibrationEffect resolve(@NonNull android.os.Vibrator);
+ @FlaggedApi("android.os.vibrator.vibration_xml_apis") public final class ParsedVibration {
+ ctor public ParsedVibration(@NonNull java.util.List<android.os.VibrationEffect>);
+ method @FlaggedApi("android.os.vibrator.vibration_xml_apis") @Nullable public android.os.VibrationEffect resolve(@NonNull android.os.Vibrator);
}
- public final class VibrationXmlParser {
- method @Nullable public static android.os.vibrator.persistence.ParsedVibration parseDocument(@NonNull java.io.Reader) throws java.io.IOException;
- method @Nullable public static android.os.VibrationEffect parseVibrationEffect(@NonNull java.io.Reader) throws java.io.IOException;
+ @FlaggedApi("android.os.vibrator.vibration_xml_apis") public final class VibrationXmlParser {
+ method @FlaggedApi("android.os.vibrator.vibration_xml_apis") @NonNull public static android.os.vibrator.persistence.ParsedVibration parse(@NonNull java.io.InputStream) throws java.io.IOException;
+ method @FlaggedApi("android.os.vibrator.vibration_xml_apis") @NonNull public static android.os.VibrationEffect parseVibrationEffect(@NonNull java.io.InputStream) throws java.io.IOException;
+ }
+
+ public static final class VibrationXmlParser.ParseFailedException extends java.io.IOException {
}
public final class VibrationXmlSerializer {
- method public static void serialize(@NonNull android.os.VibrationEffect, @NonNull java.io.Writer) throws java.io.IOException, android.os.vibrator.persistence.VibrationXmlSerializer.SerializationFailedException;
+ method public static void serialize(@NonNull android.os.VibrationEffect, @NonNull java.io.Writer) throws java.io.IOException;
}
- public static final class VibrationXmlSerializer.SerializationFailedException extends java.lang.RuntimeException {
+ public static final class VibrationXmlSerializer.SerializationFailedException extends java.io.IOException {
}
}
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index b2dcf90..91caedc 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -740,6 +740,12 @@
*/
void onBlockingScreenOn(Runnable unblocker);
+ /**
+ * Called while display is turning to screen state other than state ON to notify that any
+ * pending work from the previous blockScreenOn call should have been cancelled.
+ */
+ void cancelBlockScreenOn();
+
/** Whether auto brightness update in doze is allowed */
boolean allowAutoBrightnessInDoze();
}
@@ -774,6 +780,12 @@
boolean blockScreenOn(Runnable unblocker);
/**
+ * Called while display is turning to screen state other than state ON to notify that any
+ * pending work from the previous blockScreenOn call should have been cancelled.
+ */
+ void cancelBlockScreenOn();
+
+ /**
* Get the brightness levels used to determine automatic brightness based on lux levels.
* @param mode The auto-brightness mode
* (AutomaticBrightnessController.AutomaticBrightnessMode)
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index b01ffe5..c73a422 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -42,3 +42,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ namespace: "haptics"
+ name: "vibration_xml_apis"
+ description: "Enabled System APIs for vibration effect XML parser and serializer"
+ bug: "347273158"
+ metadata {
+ purpose: PURPOSE_FEATURE
+ }
+}
diff --git a/core/java/android/os/vibrator/persistence/ParsedVibration.java b/core/java/android/os/vibrator/persistence/ParsedVibration.java
index a16d21e..e5543ab 100644
--- a/core/java/android/os/vibrator/persistence/ParsedVibration.java
+++ b/core/java/android/os/vibrator/persistence/ParsedVibration.java
@@ -16,31 +16,35 @@
package android.os.vibrator.persistence;
+import static android.os.vibrator.Flags.FLAG_VIBRATION_XML_APIS;
+
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorInfo;
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.Collections;
import java.util.List;
+import java.util.Objects;
/**
- * The result of parsing a serialized vibration, which can be define by one or more
- * {@link VibrationEffect} and a resolution method.
+ * The result of parsing a serialized vibration.
+ *
+ * @see VibrationXmlParser
*
* @hide
*/
-@TestApi
-@SuppressLint("UnflaggedApi") // @TestApi without associated feature.
-public class ParsedVibration {
+@TestApi // This was used in CTS before the flag was introduced.
+@SystemApi
+@FlaggedApi(FLAG_VIBRATION_XML_APIS)
+public final class ParsedVibration {
private final List<VibrationEffect> mEffects;
/** @hide */
+ @TestApi
public ParsedVibration(@NonNull List<VibrationEffect> effects) {
mEffects = effects;
}
@@ -49,40 +53,28 @@
public ParsedVibration(@NonNull VibrationEffect effect) {
mEffects = List.of(effect);
}
+
/**
* Returns the first parsed vibration supported by {@code vibrator}, or {@code null} if none of
* the parsed vibrations are supported.
*
* @hide
*/
- @TestApi
+ @TestApi // This was used in CTS before the flag was introduced.
+ @SystemApi
+ @FlaggedApi(FLAG_VIBRATION_XML_APIS)
@Nullable
public VibrationEffect resolve(@NonNull Vibrator vibrator) {
return resolve(vibrator.getInfo());
}
/**
- * Returns the parsed vibrations for testing purposes.
- *
- * <p>Real callers should not use this method. Instead, they should resolve to a
- * {@link VibrationEffect} via {@link #resolve(Vibrator)}.
- *
- * @hide
- */
- @TestApi
- @VisibleForTesting
- @NonNull
- public List<VibrationEffect> getVibrationEffects() {
- return Collections.unmodifiableList(mEffects);
- }
-
- /**
* Same as {@link #resolve(Vibrator)}, but uses {@link VibratorInfo} instead for resolving.
*
* @hide
*/
@Nullable
- public final VibrationEffect resolve(@NonNull VibratorInfo info) {
+ public VibrationEffect resolve(@NonNull VibratorInfo info) {
for (int i = 0; i < mEffects.size(); i++) {
VibrationEffect effect = mEffects.get(i);
if (info.areVibrationFeaturesSupported(effect)) {
@@ -91,4 +83,21 @@
}
return null;
}
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ParsedVibration)) {
+ return false;
+ }
+ ParsedVibration other = (ParsedVibration) o;
+ return mEffects.equals(other.mEffects);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mEffects);
+ }
}
diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlParser.java b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java
index 7202d9a..e2312e0 100644
--- a/core/java/android/os/vibrator/persistence/VibrationXmlParser.java
+++ b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java
@@ -16,13 +16,15 @@
package android.os.vibrator.persistence;
+import static android.os.vibrator.Flags.FLAG_VIBRATION_XML_APIS;
+
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.VibrationEffect;
-import android.util.Slog;
import android.util.Xml;
import com.android.internal.vibrator.persistence.VibrationEffectXmlParser;
@@ -36,9 +38,12 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@@ -116,10 +121,10 @@
*
* @hide
*/
-@TestApi
-@SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+@TestApi // This was used in CTS before the flag was introduced.
+@SystemApi
+@FlaggedApi(FLAG_VIBRATION_XML_APIS)
public final class VibrationXmlParser {
- private static final String TAG = "VibrationXmlParser";
/**
* The MIME type for a xml holding a vibration.
@@ -168,55 +173,12 @@
}
/**
- * Parses XML content from given input stream into a {@link VibrationEffect}.
- *
- * <p>This method parses an XML content that contains a single, complete {@link VibrationEffect}
- * serialization. As such, the root tag must be a "vibration" tag.
- *
- * <p>This parser fails silently and returns {@code null} if the content of the input stream
- * does not follow the schema or has unsupported values.
- *
- * @return the {@link VibrationEffect} if parsed successfully, {@code null} otherwise.
- * @throws IOException error reading from given {@link Reader}
- *
- * @hide
- */
- @TestApi
- @Nullable
- public static VibrationEffect parseVibrationEffect(@NonNull Reader reader) throws IOException {
- return parseVibrationEffect(reader, /* flags= */ 0);
- }
-
- /**
- * Parses XML content from given input stream into a {@link VibrationEffect}.
- *
- * <p>This method parses an XML content that contains a single, complete {@link VibrationEffect}
- * serialization. As such, the root tag must be a "vibration" tag.
- *
- * <p>Same as {@link #parseVibrationEffect(Reader)}, with extra flags to control the parsing
- * behavior.
- *
- * @hide
- */
- @Nullable
- public static VibrationEffect parseVibrationEffect(@NonNull Reader reader, @Flags int flags)
- throws IOException {
- try {
- return parseDocumentInternal(
- reader, flags, VibrationXmlParser::parseVibrationEffectInternal);
- } catch (XmlParserException | XmlPullParserException e) {
- Slog.w(TAG, "Error parsing vibration XML", e);
- return null;
- }
- }
-
- /**
* Parses XML content from given input stream into a {@link ParsedVibration}.
*
- * <p>It supports both the "vibration" and "vibration-select" root tags.
+ * <p>It supports both the "vibration-effect" and "vibration-select" root tags.
* <ul>
- * <li>If "vibration" is the root tag, the serialization provided through {@code reader}
- * should contain a valid serialization for a single vibration.
+ * <li>If "vibration-effect" is the root tag, the serialization provided should contain a
+ * valid serialization for a single vibration.
* <li>If "vibration-select" is the root tag, the serialization may contain one or more
* valid vibration serializations.
* </ul>
@@ -225,36 +187,95 @@
* vibration(s), and the caller can get a concrete {@link VibrationEffect} by resolving this
* result to a specific vibrator.
*
- * <p>This parser fails silently and returns {@code null} if the content of the input does not
- * follow the schema or has unsupported values.
+ * <p>This parser fails with an exception if the content of the input stream does not follow the
+ * schema or has unsupported values.
*
* @return a {@link ParsedVibration}
- * @throws IOException error reading from given {@link Reader}
+ * @throws IOException error reading from given {@link InputStream} or parsing the content.
*
* @hide
*/
- @TestApi
- @Nullable
+ @TestApi // Replacing test APIs used in CTS before the flagged system APIs was introduced.
+ @SystemApi
+ @FlaggedApi(FLAG_VIBRATION_XML_APIS)
+ @NonNull
+ public static ParsedVibration parse(@NonNull InputStream inputStream) throws IOException {
+ return parseDocument(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
+ }
+
+ /**
+ * Parses XML content from given input stream into a single {@link VibrationEffect}.
+ *
+ * <p>This method parses an XML content that contains a single, complete {@link VibrationEffect}
+ * serialization. As such, the root tag must be a "vibration-effect" tag.
+ *
+ * <p>This parser fails with an exception if the content of the input stream does not follow the
+ * schema or has unsupported values.
+ *
+ * @return the parsed {@link VibrationEffect}
+ * @throws IOException error reading from given {@link InputStream} or parsing the content.
+ *
+ * @hide
+ */
+ @TestApi // Replacing test APIs used in CTS before the flagged system APIs was introduced.
+ @SystemApi
+ @FlaggedApi(FLAG_VIBRATION_XML_APIS)
+ @NonNull
+ public static VibrationEffect parseVibrationEffect(@NonNull InputStream inputStream)
+ throws IOException {
+ return parseVibrationEffect(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
+ }
+
+ /**
+ * Parses XML content from given {@link Reader} into a {@link VibrationEffect}.
+ *
+ * <p>Same as {@link #parseVibrationEffect(InputStream)}, but with a {@link Reader}.
+ *
+ * @hide
+ */
+ @NonNull
+ public static VibrationEffect parseVibrationEffect(@NonNull Reader reader) throws IOException {
+ return parseVibrationEffect(reader, /* flags= */ 0);
+ }
+
+ /**
+ * Parses XML content from given {@link Reader} into a {@link VibrationEffect}.
+ *
+ * <p>Same as {@link #parseVibrationEffect(Reader)}, with extra flags to control the parsing
+ * behavior.
+ *
+ * @hide
+ */
+ @NonNull
+ public static VibrationEffect parseVibrationEffect(@NonNull Reader reader, @Flags int flags)
+ throws IOException {
+ return parseDocumentInternal(reader, flags,
+ VibrationXmlParser::parseVibrationEffectInternal);
+ }
+
+ /**
+ * Parses XML content from given {@link Reader} into a {@link ParsedVibration}.
+ *
+ * <p>Same as {@link #parse(InputStream)}, but with a {@link Reader}.
+ *
+ * @hide
+ */
+ @NonNull
public static ParsedVibration parseDocument(@NonNull Reader reader) throws IOException {
return parseDocument(reader, /* flags= */ 0);
}
/**
- * Parses XML content from given input stream into a {@link ParsedVibration}.
+ * Parses XML content from given {@link Reader} into a {@link ParsedVibration}.
*
* <p>Same as {@link #parseDocument(Reader)}, with extra flags to control the parsing behavior.
*
* @hide
*/
- @Nullable
+ @NonNull
public static ParsedVibration parseDocument(@NonNull Reader reader, @Flags int flags)
throws IOException {
- try {
- return parseDocumentInternal(reader, flags, VibrationXmlParser::parseElementInternal);
- } catch (XmlParserException | XmlPullParserException e) {
- Slog.w(TAG, "Error parsing vibration/vibration-select XML", e);
- return null;
- }
+ return parseDocumentInternal(reader, flags, VibrationXmlParser::parseElementInternal);
}
/**
@@ -262,7 +283,7 @@
* {@link ParsedVibration}.
*
* <p>Same as {@link #parseDocument(Reader, int)}, but, instead of parsing the full XML content,
- * it takes a parser that points to either a <vibration-effect> or a <vibration-select> start
+ * it takes a parser that points to either a "vibration-effect" or a "vibration-select" start
* tag. No other parser position, including start of document, is considered valid.
*
* <p>This method parses until an end "vibration-effect" or "vibration-select" tag (depending
@@ -270,37 +291,22 @@
* will point to the end tag.
*
* @throws IOException error parsing from given {@link TypedXmlPullParser}.
- * @throws VibrationXmlParserException if the XML tag cannot be parsed into a
- * {@link ParsedVibration}. The given {@code parser} might be pointing to a child XML tag
- * that caused the parser failure.
+ * The given {@code parser} might be pointing to a child XML tag that caused the parser
+ * failure.
*
* @hide
*/
@NonNull
public static ParsedVibration parseElement(@NonNull TypedXmlPullParser parser, @Flags int flags)
- throws IOException, VibrationXmlParserException {
+ throws IOException {
try {
return parseElementInternal(parser, flags);
} catch (XmlParserException e) {
- throw new VibrationXmlParserException("Error parsing vibration-select.", e);
+ throw new ParseFailedException(e);
}
}
- /**
- * Represents an error while parsing a vibration XML input.
- *
- * @hide
- */
- public static final class VibrationXmlParserException extends Exception {
- private VibrationXmlParserException(String message, Throwable cause) {
- super(message, cause);
- }
-
- private VibrationXmlParserException(String message) {
- super(message);
- }
- }
-
+ @NonNull
private static ParsedVibration parseElementInternal(
@NonNull TypedXmlPullParser parser, @Flags int flags)
throws IOException, XmlParserException {
@@ -313,11 +319,12 @@
case XmlConstants.TAG_VIBRATION_SELECT:
return parseVibrationSelectInternal(parser, flags);
default:
- throw new XmlParserException(
- "Unexpected tag name when parsing element: " + tagName);
+ throw new ParseFailedException(
+ "Unexpected tag " + tagName + " when parsing a vibration");
}
}
+ @NonNull
private static ParsedVibration parseVibrationSelectInternal(
@NonNull TypedXmlPullParser parser, @Flags int flags)
throws IOException, XmlParserException {
@@ -332,7 +339,7 @@
return new ParsedVibration(effects);
}
- /** Parses a single XML element for "vibration" tag into a {@link VibrationEffect}. */
+ @NonNull
private static VibrationEffect parseVibrationEffectInternal(
@NonNull TypedXmlPullParser parser, @Flags int flags)
throws IOException, XmlParserException {
@@ -347,32 +354,60 @@
* This method parses a whole XML document (provided through a {@link Reader}). The root tag is
* parsed as per a provided {@link ElementParser}.
*/
+ @NonNull
private static <T> T parseDocumentInternal(
@NonNull Reader reader, @Flags int flags, ElementParser<T> parseLogic)
- throws IOException, XmlParserException, XmlPullParserException {
- TypedXmlPullParser parser = Xml.newFastPullParser();
- parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
- parser.setInput(reader);
+ throws IOException {
+ try {
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+ parser.setInput(reader);
- // Ensure XML starts with a document start tag.
- XmlReader.readDocumentStart(parser);
+ // Ensure XML starts with a document start tag.
+ XmlReader.readDocumentStart(parser);
- // Parse root tag.
- T result = parseLogic.parse(parser, flags);
+ // Parse root tag.
+ T result = parseLogic.parse(parser, flags);
- // Ensure XML ends after root tag is consumed.
- XmlReader.readDocumentEndTag(parser);
+ // Ensure XML ends after root tag is consumed.
+ XmlReader.readDocumentEndTag(parser);
- return result;
+ return result;
+ } catch (XmlPullParserException e) {
+ throw new ParseFailedException("Error initializing XMLPullParser", e);
+ } catch (XmlParserException e) {
+ throw new ParseFailedException(e);
+ }
}
/** Encapsulate a logic to parse an XML element from an open parser. */
private interface ElementParser<T> {
/** Parses a single XML element starting from the current position of the {@code parser}. */
+ @NonNull
T parse(@NonNull TypedXmlPullParser parser, @Flags int flags)
throws IOException, XmlParserException;
}
+ /**
+ * Represents an error while parsing a vibration XML input.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final class ParseFailedException extends IOException {
+ private ParseFailedException(String message) {
+ super(message);
+ }
+
+ private ParseFailedException(XmlParserException parserException) {
+ this(parserException.getMessage(), parserException);
+ }
+
+ private ParseFailedException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+
private VibrationXmlParser() {
}
}
diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java
index 2065d5d..a26c6f4 100644
--- a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java
+++ b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java
@@ -18,9 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.SuppressLint;
import android.annotation.TestApi;
-import android.os.CombinedVibration;
import android.os.VibrationEffect;
import android.util.Xml;
@@ -37,14 +35,13 @@
import java.lang.annotation.RetentionPolicy;
/**
- * Serializes {@link CombinedVibration} and {@link VibrationEffect} instances to XML.
+ * Serializes {@link VibrationEffect} instances to XML.
*
* <p>This uses the same schema expected by the {@link VibrationXmlParser}.
*
* @hide
*/
@TestApi
-@SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public final class VibrationXmlSerializer {
/**
@@ -80,20 +77,19 @@
"http://xmlpull.org/v1/doc/features.html#indent-output";
/**
- * Serializes a {@link VibrationEffect} to XML and writes output to given {@link Writer}.
+ * Serializes a {@link VibrationEffect} to XML and writes output to given {@link Writer} using
+ * UTF-8 encoding.
*
- * <p>This method will only write into the {@link Writer} if the effect can successfully
- * be represented by the XML serialization. It will throw an exception otherwise.
+ * <p>This method will only write to the stream if the effect can successfully be represented by
+ * the XML serialization. It will throw an exception otherwise.
*
- * @throws SerializationFailedException serialization of input effect failed, no data was
- * written into given {@link Writer}.
- * @throws IOException error writing to given {@link Writer}.
+ * @throws IOException serialization of input effect failed or error writing to output stream.
*
* @hide
*/
@TestApi
public static void serialize(@NonNull VibrationEffect effect, @NonNull Writer writer)
- throws SerializationFailedException, IOException {
+ throws IOException {
serialize(effect, writer, /* flags= */ 0);
}
@@ -106,7 +102,7 @@
* @hide
*/
public static void serialize(@NonNull VibrationEffect effect, @NonNull Writer writer,
- @Flags int flags) throws SerializationFailedException, IOException {
+ @Flags int flags) throws IOException {
// Serialize effect first to fail early.
XmlSerializedVibration<VibrationEffect> serializedVibration =
toSerializedVibration(effect, flags);
@@ -138,17 +134,16 @@
}
/**
- * Exception thrown when a {@link VibrationEffect} instance serialization fails.
+ * Exception thrown when a {@link VibrationEffect} serialization fails.
*
* <p>The serialization can fail if a given vibration cannot be represented using the public
- * format, or if it uses hidden APIs that are not supported for serialization (e.g.
- * {@link VibrationEffect.WaveformBuilder}).
+ * format, or if it uses a non-public representation that is not supported for serialization.
*
* @hide
*/
@TestApi
- public static final class SerializationFailedException extends RuntimeException {
- SerializationFailedException(VibrationEffect effect, Throwable cause) {
+ public static final class SerializationFailedException extends IOException {
+ private SerializationFailedException(VibrationEffect effect, Throwable cause) {
super("Serialization failed for vibration effect " + effect, cause);
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7a1ab7b..8c3390c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -30607,7 +30607,8 @@
* {@link #setPointerIcon(PointerIcon)} for mouse devices. Subclasses may override this to
* customize the icon for the given pointer.
*
- * For example, the pointer icon for a stylus pointer can be resolved in the following way:
+ * For example, to always show the PointerIcon.TYPE_HANDWRITING icon for a stylus pointer,
+ * the event can be resolved in the following way:
* <code><pre>
* @Override
* public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
@@ -30617,7 +30618,7 @@
* && (toolType == MotionEvent.TOOL_TYPE_STYLUS
* || toolType == MotionEvent.TOOL_TYPE_ERASER)) {
* // Show this pointer icon only if this pointer is a stylus.
- * return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_WAIT);
+ * return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HANDWRITING);
* }
* // Use the default logic for determining the pointer icon for other non-stylus pointers,
* // like for the mouse cursor.
diff --git a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
index a7aef92..6b0ca9f 100644
--- a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
+++ b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
@@ -181,6 +181,27 @@
}
/**
+ * Converts {@link Settings.Secure} key to {@link UserShortcutType}.
+ *
+ * @param key The shortcut key in Settings.
+ * @return The mapped type
+ */
+ @UserShortcutType
+ public static int convertToType(String key) {
+ return switch (key) {
+ case Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS -> UserShortcutType.SOFTWARE;
+ case Settings.Secure.ACCESSIBILITY_QS_TARGETS -> UserShortcutType.QUICK_SETTINGS;
+ case Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE -> UserShortcutType.HARDWARE;
+ case Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED ->
+ UserShortcutType.TRIPLETAP;
+ case Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED ->
+ UserShortcutType.TWOFINGER_DOUBLETAP;
+ default -> throw new IllegalArgumentException(
+ "Unsupported user shortcut key: " + key);
+ };
+ }
+
+ /**
* Updates an accessibility state if the accessibility service is a Always-On a11y service,
* a.k.a. AccessibilityServices that has FLAG_REQUEST_ACCESSIBILITY_BUTTON
* <p>
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 61eaa52..e21d1df 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -127,6 +127,7 @@
],
defaults: [
+ "aconfig_lib_cc_shared_link.defaults",
"latest_android_media_audio_common_types_cpp_target_shared",
],
@@ -365,6 +366,7 @@
"libdl_android",
"libtimeinstate",
"server_configurable_flags",
+ "libaconfig_storage_read_api_cc",
"libimage_io",
"libultrahdr",
"libperfetto_c",
diff --git a/core/tests/bugreports/Android.bp b/core/tests/bugreports/Android.bp
index 7c1ac48..15e07e5 100644
--- a/core/tests/bugreports/Android.bp
+++ b/core/tests/bugreports/Android.bp
@@ -30,6 +30,7 @@
"android.test.base",
],
static_libs: [
+ "android.tracing.flags-aconfig-java",
"androidx.test.rules",
"androidx.test.uiautomator_uiautomator",
"truth",
diff --git a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
index 8072d69..7294d4c 100644
--- a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
+++ b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
@@ -71,6 +71,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
@@ -102,17 +103,11 @@
// associated with the bugreport).
private static final String INTENT_BUGREPORT_FINISHED =
"com.android.internal.intent.action.BUGREPORT_FINISHED";
- private static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
- private static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
- private static final Path[] UI_TRACES_PREDUMPED = {
+ private ArrayList<Path> mUiTracesPreDumped = new ArrayList<>(Arrays.asList(
Paths.get("/data/misc/perfetto-traces/bugreport/systrace.pftrace"),
- Paths.get("/data/misc/wmtrace/ime_trace_clients.winscope"),
- Paths.get("/data/misc/wmtrace/ime_trace_managerservice.winscope"),
- Paths.get("/data/misc/wmtrace/ime_trace_service.winscope"),
- Paths.get("/data/misc/wmtrace/wm_trace.winscope"),
- Paths.get("/data/misc/wmtrace/wm_log.winscope"),
- };
+ Paths.get("/data/misc/wmtrace/wm_trace.winscope")
+ ));
private Handler mHandler;
private Executor mExecutor;
@@ -124,6 +119,17 @@
@Before
public void setup() throws Exception {
+ if (!android.tracing.Flags.perfettoIme()) {
+ mUiTracesPreDumped.add(Paths.get("/data/misc/wmtrace/ime_trace_clients.winscope"));
+ mUiTracesPreDumped.add(
+ Paths.get("/data/misc/wmtrace/ime_trace_managerservice.winscope"));
+ mUiTracesPreDumped.add(Paths.get("/data/misc/wmtrace/ime_trace_service.winscope"));
+ }
+
+ if (!android.tracing.Flags.perfettoProtologTracing()) {
+ mUiTracesPreDumped.add(Paths.get("/data/misc/wmtrace/wm_log.winscope"));
+ }
+
mHandler = createHandler();
mExecutor = (runnable) -> {
if (mHandler != null) {
@@ -206,7 +212,7 @@
mBrm.preDumpUiData();
waitTillDumpstateExitedOrTimeout();
- List<File> expectedPreDumpedTraceFiles = copyFiles(UI_TRACES_PREDUMPED);
+ List<File> expectedPreDumpedTraceFiles = copyFiles(mUiTracesPreDumped);
BugreportCallbackImpl callback = new BugreportCallbackImpl();
mBrm.startBugreport(mBugreportFd, null, fullWithUsePreDumpFlag(), mExecutor,
@@ -220,9 +226,9 @@
assertThat(mBugreportFile.length()).isGreaterThan(0L);
assertFdsAreClosed(mBugreportFd);
- assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED);
+ assertThatBugreportContainsFiles(mUiTracesPreDumped);
- List<File> actualPreDumpedTraceFiles = extractFilesFromBugreport(UI_TRACES_PREDUMPED);
+ List<File> actualPreDumpedTraceFiles = extractFilesFromBugreport(mUiTracesPreDumped);
assertThatAllFileContentsAreEqual(actualPreDumpedTraceFiles, expectedPreDumpedTraceFiles);
}
@@ -235,9 +241,9 @@
// In some corner cases, data dumped as part of the full bugreport could be the same as the
// pre-dumped data and this test would fail. Hence, here we create fake/artificial
// pre-dumped data that we know it won't match with the full bugreport data.
- createFakeTraceFiles(UI_TRACES_PREDUMPED);
+ createFakeTraceFiles(mUiTracesPreDumped);
- List<File> preDumpedTraceFiles = copyFiles(UI_TRACES_PREDUMPED);
+ List<File> preDumpedTraceFiles = copyFiles(mUiTracesPreDumped);
BugreportCallbackImpl callback = new BugreportCallbackImpl();
mBrm.startBugreport(mBugreportFd, null, full(), mExecutor,
@@ -251,9 +257,9 @@
assertThat(mBugreportFile.length()).isGreaterThan(0L);
assertFdsAreClosed(mBugreportFd);
- assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED);
+ assertThatBugreportContainsFiles(mUiTracesPreDumped);
- List<File> actualTraceFiles = extractFilesFromBugreport(UI_TRACES_PREDUMPED);
+ List<File> actualTraceFiles = extractFilesFromBugreport(mUiTracesPreDumped);
assertThatAllFileContentsAreDifferent(preDumpedTraceFiles, actualTraceFiles);
}
@@ -270,7 +276,7 @@
// 1. Pre-dump data
// 2. Start bugreport + "use pre-dump" flag (USE AND REMOVE THE PRE-DUMP FROM DISK)
// 3. Start bugreport + "use pre-dump" flag (NO PRE-DUMP AVAILABLE ON DISK)
- removeFilesIfNeeded(UI_TRACES_PREDUMPED);
+ removeFilesIfNeeded(mUiTracesPreDumped);
// Start bugreport with "use predump" flag. Because the pre-dumped data is not available
// the flag will be ignored and data will be dumped as in normal flow.
@@ -286,7 +292,7 @@
assertThat(mBugreportFile.length()).isGreaterThan(0L);
assertFdsAreClosed(mBugreportFd);
- assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED);
+ assertThatBugreportContainsFiles(mUiTracesPreDumped);
}
@Test
@@ -555,7 +561,7 @@
);
}
- private void assertThatBugreportContainsFiles(Path[] paths)
+ private void assertThatBugreportContainsFiles(List<Path> paths)
throws IOException {
List<Path> entries = listZipArchiveEntries(mBugreportFile);
for (Path pathInDevice : paths) {
@@ -564,7 +570,7 @@
}
}
- private List<File> extractFilesFromBugreport(Path[] paths) throws Exception {
+ private List<File> extractFilesFromBugreport(List<Path> paths) throws Exception {
List<File> files = new ArrayList<File>();
for (Path pathInDevice : paths) {
Path pathInArchive = Paths.get("FS" + pathInDevice.toString());
@@ -614,7 +620,7 @@
return extractedFile;
}
- private static void createFakeTraceFiles(Path[] paths) throws Exception {
+ private static void createFakeTraceFiles(List<Path> paths) throws Exception {
File src = createTempFile("fake", ".data");
Files.write("fake data".getBytes(StandardCharsets.UTF_8), src);
@@ -631,7 +637,7 @@
);
}
- private static List<File> copyFiles(Path[] paths) throws Exception {
+ private static List<File> copyFiles(List<Path> paths) throws Exception {
ArrayList<File> files = new ArrayList<File>();
for (Path src : paths) {
File dst = createTempFile(src.getFileName().toString(), ".copy");
@@ -643,7 +649,7 @@
return files;
}
- private static void removeFilesIfNeeded(Path[] paths) throws Exception {
+ private static void removeFilesIfNeeded(List<Path> paths) throws Exception {
for (Path path : paths) {
InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
"rm -f " + path.toString()
diff --git a/core/tests/vibrator/src/android/os/vibrator/persistence/ParsedVibrationTest.java b/core/tests/vibrator/src/android/os/vibrator/persistence/ParsedVibrationTest.java
index 94298dc..83a8f8f 100644
--- a/core/tests/vibrator/src/android/os/vibrator/persistence/ParsedVibrationTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/persistence/ParsedVibrationTest.java
@@ -63,6 +63,34 @@
}
@Test
+ public void testEquals() {
+ assertThat(new ParsedVibration(List.of())).isEqualTo(new ParsedVibration(List.of()));
+ assertThat(new ParsedVibration(List.of())).isNotEqualTo(new ParsedVibration(mEffect1));
+ assertThat(new ParsedVibration(mEffect1)).isEqualTo(new ParsedVibration(mEffect1));
+ assertThat(new ParsedVibration(mEffect1)).isNotEqualTo(new ParsedVibration(mEffect2));
+ assertThat(new ParsedVibration(List.of(mEffect1, mEffect2, mEffect3)))
+ .isEqualTo(new ParsedVibration(List.of(mEffect1, mEffect2, mEffect3)));
+ assertThat(new ParsedVibration(List.of(mEffect1, mEffect2)))
+ .isNotEqualTo(new ParsedVibration(List.of(mEffect2, mEffect1)));
+ }
+
+ @Test
+ public void testHashCode() {
+ assertThat(new ParsedVibration(mEffect1).hashCode())
+ .isEqualTo(new ParsedVibration(mEffect1).hashCode());
+ assertThat(new ParsedVibration(mEffect1).hashCode())
+ .isNotEqualTo(new ParsedVibration(mEffect2).hashCode());
+ assertThat(new ParsedVibration(List.of()).hashCode())
+ .isEqualTo(new ParsedVibration(List.of()).hashCode());
+ assertThat(new ParsedVibration(List.of()).hashCode())
+ .isNotEqualTo(new ParsedVibration(mEffect1).hashCode());
+ assertThat(new ParsedVibration(List.of(mEffect1, mEffect2, mEffect3)).hashCode())
+ .isEqualTo(new ParsedVibration(List.of(mEffect1, mEffect2, mEffect3)).hashCode());
+ assertThat(new ParsedVibration(List.of(mEffect1, mEffect2)).hashCode())
+ .isNotEqualTo(new ParsedVibration(List.of(mEffect2, mEffect1)).hashCode());
+ }
+
+ @Test
public void testResolve_allUnsupportedVibrations() {
when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(false);
@@ -91,21 +119,6 @@
.isEqualTo(mEffect1);
}
- @Test
- public void testGetVibrationEffects() {
- ParsedVibration parsedVibration =
- new ParsedVibration(List.of(mEffect1, mEffect2, mEffect3));
- assertThat(parsedVibration.getVibrationEffects())
- .containsExactly(mEffect1, mEffect2, mEffect3)
- .inOrder();
-
- parsedVibration = new ParsedVibration(List.of(mEffect1));
- assertThat(parsedVibration.getVibrationEffects()).containsExactly(mEffect1);
-
- parsedVibration = new ParsedVibration(List.of());
- assertThat(parsedVibration.getVibrationEffects()).isEmpty();
- }
-
private Subject assertThatResolution(
Vibrator vibrator, List<VibrationEffect> componentVibrations) {
return assertThat(new ParsedVibration(componentVibrations).resolve(vibrator));
diff --git a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
index 7d8c53f..bf9a820 100644
--- a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
@@ -37,9 +37,9 @@
import org.junit.runners.JUnit4;
import org.xmlpull.v1.XmlPullParser;
-import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@@ -74,18 +74,22 @@
.addPrimitive(PRIMITIVE_CLICK)
.addPrimitive(PRIMITIVE_TICK, 0.2497f)
.compose();
- String xml = "<vibration-effect>"
- + "<primitive-effect name=\"click\"/>"
- + "<primitive-effect name=\"tick\" scale=\"0.2497\"/>"
- + "</vibration-effect>";
+ String xml = """
+ <vibration-effect>
+ <primitive-effect name="click"/>
+ <primitive-effect name="tick" scale="0.2497"/>
+ </vibration-effect>
+ """.trim();
VibrationEffect effect2 = VibrationEffect.startComposition()
.addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356)
.addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7)
.compose();
- String xml2 = "<vibration-effect>"
- + "<primitive-effect name=\"low_tick\" delayMs=\"356\"/>"
- + "<primitive-effect name=\"spin\" scale=\"0.6364\" delayMs=\"7\"/>"
- + "</vibration-effect>";
+ String xml2 = """
+ <vibration-effect>
+ <primitive-effect name="low_tick" delayMs="356"/>
+ <primitive-effect name="spin" scale="0.6364" delayMs="7"/>
+ </vibration-effect>
+ """.trim();
TypedXmlPullParser parser = createXmlPullParser(xml);
assertParseElementSucceeds(parser, effect);
@@ -114,7 +118,12 @@
assertEndOfDocument(parser);
// Check when there is comment before the end tag.
- xml = "<vibration-effect><primitive-effect name=\"tick\"/><!-- hi --></vibration-effect>";
+ xml = """
+ <vibration-effect>
+ <primitive-effect name="tick"/>
+ <!-- hi -->
+ </vibration-effect>
+ """.trim();
parser = createXmlPullParser(xml);
assertParseElementSucceeds(
parser, VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK).compose());
@@ -128,18 +137,22 @@
.addPrimitive(PRIMITIVE_CLICK)
.addPrimitive(PRIMITIVE_TICK, 0.2497f)
.compose();
- String vibrationXml1 = "<vibration-effect>"
- + "<primitive-effect name=\"click\"/>"
- + "<primitive-effect name=\"tick\" scale=\"0.2497\"/>"
- + "</vibration-effect>";
+ String vibrationXml1 = """
+ <vibration-effect>
+ <primitive-effect name="click"/>
+ <primitive-effect name="tick" scale="0.2497"/>
+ </vibration-effect>
+ """.trim();
VibrationEffect effect2 = VibrationEffect.startComposition()
.addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356)
.addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7)
.compose();
- String vibrationXml2 = "<vibration-effect>"
- + "<primitive-effect name=\"low_tick\" delayMs=\"356\"/>"
- + "<primitive-effect name=\"spin\" scale=\"0.6364\" delayMs=\"7\"/>"
- + "</vibration-effect>";
+ String vibrationXml2 = """
+ <vibration-effect>
+ <primitive-effect name="low_tick" delayMs="356"/>
+ <primitive-effect name="spin" scale="0.6364" delayMs="7"/>
+ </vibration-effect>
+ """.trim();
String xml = "<vibration-select>" + vibrationXml1 + vibrationXml2 + "</vibration-select>";
TypedXmlPullParser parser = createXmlPullParser(xml);
@@ -183,8 +196,11 @@
@Test
public void testParseElement_withHiddenApis_onlySucceedsWithFlag() throws Exception {
// Check when the root tag is "vibration".
- String xml =
- "<vibration-effect><predefined-effect name=\"texture_tick\"/></vibration-effect>";
+ String xml = """
+ <vibration-effect>
+ <predefined-effect name="texture_tick"/>
+ </vibration-effect>
+ """.trim();
assertParseElementSucceeds(createXmlPullParser(xml),
VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS,
VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK));
@@ -199,131 +215,186 @@
}
@Test
- public void testParseElement_badXml_throwsException() throws Exception {
+ public void testParseElement_badXml_throwsException() {
// No "vibration-select" tag.
- assertParseElementFails(
- "<vibration-effect>rand text<primitive-effect name=\"click\"/></vibration-effect>");
- assertParseElementFails("<bad-tag><primitive-effect name=\"click\"/></vibration-effect>");
- assertParseElementFails("<primitive-effect name=\"click\"/></vibration-effect>");
- assertParseElementFails("<vibration-effect><primitive-effect name=\"click\"/>");
+ assertParseElementFails("""
+ <vibration-effect>
+ rand text
+ <primitive-effect name="click"/>
+ </vibration-effect>
+ """);
+ assertParseElementFails("""
+ <bad-tag>
+ <primitive-effect name="click"/>
+ </vibration-effect>
+ """);
+ assertParseElementFails("""
+ <primitive-effect name="click"/>
+ </vibration-effect>
+ """);
+ assertParseElementFails("""
+ <vibration-effect>
+ <primitive-effect name="click"/>
+ """);
// Incomplete XML.
- assertParseElementFails("<vibration-select><primitive-effect name=\"click\"/>");
- assertParseElementFails("<vibration-select>"
- + "<vibration-effect>"
- + "<primitive-effect name=\"low_tick\" delayMs=\"356\"/>"
- + "</vibration-effect>");
+ assertParseElementFails("""
+ <vibration-select>
+ <primitive-effect name="click"/>
+ """);
+ assertParseElementFails("""
+ <vibration-select>
+ <vibration-effect>
+ <primitive-effect name="low_tick" delayMs="356"/>
+ </vibration-effect>
+ """);
// Bad vibration XML.
- assertParseElementFails("<vibration-select>"
- + "<primitive-effect name=\"low_tick\" delayMs=\"356\"/>"
- + "</vibration-effect>"
- + "</vibration-select>");
+ assertParseElementFails("""
+ <vibration-select>
+ <primitive-effect name="low_tick" delayMs="356"/>
+ </vibration-effect>
+ </vibration-select>
+ """);
// "vibration-select" tag should have no attributes.
- assertParseElementFails("<vibration-select bad_attr=\"123\">"
- + "<vibration-effect>"
- + "<predefined-effect name=\"tick\"/>"
- + "</vibration-effect>"
- + "</vibration-select>");
+ assertParseElementFails("""
+ <vibration-select bad_attr="123">
+ <vibration-effect>
+ <predefined-effect name="tick"/>
+ </vibration-effect>
+ </vibration-select>
+ """);
}
@Test
- public void testPrimitives_allSucceed() throws IOException {
+ public void testInvalidEffects_allFail() {
+ // Invalid root tag.
+ String xml = """
+ <vibration>
+ <predefined-effect name="click"/>
+ </vibration>
+ """;
+
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ // Invalid effect name.
+ xml = """
+ <vibration-effect>
+ <predefined-effect name="invalid"/>
+ </vibration-effect>
+ """;
+
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+ }
+
+ @Test
+ public void testVibrationSelectTag_onlyParseDocumentSucceeds() throws Exception {
+ VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+ String xml = """
+ <vibration-select>
+ <vibration-effect><predefined-effect name="click"/></vibration-effect>
+ </vibration-select>
+ """;
+
+ assertPublicApisParseDocumentSucceeds(xml, effect);
+ assertHiddenApisParseDocumentSucceeds(xml, effect);
+
+ assertPublicApisParseVibrationEffectFails(xml);
+ assertHiddenApisParseVibrationEffectFails(xml);
+ }
+
+ @Test
+ public void testPrimitives_allSucceed() throws Exception {
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(PRIMITIVE_CLICK)
.addPrimitive(PRIMITIVE_TICK, 0.2497f)
.addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356)
.addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7)
.compose();
- String xml = "<vibration-effect>"
- + "<primitive-effect name=\"click\"/>"
- + "<primitive-effect name=\"tick\" scale=\"0.2497\"/>"
- + "<primitive-effect name=\"low_tick\" delayMs=\"356\"/>"
- + "<primitive-effect name=\"spin\" scale=\"0.6364\" delayMs=\"7\"/>"
- + "</vibration-effect>";
+ String xml = """
+ <vibration-effect>
+ <primitive-effect name="click"/>
+ <primitive-effect name="tick" scale="0.2497"/>
+ <primitive-effect name="low_tick" delayMs="356"/>
+ <primitive-effect name="spin" scale="0.6364" delayMs="7"/>
+ </vibration-effect>
+ """;
assertPublicApisParserSucceeds(xml, effect);
assertPublicApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin");
assertPublicApisRoundTrip(effect);
- assertHiddenApisParseVibrationEffectSucceeds(xml, effect);
+ assertHiddenApisParserSucceeds(xml, effect);
assertHiddenApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin");
assertHiddenApisRoundTrip(effect);
}
@Test
- public void testParseDocument_withVibrationSelectTag_withHiddenApis_onlySucceedsWithFlag()
- throws Exception {
- // Check when the root tag is "vibration-effect".
- String xml =
- "<vibration-effect><predefined-effect name=\"texture_tick\"/></vibration-effect>";
- assertParseDocumentSucceeds(xml,
- VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS,
- VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK));
- assertThat(parseDocument(xml, /* flags= */ 0)).isNull();
-
- // Check when the root tag is "vibration-select".
- xml = "<vibration-select>" + xml + "</vibration-select>";
- assertParseDocumentSucceeds(xml,
- VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS,
- VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK));
- assertThat(parseDocument(xml, /* flags= */ 0)).isNull();
- }
-
- @Test
- public void testWaveforms_allSucceed() throws IOException {
+ public void testWaveforms_allSucceed() throws Exception {
VibrationEffect effect = VibrationEffect.createWaveform(new long[]{123, 456, 789, 0},
new int[]{254, 1, 255, 0}, /* repeat= */ 0);
- String xml = "<vibration-effect>"
- + "<waveform-effect><repeating>"
- + "<waveform-entry durationMs=\"123\" amplitude=\"254\"/>"
- + "<waveform-entry durationMs=\"456\" amplitude=\"1\"/>"
- + "<waveform-entry durationMs=\"789\" amplitude=\"255\"/>"
- + "<waveform-entry durationMs=\"0\" amplitude=\"0\"/>"
- + "</repeating></waveform-effect>"
- + "</vibration-effect>";
+ String xml = """
+ <vibration-effect>
+ <waveform-effect>
+ <repeating>
+ <waveform-entry durationMs="123" amplitude="254"/>
+ <waveform-entry durationMs="456" amplitude="1"/>
+ <waveform-entry durationMs="789" amplitude="255"/>
+ <waveform-entry durationMs="0" amplitude="0"/>
+ </repeating>
+ </waveform-effect>
+ </vibration-effect>
+ """;
assertPublicApisParserSucceeds(xml, effect);
assertPublicApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0");
assertPublicApisRoundTrip(effect);
- assertHiddenApisParseVibrationEffectSucceeds(xml, effect);
+ assertHiddenApisParserSucceeds(xml, effect);
assertHiddenApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0");
assertHiddenApisRoundTrip(effect);
}
@Test
public void testPredefinedEffects_publicEffectsWithDefaultFallback_allSucceed()
- throws IOException {
+ throws Exception {
for (Map.Entry<String, Integer> entry : createPublicPredefinedEffectsMap().entrySet()) {
VibrationEffect effect = VibrationEffect.get(entry.getValue());
- String xml = String.format(
- "<vibration-effect><predefined-effect name=\"%s\"/></vibration-effect>",
+ String xml = String.format("""
+ <vibration-effect>
+ <predefined-effect name="%s"/>
+ </vibration-effect>
+ """,
entry.getKey());
assertPublicApisParserSucceeds(xml, effect);
assertPublicApisSerializerSucceeds(effect, entry.getKey());
assertPublicApisRoundTrip(effect);
- assertHiddenApisParseVibrationEffectSucceeds(xml, effect);
+ assertHiddenApisParserSucceeds(xml, effect);
assertHiddenApisSerializerSucceeds(effect, entry.getKey());
assertHiddenApisRoundTrip(effect);
}
}
@Test
- public void testPredefinedEffects_hiddenEffects_onlySucceedsWithFlag() throws IOException {
+ public void testPredefinedEffects_hiddenEffects_onlySucceedsWithFlag() throws Exception {
for (Map.Entry<String, Integer> entry : createHiddenPredefinedEffectsMap().entrySet()) {
VibrationEffect effect = VibrationEffect.get(entry.getValue());
- String xml = String.format(
- "<vibration-effect><predefined-effect name=\"%s\"/></vibration-effect>",
+ String xml = String.format("""
+ <vibration-effect>
+ <predefined-effect name="%s"/>
+ </vibration-effect>
+ """,
entry.getKey());
assertPublicApisParserFails(xml);
assertPublicApisSerializerFails(effect);
- assertHiddenApisParseVibrationEffectSucceeds(xml, effect);
+ assertHiddenApisParserSucceeds(xml, effect);
assertHiddenApisSerializerSucceeds(effect, entry.getKey());
assertHiddenApisRoundTrip(effect);
}
@@ -331,33 +402,119 @@
@Test
public void testPredefinedEffects_allEffectsWithNonDefaultFallback_onlySucceedsWithFlag()
- throws IOException {
+ throws Exception {
for (Map.Entry<String, Integer> entry : createAllPredefinedEffectsMap().entrySet()) {
boolean nonDefaultFallback = !PrebakedSegment.DEFAULT_SHOULD_FALLBACK;
VibrationEffect effect = VibrationEffect.get(entry.getValue(), nonDefaultFallback);
- String xml = String.format(
- "<vibration-effect><predefined-effect name=\"%s\" fallback=\"%s\"/>"
- + "</vibration-effect>",
+ String xml = String.format("""
+ <vibration-effect>
+ <predefined-effect name="%s" fallback="%s"/>
+ </vibration-effect>
+ """,
entry.getKey(), nonDefaultFallback);
assertPublicApisParserFails(xml);
assertPublicApisSerializerFails(effect);
- assertHiddenApisParseVibrationEffectSucceeds(xml, effect);
+ assertHiddenApisParserSucceeds(xml, effect);
assertHiddenApisSerializerSucceeds(effect, entry.getKey());
assertHiddenApisRoundTrip(effect);
}
}
- private void assertPublicApisParserFails(String xml) throws IOException {
- assertThat(parseVibrationEffect(xml, /* flags= */ 0)).isNull();
+ private void assertPublicApisParserFails(String xml) {
+ assertThrows("Expected parseVibrationEffect to fail for " + xml,
+ VibrationXmlParser.ParseFailedException.class,
+ () -> parseVibrationEffect(xml, /* flags= */ 0));
+ assertThrows("Expected parseDocument to fail for " + xml,
+ VibrationXmlParser.ParseFailedException.class,
+ () -> parseDocument(xml, /* flags= */ 0));
+ }
+
+ private void assertPublicApisParseVibrationEffectFails(String xml) {
+ assertThrows("Expected parseVibrationEffect to fail for " + xml,
+ VibrationXmlParser.ParseFailedException.class,
+ () -> parseVibrationEffect(xml, /* flags= */ 0));
}
private void assertPublicApisParserSucceeds(String xml, VibrationEffect effect)
- throws IOException {
+ throws Exception {
+ assertPublicApisParseDocumentSucceeds(xml, effect);
+ assertPublicApisParseVibrationEffectSucceeds(xml, effect);
+ }
+
+ private void assertPublicApisParseDocumentSucceeds(String xml, VibrationEffect... effects)
+ throws Exception {
+ assertThat(parseDocument(xml, /* flags= */ 0))
+ .isEqualTo(new ParsedVibration(Arrays.asList(effects)));
+ }
+
+ private void assertPublicApisParseVibrationEffectSucceeds(String xml, VibrationEffect effect)
+ throws Exception {
assertThat(parseVibrationEffect(xml, /* flags= */ 0)).isEqualTo(effect);
}
+ private void assertHiddenApisParserFails(String xml) {
+ assertThrows("Expected parseVibrationEffect to fail for " + xml,
+ VibrationXmlParser.ParseFailedException.class,
+ () -> parseVibrationEffect(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS));
+ assertThrows("Expected parseDocument to fail for " + xml,
+ VibrationXmlParser.ParseFailedException.class,
+ () -> parseDocument(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS));
+ }
+
+ private void assertHiddenApisParseVibrationEffectFails(String xml) {
+ assertThrows("Expected parseVibrationEffect to fail for " + xml,
+ VibrationXmlParser.ParseFailedException.class,
+ () -> parseVibrationEffect(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS));
+ }
+
+ private void assertHiddenApisParserSucceeds(String xml, VibrationEffect effect)
+ throws Exception {
+ assertHiddenApisParseDocumentSucceeds(xml, effect);
+ assertHiddenApisParseVibrationEffectSucceeds(xml, effect);
+ }
+
+ private void assertHiddenApisParseDocumentSucceeds(String xml, VibrationEffect... effect)
+ throws Exception {
+ assertThat(parseDocument(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS))
+ .isEqualTo(new ParsedVibration(Arrays.asList(effect)));
+ }
+
+ private void assertHiddenApisParseVibrationEffectSucceeds(String xml, VibrationEffect effect)
+ throws Exception {
+ assertThat(parseVibrationEffect(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS))
+ .isEqualTo(effect);
+ }
+
+ private void assertPublicApisSerializerFails(VibrationEffect effect) {
+ assertThrows("Expected serialization to fail for " + effect,
+ VibrationXmlSerializer.SerializationFailedException.class,
+ () -> serialize(effect));
+ }
+
+ private void assertPublicApisSerializerSucceeds(VibrationEffect effect,
+ String... expectedSegments) throws Exception {
+ assertSerializationContainsSegments(serialize(effect), expectedSegments);
+ }
+
+ private void assertHiddenApisSerializerSucceeds(VibrationEffect effect,
+ String... expectedSegments) throws Exception {
+ assertSerializationContainsSegments(
+ serialize(effect, VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS), expectedSegments);
+ }
+
+ private void assertPublicApisRoundTrip(VibrationEffect effect) throws Exception {
+ assertThat(parseVibrationEffect(serialize(effect, /* flags= */ 0), /* flags= */ 0))
+ .isEqualTo(effect);
+ }
+
+ private void assertHiddenApisRoundTrip(VibrationEffect effect) throws Exception {
+ String xml = serialize(effect, VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS);
+ assertThat(parseVibrationEffect(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS))
+ .isEqualTo(effect);
+ }
+
private TypedXmlPullParser createXmlPullParser(String xml) throws Exception {
TypedXmlPullParser parser = Xml.newFastPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
@@ -366,11 +523,6 @@
return parser;
}
- private void assertParseDocumentSucceeds(String xml, int flags, VibrationEffect... effects)
- throws Exception {
- assertThat(parseDocument(xml, flags).getVibrationEffects()).containsExactly(effects);
- }
-
/**
* Asserts parsing vibration from an open TypedXmlPullParser succeeds, and that the parser
* points to the end "vibration" or "vibration-select" tag.
@@ -385,7 +537,8 @@
String tagName = parser.getName();
assertThat(Set.of("vibration-effect", "vibration-select")).contains(tagName);
- assertThat(parseElement(parser, flags).getVibrationEffects()).containsExactly(effects);
+ assertThat(parseElement(parser, flags))
+ .isEqualTo(new ParsedVibration(Arrays.asList(effects)));
assertThat(parser.getEventType()).isEqualTo(XmlPullParser.END_TAG);
assertThat(parser.getName()).isEqualTo(tagName);
}
@@ -405,69 +558,40 @@
assertThat(parser.getEventType()).isEqualTo(parser.END_DOCUMENT);
}
- private void assertHiddenApisParseVibrationEffectSucceeds(String xml, VibrationEffect effect)
- throws IOException {
- assertThat(parseVibrationEffect(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS))
- .isEqualTo(effect);
- }
-
- private void assertPublicApisSerializerFails(VibrationEffect effect) {
- assertThrows("Expected serialization to fail for " + effect,
- VibrationXmlSerializer.SerializationFailedException.class,
- () -> serialize(effect, /* flags= */ 0));
- }
-
private void assertParseElementFails(String xml) {
assertThrows("Expected parsing to fail for " + xml,
- VibrationXmlParser.VibrationXmlParserException.class,
+ VibrationXmlParser.ParseFailedException.class,
() -> parseElement(createXmlPullParser(xml), /* flags= */ 0));
}
- private void assertPublicApisSerializerSucceeds(VibrationEffect effect,
- String... expectedSegments) throws IOException {
- assertSerializationContainsSegments(serialize(effect, /* flags= */ 0), expectedSegments);
- }
-
- private void assertHiddenApisSerializerSucceeds(VibrationEffect effect,
- String... expectedSegments) throws IOException {
- assertSerializationContainsSegments(
- serialize(effect, VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS), expectedSegments);
- }
-
private void assertSerializationContainsSegments(String xml, String[] expectedSegments) {
for (String expectedSegment : expectedSegments) {
assertThat(xml).contains(expectedSegment);
}
}
- private void assertPublicApisRoundTrip(VibrationEffect effect) throws IOException {
- assertThat(parseVibrationEffect(serialize(effect, /* flags= */ 0), /* flags= */ 0))
- .isEqualTo(effect);
- }
-
- private void assertHiddenApisRoundTrip(VibrationEffect effect) throws IOException {
- String xml = serialize(effect, VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS);
- assertThat(parseVibrationEffect(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS))
- .isEqualTo(effect);
- }
-
private static VibrationEffect parseVibrationEffect(
- String xml, @VibrationXmlParser.Flags int flags) throws IOException {
+ String xml, @VibrationXmlParser.Flags int flags) throws Exception {
return VibrationXmlParser.parseVibrationEffect(new StringReader(xml), flags);
}
- private static ParsedVibration parseDocument(String xml, int flags)
- throws IOException {
+ private static ParsedVibration parseDocument(String xml, int flags) throws Exception {
return VibrationXmlParser.parseDocument(new StringReader(xml), flags);
}
private static ParsedVibration parseElement(TypedXmlPullParser parser, int flags)
- throws IOException, VibrationXmlParser.VibrationXmlParserException {
+ throws Exception {
return VibrationXmlParser.parseElement(parser, flags);
}
+ private static String serialize(VibrationEffect effect) throws Exception {
+ StringWriter writer = new StringWriter();
+ VibrationXmlSerializer.serialize(effect, writer);
+ return writer.toString();
+ }
+
private static String serialize(VibrationEffect effect, @VibrationXmlSerializer.Flags int flags)
- throws IOException {
+ throws Exception {
StringWriter writer = new StringWriter();
VibrationXmlSerializer.serialize(effect, writer, flags);
return writer.toString();
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index 3256f31..5caedba 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -416,7 +416,8 @@
}
/**
- * Retrieve the dataspace associated with the texture image.
+ * Retrieve the dataspace associated with the texture image
+ * set by the most recent call to {@link #updateTexImage}.
*/
@SuppressLint("MethodNameUnits")
public @NamedDataSpace int getDataSpace() {
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 746c280..8f16f76 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -23,6 +23,9 @@
cc_library_shared {
name: "libjnigraphics",
+ defaults: [
+ "bug_24465209_workaround",
+ ],
cflags: [
"-Wall",
@@ -47,13 +50,6 @@
static_libs: ["libarect"],
- arch: {
- arm: {
- // TODO: This is to work around b/24465209. Remove after root cause is fixed
- pack_relocations: false,
- ldflags: ["-Wl,--hash-style=both"],
- },
- },
host_supported: true,
target: {
android: {
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
index 62ed66c..e4bc7b4 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -13,4 +13,4 @@
per-file *.xml=*
# Notification-related utilities
-per-file */notification/* = file:/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
+per-file **/notification/** = file:/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
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 cf14547..fbe2c2e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -451,6 +451,24 @@
}
}
+ @Test
+ fun transitionFromDozingToGlanceableHub_forcesCommunal() =
+ with(kosmos) {
+ testScope.runTest {
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+ communalSceneInteractor.changeScene(CommunalScenes.Blank)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
+
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = this
+ )
+
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ }
+ }
+
private fun TestScope.updateDocked(docked: Boolean) =
with(kosmos) {
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index 612f2e7..0792a50 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -34,12 +34,14 @@
import android.os.PowerManager
import android.platform.test.annotations.EnableFlags
+import android.service.dream.dreamManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
+import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -64,8 +66,10 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -120,6 +124,66 @@
@Test
@EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToLockscreen_onWakeup_canDream_glanceableHubAvailable() =
+ testScope.runTest {
+ whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ kosmos.setCommunalAvailable(true)
+ runCurrent()
+
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // If dreaming is possible and communal is available, then we should transition to
+ // GLANCEABLE_HUB when waking up.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToLockscreen_onWakeup_canNotDream_glanceableHubAvailable() =
+ testScope.runTest {
+ whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(false)
+ kosmos.setCommunalAvailable(true)
+ runCurrent()
+
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // If dreaming is NOT possible but communal is available, then we should transition to
+ // LOCKSCREEN when waking up.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToLockscreen_onWakeup_canNDream_glanceableHubNotAvailable() =
+ testScope.runTest {
+ whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ kosmos.setCommunalAvailable(false)
+ runCurrent()
+
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // If dreaming is possible but communal is NOT available, then we should transition to
+ // LOCKSCREEN when waking up.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testTransitionToGlanceableHub_onWakeup_ifIdleOnCommunal_noOccludingActivity() =
testScope.runTest {
kosmos.fakeCommunalSceneRepository.setTransitionState(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
index 2f69942..ebc78d8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.volume.panel.component.spatial.domain
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothProfile
import android.media.AudioDeviceAttributes
import android.media.AudioDeviceInfo
import android.media.session.MediaSession
@@ -24,12 +26,14 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LeAudioProfile
import com.android.settingslib.media.BluetoothMediaDevice
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.spatializerRepository
import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.volume.localMediaController
@@ -56,8 +60,15 @@
@Before
fun setup() {
with(kosmos) {
+ val leAudioProfile =
+ mock<LeAudioProfile> {
+ whenever(profileId).thenReturn(BluetoothProfile.LE_AUDIO)
+ whenever(isEnabled(any())).thenReturn(true)
+ }
val cachedBluetoothDevice: CachedBluetoothDevice = mock {
whenever(address).thenReturn("test_address")
+ whenever(profiles).thenReturn(listOf(leAudioProfile))
+ whenever(device).thenReturn(mock<BluetoothDevice> {})
}
localMediaRepository.updateCurrentConnectedDevice(
mock<BluetoothMediaDevice> {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
index 555d77c..d5566ad 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
@@ -65,16 +65,20 @@
private val kosmos = testKosmos()
private lateinit var underTest: SpatialAudioComponentInteractor
+
+ private val bluetoothDevice: BluetoothDevice = mock {}
private val a2dpProfile: A2dpProfile = mock {
whenever(profileId).thenReturn(BluetoothProfile.A2DP)
+ whenever(isEnabled(bluetoothDevice)).thenReturn(false)
}
private val leAudioProfile: LeAudioProfile = mock {
whenever(profileId).thenReturn(BluetoothProfile.LE_AUDIO)
+ whenever(isEnabled(bluetoothDevice)).thenReturn(true)
}
private val hearingAidProfile: HearingAidProfile = mock {
whenever(profileId).thenReturn(BluetoothProfile.HEARING_AID)
+ whenever(isEnabled(bluetoothDevice)).thenReturn(false)
}
- private val bluetoothDevice: BluetoothDevice = mock {}
@Before
fun setup() {
diff --git a/packages/SystemUI/res/layout/ongoing_activity_chip.xml b/packages/SystemUI/res/layout/ongoing_activity_chip.xml
index cd5c37d..beb16b3 100644
--- a/packages/SystemUI/res/layout/ongoing_activity_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_activity_chip.xml
@@ -46,6 +46,8 @@
android:tint="?android:attr/colorPrimary"
/>
+ <!-- Only one of [ongoing_activity_chip_time, ongoing_activity_chip_text] will ever
+ be shown at one time. -->
<com.android.systemui.statusbar.chips.ui.view.ChipChronometer
android:id="@+id/ongoing_activity_chip_time"
android:layout_width="wrap_content"
@@ -58,5 +60,19 @@
android:textColor="?android:attr/colorPrimary"
/>
+ <!-- Used to show generic text in the chip instead of a timer. -->
+ <TextView
+ android:id="@+id/ongoing_activity_chip_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:gravity="center|start"
+ android:paddingStart="@dimen/ongoing_activity_chip_icon_text_padding"
+ android:textAppearance="@android:style/TextAppearance.Material.Small"
+ android:fontFamily="@*android:string/config_headlineFontFamily"
+ android:textColor="?android:attr/colorPrimary"
+ android:visibility="gone"
+ />
+
</com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer>
</FrameLayout>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
index c08756f..5e2b5ff 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -74,7 +74,7 @@
val isConfirmationRequired: Flow<Boolean>
/** Fingerprint sensor type */
- val sensorType: Flow<FingerprintSensorType>
+ val fingerprintSensorType: Flow<FingerprintSensorType>
/** Switch to the credential view. */
fun onSwitchToCredential()
@@ -154,7 +154,8 @@
}
}
- override val sensorType: Flow<FingerprintSensorType> = fingerprintPropertyRepository.sensorType
+ override val fingerprintSensorType: Flow<FingerprintSensorType> =
+ fingerprintPropertyRepository.sensorType
override fun onSwitchToCredential() {
val modalities: BiometricModalities =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
index 7081661..6c6ef5a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
@@ -21,7 +21,6 @@
import android.annotation.RawRes
import android.content.res.Configuration
import android.graphics.Rect
-import android.hardware.face.Face
import android.util.RotationUtils
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
@@ -137,7 +136,7 @@
displayStateInteractor.currentRotation,
displayStateInteractor.isFolded,
displayStateInteractor.isInRearDisplayMode,
- promptSelectorInteractor.sensorType,
+ promptSelectorInteractor.fingerprintSensorType,
promptViewModel.isAuthenticated,
promptViewModel.isAuthenticating,
promptViewModel.showingError
@@ -183,7 +182,7 @@
displayStateInteractor.currentRotation,
displayStateInteractor.isFolded,
displayStateInteractor.isInRearDisplayMode,
- promptSelectorInteractor.sensorType,
+ promptSelectorInteractor.fingerprintSensorType,
promptViewModel.isAuthenticated,
promptViewModel.isAuthenticating,
promptViewModel.isPendingConfirmation,
@@ -330,7 +329,7 @@
AuthType.Coex ->
combine(
displayStateInteractor.currentRotation,
- promptSelectorInteractor.sensorType,
+ promptSelectorInteractor.fingerprintSensorType,
promptViewModel.isAuthenticated,
promptViewModel.isAuthenticating,
promptViewModel.showingError
@@ -430,7 +429,7 @@
AuthType.Fingerprint,
AuthType.Coex ->
combine(
- promptSelectorInteractor.sensorType,
+ promptSelectorInteractor.fingerprintSensorType,
promptViewModel.isAuthenticated,
promptViewModel.isAuthenticating,
promptViewModel.isPendingConfirmation,
@@ -508,7 +507,7 @@
when (activeAuthType) {
AuthType.Fingerprint ->
combine(
- promptSelectorInteractor.sensorType,
+ promptSelectorInteractor.fingerprintSensorType,
promptViewModel.isAuthenticated,
promptViewModel.isAuthenticating,
promptViewModel.showingError
@@ -546,7 +545,7 @@
}
AuthType.Coex ->
combine(
- promptSelectorInteractor.sensorType,
+ promptSelectorInteractor.fingerprintSensorType,
promptViewModel.isAuthenticated,
promptViewModel.isAuthenticating,
promptViewModel.isPendingConfirmation,
@@ -606,7 +605,7 @@
AuthType.Fingerprint,
AuthType.Coex ->
combine(
- promptSelectorInteractor.sensorType,
+ promptSelectorInteractor.fingerprintSensorType,
promptViewModel.isAuthenticated,
promptViewModel.isAuthenticating,
promptViewModel.showingError
@@ -642,7 +641,7 @@
AuthType.Fingerprint,
AuthType.Coex ->
combine(
- promptSelectorInteractor.sensorType,
+ promptSelectorInteractor.fingerprintSensorType,
displayStateInteractor.currentRotation
) { sensorType: FingerprintSensorType, rotation: DisplayRotation ->
when (sensorType) {
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/binder/IconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/common/ui/binder/IconViewBinder.kt
index 108e22b..64dedea 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/binder/IconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/binder/IconViewBinder.kt
@@ -16,6 +16,7 @@
package com.android.systemui.common.ui.binder
+import android.view.View
import android.widget.ImageView
import com.android.systemui.common.shared.model.Icon
@@ -30,4 +31,13 @@
is Icon.Resource -> view.setImageResource(icon.res)
}
}
+
+ fun bindNullable(icon: Icon?, view: ImageView) {
+ if (icon != null) {
+ view.visibility = View.VISIBLE
+ bind(icon, view)
+ } else {
+ view.visibility = View.GONE
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index 88c3f9f6..e31f1ad 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -18,6 +18,7 @@
import android.provider.Settings
import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.CoreStartable
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -91,8 +92,8 @@
keyguardTransitionInteractor.startedKeyguardTransitionStep
.mapLatest(::determineSceneAfterTransition)
.filterNotNull()
- .onEach { nextScene ->
- communalSceneInteractor.changeScene(nextScene, CommunalTransitionKeys.SimpleFade)
+ .onEach { (nextScene, nextTransition) ->
+ communalSceneInteractor.changeScene(nextScene, nextTransition)
}
.launchIn(applicationScope)
@@ -188,7 +189,7 @@
private suspend fun determineSceneAfterTransition(
lastStartedTransition: TransitionStep,
- ): SceneKey? {
+ ): Pair<SceneKey, TransitionKey>? {
val to = lastStartedTransition.to
val from = lastStartedTransition.from
val docked = dockManager.isDocked
@@ -201,22 +202,27 @@
// underneath the hub is shown. When launching activities over lockscreen, we only
// change scenes once the activity launch animation is finished, so avoid
// changing the scene here.
- CommunalScenes.Blank
+ Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
}
to == KeyguardState.GLANCEABLE_HUB && from == KeyguardState.OCCLUDED -> {
// When transitioning to the hub from an occluded state, fade out the hub without
// doing any translation.
- CommunalScenes.Communal
+ Pair(CommunalScenes.Communal, CommunalTransitionKeys.SimpleFade)
}
// Transitioning to Blank scene when entering the edit mode will be handled separately
// with custom animations.
to == KeyguardState.GONE && !communalInteractor.editModeOpen.value ->
- CommunalScenes.Blank
+ Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
!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
delay(AWAKE_DEBOUNCE_DELAY)
- CommunalScenes.Blank
+ Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
+ }
+ from == KeyguardState.DOZING && to == KeyguardState.GLANCEABLE_HUB -> {
+ // Make sure the communal hub is showing (immediately, not fading in) when
+ // transitioning from dozing to hub.
+ Pair(CommunalScenes.Communal, CommunalTransitionKeys.Immediately)
}
else -> null
}
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 f3692bd..2f40c99 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
@@ -17,8 +17,10 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
+import android.app.DreamManager
import com.android.app.animation.Interpolators
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -51,8 +53,10 @@
keyguardInteractor: KeyguardInteractor,
powerInteractor: PowerInteractor,
private val communalInteractor: CommunalInteractor,
+ private val communalSceneInteractor: CommunalSceneInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
val deviceEntryRepository: DeviceEntryRepository,
+ private val dreamManager: DreamManager,
) :
TransitionInteractor(
fromState = KeyguardState.DOZING,
@@ -119,7 +123,8 @@
.filterRelevantKeyguardStateAnd { isAwake -> isAwake }
.sample(
keyguardInteractor.isKeyguardOccluded,
- communalInteractor.isIdleOnCommunal,
+ communalInteractor.isCommunalAvailable,
+ communalSceneInteractor.isIdleOnCommunal,
canTransitionToGoneOnWake,
keyguardInteractor.primaryBouncerShowing,
)
@@ -127,6 +132,7 @@
(
_,
occluded,
+ isCommunalAvailable,
isIdleOnCommunal,
canTransitionToGoneOnWake,
primaryBouncerShowing) ->
@@ -141,6 +147,10 @@
KeyguardState.OCCLUDED
} else if (isIdleOnCommunal) {
KeyguardState.GLANCEABLE_HUB
+ } else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) {
+ // This case handles tapping the power button to transition through
+ // dream -> off -> hub.
+ KeyguardState.GLANCEABLE_HUB
} else {
KeyguardState.LOCKSCREEN
}
@@ -159,7 +169,8 @@
powerInteractor.detailedWakefulness
.filterRelevantKeyguardStateAnd { it.isAwake() }
.sample(
- communalInteractor.isIdleOnCommunal,
+ communalInteractor.isCommunalAvailable,
+ communalSceneInteractor.isIdleOnCommunal,
keyguardInteractor.biometricUnlockState,
canTransitionToGoneOnWake,
keyguardInteractor.primaryBouncerShowing,
@@ -167,6 +178,7 @@
.collect {
(
_,
+ isCommunalAvailable,
isIdleOnCommunal,
biometricUnlockState,
canDismissLockscreen,
@@ -188,6 +200,10 @@
KeyguardState.PRIMARY_BOUNCER
} else if (isIdleOnCommunal) {
KeyguardState.GLANCEABLE_HUB
+ } else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) {
+ // This case handles tapping the power button to transition through
+ // dream -> off -> hub.
+ KeyguardState.GLANCEABLE_HUB
} else {
KeyguardState.LOCKSCREEN
},
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index 24b7a01..96df728 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -224,6 +224,10 @@
});
}
+ boolean isBound() {
+ return mBound.get();
+ }
+
@WorkerThread
private void setBindService(boolean bind) {
if (mBound.get() && mUnbindImmediate.get()) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index 6bc5095..d10471d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -217,7 +217,11 @@
Log.e(TAG, "Service already bound");
return;
}
- mPendingBind = true;
+ if (!mStateManager.isBound()) {
+ // If we are bound, we don't need to set a pending bind. There's either one already or
+ // we are fully bound.
+ mPendingBind = true;
+ }
mBound = true;
mJustBound = true;
mHandler.postDelayed(mJustBoundOver, MIN_BIND_TIME);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 4715230..284239a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -47,6 +47,7 @@
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.res.R;
import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.screenrecord.data.model.ScreenRecordModel;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -146,8 +147,9 @@
if (isRecording) {
state.secondaryLabel = mContext.getString(R.string.quick_settings_screen_record_stop);
} else if (isStarting) {
- // round, since the timer isn't exact
- int countdown = (int) Math.floorDiv(mMillisUntilFinished + 500, 1000);
+ int countdown =
+ (int) ScreenRecordModel.Starting.Companion.toCountdownSeconds(
+ mMillisUntilFinished);
state.secondaryLabel = String.format("%d...", countdown);
} else {
state.secondaryLabel = mContext.getString(R.string.quick_settings_screen_record_start);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
index 7446708..e74e77f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
@@ -61,7 +61,7 @@
contentDescription = null
)
icon = { loadedIcon }
- val countDown = Math.floorDiv(data.millisUntilStarted + 500, 1000)
+ val countDown = data.countdownSeconds
sideViewIcon = QSTileState.SideViewIcon.None
secondaryLabel = String.format("%d...", countDown)
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/data/model/ScreenRecordModel.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/data/model/ScreenRecordModel.kt
index b225444..ada5d8c0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/data/model/ScreenRecordModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/data/model/ScreenRecordModel.kt
@@ -22,7 +22,17 @@
data object Recording : ScreenRecordModel
/** A screen recording will begin in [millisUntilStarted] ms. */
- data class Starting(val millisUntilStarted: Long) : ScreenRecordModel
+ data class Starting(val millisUntilStarted: Long) : ScreenRecordModel {
+ val countdownSeconds = millisUntilStarted.toCountdownSeconds()
+
+ companion object {
+ /**
+ * Returns the number of seconds until screen recording will start, used to show a 3-2-1
+ * countdown.
+ */
+ fun Long.toCountdownSeconds() = Math.floorDiv(this + 500, 1000)
+ }
+ }
/** There's nothing related to screen recording happening. */
data object DoingNothing : ScreenRecordModel
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt b/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt
new file mode 100644
index 0000000..6730d2d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.screenshot
+
+import android.net.Uri
+import android.os.UserManager
+import android.util.Log
+import android.view.WindowManager
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.UUID
+import java.util.concurrent.Executor
+import java.util.concurrent.Executors
+import java.util.function.Consumer
+import javax.inject.Inject
+
+/**
+ * A ScreenshotHandler that just saves the screenshot and calls back as appropriate, with no UI.
+ *
+ * Basically, ScreenshotController with all the UI bits ripped out.
+ */
+class HeadlessScreenshotHandler
+@Inject
+constructor(
+ private val imageExporter: ImageExporter,
+ @Main private val mainExecutor: Executor,
+ private val imageCapture: ImageCapture,
+ private val userManager: UserManager,
+ private val uiEventLogger: UiEventLogger,
+ private val notificationsControllerFactory: ScreenshotNotificationsController.Factory,
+) : ScreenshotHandler {
+
+ override fun handleScreenshot(
+ screenshot: ScreenshotData,
+ finisher: Consumer<Uri?>,
+ requestCallback: TakeScreenshotService.RequestCallback
+ ) {
+ if (screenshot.type == WindowManager.TAKE_SCREENSHOT_FULLSCREEN) {
+ screenshot.bitmap = imageCapture.captureDisplay(screenshot.displayId, crop = null)
+ }
+
+ if (screenshot.bitmap == null) {
+ Log.e(TAG, "handleScreenshot: Screenshot bitmap was null")
+ notificationsControllerFactory
+ .create(screenshot.displayId)
+ .notifyScreenshotError(R.string.screenshot_failed_to_capture_text)
+ requestCallback.reportError()
+ return
+ }
+
+ val future: ListenableFuture<ImageExporter.Result> =
+ imageExporter.export(
+ Executors.newSingleThreadExecutor(),
+ UUID.randomUUID(),
+ screenshot.bitmap,
+ screenshot.getUserOrDefault(),
+ screenshot.displayId
+ )
+ future.addListener(
+ {
+ try {
+ val result = future.get()
+ Log.d(TAG, "Saved screenshot: $result")
+ logScreenshotResultStatus(result.uri, screenshot)
+ finisher.accept(result.uri)
+ requestCallback.onFinish()
+ } catch (e: Exception) {
+ Log.d(TAG, "Failed to store screenshot", e)
+ finisher.accept(null)
+ requestCallback.reportError()
+ }
+ },
+ mainExecutor
+ )
+ }
+
+ private fun logScreenshotResultStatus(uri: Uri?, screenshot: ScreenshotData) {
+ if (uri == null) {
+ uiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0, screenshot.packageNameString)
+ notificationsControllerFactory
+ .create(screenshot.displayId)
+ .notifyScreenshotError(R.string.screenshot_failed_to_save_text)
+ } else {
+ uiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, screenshot.packageNameString)
+ if (userManager.isManagedProfile(screenshot.getUserOrDefault().identifier)) {
+ uiEventLogger.log(
+ ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE,
+ 0,
+ screenshot.packageNameString
+ )
+ }
+ }
+ }
+
+ companion object {
+ const val TAG = "HeadlessScreenshotHandler"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index e8dfac8..c87b1f5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -101,7 +101,7 @@
/**
* Controls the state and flow for screenshots.
*/
-public class ScreenshotController {
+public class ScreenshotController implements ScreenshotHandler {
private static final String TAG = logTag(ScreenshotController.class);
/**
@@ -351,7 +351,8 @@
mShowUIOnExternalDisplay = showUIOnExternalDisplay;
}
- void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher,
+ @Override
+ public void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher,
RequestCallback requestCallback) {
Assert.isMainThread();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 3c3797b..2699657 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.screenshot
import android.net.Uri
@@ -7,12 +23,12 @@
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import com.android.internal.logging.UiEventLogger
import com.android.internal.util.ScreenshotRequest
-import com.android.systemui.Flags.screenshotShelfUi2
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.res.R
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
import java.util.function.Consumer
import javax.inject.Inject
@@ -26,9 +42,13 @@
onSaved: (Uri?) -> Unit,
requestCallback: RequestCallback
)
+
fun onCloseSystemDialogsReceived()
+
fun removeWindows()
+
fun onDestroy()
+
fun executeScreenshotsAsync(
screenshotRequest: ScreenshotRequest,
onSaved: Consumer<Uri?>,
@@ -36,6 +56,14 @@
)
}
+interface ScreenshotHandler {
+ fun handleScreenshot(
+ screenshot: ScreenshotData,
+ finisher: Consumer<Uri?>,
+ requestCallback: RequestCallback
+ )
+}
+
/**
* Receives the signal to take a screenshot from [TakeScreenshotService], and calls back with the
* result.
@@ -52,10 +80,10 @@
private val screenshotRequestProcessor: ScreenshotRequestProcessor,
private val uiEventLogger: UiEventLogger,
private val screenshotNotificationControllerFactory: ScreenshotNotificationsController.Factory,
+ private val headlessScreenshotHandler: HeadlessScreenshotHandler,
) : TakeScreenshotExecutor {
-
private val displays = displayRepository.displays
- private val screenshotControllers = mutableMapOf<Int, ScreenshotController>()
+ private var screenshotController: ScreenshotController? = null
private val notificationControllers = mutableMapOf<Int, ScreenshotNotificationsController>()
/**
@@ -73,9 +101,15 @@
val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback)
displays.forEach { display ->
val displayId = display.displayId
+ var screenshotHandler: ScreenshotHandler =
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ getScreenshotController(display)
+ } else {
+ headlessScreenshotHandler
+ }
Log.d(TAG, "Executing screenshot for display $displayId")
dispatchToController(
- display = display,
+ screenshotHandler,
rawScreenshotData = ScreenshotData.fromRequest(screenshotRequest, displayId),
onSaved =
if (displayId == Display.DEFAULT_DISPLAY) {
@@ -88,7 +122,7 @@
/** All logging should be triggered only by this method. */
private suspend fun dispatchToController(
- display: Display,
+ screenshotHandler: ScreenshotHandler,
rawScreenshotData: ScreenshotData,
onSaved: (Uri?) -> Unit,
callback: RequestCallback
@@ -102,13 +136,12 @@
logScreenshotRequested(rawScreenshotData)
onFailedScreenshotRequest(rawScreenshotData, callback)
}
- .getOrNull()
- ?: return
+ .getOrNull() ?: return
logScreenshotRequested(screenshotData)
Log.d(TAG, "Screenshot request: $screenshotData")
try {
- getScreenshotController(display).handleScreenshot(screenshotData, onSaved, callback)
+ screenshotHandler.handleScreenshot(screenshotData, onSaved, callback)
} catch (e: IllegalStateException) {
Log.e(TAG, "Error while ScreenshotController was handling ScreenshotData!", e)
onFailedScreenshotRequest(screenshotData, callback)
@@ -140,44 +173,32 @@
private suspend fun getDisplaysToScreenshot(requestType: Int): List<Display> {
val allDisplays = displays.first()
- return if (requestType == TAKE_SCREENSHOT_PROVIDED_IMAGE || screenshotShelfUi2()) {
- // If this is a provided image or using the shelf UI, just screenshot th default display
+ return if (requestType == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
+ // If this is a provided image just screenshot th default display
allDisplays.filter { it.displayId == Display.DEFAULT_DISPLAY }
} else {
allDisplays.filter { it.type in ALLOWED_DISPLAY_TYPES }
}
}
- /** Propagates the close system dialog signal to all controllers. */
+ /** Propagates the close system dialog signal to the ScreenshotController. */
override fun onCloseSystemDialogsReceived() {
- screenshotControllers.forEach { (_, screenshotController) ->
- if (!screenshotController.isPendingSharedTransition) {
- screenshotController.requestDismissal(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
- }
+ if (screenshotController?.isPendingSharedTransition == false) {
+ screenshotController?.requestDismissal(SCREENSHOT_DISMISSED_OTHER)
}
}
/** Removes all screenshot related windows. */
override fun removeWindows() {
- screenshotControllers.forEach { (_, screenshotController) ->
- screenshotController.removeWindow()
- }
+ screenshotController?.removeWindow()
}
/**
* Destroys the executor. Afterwards, this class is not expected to work as intended anymore.
*/
override fun onDestroy() {
- screenshotControllers.forEach { (_, screenshotController) ->
- screenshotController.onDestroy()
- }
- screenshotControllers.clear()
- }
-
- private fun getScreenshotController(display: Display): ScreenshotController {
- return screenshotControllers.computeIfAbsent(display.displayId) {
- screenshotControllerFactory.create(display, /* showUIOnExternalDisplay= */ false)
- }
+ screenshotController?.onDestroy()
+ screenshotController = null
}
private fun getNotificationController(id: Int): ScreenshotNotificationsController {
@@ -197,6 +218,12 @@
}
}
+ private fun getScreenshotController(display: Display): ScreenshotController {
+ val controller = screenshotController ?: screenshotControllerFactory.create(display, false)
+ screenshotController = controller
+ return controller
+ }
+
/**
* Returns a [RequestCallback] that wraps [originalCallback].
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index ba3fde6..79f1874 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.call.ui.viewmodel
+import android.view.View
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.common.shared.model.Icon
@@ -60,7 +61,7 @@
val startTimeInElapsedRealtime =
state.startTimeMs - systemClock.currentTimeMillis() +
systemClock.elapsedRealtime()
- OngoingActivityChipModel.Shown(
+ OngoingActivityChipModel.Shown.Timer(
icon =
Icon.Resource(
com.android.internal.R.drawable.ic_phone,
@@ -68,26 +69,30 @@
),
colors = ColorsModel.Themed,
startTimeMs = startTimeInElapsedRealtime,
- ) {
- if (state.intent != null) {
- val backgroundView =
- it.requireViewById<ChipBackgroundContainer>(
- R.id.ongoing_activity_chip_background
- )
- // TODO(b/332662551): Log the click event.
- // This mimics OngoingCallController#updateChipClickListener.
- activityStarter.postStartActivityDismissingKeyguard(
- state.intent,
- ActivityTransitionAnimator.Controller.fromView(
- backgroundView,
- InteractionJankMonitor
- .CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
- )
- )
- }
- }
+ getOnClickListener(state),
+ )
}
}
}
.stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
+
+ private fun getOnClickListener(state: OngoingCallModel.InCall): View.OnClickListener? {
+ if (state.intent == null) {
+ return null
+ }
+
+ return View.OnClickListener { view ->
+ val backgroundView =
+ view.requireViewById<ChipBackgroundContainer>(R.id.ongoing_activity_chip_background)
+ // TODO(b/332662551): Log the click event.
+ // This mimics OngoingCallController#updateChipClickListener.
+ activityStarter.postStartActivityDismissingKeyguard(
+ state.intent,
+ ActivityTransitionAnimator.Controller.fromView(
+ backgroundView,
+ InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
+ )
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
index 53b1e75..42e921e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
@@ -70,7 +70,8 @@
}
}
}
- .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
+ // See b/347726238.
+ .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden)
/** Stops the currently active projection. */
private fun stopProjecting() {
@@ -80,7 +81,7 @@
private fun createCastToOtherDeviceChip(
state: ProjectionChipModel.Projecting,
): OngoingActivityChipModel.Shown {
- return OngoingActivityChipModel.Shown(
+ return OngoingActivityChipModel.Shown.Timer(
icon =
Icon.Resource(
CAST_TO_OTHER_DEVICE_ICON,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
index 1e9f0a1..43b1d16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
@@ -50,7 +50,6 @@
) { screenRecordState, mediaProjectionState ->
when (screenRecordState) {
is ScreenRecordModel.DoingNothing -> ScreenRecordChipModel.DoingNothing
- // TODO(b/332662551): Implement the 3-2-1 countdown chip.
is ScreenRecordModel.Starting ->
ScreenRecordChipModel.Starting(screenRecordState.millisUntilStarted)
is ScreenRecordModel.Recording -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
index 9d54c75..af6d7f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
@@ -23,6 +23,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
+import com.android.systemui.screenrecord.data.model.ScreenRecordModel.Starting.Companion.toCountdownSeconds
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.ScreenRecordChipInteractor
import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel
@@ -55,10 +56,14 @@
.map { state ->
when (state) {
is ScreenRecordChipModel.DoingNothing -> OngoingActivityChipModel.Hidden
- // TODO(b/332662551): Implement the 3-2-1 countdown chip.
- is ScreenRecordChipModel.Starting -> OngoingActivityChipModel.Hidden
+ is ScreenRecordChipModel.Starting -> {
+ OngoingActivityChipModel.Shown.Countdown(
+ colors = ColorsModel.Red,
+ secondsUntilStarted = state.millisUntilStarted.toCountdownSeconds(),
+ )
+ }
is ScreenRecordChipModel.Recording -> {
- OngoingActivityChipModel.Shown(
+ OngoingActivityChipModel.Shown.Timer(
// TODO(b/332662551): Also provide a content description.
icon = Icon.Resource(ICON, contentDescription = null),
colors = ColorsModel.Red,
@@ -71,7 +76,8 @@
}
}
}
- .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
+ // See b/347726238.
+ .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden)
private fun createDelegate(
recordedTask: ActivityManager.RunningTaskInfo?
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
index 0c24a70..c3b1456 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
@@ -66,7 +66,8 @@
}
}
}
- .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
+ // See b/347726238.
+ .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden)
/** Stops the currently active projection. */
private fun stopProjecting() {
@@ -76,7 +77,7 @@
private fun createShareToAppChip(
state: ProjectionChipModel.Projecting,
): OngoingActivityChipModel.Shown {
- return OngoingActivityChipModel.Shown(
+ return OngoingActivityChipModel.Shown.Timer(
// TODO(b/332662551): Use the right content description.
icon = Icon.Resource(SHARE_TO_APP_ICON, contentDescription = null),
colors = ColorsModel.Red,
@@ -97,7 +98,6 @@
)
companion object {
- // TODO(b/332662551): Use the right icon.
- @DrawableRes val SHARE_TO_APP_ICON = R.drawable.ic_screenshot_share
+ @DrawableRes val SHARE_TO_APP_ICON = R.drawable.ic_present_to_all
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index 4ea674a..57f609b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -25,22 +25,42 @@
data object Hidden : OngoingActivityChipModel()
/** This chip should be shown with the given information. */
- data class Shown(
- /** The icon to show on the chip. */
- val icon: Icon,
+ abstract class Shown(
+ /** The icon to show on the chip. If null, no icon will be shown. */
+ open val icon: Icon?,
/** What colors to use for the chip. */
- val colors: ColorsModel,
+ open val colors: ColorsModel,
/**
- * The time this event started, used to show the timer.
- *
- * This time should be relative to
- * [com.android.systemui.util.time.SystemClock.elapsedRealtime], *not*
- * [com.android.systemui.util.time.SystemClock.currentTimeMillis] because the
- * [ChipChronometer] is based off of elapsed realtime. See
- * [android.widget.Chronometer.setBase].
+ * Listener method to invoke when this chip is clicked. If null, the chip won't be
+ * clickable.
*/
- val startTimeMs: Long,
- /** Listener method to invoke when this chip is clicked. */
- val onClickListener: View.OnClickListener,
- ) : OngoingActivityChipModel()
+ open val onClickListener: View.OnClickListener?,
+ ) : OngoingActivityChipModel() {
+ /** The chip shows a timer, counting up from [startTimeMs]. */
+ data class Timer(
+ override val icon: Icon,
+ override val colors: ColorsModel,
+ /**
+ * The time this event started, used to show the timer.
+ *
+ * This time should be relative to
+ * [com.android.systemui.util.time.SystemClock.elapsedRealtime], *not*
+ * [com.android.systemui.util.time.SystemClock.currentTimeMillis] because the
+ * [ChipChronometer] is based off of elapsed realtime. See
+ * [android.widget.Chronometer.setBase].
+ */
+ val startTimeMs: Long,
+ override val onClickListener: View.OnClickListener?,
+ ) : Shown(icon, colors, onClickListener)
+
+ /**
+ * This chip shows a countdown using [secondsUntilStarted]. Used to inform users that an
+ * event is about to start. Typically, a [Countdown] chip will turn into a [Timer] chip.
+ */
+ data class Countdown(
+ override val colors: ColorsModel,
+ /** The number of seconds until an event is started. */
+ val secondsUntilStarted: Long,
+ ) : Shown(icon = null, colors, onClickListener = null)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
index 1b79ce4..9c8086f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
@@ -72,5 +72,8 @@
else -> call
}
}
- .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
+ // Some of the chips could have timers in them and we don't want the start time
+ // for those timers to get reset for any reason. So, as soon as any subscriber has
+ // requested the chip information, we need to maintain it forever. See b/347726238.
+ .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 2371eed..3ba62b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -44,6 +44,7 @@
import androidx.lifecycle.Observer;
+import com.android.systemui.Flags;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
@@ -57,6 +58,7 @@
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.res.R;
import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.screenrecord.data.model.ScreenRecordModel;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
@@ -354,7 +356,11 @@
mProvisionedController.addCallback(this);
mCurrentUserSetup = mProvisionedController.isCurrentUserSetup();
mZenController.addCallback(this);
- mCast.addCallback(mCastCallback);
+ if (!Flags.statusBarScreenSharingChips()) {
+ // If the flag is enabled, the cast icon is handled in the new screen sharing chips
+ // instead of here so we don't need to listen for events here.
+ mCast.addCallback(mCastCallback);
+ }
mHotspot.addCallback(mHotspotCallback);
mNextAlarmController.addCallback(mNextAlarmCallback);
mDataSaver.addCallback(this);
@@ -362,7 +368,11 @@
mPrivacyItemController.addCallback(this);
mSensorPrivacyController.addCallback(mSensorPrivacyListener);
mLocationController.addCallback(this);
- mRecordingController.addCallback(this);
+ if (!Flags.statusBarScreenSharingChips()) {
+ // If the flag is enabled, the screen record icon is handled in the new screen sharing
+ // chips instead of here so we don't need to listen for events here.
+ mRecordingController.addCallback(this);
+ }
mJavaAdapter.alwaysCollectFlow(mConnectedDisplayInteractor.getConnectedDisplayState(),
this::onConnectedDisplayAvailabilityChanged);
@@ -519,6 +529,11 @@
}
private void updateCast() {
+ if (Flags.statusBarScreenSharingChips()) {
+ // The cast icon is handled in the new screen sharing chips instead of here.
+ return;
+ }
+
boolean isCasting = false;
for (CastDevice device : mCast.getCastDevices()) {
if (device.isCasting()) {
@@ -788,6 +803,10 @@
private Runnable mRemoveCastIconRunnable = new Runnable() {
@Override
public void run() {
+ if (Flags.statusBarScreenSharingChips()) {
+ // The cast icon is handled in the new screen sharing chips instead of here.
+ return;
+ }
if (DEBUG) Log.v(TAG, "updateCast: hiding icon NOW");
mIconController.setIconVisibility(mSlotCast, false);
}
@@ -796,8 +815,13 @@
// Screen Recording
@Override
public void onCountdown(long millisUntilFinished) {
+ if (Flags.statusBarScreenSharingChips()) {
+ // The screen record icon is handled in the new screen sharing chips instead of here.
+ return;
+ }
if (DEBUG) Log.d(TAG, "screenrecord: countdown " + millisUntilFinished);
- int countdown = (int) Math.floorDiv(millisUntilFinished + 500, 1000);
+ int countdown =
+ (int) ScreenRecordModel.Starting.Companion.toCountdownSeconds(millisUntilFinished);
int resourceId = R.drawable.stat_sys_screen_record;
String description = Integer.toString(countdown);
switch (countdown) {
@@ -820,6 +844,10 @@
@Override
public void onCountdownEnd() {
+ if (Flags.statusBarScreenSharingChips()) {
+ // The screen record icon is handled in the new screen sharing chips instead of here.
+ return;
+ }
if (DEBUG) Log.d(TAG, "screenrecord: hiding icon during countdown");
mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false));
// Reset talkback priority
@@ -829,6 +857,10 @@
@Override
public void onRecordingStart() {
+ if (Flags.statusBarScreenSharingChips()) {
+ // The screen record icon is handled in the new screen sharing chips instead of here.
+ return;
+ }
if (DEBUG) Log.d(TAG, "screenrecord: showing icon");
mIconController.setIcon(mSlotScreenRecord,
R.drawable.stat_sys_screen_record,
@@ -838,6 +870,10 @@
@Override
public void onRecordingEnd() {
+ if (Flags.statusBarScreenSharingChips()) {
+ // The screen record icon is handled in the new screen sharing chips instead of here.
+ return;
+ }
// Ensure this is on the main thread
if (DEBUG) Log.d(TAG, "screenrecord: hiding icon");
mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
index d607ce0..68983a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
@@ -22,6 +22,7 @@
import android.graphics.drawable.GradientDrawable
import android.view.View
import android.widget.ImageView
+import android.widget.TextView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.Flags
@@ -92,6 +93,8 @@
chipView.requireViewById(R.id.ongoing_activity_chip_icon)
val chipTimeView: ChipChronometer =
chipView.requireViewById(R.id.ongoing_activity_chip_time)
+ val chipTextView: TextView =
+ chipView.requireViewById(R.id.ongoing_activity_chip_text)
val chipBackgroundView =
chipView.requireViewById<ChipBackgroundContainer>(
R.id.ongoing_activity_chip_background
@@ -101,14 +104,15 @@
when (chipModel) {
is OngoingActivityChipModel.Shown -> {
// Data
- IconViewBinder.bind(chipModel.icon, chipIconView)
- ChipChronometerBinder.bind(chipModel.startTimeMs, chipTimeView)
+ IconViewBinder.bindNullable(chipModel.icon, chipIconView)
+ setChipMainContent(chipModel, chipTextView, chipTimeView)
chipView.setOnClickListener(chipModel.onClickListener)
// Colors
val textColor = chipModel.colors.text(chipContext)
chipIconView.imageTintList = ColorStateList.valueOf(textColor)
chipTimeView.setTextColor(textColor)
+ chipTextView.setTextColor(textColor)
(chipBackgroundView.background as GradientDrawable).color =
chipModel.colors.background(chipContext)
@@ -117,6 +121,8 @@
)
}
is OngoingActivityChipModel.Hidden -> {
+ // The Chronometer should be stopped to prevent leaks -- see
+ // b/192243808 and [Chronometer.start].
chipTimeView.stop()
listener.onOngoingActivityStatusChanged(
hasOngoingActivity = false
@@ -130,6 +136,61 @@
}
}
+ private fun setChipMainContent(
+ chipModel: OngoingActivityChipModel.Shown,
+ chipTextView: TextView,
+ chipTimeView: ChipChronometer,
+ ) {
+ when (chipModel) {
+ is OngoingActivityChipModel.Shown.Countdown -> {
+ chipTextView.text = chipModel.secondsUntilStarted.toString()
+ chipTextView.visibility = View.VISIBLE
+
+ // The Chronometer should be stopped to prevent leaks -- see b/192243808 and
+ // [Chronometer.start].
+ chipTimeView.stop()
+ chipTimeView.visibility = View.GONE
+ }
+ is OngoingActivityChipModel.Shown.Timer -> {
+ ChipChronometerBinder.bind(chipModel.startTimeMs, chipTimeView)
+ chipTimeView.visibility = View.VISIBLE
+
+ chipTextView.visibility = View.GONE
+ }
+ }
+ updateChipTextPadding(chipModel, chipTextView, chipTimeView)
+ }
+
+ private fun updateChipTextPadding(
+ chipModel: OngoingActivityChipModel.Shown,
+ chipTextView: TextView,
+ chipTimeView: ChipChronometer,
+ ) {
+ val requiresPadding = chipModel.icon != null
+ if (requiresPadding) {
+ chipTextView.addChipTextPaddingStart()
+ chipTimeView.addChipTextPaddingStart()
+ } else {
+ chipTextView.removeChipTextPaddingStart()
+ chipTimeView.removeChipTextPaddingStart()
+ }
+ }
+
+ private fun View.addChipTextPaddingStart() {
+ this.setPaddingRelative(
+ this.context.resources.getDimensionPixelSize(
+ R.dimen.ongoing_activity_chip_icon_text_padding
+ ),
+ paddingTop,
+ paddingEnd,
+ paddingBottom,
+ )
+ }
+
+ private fun View.removeChipTextPaddingStart() {
+ this.setPaddingRelative(/* start= */ 0, paddingTop, paddingEnd, paddingBottom)
+ }
+
private fun animateLightsOutView(view: View, visible: Boolean) {
view.animate().cancel()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
index 1c86638..03483c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
@@ -20,6 +20,8 @@
import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX;
import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -279,4 +281,23 @@
verify(mTileLifecycle, never()).onStopListening();
verify(mTileLifecycle, never()).executeSetBindService(false);
}
+
+ @Test
+ public void testNoExtraPendingBindIfAlreadyBound() {
+ mTileServiceManager.startLifecycleManagerAndAddTile();
+
+ // As part of adding the tile, it will be bound and it will send a start successful to
+ // TileServices. startSuccessful will clear pending bind
+ mTileServiceManager.clearPendingBind();
+
+ // Assume we are still bound
+ when(mTileLifecycle.isBound()).thenReturn(true);
+
+ // And we want to bind again
+ mTileServiceManager.setBindAllowed(true);
+ mTileServiceManager.setBindRequested(true);
+
+ // Then the tile doesn't have pending bind
+ assertThat(mTileServiceManager.hasPendingBind()).isFalse();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/data/model/ScreenRecordModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/data/model/ScreenRecordModelTest.kt
new file mode 100644
index 0000000..9331c8d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/data/model/ScreenRecordModelTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenrecord.data.model
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.screenrecord.data.model.ScreenRecordModel.Starting.Companion.toCountdownSeconds
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+
+@SmallTest
+class ScreenRecordModelTest : SysuiTestCase() {
+ @Test
+ fun countdownSeconds_millis0_is0() {
+ assertThat(0L.toCountdownSeconds()).isEqualTo(0)
+ assertThat(ScreenRecordModel.Starting(0L).countdownSeconds).isEqualTo(0)
+ }
+
+ @Test
+ fun countdownSeconds_millis500_isOne() {
+ assertThat(500L.toCountdownSeconds()).isEqualTo(1)
+ assertThat(ScreenRecordModel.Starting(500L).countdownSeconds).isEqualTo(1)
+ }
+
+ @Test
+ fun countdownSeconds_millis999_isOne() {
+ assertThat(999L.toCountdownSeconds()).isEqualTo(1)
+ assertThat(ScreenRecordModel.Starting(999L).countdownSeconds).isEqualTo(1)
+ }
+
+ @Test
+ fun countdownSeconds_millis1000_isOne() {
+ assertThat(1000L.toCountdownSeconds()).isEqualTo(1)
+ assertThat(ScreenRecordModel.Starting(1000L).countdownSeconds).isEqualTo(1)
+ }
+
+ @Test
+ fun countdownSeconds_millis1499_isOne() {
+ assertThat(1499L.toCountdownSeconds()).isEqualTo(1)
+ assertThat(ScreenRecordModel.Starting(1499L).countdownSeconds).isEqualTo(1)
+ }
+
+ @Test
+ fun countdownSeconds_millis1500_isTwo() {
+ assertThat(1500L.toCountdownSeconds()).isEqualTo(2)
+ assertThat(ScreenRecordModel.Starting(1500L).countdownSeconds).isEqualTo(2)
+ }
+
+ @Test
+ fun countdownSeconds_millis1999_isTwo() {
+ assertThat(1599L.toCountdownSeconds()).isEqualTo(2)
+ assertThat(ScreenRecordModel.Starting(1599L).countdownSeconds).isEqualTo(2)
+ }
+
+ @Test
+ fun countdownSeconds_millis2000_isTwo() {
+ assertThat(2000L.toCountdownSeconds()).isEqualTo(2)
+ assertThat(ScreenRecordModel.Starting(2000L).countdownSeconds).isEqualTo(2)
+ }
+
+ @Test
+ fun countdownSeconds_millis2500_isThree() {
+ assertThat(2500L.toCountdownSeconds()).isEqualTo(3)
+ assertThat(ScreenRecordModel.Starting(2500L).countdownSeconds).isEqualTo(3)
+ }
+
+ @Test
+ fun countdownSeconds_millis2999_isThree() {
+ assertThat(2999L.toCountdownSeconds()).isEqualTo(3)
+ assertThat(ScreenRecordModel.Starting(2999L).countdownSeconds).isEqualTo(3)
+ }
+
+ @Test
+ fun countdownSeconds_millis3000_isThree() {
+ assertThat(3000L.toCountdownSeconds()).isEqualTo(3)
+ assertThat(ScreenRecordModel.Starting(3000L).countdownSeconds).isEqualTo(3)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index ec5589e..0b81b5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -3,9 +3,6 @@
import android.content.ComponentName
import android.graphics.Bitmap
import android.net.Uri
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
-import android.testing.AndroidTestingRunner
import android.view.Display
import android.view.Display.TYPE_EXTERNAL
import android.view.Display.TYPE_INTERNAL
@@ -18,7 +15,6 @@
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.util.ScreenshotRequest
-import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.display.data.repository.display
@@ -26,7 +22,6 @@
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.kotlinArgumentCaptor as ArgumentCaptor
import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.lang.IllegalStateException
@@ -47,8 +42,7 @@
@SmallTest
class TakeScreenshotExecutorTest : SysuiTestCase() {
- private val controller0 = mock<ScreenshotController>()
- private val controller1 = mock<ScreenshotController>()
+ private val controller = mock<ScreenshotController>()
private val notificationsController0 = mock<ScreenshotNotificationsController>()
private val notificationsController1 = mock<ScreenshotNotificationsController>()
private val controllerFactory = mock<ScreenshotController.Factory>()
@@ -60,6 +54,7 @@
private val topComponent = ComponentName(mContext, TakeScreenshotExecutorTest::class.java)
private val testScope = TestScope(UnconfinedTestDispatcher())
private val eventLogger = UiEventLoggerFake()
+ private val headlessHandler = mock<HeadlessScreenshotHandler>()
private val screenshotExecutor =
TakeScreenshotExecutorImpl(
@@ -68,20 +63,18 @@
testScope,
requestProcessor,
eventLogger,
- notificationControllerFactory
+ notificationControllerFactory,
+ headlessHandler,
)
@Before
fun setUp() {
- whenever(controllerFactory.create(any(), any())).thenAnswer {
- if (it.getArgument<Display>(0).displayId == 0) controller0 else controller1
- }
+ whenever(controllerFactory.create(any(), any())).thenReturn(controller)
whenever(notificationControllerFactory.create(eq(0))).thenReturn(notificationsController0)
whenever(notificationControllerFactory.create(eq(1))).thenReturn(notificationsController1)
}
@Test
- @DisableFlags(Flags.FLAG_SCREENSHOT_SHELF_UI2)
fun executeScreenshots_severalDisplays_callsControllerForEachOne() =
testScope.runTest {
val internalDisplay = display(TYPE_INTERNAL, id = 0)
@@ -91,14 +84,14 @@
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
verify(controllerFactory).create(eq(internalDisplay), any())
- verify(controllerFactory).create(eq(externalDisplay), any())
+ verify(controllerFactory, never()).create(eq(externalDisplay), any())
val capturer = ArgumentCaptor<ScreenshotData>()
- verify(controller0).handleScreenshot(capturer.capture(), any(), any())
+ verify(controller).handleScreenshot(capturer.capture(), any(), any())
assertThat(capturer.value.displayId).isEqualTo(0)
// OnSaved callback should be different.
- verify(controller1).handleScreenshot(capturer.capture(), any(), any())
+ verify(headlessHandler).handleScreenshot(capturer.capture(), any(), any())
assertThat(capturer.value.displayId).isEqualTo(1)
assertThat(eventLogger.numLogs()).isEqualTo(2)
@@ -113,32 +106,6 @@
}
@Test
- @EnableFlags(Flags.FLAG_SCREENSHOT_SHELF_UI2)
- fun executeScreenshots_severalDisplaysShelfUi_justCallsOne() =
- testScope.runTest {
- val internalDisplay = display(TYPE_INTERNAL, id = 0)
- val externalDisplay = display(TYPE_EXTERNAL, id = 1)
- setDisplays(internalDisplay, externalDisplay)
- val onSaved = { _: Uri? -> }
- screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
-
- verify(controllerFactory).create(eq(internalDisplay), any())
-
- val capturer = ArgumentCaptor<ScreenshotData>()
-
- verify(controller0).handleScreenshot(capturer.capture(), any(), any())
- assertThat(capturer.value.displayId).isEqualTo(0)
-
- assertThat(eventLogger.numLogs()).isEqualTo(1)
- assertThat(eventLogger.get(0).eventId)
- .isEqualTo(ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id)
- assertThat(eventLogger.get(0).packageName).isEqualTo(topComponent.packageName)
-
- screenshotExecutor.onDestroy()
- }
-
- @Test
- @DisableFlags(Flags.FLAG_SCREENSHOT_SHELF_UI2)
fun executeScreenshots_providedImageType_callsOnlyDefaultDisplayController() =
testScope.runTest {
val internalDisplay = display(TYPE_INTERNAL, id = 0)
@@ -156,10 +123,10 @@
val capturer = ArgumentCaptor<ScreenshotData>()
- verify(controller0).handleScreenshot(capturer.capture(), any(), any())
+ verify(controller).handleScreenshot(capturer.capture(), any(), any())
assertThat(capturer.value.displayId).isEqualTo(0)
// OnSaved callback should be different.
- verify(controller1, never()).handleScreenshot(any(), any(), any())
+ verify(headlessHandler, never()).handleScreenshot(any(), any(), any())
assertThat(eventLogger.numLogs()).isEqualTo(1)
assertThat(eventLogger.get(0).eventId)
@@ -170,7 +137,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_SCREENSHOT_SHELF_UI2)
fun executeScreenshots_onlyVirtualDisplays_noInteractionsWithControllers() =
testScope.runTest {
setDisplays(display(TYPE_VIRTUAL, id = 0), display(TYPE_VIRTUAL, id = 1))
@@ -178,14 +144,14 @@
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
verifyNoMoreInteractions(controllerFactory)
+ verify(headlessHandler, never()).handleScreenshot(any(), any(), any())
screenshotExecutor.onDestroy()
}
@Test
- @DisableFlags(Flags.FLAG_SCREENSHOT_SHELF_UI2)
fun executeScreenshots_allowedTypes_allCaptured() =
testScope.runTest {
- whenever(controllerFactory.create(any(), any())).thenReturn(controller0)
+ whenever(controllerFactory.create(any(), any())).thenReturn(controller)
setDisplays(
display(TYPE_INTERNAL, id = 0),
@@ -196,12 +162,12 @@
val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
- verify(controller0, times(4)).handleScreenshot(any(), any(), any())
+ verify(controller, times(1)).handleScreenshot(any(), any(), any())
+ verify(headlessHandler, times(3)).handleScreenshot(any(), any(), any())
screenshotExecutor.onDestroy()
}
@Test
- @DisableFlags(Flags.FLAG_SCREENSHOT_SHELF_UI2)
fun executeScreenshots_reportsOnFinishedOnlyWhenBothFinished() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
@@ -211,8 +177,8 @@
val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
val capturer1 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
- verify(controller0).handleScreenshot(any(), any(), capturer0.capture())
- verify(controller1).handleScreenshot(any(), any(), capturer1.capture())
+ verify(controller).handleScreenshot(any(), any(), capturer0.capture())
+ verify(headlessHandler).handleScreenshot(any(), any(), capturer1.capture())
verify(callback, never()).onFinish()
@@ -227,7 +193,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_SCREENSHOT_SHELF_UI2)
fun executeScreenshots_oneFinishesOtherFails_reportFailsOnlyAtTheEnd() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
@@ -237,8 +202,8 @@
val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
val capturer1 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
- verify(controller0).handleScreenshot(any(), any(), capturer0.capture())
- verify(controller1).handleScreenshot(any(), nullable(), capturer1.capture())
+ verify(controller).handleScreenshot(any(), any(), capturer0.capture())
+ verify(headlessHandler).handleScreenshot(any(), any(), capturer1.capture())
verify(callback, never()).onFinish()
@@ -255,7 +220,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_SCREENSHOT_SHELF_UI2)
fun executeScreenshots_allDisplaysFail_reportsFail() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
@@ -265,8 +229,8 @@
val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
val capturer1 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
- verify(controller0).handleScreenshot(any(), any(), capturer0.capture())
- verify(controller1).handleScreenshot(any(), any(), capturer1.capture())
+ verify(controller).handleScreenshot(any(), any(), capturer0.capture())
+ verify(headlessHandler).handleScreenshot(any(), any(), capturer1.capture())
verify(callback, never()).onFinish()
@@ -283,7 +247,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_SCREENSHOT_SHELF_UI2)
fun onDestroy_propagatedToControllers() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
@@ -291,59 +254,50 @@
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
screenshotExecutor.onDestroy()
- verify(controller0).onDestroy()
- verify(controller1).onDestroy()
+ verify(controller).onDestroy()
}
@Test
- @DisableFlags(Flags.FLAG_SCREENSHOT_SHELF_UI2)
- fun removeWindows_propagatedToControllers() =
+ fun removeWindows_propagatedToController() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
screenshotExecutor.removeWindows()
- verify(controller0).removeWindow()
- verify(controller1).removeWindow()
+ verify(controller).removeWindow()
screenshotExecutor.onDestroy()
}
@Test
- @DisableFlags(Flags.FLAG_SCREENSHOT_SHELF_UI2)
- fun onCloseSystemDialogsReceived_propagatedToControllers() =
+ fun onCloseSystemDialogsReceived_propagatedToController() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
screenshotExecutor.onCloseSystemDialogsReceived()
- verify(controller0).requestDismissal(any())
- verify(controller1).requestDismissal(any())
+ verify(controller).requestDismissal(any())
screenshotExecutor.onDestroy()
}
@Test
- @DisableFlags(Flags.FLAG_SCREENSHOT_SHELF_UI2)
- fun onCloseSystemDialogsReceived_someControllerHavePendingTransitions() =
+ fun onCloseSystemDialogsReceived_controllerHasPendingTransitions() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- whenever(controller0.isPendingSharedTransition).thenReturn(true)
- whenever(controller1.isPendingSharedTransition).thenReturn(false)
+ whenever(controller.isPendingSharedTransition).thenReturn(true)
val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
screenshotExecutor.onCloseSystemDialogsReceived()
- verify(controller0, never()).requestDismissal(any())
- verify(controller1).requestDismissal(any())
+ verify(controller, never()).requestDismissal(any())
screenshotExecutor.onDestroy()
}
@Test
- @DisableFlags(Flags.FLAG_SCREENSHOT_SHELF_UI2)
fun executeScreenshots_controllerCalledWithRequestProcessorReturnValue() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0))
@@ -358,14 +312,13 @@
.isEqualTo(ScreenshotData.fromRequest(screenshotRequest))
val capturer = ArgumentCaptor<ScreenshotData>()
- verify(controller0).handleScreenshot(capturer.capture(), any(), any())
+ verify(controller).handleScreenshot(capturer.capture(), any(), any())
assertThat(capturer.value).isEqualTo(toBeReturnedByProcessor)
screenshotExecutor.onDestroy()
}
@Test
- @DisableFlags(Flags.FLAG_SCREENSHOT_SHELF_UI2)
fun executeScreenshots_errorFromProcessor_logsScreenshotRequested() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
@@ -383,7 +336,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_SCREENSHOT_SHELF_UI2)
fun executeScreenshots_errorFromProcessor_logsUiError() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
@@ -401,7 +353,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_SCREENSHOT_SHELF_UI2)
fun executeScreenshots_errorFromProcessorOnDefaultDisplay_showsErrorNotification() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
@@ -428,14 +379,13 @@
}
@Test
- @DisableFlags(Flags.FLAG_SCREENSHOT_SHELF_UI2)
fun executeScreenshots_errorFromScreenshotController_reportsRequested() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
val onSaved = { _: Uri? -> }
- whenever(controller0.handleScreenshot(any(), any(), any()))
+ whenever(controller.handleScreenshot(any(), any(), any()))
.thenThrow(IllegalStateException::class.java)
- whenever(controller1.handleScreenshot(any(), any(), any()))
+ whenever(headlessHandler.handleScreenshot(any(), any(), any()))
.thenThrow(IllegalStateException::class.java)
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
@@ -449,14 +399,13 @@
}
@Test
- @DisableFlags(Flags.FLAG_SCREENSHOT_SHELF_UI2)
fun executeScreenshots_errorFromScreenshotController_reportsError() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
val onSaved = { _: Uri? -> }
- whenever(controller0.handleScreenshot(any(), any(), any()))
+ whenever(controller.handleScreenshot(any(), any(), any()))
.thenThrow(IllegalStateException::class.java)
- whenever(controller1.handleScreenshot(any(), any(), any()))
+ whenever(headlessHandler.handleScreenshot(any(), any(), any()))
.thenThrow(IllegalStateException::class.java)
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
@@ -470,14 +419,13 @@
}
@Test
- @DisableFlags(Flags.FLAG_SCREENSHOT_SHELF_UI2)
fun executeScreenshots_errorFromScreenshotController_showsErrorNotification() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
val onSaved = { _: Uri? -> }
- whenever(controller0.handleScreenshot(any(), any(), any()))
+ whenever(controller.handleScreenshot(any(), any(), any()))
.thenThrow(IllegalStateException::class.java)
- whenever(controller1.handleScreenshot(any(), any(), any()))
+ whenever(headlessHandler.handleScreenshot(any(), any(), any()))
.thenThrow(IllegalStateException::class.java)
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
@@ -496,7 +444,7 @@
assertThat(it).isNull()
onSavedCallCount += 1
}
- whenever(controller0.handleScreenshot(any(), any(), any())).thenAnswer {
+ whenever(controller.handleScreenshot(any(), any(), any())).thenAnswer {
(it.getArgument(1) as Consumer<Uri?>).accept(null)
}
@@ -525,6 +473,7 @@
var processed: ScreenshotData? = null
var toReturn: ScreenshotData? = null
var shouldThrowException = false
+
override suspend fun process(screenshot: ScreenshotData): ScreenshotData {
if (shouldThrowException) throw RequestProcessorException("")
processed = screenshot
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
index 3606b1b..c3e810e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
@@ -69,13 +69,13 @@
}
@Test
- fun chip_inCall_isShown() =
+ fun chip_inCall_isShownAsTimer() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 345, intent = null))
- assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
}
@Test
@@ -92,7 +92,8 @@
// started 2000ms ago (1000 - 3000). The OngoingActivityChipModel start time needs to be
// relative to elapsedRealtime, so it should be 2000ms before the elapsed realtime set
// on the clock.
- assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(398_000)
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs)
+ .isEqualTo(398_000)
}
@Test
@@ -127,7 +128,8 @@
// Start a call
repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 1000, intent = null))
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
- assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(398_000)
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs)
+ .isEqualTo(398_000)
// End the call
repo.setOngoingCallState(OngoingCallModel.NoCall)
@@ -140,20 +142,18 @@
// Start a new call, which started 1000ms ago
repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 102_000, intent = null))
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
- assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(499_000)
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs)
+ .isEqualTo(499_000)
}
@Test
- fun chip_inCall_nullIntent_clickListenerDoesNothing() =
+ fun chip_inCall_nullIntent_nullClickListener() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 1000, intent = null))
- val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListener
-
- clickListener.onClick(chipView)
- // Just verify nothing crashes
+ assertThat((latest as OngoingActivityChipModel.Shown).onClickListener).isNull()
}
@Test
@@ -164,8 +164,9 @@
val intent = mock<PendingIntent>()
repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 1000, intent = intent))
val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListener
+ assertThat(clickListener).isNotNull()
- clickListener.onClick(chipView)
+ clickListener!!.onClick(chipView)
verify(kosmos.activityStarter).postStartActivityDismissingKeyguard(intent, null)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
index d7935e5..bde668e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
@@ -91,7 +91,7 @@
}
@Test
- fun chip_singleTaskState_otherDevicesPackage_isShown() =
+ fun chip_singleTaskState_otherDevicesPackage_isShownAsTimer() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -101,20 +101,20 @@
createTask(taskId = 1),
)
- assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon = (latest as OngoingActivityChipModel.Shown).icon
assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_cast_connected)
}
@Test
- fun chip_entireScreenState_otherDevicesPackage_isShown() =
+ fun chip_entireScreenState_otherDevicesPackage_isShownAsTimer() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
mediaProjectionRepo.mediaProjectionState.value =
MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
- assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon = (latest as OngoingActivityChipModel.Shown).icon
assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_cast_connected)
}
@@ -162,7 +162,7 @@
MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
- assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(1234)
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(1234)
mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
@@ -175,7 +175,7 @@
)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
- assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(5678)
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(5678)
}
@Test
@@ -186,8 +186,9 @@
MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ assertThat(clickListener).isNotNull()
- clickListener.onClick(chipView)
+ clickListener!!.onClick(chipView)
verify(kosmos.mockDialogTransitionAnimator)
.showFromView(
eq(mockCastDialog),
@@ -209,8 +210,9 @@
)
val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ assertThat(clickListener).isNotNull()
- clickListener.onClick(chipView)
+ clickListener!!.onClick(chipView)
verify(kosmos.mockDialogTransitionAnimator)
.showFromView(
eq(mockCastDialog),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
index fdf0e5d..8e8b082 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
@@ -88,29 +88,75 @@
}
@Test
- fun chip_startingState_isHidden() =
+ fun chip_startingState_isShownAsCountdownWithoutIconOrClickListener() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(400)
- assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Countdown::class.java)
+ assertThat((latest as OngoingActivityChipModel.Shown).icon).isNull()
+ assertThat((latest as OngoingActivityChipModel.Shown).onClickListener).isNull()
+ }
+
+ // The millis we typically get from [ScreenRecordRepository] are around 2995, 1995, and 995.
+ @Test
+ fun chip_startingState_millis2995_is3() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(2995)
+
+ assertThat((latest as OngoingActivityChipModel.Shown.Countdown).secondsUntilStarted)
+ .isEqualTo(3)
}
@Test
- fun chip_recordingState_isShownWithIcon() =
+ fun chip_startingState_millis1995_is2() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(1995)
+
+ assertThat((latest as OngoingActivityChipModel.Shown.Countdown).secondsUntilStarted)
+ .isEqualTo(2)
+ }
+
+ @Test
+ fun chip_startingState_millis995_is1() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(995)
+
+ assertThat((latest as OngoingActivityChipModel.Shown.Countdown).secondsUntilStarted)
+ .isEqualTo(1)
+ }
+
+ @Test
+ fun chip_recordingState_isShownAsTimerWithIcon() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
- assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon = (latest as OngoingActivityChipModel.Shown).icon
assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_screenrecord)
}
@Test
- fun chip_colorsAreRed() =
+ fun chip_startingState_colorsAreRed() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(2000L)
+
+ assertThat((latest as OngoingActivityChipModel.Shown).colors).isEqualTo(ColorsModel.Red)
+ }
+
+ @Test
+ fun chip_recordingState_colorsAreRed() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -128,7 +174,7 @@
screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
- assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(1234)
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(1234)
screenRecordRepo.screenRecordState.value = ScreenRecordModel.DoingNothing
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
@@ -137,7 +183,7 @@
screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
- assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(5678)
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(5678)
}
@Test
@@ -148,8 +194,9 @@
mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting
val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ assertThat(clickListener).isNotNull()
- clickListener.onClick(chipView)
+ clickListener!!.onClick(chipView)
// EndScreenRecordingDialogDelegate will test that the dialog has the right message
verify(kosmos.mockDialogTransitionAnimator)
.showFromView(
@@ -169,8 +216,9 @@
MediaProjectionState.Projecting.EntireScreen("host.package")
val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ assertThat(clickListener).isNotNull()
- clickListener.onClick(chipView)
+ clickListener!!.onClick(chipView)
// EndScreenRecordingDialogDelegate will test that the dialog has the right message
verify(kosmos.mockDialogTransitionAnimator)
.showFromView(
@@ -193,8 +241,9 @@
)
val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ assertThat(clickListener).isNotNull()
- clickListener.onClick(chipView)
+ clickListener!!.onClick(chipView)
// EndScreenRecordingDialogDelegate will test that the dialog has the right message
verify(kosmos.mockDialogTransitionAnimator)
.showFromView(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
index 4c2546e..63c29ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
@@ -59,7 +59,7 @@
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
- verify(sysuiDialog).setIcon(R.drawable.ic_screenshot_share)
+ verify(sysuiDialog).setIcon(R.drawable.ic_present_to_all)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
index 8ea3f4a..2e5f7f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
@@ -116,7 +116,7 @@
}
@Test
- fun chip_singleTaskState_normalPackage_isShown() =
+ fun chip_singleTaskState_normalPackage_isShownAsTimer() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -126,22 +126,22 @@
createTask(taskId = 1),
)
- assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon = (latest as OngoingActivityChipModel.Shown).icon
- assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_screenshot_share)
+ assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_present_to_all)
}
@Test
- fun chip_entireScreenState_normalPackage_isShown() =
+ fun chip_entireScreenState_normalPackage_isShownAsTimer() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
mediaProjectionRepo.mediaProjectionState.value =
MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
- assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon = (latest as OngoingActivityChipModel.Shown).icon
- assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_screenshot_share)
+ assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_present_to_all)
}
@Test
@@ -165,7 +165,7 @@
MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
- assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(1234)
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(1234)
mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
@@ -178,7 +178,7 @@
)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
- assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(5678)
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(5678)
}
@Test
@@ -189,8 +189,9 @@
MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ assertThat(clickListener).isNotNull()
- clickListener.onClick(chipView)
+ clickListener!!.onClick(chipView)
verify(kosmos.mockDialogTransitionAnimator)
.showFromView(
eq(mockShareDialog),
@@ -211,8 +212,9 @@
)
val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ assertThat(clickListener).isNotNull()
- clickListener.onClick(chipView)
+ clickListener!!.onClick(chipView)
verify(kosmos.mockDialogTransitionAnimator)
.showFromView(
eq(mockShareDialog),
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 912a10a..8bc83cf 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
@@ -35,7 +35,11 @@
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
+import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -46,6 +50,7 @@
class OngoingActivityChipsViewModelTest : SysuiTestCase() {
private val kosmos = Kosmos().also { it.testCase = this }
private val testScope = kosmos.testScope
+ private val systemClock = kosmos.fakeSystemClock
private val screenRecordState = kosmos.screenRecordRepository.screenRecordState
private val mediaProjectionState = kosmos.fakeMediaProjectionRepository.mediaProjectionState
@@ -191,6 +196,39 @@
assertIsCallChip(latest)
}
+ /** Regression test for b/347726238. */
+ @Test
+ fun chip_timerDoesNotResetAfterSubscribersRestart() =
+ testScope.runTest {
+ var latest: OngoingActivityChipModel? = null
+
+ val job1 = underTest.chip.onEach { latest = it }.launchIn(this)
+
+ // Start a chip with a timer
+ systemClock.setElapsedRealtime(1234)
+ screenRecordState.value = ScreenRecordModel.Recording
+
+ runCurrent()
+
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(1234)
+
+ // Stop subscribing to the chip flow
+ job1.cancel()
+
+ // Let time pass
+ systemClock.setElapsedRealtime(5678)
+
+ // WHEN we re-subscribe to the chip flow
+ val job2 = underTest.chip.onEach { latest = it }.launchIn(this)
+
+ runCurrent()
+
+ // THEN the old start time is still used
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(1234)
+
+ job2.cancel()
+ }
+
companion object {
fun assertIsScreenRecordChip(latest: OngoingActivityChipModel?) {
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
@@ -201,7 +239,7 @@
fun assertIsShareToAppChip(latest: OngoingActivityChipModel?) {
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
val icon = (latest as OngoingActivityChipModel.Shown).icon
- assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_screenshot_share)
+ assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_present_to_all)
}
fun assertIsCallChip(latest: OngoingActivityChipModel?) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index f2f336c..dfee2ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
@@ -21,11 +21,14 @@
import android.app.admin.DevicePolicyResourcesManager
import android.content.SharedPreferences
import android.os.UserManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.telecom.TelecomManager
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
@@ -39,6 +42,7 @@
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.policy.BluetoothController
import com.android.systemui.statusbar.policy.CastController
+import com.android.systemui.statusbar.policy.CastDevice
import com.android.systemui.statusbar.policy.DataSaverController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.HotspotController
@@ -54,6 +58,7 @@
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.DateFormatUtil
import com.android.systemui.util.time.FakeSystemClock
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -78,6 +83,7 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.argumentCaptor
@RunWith(AndroidJUnit4::class)
@RunWithLooper
@@ -87,6 +93,8 @@
companion object {
private const val ALARM_SLOT = "alarm"
+ private const val CAST_SLOT = "cast"
+ private const val SCREEN_RECORD_SLOT = "screen_record"
private const val CONNECTED_DISPLAY_SLOT = "connected_display"
private const val MANAGED_PROFILE_SLOT = "managed_profile"
}
@@ -271,6 +279,101 @@
verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
}
+ @Test
+ @DisableFlags(Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ fun cast_chipsFlagOff_iconShown() {
+ statusBarPolicy.init()
+ clearInvocations(iconController)
+
+ val callbackCaptor = argumentCaptor<CastController.Callback>()
+ verify(castController).addCallback(callbackCaptor.capture())
+
+ whenever(castController.castDevices)
+ .thenReturn(
+ listOf(
+ CastDevice(
+ "id",
+ "name",
+ "description",
+ CastDevice.CastState.Connected,
+ CastDevice.CastOrigin.MediaProjection,
+ )
+ )
+ )
+ callbackCaptor.firstValue.onCastDevicesChanged()
+
+ verify(iconController).setIconVisibility(CAST_SLOT, true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ fun cast_chipsFlagOn_noCallbackRegistered() {
+ statusBarPolicy.init()
+
+ verify(castController, never()).addCallback(any())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ fun screenRecord_chipsFlagOff_iconShown_forAllStates() {
+ statusBarPolicy.init()
+ clearInvocations(iconController)
+
+ val callbackCaptor = argumentCaptor<RecordingController.RecordingStateChangeCallback>()
+ verify(recordingController).addCallback(callbackCaptor.capture())
+
+ callbackCaptor.firstValue.onCountdown(3000)
+ testableLooper.processAllMessages()
+ verify(iconController).setIconVisibility(SCREEN_RECORD_SLOT, true)
+ clearInvocations(iconController)
+
+ callbackCaptor.firstValue.onCountdownEnd()
+ testableLooper.processAllMessages()
+ verify(iconController).setIconVisibility(SCREEN_RECORD_SLOT, false)
+ clearInvocations(iconController)
+
+ callbackCaptor.firstValue.onRecordingStart()
+ testableLooper.processAllMessages()
+ verify(iconController).setIconVisibility(SCREEN_RECORD_SLOT, true)
+ clearInvocations(iconController)
+
+ callbackCaptor.firstValue.onRecordingEnd()
+ testableLooper.processAllMessages()
+ verify(iconController).setIconVisibility(SCREEN_RECORD_SLOT, false)
+ clearInvocations(iconController)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ fun screenRecord_chipsFlagOn_noCallbackRegistered() {
+ statusBarPolicy.init()
+
+ verify(recordingController, never()).addCallback(any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ fun screenRecord_chipsFlagOn_methodsDoNothing() {
+ statusBarPolicy.init()
+ clearInvocations(iconController)
+
+ statusBarPolicy.onCountdown(3000)
+ testableLooper.processAllMessages()
+ verify(iconController, never()).setIconVisibility(eq(SCREEN_RECORD_SLOT), any())
+
+ statusBarPolicy.onCountdownEnd()
+ testableLooper.processAllMessages()
+ verify(iconController, never()).setIconVisibility(eq(SCREEN_RECORD_SLOT), any())
+
+ statusBarPolicy.onRecordingStart()
+ testableLooper.processAllMessages()
+ verify(iconController, never()).setIconVisibility(eq(SCREEN_RECORD_SLOT), any())
+
+ statusBarPolicy.onRecordingEnd()
+ testableLooper.processAllMessages()
+ verify(iconController, never()).setIconVisibility(eq(SCREEN_RECORD_SLOT), any())
+ }
+
private fun createAlarmInfo(): AlarmManager.AlarmClockInfo {
return AlarmManager.AlarmClockInfo(10L, null)
}
@@ -315,13 +418,18 @@
private class FakeConnectedDisplayStateProvider : ConnectedDisplayInteractor {
private val flow = MutableSharedFlow<State>()
+
suspend fun emit(value: State) = flow.emit(value)
+
override val connectedDisplayState: Flow<State>
get() = flow
+
override val connectedDisplayAddition: Flow<Unit>
get() = TODO("Not yet implemented")
+
override val pendingDisplay: Flow<PendingDisplay?>
get() = TODO("Not yet implemented")
+
override val concurrentDisplaysInProgress: Flow<Boolean>
get() = TODO("Not yet implemented")
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index 9dae44d..5db9d31 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui
import android.app.ActivityManager
+import android.app.DreamManager
import android.app.admin.DevicePolicyManager
import android.app.trust.TrustManager
import android.os.UserManager
@@ -32,6 +33,7 @@
import com.android.systemui.biometrics.AuthController
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.ScreenLifecycle
@@ -93,6 +95,7 @@
@get:Provides val demoModeController: DemoModeController = mock(),
@get:Provides val deviceProvisionedController: DeviceProvisionedController = mock(),
@get:Provides val dozeParameters: DozeParameters = mock(),
+ @get:Provides val dreamManager: DreamManager = mock(),
@get:Provides val dumpManager: DumpManager = mock(),
@get:Provides val headsUpManager: HeadsUpManager = mock(),
@get:Provides val guestResumeSessionReceiver: GuestResumeSessionReceiver = mock(),
@@ -130,6 +133,7 @@
@get:Provides val systemUIDialogManager: SystemUIDialogManager = mock(),
@get:Provides val deviceEntryIconTransitions: Set<DeviceEntryIconTransition> = emptySet(),
@get:Provides val communalInteractor: CommunalInteractor = mock(),
+ @get:Provides val communalSceneInteractor: CommunalSceneInteractor = mock(),
@get:Provides val sceneLogger: SceneLogger = mock(),
@get:Provides val trustManager: TrustManager = mock(),
@get:Provides val primaryBouncerInteractor: PrimaryBouncerInteractor = mock(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
index edf77a0..744b127 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
@@ -16,7 +16,9 @@
package com.android.systemui.keyguard.domain.interactor
+import android.service.dream.dreamManager
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
@@ -35,8 +37,10 @@
mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
communalInteractor = communalInteractor,
+ communalSceneInteractor = communalSceneInteractor,
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
deviceEntryRepository = deviceEntryRepository,
+ dreamManager = dreamManager
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt
index 141f242..83adc79 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt
@@ -18,9 +18,11 @@
import android.annotation.SuppressLint
import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothProfile
import android.graphics.drawable.Drawable
import android.graphics.drawable.TestStubDrawable
import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LeAudioProfile
import com.android.settingslib.media.BluetoothMediaDevice
import com.android.settingslib.media.MediaDevice
import com.android.settingslib.media.PhoneMediaDevice
@@ -59,11 +61,17 @@
whenever(name).thenReturn(deviceName)
whenever(address).thenReturn(deviceAddress)
}
+ val leAudioProfile =
+ mock<LeAudioProfile> {
+ whenever(profileId).thenReturn(BluetoothProfile.LE_AUDIO)
+ whenever(isEnabled(bluetoothDevice)).thenReturn(true)
+ }
val cachedBluetoothDevice: CachedBluetoothDevice = mock {
whenever(isHearingAidDevice).thenReturn(true)
whenever(address).thenReturn(deviceAddress)
whenever(device).thenReturn(bluetoothDevice)
whenever(name).thenReturn(deviceName)
+ whenever(profiles).thenReturn(listOf(leAudioProfile))
}
return mock<BluetoothMediaDevice> {
whenever(name).thenReturn(deviceName)
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 4f9db8b..acd80ee 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -47,6 +47,11 @@
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
import static com.android.internal.accessibility.common.ShortcutConstants.USER_SHORTCUT_TYPES;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TRIPLETAP;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TWOFINGER_DOUBLETAP;
import static com.android.internal.accessibility.util.AccessibilityStatsLogUtils.logAccessibilityShortcutActivated;
import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
@@ -923,25 +928,11 @@
newValue, restoredFromSdk);
}
}
- case Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS -> {
- synchronized (mLock) {
- restoreAccessibilityButtonTargetsLocked(
- previousValue, newValue);
- }
- }
- case Settings.Secure.ACCESSIBILITY_QS_TARGETS -> {
- if (!android.view.accessibility.Flags.a11yQsShortcut()) {
- return;
- }
- restoreAccessibilityQsTargets(newValue);
- }
- case Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE -> {
- if (!android.view.accessibility.Flags
- .restoreA11yShortcutTargetService()) {
- return;
- }
- restoreAccessibilityShortcutTargetService(previousValue, newValue);
- }
+ case Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+ Settings.Secure.ACCESSIBILITY_QS_TARGETS,
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE ->
+ restoreShortcutTargets(newValue,
+ ShortcutUtils.convertToType(which));
}
}
}
@@ -1040,7 +1031,7 @@
}
persistColonDelimitedSetToSettingLocked(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
userState.mUserId, targetsFromSetting, str -> str);
- readAccessibilityButtonTargetsLocked(userState);
+ readAccessibilityShortcutTargetsLocked(userState, SOFTWARE);
onUserStateChangedLocked(userState);
}
@@ -1720,12 +1711,12 @@
}
// Turn on/off a11y qs shortcut for the a11y features based on the change in QS Panel
if (!a11yFeaturesToEnable.isEmpty()) {
- enableShortcutForTargets(/* enable= */ true, UserShortcutType.QUICK_SETTINGS,
+ enableShortcutForTargets(/* enable= */ true, QUICK_SETTINGS,
a11yFeaturesToEnable, userId);
}
if (!a11yFeaturesToRemove.isEmpty()) {
- enableShortcutForTargets(/* enable= */ false, UserShortcutType.QUICK_SETTINGS,
+ enableShortcutForTargets(/* enable= */ false, QUICK_SETTINGS,
a11yFeaturesToRemove, userId);
}
}
@@ -2057,100 +2048,78 @@
}
/**
- * User could enable accessibility services and configure accessibility button during the SUW.
- * Merges current value of accessibility button settings into the restored one to make sure
- * user's preferences of accessibility button updated in SUW are not lost.
- *
- * Called only during settings restore; currently supports only the owner user
- * TODO: http://b/22388012
- */
- void restoreAccessibilityButtonTargetsLocked(String oldSetting, String newSetting) {
- final Set<String> targetsFromSetting = new ArraySet<>();
- readColonDelimitedStringToSet(oldSetting, str -> str, targetsFromSetting,
- /* doMerge = */false);
- readColonDelimitedStringToSet(newSetting, str -> str, targetsFromSetting,
- /* doMerge = */true);
-
- final AccessibilityUserState userState = getUserStateLocked(UserHandle.USER_SYSTEM);
- userState.mAccessibilityButtonTargets.clear();
- userState.mAccessibilityButtonTargets.addAll(targetsFromSetting);
- persistColonDelimitedSetToSettingLocked(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
- UserHandle.USER_SYSTEM, userState.mAccessibilityButtonTargets, str -> str);
-
- scheduleNotifyClientsOfServicesStateChangeLocked(userState);
- onUserStateChangedLocked(userState);
- }
-
- /**
* User could configure accessibility shortcut during the SUW before restoring user data.
* Merges the current value and the new value to make sure we don't lost the setting the user's
- * preferences of accessibility qs shortcut updated in SUW are not lost.
- *
- * Called only during settings restore; currently supports only the owner user
+ * preferences of accessibility shortcut updated in SUW are not lost.
+ * Called only during settings restore; currently supports only the owner user.
+ * <P>
+ * Throws an exception if used with {@code TRIPLETAP} or {@code TWOFINGER_DOUBLETAP}.
+ * </P>
* TODO: http://b/22388012
*/
- private void restoreAccessibilityQsTargets(String newValue) {
+ private void restoreShortcutTargets(String newValue,
+ @UserShortcutType int shortcutType) {
+ assertNoTapShortcut(shortcutType);
+ if (shortcutType == QUICK_SETTINGS && !android.view.accessibility.Flags.a11yQsShortcut()) {
+ return;
+ }
+ if (shortcutType == HARDWARE
+ && !android.view.accessibility.Flags.restoreA11yShortcutTargetService()) {
+ return;
+ }
+
synchronized (mLock) {
final AccessibilityUserState userState = getUserStateLocked(UserHandle.USER_SYSTEM);
- final Set<String> mergedTargets = userState.getA11yQsTargets();
- readColonDelimitedStringToSet(newValue, str -> str, mergedTargets,
- /* doMerge = */ true);
+ final Set<String> mergedTargets = (shortcutType == HARDWARE)
+ ? new ArraySet<>(ShortcutUtils.getShortcutTargetsFromSettings(
+ mContext, shortcutType, userState.mUserId))
+ : userState.getShortcutTargetsLocked(shortcutType);
- userState.updateA11yQsTargetLocked(mergedTargets);
- persistColonDelimitedSetToSettingLocked(Settings.Secure.ACCESSIBILITY_QS_TARGETS,
+ // If dealing with the hardware shortcut,
+ // remove the default service if it wasn't present before restore,
+ // but only if the raw shortcut setting is not null (edge case during SUW).
+ // Otherwise, merge the old and new targets normally.
+ if (Flags.clearDefaultFromA11yShortcutTargetServiceRestore()
+ && shortcutType == HARDWARE) {
+ final String defaultService =
+ mContext.getString(R.string.config_defaultAccessibilityService);
+ final ComponentName defaultServiceComponent = TextUtils.isEmpty(defaultService)
+ ? null : ComponentName.unflattenFromString(defaultService);
+ boolean shouldClearDefaultService = defaultServiceComponent != null
+ && !stringSetContainsComponentName(mergedTargets, defaultServiceComponent);
+ readColonDelimitedStringToSet(newValue, str -> str,
+ mergedTargets, /*doMerge=*/true);
+
+ if (shouldClearDefaultService && stringSetContainsComponentName(
+ mergedTargets, defaultServiceComponent)) {
+ Slog.i(LOG_TAG, "Removing default service " + defaultService
+ + " from restore of "
+ + Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
+ mergedTargets.removeIf(str ->
+ defaultServiceComponent.equals(ComponentName.unflattenFromString(str)));
+ }
+ if (mergedTargets.isEmpty()) {
+ return;
+ }
+ } else {
+ readColonDelimitedStringToSet(newValue, str -> str, mergedTargets,
+ /* doMerge = */ true);
+ }
+
+ userState.updateShortcutTargetsLocked(mergedTargets, shortcutType);
+ persistColonDelimitedSetToSettingLocked(ShortcutUtils.convertToKey(shortcutType),
UserHandle.USER_SYSTEM, mergedTargets, str -> str);
scheduleNotifyClientsOfServicesStateChangeLocked(userState);
onUserStateChangedLocked(userState);
}
}
- /**
- * Merges the old and restored value of
- * {@link Settings.Secure#ACCESSIBILITY_SHORTCUT_TARGET_SERVICE}.
- *
- * <p>Also clears out {@link R.string#config_defaultAccessibilityService} from
- * the merged set if it was not present before restoring.
- */
- private void restoreAccessibilityShortcutTargetService(
- String oldValue, String restoredValue) {
- final Set<String> targetsFromSetting = new ArraySet<>();
- readColonDelimitedStringToSet(oldValue, str -> str,
- targetsFromSetting, /*doMerge=*/false);
- final String defaultService =
- mContext.getString(R.string.config_defaultAccessibilityService);
- final ComponentName defaultServiceComponent = TextUtils.isEmpty(defaultService)
- ? null : ComponentName.unflattenFromString(defaultService);
- boolean shouldClearDefaultService = defaultServiceComponent != null
- && !stringSetContainsComponentName(targetsFromSetting, defaultServiceComponent);
- readColonDelimitedStringToSet(restoredValue, str -> str,
- targetsFromSetting, /*doMerge=*/true);
- if (Flags.clearDefaultFromA11yShortcutTargetServiceRestore()) {
- if (shouldClearDefaultService && stringSetContainsComponentName(
- targetsFromSetting, defaultServiceComponent)) {
- Slog.i(LOG_TAG, "Removing default service " + defaultService
- + " from restore of "
- + Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
- targetsFromSetting.removeIf(str ->
- defaultServiceComponent.equals(ComponentName.unflattenFromString(str)));
- }
- if (targetsFromSetting.isEmpty()) {
- return;
- }
- }
- synchronized (mLock) {
- final AccessibilityUserState userState = getUserStateLocked(UserHandle.USER_SYSTEM);
- final Set<String> shortcutTargets =
- userState.getShortcutTargetsLocked(UserShortcutType.HARDWARE);
- shortcutTargets.clear();
- shortcutTargets.addAll(targetsFromSetting);
- persistColonDelimitedSetToSettingLocked(
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
- UserHandle.USER_SYSTEM, targetsFromSetting, str -> str);
- scheduleNotifyClientsOfServicesStateChangeLocked(userState);
- onUserStateChangedLocked(userState);
- }
+ private String getRawShortcutSetting(int userId, @UserShortcutType int shortcutType) {
+ return Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ ShortcutUtils.convertToKey(shortcutType), userId);
}
+
/**
* Returns {@code true} if the set contains the provided non-null {@link ComponentName}.
*
@@ -2263,7 +2232,7 @@
private void showAccessibilityTargetsSelection(int displayId,
@UserShortcutType int shortcutType) {
final Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
- final String chooserClassName = (shortcutType == UserShortcutType.HARDWARE)
+ final String chooserClassName = (shortcutType == HARDWARE)
? AccessibilityShortcutChooserActivity.class.getName()
: AccessibilityButtonChooserActivity.class.getName();
intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
@@ -3236,9 +3205,9 @@
somethingChanged |= readAudioDescriptionEnabledSettingLocked(userState);
somethingChanged |= readMagnificationEnabledSettingsLocked(userState);
somethingChanged |= readAutoclickEnabledSettingLocked(userState);
- somethingChanged |= readAccessibilityShortcutKeySettingLocked(userState);
- somethingChanged |= readAccessibilityQsTargetsLocked(userState);
- somethingChanged |= readAccessibilityButtonTargetsLocked(userState);
+ somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, HARDWARE);
+ somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, QUICK_SETTINGS);
+ somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, SOFTWARE);
somethingChanged |= readAccessibilityButtonTargetComponentLocked(userState);
somethingChanged |= readUserRecommendedUiTimeoutSettingsLocked(userState);
somethingChanged |= readMagnificationModeForDefaultDisplayLocked(userState);
@@ -3386,60 +3355,34 @@
userState.setSendMotionEventsEnabled(sendMotionEvents);
}
- private boolean readAccessibilityShortcutKeySettingLocked(AccessibilityUserState userState) {
- final String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, userState.mUserId);
+ /**
+ * Throws an exception for {@code TRIPLETAP} or {@code TWOFINGER_DOUBLETAP} types.
+ */
+ private boolean readAccessibilityShortcutTargetsLocked(AccessibilityUserState userState,
+ @UserShortcutType int shortcutType) {
+ assertNoTapShortcut(shortcutType);
+ final String settingValue = getRawShortcutSetting(userState.mUserId, shortcutType);
final Set<String> targetsFromSetting = new ArraySet<>();
- readColonDelimitedStringToSet(settingValue, str -> str, targetsFromSetting, false);
- // Fall back to device's default a11y service, only when setting is never updated.
- if (settingValue == null) {
+ // If dealing with an empty hardware shortcut, fall back to the default value.
+ if (shortcutType == HARDWARE && settingValue == null) {
final String defaultService = mContext.getString(
R.string.config_defaultAccessibilityService);
if (!TextUtils.isEmpty(defaultService)) {
- targetsFromSetting.add(defaultService);
+ // Convert to component name to reformat the target if it has a relative path.
+ ComponentName name = ComponentName.unflattenFromString(defaultService);
+ if (name != null) {
+ targetsFromSetting.add(name.flattenToString());
+ }
}
+ } else {
+ readColonDelimitedStringToSet(settingValue, str -> str, targetsFromSetting, false);
}
- final Set<String> currentTargets =
- userState.getShortcutTargetsLocked(UserShortcutType.HARDWARE);
- if (targetsFromSetting.equals(currentTargets)) {
- return false;
+ if (userState.updateShortcutTargetsLocked(targetsFromSetting, shortcutType)) {
+ scheduleNotifyClientsOfServicesStateChangeLocked(userState);
+ return true;
}
- currentTargets.clear();
- currentTargets.addAll(targetsFromSetting);
- scheduleNotifyClientsOfServicesStateChangeLocked(userState);
- return true;
- }
-
- private boolean readAccessibilityQsTargetsLocked(AccessibilityUserState userState) {
- final Set<String> targetsFromSetting = new ArraySet<>();
- readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_QS_TARGETS,
- userState.mUserId, str -> str, targetsFromSetting);
-
- final Set<String> currentTargets =
- userState.getShortcutTargetsLocked(UserShortcutType.QUICK_SETTINGS);
- if (targetsFromSetting.equals(currentTargets)) {
- return false;
- }
- userState.updateA11yQsTargetLocked(targetsFromSetting);
- scheduleNotifyClientsOfServicesStateChangeLocked(userState);
- return true;
- }
-
- private boolean readAccessibilityButtonTargetsLocked(AccessibilityUserState userState) {
- final Set<String> targetsFromSetting = new ArraySet<>();
- readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
- userState.mUserId, str -> str, targetsFromSetting);
-
- final Set<String> currentTargets =
- userState.getShortcutTargetsLocked(UserShortcutType.SOFTWARE);
- if (targetsFromSetting.equals(currentTargets)) {
- return false;
- }
- currentTargets.clear();
- currentTargets.addAll(targetsFromSetting);
- scheduleNotifyClientsOfServicesStateChangeLocked(userState);
- return true;
+ return false;
}
private boolean readAccessibilityButtonTargetComponentLocked(AccessibilityUserState userState) {
@@ -3487,14 +3430,10 @@
*/
private void updateAccessibilityShortcutKeyTargetsLocked(AccessibilityUserState userState) {
final Set<String> currentTargets =
- userState.getShortcutTargetsLocked(UserShortcutType.HARDWARE);
- final int lastSize = currentTargets.size();
- if (lastSize == 0) {
- return;
- }
+ userState.getShortcutTargetsLocked(HARDWARE);
currentTargets.removeIf(
name -> !userState.isShortcutTargetInstalledLocked(name));
- if (lastSize == currentTargets.size()) {
+ if (!userState.updateShortcutTargetsLocked(currentTargets, HARDWARE)) {
return;
}
@@ -3680,13 +3619,9 @@
final Set<String> currentTargets =
userState.getShortcutTargetsLocked(UserShortcutType.SOFTWARE);
- final int lastSize = currentTargets.size();
- if (lastSize == 0) {
- return;
- }
currentTargets.removeIf(
name -> !userState.isShortcutTargetInstalledLocked(name));
- if (lastSize == currentTargets.size()) {
+ if (!userState.updateShortcutTargetsLocked(currentTargets, SOFTWARE)) {
return;
}
@@ -3719,8 +3654,7 @@
return;
}
final Set<String> buttonTargets =
- userState.getShortcutTargetsLocked(UserShortcutType.SOFTWARE);
- int lastSize = buttonTargets.size();
+ userState.getShortcutTargetsLocked(SOFTWARE);
buttonTargets.removeIf(name -> {
if (packageName != null && name != null && !name.contains(packageName)) {
return false;
@@ -3752,13 +3686,11 @@
}
return false;
});
- boolean changed = (lastSize != buttonTargets.size());
- lastSize = buttonTargets.size();
final Set<String> shortcutKeyTargets =
- userState.getShortcutTargetsLocked(UserShortcutType.HARDWARE);
+ userState.getShortcutTargetsLocked(HARDWARE);
final Set<String> qsShortcutTargets =
- userState.getShortcutTargetsLocked(UserShortcutType.QUICK_SETTINGS);
+ userState.getShortcutTargetsLocked(QUICK_SETTINGS);
userState.mEnabledServices.forEach(componentName -> {
if (packageName != null && componentName != null
&& !packageName.equals(componentName.getPackageName())) {
@@ -3790,8 +3722,7 @@
+ " should be assign to the button or shortcut.");
buttonTargets.add(serviceName);
});
- changed |= (lastSize != buttonTargets.size());
- if (!changed) {
+ if (!userState.updateShortcutTargetsLocked(buttonTargets, SOFTWARE)) {
return;
}
@@ -3815,10 +3746,10 @@
}
final Set<String> targets =
- userState.getShortcutTargetsLocked(UserShortcutType.QUICK_SETTINGS);
+ userState.getShortcutTargetsLocked(QUICK_SETTINGS);
// Removes the targets that are no longer installed on the device.
- boolean somethingChanged = targets.removeIf(
+ targets.removeIf(
name -> !userState.isShortcutTargetInstalledLocked(name));
// Add the target if the a11y service is enabled and the tile exist in QS panel
Set<ComponentName> enabledServices = userState.getEnabledServicesLocked();
@@ -3829,14 +3760,13 @@
ComponentName tileService =
a11yFeatureToTileService.getOrDefault(enabledService, null);
if (tileService != null && currentA11yTilesInQsPanel.contains(tileService)) {
- somethingChanged |= targets.add(enabledService.flattenToString());
+ targets.add(enabledService.flattenToString());
}
}
- if (!somethingChanged) {
+ if (!userState.updateShortcutTargetsLocked(targets, QUICK_SETTINGS)) {
return;
}
- userState.updateA11yQsTargetLocked(targets);
// Update setting key with new value.
persistColonDelimitedSetToSettingLocked(
@@ -3862,14 +3792,14 @@
final List<Pair<Integer, String>> shortcutTypeAndShortcutSetting = new ArrayList<>(3);
shortcutTypeAndShortcutSetting.add(
- new Pair<>(UserShortcutType.HARDWARE,
+ new Pair<>(HARDWARE,
Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE));
shortcutTypeAndShortcutSetting.add(
new Pair<>(UserShortcutType.SOFTWARE,
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS));
if (android.view.accessibility.Flags.a11yQsShortcut()) {
shortcutTypeAndShortcutSetting.add(
- new Pair<>(UserShortcutType.QUICK_SETTINGS,
+ new Pair<>(QUICK_SETTINGS,
Settings.Secure.ACCESSIBILITY_QS_TARGETS));
}
@@ -3883,7 +3813,7 @@
shortcutSettingName,
userState.mUserId, currentTargets, str -> str);
- if (shortcutType != UserShortcutType.QUICK_SETTINGS) {
+ if (shortcutType != QUICK_SETTINGS) {
continue;
}
@@ -3968,7 +3898,7 @@
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::performAccessibilityShortcutInternal, this,
- Display.DEFAULT_DISPLAY, UserShortcutType.HARDWARE, targetName));
+ Display.DEFAULT_DISPLAY, HARDWARE, targetName));
}
/**
@@ -4115,7 +4045,7 @@
final boolean requestA11yButton = (installedServiceInfo.flags
& FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
// Turns on / off the accessibility service
- if ((targetSdk <= Build.VERSION_CODES.Q && shortcutType == UserShortcutType.HARDWARE)
+ if ((targetSdk <= Build.VERSION_CODES.Q && shortcutType == HARDWARE)
|| (targetSdk > Build.VERSION_CODES.Q && !requestA11yButton)) {
if (serviceConnection == null) {
logAccessibilityShortcutActivated(mContext, assignedTarget, shortcutType,
@@ -4129,7 +4059,7 @@
}
return true;
}
- if (shortcutType == UserShortcutType.HARDWARE && targetSdk > Build.VERSION_CODES.Q
+ if (shortcutType == HARDWARE && targetSdk > Build.VERSION_CODES.Q
&& requestA11yButton) {
if (!userState.getEnabledServicesLocked().contains(assignedTarget)) {
enableAccessibilityServiceLocked(assignedTarget, mCurrentUserId);
@@ -4222,7 +4152,7 @@
validNewTargets = newTargets;
// filter out targets that doesn't have qs shortcut
- if (shortcutType == UserShortcutType.QUICK_SETTINGS) {
+ if (shortcutType == QUICK_SETTINGS) {
validNewTargets = newTargets.stream().filter(target -> {
ComponentName targetComponent = ComponentName.unflattenFromString(target);
return featureToTileMap.containsKey(targetComponent);
@@ -4240,10 +4170,10 @@
/* defaultEmptyString= */ ""
);
- if (shortcutType == UserShortcutType.QUICK_SETTINGS) {
+ if (shortcutType == QUICK_SETTINGS) {
int numOfFeatureChanged = Math.abs(currentTargets.size() - validNewTargets.size());
logMetricForQsShortcutConfiguration(enable, numOfFeatureChanged);
- userState.updateA11yQsTargetLocked(validNewTargets);
+ userState.updateShortcutTargetsLocked(validNewTargets, QUICK_SETTINGS);
scheduleNotifyClientsOfServicesStateChangeLocked(userState);
onUserStateChangedLocked(userState);
}
@@ -4257,7 +4187,7 @@
}
// Add or Remove tile in QS Panel
- if (shortcutType == UserShortcutType.QUICK_SETTINGS) {
+ if (shortcutType == QUICK_SETTINGS) {
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::updateA11yTileServicesInQuickSettingsPanel,
this, validNewTargets, currentTargets, userId));
@@ -4266,7 +4196,7 @@
if (!enable) {
return;
}
- if (shortcutType == UserShortcutType.HARDWARE) {
+ if (shortcutType == HARDWARE) {
skipVolumeShortcutDialogTimeoutRestriction(userId);
if (com.android.server.accessibility.Flags.enableHardwareShortcutDisablesWarning()) {
persistIntToSetting(
@@ -4461,6 +4391,7 @@
shortcutTargets.add(serviceName);
}
}
+ userState.updateShortcutTargetsLocked(Set.copyOf(shortcutTargets), shortcutType);
return shortcutTargets;
}
}
@@ -5672,7 +5603,7 @@
|| mShowImeWithHardKeyboardUri.equals(uri)) {
userState.reconcileSoftKeyboardModeWithSettingsLocked();
} else if (mAccessibilityShortcutServiceIdUri.equals(uri)) {
- if (readAccessibilityShortcutKeySettingLocked(userState)) {
+ if (readAccessibilityShortcutTargetsLocked(userState, HARDWARE)) {
onUserStateChangedLocked(userState);
}
} else if (mAccessibilityButtonComponentIdUri.equals(uri)) {
@@ -5680,7 +5611,7 @@
onUserStateChangedLocked(userState);
}
} else if (mAccessibilityButtonTargetsUri.equals(uri)) {
- if (readAccessibilityButtonTargetsLocked(userState)) {
+ if (readAccessibilityShortcutTargetsLocked(userState, SOFTWARE)) {
onUserStateChangedLocked(userState);
}
} else if (mUserNonInteractiveUiTimeoutUri.equals(uri)
@@ -6505,4 +6436,10 @@
String metricId = enable ? METRIC_ID_QS_SHORTCUT_ADD : METRIC_ID_QS_SHORTCUT_REMOVE;
Counter.logIncrementWithUid(metricId, Binder.getCallingUid(), numOfFeatures);
}
+
+ private void assertNoTapShortcut(@UserShortcutType int shortcutType) {
+ if ((shortcutType & (TRIPLETAP | TWOFINGER_DOUBLETAP)) != 0) {
+ throw new IllegalArgumentException("Tap shortcuts are not supported.");
+ }
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index a37a184..de1c86a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -777,12 +777,15 @@
* @return The array set of the strings
*/
public ArraySet<String> getShortcutTargetsLocked(@UserShortcutType int shortcutType) {
+ return new ArraySet<>(getShortcutTargetsInternalLocked(shortcutType));
+ }
+ private ArraySet<String> getShortcutTargetsInternalLocked(@UserShortcutType int shortcutType) {
if (shortcutType == UserShortcutType.HARDWARE) {
return mAccessibilityShortcutKeyTargets;
} else if (shortcutType == UserShortcutType.SOFTWARE) {
return mAccessibilityButtonTargets;
} else if (shortcutType == UserShortcutType.QUICK_SETTINGS) {
- return getA11yQsTargets();
+ return mAccessibilityQsTargets;
} else if ((shortcutType == UserShortcutType.TRIPLETAP
&& isMagnificationSingleFingerTripleTapEnabledLocked()) || (
shortcutType == UserShortcutType.TWOFINGER_DOUBLETAP
@@ -795,6 +798,32 @@
}
/**
+ * Updates the corresponding shortcut targets with the provided set.
+ * Tap shortcuts don't operate using sets of targets,
+ * so trying to update {@code TRIPLETAP} or {@code TWOFINGER_DOUBLETAP}
+ * will instead throw an {@code IllegalArgumentException}
+ * @param newTargets set of targets to replace the existing set.
+ * @param shortcutType type to be replaced.
+ * @return {@code true} if the set was changed, or {@code false} if the elements are the same.
+ * @throws IllegalArgumentException if {@code TRIPLETAP} or {@code TWOFINGER_DOUBLETAP} is used.
+ */
+ boolean updateShortcutTargetsLocked(
+ Set<String> newTargets, @UserShortcutType int shortcutType) {
+ final int mask = UserShortcutType.TRIPLETAP | UserShortcutType.TWOFINGER_DOUBLETAP;
+ if ((shortcutType & mask) != 0) {
+ throw new IllegalArgumentException("Tap shortcuts cannot be updated with target sets.");
+ }
+
+ final Set<String> currentTargets = getShortcutTargetsInternalLocked(shortcutType);
+ if (newTargets.equals(currentTargets)) {
+ return false;
+ }
+ currentTargets.clear();
+ currentTargets.addAll(newTargets);
+ return true;
+ }
+
+ /**
* Whether or not the given shortcut target is installed in device.
*
* @param name The shortcut target name
@@ -844,8 +873,9 @@
);
}
- Set<String> targets = getShortcutTargetsLocked(shortcutType);
- boolean result = targets.removeIf(name -> {
+ // getting internal set lets us directly modify targets, as it's not a copy.
+ Set<String> targets = getShortcutTargetsInternalLocked(shortcutType);
+ return targets.removeIf(name -> {
ComponentName componentName;
if (name == null
|| (componentName = ComponentName.unflattenFromString(name)) == null) {
@@ -853,11 +883,6 @@
}
return componentName.equals(target);
});
- if (shortcutType == UserShortcutType.QUICK_SETTINGS) {
- updateA11yQsTargetLocked(targets);
- }
-
- return result;
}
/**
@@ -1114,11 +1139,6 @@
);
}
- public void updateA11yQsTargetLocked(Set<String> targets) {
- mAccessibilityQsTargets.clear();
- mAccessibilityQsTargets.addAll(targets);
- }
-
/**
* Returns a copy of the targets which has qs shortcut turned on
*/
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 195e94b..45ab62d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -730,7 +730,7 @@
/** Whether some specified important processes are allowed to use FIFO priority. */
boolean mAllowSpecifiedFifoScheduling = true;
- @GuardedBy("this")
+ @GuardedBy("mStrictModeCallbacks")
private final SparseArray<IUnsafeIntentStrictModeCallback>
mStrictModeCallbacks = new SparseArray<>();
@@ -9535,18 +9535,20 @@
* @param callback The binder used to communicate the violations.
*/
@Override
- public synchronized void registerStrictModeCallback(IBinder callback) {
- int callingPid = Binder.getCallingPid();
- mStrictModeCallbacks.put(callingPid,
- IUnsafeIntentStrictModeCallback.Stub.asInterface(callback));
- try {
- callback.linkToDeath(() -> {
- synchronized (ActivityManagerService.this) {
- mStrictModeCallbacks.remove(callingPid);
- }
- }, 0);
- } catch (RemoteException e) {
- mStrictModeCallbacks.remove(callingPid);
+ public void registerStrictModeCallback(IBinder callback) {
+ final int callingPid = Binder.getCallingPid();
+ synchronized (mStrictModeCallbacks) {
+ mStrictModeCallbacks.put(callingPid,
+ IUnsafeIntentStrictModeCallback.Stub.asInterface(callback));
+ try {
+ callback.linkToDeath(() -> {
+ synchronized (mStrictModeCallbacks) {
+ mStrictModeCallbacks.remove(callingPid);
+ }
+ }, 0);
+ } catch (RemoteException e) {
+ mStrictModeCallbacks.remove(callingPid);
+ }
}
}
@@ -19907,7 +19909,7 @@
public void triggerUnsafeIntentStrictMode(int callingPid, int type, Intent intent) {
final IUnsafeIntentStrictModeCallback callback;
final Intent i = intent.cloneFilter();
- synchronized (ActivityManagerService.this) {
+ synchronized (mStrictModeCallbacks) {
callback = mStrictModeCallbacks.get(callingPid);
}
if (callback != null) {
@@ -19915,7 +19917,7 @@
try {
callback.onUnsafeIntent(type, i);
} catch (RemoteException e) {
- synchronized (ActivityManagerService.this) {
+ synchronized (mStrictModeCallbacks) {
mStrictModeCallbacks.remove(callingPid);
}
}
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index 0fef55d..a188e79 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -84,6 +84,14 @@
}
@Override
+ public void cancelBlockScreenOn() {
+ if (mDisplayOffloader == null) {
+ return;
+ }
+ mDisplayOffloader.cancelBlockScreenOn();
+ }
+
+ @Override
public float[] getAutoBrightnessLevels(int mode) {
if (mode < 0 || mode > AUTO_BRIGHTNESS_MODE_MAX) {
throw new IllegalArgumentException("Unknown auto-brightness mode: " + mode);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index d14f2a0..c298bbf 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -2110,6 +2110,17 @@
Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0);
}
+ private void cancelUnblockScreenOnByDisplayOffload() {
+ if (mDisplayOffloadSession == null) {
+ return;
+ }
+ if (mPendingScreenOnUnblockerByDisplayOffload != null) {
+ // Already unblocked.
+ return;
+ }
+ mDisplayOffloadSession.cancelBlockScreenOn();
+ }
+
private boolean setScreenState(int state, @Display.StateReason int reason) {
return setScreenState(state, reason, false /*reportOnly*/);
}
@@ -2126,6 +2137,9 @@
blockScreenOnByDisplayOffload(mDisplayOffloadSession);
} else if (!isOn && mScreenTurningOnWasBlockedByDisplayOffload) {
// No longer turning screen on, so unblock previous screen on blocking immediately.
+ if (mFlags.isOffloadSessionCancelBlockScreenOnEnabled()) {
+ cancelUnblockScreenOnByDisplayOffload();
+ }
unblockScreenOnByDisplayOffload();
mScreenTurningOnWasBlockedByDisplayOffload = false;
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index b43b35b..ddb091d 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -15,6 +15,8 @@
*/
package com.android.server.display.brightness.strategy;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
@@ -133,14 +135,20 @@
// We are still in the process of updating the power state, so there's no need to trigger
// an update again
switchMode(targetDisplayState, /* sendUpdate= */ false);
- final boolean autoBrightnessEnabledInDoze =
- allowAutoBrightnessWhileDozingConfig && Display.isDozeState(targetDisplayState);
+
+ // If the policy is POLICY_DOZE and the display state is STATE_ON, auto-brightness should
+ // only be enabled if the config allows it
+ final boolean autoBrightnessEnabledInDoze = allowAutoBrightnessWhileDozingConfig
+ && policy == POLICY_DOZE && targetDisplayState != Display.STATE_OFF;
+
mIsAutoBrightnessEnabled = shouldUseAutoBrightness()
- && (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze)
+ && ((targetDisplayState == Display.STATE_ON && policy != POLICY_DOZE)
+ || autoBrightnessEnabledInDoze)
&& brightnessReason != BrightnessReason.REASON_OVERRIDE
&& mAutomaticBrightnessController != null;
mAutoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness()
- && !(targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze);
+ && !((targetDisplayState == Display.STATE_ON && policy != POLICY_DOZE)
+ || autoBrightnessEnabledInDoze);
final int autoBrightnessState = mIsAutoBrightnessEnabled
&& brightnessReason != BrightnessReason.REASON_FOLLOWER
? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java
index 4d9c18a..c87872c 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java
@@ -15,6 +15,8 @@
*/
package com.android.server.display.brightness.strategy;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.display.BrightnessConfiguration;
@@ -107,14 +109,19 @@
public void setAutoBrightnessState(int targetDisplayState,
boolean allowAutoBrightnessWhileDozingConfig, int brightnessReason, int policy,
float lastUserSetScreenBrightness, boolean userSetBrightnessChanged) {
- final boolean autoBrightnessEnabledInDoze =
- allowAutoBrightnessWhileDozingConfig && Display.isDozeState(targetDisplayState);
+ // If the policy is POLICY_DOZE and the display state is STATE_ON, auto-brightness should
+ // only be enabled if the config allows it
+ final boolean autoBrightnessEnabledInDoze = allowAutoBrightnessWhileDozingConfig
+ && policy == POLICY_DOZE && targetDisplayState != Display.STATE_OFF;
+
mIsAutoBrightnessEnabled = shouldUseAutoBrightness()
- && (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze)
+ && ((targetDisplayState == Display.STATE_ON && policy != POLICY_DOZE)
+ || autoBrightnessEnabledInDoze)
&& brightnessReason != BrightnessReason.REASON_OVERRIDE
&& mAutomaticBrightnessController != null;
mAutoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness()
- && !(targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze);
+ && !((targetDisplayState == Display.STATE_ON && policy != POLICY_DOZE)
+ || autoBrightnessEnabledInDoze);
final int autoBrightnessState = mIsAutoBrightnessEnabled
&& brightnessReason != BrightnessReason.REASON_FOLLOWER
? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index f56d803..41d18cd 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -179,6 +179,11 @@
Flags::offloadDozeOverrideHoldsWakelock
);
+ private final FlagState mOffloadSessionCancelBlockScreenOn =
+ new FlagState(
+ Flags.FLAG_OFFLOAD_SESSION_CANCEL_BLOCK_SCREEN_ON,
+ Flags::offloadSessionCancelBlockScreenOn);
+
/**
* @return {@code true} if 'port' is allowed in display layout configuration file.
*/
@@ -352,6 +357,10 @@
return mOffloadDozeOverrideHoldsWakelock.isEnabled();
}
+ public boolean isOffloadSessionCancelBlockScreenOnEnabled() {
+ return mOffloadSessionCancelBlockScreenOn.isEnabled();
+ }
+
/**
* @return Whether to ignore preferredRefreshRate app request conversion to display mode or not
*/
@@ -399,6 +408,7 @@
pw.println(" " + mIgnoreAppPreferredRefreshRate);
pw.println(" " + mSynthetic60hzModes);
pw.println(" " + mOffloadDozeOverrideHoldsWakelock);
+ pw.println(" " + mOffloadSessionCancelBlockScreenOn);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 95d0ca3..1ea5c0b 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -299,3 +299,11 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "offload_session_cancel_block_screen_on"
+ namespace: "wear_frameworks"
+ description: "Flag for DisplayPowerController to start notifying DisplayOffloadSession about cancelling screen on blocker."
+ bug: "331725519"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/power/OWNERS b/services/core/java/com/android/server/power/OWNERS
index 94340ec..c1fad33 100644
--- a/services/core/java/com/android/server/power/OWNERS
+++ b/services/core/java/com/android/server/power/OWNERS
@@ -1,6 +1,7 @@
michaelwr@google.com
santoscordon@google.com
-philipjunker@google.com
+petsjonkin@google.com
+brup@google.com
per-file ThermalManagerService.java=file:/THERMAL_OWNERS
per-file LowPowerStandbyController.java=qingxun@google.com
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
index 9756094..503a726 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
@@ -108,9 +108,9 @@
throws CustomizationParserException, IOException {
try {
return loadVibrationsInternal(res, vibratorInfo);
- } catch (VibrationXmlParser.VibrationXmlParserException
- | XmlParserException
- | XmlPullParserException e) {
+ } catch (VibrationXmlParser.ParseFailedException
+ | XmlParserException
+ | XmlPullParserException e) {
throw new CustomizationParserException(
"Error parsing haptic feedback customization file.", e);
}
@@ -121,7 +121,6 @@
Resources res, VibratorInfo vibratorInfo) throws
CustomizationParserException,
IOException,
- VibrationXmlParser.VibrationXmlParserException,
XmlParserException,
XmlPullParserException {
if (!Flags.hapticFeedbackVibrationOemCustomizationEnabled()) {
@@ -172,10 +171,6 @@
ParsedVibration parsedVibration = VibrationXmlParser.parseElement(
parser, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS);
- if (parsedVibration == null) {
- throw new CustomizationParserException(
- "Unable to parse vibration element for effect " + effectId);
- }
VibrationEffect effect = parsedVibration.resolve(vibratorInfo);
if (effect != null) {
if (effect.getDuration() == Long.MAX_VALUE) {
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 7f60dc44..5c15ccb 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -2494,9 +2494,6 @@
try {
ParsedVibration parsedVibration =
VibrationXmlParser.parseDocument(new StringReader(xml));
- if (parsedVibration == null) {
- throw new IllegalArgumentException("Error parsing vibration XML " + xml);
- }
VibratorInfo combinedVibratorInfo = getCombinedVibratorInfo();
if (combinedVibratorInfo == null) {
throw new IllegalStateException(
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index c72087b..9b8c038 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -47,8 +47,6 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.SurfaceControl.METADATA_TASK_ID;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CHANGE;
@@ -3586,15 +3584,29 @@
? null : new PictureInPictureParams(top.pictureInPictureArgs);
}
- Rect getDisplayCutoutInsets() {
- if (mDisplayContent == null || getDisplayInfo().displayCutout == null) return null;
+ /** @return The display cutout insets where the main window is not allowed to extend to. */
+ @NonNull Rect getDisplayCutoutInsets() {
+ final Rect displayCutoutInsets = new Rect();
+ if (mDisplayContent == null || getDisplayInfo().displayCutout == null) {
+ return displayCutoutInsets;
+ }
final WindowState w = getTopVisibleAppMainWindow();
- final int displayCutoutMode = w == null
- ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
- : w.getAttrs().layoutInDisplayCutoutMode;
- return (displayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
- || displayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)
- ? null : getDisplayInfo().displayCutout.getSafeInsets();
+ final Rect displayFrame;
+ if (w != null && w.mHaveFrame) {
+ displayFrame = w.getDisplayFrame();
+ } else {
+ displayFrame = mDisplayContent.getBounds();
+ displayFrame.inset(getDisplayInfo().displayCutout.getSafeInsets());
+ }
+ final Rect taskBounds = getBounds();
+ if (displayCutoutInsets.setIntersect(taskBounds, displayFrame)) {
+ displayCutoutInsets.set(
+ displayCutoutInsets.left - taskBounds.left,
+ displayCutoutInsets.top - taskBounds.top,
+ taskBounds.right - displayCutoutInsets.right,
+ taskBounds.bottom - displayCutoutInsets.bottom);
+ }
+ return displayCutoutInsets;
}
/**
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index 02b7291..e81cf9d 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -38,7 +38,6 @@
import org.junit.Test;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
public final class InputMethodSubtypeSwitchingControllerTest {
@@ -65,17 +64,17 @@
private static void addTestImeSubtypeListItems(@NonNull List<ImeSubtypeListItem> items,
@NonNull String imeName, @NonNull String imeLabel,
@Nullable List<String> subtypeLocales, boolean supportsSwitchingToNextInputMethod) {
- final ResolveInfo ri = new ResolveInfo();
- final ServiceInfo si = new ServiceInfo();
final ApplicationInfo ai = new ApplicationInfo();
ai.packageName = TEST_PACKAGE_NAME;
ai.enabled = true;
+ final ServiceInfo si = new ServiceInfo();
si.applicationInfo = ai;
si.enabled = true;
si.packageName = TEST_PACKAGE_NAME;
si.name = imeName;
si.exported = true;
si.nonLocalizedLabel = imeLabel;
+ final ResolveInfo ri = new ResolveInfo();
ri.serviceInfo = si;
List<InputMethodSubtype> subtypes = null;
if (subtypeLocales != null) {
@@ -102,8 +101,7 @@
@NonNull
private static ImeSubtypeListItem createTestItem(@NonNull ComponentName imeComponentName,
@NonNull String imeName, @NonNull String subtypeName,
- @NonNull String subtypeLocale, int subtypeIndex,
- @NonNull String systemLocale) {
+ @NonNull String subtypeLocale, int subtypeIndex) {
final var ai = new ApplicationInfo();
ai.packageName = imeComponentName.getPackageName();
ai.enabled = true;
@@ -125,26 +123,26 @@
.build());
final InputMethodInfo imi = new InputMethodInfo(ri, TEST_IS_AUX_IME,
TEST_SETTING_ACTIVITY_NAME, subtypes, TEST_IS_DEFAULT_RES_ID,
- TEST_FORCE_DEFAULT, true /* supportsSwitchingToNextInputMethod */,
- TEST_IS_VR_IME);
+ TEST_FORCE_DEFAULT, true /* supportsSwitchingToNextInputMethod */, TEST_IS_VR_IME);
return new ImeSubtypeListItem(imeName, subtypeName, imi, subtypeIndex, subtypeLocale,
- systemLocale);
+ SYSTEM_LOCALE);
}
@NonNull
private static List<ImeSubtypeListItem> createEnabledImeSubtypes() {
final var items = new ArrayList<ImeSubtypeListItem>();
- addTestImeSubtypeListItems(items, "LatinIme", "LatinIme", Arrays.asList("en_US", "fr"),
+ addTestImeSubtypeListItems(items, "LatinIme", "LatinIme", List.of("en_US", "fr"),
true /* supportsSwitchingToNextInputMethod*/);
addTestImeSubtypeListItems(items, "switchUnawareLatinIme", "switchUnawareLatinIme",
- Arrays.asList("en_UK", "hi"),
- false /* supportsSwitchingToNextInputMethod*/);
+ List.of("en_UK", "hi"), false /* supportsSwitchingToNextInputMethod*/);
+ addTestImeSubtypeListItems(items, "subtypeAwareIme", "subtypeAwareIme", null,
+ true /* supportsSwitchingToNextInputMethod */);
addTestImeSubtypeListItems(items, "subtypeUnawareIme", "subtypeUnawareIme", null,
false /* supportsSwitchingToNextInputMethod*/);
- addTestImeSubtypeListItems(items, "JapaneseIme", "JapaneseIme", Arrays.asList("ja_JP"),
+ addTestImeSubtypeListItems(items, "JapaneseIme", "JapaneseIme", List.of("ja_JP"),
true /* supportsSwitchingToNextInputMethod*/);
addTestImeSubtypeListItems(items, "switchUnawareJapaneseIme", "switchUnawareJapaneseIme",
- Arrays.asList("ja_JP"), false /* supportsSwitchingToNextInputMethod*/);
+ List.of("ja_JP"), false /* supportsSwitchingToNextInputMethod*/);
return items;
}
@@ -153,11 +151,11 @@
final var items = new ArrayList<ImeSubtypeListItem>();
addTestImeSubtypeListItems(items,
"UnknownIme", "UnknownIme",
- Arrays.asList("en_US", "hi"),
+ List.of("en_US", "hi"),
true /* supportsSwitchingToNextInputMethod*/);
addTestImeSubtypeListItems(items,
"UnknownSwitchingUnawareIme", "UnknownSwitchingUnawareIme",
- Arrays.asList("en_US"),
+ List.of("en_US"),
false /* supportsSwitchingToNextInputMethod*/);
addTestImeSubtypeListItems(items, "UnknownSubtypeUnawareIme",
"UnknownSubtypeUnawareIme", null,
@@ -209,16 +207,17 @@
final ImeSubtypeListItem latinIme_fr = enabledItems.get(1);
final ImeSubtypeListItem switchingUnawareLatinIme_en_uk = enabledItems.get(2);
final ImeSubtypeListItem switchingUnawareLatinIme_hi = enabledItems.get(3);
- final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4);
- final ImeSubtypeListItem japaneseIme_ja_jp = enabledItems.get(5);
- final ImeSubtypeListItem switchUnawareJapaneseIme_ja_jp = enabledItems.get(6);
+ final ImeSubtypeListItem subtypeAwareIme = enabledItems.get(4);
+ final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(5);
+ final ImeSubtypeListItem japaneseIme_ja_jp = enabledItems.get(6);
+ final ImeSubtypeListItem switchUnawareJapaneseIme_ja_jp = enabledItems.get(7);
final ControllerImpl controller = ControllerImpl.createFrom(
null /* currentInstance */, enabledItems);
// switching-aware loop
assertRotationOrder(controller, false /* onlyCurrentIme */,
- latinIme_en_us, latinIme_fr, japaneseIme_ja_jp);
+ latinIme_en_us, latinIme_fr, subtypeAwareIme, japaneseIme_ja_jp);
// switching-unaware loop
assertRotationOrder(controller, false /* onlyCurrentIme */,
@@ -231,6 +230,8 @@
assertRotationOrder(controller, true /* onlyCurrentIme */,
switchingUnawareLatinIme_en_uk, switchingUnawareLatinIme_hi);
assertNextInputMethod(controller, true /* onlyCurrentIme */,
+ subtypeAwareIme, null);
+ assertNextInputMethod(controller, true /* onlyCurrentIme */,
subtypeUnawareIme, null);
assertNextInputMethod(controller, true /* onlyCurrentIme */,
japaneseIme_ja_jp, null);
@@ -261,55 +262,56 @@
final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes();
final ImeSubtypeListItem latinIme_en_us = enabledItems.get(0);
final ImeSubtypeListItem latinIme_fr = enabledItems.get(1);
- final ImeSubtypeListItem switchingUnawarelatinIme_en_uk = enabledItems.get(2);
- final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3);
- final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4);
- final ImeSubtypeListItem japaneseIme_ja_jp = enabledItems.get(5);
- final ImeSubtypeListItem switchUnawareJapaneseIme_ja_jp = enabledItems.get(6);
+ final ImeSubtypeListItem switchingUnawareLatinIme_en_uk = enabledItems.get(2);
+ final ImeSubtypeListItem switchingUnawareLatinIme_hi = enabledItems.get(3);
+ final ImeSubtypeListItem subtypeAwareIme = enabledItems.get(4);
+ final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(5);
+ final ImeSubtypeListItem japaneseIme_ja_jp = enabledItems.get(6);
+ final ImeSubtypeListItem switchUnawareJapaneseIme_ja_jp = enabledItems.get(7);
final ControllerImpl controller = ControllerImpl.createFrom(
null /* currentInstance */, enabledItems);
// === switching-aware loop ===
assertRotationOrder(controller, false /* onlyCurrentIme */,
- latinIme_en_us, latinIme_fr, japaneseIme_ja_jp);
+ latinIme_en_us, latinIme_fr, subtypeAwareIme, japaneseIme_ja_jp);
// Then notify that a user did something for latinIme_fr.
onUserAction(controller, latinIme_fr);
assertRotationOrder(controller, false /* onlyCurrentIme */,
- latinIme_fr, latinIme_en_us, japaneseIme_ja_jp);
+ latinIme_fr, latinIme_en_us, subtypeAwareIme, japaneseIme_ja_jp);
// Then notify that a user did something for latinIme_fr again.
onUserAction(controller, latinIme_fr);
assertRotationOrder(controller, false /* onlyCurrentIme */,
- latinIme_fr, latinIme_en_us, japaneseIme_ja_jp);
- // Then notify that a user did something for japaneseIme_ja_JP.
- onUserAction(controller, latinIme_fr);
+ latinIme_fr, latinIme_en_us, subtypeAwareIme, japaneseIme_ja_jp);
+ // Then notify that a user did something for subtypeAwareIme.
+ onUserAction(controller, subtypeAwareIme);
assertRotationOrder(controller, false /* onlyCurrentIme */,
- japaneseIme_ja_jp, latinIme_fr, latinIme_en_us);
+ subtypeAwareIme, latinIme_fr, latinIme_en_us, japaneseIme_ja_jp);
// Check onlyCurrentIme == true.
- assertNextInputMethod(controller, true /* onlyCurrentIme */,
- japaneseIme_ja_jp, null);
assertRotationOrder(controller, true /* onlyCurrentIme */,
latinIme_fr, latinIme_en_us);
- assertRotationOrder(controller, true /* onlyCurrentIme */,
- latinIme_en_us, latinIme_fr);
+ assertNextInputMethod(controller, true /* onlyCurrentIme */,
+ subtypeAwareIme, null);
+ assertNextInputMethod(controller, true /* onlyCurrentIme */,
+ japaneseIme_ja_jp, null);
// === switching-unaware loop ===
assertRotationOrder(controller, false /* onlyCurrentIme */,
- switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi, subtypeUnawareIme,
+ switchingUnawareLatinIme_en_uk, switchingUnawareLatinIme_hi, subtypeUnawareIme,
switchUnawareJapaneseIme_ja_jp);
// User action should be ignored for switching unaware IMEs.
- onUserAction(controller, switchingUnawarelatinIme_hi);
+ onUserAction(controller, switchingUnawareLatinIme_hi);
assertRotationOrder(controller, false /* onlyCurrentIme */,
- switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi, subtypeUnawareIme,
+ switchingUnawareLatinIme_en_uk, switchingUnawareLatinIme_hi, subtypeUnawareIme,
switchUnawareJapaneseIme_ja_jp);
// User action should be ignored for switching unaware IMEs.
- onUserAction(controller, switchUnawareJapaneseIme_ja_jp);
+ onUserAction(controller, subtypeUnawareIme);
assertRotationOrder(controller, false /* onlyCurrentIme */,
- switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi, subtypeUnawareIme,
+ switchingUnawareLatinIme_en_uk, switchingUnawareLatinIme_hi, subtypeUnawareIme,
switchUnawareJapaneseIme_ja_jp);
// Check onlyCurrentIme == true.
assertRotationOrder(controller, true /* onlyCurrentIme */,
- switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi);
+ switchingUnawareLatinIme_en_uk, switchingUnawareLatinIme_hi);
assertNextInputMethod(controller, true /* onlyCurrentIme */,
subtypeUnawareIme, null);
assertNextInputMethod(controller, true /* onlyCurrentIme */,
@@ -320,28 +322,28 @@
final ControllerImpl newController = ControllerImpl.createFrom(controller,
sameEnabledItems);
assertRotationOrder(newController, false /* onlyCurrentIme */,
- japaneseIme_ja_jp, latinIme_fr, latinIme_en_us);
+ subtypeAwareIme, latinIme_fr, latinIme_en_us, japaneseIme_ja_jp);
assertRotationOrder(newController, false /* onlyCurrentIme */,
- switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi, subtypeUnawareIme,
+ switchingUnawareLatinIme_en_uk, switchingUnawareLatinIme_hi, subtypeUnawareIme,
switchUnawareJapaneseIme_ja_jp);
// Rotation order should be initialized when created with a different subtype list.
- final List<ImeSubtypeListItem> differentEnabledItems = Arrays.asList(
- latinIme_en_us, latinIme_fr, switchingUnawarelatinIme_en_uk,
- switchUnawareJapaneseIme_ja_jp);
+ final List<ImeSubtypeListItem> differentEnabledItems = List.of(
+ latinIme_en_us, latinIme_fr, subtypeAwareIme, switchingUnawareLatinIme_en_uk,
+ switchUnawareJapaneseIme_ja_jp, subtypeUnawareIme);
final ControllerImpl anotherController = ControllerImpl.createFrom(controller,
differentEnabledItems);
assertRotationOrder(anotherController, false /* onlyCurrentIme */,
- latinIme_en_us, latinIme_fr);
+ latinIme_en_us, latinIme_fr, subtypeAwareIme);
assertRotationOrder(anotherController, false /* onlyCurrentIme */,
- switchingUnawarelatinIme_en_uk, switchUnawareJapaneseIme_ja_jp);
+ switchingUnawareLatinIme_en_uk, switchUnawareJapaneseIme_ja_jp, subtypeUnawareIme);
}
@Test
public void testImeSubtypeListItem() {
final var items = new ArrayList<ImeSubtypeListItem>();
addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
- Arrays.asList("en_US", "fr", "en", "en_uk", "enn", "e", "EN_US"),
+ List.of("en_US", "fr", "en", "en_uk", "enn", "e", "EN_US"),
true /* supportsSwitchingToNextInputMethod*/);
final ImeSubtypeListItem item_en_us = items.get(0);
final ImeSubtypeListItem item_fr = items.get(1);
@@ -376,61 +378,61 @@
final ComponentName imeY1 = new ComponentName("com.example.imeY", "Ime1");
final ComponentName imeZ1 = new ComponentName("com.example.imeZ", "Ime1");
{
- final List<ImeSubtypeListItem> items = Arrays.asList(
+ final List<ImeSubtypeListItem> items = List.of(
// Subtypes of two IMEs that have the same display name "X".
// Subtypes that has the same locale of the system's.
- createTestItem(imeX1, "X", "E", "en_US", 0, "en_US"),
- createTestItem(imeX2, "X", "E", "en_US", 0, "en_US"),
- createTestItem(imeX1, "X", "Z", "en_US", 3, "en_US"),
- createTestItem(imeX2, "X", "Z", "en_US", 3, "en_US"),
- createTestItem(imeX1, "X", "", "en_US", 6, "en_US"),
- createTestItem(imeX2, "X", "", "en_US", 6, "en_US"),
+ createTestItem(imeX1, "X", "E", "en_US", 0),
+ createTestItem(imeX2, "X", "E", "en_US", 0),
+ createTestItem(imeX1, "X", "Z", "en_US", 3),
+ createTestItem(imeX2, "X", "Z", "en_US", 3),
+ createTestItem(imeX1, "X", "", "en_US", 6),
+ createTestItem(imeX2, "X", "", "en_US", 6),
// Subtypes that has the same language of the system's.
- createTestItem(imeX1, "X", "E", "en", 1, "en_US"),
- createTestItem(imeX2, "X", "E", "en", 1, "en_US"),
- createTestItem(imeX1, "X", "Z", "en", 4, "en_US"),
- createTestItem(imeX2, "X", "Z", "en", 4, "en_US"),
- createTestItem(imeX1, "X", "", "en", 7, "en_US"),
- createTestItem(imeX2, "X", "", "en", 7, "en_US"),
+ createTestItem(imeX1, "X", "E", "en", 1),
+ createTestItem(imeX2, "X", "E", "en", 1),
+ createTestItem(imeX1, "X", "Z", "en", 4),
+ createTestItem(imeX2, "X", "Z", "en", 4),
+ createTestItem(imeX1, "X", "", "en", 7),
+ createTestItem(imeX2, "X", "", "en", 7),
// Subtypes that has different language than the system's.
- createTestItem(imeX1, "X", "A", "hi_IN", 27, "en_US"),
- createTestItem(imeX2, "X", "A", "hi_IN", 27, "en_US"),
- createTestItem(imeX1, "X", "E", "ja", 2, "en_US"),
- createTestItem(imeX2, "X", "E", "ja", 2, "en_US"),
- createTestItem(imeX1, "X", "Z", "ja", 5, "en_US"),
- createTestItem(imeX2, "X", "Z", "ja", 5, "en_US"),
- createTestItem(imeX1, "X", "", "ja", 8, "en_US"),
- createTestItem(imeX2, "X", "", "ja", 8, "en_US"),
+ createTestItem(imeX1, "X", "A", "hi_IN", 27),
+ createTestItem(imeX2, "X", "A", "hi_IN", 27),
+ createTestItem(imeX1, "X", "E", "ja", 2),
+ createTestItem(imeX2, "X", "E", "ja", 2),
+ createTestItem(imeX1, "X", "Z", "ja", 5),
+ createTestItem(imeX2, "X", "Z", "ja", 5),
+ createTestItem(imeX1, "X", "", "ja", 8),
+ createTestItem(imeX2, "X", "", "ja", 8),
// Subtypes of IME "Y".
// Subtypes that has the same locale of the system's.
- createTestItem(imeY1, "Y", "E", "en_US", 9, "en_US"),
- createTestItem(imeY1, "Y", "Z", "en_US", 12, "en_US"),
- createTestItem(imeY1, "Y", "", "en_US", 15, "en_US"),
+ createTestItem(imeY1, "Y", "E", "en_US", 9),
+ createTestItem(imeY1, "Y", "Z", "en_US", 12),
+ createTestItem(imeY1, "Y", "", "en_US", 15),
// Subtypes that has the same language of the system's.
- createTestItem(imeY1, "Y", "E", "en", 10, "en_US"),
- createTestItem(imeY1, "Y", "Z", "en", 13, "en_US"),
- createTestItem(imeY1, "Y", "", "en", 16, "en_US"),
+ createTestItem(imeY1, "Y", "E", "en", 10),
+ createTestItem(imeY1, "Y", "Z", "en", 13),
+ createTestItem(imeY1, "Y", "", "en", 16),
// Subtypes that has different language than the system's.
- createTestItem(imeY1, "Y", "A", "hi_IN", 28, "en_US"),
- createTestItem(imeY1, "Y", "E", "ja", 11, "en_US"),
- createTestItem(imeY1, "Y", "Z", "ja", 14, "en_US"),
- createTestItem(imeY1, "Y", "", "ja", 17, "en_US"),
+ createTestItem(imeY1, "Y", "A", "hi_IN", 28),
+ createTestItem(imeY1, "Y", "E", "ja", 11),
+ createTestItem(imeY1, "Y", "Z", "ja", 14),
+ createTestItem(imeY1, "Y", "", "ja", 17),
// Subtypes of IME Z.
// Subtypes that has the same locale of the system's.
- createTestItem(imeZ1, "", "E", "en_US", 18, "en_US"),
- createTestItem(imeZ1, "", "Z", "en_US", 21, "en_US"),
- createTestItem(imeZ1, "", "", "en_US", 24, "en_US"),
+ createTestItem(imeZ1, "", "E", "en_US", 18),
+ createTestItem(imeZ1, "", "Z", "en_US", 21),
+ createTestItem(imeZ1, "", "", "en_US", 24),
// Subtypes that has the same language of the system's.
- createTestItem(imeZ1, "", "E", "en", 19, "en_US"),
- createTestItem(imeZ1, "", "Z", "en", 22, "en_US"),
- createTestItem(imeZ1, "", "", "en", 25, "en_US"),
+ createTestItem(imeZ1, "", "E", "en", 19),
+ createTestItem(imeZ1, "", "Z", "en", 22),
+ createTestItem(imeZ1, "", "", "en", 25),
// Subtypes that has different language than the system's.
- createTestItem(imeZ1, "", "A", "hi_IN", 29, "en_US"),
- createTestItem(imeZ1, "", "E", "ja", 20, "en_US"),
- createTestItem(imeZ1, "", "Z", "ja", 23, "en_US"),
- createTestItem(imeZ1, "", "", "ja", 26, "en_US"));
+ createTestItem(imeZ1, "", "A", "hi_IN", 29),
+ createTestItem(imeZ1, "", "E", "ja", 20),
+ createTestItem(imeZ1, "", "Z", "ja", 23),
+ createTestItem(imeZ1, "", "", "ja", 26));
// Ensure {@link java.lang.Comparable#compareTo} contracts are satisfied.
for (int i = 0; i < items.size(); ++i) {
@@ -449,10 +451,8 @@
{
// Following two items have the same priority.
- final ImeSubtypeListItem nonSystemLocale1 =
- createTestItem(imeX1, "X", "A", "ja_JP", 0, "en_US");
- final ImeSubtypeListItem nonSystemLocale2 =
- createTestItem(imeX1, "X", "A", "hi_IN", 1, "en_US");
+ final ImeSubtypeListItem nonSystemLocale1 = createTestItem(imeX1, "X", "A", "ja_JP", 0);
+ final ImeSubtypeListItem nonSystemLocale2 = createTestItem(imeX1, "X", "A", "hi_IN", 1);
assertEquals(0, nonSystemLocale1.compareTo(nonSystemLocale2));
assertEquals(0, nonSystemLocale2.compareTo(nonSystemLocale1));
// But those aren't equal to each other.
@@ -462,8 +462,8 @@
{
// Check if ComponentName is also taken into account when comparing two items.
- final ImeSubtypeListItem ime1 = createTestItem(imeX1, "X", "A", "ja_JP", 0, "en_US");
- final ImeSubtypeListItem ime2 = createTestItem(imeX2, "X", "A", "ja_JP", 0, "en_US");
+ final ImeSubtypeListItem ime1 = createTestItem(imeX1, "X", "A", "ja_JP", 0);
+ final ImeSubtypeListItem ime2 = createTestItem(imeX2, "X", "A", "ja_JP", 0);
assertTrue(ime1.compareTo(ime2) < 0);
assertTrue(ime2.compareTo(ime1) > 0);
// But those aren't equal to each other.
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
index 4409051..30c384a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
@@ -94,4 +94,11 @@
verify(mDisplayOffloader).onBlockingScreenOn(eq(unblocker));
}
+
+ @Test
+ public void testUnblockScreenOn() {
+ mSession.cancelBlockScreenOn();
+
+ verify(mDisplayOffloader).cancelBlockScreenOn();
+ }
}
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 95f0b65..bb774ee 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -105,7 +105,6 @@
import java.util.List;
-
@SmallTest
@RunWith(AndroidJUnit4.class)
public final class DisplayPowerControllerTest {
@@ -1660,6 +1659,8 @@
int initState = Display.STATE_OFF;
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+ when(mDisplayOffloadSession.blockScreenOn(any())).thenReturn(true);
+
// start with OFF.
when(mHolder.displayPowerState.getScreenState()).thenReturn(initState);
DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -1673,6 +1674,7 @@
advanceTime(1); // Run updatePowerState
verify(mDisplayOffloadSession).blockScreenOn(any(Runnable.class));
+ verify(mDisplayOffloadSession, never()).cancelBlockScreenOn();
}
@Test
@@ -1680,6 +1682,8 @@
// set up.
int initState = Display.STATE_ON;
mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+ when(mDisplayOffloadSession.blockScreenOn(any())).thenReturn(true);
+
// start with ON.
when(mHolder.displayPowerState.getScreenState()).thenReturn(initState);
DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -1692,7 +1696,78 @@
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- verify(mDisplayOffloadSession, never()).blockScreenOn(any(Runnable.class));
+ // No cancelBlockScreenOn call because we didn't block.
+ verify(mDisplayOffloadSession, never()).cancelBlockScreenOn();
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_OFFLOAD_SESSION_CANCEL_BLOCK_SCREEN_ON)
+ @Test
+ public void testOffloadBlocker_turnON_thenOFF_cancelBlockScreenOnNotCalledIfUnblocked() {
+ // Set up.
+ int initState = Display.STATE_OFF;
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+ when(mDisplayOffloadSession.blockScreenOn(any())).thenReturn(true);
+
+ // Start with OFF.
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(initState);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_OFF;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ // Go to ON.
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mDisplayOffloadSession).blockScreenOn(argumentCaptor.capture());
+
+ // Unblocked
+ argumentCaptor.getValue().run();
+ advanceTime(1); // Run updatePowerState
+
+ // Go to OFF immediately
+ dpr.policy = DisplayPowerRequest.POLICY_OFF;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ // No cancelBlockScreenOn call because we already unblocked
+ verify(mDisplayOffloadSession, never()).cancelBlockScreenOn();
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_OFFLOAD_SESSION_CANCEL_BLOCK_SCREEN_ON)
+ @Test
+ public void testOffloadBlocker_turnON_thenOFF_cancelBlockScreenOn() {
+ // Set up.
+ int initState = Display.STATE_OFF;
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+ when(mDisplayOffloadSession.blockScreenOn(any())).thenReturn(true);
+
+ // Start with OFF.
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(initState);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_OFF;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ // Go to ON.
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ // We should call blockScreenOn
+ verify(mDisplayOffloadSession).blockScreenOn(any(Runnable.class));
+
+ // Go to OFF immediately
+ dpr.policy = DisplayPowerRequest.POLICY_OFF;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ // We should call cancelBlockScreenOn
+ verify(mDisplayOffloadSession).cancelBlockScreenOn();
}
@Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 01ff35f..a7e0ebd 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -33,7 +33,6 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
@@ -83,7 +82,6 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-
@SmallTest
@RunWith(AndroidJUnit4.class)
public class LocalDisplayAdapterTest {
@@ -126,7 +124,7 @@
private DisplayOffloadSessionImpl mDisplayOffloadSession;
- private DisplayOffloader mDisplayOffloader;
+ @Mock DisplayOffloader mDisplayOffloader;
private TestListener mListener = new TestListener();
@@ -1249,24 +1247,8 @@
}
private void initDisplayOffloadSession() {
- mDisplayOffloader = spy(new DisplayOffloader() {
- @Override
- public boolean startOffload() {
- return true;
- }
-
- @Override
- public void stopOffload() {}
-
- @Override
- public void onBlockingScreenOn(Runnable unblocker) {}
-
- @Override
- public boolean allowAutoBrightnessInDoze() {
- return true;
- }
- });
-
+ when(mDisplayOffloader.startOffload()).thenReturn(true);
+ when(mDisplayOffloader.allowAutoBrightnessInDoze()).thenReturn(true);
mDisplayOffloadSession = new DisplayOffloadSessionImpl(mDisplayOffloader,
mMockedDisplayPowerController);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java
index 498bffd..4e10b98 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java
@@ -192,7 +192,7 @@
}
@Test
- public void testAutoBrightnessState_DisplayIsInDoze_ConfigDoesAllow() {
+ public void testAutoBrightnessState_DeviceIsInDoze_ConfigDoesAllow() {
mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
int targetDisplayState = Display.STATE_DOZE;
boolean allowAutoBrightnessWhileDozing = true;
@@ -218,6 +218,32 @@
}
@Test
+ public void testAutoBrightnessState_DeviceIsInDoze_ConfigDoesAllow_ScreenOff() {
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+ int targetDisplayState = Display.STATE_OFF;
+ boolean allowAutoBrightnessWhileDozing = true;
+ int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+ int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+ float lastUserSetBrightness = 0.2f;
+ boolean userSetBrightnessChanged = true;
+ Settings.System.putFloat(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.4f);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+ allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+ userSetBrightnessChanged);
+ verify(mAutomaticBrightnessController)
+ .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+ mBrightnessConfiguration,
+ lastUserSetBrightness,
+ userSetBrightnessChanged, /* adjustment */ 0.4f,
+ /* userChangedAutoBrightnessAdjustment= */ true, policy,
+ targetDisplayState, /* shouldResetShortTermModel */ true);
+ assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+ assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+ }
+
+ @Test
public void testAutoBrightnessState_DisplayIsOn() {
mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
int targetDisplayState = Display.STATE_ON;
@@ -245,6 +271,33 @@
}
@Test
+ public void testAutoBrightnessState_DisplayIsOn_PolicyIsDoze() {
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+ int targetDisplayState = Display.STATE_ON;
+ boolean allowAutoBrightnessWhileDozing = false;
+ int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+ float lastUserSetBrightness = 0.2f;
+ boolean userSetBrightnessChanged = true;
+ int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+ float pendingBrightnessAdjustment = 0.1f;
+ Settings.System.putFloat(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+ allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+ userSetBrightnessChanged);
+ verify(mAutomaticBrightnessController)
+ .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+ mBrightnessConfiguration,
+ lastUserSetBrightness,
+ userSetBrightnessChanged, pendingBrightnessAdjustment,
+ /* userChangedAutoBrightnessAdjustment= */ true, policy, targetDisplayState,
+ /* shouldResetShortTermModel */ true);
+ assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+ assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+ }
+
+ @Test
public void accommodateUserBrightnessChangesWorksAsExpected() {
// Verify the state if automaticBrightnessController is configured.
assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive());
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index 1d04baa..e16377e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -202,7 +202,7 @@
}
@Test
- public void testAutoBrightnessState_DisplayIsInDoze_ConfigDoesAllow() {
+ public void testAutoBrightnessState_DeviceIsInDoze_ConfigDoesAllow() {
mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
int targetDisplayState = Display.STATE_DOZE;
boolean allowAutoBrightnessWhileDozing = true;
@@ -228,6 +228,32 @@
}
@Test
+ public void testAutoBrightnessState_DeviceIsInDoze_ConfigDoesAllow_ScreenOff() {
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+ int targetDisplayState = Display.STATE_OFF;
+ boolean allowAutoBrightnessWhileDozing = true;
+ int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+ int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+ float lastUserSetBrightness = 0.2f;
+ boolean userSetBrightnessChanged = true;
+ Settings.System.putFloat(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.4f);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+ allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+ userSetBrightnessChanged);
+ verify(mAutomaticBrightnessController)
+ .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+ mBrightnessConfiguration,
+ lastUserSetBrightness,
+ userSetBrightnessChanged, /* adjustment */ 0.4f,
+ /* userChangedAutoBrightnessAdjustment= */ true, policy,
+ targetDisplayState, /* shouldResetShortTermModel */ true);
+ assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+ assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+ }
+
+ @Test
public void testAutoBrightnessState_DisplayIsOn() {
mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
int targetDisplayState = Display.STATE_ON;
@@ -255,6 +281,33 @@
}
@Test
+ public void testAutoBrightnessState_DisplayIsOn_PolicyIsDoze() {
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+ int targetDisplayState = Display.STATE_ON;
+ boolean allowAutoBrightnessWhileDozing = false;
+ int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+ float lastUserSetBrightness = 0.2f;
+ boolean userSetBrightnessChanged = true;
+ int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+ float pendingBrightnessAdjustment = 0.1f;
+ Settings.System.putFloat(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+ allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+ userSetBrightnessChanged);
+ verify(mAutomaticBrightnessController)
+ .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+ mBrightnessConfiguration,
+ lastUserSetBrightness,
+ userSetBrightnessChanged, pendingBrightnessAdjustment,
+ /* userChangedAutoBrightnessAdjustment= */ true, policy, targetDisplayState,
+ /* shouldResetShortTermModel */ true);
+ assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+ assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+ }
+
+ @Test
public void testAutoBrightnessState_modeSwitch() {
// Setup the test
when(mDisplayManagerFlags.areAutoBrightnessModesEnabled()).thenReturn(true);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 4e8c755..9884085 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -25,6 +25,8 @@
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.server.accessibility.AccessibilityManagerService.ACTION_LAUNCH_HEARING_DEVICES_DIALOG;
import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER;
@@ -1082,7 +1084,7 @@
mA11yms.enableShortcutsForTargets(
/* enable= */ true,
- UserShortcutType.HARDWARE,
+ HARDWARE,
List.of(target),
mA11yms.getCurrentUserIdLocked());
mTestableLooper.processAllMessages();
@@ -1346,14 +1348,14 @@
mA11yms.enableShortcutsForTargets(
/* enable= */ true,
- UserShortcutType.HARDWARE,
+ HARDWARE,
List.of(TARGET_STANDARD_A11Y_SERVICE.flattenToString()),
mA11yms.getCurrentUserIdLocked());
mTestableLooper.processAllMessages();
assertThat(
ShortcutUtils.isComponentIdExistingInSettings(
- mTestableContext, ShortcutConstants.UserShortcutType.HARDWARE,
+ mTestableContext, HARDWARE,
TARGET_STANDARD_A11Y_SERVICE.flattenToString())
).isTrue();
}
@@ -1367,7 +1369,7 @@
mA11yms.enableShortcutsForTargets(
/* enable= */ false,
- UserShortcutType.HARDWARE,
+ HARDWARE,
List.of(TARGET_STANDARD_A11Y_SERVICE.flattenToString()),
mA11yms.getCurrentUserIdLocked());
mTestableLooper.processAllMessages();
@@ -1375,7 +1377,7 @@
assertThat(
ShortcutUtils.isComponentIdExistingInSettings(
mTestableContext,
- ShortcutConstants.UserShortcutType.HARDWARE,
+ HARDWARE,
TARGET_STANDARD_A11Y_SERVICE.flattenToString()))
.isFalse();
}
@@ -1390,14 +1392,14 @@
mA11yms.enableShortcutsForTargets(
/* enable= */ true,
- UserShortcutType.QUICK_SETTINGS,
+ QUICK_SETTINGS,
List.of(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString()),
mA11yms.getCurrentUserIdLocked());
mTestableLooper.processAllMessages();
assertThat(
ShortcutUtils.isComponentIdExistingInSettings(
- mTestableContext, UserShortcutType.QUICK_SETTINGS,
+ mTestableContext, QUICK_SETTINGS,
TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString())
).isTrue();
verify(mStatusBarManagerInternal)
@@ -1417,14 +1419,14 @@
mA11yms.enableShortcutsForTargets(
/* enable= */ false,
- UserShortcutType.QUICK_SETTINGS,
+ QUICK_SETTINGS,
List.of(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString()),
mA11yms.getCurrentUserIdLocked());
mTestableLooper.processAllMessages();
assertThat(
ShortcutUtils.isComponentIdExistingInSettings(
- mTestableContext, UserShortcutType.QUICK_SETTINGS,
+ mTestableContext, QUICK_SETTINGS,
TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString())
).isFalse();
verify(mStatusBarManagerInternal)
@@ -1614,44 +1616,49 @@
@Test
@EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
- public void restoreAccessibilityQsTargets_a11yQsTargetsRestored() {
+ public void restoreShortcutTargets_qs_a11yQsTargetsRestored() {
String daltonizerTile =
AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString();
String colorInversionTile =
AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString();
final AccessibilityUserState userState = new AccessibilityUserState(
UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- userState.updateA11yQsTargetLocked(Set.of(daltonizerTile));
+ userState.updateShortcutTargetsLocked(Set.of(daltonizerTile), QUICK_SETTINGS);
mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
broadcastSettingRestored(
- Settings.Secure.ACCESSIBILITY_QS_TARGETS,
- /*previousValue=*/null,
+ ShortcutUtils.convertToKey(QUICK_SETTINGS),
/*newValue=*/colorInversionTile);
- assertThat(mA11yms.mUserStates.get(UserHandle.USER_SYSTEM).getA11yQsTargets())
- .containsExactlyElementsIn(Set.of(daltonizerTile, colorInversionTile));
+ Set<String> expected = Set.of(daltonizerTile, colorInversionTile);
+ assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(QUICK_SETTINGS)))
+ .containsExactlyElementsIn(expected);
+ assertThat(userState.getShortcutTargetsLocked(QUICK_SETTINGS))
+ .containsExactlyElementsIn(expected);
}
@Test
@DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
- public void restoreAccessibilityQsTargets_a11yQsTargetsNotRestored() {
+ public void restoreShortcutTargets_qs_a11yQsTargetsNotRestored() {
String daltonizerTile =
AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString();
String colorInversionTile =
AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString();
final AccessibilityUserState userState = new AccessibilityUserState(
UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- userState.updateA11yQsTargetLocked(Set.of(daltonizerTile));
+ userState.updateShortcutTargetsLocked(Set.of(daltonizerTile), QUICK_SETTINGS);
+ putShortcutSettingForUser(QUICK_SETTINGS, daltonizerTile, userState.mUserId);
mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
broadcastSettingRestored(
- Settings.Secure.ACCESSIBILITY_QS_TARGETS,
- /*previousValue=*/null,
+ ShortcutUtils.convertToKey(QUICK_SETTINGS),
/*newValue=*/colorInversionTile);
- assertThat(userState.getA11yQsTargets())
- .containsExactlyElementsIn(Set.of(daltonizerTile));
+ Set<String> expected = Set.of(daltonizerTile);
+ assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(QUICK_SETTINGS)))
+ .containsExactlyElementsIn(expected);
+ assertThat(userState.getShortcutTargetsLocked(QUICK_SETTINGS))
+ .containsExactlyElementsIn(expected);
}
@Test
@@ -1717,27 +1724,26 @@
@Test
@EnableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE)
- public void restoreA11yShortcutTargetService_targetsMerged() {
+ public void restoreShortcutTargets_hardware_targetsMerged() {
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
final String servicePrevious = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
final String otherPrevious = TARGET_MAGNIFICATION;
- final String combinedPrevious = String.join(":", servicePrevious, otherPrevious);
final String serviceRestored = TARGET_STANDARD_A11Y_SERVICE.flattenToString();
final AccessibilityUserState userState = new AccessibilityUserState(
UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
setupShortcutTargetServices(userState);
+ mA11yms.enableShortcutsForTargets(
+ true, HARDWARE, List.of(servicePrevious, otherPrevious), userState.mUserId);
broadcastSettingRestored(
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
- /*previousValue=*/combinedPrevious,
+ ShortcutUtils.convertToKey(HARDWARE),
/*newValue=*/serviceRestored);
final Set<String> expected = Set.of(servicePrevious, otherPrevious, serviceRestored);
- assertThat(readStringsFromSetting(
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE))
+ assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(HARDWARE)))
.containsExactlyElementsIn(expected);
- assertThat(mA11yms.mUserStates.get(UserHandle.USER_SYSTEM)
- .getShortcutTargetsLocked(UserShortcutType.HARDWARE))
+ assertThat(userState.getShortcutTargetsLocked(HARDWARE))
.containsExactlyElementsIn(expected);
}
@@ -1745,7 +1751,7 @@
@EnableFlags({
android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE,
Flags.FLAG_CLEAR_DEFAULT_FROM_A11Y_SHORTCUT_TARGET_SERVICE_RESTORE})
- public void restoreA11yShortcutTargetService_alreadyHadDefaultService_doesNotClear() {
+ public void restoreShortcutTargets_hardware_alreadyHadDefaultService_doesNotClear() {
final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE.flattenToString();
mTestableContext.getOrCreateTestableResources().addOverride(
R.string.config_defaultAccessibilityService, serviceDefault);
@@ -1754,17 +1760,18 @@
mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
setupShortcutTargetServices(userState);
+ // default is present in userState & setting, so it's not cleared
+ putShortcutSettingForUser(HARDWARE, serviceDefault, UserHandle.USER_SYSTEM);
+ userState.updateShortcutTargetsLocked(Set.of(serviceDefault), HARDWARE);
+
broadcastSettingRestored(
Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
- /*previousValue=*/serviceDefault,
/*newValue=*/serviceDefault);
final Set<String> expected = Set.of(serviceDefault);
- assertThat(readStringsFromSetting(
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE))
+ assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(HARDWARE)))
.containsExactlyElementsIn(expected);
- assertThat(mA11yms.mUserStates.get(UserHandle.USER_SYSTEM)
- .getShortcutTargetsLocked(UserShortcutType.HARDWARE))
+ assertThat(userState.getShortcutTargetsLocked(HARDWARE))
.containsExactlyElementsIn(expected);
}
@@ -1772,7 +1779,7 @@
@EnableFlags({
android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE,
Flags.FLAG_CLEAR_DEFAULT_FROM_A11Y_SHORTCUT_TARGET_SERVICE_RESTORE})
- public void restoreA11yShortcutTargetService_didNotHaveDefaultService_clearsDefaultService() {
+ public void restoreShortcutTargets_hardware_didNotHaveDefaultService_clearsDefaultService() {
final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE.flattenToString();
final String serviceRestored = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
// Restored value from the broadcast contains both default and non-default service.
@@ -1784,18 +1791,45 @@
mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
setupShortcutTargetServices(userState);
- broadcastSettingRestored(
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
- /*previousValue=*/null,
+ broadcastSettingRestored(ShortcutUtils.convertToKey(HARDWARE),
/*newValue=*/combinedRestored);
// The default service is cleared from the final restored value.
final Set<String> expected = Set.of(serviceRestored);
- assertThat(readStringsFromSetting(
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE))
+ assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(HARDWARE)))
.containsExactlyElementsIn(expected);
- assertThat(mA11yms.mUserStates.get(UserHandle.USER_SYSTEM)
- .getShortcutTargetsLocked(UserShortcutType.HARDWARE))
+ assertThat(userState.getShortcutTargetsLocked(HARDWARE))
+ .containsExactlyElementsIn(expected);
+ }
+
+ @Test
+ @EnableFlags({
+ android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE,
+ Flags.FLAG_CLEAR_DEFAULT_FROM_A11Y_SHORTCUT_TARGET_SERVICE_RESTORE})
+ public void restoreShortcutTargets_hardware_nullSetting_clearsDefaultService() {
+ final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE.flattenToString();
+ final String serviceRestored = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
+ // Restored value from the broadcast contains both default and non-default service.
+ final String combinedRestored = String.join(":", serviceDefault, serviceRestored);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.string.config_defaultAccessibilityService, serviceDefault);
+ final AccessibilityUserState userState = new AccessibilityUserState(
+ UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ setupShortcutTargetServices(userState);
+
+ // UserState has default, but setting is null (this emulates a typical scenario in SUW).
+ userState.updateShortcutTargetsLocked(Set.of(serviceDefault), HARDWARE);
+ putShortcutSettingForUser(HARDWARE, null, UserHandle.USER_SYSTEM);
+
+ broadcastSettingRestored(ShortcutUtils.convertToKey(HARDWARE),
+ /*newValue=*/combinedRestored);
+
+ // The default service is cleared from the final restored value.
+ final Set<String> expected = Set.of(serviceRestored);
+ assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(HARDWARE)))
+ .containsExactlyElementsIn(expected);
+ assertThat(userState.getShortcutTargetsLocked(HARDWARE))
.containsExactlyElementsIn(expected);
}
@@ -1806,11 +1840,10 @@
return result;
}
- private void broadcastSettingRestored(String setting, String previousValue, String newValue) {
+ private void broadcastSettingRestored(String setting, String newValue) {
Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
.putExtra(Intent.EXTRA_SETTING_NAME, setting)
- .putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, previousValue)
.putExtra(Intent.EXTRA_SETTING_NEW_VALUE, newValue);
sendBroadcastToAccessibilityManagerService(intent);
mTestableLooper.processAllMessages();
@@ -1952,4 +1985,13 @@
private static boolean isSameCurrentUser(AccessibilityManagerService service, Context context) {
return service.getCurrentUserIdLocked() == context.getUserId();
}
+
+ private void putShortcutSettingForUser(@UserShortcutType int shortcutType,
+ String shortcutValue, int userId) {
+ Settings.Secure.putStringForUser(
+ mTestableContext.getContentResolver(),
+ ShortcutUtils.convertToKey(shortcutType),
+ shortcutValue,
+ userId);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index b269beb9..9fad14d 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -28,6 +28,7 @@
import static android.view.accessibility.AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED;
import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain;
import static com.google.common.truth.Truth.assertThat;
@@ -429,20 +430,20 @@
}
@Test
- public void updateA11yQsTargetLocked_valueUpdated() {
+ public void updateShortcutTargetsLocked_quickSettings_valueUpdated() {
Set<String> newTargets = Set.of(
AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString(),
AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString()
);
- mUserState.updateA11yQsTargetLocked(newTargets);
+ mUserState.updateShortcutTargetsLocked(newTargets, QUICK_SETTINGS);
assertThat(mUserState.getA11yQsTargets()).isEqualTo(newTargets);
}
@Test
public void getA11yQsTargets_returnsCopiedData() {
- updateA11yQsTargetLocked_valueUpdated();
+ updateShortcutTargetsLocked_quickSettings_valueUpdated();
Set<String> targets = mUserState.getA11yQsTargets();
targets.clear();
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java
index ba0e3db..3c93c88 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java
@@ -24,6 +24,7 @@
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
/**
* Data label representation with data shared and data collected maps containing zero or more {@link
@@ -138,7 +139,7 @@
"|",
dataType.getPurposes().stream()
.map(DataType.Purpose::toString)
- .toList()));
+ .collect(Collectors.toList())));
dataLabelsEle.appendChild(hrDataTypeEle);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java
index d2326d1..284a4b8 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java
@@ -25,6 +25,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* Data usage type representation. Types are specific to a {@link DataCategory} and contains
@@ -182,7 +183,7 @@
XmlUtils.OD_NAME_PURPOSES,
this.getPurposes().stream()
.map(p -> String.valueOf(p.getValue()))
- .toList()));
+ .collect(Collectors.toList())));
}
maybeAddBoolToOdElement(
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
index 26b5639..2c1517b 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
@@ -25,6 +25,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.stream.Collectors;
public class XmlUtils {
public static final String DATA_TYPE_SEPARATOR = "_data_type_";
@@ -131,7 +132,9 @@
/** Gets the top-level children with the tag name.. */
public static List<Element> getChildrenByTagName(Node parentEle, String tagName) {
var elements = XmlUtils.asElementList(parentEle.getChildNodes());
- return elements.stream().filter(e -> e.getTagName().equals(tagName)).toList();
+ return elements.stream()
+ .filter(e -> e.getTagName().equals(tagName))
+ .collect(Collectors.toList());
}
/**
@@ -286,7 +289,8 @@
/** Gets a pipeline-split attribute. */
public static List<String> getPipelineSplitAttr(Element ele, String attrName, boolean required)
throws MalformedXmlException {
- List<String> list = Arrays.stream(ele.getAttribute(attrName).split("\\|")).toList();
+ List<String> list =
+ Arrays.stream(ele.getAttribute(attrName).split("\\|")).collect(Collectors.toList());
if ((list.isEmpty() || list.get(0).isEmpty()) && required) {
throw new MalformedXmlException(
String.format(
@@ -315,7 +319,7 @@
List<Element> boolEles =
XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_BOOLEAN).stream()
.filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName))
- .toList();
+ .collect(Collectors.toList());
if (boolEles.size() > 1) {
throw new MalformedXmlException(
String.format(
@@ -346,7 +350,7 @@
List<Element> longEles =
XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_LONG).stream()
.filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName))
- .toList();
+ .collect(Collectors.toList());
if (longEles.size() > 1) {
throw new MalformedXmlException(
String.format(
@@ -377,7 +381,7 @@
List<Element> eles =
XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_STRING).stream()
.filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName))
- .toList();
+ .collect(Collectors.toList());
if (eles.size() > 1) {
throw new MalformedXmlException(
String.format(
@@ -405,7 +409,7 @@
List<Element> eles =
XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_PBUNDLE_AS_MAP).stream()
.filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName))
- .toList();
+ .collect(Collectors.toList());
if (eles.size() > 1) {
throw new MalformedXmlException(
String.format(
@@ -449,7 +453,7 @@
List<Element> intArrayEles =
XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_INT_ARRAY).stream()
.filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName))
- .toList();
+ .collect(Collectors.toList());
if (intArrayEles.size() > 1) {
throw new MalformedXmlException(
String.format("Found more than one %s in %s.", nameName, ele.getTagName()));
@@ -502,7 +506,7 @@
List<Element> arrayEles =
XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_STRING_ARRAY).stream()
.filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName))
- .toList();
+ .collect(Collectors.toList());
if (arrayEles.size() > 1) {
throw new MalformedXmlException(
String.format(