Merge "Add required resources for bluechip/m3" into main
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 93febca4..2babf6a 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -96,14 +96,19 @@
android_ravenwood_libgroup {
name: "ravenwood-runtime",
libs: [
- "framework-minus-apex.ravenwood",
- "hoststubgen-helper-runtime.ravenwood",
- "hoststubgen-helper-framework-runtime.ravenwood",
+ // Prefixed with "200" to ensure it's sorted early in Tradefed classpath
+ // so that we provide a concrete implementation before Mainline stubs
+ "200-kxml2-android",
"all-updatable-modules-system-stubs",
+ "android.test.mock.ravenwood",
+ "framework-minus-apex.ravenwood",
+ "hoststubgen-helper-framework-runtime.ravenwood",
+ "hoststubgen-helper-runtime.ravenwood",
+
+ // Provide runtime versions of utils linked in below
"junit",
"truth",
"ravenwood-junit-impl",
- "android.test.mock.ravenwood",
"mockito-ravenwood-prebuilt",
"inline-mockito-ravenwood-prebuilt",
],
diff --git a/api/api.go b/api/api.go
index c733f5b..e885823 100644
--- a/api/api.go
+++ b/api/api.go
@@ -79,7 +79,45 @@
var PrepareForCombinedApisTest = android.FixtureRegisterWithContext(registerBuildComponents)
+func (a *CombinedApis) apiFingerprintStubDeps() []string {
+ ret := []string{}
+ ret = append(
+ ret,
+ transformArray(a.properties.Bootclasspath, "", ".stubs")...,
+ )
+ ret = append(
+ ret,
+ transformArray(a.properties.Bootclasspath, "", ".stubs.system")...,
+ )
+ ret = append(
+ ret,
+ transformArray(a.properties.Bootclasspath, "", ".stubs.module_lib")...,
+ )
+ ret = append(
+ ret,
+ transformArray(a.properties.System_server_classpath, "", ".stubs.system_server")...,
+ )
+ return ret
+}
+
+func (a *CombinedApis) DepsMutator(ctx android.BottomUpMutatorContext) {
+ ctx.AddDependency(ctx.Module(), nil, a.apiFingerprintStubDeps()...)
+}
+
func (a *CombinedApis) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ ctx.WalkDeps(func(child, parent android.Module) bool {
+ if _, ok := child.(java.AndroidLibraryDependency); ok && child.Name() != "framework-res" {
+ // Stubs of BCP and SSCP libraries should not have any dependencies on apps
+ // This check ensures that we do not run into circular dependencies when UNBUNDLED_BUILD_TARGET_SDK_WITH_API_FINGERPRINT=true
+ ctx.ModuleErrorf(
+ "Module %s is not a valid dependency of the stub library %s\n."+
+ "If this dependency has been added via `libs` of java_sdk_library, please move it to `impl_only_libs`\n",
+ child.Name(), parent.Name())
+ return false // error detected
+ }
+ return true
+ })
+
}
type genruleProps struct {
diff --git a/cmds/uinput/README.md b/cmds/uinput/README.md
index b6e4e0d..f177586 100644
--- a/cmds/uinput/README.md
+++ b/cmds/uinput/README.md
@@ -154,7 +154,8 @@
#### `delay`
-Add a delay to command processing
+Add a delay between the processing of commands. The delay will be timed from when the last delay
+ended, rather than from the current time, to allow for more precise timings to be produced.
| Field | Type | Description |
|:-------------:|:-------------:|:-------------------------- |
diff --git a/cmds/uinput/jni/com_android_commands_uinput_Device.cpp b/cmds/uinput/jni/com_android_commands_uinput_Device.cpp
index a78a465..bd61000 100644
--- a/cmds/uinput/jni/com_android_commands_uinput_Device.cpp
+++ b/cmds/uinput/jni/com_android_commands_uinput_Device.cpp
@@ -166,14 +166,14 @@
::ioctl(mFd, UI_DEV_DESTROY);
}
-void UinputDevice::injectEvent(uint16_t type, uint16_t code, int32_t value) {
+void UinputDevice::injectEvent(std::chrono::microseconds timestamp, uint16_t type, uint16_t code,
+ int32_t value) {
struct input_event event = {};
event.type = type;
event.code = code;
event.value = value;
- timespec ts;
- clock_gettime(CLOCK_MONOTONIC, &ts);
- TIMESPEC_TO_TIMEVAL(&event.time, &ts);
+ event.time.tv_sec = timestamp.count() / 1'000'000;
+ event.time.tv_usec = timestamp.count() % 1'000'000;
if (::write(mFd, &event, sizeof(input_event)) < 0) {
ALOGE("Could not write event %" PRIu16 " %" PRIu16 " with value %" PRId32 " : %s", type,
@@ -268,12 +268,12 @@
}
}
-static void injectEvent(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint type, jint code,
- jint value) {
+static void injectEvent(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jlong timestampMicros,
+ jint type, jint code, jint value) {
uinput::UinputDevice* d = reinterpret_cast<uinput::UinputDevice*>(ptr);
if (d != nullptr) {
- d->injectEvent(static_cast<uint16_t>(type), static_cast<uint16_t>(code),
- static_cast<int32_t>(value));
+ d->injectEvent(std::chrono::microseconds(timestampMicros), static_cast<uint16_t>(type),
+ static_cast<uint16_t>(code), static_cast<int32_t>(value));
} else {
ALOGE("Could not inject event, Device* is null!");
}
@@ -330,7 +330,7 @@
"(Ljava/lang/String;IIIIIILjava/lang/String;"
"Lcom/android/commands/uinput/Device$DeviceCallback;)J",
reinterpret_cast<void*>(openUinputDevice)},
- {"nativeInjectEvent", "(JIII)V", reinterpret_cast<void*>(injectEvent)},
+ {"nativeInjectEvent", "(JJIII)V", reinterpret_cast<void*>(injectEvent)},
{"nativeConfigure", "(II[I)V", reinterpret_cast<void*>(configure)},
{"nativeSetAbsInfo", "(IILandroid/os/Parcel;)V", reinterpret_cast<void*>(setAbsInfo)},
{"nativeCloseUinputDevice", "(J)V", reinterpret_cast<void*>(closeUinputDevice)},
diff --git a/cmds/uinput/jni/com_android_commands_uinput_Device.h b/cmds/uinput/jni/com_android_commands_uinput_Device.h
index 9769a75..72c8647 100644
--- a/cmds/uinput/jni/com_android_commands_uinput_Device.h
+++ b/cmds/uinput/jni/com_android_commands_uinput_Device.h
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-#include <memory>
-#include <vector>
-
+#include <android-base/unique_fd.h>
#include <jni.h>
#include <linux/input.h>
-#include <android-base/unique_fd.h>
+#include <chrono>
+#include <memory>
+#include <vector>
+
#include "src/com/android/commands/uinput/InputAbsInfo.h"
namespace android {
@@ -53,7 +54,8 @@
virtual ~UinputDevice();
- void injectEvent(uint16_t type, uint16_t code, int32_t value);
+ void injectEvent(std::chrono::microseconds timestamp, uint16_t type, uint16_t code,
+ int32_t value);
int handleEvents(int events);
private:
diff --git a/cmds/uinput/src/com/android/commands/uinput/Device.java b/cmds/uinput/src/com/android/commands/uinput/Device.java
index 25d3a34..b452fc7 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Device.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Device.java
@@ -55,7 +55,7 @@
private final SparseArray<InputAbsInfo> mAbsInfo;
private final OutputStream mOutputStream;
private final Object mCond = new Object();
- private long mTimeToSend;
+ private long mTimeToSendNanos;
static {
System.loadLibrary("uinputcommand_jni");
@@ -65,7 +65,8 @@
int productId, int versionId, int bus, int ffEffectsMax, String port,
DeviceCallback callback);
private static native void nativeCloseUinputDevice(long ptr);
- private static native void nativeInjectEvent(long ptr, int type, int code, int value);
+ private static native void nativeInjectEvent(long ptr, long timestampMicros, int type, int code,
+ int value);
private static native void nativeConfigure(int handle, int code, int[] configs);
private static native void nativeSetAbsInfo(int handle, int axisCode, Parcel axisParcel);
private static native int nativeGetEvdevEventTypeByLabel(String label);
@@ -101,27 +102,54 @@
}
mHandler.obtainMessage(MSG_OPEN_UINPUT_DEVICE, args).sendToTarget();
- mTimeToSend = SystemClock.uptimeMillis();
+ mTimeToSendNanos = SystemClock.uptimeNanos();
+ }
+
+ private long getTimeToSendMillis() {
+ // Since we can only specify delays in milliseconds but evemu timestamps are in
+ // microseconds, we have to round up the delays to avoid setting event timestamps
+ // which are in the future (which the kernel would silently reject and replace with
+ // the current time).
+ //
+ // This should be the same as (long) Math.ceil(mTimeToSendNanos / 1_000_000.0), except
+ // without the precision loss that comes from converting from long to double and back.
+ return mTimeToSendNanos / 1_000_000 + ((mTimeToSendNanos % 1_000_000 > 0) ? 1 : 0);
}
/**
* Inject uinput events to device
*
* @param events Array of raw uinput events.
+ * @param offsetMicros The difference in microseconds between the timestamps of the previous
+ * batch of events injected and this batch. If set to -1, the current
+ * timestamp will be used.
*/
- public void injectEvent(int[] events) {
+ public void injectEvent(int[] events, long offsetMicros) {
// if two messages are sent at identical time, they will be processed in order received
- Message msg = mHandler.obtainMessage(MSG_INJECT_EVENT, events);
- mHandler.sendMessageAtTime(msg, mTimeToSend);
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = events;
+ args.argl1 = offsetMicros;
+ args.argl2 = SystemClock.uptimeNanos();
+ Message msg = mHandler.obtainMessage(MSG_INJECT_EVENT, args);
+ mHandler.sendMessageAtTime(msg, getTimeToSendMillis());
}
/**
- * Impose a delay to the device for execution.
+ * Delay subsequent device activity by the specified amount of time.
*
- * @param delay Time to delay in unit of milliseconds.
+ * <p>Note that although the delay is specified in nanoseconds, due to limitations of {@link
+ * Handler}'s API, scheduling only occurs with millisecond precision. When scheduling an
+ * injection or sync, the time at which it is scheduled will be rounded up to the nearest
+ * millisecond. While this means that a particular injection cannot be scheduled precisely,
+ * rounding errors will not accumulate over time. For example, if five injections are scheduled
+ * with a delay of 1,200,000ns before each one, the total delay will be 6ms, as opposed to the
+ * 10ms it would have been if each individual delay had been rounded up (as {@link EvemuParser}
+ * would otherwise have to do to avoid sending timestamps that are in the future).
+ *
+ * @param delayNanos Time to delay in unit of nanoseconds.
*/
- public void addDelay(int delay) {
- mTimeToSend = Math.max(SystemClock.uptimeMillis(), mTimeToSend) + delay;
+ public void addDelayNanos(long delayNanos) {
+ mTimeToSendNanos += delayNanos;
}
/**
@@ -131,7 +159,8 @@
* @param syncToken The token for this sync command.
*/
public void syncEvent(String syncToken) {
- mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_SYNC_EVENT, syncToken), mTimeToSend);
+ mHandler.sendMessageAtTime(
+ mHandler.obtainMessage(MSG_SYNC_EVENT, syncToken), getTimeToSendMillis());
}
/**
@@ -140,7 +169,8 @@
*/
public void close() {
Message msg = mHandler.obtainMessage(MSG_CLOSE_UINPUT_DEVICE);
- mHandler.sendMessageAtTime(msg, Math.max(SystemClock.uptimeMillis(), mTimeToSend) + 1);
+ mHandler.sendMessageAtTime(
+ msg, Math.max(SystemClock.uptimeMillis(), getTimeToSendMillis()) + 1);
try {
synchronized (mCond) {
mCond.wait();
@@ -151,6 +181,7 @@
private class DeviceHandler extends Handler {
private long mPtr;
+ private long mLastInjectTimestampMicros = -1;
private int mBarrierToken;
DeviceHandler(Looper looper) {
@@ -160,7 +191,7 @@
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_OPEN_UINPUT_DEVICE:
+ case MSG_OPEN_UINPUT_DEVICE: {
SomeArgs args = (SomeArgs) msg.obj;
String name = (String) args.arg1;
mPtr = nativeOpenUinputDevice(name, args.argi1 /* id */,
@@ -177,15 +208,43 @@
}
args.recycle();
break;
- case MSG_INJECT_EVENT:
- if (mPtr != 0) {
- int[] events = (int[]) msg.obj;
- for (int pos = 0; pos + 2 < events.length; pos += 3) {
- nativeInjectEvent(mPtr, events[pos], events[pos + 1], events[pos + 2]);
- }
+ }
+ case MSG_INJECT_EVENT: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ if (mPtr == 0) {
+ args.recycle();
+ break;
}
+ long offsetMicros = args.argl1;
+ if (mLastInjectTimestampMicros == -1 || offsetMicros == -1) {
+ // There's often a delay of a few milliseconds between the time specified to
+ // Handler.sendMessageAtTime and the handler actually being called, due to
+ // the way threads are scheduled. We don't take this into account when
+ // calling addDelayNanos between the first batch of event injections (when
+ // we set the "base timestamp" from which all others will be offset) and the
+ // second batch, meaning that the actual time between the handler calls for
+ // those batches may be less than the offset between their timestamps. When
+ // that happens, we would pass a timestamp for the second batch that's
+ // actually in the future. The kernel's uinput API rejects timestamps that
+ // are in the future and uses the current time instead, making the reported
+ // timestamps inconsistent with the recording we're replaying.
+ //
+ // To prevent this, we need to use the time at which we scheduled this first
+ // batch, rather than the actual current time.
+ mLastInjectTimestampMicros = args.argl2 / 1000;
+ } else {
+ mLastInjectTimestampMicros += offsetMicros;
+ }
+
+ int[] events = (int[]) args.arg1;
+ for (int pos = 0; pos + 2 < events.length; pos += 3) {
+ nativeInjectEvent(mPtr, mLastInjectTimestampMicros, events[pos],
+ events[pos + 1], events[pos + 2]);
+ }
+ args.recycle();
break;
- case MSG_CLOSE_UINPUT_DEVICE:
+ }
+ case MSG_CLOSE_UINPUT_DEVICE: {
if (mPtr != 0) {
nativeCloseUinputDevice(mPtr);
getLooper().quitSafely();
@@ -198,11 +257,14 @@
mCond.notify();
}
break;
- case MSG_SYNC_EVENT:
+ }
+ case MSG_SYNC_EVENT: {
handleSyncEvent((String) msg.obj);
break;
- default:
+ }
+ default: {
throw new IllegalArgumentException("Unknown device message");
+ }
}
}
diff --git a/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java b/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java
index 7652f24..da99162 100644
--- a/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java
+++ b/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java
@@ -44,7 +44,7 @@
* recordings, this will always be the same.
*/
private static final int DEVICE_ID = 1;
- private static final int REGISTRATION_DELAY_MILLIS = 500;
+ private static final int REGISTRATION_DELAY_NANOS = 500_000_000;
private static class CommentAwareReader {
private final LineNumberReader mReader;
@@ -152,7 +152,7 @@
final Event.Builder delayEb = new Event.Builder();
delayEb.setId(DEVICE_ID);
delayEb.setCommand(Event.Command.DELAY);
- delayEb.setDurationMillis(REGISTRATION_DELAY_MILLIS);
+ delayEb.setDurationNanos(REGISTRATION_DELAY_NANOS);
mQueuedEvents.add(delayEb.build());
}
@@ -175,7 +175,6 @@
throw new ParsingException(
"Invalid timestamp '" + parts[0] + "' (should contain a single '.')", mReader);
}
- // TODO(b/310958309): use timeMicros to set the timestamp on the event being sent.
final long timeMicros =
parseLong(timeParts[0], 10) * 1_000_000 + parseInt(timeParts[1], 10);
final Event.Builder eb = new Event.Builder();
@@ -192,21 +191,18 @@
return eb.build();
} else {
final long delayMicros = timeMicros - mLastEventTimeMicros;
- // The shortest delay supported by Handler.sendMessageAtTime (used for timings by the
- // Device class) is 1ms, so ignore time differences smaller than that.
- if (delayMicros < 1000) {
- mLastEventTimeMicros = timeMicros;
+ eb.setTimestampOffsetMicros(delayMicros);
+ if (delayMicros == 0) {
return eb.build();
- } else {
- // Send a delay now, and queue the actual event for the next call.
- mQueuedEvents.add(eb.build());
- mLastEventTimeMicros = timeMicros;
- final Event.Builder delayEb = new Event.Builder();
- delayEb.setId(DEVICE_ID);
- delayEb.setCommand(Event.Command.DELAY);
- delayEb.setDurationMillis((int) (delayMicros / 1000));
- return delayEb.build();
}
+ // Send a delay now, and queue the actual event for the next call.
+ mQueuedEvents.add(eb.build());
+ mLastEventTimeMicros = timeMicros;
+ final Event.Builder delayEb = new Event.Builder();
+ delayEb.setId(DEVICE_ID);
+ delayEb.setCommand(Event.Command.DELAY);
+ delayEb.setDurationNanos(delayMicros * 1000);
+ return delayEb.build();
}
}
diff --git a/cmds/uinput/src/com/android/commands/uinput/Event.java b/cmds/uinput/src/com/android/commands/uinput/Event.java
index 0f16a27..9e7ee09 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Event.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Event.java
@@ -99,8 +99,9 @@
private int mVersionId;
private int mBusId;
private int[] mInjections;
+ private long mTimestampOffsetMicros = -1;
private SparseArray<int[]> mConfiguration;
- private int mDurationMillis;
+ private long mDurationNanos;
private int mFfEffectsMax = 0;
private String mInputPort;
private SparseArray<InputAbsInfo> mAbsInfo;
@@ -139,19 +140,28 @@
}
/**
+ * Returns the number of microseconds that should be added to the previous {@code INJECT}
+ * event's timestamp to produce the timestamp for this {@code INJECT} event. A value of -1
+ * indicates that the current timestamp should be used instead.
+ */
+ public long getTimestampOffsetMicros() {
+ return mTimestampOffsetMicros;
+ }
+
+ /**
* Returns a {@link SparseArray} describing the event codes that should be registered for the
* device. The keys are uinput ioctl codes (such as those returned from {@link
* UinputControlCode#getValue()}, while the values are arrays of event codes to be enabled with
* those ioctls. For example, key 101 (corresponding to {@link UinputControlCode#UI_SET_KEYBIT})
- * could have values 0x110 ({@code BTN_LEFT}, 0x111 ({@code BTN_RIGHT}), and 0x112
+ * could have values 0x110 ({@code BTN_LEFT}), 0x111 ({@code BTN_RIGHT}), and 0x112
* ({@code BTN_MIDDLE}).
*/
public SparseArray<int[]> getConfiguration() {
return mConfiguration;
}
- public int getDurationMillis() {
- return mDurationMillis;
+ public long getDurationNanos() {
+ return mDurationNanos;
}
public int getFfEffectsMax() {
@@ -182,7 +192,7 @@
+ ", busId=" + mBusId
+ ", events=" + Arrays.toString(mInjections)
+ ", configuration=" + mConfiguration
- + ", duration=" + mDurationMillis + "ms"
+ + ", duration=" + mDurationNanos + "ns"
+ ", ff_effects_max=" + mFfEffectsMax
+ ", port=" + mInputPort
+ "}";
@@ -211,6 +221,10 @@
mEvent.mInjections = events;
}
+ public void setTimestampOffsetMicros(long offsetMicros) {
+ mEvent.mTimestampOffsetMicros = offsetMicros;
+ }
+
/**
* Sets the event codes that should be registered with a {@code register} command.
*
@@ -237,8 +251,8 @@
mEvent.mBusId = busId;
}
- public void setDurationMillis(int durationMillis) {
- mEvent.mDurationMillis = durationMillis;
+ public void setDurationNanos(long durationNanos) {
+ mEvent.mDurationNanos = durationNanos;
}
public void setFfEffectsMax(int ffEffectsMax) {
@@ -271,7 +285,7 @@
}
}
case DELAY -> {
- if (mEvent.mDurationMillis <= 0) {
+ if (mEvent.mDurationNanos <= 0) {
throw new IllegalStateException("Delay has missing or invalid duration");
}
}
diff --git a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java
index ed3ff33..6994f0c 100644
--- a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java
+++ b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java
@@ -71,7 +71,8 @@
case "configuration" -> eb.setConfiguration(readConfiguration());
case "ff_effects_max" -> eb.setFfEffectsMax(readInt());
case "abs_info" -> eb.setAbsInfo(readAbsInfoArray());
- case "duration" -> eb.setDurationMillis(readInt());
+ // Duration is specified in milliseconds in the JSON-style format.
+ case "duration" -> eb.setDurationNanos(readInt() * 1_000_000L);
case "port" -> eb.setInputPort(mReader.nextString());
case "syncToken" -> eb.setSyncToken(mReader.nextString());
default -> mReader.skipValue();
diff --git a/cmds/uinput/src/com/android/commands/uinput/Uinput.java b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
index 04df279..760e981 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Uinput.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
@@ -134,8 +134,8 @@
switch (Objects.requireNonNull(e.getCommand())) {
case REGISTER ->
error("Device id=" + e.getId() + " is already registered. Ignoring event.");
- case INJECT -> d.injectEvent(e.getInjections());
- case DELAY -> d.addDelay(e.getDurationMillis());
+ case INJECT -> d.injectEvent(e.getInjections(), e.getTimestampOffsetMicros());
+ case DELAY -> d.addDelayNanos(e.getDurationNanos());
case SYNC -> d.syncEvent(e.getSyncToken());
}
}
diff --git a/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java b/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java
index a05cc67..5239fbc 100644
--- a/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java
+++ b/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java
@@ -183,16 +183,22 @@
}
private void assertInjectEvent(Event event, int eventType, int eventCode, int value) {
+ assertInjectEvent(event, eventType, eventCode, value, 0);
+ }
+
+ private void assertInjectEvent(Event event, int eventType, int eventCode, int value,
+ long timestampOffsetMicros) {
assertThat(event).isNotNull();
assertThat(event.getCommand()).isEqualTo(Event.Command.INJECT);
assertThat(event.getInjections()).asList()
.containsExactly(eventType, eventCode, value).inOrder();
+ assertThat(event.getTimestampOffsetMicros()).isEqualTo(timestampOffsetMicros);
}
- private void assertDelayEvent(Event event, int durationMillis) {
+ private void assertDelayEvent(Event event, int durationNanos) {
assertThat(event).isNotNull();
assertThat(event.getCommand()).isEqualTo(Event.Command.DELAY);
- assertThat(event.getDurationMillis()).isEqualTo(durationMillis);
+ assertThat(event.getDurationNanos()).isEqualTo(durationNanos);
}
@Test
@@ -207,7 +213,7 @@
EvemuParser parser = new EvemuParser(reader);
assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.REGISTER);
assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY);
- assertInjectEvent(parser.getNextEvent(), 0x2, 0x0, 1);
+ assertInjectEvent(parser.getNextEvent(), 0x2, 0x0, 1, -1);
assertInjectEvent(parser.getNextEvent(), 0x2, 0x1, -2);
assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
}
@@ -228,17 +234,17 @@
assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.REGISTER);
assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY);
- assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 1);
+ assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 1, -1);
assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
- assertDelayEvent(parser.getNextEvent(), 10);
+ assertDelayEvent(parser.getNextEvent(), 10_000_000);
- assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 0);
+ assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 0, 10_000);
assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
- assertDelayEvent(parser.getNextEvent(), 1000);
+ assertDelayEvent(parser.getNextEvent(), 1_000_000_000);
- assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 1);
+ assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 1, 1_000_000);
assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
}
@@ -477,7 +483,7 @@
assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY);
- assertInjectEvent(parser.getNextEvent(), 0x3, 0x39, 0);
+ assertInjectEvent(parser.getNextEvent(), 0x3, 0x39, 0, -1);
assertInjectEvent(parser.getNextEvent(), 0x3, 0x35, 891);
assertInjectEvent(parser.getNextEvent(), 0x3, 0x36, 333);
assertInjectEvent(parser.getNextEvent(), 0x3, 0x3a, 56);
@@ -490,8 +496,8 @@
assertInjectEvent(parser.getNextEvent(), 0x3, 0x18, 56);
assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
- assertDelayEvent(parser.getNextEvent(), 6);
+ assertDelayEvent(parser.getNextEvent(), 6_080_000);
- assertInjectEvent(parser.getNextEvent(), 0x3, 0x0035, 888);
+ assertInjectEvent(parser.getNextEvent(), 0x3, 0x0035, 888, 6_080);
}
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 1748763..05d510d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -165,6 +165,7 @@
field public static final String MANAGE_DEVICE_POLICY_LOCK = "android.permission.MANAGE_DEVICE_POLICY_LOCK";
field public static final String MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS = "android.permission.MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS";
field public static final String MANAGE_DEVICE_POLICY_LOCK_TASK = "android.permission.MANAGE_DEVICE_POLICY_LOCK_TASK";
+ field @FlaggedApi("android.app.admin.flags.esim_management_enabled") public static final String MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS = "android.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS";
field public static final String MANAGE_DEVICE_POLICY_METERED_DATA = "android.permission.MANAGE_DEVICE_POLICY_METERED_DATA";
field public static final String MANAGE_DEVICE_POLICY_MICROPHONE = "android.permission.MANAGE_DEVICE_POLICY_MICROPHONE";
field public static final String MANAGE_DEVICE_POLICY_MOBILE_NETWORK = "android.permission.MANAGE_DEVICE_POLICY_MOBILE_NETWORK";
@@ -653,6 +654,7 @@
field public static final int contentInsetRight = 16843862; // 0x1010456
field public static final int contentInsetStart = 16843859; // 0x1010453
field public static final int contentInsetStartWithNavigation = 16844066; // 0x1010522
+ field @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") public static final int contentSensitivity;
field public static final int contextClickable = 16844007; // 0x10104e7
field public static final int contextDescription = 16844078; // 0x101052e
field public static final int contextPopupMenuStyle = 16844033; // 0x1010501
@@ -1602,6 +1604,7 @@
field public static final int supportedTypes = 16844369; // 0x1010651
field public static final int supportsAssist = 16844016; // 0x10104f0
field public static final int supportsBatteryGameMode = 16844374; // 0x1010656
+ field @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public static final int supportsConnectionlessStylusHandwriting;
field public static final int supportsInlineSuggestions = 16844301; // 0x101060d
field public static final int supportsInlineSuggestionsWithTouchExploration = 16844397; // 0x101066d
field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1
@@ -8065,6 +8068,7 @@
method public CharSequence getStartUserSessionMessage(@NonNull android.content.ComponentName);
method @Deprecated public boolean getStorageEncryption(@Nullable android.content.ComponentName);
method public int getStorageEncryptionStatus();
+ method @FlaggedApi("android.app.admin.flags.esim_management_enabled") @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS) public java.util.Set<java.lang.Integer> getSubscriptionsIds();
method @Nullable public android.app.admin.SystemUpdatePolicy getSystemUpdatePolicy();
method @Nullable public android.os.PersistableBundle getTransferOwnershipBundle();
method @Nullable public java.util.List<android.os.PersistableBundle> getTrustAgentConfiguration(@Nullable android.content.ComponentName, @NonNull android.content.ComponentName);
@@ -10746,6 +10750,7 @@
field public static final String PERFORMANCE_HINT_SERVICE = "performance_hint";
field public static final String POWER_SERVICE = "power";
field public static final String PRINT_SERVICE = "print";
+ field @FlaggedApi("android.os.telemetry_apis_framework_initialization") public static final String PROFILING_SERVICE = "profiling";
field public static final int RECEIVER_EXPORTED = 2; // 0x2
field public static final int RECEIVER_NOT_EXPORTED = 4; // 0x4
field public static final int RECEIVER_VISIBLE_TO_INSTANT_APPS = 1; // 0x1
@@ -11336,10 +11341,14 @@
field public static final String EXTRA_CHANGED_COMPONENT_NAME_LIST = "android.intent.extra.changed_component_name_list";
field public static final String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list";
field public static final String EXTRA_CHANGED_UID_LIST = "android.intent.extra.changed_uid_list";
+ field @FlaggedApi("android.service.chooser.chooser_payload_toggling") public static final String EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI = "android.intent.extra.CHOOSER_ADDITIONAL_CONTENT_URI";
field @FlaggedApi("android.service.chooser.chooser_album_text") public static final String EXTRA_CHOOSER_CONTENT_TYPE_HINT = "android.intent.extra.CHOOSER_CONTENT_TYPE_HINT";
field public static final String EXTRA_CHOOSER_CUSTOM_ACTIONS = "android.intent.extra.CHOOSER_CUSTOM_ACTIONS";
+ field @FlaggedApi("android.service.chooser.chooser_payload_toggling") public static final String EXTRA_CHOOSER_FOCUSED_ITEM_POSITION = "android.intent.extra.CHOOSER_FOCUSED_ITEM_POSITION";
field public static final String EXTRA_CHOOSER_MODIFY_SHARE_ACTION = "android.intent.extra.CHOOSER_MODIFY_SHARE_ACTION";
field public static final String EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER";
+ field @FlaggedApi("android.service.chooser.enable_chooser_result") public static final String EXTRA_CHOOSER_RESULT = "android.intent.extra.CHOOSER_RESULT";
+ field @FlaggedApi("android.service.chooser.enable_chooser_result") public static final String EXTRA_CHOOSER_RESULT_INTENT_SENDER = "android.intent.extra.CHOOSER_RESULT_INTENT_SENDER";
field public static final String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS";
field public static final String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
field public static final String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
@@ -13059,6 +13068,7 @@
field public static final String FEATURE_AUDIO_LOW_LATENCY = "android.hardware.audio.low_latency";
field public static final String FEATURE_AUDIO_OUTPUT = "android.hardware.audio.output";
field public static final String FEATURE_AUDIO_PRO = "android.hardware.audio.pro";
+ field @FlaggedApi("android.media.audio.feature_spatial_audio_headtracking_low_latency") public static final String FEATURE_AUDIO_SPATIAL_HEADTRACKING_LOW_LATENCY = "android.hardware.audio.spatial.headtracking.low_latency";
field public static final String FEATURE_AUTOFILL = "android.software.autofill";
field public static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
field public static final String FEATURE_BACKUP = "android.software.backup";
@@ -25904,8 +25914,11 @@
@FlaggedApi("com.android.media.editing.flags.add_media_metrics_editing") public final class EditingEndedEvent extends android.media.metrics.Event implements android.os.Parcelable {
method public int describeContents();
method public int getErrorCode();
+ method @Nullable public String getExporterName();
+ method public float getFinalProgressPercent();
method public int getFinalState();
method @NonNull public java.util.List<android.media.metrics.MediaItemInfo> getInputMediaItemInfos();
+ method @Nullable public String getMuxerName();
method public long getOperationTypes();
method @Nullable public android.media.metrics.MediaItemInfo getOutputMediaItemInfo();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -25940,6 +25953,7 @@
field public static final long OPERATION_TYPE_VIDEO_EDIT = 4L; // 0x4L
field public static final long OPERATION_TYPE_VIDEO_TRANSCODE = 1L; // 0x1L
field public static final long OPERATION_TYPE_VIDEO_TRANSMUX = 16L; // 0x10L
+ field public static final int PROGRESS_PERCENT_UNKNOWN = -1; // 0xffffffff
field public static final int TIME_SINCE_CREATED_UNKNOWN = -1; // 0xffffffff
}
@@ -25949,7 +25963,10 @@
method @NonNull public android.media.metrics.EditingEndedEvent.Builder addOperationType(long);
method @NonNull public android.media.metrics.EditingEndedEvent build();
method @NonNull public android.media.metrics.EditingEndedEvent.Builder setErrorCode(int);
+ method @NonNull public android.media.metrics.EditingEndedEvent.Builder setExporterName(@NonNull String);
+ method @NonNull public android.media.metrics.EditingEndedEvent.Builder setFinalProgressPercent(@FloatRange(from=0, to=100) float);
method @NonNull public android.media.metrics.EditingEndedEvent.Builder setMetricsBundle(@NonNull android.os.Bundle);
+ method @NonNull public android.media.metrics.EditingEndedEvent.Builder setMuxerName(@NonNull String);
method @NonNull public android.media.metrics.EditingEndedEvent.Builder setOutputMediaItemInfo(@NonNull android.media.metrics.MediaItemInfo);
method @NonNull public android.media.metrics.EditingEndedEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=android.media.metrics.EditingEndedEvent.TIME_SINCE_CREATED_UNKNOWN) long);
}
@@ -27827,7 +27844,7 @@
package android.media.tv.ad {
- @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public class TvAdManager {
+ @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public final class TvAdManager {
method @NonNull public java.util.List<android.media.tv.ad.TvAdServiceInfo> getTvAdServiceList();
method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdManager.TvAdServiceCallback);
method public void sendAppLinkCommand(@NonNull String, @NonNull android.os.Bundle);
@@ -27838,6 +27855,14 @@
field public static final String APP_LINK_KEY_COMMAND_TYPE = "command_type";
field public static final String APP_LINK_KEY_PACKAGE_NAME = "package_name";
field public static final String APP_LINK_KEY_SERVICE_ID = "service_id";
+ field public static final int ERROR_BLOCKED = 5; // 0x5
+ field public static final int ERROR_ENCRYPTED = 6; // 0x6
+ field public static final int ERROR_NONE = 0; // 0x0
+ field public static final int ERROR_NOT_SUPPORTED = 2; // 0x2
+ field public static final int ERROR_RESOURCE_UNAVAILABLE = 4; // 0x4
+ field public static final int ERROR_UNKNOWN = 1; // 0x1
+ field public static final int ERROR_UNKNOWN_CHANNEL = 7; // 0x7
+ field public static final int ERROR_WEAK_SIGNAL = 3; // 0x3
field public static final String INTENT_KEY_AD_SERVICE_ID = "ad_service_id";
field public static final String INTENT_KEY_CHANNEL_URI = "channel_uri";
field public static final String INTENT_KEY_COMMAND_TYPE = "command_type";
@@ -27850,6 +27875,9 @@
field public static final String SESSION_DATA_TYPE_AD_REQUEST = "ad_request";
field public static final String SESSION_DATA_TYPE_BROADCAST_INFO_REQUEST = "broadcast_info_request";
field public static final String SESSION_DATA_TYPE_REMOVE_BROADCAST_INFO_REQUEST = "remove_broadcast_info_request";
+ field public static final int SESSION_STATE_ERROR = 3; // 0x3
+ field public static final int SESSION_STATE_RUNNING = 2; // 0x2
+ field public static final int SESSION_STATE_STOPPED = 1; // 0x1
}
public abstract static class TvAdManager.TvAdServiceCallback {
@@ -27872,7 +27900,12 @@
ctor public TvAdService.Session(@NonNull android.content.Context);
method public boolean isMediaViewEnabled();
method @CallSuper public void layoutSurface(int, int, int, int);
+ method @CallSuper public void notifySessionStateChanged(int, int);
method @Nullable public android.view.View onCreateMediaView();
+ method public void onCurrentChannelUri(@Nullable android.net.Uri);
+ method public void onCurrentTvInputId(@Nullable String);
+ method public void onCurrentVideoBounds(@NonNull android.graphics.Rect);
+ method public void onError(@NonNull String, @NonNull android.os.Bundle);
method public boolean onGenericMotionEvent(@NonNull android.view.MotionEvent);
method public boolean onKeyDown(int, @Nullable android.view.KeyEvent);
method public boolean onKeyLongPress(int, @Nullable android.view.KeyEvent);
@@ -27882,12 +27915,20 @@
method public abstract void onRelease();
method public void onResetAdService();
method public abstract boolean onSetSurface(@Nullable android.view.Surface);
+ method public void onSigningResult(@NonNull String, @NonNull byte[]);
method public void onStartAdService();
method public void onStopAdService();
method public void onSurfaceChanged(int, int, int);
method public boolean onTouchEvent(@NonNull android.view.MotionEvent);
+ method public void onTrackInfoList(@NonNull java.util.List<android.media.tv.TvTrackInfo>);
method public boolean onTrackballEvent(@NonNull android.view.MotionEvent);
method public void onTvInputSessionData(@NonNull String, @NonNull android.os.Bundle);
+ method public void onTvMessage(int, @NonNull android.os.Bundle);
+ method @CallSuper public void requestCurrentChannelUri();
+ method @CallSuper public void requestCurrentTvInputId();
+ method @CallSuper public void requestCurrentVideoBounds();
+ method @CallSuper public void requestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
+ method @CallSuper public void requestTrackInfoList();
method public void sendTvAdSessionData(@NonNull String, @NonNull android.os.Bundle);
method @CallSuper public void setMediaViewEnabled(boolean);
}
@@ -27906,9 +27947,12 @@
ctor public TvAdView(@NonNull android.content.Context);
ctor public TvAdView(@NonNull android.content.Context, @Nullable android.util.AttributeSet);
ctor public TvAdView(@NonNull android.content.Context, @Nullable android.util.AttributeSet, int);
+ method public void clearCallback();
method public void clearOnUnhandledInputEventListener();
method public boolean dispatchUnhandledInputEvent(@NonNull android.view.InputEvent);
method @Nullable public android.media.tv.ad.TvAdView.OnUnhandledInputEventListener getOnUnhandledInputEventListener();
+ method public void notifyError(@NonNull String, @NonNull android.os.Bundle);
+ method public void notifyTvMessage(@NonNull int, @NonNull android.os.Bundle);
method public void onAttachedToWindow();
method public void onDetachedFromWindow();
method public void onLayout(boolean, int, int, int, int);
@@ -27918,16 +27962,34 @@
method public void prepareAdService(@NonNull String, @NonNull String);
method public void reset();
method public void resetAdService();
+ method public void sendCurrentChannelUri(@Nullable android.net.Uri);
+ method public void sendCurrentTvInputId(@Nullable String);
+ method public void sendCurrentVideoBounds(@NonNull android.graphics.Rect);
+ method public void sendSigningResult(@NonNull String, @NonNull byte[]);
+ method public void sendTrackInfoList(@Nullable java.util.List<android.media.tv.TvTrackInfo>);
+ method public void setCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdView.TvAdCallback);
method public void setOnUnhandledInputEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdView.OnUnhandledInputEventListener);
method public boolean setTvView(@Nullable android.media.tv.TvView);
method public void startAdService();
method public void stopAdService();
+ field public static final String ERROR_KEY_ERROR_CODE = "error_code";
+ field public static final String ERROR_KEY_METHOD_NAME = "method_name";
}
public static interface TvAdView.OnUnhandledInputEventListener {
method public boolean onUnhandledInputEvent(@NonNull android.view.InputEvent);
}
+ public abstract static class TvAdView.TvAdCallback {
+ ctor public TvAdView.TvAdCallback();
+ method public void onRequestCurrentChannelUri(@NonNull String);
+ method public void onRequestCurrentTvInputId(@NonNull String);
+ method public void onRequestCurrentVideoBounds(@NonNull String);
+ method public void onRequestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
+ method public void onRequestTrackInfoList(@NonNull String);
+ method public void onStateChanged(@NonNull String, int, int);
+ }
+
}
package android.media.tv.interactive {
@@ -35501,6 +35563,7 @@
ctor public CallLog.Calls();
method public static String getLastOutgoingCall(android.content.Context);
field public static final int ANSWERED_EXTERNALLY_TYPE = 7; // 0x7
+ field @FlaggedApi("com.android.server.telecom.flags.business_call_composer") public static final String ASSERTED_DISPLAY_NAME = "asserted_display_name";
field public static final long AUTO_MISSED_EMERGENCY_CALL = 1L; // 0x1L
field public static final long AUTO_MISSED_MAXIMUM_DIALING = 4L; // 0x4L
field public static final long AUTO_MISSED_MAXIMUM_RINGING = 2L; // 0x2L
@@ -35547,6 +35610,7 @@
field public static final int FEATURES_WIFI = 8; // 0x8
field public static final String GEOCODED_LOCATION = "geocoded_location";
field public static final int INCOMING_TYPE = 1; // 0x1
+ field @FlaggedApi("com.android.server.telecom.flags.business_call_composer") public static final String IS_BUSINESS_CALL = "is_business_call";
field public static final String IS_READ = "is_read";
field public static final String LAST_MODIFIED = "last_modified";
field public static final String LIMIT_PARAM_KEY = "limit";
@@ -40246,6 +40310,21 @@
package android.service.chooser {
+ @FlaggedApi("android.service.chooser.chooser_payload_toggling") public interface AdditionalContentContract {
+ }
+
+ public static interface AdditionalContentContract.Columns {
+ field public static final String URI = "uri";
+ }
+
+ public static interface AdditionalContentContract.CursorExtraKeys {
+ field public static final String POSITION = "position";
+ }
+
+ public static interface AdditionalContentContract.MethodNames {
+ field public static final String ON_SELECTION_CHANGED = "onSelectionChanged";
+ }
+
public final class ChooserAction implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.app.PendingIntent getAction();
@@ -40260,6 +40339,19 @@
method @NonNull public android.service.chooser.ChooserAction build();
}
+ @FlaggedApi("android.service.chooser.enable_chooser_result") public final class ChooserResult implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.content.ComponentName getSelectedComponent();
+ method public int getType();
+ method public boolean isShortcut();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int CHOOSER_RESULT_COPY = 1; // 0x1
+ field public static final int CHOOSER_RESULT_EDIT = 2; // 0x2
+ field public static final int CHOOSER_RESULT_SELECTED_COMPONENT = 0; // 0x0
+ field public static final int CHOOSER_RESULT_UNKNOWN = -1; // 0xffffffff
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.chooser.ChooserResult> CREATOR;
+ }
+
@Deprecated public final class ChooserTarget implements android.os.Parcelable {
ctor @Deprecated public ChooserTarget(CharSequence, android.graphics.drawable.Icon, float, android.content.ComponentName, @Nullable android.os.Bundle);
method @Deprecated public int describeContents();
@@ -42024,8 +42116,10 @@
field @Deprecated public static final String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts";
field public static final String EVENT_CLEAR_DIAGNOSTIC_MESSAGE = "android.telecom.event.CLEAR_DIAGNOSTIC_MESSAGE";
field public static final String EVENT_DISPLAY_DIAGNOSTIC_MESSAGE = "android.telecom.event.DISPLAY_DIAGNOSTIC_MESSAGE";
+ field @FlaggedApi("com.android.server.telecom.flags.business_call_composer") public static final String EXTRA_ASSERTED_DISPLAY_NAME = "android.telecom.extra.ASSERTED_DISPLAY_NAME";
field public static final String EXTRA_DIAGNOSTIC_MESSAGE = "android.telecom.extra.DIAGNOSTIC_MESSAGE";
field public static final String EXTRA_DIAGNOSTIC_MESSAGE_ID = "android.telecom.extra.DIAGNOSTIC_MESSAGE_ID";
+ field @FlaggedApi("com.android.server.telecom.flags.business_call_composer") public static final String EXTRA_IS_BUSINESS_CALL = "android.telecom.extra.IS_BUSINESS_CALL";
field public static final String EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB = "android.telecom.extra.IS_SUPPRESSED_BY_DO_NOT_DISTURB";
field public static final String EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS = "android.telecom.extra.LAST_EMERGENCY_CALLBACK_TIME_MILLIS";
field public static final String EXTRA_SILENT_RINGING_REQUESTED = "android.telecom.extra.SILENT_RINGING_REQUESTED";
@@ -43653,6 +43747,7 @@
field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool";
field public static final String KEY_SUBSCRIPTION_GROUP_UUID_STRING = "subscription_group_uuid_string";
field public static final String KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY = "supported_premium_capabilities_int_array";
+ field @FlaggedApi("com.android.server.telecom.flags.business_call_composer") public static final String KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL = "supports_business_call_composer_bool";
field public static final String KEY_SUPPORTS_CALL_COMPOSER_BOOL = "supports_call_composer_bool";
field public static final String KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL = "supports_device_to_device_communication_using_dtmf_bool";
field public static final String KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL = "supports_device_to_device_communication_using_rtp_bool";
@@ -45918,6 +46013,7 @@
field public static final int AUTHTYPE_EAP_SIM = 128; // 0x80
field public static final int AUTHTYPE_GBA_BOOTSTRAP = 132; // 0x84
field public static final int AUTHTYPE_GBA_NAF_KEY_EXTERNAL = 133; // 0x85
+ field @FlaggedApi("com.android.server.telecom.flags.business_call_composer") public static final int CALL_COMPOSER_STATUS_BUSINESS_ONLY = 2; // 0x2
field public static final int CALL_COMPOSER_STATUS_OFF = 0; // 0x0
field public static final int CALL_COMPOSER_STATUS_ON = 1; // 0x1
field public static final int CALL_STATE_IDLE = 0; // 0x0
@@ -46470,8 +46566,8 @@
public class EuiccManager {
method @NonNull public android.telephony.euicc.EuiccManager createForCardId(int);
- method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void deleteSubscription(int, android.app.PendingIntent);
- method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void downloadSubscription(android.telephony.euicc.DownloadableSubscription, boolean, android.app.PendingIntent);
+ method @RequiresPermission(anyOf={"android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS", android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS}) public void deleteSubscription(int, android.app.PendingIntent);
+ method @RequiresPermission(anyOf={"android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS", android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS}) public void downloadSubscription(android.telephony.euicc.DownloadableSubscription, boolean, android.app.PendingIntent);
method @FlaggedApi("com.android.internal.telephony.flags.esim_available_memory") @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE", "carrier privileges"}) public long getAvailableMemoryInBytes();
method @Nullable public String getEid();
method @Nullable public android.telephony.euicc.EuiccInfo getEuiccInfo();
@@ -46954,6 +47050,7 @@
public static class MmTelFeature.MmTelCapabilities {
method public final boolean isCapable(int);
field public static final int CAPABILITY_TYPE_CALL_COMPOSER = 16; // 0x10
+ field @FlaggedApi("com.android.server.telecom.flags.business_call_composer") public static final int CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY = 32; // 0x20
field public static final int CAPABILITY_TYPE_SMS = 8; // 0x8
field public static final int CAPABILITY_TYPE_UT = 4; // 0x4
field public static final int CAPABILITY_TYPE_VIDEO = 2; // 0x2
@@ -54023,8 +54120,11 @@
method @Deprecated @NonNull public android.view.WindowInsets consumeDisplayCutout();
method @Deprecated @NonNull public android.view.WindowInsets consumeStableInsets();
method @Deprecated @NonNull public android.view.WindowInsets consumeSystemWindowInsets();
+ method @FlaggedApi("android.view.flags.customizable_window_headers") @NonNull public java.util.List<android.graphics.Rect> getBoundingRects(int);
+ method @FlaggedApi("android.view.flags.customizable_window_headers") @NonNull public java.util.List<android.graphics.Rect> getBoundingRectsIgnoringVisibility(int);
method @Nullable public android.view.DisplayCutout getDisplayCutout();
method @Nullable public android.view.DisplayShape getDisplayShape();
+ method @FlaggedApi("android.view.flags.customizable_window_headers") @NonNull public android.util.Size getFrame();
method @NonNull public android.graphics.Insets getInsets(int);
method @NonNull public android.graphics.Insets getInsetsIgnoringVisibility(int);
method @Deprecated @NonNull public android.graphics.Insets getMandatorySystemGestureInsets();
@@ -54059,8 +54159,11 @@
ctor public WindowInsets.Builder();
ctor public WindowInsets.Builder(@NonNull android.view.WindowInsets);
method @NonNull public android.view.WindowInsets build();
+ method @FlaggedApi("android.view.flags.customizable_window_headers") @NonNull public android.view.WindowInsets.Builder setBoundingRects(int, @NonNull java.util.List<android.graphics.Rect>);
+ method @FlaggedApi("android.view.flags.customizable_window_headers") @NonNull public android.view.WindowInsets.Builder setBoundingRectsIgnoringVisibility(int, @NonNull java.util.List<android.graphics.Rect>);
method @NonNull public android.view.WindowInsets.Builder setDisplayCutout(@Nullable android.view.DisplayCutout);
method @NonNull public android.view.WindowInsets.Builder setDisplayShape(@NonNull android.view.DisplayShape);
+ method @FlaggedApi("android.view.flags.customizable_window_headers") @NonNull public android.view.WindowInsets.Builder setFrame(int, int);
method @NonNull public android.view.WindowInsets.Builder setInsets(int, @NonNull android.graphics.Insets);
method @NonNull public android.view.WindowInsets.Builder setInsetsIgnoringVisibility(int, @NonNull android.graphics.Insets) throws java.lang.IllegalArgumentException;
method @Deprecated @NonNull public android.view.WindowInsets.Builder setMandatorySystemGestureInsets(@NonNull android.graphics.Insets);
@@ -55622,6 +55725,14 @@
field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.CompletionInfo> CREATOR;
}
+ @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public interface ConnectionlessHandwritingCallback {
+ method public void onError(int);
+ method public void onResult(@NonNull CharSequence);
+ field public static final int CONNECTIONLESS_HANDWRITING_ERROR_NO_TEXT_RECOGNIZED = 0; // 0x0
+ field public static final int CONNECTIONLESS_HANDWRITING_ERROR_OTHER = 2; // 0x2
+ field public static final int CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED = 1; // 0x1
+ }
+
public final class CorrectionInfo implements android.os.Parcelable {
ctor public CorrectionInfo(int, CharSequence, CharSequence);
method public int describeContents();
@@ -56033,6 +56144,7 @@
method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
method public CharSequence loadLabel(android.content.pm.PackageManager);
method public boolean shouldShowInInputMethodPicker();
+ method @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public boolean supportsConnectionlessStylusHandwriting();
method public boolean supportsStylusHandwriting();
method public boolean suppressesSpellChecker();
method public void writeToParcel(android.os.Parcel, int);
@@ -56062,6 +56174,7 @@
method public boolean isAcceptingText();
method public boolean isActive(android.view.View);
method public boolean isActive();
+ method @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public boolean isConnectionlessStylusHandwritingAvailable();
method public boolean isFullscreenMode();
method public boolean isInputMethodSuppressingSpellChecker();
method public boolean isStylusHandwritingAvailable();
@@ -56082,6 +56195,9 @@
method public boolean showSoftInput(android.view.View, int, android.os.ResultReceiver);
method @Deprecated public void showSoftInputFromInputMethod(android.os.IBinder, int);
method @Deprecated public void showStatusIcon(android.os.IBinder, String, @DrawableRes int);
+ method @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public void startConnectionlessStylusHandwriting(@NonNull android.view.View, @Nullable android.view.inputmethod.CursorAnchorInfo, @NonNull java.util.concurrent.Executor, @NonNull android.view.inputmethod.ConnectionlessHandwritingCallback);
+ method @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public void startConnectionlessStylusHandwritingForDelegation(@NonNull android.view.View, @Nullable android.view.inputmethod.CursorAnchorInfo, @NonNull java.util.concurrent.Executor, @NonNull android.view.inputmethod.ConnectionlessHandwritingCallback);
+ method @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public void startConnectionlessStylusHandwritingForDelegation(@NonNull android.view.View, @Nullable android.view.inputmethod.CursorAnchorInfo, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.view.inputmethod.ConnectionlessHandwritingCallback);
method public void startStylusHandwriting(@NonNull android.view.View);
method @Deprecated public boolean switchToLastInputMethod(android.os.IBinder);
method @Deprecated public boolean switchToNextInputMethod(android.os.IBinder, boolean);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 1273da7..af8b708 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -412,6 +412,19 @@
field public static final int VPN_UID = 1016; // 0x3f8
}
+ @FlaggedApi("android.os.telemetry_apis_framework_initialization") public class ProfilingServiceManager {
+ method @NonNull public android.os.ProfilingServiceManager.ServiceRegisterer getProfilingServiceRegisterer();
+ }
+
+ public static class ProfilingServiceManager.ServiceNotFoundException extends java.lang.Exception {
+ ctor public ProfilingServiceManager.ServiceNotFoundException(@NonNull String);
+ }
+
+ public static final class ProfilingServiceManager.ServiceRegisterer {
+ method @Nullable public android.os.IBinder get();
+ method @Nullable public android.os.IBinder getOrThrow() throws android.os.ProfilingServiceManager.ServiceNotFoundException;
+ }
+
public final class ServiceManager {
method @NonNull public static String[] getDeclaredInstances(@NonNull String);
method public static boolean isDeclared(@NonNull String);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0f1da41..9751405 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -282,6 +282,7 @@
field public static final String RADIO_SCAN_WITHOUT_LOCATION = "android.permission.RADIO_SCAN_WITHOUT_LOCATION";
field public static final String READ_ACTIVE_EMERGENCY_SESSION = "android.permission.READ_ACTIVE_EMERGENCY_SESSION";
field public static final String READ_APP_SPECIFIC_LOCALES = "android.permission.READ_APP_SPECIFIC_LOCALES";
+ field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String READ_BLOCKED_NUMBERS = "android.permission.READ_BLOCKED_NUMBERS";
field public static final String READ_CARRIER_APP_INFO = "android.permission.READ_CARRIER_APP_INFO";
field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
field public static final String READ_CLIPBOARD_IN_BACKGROUND = "android.permission.READ_CLIPBOARD_IN_BACKGROUND";
@@ -409,6 +410,7 @@
field public static final String WIFI_UPDATE_COEX_UNSAFE_CHANNELS = "android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS";
field public static final String WIFI_UPDATE_USABILITY_STATS_SCORE = "android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE";
field public static final String WRITE_ALLOWLISTED_DEVICE_CONFIG = "android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG";
+ field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String WRITE_BLOCKED_NUMBERS = "android.permission.WRITE_BLOCKED_NUMBERS";
field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
field public static final String WRITE_DREAM_STATE = "android.permission.WRITE_DREAM_STATE";
field public static final String WRITE_EMBEDDED_SUBSCRIPTIONS = "android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS";
@@ -1038,13 +1040,20 @@
method @Nullable public android.content.ComponentName getAllowedNotificationAssistant();
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public java.util.List<android.content.ComponentName> getEnabledNotificationListeners();
method public boolean isNotificationAssistantAccessGranted(@NonNull android.content.ComponentName);
+ method @FlaggedApi("android.service.notification.callstyle_callback_api") @RequiresPermission(allOf={android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.ACCESS_NOTIFICATIONS}) public void registerCallNotificationEventListener(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull android.app.NotificationManager.CallNotificationEventListener);
method public void setNotificationAssistantAccessGranted(@Nullable android.content.ComponentName, boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted(@NonNull android.content.ComponentName, boolean, boolean);
+ method @FlaggedApi("android.service.notification.callstyle_callback_api") @RequiresPermission(allOf={android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.ACCESS_NOTIFICATIONS}) public void unregisterCallNotificationEventListener(@NonNull android.app.NotificationManager.CallNotificationEventListener);
field @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE) public static final String ACTION_CLOSE_NOTIFICATION_HANDLER_PANEL = "android.app.action.CLOSE_NOTIFICATION_HANDLER_PANEL";
field @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE) public static final String ACTION_OPEN_NOTIFICATION_HANDLER_PANEL = "android.app.action.OPEN_NOTIFICATION_HANDLER_PANEL";
field @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE) public static final String ACTION_TOGGLE_NOTIFICATION_HANDLER_PANEL = "android.app.action.TOGGLE_NOTIFICATION_HANDLER_PANEL";
}
+ @FlaggedApi("android.service.notification.callstyle_callback_api") public static interface NotificationManager.CallNotificationEventListener {
+ method @FlaggedApi("android.service.notification.callstyle_callback_api") public void onCallNotificationPosted(@NonNull String, @NonNull android.os.UserHandle);
+ method @FlaggedApi("android.service.notification.callstyle_callback_api") public void onCallNotificationRemoved(@NonNull String, @NonNull android.os.UserHandle);
+ }
+
public final class RemoteLockscreenValidationResult implements android.os.Parcelable {
method public int describeContents();
method public int getResultCode();
@@ -6642,6 +6651,7 @@
method public int getSupportedRoleCombinations();
method public int getUsbDataStatus();
method public boolean isConnected();
+ method @FlaggedApi("android.hardware.usb.flags.enable_is_pd_compliant_api") public boolean isPdCompliant();
method public boolean isPowerTransferLimited();
method public boolean isRoleCombinationSupported(int, int);
method public void writeToParcel(android.os.Parcel, int);
@@ -11457,6 +11467,29 @@
package android.provider {
+ public static class BlockedNumberContract.BlockedNumbers {
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static void endBlockSuppression(@NonNull android.content.Context);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @NonNull @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static android.provider.BlockedNumberContract.BlockedNumbers.BlockSuppressionStatus getBlockSuppressionStatus(@NonNull android.content.Context);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static boolean getBlockedNumberSetting(@NonNull android.content.Context, @NonNull String);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static void notifyEmergencyContact(@NonNull android.content.Context);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static void setBlockedNumberSetting(@NonNull android.content.Context, @NonNull String, boolean);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static boolean shouldShowEmergencyCallNotification(@NonNull android.content.Context);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static int shouldSystemBlockNumber(@NonNull android.content.Context, @NonNull String, int, boolean);
+ field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ACTION_BLOCK_SUPPRESSION_STATE_CHANGED = "android.provider.action.BLOCK_SUPPRESSION_STATE_CHANGED";
+ field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_BLOCK_PAYPHONE = "block_payphone_calls_setting";
+ field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_BLOCK_PRIVATE = "block_private_number_calls_setting";
+ field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE = "block_unavailable_calls_setting";
+ field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_BLOCK_UNKNOWN = "block_unknown_calls_setting";
+ field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED = "block_numbers_not_in_contacts_setting";
+ field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION = "show_emergency_call_notification";
+ }
+
+ @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final class BlockedNumberContract.BlockedNumbers.BlockSuppressionStatus {
+ ctor public BlockedNumberContract.BlockedNumbers.BlockSuppressionStatus(boolean, long);
+ method public boolean getIsSuppressed();
+ method public long getUntilTimestampMillis();
+ }
+
public class CallLog {
method @RequiresPermission(allOf={android.Manifest.permission.WRITE_CALL_LOG, android.Manifest.permission.INTERACT_ACROSS_USERS}) public static void storeCallComposerPicture(@NonNull android.content.Context, @NonNull java.io.InputStream, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.net.Uri,android.provider.CallLog.CallComposerLoggingException>);
}
@@ -16118,6 +16151,7 @@
field public static final int DIALSTRING_USSD = 2; // 0x2
field public static final String EXTRA_ADDITIONAL_CALL_INFO = "AdditionalCallInfo";
field public static final String EXTRA_ADDITIONAL_SIP_INVITE_FIELDS = "android.telephony.ims.extra.ADDITIONAL_SIP_INVITE_FIELDS";
+ field @FlaggedApi("com.android.server.telecom.flags.business_call_composer") public static final String EXTRA_ASSERTED_DISPLAY_NAME = "android.telephony.ims.extra.ASSERTED_DISPLAY_NAME";
field public static final String EXTRA_CALL_DISCONNECT_CAUSE = "android.telephony.ims.extra.CALL_DISCONNECT_CAUSE";
field public static final String EXTRA_CALL_NETWORK_TYPE = "android.telephony.ims.extra.CALL_NETWORK_TYPE";
field @Deprecated public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a7f80dd..f8a6af1 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -921,7 +921,33 @@
package android.companion.virtual {
public final class VirtualDeviceManager {
+ method public int getAudioPlaybackSessionId(int);
+ method public int getAudioRecordingSessionId(int);
+ method public int getDeviceIdForDisplayId(int);
+ method public int getDevicePolicy(int, int);
method @FlaggedApi("android.companion.virtual.flags.interactive_screen_mirror") public boolean isVirtualDeviceOwnedMirrorDisplay(int);
+ method public void playSoundEffect(int, int);
+ }
+
+}
+
+package android.companion.virtual.camera {
+
+ @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCamera implements java.io.Closeable {
+ method @NonNull public String getId();
+ }
+
+}
+
+package android.companion.virtual.sensor {
+
+ public final class VirtualSensor implements android.os.Parcelable {
+ ctor public VirtualSensor(int, int, @NonNull String);
+ method public int getHandle();
+ }
+
+ public final class VirtualSensorConfig implements android.os.Parcelable {
+ method public int getFlags();
}
}
@@ -988,6 +1014,7 @@
method public void setAutofillOptions(@Nullable android.content.AutofillOptions);
method public void setContentCaptureOptions(@Nullable android.content.ContentCaptureOptions);
method public void updateDeviceId(int);
+ method public abstract void updateDisplay(int);
field public static final String ATTENTION_SERVICE = "attention";
field public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
field public static final String DEVICE_IDLE_CONTROLLER = "deviceidle";
@@ -1001,6 +1028,7 @@
public class ContextWrapper extends android.content.Context {
method public int getDisplayId();
+ method public void updateDisplay(int);
}
public class Intent implements java.lang.Cloneable android.os.Parcelable {
@@ -2089,7 +2117,7 @@
package android.media.projection {
public final class MediaProjectionManager {
- method @NonNull public android.content.Intent createScreenCaptureIntent(@Nullable android.app.ActivityOptions.LaunchCookie);
+ method @NonNull public android.content.Intent createScreenCaptureIntent(@NonNull android.app.ActivityOptions.LaunchCookie);
}
}
@@ -2707,17 +2735,17 @@
package android.os.vibrator.persistence {
- @FlaggedApi("android.os.vibrator.enable_vibration_serialization_apis") public class ParsedVibration {
+ 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.enable_vibration_serialization_apis") public final class VibrationXmlParser {
+ 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.enable_vibration_serialization_apis") public final class VibrationXmlSerializer {
+ 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;
}
@@ -3056,6 +3084,14 @@
method @Deprecated public boolean isBound();
}
+ @FlaggedApi("android.app.modes_api") public final class ZenDeviceEffects implements android.os.Parcelable {
+ method @NonNull public java.util.Set<java.lang.String> getExtraEffects();
+ }
+
+ @FlaggedApi("android.app.modes_api") public static final class ZenDeviceEffects.Builder {
+ method @NonNull public android.service.notification.ZenDeviceEffects.Builder setExtraEffects(@NonNull java.util.Set<java.lang.String>);
+ }
+
public final class ZenPolicy implements android.os.Parcelable {
method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy overwrittenWith(@Nullable android.service.notification.ZenPolicy);
}
@@ -3537,6 +3573,7 @@
public final class Display {
method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void clearUserPreferredDisplayMode();
method @NonNull public android.view.Display.Mode getDefaultMode();
+ method public int getRemoveMode();
method @NonNull public int[] getReportedHdrTypes();
method @NonNull public android.graphics.ColorSpace[] getSupportedWideColorGamut();
method @Nullable public android.view.Display.Mode getSystemPreferredDisplayMode();
@@ -3901,7 +3938,7 @@
}
public final class InputMethodInfo implements android.os.Parcelable {
- ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, @NonNull String, boolean, @NonNull String);
+ ctor @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, @NonNull String, boolean, boolean, @NonNull String);
ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, int);
field public static final int COMPONENT_NAME_MAX_LENGTH = 1000; // 0x3e8
field public static final int MAX_IMES_PER_PACKAGE = 20; // 0x14
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index de6a848..b25d5eb 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -146,6 +146,8 @@
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.Process;
+import android.os.ProfilingFrameworkInitializer;
+import android.os.ProfilingServiceManager;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -8588,6 +8590,9 @@
NfcFrameworkInitializer.setNfcServiceManager(new NfcServiceManager());
DeviceConfigInitializer.setDeviceConfigServiceManager(new DeviceConfigServiceManager());
SeFrameworkInitializer.setSeServiceManager(new SeServiceManager());
+ if (android.server.Flags.telemetryApisService()) {
+ ProfilingFrameworkInitializer.setProfilingServiceManager(new ProfilingServiceManager());
+ }
}
private void purgePendingResources() {
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index d57a4e5..f6373d6 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -487,6 +487,9 @@
public void validate() {
if (Flags.modesApi()) {
checkValidType(mType);
+ if (mDeviceEffects != null) {
+ mDeviceEffects.validate();
+ }
}
}
diff --git a/core/java/android/app/ICallNotificationEventCallback.aidl b/core/java/android/app/ICallNotificationEventCallback.aidl
new file mode 100644
index 0000000..ba34829
--- /dev/null
+++ b/core/java/android/app/ICallNotificationEventCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.os.UserHandle;
+
+/**
+ * Callback to be called when a call notification is posted or removed
+ *
+ * @hide
+ */
+oneway interface ICallNotificationEventCallback {
+ void onCallNotificationPosted(String packageName, in UserHandle userHandle);
+ void onCallNotificationRemoved(String packageName, in UserHandle userHandle);
+}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 578105f..b5e3556 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -24,6 +24,7 @@
import android.app.NotificationChannelGroup;
import android.app.NotificationHistory;
import android.app.NotificationManager;
+import android.app.ICallNotificationEventCallback;
import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.Intent;
@@ -247,4 +248,10 @@
@EnforcePermission("MANAGE_TOAST_RATE_LIMITING")
void setToastRateLimitingEnabled(boolean enable);
+
+ @EnforcePermission(allOf={"INTERACT_ACROSS_USERS", "ACCESS_NOTIFICATIONS"})
+ void registerCallNotificationEventListener(String packageName, in UserHandle userHandle, in ICallNotificationEventCallback listener);
+ @EnforcePermission(allOf={"INTERACT_ACROSS_USERS", "ACCESS_NOTIFICATIONS"})
+ void unregisterCallNotificationEventListener(String packageName, in UserHandle userHandle, in ICallNotificationEventCallback listener);
+
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index aa9de81..d6e8ae3 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3020,6 +3020,43 @@
}
/**
+ * @hide
+ */
+ public String loadHeaderAppName(Context context) {
+ CharSequence name = null;
+ // Check if there is a non-empty substitute app name and return that.
+ if (extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) {
+ name = extras.getString(EXTRA_SUBSTITUTE_APP_NAME);
+ if (!TextUtils.isEmpty(name)) {
+ return name.toString();
+ }
+ }
+ // If not, try getting the app info from extras.
+ if (context == null) {
+ return null;
+ }
+ final PackageManager pm = context.getPackageManager();
+ if (TextUtils.isEmpty(name)) {
+ if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) {
+ final ApplicationInfo info = extras.getParcelable(EXTRA_BUILDER_APPLICATION_INFO,
+ ApplicationInfo.class);
+ if (info != null) {
+ name = pm.getApplicationLabel(info);
+ }
+ }
+ }
+ // If that's still empty, use the one from the context directly.
+ if (TextUtils.isEmpty(name)) {
+ name = pm.getApplicationLabel(context.getApplicationInfo());
+ }
+ // If there's still nothing, ¯\_(ツ)_/¯
+ if (TextUtils.isEmpty(name)) {
+ return null;
+ }
+ return name.toString();
+ }
+
+ /**
* Removes heavyweight parts of the Notification object for archival or for sending to
* listeners when the full contents are not necessary.
* @hide
@@ -5769,34 +5806,7 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public String loadHeaderAppName() {
- CharSequence name = null;
- final PackageManager pm = mContext.getPackageManager();
- if (mN.extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) {
- // only system packages which lump together a bunch of unrelated stuff
- // may substitute a different name to make the purpose of the
- // notification more clear. the correct package label should always
- // be accessible via SystemUI.
- final String pkg = mContext.getPackageName();
- final String subName = mN.extras.getString(EXTRA_SUBSTITUTE_APP_NAME);
- if (PackageManager.PERMISSION_GRANTED == pm.checkPermission(
- android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg)) {
- name = subName;
- } else {
- Log.w(TAG, "warning: pkg "
- + pkg + " attempting to substitute app name '" + subName
- + "' without holding perm "
- + android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME);
- }
- }
- if (TextUtils.isEmpty(name)) {
- name = pm.getApplicationLabel(mContext.getApplicationInfo());
- }
- if (TextUtils.isEmpty(name)) {
- // still nothing?
- return null;
- }
-
- return String.valueOf(name);
+ return mN.loadHeaderAppName(mContext);
}
/**
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 8c886fe..9dfb5b0 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -16,6 +16,7 @@
package android.app;
+import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -69,6 +70,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.Executor;
/**
* Class to notify the user of events that happen. This is how you tell
@@ -627,6 +629,9 @@
*/
public static int MAX_SERVICE_COMPONENT_NAME_LENGTH = 500;
+ private final Map<CallNotificationEventListener, CallNotificationEventCallbackStub>
+ mCallNotificationEventCallbacks = new HashMap<>();
+
@UnsupportedAppUsage
private static INotificationManager sService;
@@ -2848,4 +2853,126 @@
default: return defValue;
}
}
+
+ /**
+ * Callback to receive updates when a call notification has been posted or removed
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
+ public interface CallNotificationEventListener {
+ /**
+ * Called when a call notification was posted by a package this listener
+ * has registered for.
+ * @param packageName package name of the app that posted the removed notification
+ */
+ @FlaggedApi(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
+ void onCallNotificationPosted(@NonNull String packageName, @NonNull UserHandle userHandle);
+
+ /**
+ * Called when a call notification was removed by a package this listener
+ * has registered for.
+ * @param packageName package name of the app that removed notification
+ */
+ @FlaggedApi(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
+ void onCallNotificationRemoved(@NonNull String packageName, @NonNull UserHandle userHandle);
+ }
+
+ private static class CallNotificationEventCallbackStub extends
+ ICallNotificationEventCallback.Stub {
+ final String mPackageName;
+ final UserHandle mUserHandle;
+ final Executor mExecutor;
+ final CallNotificationEventListener mListener;
+
+ CallNotificationEventCallbackStub(@NonNull String packageName,
+ @NonNull UserHandle userHandle, @NonNull @CallbackExecutor Executor executor,
+ @NonNull CallNotificationEventListener listener) {
+ mPackageName = packageName;
+ mUserHandle = userHandle;
+ mExecutor = executor;
+ mListener = listener;
+ }
+
+ @FlaggedApi(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
+ @Override
+ public void onCallNotificationPosted(String packageName, UserHandle userHandle) {
+ mExecutor.execute(() -> mListener.onCallNotificationPosted(packageName, userHandle));
+ }
+
+ @FlaggedApi(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
+ @Override
+ public void onCallNotificationRemoved(String packageName, UserHandle userHandle) {
+ mExecutor.execute(() -> mListener.onCallNotificationRemoved(packageName, userHandle));
+ }
+ }
+
+ /**
+ * Register a listener to be notified when a call notification is posted or removed
+ * for a specific package and user.
+ *
+ * @param packageName Which package to monitor
+ * @param userHandle Which user to monitor
+ * @param executor Callback will run on this executor
+ * @param listener Listener to register
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.ACCESS_NOTIFICATIONS})
+ @FlaggedApi(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
+ public void registerCallNotificationEventListener(@NonNull String packageName,
+ @NonNull UserHandle userHandle, @NonNull @CallbackExecutor Executor executor,
+ @NonNull CallNotificationEventListener listener) {
+ checkRequired("packageName", packageName);
+ checkRequired("userHandle", userHandle);
+ checkRequired("executor", executor);
+ checkRequired("listener", listener);
+ INotificationManager service = getService();
+ try {
+ synchronized (mCallNotificationEventCallbacks) {
+ CallNotificationEventCallbackStub callbackStub =
+ new CallNotificationEventCallbackStub(packageName, userHandle,
+ executor, listener);
+ mCallNotificationEventCallbacks.put(listener, callbackStub);
+
+ service.registerCallNotificationEventListener(packageName, userHandle,
+ callbackStub);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregister a listener that was previously
+ * registered with {@link #registerCallNotificationEventListener}
+ *
+ * @param listener Listener to unregister
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.ACCESS_NOTIFICATIONS})
+ public void unregisterCallNotificationEventListener(
+ @NonNull CallNotificationEventListener listener) {
+ checkRequired("listener", listener);
+ INotificationManager service = getService();
+ try {
+ synchronized (mCallNotificationEventCallbacks) {
+ CallNotificationEventCallbackStub callbackStub =
+ mCallNotificationEventCallbacks.remove(listener);
+ if (callbackStub != null) {
+ service.unregisterCallNotificationEventListener(callbackStub.mPackageName,
+ callbackStub.mUserHandle, callbackStub);
+ }
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
}
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 6255260..24a5157 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -550,7 +550,7 @@
@UnsupportedAppUsage
protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key,
@Nullable ApkAssetsSupplier apkSupplier) {
- final AssetManager.Builder builder = new AssetManager.Builder().setNoInit();
+ final AssetManager.Builder builder = new AssetManager.Builder();
final ArrayList<ApkKey> apkKeys = extractApkKeys(key);
for (int i = 0, n = apkKeys.size(); i < n; i++) {
@@ -1555,7 +1555,7 @@
} else if(overlayPaths == null) {
return ArrayUtils.cloneOrNull(resourceDirs);
} else {
- final var paths = new ArrayList<String>(overlayPaths.length + resourceDirs.length);
+ final ArrayList<String> paths = new ArrayList<>();
for (final String path : overlayPaths) {
paths.add(path);
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index ba9c895..9d35dc3 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -186,6 +186,7 @@
import android.os.PerformanceHintManager;
import android.os.PermissionEnforcer;
import android.os.PowerManager;
+import android.os.ProfilingFrameworkInitializer;
import android.os.RecoverySystem;
import android.os.SecurityStateManager;
import android.os.ServiceManager;
@@ -1662,6 +1663,9 @@
if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()) {
EnhancedConfirmationFrameworkInitializer.registerServiceWrappers();
}
+ if (android.server.Flags.telemetryApisService()) {
+ ProfilingFrameworkInitializer.registerServiceWrappers();
+ }
} finally {
// If any of the above code throws, we're in a pretty bad shape and the process
// will likely crash, but we'll reset it just in case there's an exception handler...
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index c649e62..9d50810 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -51,6 +51,7 @@
import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY;
import static android.Manifest.permission.SET_TIME;
import static android.Manifest.permission.SET_TIME_ZONE;
+import static android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED;
import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
@@ -17300,4 +17301,33 @@
public boolean isOnboardingBugreportV2FlagEnabled() {
return onboardingBugreportV2Enabled();
}
+
+ /**
+ * Returns the subscription ids of all subscriptions which was downloaded by the calling
+ * admin.
+ *
+ * <p> This returns only the subscriptions which were downloaded by the calling admin via
+ * {@link android.telephony.euicc.EuiccManager#downloadSubscription}.
+ * If a susbcription is returned by this method then in it subject to management controls
+ * and cannot be removed by users.
+ *
+ * <p> Callable by device owners and profile owners.
+ *
+ * @throws SecurityException if the caller is not authorized to call this method
+ * @return ids of all managed subscriptions currently downloaded by an admin on the device
+ */
+ @FlaggedApi(FLAG_ESIM_MANAGEMENT_ENABLED)
+ @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS)
+ @NonNull
+ public Set<Integer> getSubscriptionsIds() {
+ throwIfParentInstance("getSubscriptionsIds");
+ if (mService != null) {
+ try {
+ return intArrayToSet(mService.getSubscriptionIds(mContext.getPackageName()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return new HashSet<>();
+ }
}
\ No newline at end of file
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index efcf563..f72fdc0 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -613,4 +613,6 @@
void setContentProtectionPolicy(in ComponentName who, String callerPackageName, int policy);
int getContentProtectionPolicy(in ComponentName who, String callerPackageName);
+
+ int[] getSubscriptionIds(String callerPackageName);
}
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 3c98ef9..5420e53 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -57,6 +57,13 @@
}
flag {
+ name: "assist_content_user_restriction_enabled"
+ namespace: "enterprise"
+ description: "Prevent work data leakage by sending assist content to privileged apps."
+ bug: "322975406"
+}
+
+flag {
name: "default_sms_personal_app_suspension_fix_enabled"
namespace: "enterprise"
description: "Exempt the default sms app of the context user for suspension when calling setPersonalAppsSuspended"
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index a16e94a..3304475 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -26,6 +26,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
@@ -333,6 +334,8 @@
*
* @hide
*/
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
public @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
int deviceId, @VirtualDeviceParams.PolicyType int policyType) {
if (mService == null) {
@@ -351,6 +354,8 @@
*
* @hide
*/
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
public int getDeviceIdForDisplayId(int displayId) {
if (mService == null) {
Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service.");
@@ -446,6 +451,8 @@
*
* @hide
*/
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
public int getAudioPlaybackSessionId(int deviceId) {
if (mService == null) {
return AUDIO_SESSION_ID_GENERATE;
@@ -470,6 +477,8 @@
*
* @hide
*/
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
public int getAudioRecordingSessionId(int deviceId) {
if (mService == null) {
return AUDIO_SESSION_ID_GENERATE;
@@ -491,6 +500,8 @@
*
* @hide
*/
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
public void playSoundEffect(int deviceId, @AudioManager.SystemSoundEffect int effectType) {
if (mService == null) {
Log.w(TAG, "Failed to dispatch sound effect; no virtual device manager service.");
diff --git a/core/java/android/companion/virtual/camera/VirtualCamera.java b/core/java/android/companion/virtual/camera/VirtualCamera.java
index 9d6c14b..f727589 100644
--- a/core/java/android/companion/virtual/camera/VirtualCamera.java
+++ b/core/java/android/companion/virtual/camera/VirtualCamera.java
@@ -18,7 +18,9 @@
import android.annotation.FlaggedApi;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.VirtualDeviceManager;
import android.companion.virtual.VirtualDeviceParams;
@@ -84,6 +86,8 @@
* Returns the id of this virtual camera instance.
* @hide
*/
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
@NonNull
public String getId() {
return mCameraId;
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensor.java b/core/java/android/companion/virtual/sensor/VirtualSensor.java
index 14c7997..37e494b 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensor.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensor.java
@@ -18,7 +18,9 @@
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.companion.virtual.IVirtualDevice;
import android.hardware.Sensor;
import android.os.IBinder;
@@ -54,6 +56,15 @@
mToken = token;
}
+ /**
+ * @hide
+ */
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
+ public VirtualSensor(int handle, int type, @NonNull String name) {
+ this(handle, type, name, /*virtualDevice=*/null, /*token=*/null);
+ }
+
private VirtualSensor(Parcel parcel) {
mHandle = parcel.readInt();
mType = parcel.readInt();
@@ -67,6 +78,8 @@
*
* @hide
*/
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
public int getHandle() {
return mHandle;
}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
index 0dbe411..21ad914 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
@@ -20,7 +20,9 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.hardware.Sensor;
import android.hardware.SensorDirectChannel;
import android.os.Parcel;
@@ -217,6 +219,8 @@
*
* @hide
*/
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
public int getFlags() {
return mFlags;
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index b8d7543..5166b61 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -6553,6 +6553,15 @@
public static final String CONTACT_KEYS_SERVICE = "contact_keys";
/**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link android.os.ProfilingManager}.
+ *
+ * @see #getSystemService(String)
+ */
+ @FlaggedApi(android.os.Flags.FLAG_TELEMETRY_APIS_FRAMEWORK_INITIALIZATION)
+ public static final String PROFILING_SERVICE = "profiling";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
@@ -7706,9 +7715,13 @@
}
/**
+ * Updates the display association of this Context with the display with the given ID.
+ *
* @hide
*/
@SuppressWarnings("HiddenAbstractMethod")
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
public abstract void updateDisplay(int displayId);
/**
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 333c363..e8031a3 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -56,6 +56,7 @@
import android.os.Build;
import android.os.Bundle;
import android.os.BundleMerger;
+import android.os.CancellationSignal;
import android.os.IBinder;
import android.os.IncidentManager;
import android.os.Parcel;
@@ -73,7 +74,9 @@
import android.provider.DocumentsProvider;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
+import android.service.chooser.AdditionalContentContract;
import android.service.chooser.ChooserAction;
+import android.service.chooser.ChooserResult;
import android.telecom.PhoneAccount;
import android.telecom.TelecomManager;
import android.text.TextUtils;
@@ -1060,7 +1063,7 @@
}
if (sender != null) {
- intent.putExtra(EXTRA_CHOSEN_COMPONENT_INTENT_SENDER, sender);
+ intent.putExtra(EXTRA_CHOOSER_RESULT_INTENT_SENDER, sender);
}
// Migrate any clip data and flags from target.
@@ -6065,6 +6068,62 @@
public static final int CHOOSER_CONTENT_TYPE_ALBUM = 1;
/**
+ * Optional argument used to provide a {@link ContentProvider} {@link Uri} to an
+ * {@link #ACTION_CHOOSER} Intent which allows additional toggleable items to be included
+ * in the sharing UI.
+ * <p>
+ * For example, this could be used to show photos being shared in the context of the user's
+ * entire photo roll, with the option to change the set of photos being shared.
+ * <p>
+ * When this is provided in an {@link #ACTION_CHOOSER} Intent with an {@link #ACTION_SEND} or
+ * {@link #ACTION_SEND_MULTIPLE} target Intent, the sharesheet will query (see
+ * {@link ContentProvider#query(Uri, String[], Bundle, CancellationSignal)}) this URI to
+ * retrieve a set of additional items available for selection. The set of items returned by the
+ * content provider is expected to contain all the items from the {@link #EXTRA_STREAM}
+ * argument, in their relative order, which will be marked as selected. The URI's authority
+ * must be different from any shared items URI provided in {@link #EXTRA_STREAM} or returned by
+ * the provider.
+ *
+ * <p>The {@link Bundle} argument of the
+ * {@link ContentProvider#query(Uri, String[], Bundle, CancellationSignal)}
+ * method will contains the original intent Chooser has been launched with under the
+ * {@link #EXTRA_INTENT} key as a context for the current sharing session. The returned
+ * {@link android.database.Cursor} should contain
+ * {@link android.service.chooser.AdditionalContentContract.Columns#URI} column for the item URI
+ * and, optionally, {@link AdditionalContentContract.CursorExtraKeys#POSITION} extra that
+ * specifies the cursor starting position; the item at this position is expected to match the
+ * item specified by {@link #EXTRA_CHOOSER_FOCUSED_ITEM_POSITION}.</p>
+ *
+ * <p>When the user makes a selection change,
+ * {@link ContentProvider#call(String, String, Bundle)} method will be invoked with the "method"
+ * argument set to
+ * {@link android.service.chooser.AdditionalContentContract.MethodNames#ON_SELECTION_CHANGED},
+ * the "arg" argument set to this argument's value, and the "extras" {@link Bundle} argument
+ * containing {@link #EXTRA_INTENT} key containing the original intent Chooser has been launched
+ * with but with the modified target intent --Chooser will modify the target intent according to
+ * the selection changes made by the user.
+ * Applications may implement this method to change any of the following Chooser arguments by
+ * returning new values in the result bundle:
+ * {@link #EXTRA_CHOOSER_TARGETS}, {@link #EXTRA_ALTERNATE_INTENTS},
+ * {@link #EXTRA_CHOOSER_CUSTOM_ACTIONS},
+ * {@link #EXTRA_CHOOSER_MODIFY_SHARE_ACTION},
+ * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER}.</p>
+ */
+ @FlaggedApi(android.service.chooser.Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING)
+ public static final String EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI =
+ "android.intent.extra.CHOOSER_ADDITIONAL_CONTENT_URI";
+
+ /**
+ * Optional argument to be used with {@link #EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI}, used in
+ * combination with {@link #EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI}.
+ * An integer, zero-based index into {@link #EXTRA_STREAM} argument indicating the item that
+ * should be focused by the Chooser in preview.
+ */
+ @FlaggedApi(android.service.chooser.Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING)
+ public static final String EXTRA_CHOOSER_FOCUSED_ITEM_POSITION =
+ "android.intent.extra.CHOOSER_FOCUSED_ITEM_POSITION";
+
+ /**
* An {@code ArrayList} of {@code String} annotations describing content for
* {@link #ACTION_CHOOSER}.
*
@@ -6308,6 +6367,25 @@
"android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
/**
+ * An {@link IntentSender} that will be notified when a user successfully chooses a target
+ * component or initiates an action such as copy or edit within an {@link #ACTION_CHOOSER}
+ * activity. The IntentSender will have the extra {@link #EXTRA_CHOOSER_RESULT} describing
+ * the result.
+ */
+ @FlaggedApi(android.service.chooser.Flags.FLAG_ENABLE_CHOOSER_RESULT)
+ public static final String EXTRA_CHOOSER_RESULT_INTENT_SENDER =
+ "android.intent.extra.CHOOSER_RESULT_INTENT_SENDER";
+
+ /**
+ * A {@link ChooserResult} which describes how the sharing session completed.
+ * <p>
+ * An instance is supplied to the optional IntentSender provided to
+ * {@link #createChooser(Intent, CharSequence, IntentSender)} when the session completes.
+ */
+ @FlaggedApi(android.service.chooser.Flags.FLAG_ENABLE_CHOOSER_RESULT)
+ public static final String EXTRA_CHOOSER_RESULT = "android.intent.extra.CHOOSER_RESULT";
+
+ /**
* The {@link ComponentName} chosen by the user to complete an action.
*
* @see #EXTRA_CHOSEN_COMPONENT_INTENT_SENDER
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index d913581..e290722 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -677,7 +677,7 @@
* has at least one HTTP or HTTPS data URI pattern defined, and optionally
* does not define any non-http/https data URI patterns.
*
- * This will check if if the Intent action is {@link android.content.Intent#ACTION_VIEW} and
+ * This will check if the Intent action is {@link android.content.Intent#ACTION_VIEW} and
* the Intent category is {@link android.content.Intent#CATEGORY_BROWSABLE} and the Intent
* data scheme is "http" or "https".
*
@@ -718,7 +718,7 @@
}
// We get here if:
- // 1) onlyWebSchemes and no non-web schemes were found, i.e success; or
+ // 1) onlyWebSchemes and no non-web schemes were found, i.e. success; or
// 2) !onlyWebSchemes and no http/https schemes were found, i.e. failure.
return onlyWebSchemes;
}
@@ -728,7 +728,7 @@
*
* @return True if the filter needs to be automatically verified. False otherwise.
*
- * This will check if if the Intent action is {@link android.content.Intent#ACTION_VIEW} and
+ * This will check if the Intent action is {@link android.content.Intent#ACTION_VIEW} and
* the Intent category is {@link android.content.Intent#CATEGORY_BROWSABLE} and the Intent
* data scheme is "http" or "https".
*
diff --git a/core/java/android/content/pm/OWNERS b/core/java/android/content/pm/OWNERS
index fb95608895..c6f5220 100644
--- a/core/java/android/content/pm/OWNERS
+++ b/core/java/android/content/pm/OWNERS
@@ -3,11 +3,22 @@
file:/PACKAGE_MANAGER_OWNERS
per-file PackageParser.java = set noparent
-per-file PackageParser.java = chiuwinson@google.com,patb@google.com
+per-file PackageParser.java = file:/PACKAGE_MANAGER_OWNERS
+
+# Bug component: 166829 = per-file *Capability*
per-file *Capability* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
+# Bug component: 166829 = per-file *Shortcut*
per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
+
+# Bug component: 860423 = per-file *Launcher*
per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS
+
+# Bug component: 578329 = per-file *UserInfo*
per-file UserInfo* = file:/MULTIUSER_OWNERS
+# Bug component: 578329 = per-file *UserProperties*
per-file *UserProperties* = file:/MULTIUSER_OWNERS
+# Bug component: 578329 = per-file *multiuser*
per-file *multiuser* = file:/MULTIUSER_OWNERS
-per-file IBackgroundInstallControlService.aidl = file:/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
+
+# Bug component: 1219020 = per-file *BackgroundInstallControl*
+per-file *BackgroundInstallControl* = file:/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 407ffbb..49c8a7c 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -16,6 +16,8 @@
package android.content.pm;
+import static android.media.audio.Flags.FLAG_FEATURE_SPATIAL_AUDIO_HEADTRACKING_LOW_LATENCY;
+
import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES;
import android.Manifest;
@@ -3065,6 +3067,17 @@
public static final String FEATURE_AUDIO_PRO = "android.hardware.audio.pro";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}
+ * which indicates whether head tracking for spatial audio operates with low-latency,
+ * as defined by the CDD criteria for the feature.
+ *
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ @FlaggedApi(FLAG_FEATURE_SPATIAL_AUDIO_HEADTRACKING_LOW_LATENCY)
+ public static final String FEATURE_AUDIO_SPATIAL_HEADTRACKING_LOW_LATENCY =
+ "android.hardware.audio.spatial.headtracking.low_latency";
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device is capable of communicating with
* other devices via Bluetooth.
diff --git a/core/java/android/content/pm/overlay/OverlayPaths.java b/core/java/android/content/pm/overlay/OverlayPaths.java
index a4db733..bd74b0b 100644
--- a/core/java/android/content/pm/overlay/OverlayPaths.java
+++ b/core/java/android/content/pm/overlay/OverlayPaths.java
@@ -49,6 +49,13 @@
public static class Builder {
final OverlayPaths mPaths = new OverlayPaths();
+ public Builder() {}
+
+ public Builder(@NonNull OverlayPaths base) {
+ mPaths.mResourceDirs.addAll(base.getResourceDirs());
+ mPaths.mOverlayPaths.addAll(base.getOverlayPaths());
+ }
+
/**
* Adds a non-APK path to the contents of {@link OverlayPaths#getOverlayPaths()}.
*/
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index d259e97..23b9d0b 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -137,8 +137,6 @@
private ArrayList<ApkAssets> mUserApkAssets = new ArrayList<>();
private ArrayList<ResourcesLoader> mLoaders = new ArrayList<>();
- private boolean mNoInit = false;
-
public Builder addApkAssets(ApkAssets apkAssets) {
mUserApkAssets.add(apkAssets);
return this;
@@ -149,11 +147,6 @@
return this;
}
- public Builder setNoInit() {
- mNoInit = true;
- return this;
- }
-
public AssetManager build() {
// Retrieving the system ApkAssets forces their creation as well.
final ApkAssets[] systemApkAssets = getSystem().getApkAssets();
@@ -195,7 +188,7 @@
final AssetManager assetManager = new AssetManager(false /*sentinel*/);
assetManager.mApkAssets = apkAssets;
AssetManager.nativeSetApkAssets(assetManager.mObject, apkAssets,
- false /*invalidateCaches*/, mNoInit /*preset*/);
+ false /*invalidateCaches*/);
assetManager.mLoaders = mLoaders.isEmpty() ? null
: mLoaders.toArray(new ResourcesLoader[0]);
@@ -336,7 +329,7 @@
synchronized (this) {
ensureOpenLocked();
mApkAssets = newApkAssets;
- nativeSetApkAssets(mObject, mApkAssets, invalidateCaches, false);
+ nativeSetApkAssets(mObject, mApkAssets, invalidateCaches);
if (invalidateCaches) {
// Invalidate all caches.
invalidateCachesLocked(-1);
@@ -503,7 +496,7 @@
mApkAssets = Arrays.copyOf(mApkAssets, count + 1);
mApkAssets[count] = assets;
- nativeSetApkAssets(mObject, mApkAssets, true, false);
+ nativeSetApkAssets(mObject, mApkAssets, true);
invalidateCachesLocked(-1);
return count + 1;
}
@@ -1510,29 +1503,12 @@
int navigation, int screenWidth, int screenHeight, int smallestScreenWidthDp,
int screenWidthDp, int screenHeightDp, int screenLayout, int uiMode, int colorMode,
int grammaticalGender, int majorVersion) {
- setConfigurationInternal(mcc, mnc, defaultLocale, locales, orientation,
- touchscreen, density, keyboard, keyboardHidden, navigation, screenWidth,
- screenHeight, smallestScreenWidthDp, screenWidthDp, screenHeightDp,
- screenLayout, uiMode, colorMode, grammaticalGender, majorVersion, false);
- }
-
- /**
- * Change the configuration used when retrieving resources, and potentially force a refresh of
- * the state. Not for use by applications.
- * @hide
- */
- void setConfigurationInternal(int mcc, int mnc, String defaultLocale, String[] locales,
- int orientation, int touchscreen, int density, int keyboard, int keyboardHidden,
- int navigation, int screenWidth, int screenHeight, int smallestScreenWidthDp,
- int screenWidthDp, int screenHeightDp, int screenLayout, int uiMode, int colorMode,
- int grammaticalGender, int majorVersion, boolean forceRefresh) {
synchronized (this) {
ensureValidLocked();
nativeSetConfiguration(mObject, mcc, mnc, defaultLocale, locales, orientation,
touchscreen, density, keyboard, keyboardHidden, navigation, screenWidth,
screenHeight, smallestScreenWidthDp, screenWidthDp, screenHeightDp,
- screenLayout, uiMode, colorMode, grammaticalGender, majorVersion,
- forceRefresh);
+ screenLayout, uiMode, colorMode, grammaticalGender, majorVersion);
}
}
@@ -1617,13 +1593,13 @@
private static native long nativeCreate();
private static native void nativeDestroy(long ptr);
private static native void nativeSetApkAssets(long ptr, @NonNull ApkAssets[] apkAssets,
- boolean invalidateCaches, boolean preset);
+ boolean invalidateCaches);
private static native void nativeSetConfiguration(long ptr, int mcc, int mnc,
@Nullable String defaultLocale, @NonNull String[] locales, int orientation,
int touchscreen, int density, int keyboard, int keyboardHidden, int navigation,
int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp,
int screenHeightDp, int screenLayout, int uiMode, int colorMode, int grammaticalGender,
- int majorVersion, boolean forceRefresh);
+ int majorVersion);
private static native @NonNull SparseArray<String> nativeGetAssignedPackageIdentifiers(
long ptr, boolean includeOverlays, boolean includeLoaders);
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 079c2c1..5e442b8 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -200,7 +200,7 @@
mMetrics.setToDefaults();
mDisplayAdjustments = displayAdjustments;
mConfiguration.setToDefaults();
- updateConfigurationImpl(config, metrics, displayAdjustments.getCompatibilityInfo(), true);
+ updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo());
}
public DisplayAdjustments getDisplayAdjustments() {
@@ -402,12 +402,7 @@
}
public void updateConfiguration(Configuration config, DisplayMetrics metrics,
- CompatibilityInfo compat) {
- updateConfigurationImpl(config, metrics, compat, false);
- }
-
- private void updateConfigurationImpl(Configuration config, DisplayMetrics metrics,
- CompatibilityInfo compat, boolean forceAssetsRefresh) {
+ CompatibilityInfo compat) {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesImpl#updateConfiguration");
try {
synchronized (mAccessLock) {
@@ -533,7 +528,7 @@
keyboardHidden = mConfiguration.keyboardHidden;
}
- mAssets.setConfigurationInternal(mConfiguration.mcc, mConfiguration.mnc,
+ mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
defaultLocale,
selectedLocales,
mConfiguration.orientation,
@@ -544,7 +539,7 @@
mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,
mConfiguration.screenLayout, mConfiguration.uiMode,
mConfiguration.colorMode, mConfiguration.getGrammaticalGender(),
- Build.VERSION.RESOURCES_SDK_INT, forceAssetsRefresh);
+ Build.VERSION.RESOURCES_SDK_INT);
if (DEBUG_CONFIG) {
Slog.i(TAG, "**** Updating config of " + this + ": final config is "
diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java
index 4a5c4c8..8616b6b 100644
--- a/core/java/android/hardware/usb/UsbPortStatus.java
+++ b/core/java/android/hardware/usb/UsbPortStatus.java
@@ -16,13 +16,11 @@
package android.hardware.usb;
-import android.Manifest;
import android.annotation.CheckResult;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.hardware.usb.flags.Flags;
import android.os.Parcel;
@@ -30,7 +28,6 @@
import com.android.internal.annotations.Immutable;
-import java.lang.StringBuilder;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -580,6 +577,21 @@
}
/**
+ * This function checks if the port is USB Power Delivery (PD) compliant -
+ * https://www.usb.org/usb-charger-pd. All of the power and data roles must be supported for a
+ * port to be PD compliant.
+ *
+ * @return true if the port is PD compliant.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_IS_PD_COMPLIANT_API)
+ public boolean isPdCompliant() {
+ return isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_DEVICE)
+ && isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_HOST)
+ && isRoleCombinationSupported(POWER_ROLE_SOURCE, DATA_ROLE_DEVICE)
+ && isRoleCombinationSupported(POWER_ROLE_SOURCE, DATA_ROLE_HOST);
+ }
+
+ /**
* Get the supported role combinations.
*/
public int getSupportedRoleCombinations() {
diff --git a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
new file mode 100644
index 0000000..cc56a31
--- /dev/null
+++ b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.hardware.usb.flags"
+
+flag {
+ name: "enable_is_pd_compliant_api"
+ namespace: "usb"
+ description: "Feature flag for the api to check if a port is PD compliant"
+ bug: "323470419"
+}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index a6b2ed0..e090942 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1899,7 +1899,7 @@
public short batteryTemperature;
// Battery voltage in millivolts (mV).
@UnsupportedAppUsage
- public char batteryVoltage;
+ public short batteryVoltage;
// The charge of the battery in micro-Ampere-hours.
public int batteryChargeUah;
@@ -2161,7 +2161,7 @@
batteryPlugType = (byte)((bat>>24)&0xf);
int bat2 = src.readInt();
batteryTemperature = (short)(bat2&0xffff);
- batteryVoltage = (char)((bat2>>16)&0xffff);
+ batteryVoltage = (short) ((bat2 >> 16) & 0xffff);
batteryChargeUah = src.readInt();
modemRailChargeMah = src.readDouble();
wifiRailChargeMah = src.readDouble();
diff --git a/core/java/android/os/ProfilingServiceManager.java b/core/java/android/os/ProfilingServiceManager.java
new file mode 100644
index 0000000..cc77f5b
--- /dev/null
+++ b/core/java/android/os/ProfilingServiceManager.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.SystemApi.Client;
+
+/**
+ * Provides a way to register and obtain the system service binder objects managed by the profiling
+ * service.
+ *
+ * <p> Only the profiling mainline module will be able to access an instance of this class.
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_TELEMETRY_APIS_FRAMEWORK_INITIALIZATION)
+@SystemApi(client = Client.MODULE_LIBRARIES)
+public class ProfilingServiceManager {
+
+ /** @hide */
+ public ProfilingServiceManager() {}
+
+ /**
+ * A class that exposes the methods to register and obtain each system service.
+ */
+ public static final class ServiceRegisterer {
+ private final String mServiceName;
+
+ /** @hide */
+ public ServiceRegisterer(String serviceName) {
+ mServiceName = serviceName;
+ }
+
+ /**
+ * Get the system server binding object for ProfilingService.
+ *
+ * <p> This blocks until the service instance is ready.
+ * or a timeout happens, in which case it returns null.
+ */
+ @Nullable
+ public IBinder get() {
+ return ServiceManager.getService(mServiceName);
+ }
+
+ /**
+ * Get the system server binding object for a service.
+ *
+ * <p>This blocks until the service instance is ready,
+ * or a timeout happens, in which case it throws {@link ServiceNotFoundException}.
+ */
+ @Nullable
+ public IBinder getOrThrow() throws ServiceNotFoundException {
+ try {
+ return ServiceManager.getServiceOrThrow(mServiceName);
+ } catch (ServiceManager.ServiceNotFoundException e) {
+ throw new ServiceNotFoundException(mServiceName);
+ }
+ }
+ }
+
+ /**
+ * See {@link ServiceRegisterer#getOrThrow()}
+ */
+ public static class ServiceNotFoundException extends ServiceManager.ServiceNotFoundException {
+ /**
+ * Constructor
+ *
+ * @param name the name of the binder service that cannot be found.
+ */
+ public ServiceNotFoundException(@NonNull String name) {
+ super(name);
+ }
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the "profiling" service.
+ */
+ @NonNull
+ public ServiceRegisterer getProfilingServiceRegisterer() {
+ return new ServiceRegisterer("profiling_service");
+ }
+}
diff --git a/core/java/android/os/storage/OWNERS b/core/java/android/os/storage/OWNERS
index 6941857..2cb16d337b 100644
--- a/core/java/android/os/storage/OWNERS
+++ b/core/java/android/os/storage/OWNERS
@@ -10,7 +10,6 @@
krishang@google.com
riyaghai@google.com
sahanas@google.com
-sergeynv@google.com
shikhamalhotra@google.com
shubhisaxena@google.com
tylersaunders@google.com
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index ea9375e..d485eca 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -16,13 +16,6 @@
flag {
namespace: "haptics"
- name: "enable_vibration_serialization_apis"
- description: "Enables the APIs for vibration serialization/deserialization."
- bug: "245129509"
-}
-
-flag {
- namespace: "haptics"
name: "haptic_feedback_vibration_oem_customization_enabled"
description: "Enables OEMs/devices to customize vibrations for haptic feedback"
# Make read only. This is because the flag is used only once, and this could happen before
diff --git a/core/java/android/os/vibrator/persistence/ParsedVibration.java b/core/java/android/os/vibrator/persistence/ParsedVibration.java
index 3d1deea..a16d21e 100644
--- a/core/java/android/os/vibrator/persistence/ParsedVibration.java
+++ b/core/java/android/os/vibrator/persistence/ParsedVibration.java
@@ -16,9 +16,9 @@
package android.os.vibrator.persistence;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.os.VibrationEffect;
import android.os.Vibrator;
@@ -35,8 +35,8 @@
*
* @hide
*/
-@FlaggedApi(android.os.vibrator.Flags.FLAG_ENABLE_VIBRATION_SERIALIZATION_APIS)
@TestApi
+@SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public class ParsedVibration {
private final List<VibrationEffect> mEffects;
diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlParser.java b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java
index 3d711a7..7202d9a 100644
--- a/core/java/android/os/vibrator/persistence/VibrationXmlParser.java
+++ b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java
@@ -16,10 +16,10 @@
package android.os.vibrator.persistence;
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.os.VibrationEffect;
import android.util.Slog;
@@ -116,8 +116,8 @@
*
* @hide
*/
-@FlaggedApi(android.os.vibrator.Flags.FLAG_ENABLE_VIBRATION_SERIALIZATION_APIS)
@TestApi
+@SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public final class VibrationXmlParser {
private static final String TAG = "VibrationXmlParser";
diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java
index 2880454..2065d5d 100644
--- a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java
+++ b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java
@@ -16,9 +16,9 @@
package android.os.vibrator.persistence;
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.os.CombinedVibration;
import android.os.VibrationEffect;
@@ -43,8 +43,8 @@
*
* @hide
*/
-@FlaggedApi(android.os.vibrator.Flags.FLAG_ENABLE_VIBRATION_SERIALIZATION_APIS)
@TestApi
+@SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public final class VibrationXmlSerializer {
/**
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 8c70501..9d7fb70 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -94,3 +94,11 @@
description: "Enable signature permission allowlist"
bug: "308573169"
}
+
+flag {
+ name: "device_aware_permissions_enabled"
+ is_fixed_read_only: true
+ namespace: "permissions"
+ description: "When the flag is off no permissions can be device aware"
+ bug: "274852670"
+}
\ No newline at end of file
diff --git a/core/java/android/provider/BlockedNumberContract.java b/core/java/android/provider/BlockedNumberContract.java
index 5d00b29..4075e90 100644
--- a/core/java/android/provider/BlockedNumberContract.java
+++ b/core/java/android/provider/BlockedNumberContract.java
@@ -15,12 +15,20 @@
*/
package android.provider;
+import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.annotation.WorkerThread;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.telecom.Log;
+import android.telecom.TelecomManager;
+
+import com.android.server.telecom.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -214,6 +222,333 @@
* <p>TYPE: String</p>
*/
public static final String COLUMN_E164_NUMBER = "e164_number";
+
+ /**
+ * A protected broadcast intent action for letting components with
+ * {@link android.Manifest.permission#READ_BLOCKED_NUMBERS} know that the block suppression
+ * status as returned by {@link #getBlockSuppressionStatus(Context)} has been updated.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static final String ACTION_BLOCK_SUPPRESSION_STATE_CHANGED =
+ "android.provider.action.BLOCK_SUPPRESSION_STATE_CHANGED";
+
+ /**
+ * Preference key of block numbers not in contacts setting.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static final String ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED =
+ "block_numbers_not_in_contacts_setting";
+
+ /**
+ * Preference key of block private number calls setting.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static final String ENHANCED_SETTING_KEY_BLOCK_PRIVATE =
+ "block_private_number_calls_setting";
+
+ /**
+ * Preference key of block payphone calls setting.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static final String ENHANCED_SETTING_KEY_BLOCK_PAYPHONE =
+ "block_payphone_calls_setting";
+
+ /**
+ * Preference key of block unknown calls setting.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static final String ENHANCED_SETTING_KEY_BLOCK_UNKNOWN =
+ "block_unknown_calls_setting";
+
+ /**
+ * Preference key for whether should show an emergency call notification.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static final String ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION =
+ "show_emergency_call_notification";
+
+ /**
+ * Preference key of block unavailable calls setting.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static final String ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE =
+ "block_unavailable_calls_setting";
+
+ /**
+ * Notifies the provider that emergency services were contacted by the user.
+ * <p> This results in {@link #shouldSystemBlockNumber} returning {@code false} independent
+ * of the contents of the provider for a duration defined by
+ * {@link android.telephony.CarrierConfigManager#KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT}
+ * the provider unless {@link #endBlockSuppression(Context)} is called.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.READ_BLOCKED_NUMBERS,
+ android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+ })
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static void notifyEmergencyContact(@NonNull Context context) {
+ verifyBlockedNumbersPermission(context);
+ try {
+ Log.i(LOG_TAG, "notifyEmergencyContact; caller=%s", context.getOpPackageName());
+ context.getContentResolver().call(
+ AUTHORITY_URI, SystemContract.METHOD_NOTIFY_EMERGENCY_CONTACT, null, null);
+ } catch (NullPointerException | IllegalArgumentException ex) {
+ // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
+ // either of these happen.
+ Log.w(null, "notifyEmergencyContact: provider not ready.");
+ }
+ }
+
+ /**
+ * Notifies the provider to disable suppressing blocking. If emergency services were not
+ * contacted recently at all, calling this method is a no-op.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.READ_BLOCKED_NUMBERS,
+ android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+ })
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static void endBlockSuppression(@NonNull Context context) {
+ verifyBlockedNumbersPermission(context);
+ String caller = context.getOpPackageName();
+ Log.i(LOG_TAG, "endBlockSuppression: caller=%s", caller);
+ context.getContentResolver().call(
+ AUTHORITY_URI, SystemContract.METHOD_END_BLOCK_SUPPRESSION, null, null);
+ }
+
+ /**
+ * Returns {@code true} if {@code phoneNumber} is blocked taking
+ * {@link #notifyEmergencyContact(Context)} into consideration. If emergency services
+ * have not been contacted recently and enhanced call blocking not been enabled, this
+ * method is equivalent to {@link #isBlocked(Context, String)}.
+ *
+ * @param context the context of the caller.
+ * @param phoneNumber the number to check.
+ * @param numberPresentation the presentation code associated with the call.
+ * @param isNumberInContacts indicates if the provided number exists as a contact.
+ * @return result code indicating if the number should be blocked, and if so why.
+ * Valid values are: {@link #STATUS_NOT_BLOCKED}, {@link #STATUS_BLOCKED_IN_LIST},
+ * {@link #STATUS_BLOCKED_NOT_IN_CONTACTS}, {@link #STATUS_BLOCKED_PAYPHONE},
+ * {@link #STATUS_BLOCKED_RESTRICTED}, {@link #STATUS_BLOCKED_UNKNOWN_NUMBER}.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.READ_BLOCKED_NUMBERS,
+ android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+ })
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static int shouldSystemBlockNumber(@NonNull Context context,
+ @NonNull String phoneNumber, @TelecomManager.Presentation int numberPresentation,
+ boolean isNumberInContacts) {
+ verifyBlockedNumbersPermission(context);
+ try {
+ String caller = context.getOpPackageName();
+ Bundle extras = new Bundle();
+ extras.putInt(BlockedNumberContract.EXTRA_CALL_PRESENTATION, numberPresentation);
+ extras.putBoolean(BlockedNumberContract.EXTRA_CONTACT_EXIST, isNumberInContacts);
+ final Bundle res = context.getContentResolver().call(AUTHORITY_URI,
+ SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER, phoneNumber, extras);
+ int blockResult = res != null ? res.getInt(RES_BLOCK_STATUS, STATUS_NOT_BLOCKED) :
+ BlockedNumberContract.STATUS_NOT_BLOCKED;
+ Log.d(LOG_TAG, "shouldSystemBlockNumber: number=%s, caller=%s, result=%s",
+ Log.piiHandle(phoneNumber), caller,
+ SystemContract.blockStatusToString(blockResult));
+ return blockResult;
+ } catch (NullPointerException | IllegalArgumentException ex) {
+ // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
+ // either of these happen.
+ Log.w(null, "shouldSystemBlockNumber: provider not ready.");
+ return BlockedNumberContract.STATUS_NOT_BLOCKED;
+ }
+ }
+
+ /**
+ * Returns the current status of block suppression.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.READ_BLOCKED_NUMBERS,
+ android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+ })
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static @NonNull BlockSuppressionStatus getBlockSuppressionStatus(
+ @NonNull Context context) {
+ verifyBlockedNumbersPermission(context);
+ final Bundle res = context.getContentResolver().call(
+ AUTHORITY_URI, SystemContract.METHOD_GET_BLOCK_SUPPRESSION_STATUS, null, null);
+ BlockSuppressionStatus blockSuppressionStatus = new BlockSuppressionStatus(
+ res.getBoolean(SystemContract.RES_IS_BLOCKING_SUPPRESSED, false),
+ res.getLong(SystemContract.RES_BLOCKING_SUPPRESSED_UNTIL_TIMESTAMP, 0));
+ Log.d(LOG_TAG, "getBlockSuppressionStatus: caller=%s, status=%s",
+ context.getOpPackageName(), blockSuppressionStatus);
+ return blockSuppressionStatus;
+ }
+
+ /**
+ * Check whether should show the emergency call notification.
+ *
+ * @param context the context of the caller.
+ * @return {@code true} if should show emergency call notification. {@code false} otherwise.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.READ_BLOCKED_NUMBERS,
+ android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+ })
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static boolean shouldShowEmergencyCallNotification(@NonNull Context context) {
+ verifyBlockedNumbersPermission(context);
+ try {
+ final Bundle res = context.getContentResolver().call(AUTHORITY_URI,
+ SystemContract.METHOD_SHOULD_SHOW_EMERGENCY_CALL_NOTIFICATION, null, null);
+ return res != null && res.getBoolean(RES_SHOW_EMERGENCY_CALL_NOTIFICATION, false);
+ } catch (NullPointerException | IllegalArgumentException ex) {
+ // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
+ // either of these happen.
+ Log.w(null, "shouldShowEmergencyCallNotification: provider not ready.");
+ return false;
+ }
+ }
+
+ /**
+ * Check whether the enhanced block setting is enabled.
+ *
+ * @param context the context of the caller.
+ * @param key the key of the setting to check, can be
+ * {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED}
+ * {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_PRIVATE}
+ * {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_PAYPHONE}
+ * {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNKNOWN}
+ * {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE}
+ * {@link SystemContract#ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION}
+ * @return {@code true} if the setting is enabled. {@code false} otherwise.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.READ_BLOCKED_NUMBERS,
+ android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+ })
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static boolean getBlockedNumberSetting(
+ @NonNull Context context, @NonNull String key) {
+ verifyBlockedNumbersPermission(context);
+ Bundle extras = new Bundle();
+ extras.putString(EXTRA_ENHANCED_SETTING_KEY, key);
+ try {
+ final Bundle res = context.getContentResolver().call(AUTHORITY_URI,
+ SystemContract.METHOD_GET_ENHANCED_BLOCK_SETTING, null, extras);
+ return res != null && res.getBoolean(RES_ENHANCED_SETTING_IS_ENABLED, false);
+ } catch (NullPointerException | IllegalArgumentException ex) {
+ // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
+ // either of these happen.
+ Log.w(null, "getEnhancedBlockSetting: provider not ready.");
+ return false;
+ }
+ }
+
+ /**
+ * Set the enhanced block setting enabled status.
+ *
+ * @param context the context of the caller.
+ * @param key the key of the setting to set, can be
+ * {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED}
+ * {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_PRIVATE}
+ * {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_PAYPHONE}
+ * {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNKNOWN}
+ * {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE}
+ * {@link SystemContract#ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION}
+ * @param value the enabled statue of the setting to set.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.READ_BLOCKED_NUMBERS,
+ android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+ })
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static void setBlockedNumberSetting(@NonNull Context context,
+ @NonNull String key, boolean value) {
+ verifyBlockedNumbersPermission(context);
+ Bundle extras = new Bundle();
+ extras.putString(EXTRA_ENHANCED_SETTING_KEY, key);
+ extras.putBoolean(EXTRA_ENHANCED_SETTING_VALUE, value);
+ context.getContentResolver().call(AUTHORITY_URI,
+ SystemContract.METHOD_SET_ENHANCED_BLOCK_SETTING, null, extras);
+ }
+
+ /**
+ * Represents the current status of
+ * {@link #shouldSystemBlockNumber(Context, String, int, boolean)}. If emergency services
+ * have been contacted recently, {@link #mIsSuppressed} is {@code true}, and blocking
+ * is disabled until the timestamp {@link #mUntilTimestampMillis}.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static final class BlockSuppressionStatus {
+ private boolean mIsSuppressed;
+
+ /**
+ * Timestamp in milliseconds from epoch.
+ */
+ private long mUntilTimestampMillis;
+
+ public BlockSuppressionStatus(boolean isSuppressed, long untilTimestampMillis) {
+ this.mIsSuppressed = isSuppressed;
+ this.mUntilTimestampMillis = untilTimestampMillis;
+ }
+
+ @Override
+ public String toString() {
+ return "[BlockSuppressionStatus; isSuppressed=" + mIsSuppressed + ", until="
+ + mUntilTimestampMillis + "]";
+ }
+
+ public boolean getIsSuppressed() {
+ return mIsSuppressed;
+ }
+
+ public long getUntilTimestampMillis() {
+ return mUntilTimestampMillis;
+ }
+ }
+
+ /**
+ * Verifies that the caller holds both the
+ * {@link android.Manifest.permission#READ_BLOCKED_NUMBERS} permission and the
+ * {@link android.Manifest.permission#WRITE_BLOCKED_NUMBERS} permission.
+ *
+ * @param context
+ * @throws SecurityException if the caller is missing the necessary permissions
+ */
+ private static void verifyBlockedNumbersPermission(Context context) {
+ context.enforceCallingOrSelfPermission(Manifest.permission.READ_BLOCKED_NUMBERS,
+ "Caller does not have the android.permission.READ_BLOCKED_NUMBERS permission");
+ context.enforceCallingOrSelfPermission(Manifest.permission.WRITE_BLOCKED_NUMBERS,
+ "Caller does not have the android.permission.WRITE_BLOCKED_NUMBERS permission");
+ }
}
/** @hide */
@@ -558,7 +893,7 @@
* {@link #ENHANCED_SETTING_KEY_BLOCK_PAYPHONE}
* {@link #ENHANCED_SETTING_KEY_BLOCK_UNKNOWN}
* {@link #ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE}
- * {@link #ENHANCED_SETTING_KEY_EMERGENCY_CALL_NOTIFICATION_SHOWING}
+ * {@link #ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION}
* @return {@code true} if the setting is enabled. {@code false} otherwise.
*/
public static boolean getEnhancedBlockSetting(Context context, String key) {
@@ -586,7 +921,7 @@
* {@link #ENHANCED_SETTING_KEY_BLOCK_PAYPHONE}
* {@link #ENHANCED_SETTING_KEY_BLOCK_UNKNOWN}
* {@link #ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE}
- * {@link #ENHANCED_SETTING_KEY_EMERGENCY_CALL_NOTIFICATION_SHOWING}
+ * {@link #ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION}
* @param value the enabled statue of the setting to set.
*/
public static void setEnhancedBlockSetting(Context context, String key, boolean value) {
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 7d127ad..c13dd36 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -19,6 +19,7 @@
import android.Manifest;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.LongDef;
import android.annotation.NonNull;
@@ -55,6 +56,8 @@
import android.text.TextUtils;
import android.util.Log;
+import com.android.server.telecom.flags.Flags;
+
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@@ -427,6 +430,8 @@
private double mLongitude = Double.NaN;
private Uri mPictureUri;
private int mIsPhoneAccountMigrationPending;
+ private boolean mIsBusinessCall;
+ private String mBusinessName;
/**
* @param callerInfo the CallerInfo object to get the target contact from.
@@ -645,15 +650,44 @@
}
/**
+ * @param isBusinessCall should be set if the caller is a business call
+ */
+ @FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER)
+ public @NonNull AddCallParametersBuilder setIsBusinessCall(boolean isBusinessCall) {
+ mIsBusinessCall = isBusinessCall;
+ return this;
+ }
+
+ /**
+ * @param businessName should be set if the caller is a business call
+ */
+ @FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER)
+ public @NonNull AddCallParametersBuilder setBusinessName(String businessName) {
+ mBusinessName = businessName;
+ return this;
+ }
+
+ /**
* Builds the object
*/
public @NonNull AddCallParams build() {
- return new AddCallParams(mCallerInfo, mNumber, mPostDialDigits, mViaNumber,
- mPresentation, mCallType, mFeatures, mAccountHandle, mStart, mDuration,
- mDataUsage, mAddForAllUsers, mUserToBeInsertedTo, mIsRead, mCallBlockReason,
- mCallScreeningAppName, mCallScreeningComponentName, mMissedReason,
- mPriority, mSubject, mLatitude, mLongitude, mPictureUri,
- mIsPhoneAccountMigrationPending);
+ if (Flags.businessCallComposer()) {
+ return new AddCallParams(mCallerInfo, mNumber, mPostDialDigits, mViaNumber,
+ mPresentation, mCallType, mFeatures, mAccountHandle, mStart, mDuration,
+ mDataUsage, mAddForAllUsers, mUserToBeInsertedTo, mIsRead,
+ mCallBlockReason,
+ mCallScreeningAppName, mCallScreeningComponentName, mMissedReason,
+ mPriority, mSubject, mLatitude, mLongitude, mPictureUri,
+ mIsPhoneAccountMigrationPending, mIsBusinessCall, mBusinessName);
+ } else {
+ return new AddCallParams(mCallerInfo, mNumber, mPostDialDigits, mViaNumber,
+ mPresentation, mCallType, mFeatures, mAccountHandle, mStart, mDuration,
+ mDataUsage, mAddForAllUsers, mUserToBeInsertedTo, mIsRead,
+ mCallBlockReason,
+ mCallScreeningAppName, mCallScreeningComponentName, mMissedReason,
+ mPriority, mSubject, mLatitude, mLongitude, mPictureUri,
+ mIsPhoneAccountMigrationPending);
+ }
}
}
@@ -681,6 +715,8 @@
private double mLongitude = Double.NaN;
private Uri mPictureUri;
private int mIsPhoneAccountMigrationPending;
+ private boolean mIsBusinessCall;
+ private String mBusinessName;
private AddCallParams(CallerInfo callerInfo, String number, String postDialDigits,
String viaNumber, int presentation, int callType, int features,
@@ -717,6 +753,43 @@
mIsPhoneAccountMigrationPending = isPhoneAccountMigrationPending;
}
+ private AddCallParams(CallerInfo callerInfo, String number, String postDialDigits,
+ String viaNumber, int presentation, int callType, int features,
+ PhoneAccountHandle accountHandle, long start, int duration, long dataUsage,
+ boolean addForAllUsers, UserHandle userToBeInsertedTo, boolean isRead,
+ int callBlockReason,
+ CharSequence callScreeningAppName, String callScreeningComponentName,
+ long missedReason,
+ int priority, String subject, double latitude, double longitude, Uri pictureUri,
+ int isPhoneAccountMigrationPending, boolean isBusinessCall, String businessName) {
+ mCallerInfo = callerInfo;
+ mNumber = number;
+ mPostDialDigits = postDialDigits;
+ mViaNumber = viaNumber;
+ mPresentation = presentation;
+ mCallType = callType;
+ mFeatures = features;
+ mAccountHandle = accountHandle;
+ mStart = start;
+ mDuration = duration;
+ mDataUsage = dataUsage;
+ mAddForAllUsers = addForAllUsers;
+ mUserToBeInsertedTo = userToBeInsertedTo;
+ mIsRead = isRead;
+ mCallBlockReason = callBlockReason;
+ mCallScreeningAppName = callScreeningAppName;
+ mCallScreeningComponentName = callScreeningComponentName;
+ mMissedReason = missedReason;
+ mPriority = priority;
+ mSubject = subject;
+ mLatitude = latitude;
+ mLongitude = longitude;
+ mPictureUri = pictureUri;
+ mIsPhoneAccountMigrationPending = isPhoneAccountMigrationPending;
+ mIsBusinessCall = isBusinessCall;
+ mBusinessName = businessName;
+ }
+
}
/**
@@ -915,6 +988,19 @@
*/
public static final String NUMBER = "number";
+
+ /**
+ * Boolean indicating whether the call is a business call.
+ */
+ @FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER)
+ public static final String IS_BUSINESS_CALL = "is_business_call";
+
+ /**
+ * String that stores the asserted display name associated with business call.
+ */
+ @FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER)
+ public static final String ASSERTED_DISPLAY_NAME = "asserted_display_name";
+
/**
* The number presenting rules set by the network.
*
@@ -1713,7 +1799,6 @@
}
ContentValues values = new ContentValues(14);
-
values.put(NUMBER, params.mNumber);
values.put(POST_DIAL_DIGITS, params.mPostDialDigits);
values.put(VIA_NUMBER, params.mViaNumber);
@@ -1746,7 +1831,10 @@
values.put(COMPOSER_PHOTO_URI, params.mPictureUri.toString());
}
values.put(IS_PHONE_ACCOUNT_MIGRATION_PENDING, params.mIsPhoneAccountMigrationPending);
-
+ if (Flags.businessCallComposer()) {
+ values.put(IS_BUSINESS_CALL, Integer.valueOf(params.mIsBusinessCall ? 1 : 0));
+ values.put(ASSERTED_DISPLAY_NAME, params.mBusinessName);
+ }
if ((params.mCallerInfo != null) && (params.mCallerInfo.getContactId() > 0)) {
// Update usage information for the number associated with the contact ID.
// We need to use both the number and the ID for obtaining a data ID since other
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b026ce9..ecf1937 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11061,6 +11061,15 @@
public static final String SEARCH_LONG_PRESS_HOME_ENABLED =
"search_long_press_home_enabled";
+
+ /**
+ * Whether or not the accessibility data streaming is enbled for the
+ * {@link VisualQueryDetectedResult#setAccessibilityDetectionData}.
+ * @hide
+ */
+ public static final String VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED =
+ "visual_query_accessibility_detection_enabled";
+
/**
* Control whether Night display is currently activated.
* @hide
@@ -12286,6 +12295,14 @@
"extra_automatic_power_save_mode";
/**
+ * Whether contextual screen timeout is enabled.
+ *
+ * @hide
+ */
+ public static final String CONTEXTUAL_SCREEN_TIMEOUT_ENABLED =
+ "contextual_screen_timeout_enabled";
+
+ /**
* Whether lockscreen weather is enabled.
*
* @hide
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 658cec8..88bd87e 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4956,6 +4956,26 @@
*/
public static final String COLUMN_SERVICE_CAPABILITIES = "service_capabilities";
+ /**
+ * TelephonyProvider column name for satellite entitlement status. The value of this column
+ * is set based on entitlement query result for satellite configuration.
+ * By default, it's disabled.
+ *
+ * @hide
+ */
+ public static final String COLUMN_SATELLITE_ENTITLEMENT_STATUS =
+ "satellite_entitlement_status";
+
+ /**
+ * TelephonyProvider column name for satellite entitlement plmns. The value of this
+ * column is set based on entitlement query result for satellite configuration.
+ * By default, it's empty.
+ *
+ * @hide
+ */
+ public static final String COLUMN_SATELLITE_ENTITLEMENT_PLMNS =
+ "satellite_entitlement_plmns";
+
/** All columns in {@link SimInfo} table. */
private static final List<String> ALL_COLUMNS = List.of(
COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID,
@@ -5029,7 +5049,9 @@
COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
COLUMN_IS_NTN,
COLUMN_SERVICE_CAPABILITIES,
- COLUMN_TRANSFER_STATUS
+ COLUMN_TRANSFER_STATUS,
+ COLUMN_SATELLITE_ENTITLEMENT_STATUS,
+ COLUMN_SATELLITE_ENTITLEMENT_PLMNS
);
/**
diff --git a/core/java/android/service/chooser/AdditionalContentContract.java b/core/java/android/service/chooser/AdditionalContentContract.java
new file mode 100644
index 0000000..f679e8a
--- /dev/null
+++ b/core/java/android/service/chooser/AdditionalContentContract.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.chooser;
+
+import android.annotation.FlaggedApi;
+
+/**
+ * Specifies constants used by Chooser when interacting with the additional content provider,
+ * see {@link android.content.Intent#EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI}.
+ */
+@FlaggedApi(android.service.chooser.Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING)
+public interface AdditionalContentContract {
+
+ interface Columns {
+ /**
+ * Content URI for this item.
+ * <p>
+ * Note that this content URI must have a different authority from the content provided
+ * given in {@link android.content.Intent#EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI}.
+ */
+ String URI = "uri";
+ }
+
+ /**
+ * Constants for {@link android.database.Cursor#getExtras} keys.
+ */
+ interface CursorExtraKeys {
+ /**
+ * An integer, zero-based cursor position that corresponds to the URI specified
+ * with the {@link android.content.Intent#EXTRA_CHOOSER_FOCUSED_ITEM_POSITION} index into
+ * the @link android.content.Intent#EXTRA_STREAM} array.
+ */
+ String POSITION = "position";
+ }
+
+ /**
+ * Constants for method names used with {@link android.content.ContentResolver#call} method.
+ */
+ interface MethodNames {
+ /**
+ * A method name Chooser is using to notify the sharing app about a shared items selection
+ * change.
+ */
+ String ON_SELECTION_CHANGED = "onSelectionChanged";
+ }
+}
diff --git a/core/java/android/service/chooser/ChooserResult.java b/core/java/android/service/chooser/ChooserResult.java
new file mode 100644
index 0000000..4603be1
--- /dev/null
+++ b/core/java/android/service/chooser/ChooserResult.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.chooser;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.compat.annotation.Overridable;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * An event reported to a supplied [IntentSender] by the system chooser when an activity is selected
+ * or other actions are taken to complete the session.
+ *
+ * @see Intent#EXTRA_CHOOSER_RESULT_INTENT_SENDER
+ */
+@FlaggedApi(android.service.chooser.Flags.FLAG_ENABLE_CHOOSER_RESULT)
+public final class ChooserResult implements Parcelable {
+
+ /**
+ * Controls whether to send ChooserResult to the optional IntentSender supplied to the Chooser.
+ * <p>
+ * When enabled, ChooserResult is added to the provided Intent as
+ * {@link Intent#EXTRA_CHOOSER_RESULT}, and sent for actions such as copy and edit, in addition
+ * to activity selection. When disabled, only the selected component
+ * is provided in {@link Intent#EXTRA_CHOSEN_COMPONENT}.
+ * <p>
+ * See: {@link Intent#createChooser(Intent, CharSequence, IntentSender)}
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @Overridable
+ public static final long SEND_CHOOSER_RESULT = 263474465L;
+
+ /** @hide */
+ @IntDef({
+ CHOOSER_RESULT_UNKNOWN,
+ CHOOSER_RESULT_SELECTED_COMPONENT,
+ CHOOSER_RESULT_COPY,
+ CHOOSER_RESULT_EDIT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResultType { }
+
+ /** An unknown action was taken to complete the session. */
+ public static final int CHOOSER_RESULT_UNKNOWN = -1;
+ /** The session was completed by selecting an activity to launch. */
+ public static final int CHOOSER_RESULT_SELECTED_COMPONENT = 0;
+ /** The session was completed by invoking the copy action. */
+ public static final int CHOOSER_RESULT_COPY = 1;
+ /** The session was completed by invoking the edit action. */
+ public static final int CHOOSER_RESULT_EDIT = 2;
+
+ @ResultType
+ private final int mType;
+ private final ComponentName mSelectedComponent;
+ private final boolean mIsShortcut;
+
+ private ChooserResult(@NonNull Parcel source) {
+ mType = source.readInt();
+ mSelectedComponent = ComponentName.readFromParcel(source);
+ mIsShortcut = source.readBoolean();
+ }
+
+ /** @hide */
+ public ChooserResult(@ResultType int type, @Nullable ComponentName componentName,
+ boolean isShortcut) {
+ mType = type;
+ mSelectedComponent = componentName;
+ mIsShortcut = isShortcut;
+ }
+
+ /**
+ * The type of the result.
+ *
+ * @return the type of the result
+ */
+ @ResultType
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Provides the component of the Activity selected for results with type
+ * when type is {@link ChooserResult#CHOOSER_RESULT_SELECTED_COMPONENT}.
+ * <p>
+ * For all other types, this value is null.
+ *
+ * @return the component name selected
+ */
+ @Nullable
+ public ComponentName getSelectedComponent() {
+ return mSelectedComponent;
+ }
+
+ /**
+ * Whether the selected component was provided by the app from as a shortcut.
+ *
+ * @return true if the selected component is a shortcut, false otherwise
+ */
+ public boolean isShortcut() {
+ return mIsShortcut;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<ChooserResult> CREATOR =
+ new Creator<>() {
+ @Override
+ public ChooserResult createFromParcel(Parcel source) {
+ return new ChooserResult(source);
+ }
+
+ @Override
+ public ChooserResult[] newArray(int size) {
+ return new ChooserResult[0];
+ }
+ };
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mType);
+ ComponentName.writeToParcel(mSelectedComponent, dest);
+ dest.writeBoolean(mIsShortcut);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ChooserResult that = (ChooserResult) o;
+ return mType == that.mType
+ && mIsShortcut == that.mIsShortcut
+ && Objects.equals(mSelectedComponent, that.mSelectedComponent);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mType, mSelectedComponent, mIsShortcut);
+ }
+}
diff --git a/core/java/android/service/notification/ZenDeviceEffects.java b/core/java/android/service/notification/ZenDeviceEffects.java
index 90049e6..22b1be0 100644
--- a/core/java/android/service/notification/ZenDeviceEffects.java
+++ b/core/java/android/service/notification/ZenDeviceEffects.java
@@ -20,6 +20,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.app.Flags;
import android.os.Parcel;
import android.os.Parcelable;
@@ -27,7 +28,10 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.Objects;
+import java.util.Set;
/**
* Represents the set of device effects (affecting display and device behavior in general) that
@@ -51,6 +55,7 @@
FIELD_DISABLE_TOUCH,
FIELD_MINIMIZE_RADIO_USAGE,
FIELD_MAXIMIZE_DOZE,
+ FIELD_EXTRA_EFFECTS
})
@Retention(RetentionPolicy.SOURCE)
public @interface ModifiableField {}
@@ -95,6 +100,12 @@
* @hide
*/
public static final int FIELD_MAXIMIZE_DOZE = 1 << 9;
+ /**
+ * @hide
+ */
+ public static final int FIELD_EXTRA_EFFECTS = 1 << 10;
+
+ private static final int MAX_EFFECTS_LENGTH = 2_000; // characters
private final boolean mGrayscale;
private final boolean mSuppressAmbientDisplay;
@@ -107,11 +118,12 @@
private final boolean mDisableTouch;
private final boolean mMinimizeRadioUsage;
private final boolean mMaximizeDoze;
+ private final Set<String> mExtraEffects;
private ZenDeviceEffects(boolean grayscale, boolean suppressAmbientDisplay,
boolean dimWallpaper, boolean nightMode, boolean disableAutoBrightness,
boolean disableTapToWake, boolean disableTiltToWake, boolean disableTouch,
- boolean minimizeRadioUsage, boolean maximizeDoze) {
+ boolean minimizeRadioUsage, boolean maximizeDoze, Set<String> extraEffects) {
mGrayscale = grayscale;
mSuppressAmbientDisplay = suppressAmbientDisplay;
mDimWallpaper = dimWallpaper;
@@ -122,6 +134,21 @@
mDisableTouch = disableTouch;
mMinimizeRadioUsage = minimizeRadioUsage;
mMaximizeDoze = maximizeDoze;
+ mExtraEffects = Collections.unmodifiableSet(extraEffects);
+ }
+
+ /** @hide */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public void validate() {
+ int extraEffectsLength = 0;
+ for (String extraEffect : mExtraEffects) {
+ extraEffectsLength += extraEffect.length();
+ }
+ if (extraEffectsLength > MAX_EFFECTS_LENGTH) {
+ throw new IllegalArgumentException(
+ "Total size of extra effects must be at most " + MAX_EFFECTS_LENGTH
+ + " characters");
+ }
}
@Override
@@ -138,19 +165,20 @@
&& this.mDisableTiltToWake == that.mDisableTiltToWake
&& this.mDisableTouch == that.mDisableTouch
&& this.mMinimizeRadioUsage == that.mMinimizeRadioUsage
- && this.mMaximizeDoze == that.mMaximizeDoze;
+ && this.mMaximizeDoze == that.mMaximizeDoze
+ && Objects.equals(this.mExtraEffects, that.mExtraEffects);
}
@Override
public int hashCode() {
return Objects.hash(mGrayscale, mSuppressAmbientDisplay, mDimWallpaper, mNightMode,
mDisableAutoBrightness, mDisableTapToWake, mDisableTiltToWake, mDisableTouch,
- mMinimizeRadioUsage, mMaximizeDoze);
+ mMinimizeRadioUsage, mMaximizeDoze, mExtraEffects);
}
@Override
public String toString() {
- ArrayList<String> effects = new ArrayList<>(10);
+ ArrayList<String> effects = new ArrayList<>(11);
if (mGrayscale) effects.add("grayscale");
if (mSuppressAmbientDisplay) effects.add("suppressAmbientDisplay");
if (mDimWallpaper) effects.add("dimWallpaper");
@@ -161,6 +189,9 @@
if (mDisableTouch) effects.add("disableTouch");
if (mMinimizeRadioUsage) effects.add("minimizeRadioUsage");
if (mMaximizeDoze) effects.add("maximizeDoze");
+ if (mExtraEffects.size() > 0) {
+ effects.add("extraEffects=[" + String.join(",", mExtraEffects) + "]");
+ }
return "[" + String.join(", ", effects) + "]";
}
@@ -197,6 +228,9 @@
if ((bitmask & FIELD_MAXIMIZE_DOZE) != 0) {
modified.add("FIELD_MAXIMIZE_DOZE");
}
+ if ((bitmask & FIELD_EXTRA_EFFECTS) != 0) {
+ modified.add("FIELD_EXTRA_EFFECTS");
+ }
return "{" + String.join(",", modified) + "}";
}
@@ -270,7 +304,7 @@
}
/**
- * Whether Doze should be enhanced (e.g. with more aggresive activation, or less frequent
+ * Whether Doze should be enhanced (e.g. with more aggressive activation, or less frequent
* maintenance windows) while the rule is active.
* @hide
*/
@@ -279,13 +313,26 @@
}
/**
+ * (Immutable) set of extra effects to be applied while the rule is active. Extra effects are
+ * not used in AOSP, but OEMs may add support for them by providing a custom
+ * {@link DeviceEffectsApplier}.
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public Set<String> getExtraEffects() {
+ return mExtraEffects;
+ }
+
+ /**
* Whether any of the effects are set up.
* @hide
*/
public boolean hasEffects() {
return mGrayscale || mSuppressAmbientDisplay || mDimWallpaper || mNightMode
|| mDisableAutoBrightness || mDisableTapToWake || mDisableTiltToWake
- || mDisableTouch || mMinimizeRadioUsage || mMaximizeDoze;
+ || mDisableTouch || mMinimizeRadioUsage || mMaximizeDoze
+ || mExtraEffects.size() > 0;
}
/** {@link Parcelable.Creator} that instantiates {@link ZenDeviceEffects} objects. */
@@ -296,7 +343,8 @@
return new ZenDeviceEffects(in.readBoolean(),
in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(),
in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(),
- in.readBoolean());
+ in.readBoolean(),
+ Set.of(in.readArray(String.class.getClassLoader(), String.class)));
}
@Override
@@ -322,6 +370,7 @@
dest.writeBoolean(mDisableTouch);
dest.writeBoolean(mMinimizeRadioUsage);
dest.writeBoolean(mMaximizeDoze);
+ dest.writeArray(mExtraEffects.toArray(new String[0]));
}
/** Builder class for {@link ZenDeviceEffects} objects. */
@@ -338,6 +387,7 @@
private boolean mDisableTouch;
private boolean mMinimizeRadioUsage;
private boolean mMaximizeDoze;
+ private final HashSet<String> mExtraEffects = new HashSet<>();
/**
* Instantiates a new {@link ZenPolicy.Builder} with all effects set to default (disabled).
@@ -360,6 +410,7 @@
mDisableTouch = zenDeviceEffects.shouldDisableTouch();
mMinimizeRadioUsage = zenDeviceEffects.shouldMinimizeRadioUsage();
mMaximizeDoze = zenDeviceEffects.shouldMaximizeDoze();
+ mExtraEffects.addAll(zenDeviceEffects.getExtraEffects());
}
/**
@@ -450,7 +501,7 @@
}
/**
- * Sets whether Doze should be enhanced (e.g. with more aggresive activation, or less
+ * Sets whether Doze should be enhanced (e.g. with more aggressive activation, or less
* frequent maintenance windows) while the rule is active.
* @hide
*/
@@ -461,6 +512,54 @@
}
/**
+ * Sets the extra effects to be applied while the rule is active. Extra effects are not
+ * used in AOSP, but OEMs may add support for them by providing a custom
+ * {@link DeviceEffectsApplier}.
+ *
+ * @apiNote The total size of the extra effects (concatenation of strings) is limited.
+ *
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public Builder setExtraEffects(@NonNull Set<String> extraEffects) {
+ Objects.requireNonNull(extraEffects);
+ mExtraEffects.clear();
+ mExtraEffects.addAll(extraEffects);
+ return this;
+ }
+
+ /**
+ * Adds the supplied extra effects to the set to be applied while the rule is active.
+ * Extra effects are not used in AOSP, but OEMs may add support for them by providing a
+ * custom {@link DeviceEffectsApplier}.
+ *
+ * @apiNote The total size of the extra effects (concatenation of strings) is limited.
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder addExtraEffects(@NonNull Set<String> extraEffects) {
+ mExtraEffects.addAll(Objects.requireNonNull(extraEffects));
+ return this;
+ }
+
+ /**
+ * Adds the supplied extra effect to the set to be applied while the rule is active.
+ * Extra effects are not used in AOSP, but OEMs may add support for them by providing a
+ * custom {@link DeviceEffectsApplier}.
+ *
+ * @apiNote The total size of the extra effects (concatenation of strings) is limited.
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder addExtraEffect(@NonNull String extraEffect) {
+ mExtraEffects.add(Objects.requireNonNull(extraEffect));
+ return this;
+ }
+
+ /**
* Applies the effects that are {@code true} on the supplied {@link ZenDeviceEffects} to
* this builder (essentially logically-ORing the effect set).
* @hide
@@ -478,6 +577,7 @@
if (effects.shouldDisableTouch()) setShouldDisableTouch(true);
if (effects.shouldMinimizeRadioUsage()) setShouldMinimizeRadioUsage(true);
if (effects.shouldMaximizeDoze()) setShouldMaximizeDoze(true);
+ addExtraEffects(effects.getExtraEffects());
return this;
}
@@ -487,7 +587,7 @@
return new ZenDeviceEffects(mGrayscale,
mSuppressAmbientDisplay, mDimWallpaper, mNightMode, mDisableAutoBrightness,
mDisableTapToWake, mDisableTiltToWake, mDisableTouch, mMinimizeRadioUsage,
- mMaximizeDoze);
+ mMaximizeDoze, mExtraEffects);
}
}
}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index d4a5356..f169ecd 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -27,6 +27,7 @@
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AlarmManager;
@@ -65,17 +66,21 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.Instant;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
+import java.util.regex.Pattern;
/**
* Persisted configuration for zen mode.
@@ -272,8 +277,13 @@
private static final String DEVICE_EFFECT_DISABLE_TOUCH = "zdeDisableTouch";
private static final String DEVICE_EFFECT_MINIMIZE_RADIO_USAGE = "zdeMinimizeRadioUsage";
private static final String DEVICE_EFFECT_MAXIMIZE_DOZE = "zdeMaximizeDoze";
+ private static final String DEVICE_EFFECT_EXTRAS = "zdeExtraEffects";
private static final String DEVICE_EFFECT_USER_MODIFIED_FIELDS = "zdeUserModifiedFields";
+ private static final String ITEM_SEPARATOR = ",";
+ private static final String ITEM_SEPARATOR_ESCAPE = "\\";
+ private static final Pattern ITEM_SPLITTER_REGEX = Pattern.compile("(?<!\\\\),");
+
@UnsupportedAppUsage
public boolean allowAlarms = DEFAULT_ALLOW_ALARMS;
public boolean allowMedia = DEFAULT_ALLOW_MEDIA;
@@ -1099,6 +1109,7 @@
.setShouldMinimizeRadioUsage(
safeBoolean(parser, DEVICE_EFFECT_MINIMIZE_RADIO_USAGE, false))
.setShouldMaximizeDoze(safeBoolean(parser, DEVICE_EFFECT_MAXIMIZE_DOZE, false))
+ .setExtraEffects(safeStringSet(parser, DEVICE_EFFECT_EXTRAS))
.build();
return deviceEffects.hasEffects() ? deviceEffects : null;
@@ -1123,6 +1134,7 @@
writeBooleanIfTrue(out, DEVICE_EFFECT_MINIMIZE_RADIO_USAGE,
deviceEffects.shouldMinimizeRadioUsage());
writeBooleanIfTrue(out, DEVICE_EFFECT_MAXIMIZE_DOZE, deviceEffects.shouldMaximizeDoze());
+ writeStringSet(out, DEVICE_EFFECT_EXTRAS, deviceEffects.getExtraEffects());
}
private static void writeBooleanIfTrue(TypedXmlSerializer out, String att, boolean value)
@@ -1132,6 +1144,26 @@
}
}
+ private static void writeStringSet(TypedXmlSerializer out, String att, Set<String> values)
+ throws IOException {
+ if (values.isEmpty()) {
+ return;
+ }
+ // We escape each item by replacing "\" by "\\" and "," by "\,". Then we concatenate with
+ // "," as separator. Reading performs the same operations in the opposite order.
+ List<String> escapedItems = new ArrayList<>();
+ for (String item : values) {
+ escapedItems.add(
+ item
+ .replace(ITEM_SEPARATOR_ESCAPE,
+ ITEM_SEPARATOR_ESCAPE + ITEM_SEPARATOR_ESCAPE)
+ .replace(ITEM_SEPARATOR,
+ ITEM_SEPARATOR_ESCAPE + ITEM_SEPARATOR));
+ }
+ String serialized = String.join(ITEM_SEPARATOR, escapedItems);
+ out.attribute(null, att, serialized);
+ }
+
public static boolean isValidHour(int val) {
return val >= 0 && val < 24;
}
@@ -1182,6 +1214,26 @@
return tryParseLong(val, defValue);
}
+ @NonNull
+ private static Set<String> safeStringSet(TypedXmlPullParser parser, String att) {
+ Set<String> values = new HashSet<>();
+
+ String serialized = parser.getAttributeValue(null, att);
+ if (!TextUtils.isEmpty(serialized)) {
+ // We split on every "," that is *not preceded* by the escape character "\".
+ // Then we reverse the escaping done on each individual item.
+ String[] escapedItems = ITEM_SPLITTER_REGEX.split(serialized);
+ for (String escapedItem : escapedItems) {
+ values.add(escapedItem
+ .replace(ITEM_SEPARATOR_ESCAPE + ITEM_SEPARATOR_ESCAPE,
+ ITEM_SEPARATOR_ESCAPE)
+ .replace(ITEM_SEPARATOR_ESCAPE + ITEM_SEPARATOR,
+ ITEM_SEPARATOR));
+ }
+ }
+ return values;
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 6410609..2028c40 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -711,18 +711,21 @@
public void draw(Canvas c, Path highlight, Paint highlightpaint,
int cursorOffset) {
if (mDirect != null && highlight == null) {
+ float leftShift = 0;
if (getUseBoundsForWidth()) {
- c.save();
RectF drawingRect = computeDrawingBoundingBox();
if (drawingRect.left < 0) {
- c.translate(-drawingRect.left, 0);
+ leftShift = -drawingRect.left;
+ c.translate(leftShift, 0);
}
}
c.drawText(mDirect, 0, mBottom - mDesc, mPaint);
- if (getUseBoundsForWidth()) {
- c.restore();
+ if (leftShift != 0) {
+ // Manually translate back to the original position because of b/324498002, using
+ // save/restore disappears the toggle switch drawables.
+ c.translate(-leftShift, 0);
}
} else {
super.draw(c, highlight, highlightpaint, cursorOffset);
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 8ddb42d..e5d199a 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -464,11 +464,12 @@
@Nullable Path selectionPath,
@Nullable Paint selectionPaint,
int cursorOffsetVertical) {
+ float leftShift = 0;
if (mUseBoundsForWidth) {
- canvas.save();
RectF drawingRect = computeDrawingBoundingBox();
if (drawingRect.left < 0) {
- canvas.translate(-drawingRect.left, 0);
+ leftShift = -drawingRect.left;
+ canvas.translate(leftShift, 0);
}
}
final long lineRange = getLineRangeForDraw(canvas);
@@ -479,8 +480,10 @@
drawWithoutText(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint,
cursorOffsetVertical, firstLine, lastLine);
drawText(canvas, firstLine, lastLine);
- if (mUseBoundsForWidth) {
- canvas.restore();
+ if (leftShift != 0) {
+ // Manually translate back to the original position because of b/324498002, using
+ // save/restore disappears the toggle switch drawables.
+ canvas.translate(-leftShift, 0);
}
}
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index f37c4c2a..f68fcab9 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -110,3 +110,12 @@
description: "A flag for replacing AndroidBidi with android.icu.text.Bidi."
bug: "317144801"
}
+
+flag {
+ name: "lazy_variation_instance"
+ namespace: "text"
+ description: "A flag for enabling lazy variation instance creation."
+ # Make read only, as it could be used before the Settings provider is initialized.
+ is_fixed_read_only: true
+ bug: "324676775"
+}
diff --git a/core/java/android/tracing/OWNERS b/core/java/android/tracing/OWNERS
index 2ebe2e9..f67844d 100644
--- a/core/java/android/tracing/OWNERS
+++ b/core/java/android/tracing/OWNERS
@@ -1,6 +1,4 @@
carmenjackson@google.com
kevinjeon@google.com
-pablogamito@google.com
-natanieljr@google.com
-keanmariotti@google.com
+include platform/development:/tools/winscope/OWNERS
include platform/external/perfetto:/OWNERS
diff --git a/core/java/android/util/Xml.java b/core/java/android/util/Xml.java
index ec6e90b..50d419f 100644
--- a/core/java/android/util/Xml.java
+++ b/core/java/android/util/Xml.java
@@ -53,9 +53,12 @@
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
+import javax.xml.parsers.SAXParserFactory;
+
/**
* XML utility methods.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class Xml {
private Xml() {}
@@ -73,8 +76,33 @@
*
* @hide
*/
- public static final boolean ENABLE_BINARY_DEFAULT = SystemProperties
- .getBoolean("persist.sys.binary_xml", true);
+ public static final boolean ENABLE_BINARY_DEFAULT = shouldEnableBinaryDefault();
+
+ @android.ravenwood.annotation.RavenwoodReplace
+ private static boolean shouldEnableBinaryDefault() {
+ return SystemProperties.getBoolean("persist.sys.binary_xml", true);
+ }
+
+ private static boolean shouldEnableBinaryDefault$ravenwood() {
+ return true;
+ }
+
+ /**
+ * Feature flag: when set, {@link #resolvePullParser(InputStream)}} will attempt to sniff
+ * using {@code pread} optimization.
+ *
+ * @hide
+ */
+ public static final boolean ENABLE_RESOLVE_OPTIMIZATIONS = shouldEnableResolveOptimizations();
+
+ @android.ravenwood.annotation.RavenwoodReplace
+ private static boolean shouldEnableResolveOptimizations() {
+ return true;
+ }
+
+ private static boolean shouldEnableResolveOptimizations$ravenwood() {
+ return false;
+ }
/**
* Parses the given xml string and fires events on the given SAX handler.
@@ -82,7 +110,7 @@
public static void parse(String xml, ContentHandler contentHandler)
throws SAXException {
try {
- XMLReader reader = XmlObjectFactory.newXMLReader();
+ XMLReader reader = newXMLReader();
reader.setContentHandler(contentHandler);
reader.parse(new InputSource(new StringReader(xml)));
} catch (IOException e) {
@@ -96,7 +124,7 @@
*/
public static void parse(Reader in, ContentHandler contentHandler)
throws IOException, SAXException {
- XMLReader reader = XmlObjectFactory.newXMLReader();
+ XMLReader reader = newXMLReader();
reader.setContentHandler(contentHandler);
reader.parse(new InputSource(in));
}
@@ -107,7 +135,7 @@
*/
public static void parse(InputStream in, Encoding encoding,
ContentHandler contentHandler) throws IOException, SAXException {
- XMLReader reader = XmlObjectFactory.newXMLReader();
+ XMLReader reader = newXMLReader();
reader.setContentHandler(contentHandler);
InputSource source = new InputSource(in);
source.setEncoding(encoding.expatName);
@@ -120,19 +148,26 @@
@android.ravenwood.annotation.RavenwoodReplace
public static XmlPullParser newPullParser() {
try {
- XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
+ XmlPullParser parser = newXmlPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
return parser;
} catch (XmlPullParserException e) {
- throw new AssertionError();
+ throw new AssertionError(e);
}
}
/** @hide */
public static XmlPullParser newPullParser$ravenwood() {
- // TODO: remove once we're linking against libcore
- return new BinaryXmlPullParser();
+ try {
+ // Prebuilt kxml2-android does not support FEATURE_PROCESS_DOCDECL, so omit here;
+ // it's quite rare and all tests are passing
+ XmlPullParser parser = newXmlPullParser();
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+ return parser;
+ } catch (XmlPullParserException e) {
+ throw new AssertionError(e);
+ }
}
/**
@@ -145,17 +180,10 @@
* @hide
*/
@SuppressWarnings("AndroidFrameworkEfficientXml")
- @android.ravenwood.annotation.RavenwoodReplace
public static @NonNull TypedXmlPullParser newFastPullParser() {
return XmlUtils.makeTyped(newPullParser());
}
- /** @hide */
- public static TypedXmlPullParser newFastPullParser$ravenwood() {
- // TODO: remove once we're linking against libcore
- return new BinaryXmlPullParser();
- }
-
/**
* Creates a new {@link XmlPullParser} that reads XML documents using a
* custom binary wire protocol which benchmarking has shown to be 8.5x
@@ -189,11 +217,10 @@
*
* @hide
*/
- @android.ravenwood.annotation.RavenwoodReplace
public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in)
throws IOException {
final byte[] magic = new byte[4];
- if (in instanceof FileInputStream) {
+ if (ENABLE_RESOLVE_OPTIMIZATIONS && in instanceof FileInputStream) {
try {
Os.pread(((FileInputStream) in).getFD(), magic, 0, magic.length, 0);
} catch (ErrnoException e) {
@@ -222,31 +249,11 @@
return xml;
}
- /** @hide */
- public static @NonNull TypedXmlPullParser resolvePullParser$ravenwood(@NonNull InputStream in)
- throws IOException {
- // TODO: remove once we're linking against libcore
- final TypedXmlPullParser xml = new BinaryXmlPullParser();
- try {
- xml.setInput(in, StandardCharsets.UTF_8.name());
- } catch (XmlPullParserException e) {
- throw new IOException(e);
- }
- return xml;
- }
-
/**
* Creates a new xml serializer.
*/
- @android.ravenwood.annotation.RavenwoodReplace
public static XmlSerializer newSerializer() {
- return XmlObjectFactory.newXmlSerializer();
- }
-
- /** @hide */
- public static XmlSerializer newSerializer$ravenwood() {
- // TODO: remove once we're linking against libcore
- return new BinaryXmlSerializer();
+ return newXmlSerializer();
}
/**
@@ -259,17 +266,10 @@
* @hide
*/
@SuppressWarnings("AndroidFrameworkEfficientXml")
- @android.ravenwood.annotation.RavenwoodReplace
public static @NonNull TypedXmlSerializer newFastSerializer() {
return XmlUtils.makeTyped(new FastXmlSerializer());
}
- /** @hide */
- public static @NonNull TypedXmlSerializer newFastSerializer$ravenwood() {
- // TODO: remove once we're linking against libcore
- return new BinaryXmlSerializer();
- }
-
/**
* Creates a new {@link XmlSerializer} that writes XML documents using a
* custom binary wire protocol which benchmarking has shown to be 4.4x
@@ -334,7 +334,6 @@
*
* @hide
*/
- @android.ravenwood.annotation.RavenwoodKeep
public static void copy(@NonNull XmlPullParser in, @NonNull XmlSerializer out)
throws XmlPullParserException, IOException {
// Some parsers may have already consumed the event that starts the
@@ -394,7 +393,6 @@
* unsupported, which can confuse serializers. This method normalizes empty
* strings to be {@code null}.
*/
- @android.ravenwood.annotation.RavenwoodKeep
private static @Nullable String normalizeNamespace(@Nullable String namespace) {
if (namespace == null || namespace.isEmpty()) {
return null;
@@ -457,4 +455,45 @@
? (AttributeSet) parser
: new XmlPullAttributes(parser);
}
+
+ @android.ravenwood.annotation.RavenwoodReplace
+ private static @NonNull XmlSerializer newXmlSerializer() {
+ return XmlObjectFactory.newXmlSerializer();
+ }
+
+ private static @NonNull XmlSerializer newXmlSerializer$ravenwood() {
+ try {
+ return XmlPullParserFactory.newInstance().newSerializer();
+ } catch (XmlPullParserException e) {
+ throw new UnsupportedOperationException(e);
+ }
+ }
+
+ @android.ravenwood.annotation.RavenwoodReplace
+ private static @NonNull XmlPullParser newXmlPullParser() {
+ return XmlObjectFactory.newXmlPullParser();
+ }
+
+ private static @NonNull XmlPullParser newXmlPullParser$ravenwood() {
+ try {
+ return XmlPullParserFactory.newInstance().newPullParser();
+ } catch (XmlPullParserException e) {
+ throw new UnsupportedOperationException(e);
+ }
+ }
+
+ @android.ravenwood.annotation.RavenwoodReplace
+ private static @NonNull XMLReader newXMLReader() {
+ return XmlObjectFactory.newXMLReader();
+ }
+
+ private static @NonNull XMLReader newXMLReader$ravenwood() {
+ try {
+ final SAXParserFactory factory = SAXParserFactory.newInstance();
+ factory.setNamespaceAware(true);
+ return factory.newSAXParser().getXMLReader();
+ } catch (Exception e) {
+ throw new UnsupportedOperationException(e);
+ }
+ }
}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 0006139..1f2b4fa 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1211,6 +1211,8 @@
* @see #REMOVE_MODE_DESTROY_CONTENT
*/
// TODO (b/114338689): Remove the method and use IWindowManager#getRemoveContentMode
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
public int getRemoveMode() {
return mDisplayInfo.removeMode;
}
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index 83bdb08..fe98fab 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -18,6 +18,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.IBinder;
@@ -110,6 +111,12 @@
private Insets mMinimalInsetsSizeInDisplayCutoutSafe = null;
/**
+ * Indicates the bounding rectangles within the provided insets frame, in relative coordinates
+ * to the source frame.
+ */
+ private Rect[] mBoundingRects = null;
+
+ /**
* Creates an InsetsFrameProvider which describes what frame an insets source should have.
*
* @param owner the owner of this provider. We might have multiple sources with the same type on
@@ -205,6 +212,22 @@
return mMinimalInsetsSizeInDisplayCutoutSafe;
}
+ /**
+ * Sets the bounding rectangles within and relative to the source frame.
+ */
+ public InsetsFrameProvider setBoundingRects(@Nullable Rect[] boundingRects) {
+ mBoundingRects = boundingRects == null ? null : boundingRects.clone();
+ return this;
+ }
+
+ /**
+ * Returns the arbitrary bounding rects, or null if none were set.
+ */
+ @Nullable
+ public Rect[] getBoundingRects() {
+ return mBoundingRects;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -231,6 +254,9 @@
sb.append(", mMinimalInsetsSizeInDisplayCutoutSafe=")
.append(mMinimalInsetsSizeInDisplayCutoutSafe);
}
+ if (mBoundingRects != null) {
+ sb.append(", mBoundingRects=").append(Arrays.toString(mBoundingRects));
+ }
sb.append("}");
return sb.toString();
}
@@ -257,6 +283,7 @@
mInsetsSizeOverrides = in.createTypedArray(InsetsSizeOverride.CREATOR);
mArbitraryRectangle = in.readTypedObject(Rect.CREATOR);
mMinimalInsetsSizeInDisplayCutoutSafe = in.readTypedObject(Insets.CREATOR);
+ mBoundingRects = in.createTypedArray(Rect.CREATOR);
}
@Override
@@ -268,6 +295,7 @@
out.writeTypedArray(mInsetsSizeOverrides, flags);
out.writeTypedObject(mArbitraryRectangle, flags);
out.writeTypedObject(mMinimalInsetsSizeInDisplayCutoutSafe, flags);
+ out.writeTypedArray(mBoundingRects, flags);
}
public boolean idEquals(InsetsFrameProvider o) {
@@ -288,14 +316,15 @@
&& Arrays.equals(mInsetsSizeOverrides, other.mInsetsSizeOverrides)
&& Objects.equals(mArbitraryRectangle, other.mArbitraryRectangle)
&& Objects.equals(mMinimalInsetsSizeInDisplayCutoutSafe,
- other.mMinimalInsetsSizeInDisplayCutoutSafe);
+ other.mMinimalInsetsSizeInDisplayCutoutSafe)
+ && Arrays.equals(mBoundingRects, other.mBoundingRects);
}
@Override
public int hashCode() {
return Objects.hash(mId, mSource, mFlags, mInsetsSize,
Arrays.hashCode(mInsetsSizeOverrides), mArbitraryRectangle,
- mMinimalInsetsSizeInDisplayCutoutSafe);
+ mMinimalInsetsSizeInDisplayCutoutSafe, Arrays.hashCode(mBoundingRects));
}
public static final @NonNull Parcelable.Creator<InsetsFrameProvider> CREATOR =
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index bc33d5e..f9eba29 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -38,6 +38,8 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Objects;
import java.util.StringJoiner;
@@ -105,6 +107,12 @@
})
public @interface Flags {}
+ /**
+ * Used when there are no bounding rects to describe an inset, which is only possible when the
+ * insets itself is {@link Insets#NONE}.
+ */
+ private static final Rect[] NO_BOUNDING_RECTS = new Rect[0];
+
private @Flags int mFlags;
/**
@@ -117,6 +125,7 @@
/** Frame of the source in screen coordinate space */
private final Rect mFrame;
private @Nullable Rect mVisibleFrame;
+ private @Nullable Rect[] mBoundingRects;
private boolean mVisible;
@@ -127,6 +136,7 @@
private @InternalInsetsSide int mSideHint = SIDE_NONE;
private final Rect mTmpFrame = new Rect();
+ private final Rect mTmpBoundingRect = new Rect();
public InsetsSource(int id, @InsetsType int type) {
mId = id;
@@ -145,6 +155,9 @@
: null;
mFlags = other.mFlags;
mSideHint = other.mSideHint;
+ mBoundingRects = other.mBoundingRects != null
+ ? other.mBoundingRects.clone()
+ : null;
}
public void set(InsetsSource other) {
@@ -155,6 +168,9 @@
: null;
mFlags = other.mFlags;
mSideHint = other.mSideHint;
+ mBoundingRects = other.mBoundingRects != null
+ ? other.mBoundingRects.clone()
+ : null;
}
public InsetsSource setFrame(int left, int top, int right, int bottom) {
@@ -199,6 +215,15 @@
return this;
}
+ /**
+ * Set the bounding rectangles of this source. They are expected to be relative to the source
+ * frame.
+ */
+ public InsetsSource setBoundingRects(@Nullable Rect[] rects) {
+ mBoundingRects = rects != null ? rects.clone() : null;
+ return this;
+ }
+
public int getId() {
return mId;
}
@@ -228,6 +253,13 @@
}
/**
+ * Returns the bounding rectangles of this source.
+ */
+ public @Nullable Rect[] getBoundingRects() {
+ return mBoundingRects;
+ }
+
+ /**
* Calculates the insets this source will cause to a client window.
*
* @param relativeFrame The frame to calculate the insets relative to.
@@ -313,6 +345,82 @@
}
/**
+ * Calculates the bounding rects the source will cause to a client window.
+ */
+ public @NonNull Rect[] calculateBoundingRects(Rect relativeFrame, boolean ignoreVisibility) {
+ if (!ignoreVisibility && !mVisible) {
+ return NO_BOUNDING_RECTS;
+ }
+
+ final Rect frame = getFrame();
+ if (mBoundingRects == null) {
+ // No bounding rects set, make a single bounding rect that covers the intersection of
+ // the |frame| and the |relativeFrame|.
+ return mTmpBoundingRect.setIntersect(frame, relativeFrame)
+ ? new Rect[]{ new Rect(mTmpBoundingRect) }
+ : NO_BOUNDING_RECTS;
+
+ }
+
+ // Special treatment for captionBar inset type. During drag-resizing, the |frame| and
+ // |boundingRects| may not get updated as quickly as |relativeFrame|, so just assume the
+ // |frame| will always be either at the top or bottom of |relativeFrame|. This means some
+ // calculations to make |boundingRects| relative to |relativeFrame| can be skipped or
+ // simplified.
+ // TODO(b/254128050): remove special treatment.
+ if (getType() == WindowInsets.Type.captionBar()) {
+ final ArrayList<Rect> validBoundingRects = new ArrayList<>();
+ for (final Rect boundingRect : mBoundingRects) {
+ // Assume that the caption |frame| and |relativeFrame| perfectly align at the top
+ // or bottom, meaning that the provided |boundingRect|, which is relative to the
+ // |frame| either is already relative to |relativeFrame| (for top captionBar()), or
+ // just needs to be made relative to |relativeFrame| for bottom bars.
+ final int frameHeight = frame.height();
+ mTmpBoundingRect.set(boundingRect);
+ if (getId() == ID_IME_CAPTION_BAR) {
+ mTmpBoundingRect.offset(0, relativeFrame.height() - frameHeight);
+ }
+ validBoundingRects.add(new Rect(mTmpBoundingRect));
+ }
+ return validBoundingRects.toArray(new Rect[validBoundingRects.size()]);
+ }
+
+ // Regular treatment for non-captionBar inset types.
+ final ArrayList<Rect> validBoundingRects = new ArrayList<>();
+ for (final Rect boundingRect : mBoundingRects) {
+ // |boundingRect| was provided relative to |frame|. Make it absolute to be in the same
+ // coordinate system as |frame|.
+ final Rect absBoundingRect = new Rect(
+ boundingRect.left + frame.left,
+ boundingRect.top + frame.top,
+ boundingRect.right + frame.left,
+ boundingRect.bottom + frame.top
+ );
+ // Now find the intersection of that |absBoundingRect| with |relativeFrame|. In other
+ // words, whichever part of the bounding rect is inside the window frame.
+ if (!mTmpBoundingRect.setIntersect(absBoundingRect, relativeFrame)) {
+ // It's possible for this to be empty if the frame and bounding rects were larger
+ // than the |relativeFrame|, such as when a system window is wider than the app
+ // window width. Just ignore that rect since it will have no effect on the
+ // window insets.
+ continue;
+ }
+ // At this point, |mTmpBoundingRect| is a valid bounding rect located fully inside the
+ // window, convert it to be relative to the window so that apps don't need to know the
+ // location of the window to understand bounding rects.
+ validBoundingRects.add(new Rect(
+ mTmpBoundingRect.left - relativeFrame.left,
+ mTmpBoundingRect.top - relativeFrame.top,
+ mTmpBoundingRect.right - relativeFrame.left,
+ mTmpBoundingRect.bottom - relativeFrame.top));
+ }
+ if (validBoundingRects.isEmpty()) {
+ return NO_BOUNDING_RECTS;
+ }
+ return validBoundingRects.toArray(new Rect[validBoundingRects.size()]);
+ }
+
+ /**
* Outputs the intersection of two rectangles. The shared edges will also be counted in the
* intersection.
*
@@ -467,6 +575,7 @@
pw.print(" visible="); pw.print(mVisible);
pw.print(" flags="); pw.print(flagsToString(mFlags));
pw.print(" sideHint="); pw.print(sideToString(mSideHint));
+ pw.print(" boundingRects="); pw.print(Arrays.toString(mBoundingRects));
pw.println();
}
@@ -492,12 +601,14 @@
if (mSideHint != that.mSideHint) return false;
if (excludeInvisibleImeFrames && !mVisible && mType == WindowInsets.Type.ime()) return true;
if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false;
- return mFrame.equals(that.mFrame);
+ if (!mFrame.equals(that.mFrame)) return false;
+ return Arrays.equals(mBoundingRects, that.mBoundingRects);
}
@Override
public int hashCode() {
- return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags, mSideHint);
+ return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags, mSideHint,
+ Arrays.hashCode(mBoundingRects));
}
public InsetsSource(Parcel in) {
@@ -512,6 +623,7 @@
mVisible = in.readBoolean();
mFlags = in.readInt();
mSideHint = in.readInt();
+ mBoundingRects = in.createTypedArray(Rect.CREATOR);
}
@Override
@@ -533,6 +645,7 @@
dest.writeBoolean(mVisible);
dest.writeInt(mFlags);
dest.writeInt(mSideHint);
+ dest.writeTypedArray(mBoundingRects, flags);
}
@Override
@@ -543,6 +656,7 @@
+ " mVisible=" + mVisible
+ " mFlags=" + flagsToString(mFlags)
+ " mSideHint=" + sideToString(mSideHint)
+ + " mBoundingRects=" + Arrays.toString(mBoundingRects)
+ "}";
}
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index c88da9e..21eec67 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -128,6 +128,8 @@
final Rect relativeFrameMax = new Rect(frame);
@InsetsType int forceConsumingTypes = 0;
@InsetsType int suppressScrimTypes = 0;
+ final Rect[][] typeBoundingRectsMap = new Rect[Type.SIZE][];
+ final Rect[][] typeMaxBoundingRectsMap = new Rect[Type.SIZE][];
for (int i = mSources.size() - 1; i >= 0; i--) {
final InsetsSource source = mSources.valueAt(i);
final @InsetsType int type = source.getType();
@@ -141,7 +143,7 @@
}
processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap,
- idSideMap, typeVisibilityMap);
+ idSideMap, typeVisibilityMap, typeBoundingRectsMap);
// IME won't be reported in max insets as the size depends on the EditorInfo of the IME
// target.
@@ -154,7 +156,7 @@
}
processSource(ignoringVisibilitySource, relativeFrameMax,
true /* ignoreVisibility */, typeMaxInsetsMap, null /* idSideMap */,
- null /* typeVisibilityMap */);
+ null /* typeVisibilityMap */, typeMaxBoundingRectsMap);
}
}
final int softInputAdjustMode = legacySoftInputMode & SOFT_INPUT_MASK_ADJUST;
@@ -175,7 +177,8 @@
calculateRelativeRoundedCorners(frame),
calculateRelativePrivacyIndicatorBounds(frame),
calculateRelativeDisplayShape(frame),
- compatInsetsTypes, (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0);
+ compatInsetsTypes, (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0,
+ typeBoundingRectsMap, typeMaxBoundingRectsMap, frame.width(), frame.height());
}
private DisplayCutout calculateRelativeCutout(Rect frame) {
@@ -328,12 +331,13 @@
private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
Insets[] typeInsetsMap, @Nullable @InternalInsetsSide SparseIntArray idSideMap,
- @Nullable boolean[] typeVisibilityMap) {
+ @Nullable boolean[] typeVisibilityMap, Rect[][] typeBoundingRectsMap) {
Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility);
+ final Rect[] boundingRects = source.calculateBoundingRects(relativeFrame, ignoreVisibility);
final int type = source.getType();
processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
- insets, type);
+ typeBoundingRectsMap, insets, boundingRects, type);
if (type == Type.MANDATORY_SYSTEM_GESTURES) {
// Mandatory system gestures are also system gestures.
@@ -342,24 +346,25 @@
// ability to set systemGestureInsets() independently from
// mandatorySystemGestureInsets() in the Builder.
processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
- insets, Type.SYSTEM_GESTURES);
+ typeBoundingRectsMap, insets, boundingRects, Type.SYSTEM_GESTURES);
}
if (type == Type.CAPTION_BAR) {
// Caption should also be gesture and tappable elements. This should not be needed when
// the caption is added from the shell, as the shell can add other types at the same
// time.
processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
- insets, Type.SYSTEM_GESTURES);
+ typeBoundingRectsMap, insets, boundingRects, Type.SYSTEM_GESTURES);
processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
- insets, Type.MANDATORY_SYSTEM_GESTURES);
+ typeBoundingRectsMap, insets, boundingRects, Type.MANDATORY_SYSTEM_GESTURES);
processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
- insets, Type.TAPPABLE_ELEMENT);
+ typeBoundingRectsMap, insets, boundingRects, Type.TAPPABLE_ELEMENT);
}
}
private void processSourceAsPublicType(InsetsSource source, Insets[] typeInsetsMap,
@InternalInsetsSide @Nullable SparseIntArray idSideMap,
- @Nullable boolean[] typeVisibilityMap, Insets insets, int type) {
+ @Nullable boolean[] typeVisibilityMap, Rect[][] typeBoundingRectsMap,
+ Insets insets, Rect[] boundingRects, int type) {
int index = indexOf(type);
// Don't put Insets.NONE into typeInsetsMap. Otherwise, two WindowInsets can be considered
@@ -384,6 +389,22 @@
idSideMap.put(source.getId(), insetSide);
}
}
+
+ if (typeBoundingRectsMap != null && boundingRects.length > 0) {
+ final Rect[] existing = typeBoundingRectsMap[index];
+ if (existing == null) {
+ typeBoundingRectsMap[index] = boundingRects;
+ } else {
+ typeBoundingRectsMap[index] = concatenate(existing, boundingRects);
+ }
+ }
+ }
+
+ private static Rect[] concatenate(Rect[] a, Rect[] b) {
+ final Rect[] c = new Rect[a.length + b.length];
+ System.arraycopy(a, 0, c, 0, a.length);
+ System.arraycopy(b, 0, c, a.length, b.length);
+ return c;
}
/**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e781f3c..dc0b1a7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -6394,6 +6394,9 @@
case R.styleable.View_handwritingBoundsOffsetBottom:
mHandwritingBoundsOffsetBottom = a.getDimension(attr, 0);
break;
+ case R.styleable.View_contentSensitivity:
+ setContentSensitivity(a.getInt(attr, CONTENT_SENSITIVITY_AUTO));
+ break;
}
}
@@ -22299,6 +22302,9 @@
* Retrieve a unique token identifying the window this view is attached to.
* @return Return the window's token for use in
* {@link WindowManager.LayoutParams#token WindowManager.LayoutParams.token}.
+ * This token maybe null if this view is not attached to a window.
+ * @see #isAttachedToWindow() for current window attach state
+ * @see OnAttachStateChangeListener to listen to window attach/detach state changes
*/
public IBinder getWindowToken() {
return mAttachInfo != null ? mAttachInfo.mWindowToken : null;
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 921afaa..fbebe1e 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -34,6 +34,7 @@
import static android.view.WindowInsets.Type.indexOf;
import static android.view.WindowInsets.Type.systemBars;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -44,8 +45,10 @@
import android.content.Intent;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.util.Size;
import android.view.View.OnApplyWindowInsetsListener;
import android.view.WindowInsets.Type.InsetsType;
+import android.view.flags.Flags;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethod;
@@ -54,7 +57,10 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
import java.util.Objects;
/**
@@ -78,6 +84,8 @@
private final Insets[] mTypeInsetsMap;
private final Insets[] mTypeMaxInsetsMap;
private final boolean[] mTypeVisibilityMap;
+ private final Rect[][] mTypeBoundingRectsMap;
+ private final Rect[][] mTypeMaxBoundingRectsMap;
@Nullable private Rect mTempRect;
private final boolean mIsRound;
@@ -85,6 +93,8 @@
@Nullable private final RoundedCorners mRoundedCorners;
@Nullable private final PrivacyIndicatorBounds mPrivacyIndicatorBounds;
@Nullable private final DisplayShape mDisplayShape;
+ private final int mFrameWidth;
+ private final int mFrameHeight;
private final @InsetsType int mForceConsumingTypes;
private final @InsetsType int mSuppressScrimTypes;
@@ -114,7 +124,7 @@
static {
CONSUMED = new WindowInsets(createCompatTypeMap(null), createCompatTypeMap(null),
createCompatVisibilityMap(createCompatTypeMap(null)), false, 0, 0, null,
- null, null, null, systemBars(), false);
+ null, null, null, systemBars(), false, null, null, 0, 0);
}
/**
@@ -139,7 +149,10 @@
RoundedCorners roundedCorners,
PrivacyIndicatorBounds privacyIndicatorBounds,
DisplayShape displayShape,
- @InsetsType int compatInsetsTypes, boolean compatIgnoreVisibility) {
+ @InsetsType int compatInsetsTypes, boolean compatIgnoreVisibility,
+ Rect[][] typeBoundingRectsMap,
+ Rect[][] typeMaxBoundingRectsMap,
+ int frameWidth, int frameHeight) {
mSystemWindowInsetsConsumed = typeInsetsMap == null;
mTypeInsetsMap = mSystemWindowInsetsConsumed
? new Insets[SIZE]
@@ -164,6 +177,14 @@
mRoundedCorners = roundedCorners;
mPrivacyIndicatorBounds = privacyIndicatorBounds;
mDisplayShape = displayShape;
+ mTypeBoundingRectsMap = (mSystemWindowInsetsConsumed || typeBoundingRectsMap == null)
+ ? new Rect[SIZE][]
+ : typeBoundingRectsMap.clone();
+ mTypeMaxBoundingRectsMap = (mStableInsetsConsumed || typeMaxBoundingRectsMap == null)
+ ? new Rect[SIZE][]
+ : typeMaxBoundingRectsMap.clone();
+ mFrameWidth = frameWidth;
+ mFrameHeight = frameHeight;
}
/**
@@ -181,7 +202,11 @@
src.mPrivacyIndicatorBounds,
src.mDisplayShape,
src.mCompatInsetsTypes,
- src.mCompatIgnoreVisibility);
+ src.mCompatIgnoreVisibility,
+ src.mSystemWindowInsetsConsumed ? null : src.mTypeBoundingRectsMap,
+ src.mStableInsetsConsumed ? null : src.mTypeMaxBoundingRectsMap,
+ src.mFrameWidth,
+ src.mFrameHeight);
}
private static DisplayCutout displayCutoutCopyConstructorArgument(WindowInsets w) {
@@ -233,7 +258,8 @@
@UnsupportedAppUsage
public WindowInsets(Rect systemWindowInsets) {
this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, 0, 0,
- null, null, null, null, systemBars(), false /* compatIgnoreVisibility */);
+ null, null, null, null, systemBars(), false /* compatIgnoreVisibility */,
+ new Rect[SIZE][], null, 0, 0);
}
/**
@@ -475,6 +501,111 @@
}
/**
+ * Returns a list of {@link Rect}s, each of which is the bounding rectangle for an area
+ * that is being partially or fully obscured inside the window.
+ *
+ * <p>
+ * May be used with or instead of {@link Insets} for finer avoidance of regions that may be
+ * partially obscuring the window but may be smaller than those provided by
+ * {@link #getInsets(int)}.
+ * </p>
+ *
+ * <p>
+ * The {@link Rect}s returned are always cropped to the bounds of the window frame and their
+ * coordinate values are relative to the {@link #getFrame()}, regardless of the window's
+ * position on screen.
+ * </p>
+ *
+ * <p>
+ * If inset by {@link #inset(Insets)}, bounding rects that intersect with the provided insets
+ * will be resized to only include the intersection with the remaining frame. Bounding rects
+ * may be completely removed if they no longer intersect with the new instance.
+ * </p>
+ *
+ * @param typeMask the insets type for which to obtain the bounding rectangles
+ * @return the bounding rectangles
+ */
+ @FlaggedApi(Flags.FLAG_CUSTOMIZABLE_WINDOW_HEADERS)
+ @NonNull
+ public List<Rect> getBoundingRects(@InsetsType int typeMask) {
+ Rect[] allRects = null;
+ for (int i = FIRST; i <= LAST; i = i << 1) {
+ if ((typeMask & i) == 0) {
+ continue;
+ }
+ final Rect[] rects = mTypeBoundingRectsMap[indexOf(i)];
+ if (rects == null) {
+ continue;
+ }
+ if (allRects == null) {
+ allRects = rects;
+ } else {
+ final Rect[] concat = new Rect[allRects.length + rects.length];
+ System.arraycopy(allRects, 0, concat, 0, allRects.length);
+ System.arraycopy(rects, 0, concat, allRects.length, rects.length);
+ allRects = concat;
+ }
+ }
+ if (allRects == null) {
+ return Collections.emptyList();
+ }
+ return Arrays.asList(allRects);
+ }
+
+ /**
+ * Returns a list of {@link Rect}s, each of which is the bounding rectangle for an area that
+ * can be partially or fully obscured inside the window, regardless of whether
+ * that type is currently visible or not.
+ *
+ * <p> The bounding rects represent areas of a window that <b>may</b> be partially or fully
+ * obscured by the {@code type}. This value does not change based on the visibility state of
+ * those elements. For example, if the status bar is normally shown, but temporarily hidden,
+ * the bounding rects returned here will provide the rects associated with the status bar being
+ * shown.</p>
+ *
+ * <p>
+ * May be used with or instead of {@link Insets} for finer avoidance of regions that may be
+ * partially obscuring the window but may be smaller than those provided by
+ * {@link #getInsetsIgnoringVisibility(int)}.
+ * </p>
+ *
+ * <p>
+ * The {@link Rect}s returned are always cropped to the bounds of the window frame and their
+ * coordinate values are relative to the {@link #getFrame()}, regardless of the window's
+ * position on screen.
+ * </p>
+ *
+ * @param typeMask the insets type for which to obtain the bounding rectangles
+ * @return the bounding rectangles
+ */
+ @FlaggedApi(Flags.FLAG_CUSTOMIZABLE_WINDOW_HEADERS)
+ @NonNull
+ public List<Rect> getBoundingRectsIgnoringVisibility(@InsetsType int typeMask) {
+ Rect[] allRects = null;
+ for (int i = FIRST; i <= LAST; i = i << 1) {
+ if ((typeMask & i) == 0) {
+ continue;
+ }
+ final Rect[] rects = mTypeMaxBoundingRectsMap[indexOf(i)];
+ if (rects == null) {
+ continue;
+ }
+ if (allRects == null) {
+ allRects = rects;
+ } else {
+ final Rect[] concat = new Rect[allRects.length + rects.length];
+ System.arraycopy(allRects, 0, concat, 0, allRects.length);
+ System.arraycopy(rects, 0, concat, allRects.length, rects.length);
+ allRects = concat;
+ }
+ }
+ if (allRects == null) {
+ return Collections.emptyList();
+ }
+ return Arrays.asList(allRects);
+ }
+
+ /**
* Returns the display cutout if there is one.
*
* <p>Note: the display cutout will already be {@link #consumeDisplayCutout consumed} during
@@ -555,7 +686,10 @@
mTypeVisibilityMap,
mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape,
- mCompatInsetsTypes, mCompatIgnoreVisibility);
+ mCompatInsetsTypes, mCompatIgnoreVisibility,
+ mSystemWindowInsetsConsumed ? null : mTypeBoundingRectsMap,
+ mStableInsetsConsumed ? null : mTypeMaxBoundingRectsMap,
+ mFrameWidth, mFrameHeight);
}
@@ -610,7 +744,7 @@
(mCompatInsetsTypes & displayCutout()) != 0
? null : displayCutoutCopyConstructorArgument(this),
mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, mCompatInsetsTypes,
- mCompatIgnoreVisibility);
+ mCompatIgnoreVisibility, null, null, mFrameWidth, mFrameHeight);
}
// TODO(b/119190588): replace @code with @link below
@@ -914,6 +1048,10 @@
result.append(Type.toString(1 << i)).append("=").append(insets)
.append(" max=").append(maxInsets)
.append(" vis=").append(visible)
+ .append(" boundingRects=")
+ .append(Arrays.toString(mTypeBoundingRectsMap[i]))
+ .append(" maxBoundingRects=")
+ .append(Arrays.toString(mTypeMaxBoundingRectsMap[i]))
.append("\n ");
}
}
@@ -942,6 +1080,10 @@
result.append("displayCutoutConsumed=" + mDisplayCutoutConsumed);
result.append("\n ");
result.append(isRound() ? "round" : "");
+ result.append("\n ");
+ result.append("frameWidth=" + mFrameWidth);
+ result.append("\n ");
+ result.append("frameHeight=" + mFrameHeight);
result.append("}");
return result.toString();
}
@@ -1013,6 +1155,27 @@
}
/**
+ * Returns the assumed size of the window, relative to which the {@link #getInsets} and
+ * {@link #getBoundingRects} have been calculated.
+ *
+ * <p> May be used with {@link #getBoundingRects} to better understand their position within
+ * the window, such as the area between the edge of a bounding rect and the edge of the window.
+ *
+ * <p>Note: the size may not match the actual size of the window, which is determined during
+ * the layout pass - as {@link WindowInsets} are dispatched before layout.
+ *
+ * <p>Caution: using this value in determining the actual window size may make the result of
+ * layout passes unstable and should be avoided.
+ *
+ * @return the assumed size of the window during the inset calculation
+ */
+ @FlaggedApi(Flags.FLAG_CUSTOMIZABLE_WINDOW_HEADERS)
+ @NonNull
+ public Size getFrame() {
+ return new Size(mFrameWidth, mFrameHeight);
+ }
+
+ /**
* @see #inset(int, int, int, int)
* @hide
*/
@@ -1039,7 +1202,17 @@
? null
: mPrivacyIndicatorBounds.inset(left, top, right, bottom),
mDisplayShape,
- mCompatInsetsTypes, mCompatIgnoreVisibility);
+ mCompatInsetsTypes, mCompatIgnoreVisibility,
+ mSystemWindowInsetsConsumed
+ ? null
+ : insetBoundingRects(mTypeBoundingRectsMap, left, top, right, bottom,
+ mFrameWidth, mFrameHeight),
+ mStableInsetsConsumed
+ ? null
+ : insetBoundingRects(mTypeMaxBoundingRectsMap, left, top, right, bottom,
+ mFrameWidth, mFrameHeight),
+ Math.max(0, mFrameWidth - left - right),
+ Math.max(0, mFrameHeight - top - bottom));
}
@Override
@@ -1060,7 +1233,11 @@
&& Objects.equals(mDisplayCutout, that.mDisplayCutout)
&& Objects.equals(mRoundedCorners, that.mRoundedCorners)
&& Objects.equals(mPrivacyIndicatorBounds, that.mPrivacyIndicatorBounds)
- && Objects.equals(mDisplayShape, that.mDisplayShape);
+ && Objects.equals(mDisplayShape, that.mDisplayShape)
+ && Arrays.deepEquals(mTypeBoundingRectsMap, that.mTypeBoundingRectsMap)
+ && Arrays.deepEquals(mTypeMaxBoundingRectsMap, that.mTypeMaxBoundingRectsMap)
+ && mFrameWidth == that.mFrameWidth
+ && mFrameHeight == that.mFrameHeight;
}
@Override
@@ -1069,7 +1246,8 @@
Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners,
mForceConsumingTypes, mSuppressScrimTypes, mSystemWindowInsetsConsumed,
mStableInsetsConsumed, mDisplayCutoutConsumed, mPrivacyIndicatorBounds,
- mDisplayShape);
+ mDisplayShape, Arrays.deepHashCode(mTypeBoundingRectsMap),
+ Arrays.deepHashCode(mTypeMaxBoundingRectsMap), mFrameWidth, mFrameHeight);
}
@@ -1110,6 +1288,68 @@
return Insets.of(newLeft, newTop, newRight, newBottom);
}
+ static Rect[][] insetBoundingRects(Rect[][] typeBoundingRectsMap,
+ int insetLeft, int insetTop, int insetRight, int insetBottom, int frameWidth,
+ int frameHeight) {
+ if (insetLeft == 0 && insetTop == 0 && insetRight == 0 && insetBottom == 0) {
+ return typeBoundingRectsMap;
+ }
+ boolean cloned = false;
+ for (int i = 0; i < SIZE; i++) {
+ final Rect[] boundingRects = typeBoundingRectsMap[i];
+ if (boundingRects == null) {
+ continue;
+ }
+ final Rect[] insetBoundingRects = insetBoundingRects(boundingRects,
+ insetLeft, insetTop, insetRight, insetBottom, frameWidth, frameHeight);
+ if (!Arrays.equals(insetBoundingRects, boundingRects)) {
+ if (!cloned) {
+ typeBoundingRectsMap = typeBoundingRectsMap.clone();
+ cloned = true;
+ }
+ typeBoundingRectsMap[i] = insetBoundingRects;
+ }
+ }
+ return typeBoundingRectsMap;
+ }
+
+ static Rect[] insetBoundingRects(Rect[] boundingRects,
+ int left, int top, int right, int bottom, int frameWidth, int frameHeight) {
+ final List<Rect> insetBoundingRectsList = new ArrayList<>();
+ for (int i = 0; i < boundingRects.length; i++) {
+ final Rect insetRect = insetRect(boundingRects[i], left, top, right, bottom,
+ frameWidth, frameHeight);
+ if (insetRect != null) {
+ insetBoundingRectsList.add(insetRect);
+ }
+ }
+ return insetBoundingRectsList.toArray(new Rect[0]);
+ }
+
+ private static Rect insetRect(Rect orig, int insetLeft, int insetTop, int insetRight,
+ int insetBottom, int frameWidth, int frameHeight) {
+ if (orig == null) {
+ return null;
+ }
+
+ // Calculate the inset frame, and leave it in that coordinate space for easier comparison
+ // against the |orig| rect.
+ final Rect insetFrame = new Rect(insetLeft, insetTop, frameWidth - insetRight,
+ frameHeight - insetBottom);
+ // Then the intersecting portion of |orig| with the inset |insetFrame|.
+ final Rect insetRect = new Rect();
+ if (insetRect.setIntersect(insetFrame, orig)) {
+ // The intersection is the inset rect, but its position must be shifted to be relative
+ // to the frame. Since the new frame will start at left=|insetLeft| and top=|insetTop|,
+ // just offset that much back in the direction of the origin of the frame.
+ insetRect.offset(-insetLeft, -insetTop);
+ return insetRect;
+ } else {
+ // The |orig| rect does not intersect with the new frame at all, so don't report it.
+ return null;
+ }
+ }
+
/**
* @return whether system window insets have been consumed.
*/
@@ -1125,6 +1365,8 @@
private final Insets[] mTypeInsetsMap;
private final Insets[] mTypeMaxInsetsMap;
private final boolean[] mTypeVisibilityMap;
+ private final Rect[][] mTypeBoundingRectsMap;
+ private final Rect[][] mTypeMaxBoundingRectsMap;
private boolean mSystemInsetsConsumed = true;
private boolean mStableInsetsConsumed = true;
@@ -1137,6 +1379,8 @@
private @InsetsType int mSuppressScrimTypes;
private PrivacyIndicatorBounds mPrivacyIndicatorBounds = new PrivacyIndicatorBounds();
+ private int mFrameWidth;
+ private int mFrameHeight;
/**
* Creates a builder where all insets are initially consumed.
@@ -1145,6 +1389,8 @@
mTypeInsetsMap = new Insets[SIZE];
mTypeMaxInsetsMap = new Insets[SIZE];
mTypeVisibilityMap = new boolean[SIZE];
+ mTypeBoundingRectsMap = new Rect[SIZE][];
+ mTypeMaxBoundingRectsMap = new Rect[SIZE][];
}
/**
@@ -1165,6 +1411,10 @@
mSuppressScrimTypes = insets.mSuppressScrimTypes;
mPrivacyIndicatorBounds = insets.mPrivacyIndicatorBounds;
mDisplayShape = insets.mDisplayShape;
+ mTypeBoundingRectsMap = insets.mTypeBoundingRectsMap.clone();
+ mTypeMaxBoundingRectsMap = insets.mTypeMaxBoundingRectsMap.clone();
+ mFrameWidth = insets.mFrameWidth;
+ mFrameHeight = insets.mFrameHeight;
}
/**
@@ -1452,6 +1702,68 @@
}
/**
+ * Sets the bounding rects.
+ *
+ * @param typeMask the inset types to which these rects apply.
+ * @param rects the bounding rects.
+ * @return itself.
+ */
+ @FlaggedApi(Flags.FLAG_CUSTOMIZABLE_WINDOW_HEADERS)
+ @NonNull
+ public Builder setBoundingRects(@InsetsType int typeMask, @NonNull List<Rect> rects) {
+ for (int i = FIRST; i <= LAST; i = i << 1) {
+ if ((typeMask & i) == 0) {
+ continue;
+ }
+ mTypeBoundingRectsMap[indexOf(i)] = rects.toArray(new Rect[0]);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the bounding rects while ignoring their visibility state.
+ *
+ * @param typeMask the inset types to which these rects apply.
+ * @param rects the bounding rects.
+ * @return itself.
+ *
+ * @throws IllegalArgumentException If {@code typeMask} contains {@link Type#ime()}.
+ * Maximum bounding rects are not available for this type as the height of the IME is
+ * dynamic depending on the {@link EditorInfo} of the currently focused view, as well as
+ * the UI state of the IME.
+ */
+ @FlaggedApi(Flags.FLAG_CUSTOMIZABLE_WINDOW_HEADERS)
+ @NonNull
+ public Builder setBoundingRectsIgnoringVisibility(@InsetsType int typeMask,
+ @NonNull List<Rect> rects) {
+ if (typeMask == IME) {
+ throw new IllegalArgumentException("Maximum bounding rects not available for IME");
+ }
+ for (int i = FIRST; i <= LAST; i = i << 1) {
+ if ((typeMask & i) == 0) {
+ continue;
+ }
+ mTypeMaxBoundingRectsMap[indexOf(i)] = rects.toArray(new Rect[0]);
+ }
+ return this;
+ }
+
+ /**
+ * Set the frame size.
+ *
+ * @param width the width of the frame.
+ * @param height the height of the frame.
+ * @return itself.
+ */
+ @FlaggedApi(Flags.FLAG_CUSTOMIZABLE_WINDOW_HEADERS)
+ @NonNull
+ public Builder setFrame(int width, int height) {
+ mFrameWidth = width;
+ mFrameHeight = height;
+ return this;
+ }
+
+ /**
* Builds a {@link WindowInsets} instance.
*
* @return the {@link WindowInsets} instance.
@@ -1462,7 +1774,10 @@
mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap,
mIsRound, mForceConsumingTypes, mSuppressScrimTypes, mDisplayCutout,
mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, systemBars(),
- false /* compatIgnoreVisibility */);
+ false /* compatIgnoreVisibility */,
+ mSystemInsetsConsumed ? null : mTypeBoundingRectsMap,
+ mStableInsetsConsumed ? null : mTypeMaxBoundingRectsMap,
+ mFrameWidth, mFrameHeight);
}
}
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index ae00b70..2fb5213 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -520,11 +520,16 @@
public void registerTrustedPresentationListener(@NonNull IBinder window,
@NonNull TrustedPresentationThresholds thresholds, @NonNull Executor executor,
@NonNull Consumer<Boolean> listener) {
+ Objects.requireNonNull(window, "window must not be null");
+ Objects.requireNonNull(thresholds, "thresholds must not be null");
+ Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(listener, "listener must not be null");
mGlobal.registerTrustedPresentationListener(window, thresholds, executor, listener);
}
@Override
public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
+ Objects.requireNonNull(listener, "listener must not be null");
mGlobal.unregisterTrustedPresentationListener(listener);
}
diff --git a/core/java/android/view/inputmethod/ConnectionlessHandwritingCallback.java b/core/java/android/view/inputmethod/ConnectionlessHandwritingCallback.java
new file mode 100644
index 0000000..d985732
--- /dev/null
+++ b/core/java/android/view/inputmethod/ConnectionlessHandwritingCallback.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.view.View;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * Interface to receive the result of starting a connectionless stylus handwriting session using
+ * one of {@link InputMethodManager#startConnectionlessStylusHandwriting(View, CursorAnchorInfo,
+ * Executor,ConnectionlessHandwritingCallback)}, {@link
+ * InputMethodManager#startConnectionlessStylusHandwritingForDelegation(View, CursorAnchorInfo,
+ * Executor, ConnectionlessHandwritingCallback)}, or {@link
+ * InputMethodManager#startConnectionlessStylusHandwritingForDelegation(View, CursorAnchorInfo,
+ * String, Executor, ConnectionlessHandwritingCallback)}.
+ */
+@FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING)
+public interface ConnectionlessHandwritingCallback {
+
+ /** @hide */
+ @IntDef(prefix = {"CONNECTIONLESS_HANDWRITING_ERROR_"}, value = {
+ CONNECTIONLESS_HANDWRITING_ERROR_NO_TEXT_RECOGNIZED,
+ CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED,
+ CONNECTIONLESS_HANDWRITING_ERROR_OTHER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ConnectionlessHandwritingError {
+ }
+
+ /**
+ * Error code indicating that the connectionless handwriting session started and completed
+ * but no text was recognized.
+ */
+ int CONNECTIONLESS_HANDWRITING_ERROR_NO_TEXT_RECOGNIZED = 0;
+
+ /**
+ * Error code indicating that the connectionless handwriting session was not started as the
+ * current IME does not support it.
+ */
+ int CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED = 1;
+
+ /**
+ * Error code for any other reason that the connectionless handwriting session did not complete
+ * successfully. Either the session could not start, or the session started but did not complete
+ * successfully.
+ */
+ int CONNECTIONLESS_HANDWRITING_ERROR_OTHER = 2;
+
+ /**
+ * Callback when the connectionless handwriting session completed successfully and
+ * recognized text.
+ */
+ void onResult(@NonNull CharSequence text);
+
+ /** Callback when the connectionless handwriting session did not complete successfully. */
+ void onError(@ConnectionlessHandwritingError int errorCode);
+}
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index 89da041..dc5e0e5 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -34,6 +34,7 @@
import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.internal.inputmethod.IConnectionlessHandwritingCallback;
import com.android.internal.inputmethod.IImeTracker;
import com.android.internal.inputmethod.IInputMethodClient;
import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
@@ -492,6 +493,27 @@
}
@AnyThread
+ static boolean startConnectionlessStylusHandwriting(
+ @NonNull IInputMethodClient client,
+ @UserIdInt int userId,
+ @Nullable CursorAnchorInfo cursorAnchorInfo,
+ @Nullable String delegatePackageName,
+ @Nullable String delegatorPackageName,
+ @NonNull IConnectionlessHandwritingCallback callback) {
+ final IInputMethodManager service = getService();
+ if (service == null) {
+ return false;
+ }
+ try {
+ service.startConnectionlessStylusHandwriting(client, userId, cursorAnchorInfo,
+ delegatePackageName, delegatorPackageName, callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return true;
+ }
+
+ @AnyThread
static void prepareStylusHandwritingDelegation(
@NonNull IInputMethodClient client,
@UserIdInt int userId,
@@ -530,13 +552,14 @@
@AnyThread
@RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
- static boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
+ static boolean isStylusHandwritingAvailableAsUser(
+ @UserIdInt int userId, boolean connectionless) {
final IInputMethodManager service = getService();
if (service == null) {
return false;
}
try {
- return service.isStylusHandwritingAvailableAsUser(userId);
+ return service.isStylusHandwritingAvailableAsUser(userId, connectionless);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index b60efc1..7c9678f 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -204,6 +204,9 @@
*/
private final boolean mSupportsStylusHandwriting;
+ /** The flag whether this IME supports connectionless stylus handwriting sessions. */
+ private final boolean mSupportsConnectionlessStylusHandwriting;
+
/**
* The stylus handwriting setting activity's name, used by the system settings to
* launch the stylus handwriting specific setting activity of this input method.
@@ -330,6 +333,9 @@
com.android.internal.R.styleable.InputMethod_configChanges, 0);
mSupportsStylusHandwriting = sa.getBoolean(
com.android.internal.R.styleable.InputMethod_supportsStylusHandwriting, false);
+ mSupportsConnectionlessStylusHandwriting = sa.getBoolean(
+ com.android.internal.R.styleable
+ .InputMethod_supportsConnectionlessStylusHandwriting, false);
stylusHandwritingSettingsActivity = sa.getString(
com.android.internal.R.styleable.InputMethod_stylusHandwritingSettingsActivity);
sa.recycle();
@@ -442,6 +448,7 @@
mSubtypes = source.mSubtypes;
mHandledConfigChanges = source.mHandledConfigChanges;
mSupportsStylusHandwriting = source.mSupportsStylusHandwriting;
+ mSupportsConnectionlessStylusHandwriting = source.mSupportsConnectionlessStylusHandwriting;
mForceDefault = source.mForceDefault;
mStylusHandwritingSettingsActivityAttr = source.mStylusHandwritingSettingsActivityAttr;
}
@@ -463,6 +470,7 @@
mSubtypes = new InputMethodSubtypeArray(source);
mHandledConfigChanges = source.readInt();
mSupportsStylusHandwriting = source.readBoolean();
+ mSupportsConnectionlessStylusHandwriting = source.readBoolean();
mStylusHandwritingSettingsActivityAttr = source.readString8();
mForceDefault = false;
}
@@ -479,6 +487,7 @@
false /* inlineSuggestionsEnabled */, false /* isVrOnly */,
false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */,
false /* supportsStylusHandwriting */,
+ false /* supportConnectionlessStylusHandwriting */,
null /* stylusHandwritingSettingsActivityAttr */,
false /* inlineSuggestionsEnabled */);
}
@@ -488,9 +497,11 @@
* @hide
*/
@TestApi
+ @FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING)
public InputMethodInfo(@NonNull String packageName, @NonNull String className,
@NonNull CharSequence label, @NonNull String settingsActivity,
@NonNull String languageSettingsActivity, boolean supportStylusHandwriting,
+ boolean supportConnectionlessStylusHandwriting,
@NonNull String stylusHandwritingSettingsActivityAttr) {
this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */,
settingsActivity, languageSettingsActivity, null /* subtypes */,
@@ -498,8 +509,8 @@
true /* supportsSwitchingToNextInputMethod */,
false /* inlineSuggestionsEnabled */, false /* isVrOnly */,
false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */,
- supportStylusHandwriting, stylusHandwritingSettingsActivityAttr,
- false /* inlineSuggestionsEnabled */);
+ supportStylusHandwriting, supportConnectionlessStylusHandwriting,
+ stylusHandwritingSettingsActivityAttr, false /* inlineSuggestionsEnabled */);
}
/**
@@ -517,6 +528,7 @@
false /* inlineSuggestionsEnabled */, false /* isVrOnly */,
false /* isVirtualDeviceOnly */, handledConfigChanges,
false /* supportsStylusHandwriting */,
+ false /* supportConnectionlessStylusHandwriting */,
null /* stylusHandwritingSettingsActivityAttr */,
false /* inlineSuggestionsEnabled */);
}
@@ -533,6 +545,7 @@
true /* supportsSwitchingToNextInputMethod */, false /* inlineSuggestionsEnabled */,
false /* isVrOnly */, false /* isVirtualDeviceOnly */, 0 /* handledconfigChanges */,
false /* supportsStylusHandwriting */,
+ false /* supportConnectionlessStylusHandwriting */,
null /* stylusHandwritingSettingsActivityAttr */,
false /* inlineSuggestionsEnabled */);
}
@@ -549,6 +562,7 @@
supportsSwitchingToNextInputMethod, false /* inlineSuggestionsEnabled */, isVrOnly,
false /* isVirtualDeviceOnly */,
0 /* handledConfigChanges */, false /* supportsStylusHandwriting */,
+ false /* supportConnectionlessStylusHandwriting */,
null /* stylusHandwritingSettingsActivityAttr */,
false /* inlineSuggestionsEnabled */);
}
@@ -562,7 +576,8 @@
int isDefaultResId, boolean forceDefault,
boolean supportsSwitchingToNextInputMethod, boolean inlineSuggestionsEnabled,
boolean isVrOnly, boolean isVirtualDeviceOnly, int handledConfigChanges,
- boolean supportsStylusHandwriting, String stylusHandwritingSettingsActivityAttr,
+ boolean supportsStylusHandwriting, boolean supportsConnectionlessStylusHandwriting,
+ String stylusHandwritingSettingsActivityAttr,
boolean supportsInlineSuggestionsWithTouchExploration) {
final ServiceInfo si = ri.serviceInfo;
mService = ri;
@@ -583,6 +598,7 @@
mIsVirtualDeviceOnly = isVirtualDeviceOnly;
mHandledConfigChanges = handledConfigChanges;
mSupportsStylusHandwriting = supportsStylusHandwriting;
+ mSupportsConnectionlessStylusHandwriting = supportsConnectionlessStylusHandwriting;
mStylusHandwritingSettingsActivityAttr = stylusHandwritingSettingsActivityAttr;
}
@@ -763,6 +779,16 @@
}
/**
+ * Returns whether the IME supports connectionless stylus handwriting sessions.
+ *
+ * @attr ref android.R.styleable#InputMethod_supportsConnectionlessStylusHandwriting
+ */
+ @FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING)
+ public boolean supportsConnectionlessStylusHandwriting() {
+ return mSupportsConnectionlessStylusHandwriting;
+ }
+
+ /**
* Returns {@link Intent} for stylus handwriting settings activity with
* {@link Intent#getAction() Intent action} {@link #ACTION_STYLUS_HANDWRITING_SETTINGS}
* if IME {@link #supportsStylusHandwriting() supports stylus handwriting}, else
@@ -828,6 +854,8 @@
+ " mSuppressesSpellChecker=" + mSuppressesSpellChecker
+ " mShowInInputMethodPicker=" + mShowInInputMethodPicker
+ " mSupportsStylusHandwriting=" + mSupportsStylusHandwriting
+ + " mSupportsConnectionlessStylusHandwriting="
+ + mSupportsConnectionlessStylusHandwriting
+ " mStylusHandwritingSettingsActivityAttr="
+ mStylusHandwritingSettingsActivityAttr);
pw.println(prefix + "mIsDefaultResId=0x"
@@ -947,6 +975,7 @@
mSubtypes.writeToParcel(dest);
dest.writeInt(mHandledConfigChanges);
dest.writeBoolean(mSupportsStylusHandwriting);
+ dest.writeBoolean(mSupportsConnectionlessStylusHandwriting);
dest.writeString8(mStylusHandwritingSettingsActivityAttr);
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 3b07f27..f4b09df 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -38,6 +38,7 @@
import static com.android.internal.inputmethod.StartInputReason.BOUND_TO_IMMS;
import android.Manifest;
+import android.annotation.CallbackExecutor;
import android.annotation.DisplayContext;
import android.annotation.DrawableRes;
import android.annotation.DurationMillisLong;
@@ -108,6 +109,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.internal.inputmethod.IConnectionlessHandwritingCallback;
import com.android.internal.inputmethod.IInputMethodClient;
import com.android.internal.inputmethod.IInputMethodSession;
import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
@@ -134,6 +136,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
@@ -566,8 +569,15 @@
@GuardedBy("mH")
private PropertyInvalidatedCache<Integer, Boolean> mStylusHandwritingAvailableCache;
+ /** Cached value for {@link #isConnectionlessStylusHandwritingAvailable} for userId. */
+ @GuardedBy("mH")
+ private PropertyInvalidatedCache<Integer, Boolean>
+ mConnectionlessStylusHandwritingAvailableCache;
+
private static final String CACHE_KEY_STYLUS_HANDWRITING_PROPERTY =
"cache_key.system_server.stylus_handwriting";
+ private static final String CACHE_KEY_CONNECTIONLESS_STYLUS_HANDWRITING_PROPERTY =
+ "cache_key.system_server.connectionless_stylus_handwriting";
@GuardedBy("mH")
private int mCursorSelStart;
@@ -691,6 +701,17 @@
PropertyInvalidatedCache.invalidateCache(CACHE_KEY_STYLUS_HANDWRITING_PROPERTY);
}
+ /**
+ * Calling this will invalidate the local connectionless stylus handwriting availability cache,
+ * which forces the next query in any process to recompute the cache.
+ *
+ * @hide
+ */
+ public static void invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches() {
+ PropertyInvalidatedCache.invalidateCache(
+ CACHE_KEY_CONNECTIONLESS_STYLUS_HANDWRITING_PROPERTY);
+ }
+
private static boolean isAutofillUIShowing(View servedView) {
AutofillManager afm = servedView.getContext().getSystemService(AutofillManager.class);
return afm != null && afm.isAutofillUiShowing();
@@ -1584,7 +1605,7 @@
@Override
public Boolean recompute(Integer userId) {
return IInputMethodManagerGlobalInvoker.isStylusHandwritingAvailableAsUser(
- userId);
+ userId, /* connectionless= */ false);
}
};
}
@@ -1594,6 +1615,30 @@
}
/**
+ * Returns {@code true} if the currently selected IME supports connectionless stylus handwriting
+ * sessions and is enabled.
+ */
+ @FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING)
+ public boolean isConnectionlessStylusHandwritingAvailable() {
+ if (ActivityThread.currentApplication() == null) {
+ return false;
+ }
+ synchronized (mH) {
+ if (mConnectionlessStylusHandwritingAvailableCache == null) {
+ mConnectionlessStylusHandwritingAvailableCache = new PropertyInvalidatedCache<>(
+ /* maxEntries= */ 4, CACHE_KEY_CONNECTIONLESS_STYLUS_HANDWRITING_PROPERTY) {
+ @Override
+ public Boolean recompute(@NonNull Integer userId) {
+ return IInputMethodManagerGlobalInvoker.isStylusHandwritingAvailableAsUser(
+ userId, /* connectionless= */ true);
+ }
+ };
+ }
+ return mConnectionlessStylusHandwritingAvailableCache.query(UserHandle.myUserId());
+ }
+ }
+
+ /**
* Returns the list of installed input methods for the specified user.
*
* <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
@@ -2433,6 +2478,127 @@
}
/**
+ * Starts a connectionless stylus handwriting session. A connectionless session differs from a
+ * regular stylus handwriting session in that the IME does not use an input connection to
+ * communicate with a text editor. Instead, the IME directly returns recognised handwritten text
+ * via a callback.
+ *
+ * <p>The {code cursorAnchorInfo} may be used by the IME to improve the handwriting recognition
+ * accuracy and user experience of the handwriting session. Usually connectionless handwriting
+ * is used for a view which appears like a text editor but does not really support text editing.
+ * For best results, the {code cursorAnchorInfo} should be populated as it would be for a real
+ * text editor (for example, the insertion marker location can be set to where the user would
+ * expect it to be, even if there is no visible cursor).
+ *
+ * @param view the view receiving stylus events
+ * @param cursorAnchorInfo positional information about the view receiving stylus events
+ * @param callbackExecutor the executor to run the callback on
+ * @param callback the callback to receive the result
+ */
+ @FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING)
+ public void startConnectionlessStylusHandwriting(@NonNull View view,
+ @Nullable CursorAnchorInfo cursorAnchorInfo,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull ConnectionlessHandwritingCallback callback) {
+ startConnectionlessStylusHandwritingInternal(
+ view, cursorAnchorInfo, null, null, callbackExecutor, callback);
+ }
+
+ /**
+ * Starts a connectionless stylus handwriting session (see {@link
+ * #startConnectionlessStylusHandwriting}) and additionally enables the recognised handwritten
+ * text to be later committed to a text editor using {@link
+ * #acceptStylusHandwritingDelegation(View)}.
+ *
+ * <p>After a connectionless session started using this method completes successfully, a text
+ * editor view, called the delegate view, may call {@link
+ * #acceptStylusHandwritingDelegation(View)} which will request the IME to commit the recognised
+ * handwritten text from the connectionless session to the delegate view.
+ *
+ * <p>The delegate view must belong to the same package as the delegator view for the delegation
+ * to succeed. If the delegate view belongs to a different package, use {@link
+ * #startConnectionlessStylusHandwritingForDelegation(View, CursorAnchorInfo, String, Executor,
+ * ConnectionlessHandwritingCallback)} instead.
+ *
+ * @param delegatorView the view receiving stylus events
+ * @param cursorAnchorInfo positional information about the view receiving stylus events
+ * @param callbackExecutor the executor to run the callback on
+ * @param callback the callback to receive the result
+ */
+ @FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING)
+ public void startConnectionlessStylusHandwritingForDelegation(@NonNull View delegatorView,
+ @Nullable CursorAnchorInfo cursorAnchorInfo,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull ConnectionlessHandwritingCallback callback) {
+ String delegatorPackageName = delegatorView.getContext().getOpPackageName();
+ startConnectionlessStylusHandwritingInternal(delegatorView, cursorAnchorInfo,
+ delegatorPackageName, delegatorPackageName, callbackExecutor, callback);
+ }
+
+ /**
+ * Starts a connectionless stylus handwriting session (see {@link
+ * #startConnectionlessStylusHandwriting}) and additionally enables the recognised handwritten
+ * text to be later committed to a text editor using {@link
+ * #acceptStylusHandwritingDelegation(View, String)}.
+ *
+ * <p>After a connectionless session started using this method completes successfully, a text
+ * editor view, called the delegate view, may call {@link
+ * #acceptStylusHandwritingDelegation(View, String)} which will request the IME to commit the
+ * recognised handwritten text from the connectionless session to the delegate view.
+ *
+ * <p>The delegate view must belong to {@code delegatePackageName} for the delegation to
+ * succeed.
+ *
+ * @param delegatorView the view receiving stylus events
+ * @param cursorAnchorInfo positional information about the view receiving stylus events
+ * @param delegatePackageName name of the package containing the delegate view which will accept
+ * the delegation
+ * @param callbackExecutor the executor to run the callback on
+ * @param callback the callback to receive the result
+ */
+ @FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING)
+ public void startConnectionlessStylusHandwritingForDelegation(@NonNull View delegatorView,
+ @Nullable CursorAnchorInfo cursorAnchorInfo,
+ @NonNull String delegatePackageName,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull ConnectionlessHandwritingCallback callback) {
+ Objects.requireNonNull(delegatePackageName);
+ String delegatorPackageName = delegatorView.getContext().getOpPackageName();
+ startConnectionlessStylusHandwritingInternal(delegatorView, cursorAnchorInfo,
+ delegatorPackageName, delegatePackageName, callbackExecutor, callback);
+ }
+
+ private void startConnectionlessStylusHandwritingInternal(@NonNull View view,
+ @Nullable CursorAnchorInfo cursorAnchorInfo,
+ @Nullable String delegatorPackageName,
+ @Nullable String delegatePackageName,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull ConnectionlessHandwritingCallback callback) {
+ Objects.requireNonNull(view);
+ Objects.requireNonNull(callbackExecutor);
+ Objects.requireNonNull(callback);
+ // Re-dispatch if there is a context mismatch.
+ final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+ if (fallbackImm != null) {
+ fallbackImm.startConnectionlessStylusHandwritingInternal(view, cursorAnchorInfo,
+ delegatorPackageName, delegatePackageName, callbackExecutor, callback);
+ }
+
+ checkFocus();
+ synchronized (mH) {
+ if (view.getViewRootImpl() != mCurRootView) {
+ Log.w(TAG, "Ignoring startConnectionlessStylusHandwriting: "
+ + "View's window does not have focus.");
+ return;
+ }
+ IInputMethodManagerGlobalInvoker.startConnectionlessStylusHandwriting(
+ mClient, UserHandle.myUserId(), cursorAnchorInfo,
+ delegatePackageName, delegatorPackageName,
+ new ConnectionlessHandwritingCallbackProxy(callbackExecutor, callback));
+ }
+ }
+
+ /**
* Prepares delegation of starting stylus handwriting session to a different editor in same
* or different window than the view on which initial handwriting stroke was detected.
*
@@ -2511,12 +2677,18 @@
* {@link #acceptStylusHandwritingDelegation(View, String)} instead.</p>
*
* @param delegateView delegate view capable of receiving input via {@link InputConnection}
- * on which {@link #startStylusHandwriting(View)} will be called.
* @return {@code true} if view belongs to same application package as used in
- * {@link #prepareStylusHandwritingDelegation(View)} and handwriting session can start.
- * @see #acceptStylusHandwritingDelegation(View, String)
+ * {@link #prepareStylusHandwritingDelegation(View)} and delegation is accepted
* @see #prepareStylusHandwritingDelegation(View)
+ * @see #acceptStylusHandwritingDelegation(View, String)
*/
+ // TODO(b/300979854): Once connectionless APIs are finalised, update documentation to add:
+ // <p>Otherwise, if the delegator view previously started delegation using {@link
+ // #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver, CursorAnchorInfo)},
+ // requests the IME to commit the recognised handwritten text from the connectionless session to
+ // the delegate view.
+ // @see #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver,
+ // CursorAnchorInfo)
public boolean acceptStylusHandwritingDelegation(@NonNull View delegateView) {
return startStylusHandwritingInternal(
delegateView, delegateView.getContext().getOpPackageName(),
@@ -2533,13 +2705,19 @@
* {@link #acceptStylusHandwritingDelegation(View)} instead.</p>
*
* @param delegateView delegate view capable of receiving input via {@link InputConnection}
- * on which {@link #startStylusHandwriting(View)} will be called.
* @param delegatorPackageName package name of the delegator that handled initial stylus stroke.
- * @return {@code true} if view belongs to allowed delegate package declared in
- * {@link #prepareStylusHandwritingDelegation(View, String)} and handwriting session can start.
+ * @return {@code true} if view belongs to allowed delegate package declared in {@link
+ * #prepareStylusHandwritingDelegation(View, String)} and delegation is accepted
* @see #prepareStylusHandwritingDelegation(View, String)
* @see #acceptStylusHandwritingDelegation(View)
*/
+ // TODO(b/300979854): Once connectionless APIs are finalised, update documentation to add:
+ // <p>Otherwise, if the delegator view previously started delegation using {@link
+ // #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver, CursorAnchorInfo,
+ // String)}, requests the IME to commit the recognised handwritten text from the connectionless
+ // session to the delegate view.
+ // @see #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver,
+ // CursorAnchorInfo, String)
public boolean acceptStylusHandwritingDelegation(
@NonNull View delegateView, @NonNull String delegatorPackageName) {
Objects.requireNonNull(delegatorPackageName);
@@ -2556,15 +2734,21 @@
* <p>Note: If delegator and delegate are in the same application package, use {@link
* #acceptStylusHandwritingDelegation(View)} instead.
*
- * @param delegateView delegate view capable of receiving input via {@link InputConnection} on
- * which {@link #startStylusHandwriting(View)} will be called.
+ * @param delegateView delegate view capable of receiving input via {@link InputConnection}
* @param delegatorPackageName package name of the delegator that handled initial stylus stroke.
* @param flags {@link #HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED} or {@code 0}
* @return {@code true} if view belongs to allowed delegate package declared in {@link
- * #prepareStylusHandwritingDelegation(View, String)} and handwriting session can start.
+ * #prepareStylusHandwritingDelegation(View, String)} and delegation is accepted
* @see #prepareStylusHandwritingDelegation(View, String)
* @see #acceptStylusHandwritingDelegation(View)
*/
+ // TODO(b/300979854): Once connectionless APIs are finalised, update documentation to add:
+ // <p>Otherwise, if the delegator view previously started delegation using {@link
+ // #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver, CursorAnchorInfo,
+ // String)}, requests the IME to commit the recognised handwritten text from the connectionless
+ // session to the delegate view.
+ // @see #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver,
+ // CursorAnchorInfo, String)
@FlaggedApi(FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR)
public boolean acceptStylusHandwritingDelegation(
@NonNull View delegateView, @NonNull String delegatorPackageName,
@@ -4315,6 +4499,73 @@
public void onFinishedInputEvent(Object token, boolean handled);
}
+ private static class ConnectionlessHandwritingCallbackProxy
+ extends IConnectionlessHandwritingCallback.Stub {
+ private final Object mLock = new Object();
+
+ @Nullable
+ @GuardedBy("mLock")
+ private Executor mExecutor;
+
+ @Nullable
+ @GuardedBy("mLock")
+ private ConnectionlessHandwritingCallback mCallback;
+
+ ConnectionlessHandwritingCallbackProxy(
+ @NonNull Executor executor, @NonNull ConnectionlessHandwritingCallback callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onResult(CharSequence text) {
+ Executor executor;
+ ConnectionlessHandwritingCallback callback;
+ synchronized (mLock) {
+ if (mExecutor == null || mCallback == null) {
+ return;
+ }
+ executor = mExecutor;
+ callback = mCallback;
+ mExecutor = null;
+ mCallback = null;
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (TextUtils.isEmpty(text)) {
+ executor.execute(() -> callback.onError(
+ ConnectionlessHandwritingCallback
+ .CONNECTIONLESS_HANDWRITING_ERROR_NO_TEXT_RECOGNIZED));
+ } else {
+ executor.execute(() -> callback.onResult(text));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void onError(int errorCode) {
+ Executor executor;
+ ConnectionlessHandwritingCallback callback;
+ synchronized (mLock) {
+ if (mExecutor == null || mCallback == null) {
+ return;
+ }
+ executor = mExecutor;
+ callback = mCallback;
+ mExecutor = null;
+ mCallback = null;
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> callback.onError(errorCode));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
private final class ImeInputEventSender extends InputEventSender {
public ImeInputEventSender(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index 8b91bcb..55986e7 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -70,3 +70,11 @@
bug: "308827131"
is_fixed_read_only: true
}
+
+flag {
+ name: "connectionless_handwriting"
+ namespace: "input_method"
+ description: "Feature flag for connectionless stylus handwriting APIs"
+ bug: "300979854"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
index e7280d0..40e28cb 100644
--- a/core/java/android/window/BackProgressAnimator.java
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -17,6 +17,7 @@
package android.window;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.util.FloatProperty;
import com.android.internal.dynamicanimation.animation.DynamicAnimation;
@@ -44,6 +45,14 @@
private float mProgress = 0;
private BackMotionEvent mLastBackEvent;
private boolean mBackAnimationInProgress = false;
+ @Nullable
+ private Runnable mBackCancelledFinishRunnable;
+ private final DynamicAnimation.OnAnimationEndListener mOnAnimationEndListener =
+ (animation, canceled, value, velocity) -> {
+ invokeBackCancelledRunnable();
+ reset();
+ };
+
private void setProgress(float progress) {
mProgress = progress;
@@ -116,6 +125,11 @@
* Resets the back progress animation. This should be called when back is invoked or cancelled.
*/
public void reset() {
+ if (mBackCancelledFinishRunnable != null) {
+ // Ensure that last progress value that apps see is 0
+ updateProgressValue(0);
+ invokeBackCancelledRunnable();
+ }
mSpring.animateToFinalPosition(0);
if (mSpring.canSkipToEnd()) {
mSpring.skipToEnd();
@@ -136,17 +150,8 @@
* @param finishCallback the callback to be invoked when the progress is reach to 0.
*/
public void onBackCancelled(@NonNull Runnable finishCallback) {
- final DynamicAnimation.OnAnimationEndListener listener =
- new DynamicAnimation.OnAnimationEndListener() {
- @Override
- public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
- float velocity) {
- mSpring.removeEndListener(this);
- finishCallback.run();
- reset();
- }
- };
- mSpring.addEndListener(listener);
+ mBackCancelledFinishRunnable = finishCallback;
+ mSpring.addEndListener(mOnAnimationEndListener);
mSpring.animateToFinalPosition(0);
}
@@ -164,4 +169,10 @@
progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge()));
}
-}
+ private void invokeBackCancelledRunnable() {
+ mSpring.removeEndListener(mOnAnimationEndListener);
+ mBackCancelledFinishRunnable.run();
+ mBackCancelledFinishRunnable = null;
+ }
+
+}
\ No newline at end of file
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index efc71d7..76a34ae 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -676,18 +676,20 @@
* This identifies them.
* @param type The {@link InsetsType} of the insets source.
* @param frame The rectangle area of the insets source.
+ * @param boundingRects The bounding rects within this inset, relative to the |frame|.
* @hide
*/
@NonNull
public WindowContainerTransaction addInsetsSource(
@NonNull WindowContainerToken receiver,
- IBinder owner, int index, @InsetsType int type, Rect frame) {
+ IBinder owner, int index, @InsetsType int type, Rect frame, Rect[] boundingRects) {
final HierarchyOp hierarchyOp =
new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER)
.setContainer(receiver.asBinder())
.setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type)
.setSource(InsetsFrameProvider.SOURCE_ARBITRARY_RECTANGLE)
- .setArbitraryRectangle(frame))
+ .setArbitraryRectangle(frame)
+ .setBoundingRects(boundingRects))
.setInsetsFrameOwner(owner)
.build();
mHierarchyOps.add(hierarchyOp);
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 5c911f4..45d7767 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -371,11 +371,11 @@
}
final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) {
+ mProgressAnimator.reset();
callback.onBackStarted(new BackEvent(
backEvent.getTouchX(), backEvent.getTouchY(),
backEvent.getProgress(), backEvent.getSwipeEdge()));
- mProgressAnimator.onBackStarted(backEvent, event ->
- callback.onBackProgressed(event));
+ mProgressAnimator.onBackStarted(backEvent, callback::onBackProgressed);
}
});
}
diff --git a/core/java/com/android/internal/inputmethod/IConnectionlessHandwritingCallback.aidl b/core/java/com/android/internal/inputmethod/IConnectionlessHandwritingCallback.aidl
new file mode 100644
index 0000000..e564599
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/IConnectionlessHandwritingCallback.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+/** Binder interface to receive a result from a connectionless stylus handwriting session. */
+oneway interface IConnectionlessHandwritingCallback {
+ void onResult(in CharSequence text);
+ void onError(int errorCode);
+}
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 0b7593a..c5c17cf 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -1146,7 +1146,7 @@
mHistoryCur.batteryHealth = (byte) health;
mHistoryCur.batteryPlugType = (byte) plugType;
mHistoryCur.batteryTemperature = (short) temperature;
- mHistoryCur.batteryVoltage = (char) voltageMv;
+ mHistoryCur.batteryVoltage = (short) voltageMv;
mHistoryCur.batteryChargeUah = chargeUah;
}
}
@@ -2010,7 +2010,11 @@
int bits = 0;
bits = setBitField(bits, h.batteryLevel, 25, 0xfe000000 /* 7F << 25 */);
bits = setBitField(bits, h.batteryTemperature, 15, 0x01ff8000 /* 3FF << 15 */);
- bits = setBitField(bits, h.batteryVoltage, 1, 0x00007ffe /* 3FFF << 1 */);
+ short voltage = (short) h.batteryVoltage;
+ if (voltage == -1) {
+ voltage = 0x3FFF;
+ }
+ bits = setBitField(bits, voltage, 1, 0x00007ffe /* 3FFF << 1 */);
return bits;
}
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index 739ee48..b2a6a93 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -309,7 +309,12 @@
private static void readBatteryLevelInt(int batteryLevelInt, BatteryStats.HistoryItem out) {
out.batteryLevel = (byte) ((batteryLevelInt & 0xfe000000) >>> 25);
out.batteryTemperature = (short) ((batteryLevelInt & 0x01ff8000) >>> 15);
- out.batteryVoltage = (char) ((batteryLevelInt & 0x00007ffe) >>> 1);
+ int voltage = ((batteryLevelInt & 0x00007ffe) >>> 1);
+ if (voltage == 0x3FFF) {
+ voltage = -1;
+ }
+
+ out.batteryVoltage = (short) voltage;
}
/**
diff --git a/core/java/com/android/internal/protolog/OWNERS b/core/java/com/android/internal/protolog/OWNERS
new file mode 100644
index 0000000..18cf2be
--- /dev/null
+++ b/core/java/com/android/internal/protolog/OWNERS
@@ -0,0 +1,3 @@
+# ProtoLog owners
+# Bug component: 1157642
+include platform/development:/tools/winscope/OWNERS
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 595bf3b..e95127b 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -17,12 +17,14 @@
package com.android.internal.view;
import android.os.ResultReceiver;
+import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.EditorInfo;
import android.window.ImeOnBackInvokedDispatcher;
+import com.android.internal.inputmethod.IConnectionlessHandwritingCallback;
import com.android.internal.inputmethod.IImeTracker;
import com.android.internal.inputmethod.IInputMethodClient;
import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
@@ -144,6 +146,9 @@
/** Start Stylus handwriting session **/
void startStylusHandwriting(in IInputMethodClient client);
+ oneway void startConnectionlessStylusHandwriting(in IInputMethodClient client, int userId,
+ in CursorAnchorInfo cursorAnchorInfo, in String delegatePackageName,
+ in String delegatorPackageName, in IConnectionlessHandwritingCallback callback);
/** Prepares delegation of starting stylus handwriting session to a different editor **/
void prepareStylusHandwritingDelegation(in IInputMethodClient client,
@@ -158,7 +163,7 @@
/** Returns {@code true} if currently selected IME supports Stylus handwriting. */
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
- boolean isStylusHandwritingAvailableAsUser(int userId);
+ boolean isStylusHandwritingAvailableAsUser(int userId, boolean connectionless);
/** add virtual stylus id for test Stylus handwriting session **/
@EnforcePermission("TEST_INPUT_METHOD")
diff --git a/core/java/com/android/internal/widget/NotificationOptimizedLinearLayout.java b/core/java/com/android/internal/widget/NotificationOptimizedLinearLayout.java
index b5e9b8f..0ceba25 100644
--- a/core/java/com/android/internal/widget/NotificationOptimizedLinearLayout.java
+++ b/core/java/com/android/internal/widget/NotificationOptimizedLinearLayout.java
@@ -53,6 +53,7 @@
* - LinearLayout doesn't have <code>weightSum</code>.
* - Horizontal LinearLayout's width should be measured EXACTLY.
* - Horizontal LinearLayout shouldn't need baseLineAlignment.
+ * - Horizontal LinearLayout shouldn't have any child that has negative left or right margin.
* - Vertical LinearLayout shouldn't have MATCH_PARENT children when it is not measured EXACTLY.
*
* @hide
@@ -88,7 +89,7 @@
final View weightedChildView = getSingleWeightedChild();
mShouldUseOptimizedLayout =
isUseOptimizedLinearLayoutFlagEnabled() && weightedChildView != null
- && isLinearLayoutUsable(widthMeasureSpec, heightMeasureSpec);
+ && isOptimizationPossible(widthMeasureSpec, heightMeasureSpec);
if (mShouldUseOptimizedLayout) {
onMeasureOptimized(weightedChildView, widthMeasureSpec, heightMeasureSpec);
@@ -118,7 +119,7 @@
* @param heightMeasureSpec The height measurement specification.
* @return `true` if optimization is possible, `false` otherwise.
*/
- private boolean isLinearLayoutUsable(int widthMeasureSpec, int heightMeasureSpec) {
+ private boolean isOptimizationPossible(int widthMeasureSpec, int heightMeasureSpec) {
final boolean hasWeightSum = getWeightSum() > 0.0f;
if (hasWeightSum) {
logSkipOptimizedOnMeasure("Has weightSum.");
@@ -142,10 +143,36 @@
logSkipOptimizedOnMeasure("Need to apply baseline.");
return false;
}
+
+ if (requiresNegativeMarginHandlingForHorizontalLinearLayout()) {
+ logSkipOptimizedOnMeasure("Need to handle negative margins.");
+ return false;
+ }
return true;
}
/**
+ * @return if the horizontal linearlayout requires to handle negative margins in its children.
+ * In that case, we can't use excessSpace because LinearLayout negative margin handling for
+ * excess space and WRAP_CONTENT is different.
+ */
+ private boolean requiresNegativeMarginHandlingForHorizontalLinearLayout() {
+ if (getOrientation() == VERTICAL) {
+ return false;
+ }
+
+ final List<View> activeChildren = getActiveChildren();
+ for (int i = 0; i < activeChildren.size(); i++) {
+ final View child = activeChildren.get(i);
+ final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+ if (lp.leftMargin < 0 || lp.rightMargin < 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* @return if the vertical linearlayout requires match_parent children remeasure
*/
private boolean requiresMatchParentRemeasureForVerticalLinearLayout(int widthMeasureSpec) {
@@ -337,94 +364,81 @@
*/
private void measureVerticalOptimized(@NonNull View weightedChildView, int widthMeasureSpec,
int heightMeasureSpec) {
- final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ int totalLength = 0;
int maxWidth = 0;
- int usedHeight = 0;
- final List<View> activeChildren = getActiveChildren();
- final int activeChildCount = activeChildren.size();
+ final int availableHeight = MeasureSpec.getSize(heightMeasureSpec);
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- final boolean isContentFirstItem = !activeChildren.isEmpty() && activeChildren.get(0)
- == weightedChildView;
-
- final boolean isContentLastItem = !activeChildren.isEmpty() && activeChildren.get(
- activeChildCount - 1) == weightedChildView;
-
- final int horizontalPaddings = getPaddingLeft() + getPaddingRight();
-
- // 1. Measure other child views.
- for (int i = 0; i < activeChildCount; i++) {
- final View child = activeChildren.get(i);
- if (child == weightedChildView) {
+ // 1. Measure all unweighted children
+ for (int i = 0; i < getChildCount(); i++) {
+ final View child = getChildAt(i);
+ if (child == null || child.getVisibility() == GONE) {
continue;
}
+
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
- int requiredVerticalPadding = lp.topMargin + lp.bottomMargin;
- if (!isContentFirstItem && i == 0) {
- requiredVerticalPadding += getPaddingTop();
- }
- if (!isContentLastItem && i == activeChildCount - 1) {
- requiredVerticalPadding += getPaddingBottom();
+ if (child == weightedChildView) {
+ // In excessMode, LinearLayout add weighted child top and bottom margins to
+ // totalLength when their sum is positive.
+ if (lp.height == 0 && heightMode == MeasureSpec.EXACTLY) {
+ totalLength = Math.max(totalLength, totalLength + lp.topMargin
+ + lp.bottomMargin);
+ }
+ continue;
}
- child.measure(ViewGroup.getChildMeasureSpec(widthMeasureSpec,
- horizontalPaddings + lp.leftMargin + lp.rightMargin,
- child.getLayoutParams().width),
- ViewGroup.getChildMeasureSpec(heightMeasureSpec, requiredVerticalPadding,
- lp.height));
+ measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
+ // LinearLayout only adds measured children heights and its top and bottom margins
+ // to totalLength when their sum is positive.
+ totalLength = Math.max(totalLength,
+ totalLength + child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
- usedHeight += child.getMeasuredHeight() + requiredVerticalPadding;
}
- // measure content
+ // Add padding to totalLength that we are going to use for remaining space.
+ totalLength += mPaddingTop + mPaddingBottom;
+
+ // 2. generate measure spec for weightedChildView.
final MarginLayoutParams lp = (MarginLayoutParams) weightedChildView.getLayoutParams();
+ // height should be AT_MOST for non EXACT cases.
+ final int childHeightMeasureMode =
+ heightMode == MeasureSpec.EXACTLY ? MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
+ final int childHeightMeasureSpec;
- int usedSpace = usedHeight + lp.topMargin + lp.bottomMargin;
- if (isContentFirstItem) {
- usedSpace += getPaddingTop();
+ // In excess mode, LinearLayout measures weighted children with remaining space. Otherwise,
+ // it is measured with remaining space just like other children.
+ if (lp.height == 0 && heightMode == MeasureSpec.EXACTLY) {
+ childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
+ Math.max(0, availableHeight - totalLength), childHeightMeasureMode);
+ } else {
+ final int usedHeight = lp.topMargin + lp.bottomMargin + totalLength;
+ childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
+ Math.max(0, availableHeight - usedHeight), childHeightMeasureMode);
}
- if (isContentLastItem) {
- usedSpace += getPaddingBottom();
- }
+ final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
+ mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin, lp.width);
- final int availableWidth = MeasureSpec.getSize(widthMeasureSpec);
- final int availableHeight = MeasureSpec.getSize(heightMeasureSpec);
-
- final int childWidthMeasureSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
- horizontalPaddings + lp.leftMargin + lp.rightMargin, lp.width);
-
- // 2. Calculate remaining height for weightedChildView.
- final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
- Math.max(0, availableHeight - usedSpace), MeasureSpec.AT_MOST);
-
- // 3. Measure weightedChildView with the remaining remaining space.
+ // 3. Measure weightedChildView with the remaining space.
weightedChildView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+
+ totalLength = Math.max(totalLength,
+ totalLength + weightedChildView.getMeasuredHeight() + lp.topMargin
+ + lp.bottomMargin);
+
maxWidth = Math.max(maxWidth,
weightedChildView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
- final int totalUsedHeight = usedSpace + weightedChildView.getMeasuredHeight();
+ // Add padding to width
+ maxWidth += getPaddingLeft() + getPaddingRight();
- final int measuredWidth;
- if (widthMode == MeasureSpec.EXACTLY) {
- measuredWidth = availableWidth;
- } else {
- measuredWidth = maxWidth + getPaddingStart() + getPaddingEnd();
- }
-
- final int measuredHeight;
- if (heightMode == MeasureSpec.EXACTLY) {
- measuredHeight = availableHeight;
- } else {
- measuredHeight = totalUsedHeight;
- }
-
- // 4. Set the container size
- setMeasuredDimension(
- resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth),
- widthMeasureSpec),
- Math.max(getSuggestedMinimumHeight(), measuredHeight));
+ // Resolve final dimensions
+ final int finalWidth = resolveSizeAndState(Math.max(maxWidth, getSuggestedMinimumWidth()),
+ widthMeasureSpec, 0);
+ final int finalHeight = resolveSizeAndState(
+ Math.max(totalLength, getSuggestedMinimumHeight()), heightMeasureSpec, 0);
+ setMeasuredDimension(finalWidth, finalHeight);
}
@NonNull
diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp
index ce4a337..8dc9d0a 100644
--- a/core/jni/android_os_VintfObject.cpp
+++ b/core/jni/android_os_VintfObject.cpp
@@ -39,6 +39,7 @@
using vintf::HalManifest;
using vintf::Level;
using vintf::SchemaType;
+using vintf::SepolicyVersion;
using vintf::to_string;
using vintf::toXml;
using vintf::Version;
@@ -139,7 +140,7 @@
return nullptr;
}
- Version latest;
+ SepolicyVersion latest;
for (const auto& range : versions) {
latest = std::max(latest, range.maxVer());
}
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 3d0ab4e..3ee15ab 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -314,8 +314,7 @@
}
static void NativeSetApkAssets(JNIEnv* env, jclass /*clazz*/, jlong ptr,
- jobjectArray apk_assets_array, jboolean invalidate_caches,
- jboolean preset) {
+ jobjectArray apk_assets_array, jboolean invalidate_caches) {
ATRACE_NAME("AssetManager::SetApkAssets");
const jsize apk_assets_len = env->GetArrayLength(apk_assets_array);
@@ -344,11 +343,7 @@
}
auto assetmanager = LockAndStartAssetManager(ptr);
- if (preset) {
- assetmanager->PresetApkAssets(apk_assets);
- } else {
- assetmanager->SetApkAssets(apk_assets, invalidate_caches);
- }
+ assetmanager->SetApkAssets(apk_assets, invalidate_caches);
}
static void NativeSetConfiguration(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint mcc, jint mnc,
@@ -358,7 +353,7 @@
jint screen_height, jint smallest_screen_width_dp,
jint screen_width_dp, jint screen_height_dp, jint screen_layout,
jint ui_mode, jint color_mode, jint grammatical_gender,
- jint major_version, jboolean force_refresh) {
+ jint major_version) {
ATRACE_NAME("AssetManager::SetConfiguration");
const jsize locale_count = (locales == NULL) ? 0 : env->GetArrayLength(locales);
@@ -418,7 +413,7 @@
}
auto assetmanager = LockAndStartAssetManager(ptr);
- assetmanager->SetConfigurations(std::move(configs), force_refresh != JNI_FALSE);
+ assetmanager->SetConfigurations(configs);
assetmanager->SetDefaultLocale(default_locale_int);
}
@@ -1527,8 +1522,8 @@
// AssetManager setup methods.
{"nativeCreate", "()J", (void*)NativeCreate},
{"nativeDestroy", "(J)V", (void*)NativeDestroy},
- {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;ZZ)V", (void*)NativeSetApkAssets},
- {"nativeSetConfiguration", "(JIILjava/lang/String;[Ljava/lang/String;IIIIIIIIIIIIIIIIZ)V",
+ {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets},
+ {"nativeSetConfiguration", "(JIILjava/lang/String;[Ljava/lang/String;IIIIIIIIIIIIIIII)V",
(void*)NativeSetConfiguration},
{"nativeGetAssignedPackageIdentifiers", "(JZZ)Landroid/util/SparseArray;",
(void*)NativeGetAssignedPackageIdentifiers},
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index c65794e..b900fa6 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -22,7 +22,6 @@
per-file android/hardware/location/context_hub_info.proto = file:/services/core/java/com/android/server/location/contexthub/OWNERS
# Biometrics
-jaggies@google.com
jbolinger@google.com
# Launcher
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index c62e536..4fc9b40 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -144,6 +144,7 @@
optional SettingProto long_press_home_enabled = 11 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto search_press_hold_nav_handle_enabled = 12 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto search_long_press_home_enabled = 13 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto visual_query_accessibility_detection_enabled = 14 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Assist assist = 7;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a425bb0..72340e5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -835,6 +835,7 @@
<!-- Added in V -->
<protected-broadcast android:name="android.intent.action.PROFILE_AVAILABLE" />
<protected-broadcast android:name="android.intent.action.PROFILE_UNAVAILABLE" />
+ <protected-broadcast android:name="android.app.action.CONSOLIDATED_NOTIFICATION_POLICY_CHANGED" />
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
@@ -3799,6 +3800,13 @@
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION"
android:protectionLevel="internal|role" />
+ <!-- Allows an application to set policy related to subscriptions downloaded by an admin.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ @FlaggedApi("android.app.admin.flags.esim_management_enabled") -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS"
+ android:protectionLevel="internal|role" />
+
<!-- Allows an application to set device policies outside the current user
that are critical for securing data within the current user.
<p>Holding this permission allows the use of other held MANAGE_DEVICE_POLICY_*
@@ -3829,6 +3837,7 @@
@hide This is not a third-party API (intended for OEMs and system apps). -->
<permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES"
android:protectionLevel="signature|installer" />
+ <uses-permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES" />
<!-- @SystemApi @hide Allows an application to set a device owner on retail demo devices.-->
<permission android:name="android.permission.PROVISION_DEMO_DEVICE"
@@ -5921,7 +5930,7 @@
<permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS"
android:protectionLevel="signature|privileged" />
- <!-- Allows an application to collect usage infomation about brightness slider changes.
+ <!-- Allows an application to collect usage information about brightness slider changes.
<p>Not for use by third-party applications.</p>
@hide
@SystemApi
@@ -7033,12 +7042,16 @@
<!-- Allows the holder to read blocked numbers. See
{@link android.provider.BlockedNumberContract}.
+ @SystemApi
+ @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")
@hide -->
<permission android:name="android.permission.READ_BLOCKED_NUMBERS"
android:protectionLevel="signature" />
<!-- Allows the holder to write blocked numbers. See
{@link android.provider.BlockedNumberContract}.
+ @SystemApi
+ @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")
@hide -->
<permission android:name="android.permission.WRITE_BLOCKED_NUMBERS"
android:protectionLevel="signature" />
diff --git a/core/res/OWNERS b/core/res/OWNERS
index 332ad2a..6924248 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -8,7 +8,6 @@
hackbod@android.com
hackbod@google.com
ilyamaty@google.com
-jaggies@google.com
jbolinger@google.com
jsharkey@android.com
jsharkey@google.com
diff --git a/core/res/res/layout/app_perms_summary.xml b/core/res/res/layout/app_perms_summary.xml
index b8d93ac..509b988 100644
--- a/core/res/res/layout/app_perms_summary.xml
+++ b/core/res/res/layout/app_perms_summary.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
-<!-- Describes permission item consisting of a group name and the list of permisisons under the group -->
+<!-- Describes permission item consisting of a group name and the list of permissions under the group -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 6803a07..4ee03de 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3643,6 +3643,18 @@
<p> The default value is 40dp for {@link android.widget.TextView} and
{@link android.widget.EditText}, and 0dp for all other views. -->
<attr name="handwritingBoundsOffsetBottom" format="dimension" />
+
+ <!-- Sets whether this view renders sensitive content. -->
+ <!-- @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") -->
+ <attr name="contentSensitivity">
+ <!-- Let the Android System use its heuristics to determine if the view renders
+ sensitive content. -->
+ <enum name="auto" value="0" />
+ <!-- This view renders sensitive content. -->
+ <enum name="sensitive" value="0x1" />
+ <!-- This view doesn't render sensitive content. -->
+ <enum name="notSensitive" value="0x2" />
+ </attr>
</declare-styleable>
<!-- Attributes that can be assigned to a tag for a particular View. -->
@@ -3968,6 +3980,26 @@
{@link android.inputmethodservice.InputMethodService#onFinishInput()}.
-->
<attr name="supportsStylusHandwriting" format="boolean" />
+ <!-- Specifies whether the IME supports connectionless stylus handwriting sessions. A
+ connectionless session differs from a regular session in that the IME does not use an
+ input connection to communicate with a text editor. Instead, the IME directly returns
+ recognised handwritten text via an {@link
+ android.inputmethodservice.InputMethodService} handwriting lifecycle API.
+
+ <p>If the IME supports connectionless sessions, apps or framework may start a
+ connectionless session when a stylus motion event sequence begins. {@link
+ android.inputmethodservice.InputMethodService#onStartConnectionlessStylusHandwriting}
+ is called. If the IME is ready for stylus input, it should return {code true} to start
+ the basic mode session. As in the regular session, the IME will receive stylus motion
+ events to the stylus handwriting window and should render ink to a view in this window.
+ When the user has stopped handwriting, the IME should end the session and deliver the
+ result by calling {@link
+ android.inputmethodservice.InputMethodService#finishConnectionlessStylusHandwriting}.
+
+ The default value is {code false}. If {code true}, {@link
+ android.R.attr#supportsStylusHandwriting} should also be {code true}.
+ -->
+ <attr name="supportsConnectionlessStylusHandwriting" format="boolean" />
<!-- Class name of an activity that allows the user to modify the stylus handwriting
settings for this service -->
<attr name="stylusHandwritingSettingsActivity" format="string" />
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 78ce2d9..104b7cd 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -58,6 +58,12 @@
<integer name="auto_data_switch_availability_stability_time_threshold_millis">10000</integer>
<java-symbol type="integer" name="auto_data_switch_availability_stability_time_threshold_millis" />
+ <!-- Define the bar of considering the RAT and signal strength advantage of a subscription to be
+ stable in milliseconds, where 0 means immediate switch, and negative milliseconds indicates the
+ switch base on RAT and signal strength feature is disabled.-->
+ <integer name="auto_data_switch_performance_stability_time_threshold_millis">120000</integer>
+ <java-symbol type="integer" name="auto_data_switch_performance_stability_time_threshold_millis" />
+
<!-- Define the maximum retry times when a validation for switching failed.-->
<integer name="auto_data_switch_validation_max_retry">7</integer>
<java-symbol type="integer" name="auto_data_switch_validation_max_retry" />
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 58b2c61..dcb6bb0 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -155,6 +155,10 @@
<public name="languageSettingsActivity"/>
<!-- @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") -->
<public name="useLocalePreferredLineHeightForMinimum"/>
+ <!-- @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") -->
+ <public name="contentSensitivity" />
+ <!-- @FlaggedApi("android.view.inputmethod.connectionless_handwriting") -->
+ <public name="supportsConnectionlessStylusHandwriting" />
</staging-public-group>
<staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
index 5aace81..e4cf7ac 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
@@ -286,7 +286,7 @@
int scanStatus = mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false);
verify(mTunerMock).seek(/* directionDown= */ true, /* skipSubChannel= */ false);
- assertWithMessage("Status for scaning")
+ assertWithMessage("Status for scanning")
.that(scanStatus).isEqualTo(RadioManager.STATUS_OK);
verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO);
}
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
index 0806fa0..db95d7a 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
@@ -99,7 +99,7 @@
}
}
- public int getSoftApInterations() {
+ public int getSoftApIterations() {
return mSoftApIterations;
}
diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index e1bcd4a..936f4d7 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -279,5 +279,204 @@
}
}
+ @Test
+ public void testCalculateBoundingRects_noBoundingRects_createsSingleRect() {
+ mSource.setFrame(new Rect(0, 0, 1000, 100));
+ mSource.setBoundingRects(null);
+
+ final Rect[] rects = mSource.calculateBoundingRects(new Rect(0, 0, 1000, 1000), false);
+
+ assertEquals(1, rects.length);
+ assertEquals(new Rect(0, 0, 1000, 100), rects[0]);
+ }
+
+ @Test
+ public void testCalculateBoundingRects_noBoundingRectsAndLargerFrame_singleRectFitsRelFrame() {
+ mSource.setFrame(new Rect(0, 0, 1000, 100));
+ mSource.setBoundingRects(null);
+
+ final Rect[] rects = mSource.calculateBoundingRects(new Rect(0, 0, 500, 1000), false);
+
+ assertEquals(1, rects.length);
+ assertEquals(new Rect(0, 0, 500, 100), rects[0]);
+ }
+
+ @Test
+ public void testCalculateBoundingRects_frameAtOrigin_resultRelativeToRelFrame() {
+ mSource.setFrame(new Rect(0, 0, 1000, 100));
+ mSource.setBoundingRects(new Rect[]{
+ new Rect(0, 0, 300, 100),
+ new Rect(800, 0, 1000, 100),
+ });
+
+ final Rect[] rects = mSource.calculateBoundingRects(new Rect(0, 0, 1000, 1000), false);
+
+ assertEquals(2, rects.length);
+ assertEquals(new Rect(0, 0, 300, 100), rects[0]);
+ assertEquals(new Rect(800, 0, 1000, 100), rects[1]);
+ }
+
+ @Test
+ public void testCalculateBoundingRects_notAtOrigin_resultRelativeToRelFrame() {
+ mSource.setFrame(new Rect(100, 100, 1100, 200));
+ mSource.setBoundingRects(new Rect[]{
+ new Rect(0, 0, 300, 100), // 300x100, aligned left
+ new Rect(800, 0, 1000, 100), // 200x100, aligned right
+ });
+
+ final Rect[] rects = mSource.calculateBoundingRects(new Rect(100, 100, 1100, 1100), false);
+
+ assertEquals(2, rects.length);
+ assertEquals(new Rect(0, 0, 300, 100), rects[0]);
+ assertEquals(new Rect(800, 0, 1000, 100), rects[1]);
+ }
+
+ @Test
+ public void testCalculateBoundingRects_boundingRectFullyInsideFrameInWindow() {
+ mSource.setFrame(new Rect(0, 0, 1000, 100));
+ mSource.setBoundingRects(new Rect[]{
+ new Rect(100, 0, 400, 100), // Inside |frame| and |relativeFrame|.
+ });
+
+ final Rect[] rects = mSource.calculateBoundingRects(new Rect(0, 0, 500, 100), false);
+
+ assertEquals(1, rects.length);
+ assertEquals(new Rect(100, 0, 400, 100), rects[0]);
+ }
+
+ @Test
+ public void testCalculateBoundingRects_boundingRectOutsideFrameInWindow_dropped() {
+ mSource.setFrame(new Rect(0, 0, 1000, 100));
+ mSource.setBoundingRects(new Rect[]{
+ new Rect(700, 0, 1000, 100), // Inside |frame|, but outside |relativeFrame|.
+ });
+
+ final Rect[] rects = mSource.calculateBoundingRects(new Rect(0, 0, 500, 100), false);
+
+ assertEquals(0, rects.length);
+ }
+
+ @Test
+ public void testCalculateBoundingRects_boundingRectPartlyOutsideFrameInWindow_cropped() {
+ mSource.setFrame(new Rect(0, 0, 1000, 100));
+ mSource.setBoundingRects(new Rect[]{
+ new Rect(400, 0, 600, 100), // Inside |frame|, and only half inside |relativeFrame|.
+ });
+
+ final Rect[] rects = mSource.calculateBoundingRects(new Rect(0, 0, 500, 100), false);
+
+ assertEquals(1, rects.length);
+ assertEquals(new Rect(400, 0, 500, 100), rects[0]);
+ }
+
+ @Test
+ public void testCalculateBoundingRects_framesNotAtOrigin_resultRelativeToWindowFrame() {
+ mSource.setFrame(new Rect(100, 100, 1100, 200));
+ mSource.setBoundingRects(new Rect[]{
+ new Rect(0, 0, 300, 100), // 300x100 aligned to left.
+ new Rect(800, 0, 1000, 100) // 200x100 align to right.
+ });
+
+ final Rect[] rects = mSource.calculateBoundingRects(new Rect(100, 100, 1100, 1100), false);
+
+ assertEquals(2, rects.length);
+ assertEquals(new Rect(0, 0, 300, 100), rects[0]);
+ assertEquals(new Rect(800, 0, 1000, 100), rects[1]);
+ }
+
+ @Test
+ public void testCalculateBoundingRects_captionBar() {
+ mCaptionSource.setFrame(new Rect(0, 0, 1000, 100));
+ mCaptionSource.setBoundingRects(new Rect[]{
+ new Rect(0, 0, 200, 100), // 200x100, aligned left.
+ new Rect(800, 0, 1000, 100) // 200x100, aligned right.
+ });
+
+ final Rect[] rects = mCaptionSource.calculateBoundingRects(
+ new Rect(0, 0, 1000, 1000), false);
+
+ assertEquals(2, rects.length);
+ assertEquals(new Rect(0, 0, 200, 100), rects[0]);
+ assertEquals(new Rect(800, 0, 1000, 100), rects[1]);
+ }
+
+ @Test
+ public void testCalculateBoundingRects_captionBarFrameMisaligned_rectsFixedToTop() {
+ mCaptionSource.setFrame(new Rect(500, 500, 1500, 600));
+ mCaptionSource.setBoundingRects(new Rect[]{
+ new Rect(0, 0, 100, 100), // 100x100, aligned to left/top of frame
+ });
+
+ final Rect[] rects = mCaptionSource.calculateBoundingRects(
+ new Rect(495, 495, 1500, 1500), false);
+
+ assertEquals(1, rects.length);
+ // rect should be aligned to the top of relative frame, as if the caption frame had been
+ // corrected to be aligned at the top.
+ assertEquals(new Rect(0, 0, 100, 100), rects[0]);
+ }
+
+ @Test
+ public void testCalculateBoundingRects_imeCaptionBarFrameMisaligned_rectsFixedToBottom() {
+ mImeCaptionSource.setFrame(new Rect(500, 1400, 1500, 1500));
+ mImeCaptionSource.setBoundingRects(new Rect[]{
+ new Rect(0, 0, 100, 100), // 100x100, aligned to left/top of frame
+ });
+
+ final Rect[] rects = mImeCaptionSource.calculateBoundingRects(
+ new Rect(495, 495, 1500, 1500), false);
+
+ assertEquals(1, rects.length);
+ // rect should be aligned to the bottom of relative frame, as if the ime caption frame had
+ // been corrected to be aligned at the top.
+ assertEquals(new Rect(0, 905, 100, 1005), rects[0]);
+ }
+
+ @Test
+ public void testCalculateBoundingRects_imeCaptionBar() {
+ mImeCaptionSource.setFrame(new Rect(0, 900, 1000, 1000)); // Frame at the bottom.
+ mImeCaptionSource.setBoundingRects(new Rect[]{
+ new Rect(0, 0, 200, 100), // 200x100, aligned left.
+ });
+
+ final Rect[] rects = mImeCaptionSource.calculateBoundingRects(
+ new Rect(0, 0, 1000, 1000), false);
+
+ assertEquals(1, rects.length);
+ assertEquals(new Rect(0, 900, 200, 1000), rects[0]);
+ }
+
+ @Test
+ public void testCalculateBoundingRects_invisible() {
+ mSource.setFrame(new Rect(0, 0, 1000, 100));
+ mSource.setBoundingRects(new Rect[]{
+ new Rect(0, 0, 300, 100),
+ new Rect(800, 0, 1000, 100),
+ });
+ mSource.setVisible(false);
+
+ final Rect[] rects = mSource.calculateBoundingRects(new Rect(0, 0, 1000, 1000),
+ false /* ignoreVisibility */);
+
+ assertEquals(0, rects.length);
+ }
+
+ @Test
+ public void testCalculateBoundingRects_ignoreVisibility() {
+ mSource.setFrame(new Rect(0, 0, 1000, 100));
+ mSource.setBoundingRects(new Rect[]{
+ new Rect(0, 0, 300, 100),
+ new Rect(800, 0, 1000, 100),
+ });
+ mSource.setVisible(false);
+
+ final Rect[] rects = mSource.calculateBoundingRects(new Rect(0, 0, 1000, 1000),
+ true /* ignoreVisibility */);
+
+ assertEquals(2, rects.length);
+ assertEquals(new Rect(0, 0, 300, 100), rects[0]);
+ assertEquals(new Rect(800, 0, 1000, 100), rects[1]);
+ }
+
// Parcel and equals already tested via InsetsStateTest
}
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 672875a..16bd20a 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -63,6 +63,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.List;
+
/**
* Tests for {@link InsetsState}.
*
@@ -88,6 +90,8 @@
null /* owner */, 1 /* index */, navigationBars());
private static final int ID_BOTTOM_GESTURES = InsetsSource.createId(
null /* owner */, 0 /* index */, systemGestures());
+ private static final int ID_EXTRA_CAPTION_BAR = InsetsSource.createId(
+ null /* owner */, 2 /* index */, captionBar());
private final InsetsState mState = new InsetsState();
private final InsetsState mState2 = new InsetsState();
@@ -420,9 +424,11 @@
public void testEquals_visibility() {
mState.getOrCreateSource(ID_IME, ime())
.setFrame(new Rect(0, 0, 100, 100))
+ .setBoundingRects(new Rect[]{ new Rect(0, 0, 10, 10) })
.setVisible(true);
mState2.getOrCreateSource(ID_IME, ime())
- .setFrame(new Rect(0, 0, 100, 100));
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setBoundingRects(new Rect[]{ new Rect(0, 0, 10, 10) });
assertNotEqualsAndHashCode();
}
@@ -441,6 +447,30 @@
}
@Test
+ public void testEquals_sameBoundingRects() {
+ mState.getOrCreateSource(ID_CAPTION_BAR, captionBar())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setBoundingRects(new Rect[]{ new Rect(0, 0, 10, 10) })
+ .setVisible(true);
+ mState2.getOrCreateSource(ID_CAPTION_BAR, captionBar())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setBoundingRects(new Rect[]{ new Rect(0, 0, 10, 10) });
+ assertEqualsAndHashCode();
+ }
+
+ @Test
+ public void testEquals_differentBoundingRects() {
+ mState.getOrCreateSource(ID_CAPTION_BAR, captionBar())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setBoundingRects(new Rect[]{ new Rect(0, 0, 10, 10) })
+ .setVisible(true);
+ mState2.getOrCreateSource(ID_CAPTION_BAR, captionBar())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setBoundingRects(new Rect[]{ new Rect(0, 0, 20, 20) });
+ assertNotEqualsAndHashCode();
+ }
+
+ @Test
public void testEquals_samePrivacyIndicator() {
Rect one = new Rect(0, 1, 2, 3);
Rect two = new Rect(4, 5, 6, 7);
@@ -734,4 +764,94 @@
assertEquals(1, onIdNotFoundInState2Called[0]); // 1000.
assertEquals(1, onFinishCalled[0]);
}
+
+ @Test
+ public void testCalculateBoundingRects() {
+ mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 1000, 100))
+ .setBoundingRects(null)
+ .setVisible(true);
+ mState.getOrCreateSource(ID_CAPTION_BAR, captionBar())
+ .setFrame(new Rect(0, 0, 1000, 100))
+ .setBoundingRects(new Rect[]{
+ new Rect(0, 0, 200, 100),
+ new Rect(800, 0, 1000, 100)
+ })
+ .setVisible(true);
+ SparseIntArray typeSideMap = new SparseIntArray();
+
+ WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 1000, 1000), null, false,
+ SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
+ typeSideMap);
+
+ assertEquals(
+ List.of(new Rect(0, 0, 1000, 100)),
+ insets.getBoundingRects(Type.statusBars())
+ );
+ assertEquals(
+ List.of(
+ new Rect(0, 0, 200, 100),
+ new Rect(800, 0, 1000, 100)
+ ),
+ insets.getBoundingRects(Type.captionBar())
+ );
+ }
+
+ @Test
+ public void testCalculateBoundingRects_multipleSourcesOfSameType_concatenated() {
+ mState.getOrCreateSource(ID_CAPTION_BAR, captionBar())
+ .setFrame(new Rect(0, 0, 1000, 100))
+ .setBoundingRects(new Rect[]{new Rect(0, 0, 200, 100)})
+ .setVisible(true);
+ mState.getOrCreateSource(ID_EXTRA_CAPTION_BAR, captionBar())
+ .setFrame(new Rect(0, 0, 1000, 100))
+ .setBoundingRects(new Rect[]{new Rect(800, 0, 1000, 100)})
+ .setVisible(true);
+ SparseIntArray typeSideMap = new SparseIntArray();
+
+ WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 1000, 1000), null, false,
+ SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
+ typeSideMap);
+
+ final List<Rect> expected = List.of(
+ new Rect(0, 0, 200, 100),
+ new Rect(800, 0, 1000, 100)
+ );
+ final List<Rect> actual = insets.getBoundingRects(captionBar());
+ assertEquals(expected.size(), actual.size());
+
+ // Order does not matter.
+ assertTrue(actual.containsAll(expected));
+ }
+
+ @Test
+ public void testCalculateBoundingRects_captionBar_reportedAsSysGesturesAndTappableElement() {
+ mState.getOrCreateSource(ID_CAPTION_BAR, captionBar())
+ .setFrame(new Rect(0, 0, 1000, 100))
+ .setBoundingRects(new Rect[]{new Rect(0, 0, 200, 100)})
+ .setVisible(true);
+ SparseIntArray typeSideMap = new SparseIntArray();
+
+ WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 1000, 1000), null, false,
+ SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
+ typeSideMap);
+
+ assertEquals(
+ List.of(new Rect(0, 0, 200, 100)),
+ insets.getBoundingRects(Type.captionBar())
+ );
+ assertEquals(
+ List.of(new Rect(0, 0, 200, 100)),
+ insets.getBoundingRects(Type.systemGestures())
+ );
+ assertEquals(
+ List.of(new Rect(0, 0, 200, 100)),
+ insets.getBoundingRects(Type.mandatorySystemGestures())
+ );
+ assertEquals(
+ List.of(new Rect(0, 0, 200, 100)),
+ insets.getBoundingRects(Type.tappableElement())
+ );
+
+ }
}
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index 69abf5f..ab4543c 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -41,14 +41,14 @@
public void systemWindowInsets_afterConsuming_isConsumed() {
assertTrue(new WindowInsets(WindowInsets.createCompatTypeMap(new Rect(1, 2, 3, 4)), null,
null, false, 0, 0, null, null, null, null,
- WindowInsets.Type.systemBars(), false)
+ WindowInsets.Type.systemBars(), false, null, null, 0, 0)
.consumeSystemWindowInsets().isConsumed());
}
@Test
public void multiNullConstructor_isConsumed() {
assertTrue(new WindowInsets(null, null, null, false, 0, 0, null, null, null, null,
- WindowInsets.Type.systemBars(), false).isConsumed());
+ WindowInsets.Type.systemBars(), false, null, null, 0, 0).isConsumed());
}
@Test
@@ -65,7 +65,7 @@
WindowInsets.assignCompatInsets(insets, new Rect(0, 0, 0, 0));
WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, 0,
0, null, null, null, DisplayShape.NONE, systemBars(),
- true /* compatIgnoreVisibility */);
+ true /* compatIgnoreVisibility */, null, null, 0, 0);
assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets());
}
}
diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
index 84dd274..8d66cfc 100644
--- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
@@ -169,7 +169,8 @@
private WindowInsets insetsWith(Insets content, DisplayCutout cutout) {
return new WindowInsets(WindowInsets.createCompatTypeMap(content.toRect()), null, null,
- false, 0, 0, cutout, null, null, null, WindowInsets.Type.systemBars(), false);
+ false, 0, 0, cutout, null, null, null, WindowInsets.Type.systemBars(), false,
+ null, null, 0, 0);
}
private ViewGroup createViewGroupWithId(int id) {
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java
index 08333ec..bf9221a 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java
@@ -31,6 +31,7 @@
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.widget.LinearLayout;
+import android.widget.TextView;
import android.widget.flags.Flags;
import androidx.test.InstrumentationRegistry;
@@ -73,7 +74,7 @@
private static final int[] LAYOUT_PARAMS = {MATCH_PARENT, WRAP_CONTENT, 0, 50};
private static final int[] CHILD_WEIGHTS = {0, 1};
-
+ private static final int[] CHILD_MARGINS = {0, 10, -10};
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -84,35 +85,96 @@
mContext = InstrumentationRegistry.getTargetContext();
}
+
@Test
public void test() throws Throwable {
+ final List<View> controlChildren =
+ new ArrayList<>();
+ final List<View> testChildren =
+ new ArrayList<>();
+
+ final View controlChild1 = buildChildView();
+ final View controlChild2 = buildChildView();
+ controlChildren.add(controlChild1);
+ controlChildren.add(controlChild2);
+
+ final View testChild1 = buildChildView();
+ final View testChild2 = buildChildView();
+ testChildren.add(testChild1);
+ testChildren.add(testChild2);
+
+ final LinearLayout controlContainer = buildLayout(false, controlChildren);
+
+ final LinearLayout testContainer = buildLayout(true, testChildren);
+
+ final LinearLayout.LayoutParams firstChildLayoutParams = new LinearLayout.LayoutParams(0,
+ 0);
+ final LinearLayout.LayoutParams secondChildLayoutParams = new LinearLayout.LayoutParams(0,
+ 0);
+ controlChild1.setLayoutParams(firstChildLayoutParams);
+ controlChild2.setLayoutParams(secondChildLayoutParams);
+ testChild1.setLayoutParams(firstChildLayoutParams);
+ testChild2.setLayoutParams(secondChildLayoutParams);
+
for (int orientation : ORIENTATIONS) {
- for (int widthSpec : MEASURE_SPECS) {
- for (int heightSpec : MEASURE_SPECS) {
- for (int firstChildGravity : GRAVITIES) {
- for (int secondChildGravity : GRAVITIES) {
- for (int firstChildLayoutWidth : LAYOUT_PARAMS) {
- for (int firstChildLayoutHeight : LAYOUT_PARAMS) {
- for (int secondChildLayoutWidth : LAYOUT_PARAMS) {
- for (int secondChildLayoutHeight : LAYOUT_PARAMS) {
+ controlContainer.setOrientation(orientation);
+ testContainer.setOrientation(orientation);
+
+ for (int firstChildLayoutWidth : LAYOUT_PARAMS) {
+ firstChildLayoutParams.width = firstChildLayoutWidth;
+ for (int firstChildLayoutHeight : LAYOUT_PARAMS) {
+ firstChildLayoutParams.height = firstChildLayoutHeight;
+
+ for (int secondChildLayoutWidth : LAYOUT_PARAMS) {
+ secondChildLayoutParams.width = secondChildLayoutWidth;
+ for (int secondChildLayoutHeight : LAYOUT_PARAMS) {
+ secondChildLayoutParams.height = secondChildLayoutHeight;
+
+ for (int firstChildMargin : CHILD_MARGINS) {
+ firstChildLayoutParams.setMargins(firstChildMargin,
+ firstChildMargin, firstChildMargin, firstChildMargin);
+ for (int secondChildMargin : CHILD_MARGINS) {
+ secondChildLayoutParams.setMargins(secondChildMargin,
+ secondChildMargin, secondChildMargin,
+ secondChildMargin);
+
+ for (int firstChildGravity : GRAVITIES) {
+ firstChildLayoutParams.gravity = firstChildGravity;
+ for (int secondChildGravity : GRAVITIES) {
+ secondChildLayoutParams.gravity = secondChildGravity;
+
for (int firstChildWeight : CHILD_WEIGHTS) {
+ firstChildLayoutParams.weight = firstChildWeight;
for (int secondChildWeight : CHILD_WEIGHTS) {
- executeTest(/*testSpec =*/createTestSpec(
- orientation,
- widthSpec, heightSpec,
- firstChildLayoutWidth,
- firstChildLayoutHeight,
- secondChildLayoutWidth,
- secondChildLayoutHeight,
- firstChildGravity,
- secondChildGravity,
- firstChildWeight,
- secondChildWeight));
+ secondChildLayoutParams.weight =
+ secondChildWeight;
+
+ for (int widthSpec : MEASURE_SPECS) {
+ for (int heightSpec : MEASURE_SPECS) {
+ executeTest(controlContainer,
+ testContainer,
+ createTestSpec(
+ orientation,
+ widthSpec, heightSpec,
+ firstChildLayoutWidth,
+ firstChildLayoutHeight,
+ secondChildLayoutWidth,
+ secondChildLayoutHeight,
+ firstChildGravity,
+ secondChildGravity,
+ firstChildWeight,
+ secondChildWeight,
+ firstChildMargin,
+ secondChildMargin)
+ );
+ }
+ }
}
}
}
}
}
+
}
}
}
@@ -121,47 +183,8 @@
}
}
- private void executeTest(TestSpec testSpec) {
- // GIVEN
- final List<View> controlChildren =
- new ArrayList<>();
- final List<View> testChildren =
- new ArrayList<>();
-
- controlChildren.add(
- buildChildView(
- testSpec.mFirstChildLayoutWidth,
- testSpec.mFirstChildLayoutHeight,
- testSpec.mFirstChildGravity,
- testSpec.mFirstChildWeight));
- controlChildren.add(
- buildChildView(
- testSpec.mSecondChildLayoutWidth,
- testSpec.mSecondChildLayoutHeight,
- testSpec.mSecondChildGravity,
- testSpec.mSecondChildWeight));
-
- testChildren.add(
- buildChildView(
- testSpec.mFirstChildLayoutWidth,
- testSpec.mFirstChildLayoutHeight,
- testSpec.mFirstChildGravity,
- testSpec.mFirstChildWeight));
- testChildren.add(
- buildChildView(
- testSpec.mSecondChildLayoutWidth,
- testSpec.mSecondChildLayoutHeight,
- testSpec.mSecondChildGravity,
- testSpec.mSecondChildWeight));
-
- final LinearLayout controlContainer = buildLayout(false,
- testSpec.mOrientation,
- controlChildren);
-
- final LinearLayout testContainer = buildLayout(true,
- testSpec.mOrientation,
- testChildren);
-
+ private void executeTest(LinearLayout controlContainer, LinearLayout testContainer,
+ TestSpec testSpec) {
// WHEN
controlContainer.measure(testSpec.mWidthSpec, testSpec.mHeightSpec);
testContainer.measure(testSpec.mWidthSpec, testSpec.mHeightSpec);
@@ -171,6 +194,7 @@
assertLayoutsEqual("Test Case:" + testSpec, controlContainer, testContainer);
}
+
private static class TestSpec {
private final int mOrientation;
private final int mWidthSpec;
@@ -183,6 +207,8 @@
private final int mSecondChildGravity;
private final int mFirstChildWeight;
private final int mSecondChildWeight;
+ private final int mFirstChildMargin;
+ private final int mSecondChildMargin;
TestSpec(
int orientation,
@@ -195,7 +221,9 @@
int firstChildGravity,
int secondChildGravity,
int firstChildWeight,
- int secondChildWeight) {
+ int secondChildWeight,
+ int firstChildMargin,
+ int secondChildMargin) {
mOrientation = orientation;
mWidthSpec = widthSpec;
mHeightSpec = heightSpec;
@@ -207,6 +235,8 @@
mSecondChildGravity = secondChildGravity;
mFirstChildWeight = firstChildWeight;
mSecondChildWeight = secondChildWeight;
+ mFirstChildMargin = firstChildMargin;
+ mSecondChildMargin = secondChildMargin;
}
@Override
@@ -223,6 +253,8 @@
+ ", mSecondChildGravity=" + mSecondChildGravity
+ ", mFirstChildWeight=" + mFirstChildWeight
+ ", mSecondChildWeight=" + mSecondChildWeight
+ + ", mFirstChildMargin=" + mFirstChildMargin
+ + ", mSecondChildMargin=" + mSecondChildMargin
+ '}';
}
@@ -246,15 +278,13 @@
}
}
- private LinearLayout buildLayout(boolean isNotificationOptimized,
- @LinearLayout.OrientationMode int orientation, List<View> children) {
+ private LinearLayout buildLayout(boolean isNotificationOptimized, List<View> children) {
final LinearLayout linearLayout;
if (isNotificationOptimized) {
linearLayout = new NotificationOptimizedLinearLayout(mContext);
} else {
linearLayout = new LinearLayout(mContext);
}
- linearLayout.setOrientation(orientation);
for (int i = 0; i < children.size(); i++) {
linearLayout.addView(children.get(i));
}
@@ -262,7 +292,8 @@
}
private void assertLayoutsEqual(String testCase, View controlView, View testView) {
- mExpect.withMessage("MeasuredWidths are not equal. Test Case:" + testCase)
+ mExpect.withMessage(
+ "MeasuredWidths are not equal. Test Case:" + testCase)
.that(testView.getMeasuredWidth()).isEqualTo(controlView.getMeasuredWidth());
mExpect.withMessage("MeasuredHeights are not equal. Test Case:" + testCase)
.that(testView.getMeasuredHeight()).isEqualTo(controlView.getMeasuredHeight());
@@ -286,23 +317,12 @@
}
}
- private static class TestView extends View {
- TestView(Context context) {
- super(context);
- }
-
- @Override
- public int getBaseline() {
- return 5;
- }
- }
-
-
private TestSpec createTestSpec(int orientation,
int widthSpec, int heightSpec,
int firstChildLayoutWidth, int firstChildLayoutHeight, int secondChildLayoutWidth,
int secondChildLayoutHeight, int firstChildGravity, int secondChildGravity,
- int firstChildWeight, int secondChildWeight) {
+ int firstChildWeight, int secondChildWeight, int firstChildMargin,
+ int secondChildMargin) {
return new TestSpec(
orientation,
@@ -314,16 +334,16 @@
firstChildGravity,
secondChildGravity,
firstChildWeight,
- secondChildWeight);
+ secondChildWeight,
+ firstChildMargin,
+ secondChildMargin);
}
- private View buildChildView(int childLayoutWidth, int childLayoutHeight,
- int childGravity, int childWeight) {
- final View childView = new TestView(mContext);
- // Set desired size using LayoutParams
- LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(childLayoutWidth,
- childLayoutHeight, childWeight);
- params.gravity = childGravity;
+ private View buildChildView() {
+ final View childView = new TextView(mContext);
+ // this is initial value. We are going to mutate this layout params during the test.
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(MATCH_PARENT,
+ WRAP_CONTENT);
childView.setLayoutParams(params);
return childView;
}
diff --git a/core/tests/utiltests/src/com/android/internal/util/FastXmlSerializerTest.java b/core/tests/utiltests/src/com/android/internal/util/FastXmlSerializerTest.java
index 7723d58..f91172d 100644
--- a/core/tests/utiltests/src/com/android/internal/util/FastXmlSerializerTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/FastXmlSerializerTest.java
@@ -45,7 +45,6 @@
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
-@IgnoreUnderRavenwood(blockedBy = Xml.class)
public class FastXmlSerializerTest {
private static final String TAG = "FastXmlSerializerTest";
@@ -146,6 +145,7 @@
@Test
@LargeTest
+ @IgnoreUnderRavenwood(reason = "Long test runtime")
public void testAllCharacters() throws Exception {
boolean ok = true;
for (int i = 0; i < 0xffff; i++) {
diff --git a/data/etc/com.android.launcher3.xml b/data/etc/com.android.launcher3.xml
index 5616d1d..47e2e38 100644
--- a/data/etc/com.android.launcher3.xml
+++ b/data/etc/com.android.launcher3.xml
@@ -26,5 +26,6 @@
<permission name="android.permission.STATUS_BAR"/>
<permission name="android.permission.STOP_APP_SWITCHES"/>
<permission name="android.permission.ACCESS_SHORTCUTS"/>
+ <permission name="android.permission.ACCESS_HIDDEN_PROFILES_FULL"/>
</privapp-permissions>
</permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index d8713f7a..fdb5208 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -575,6 +575,9 @@
<permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
<!-- Permissions required for CTS test - CtsContactKeysProviderPrivilegedApp -->
<permission name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"/>
+ <!-- Permission required for CTS test BlockedNumberContractTest -->
+ <permission name="android.permission.WRITE_BLOCKED_NUMBERS" />
+ <permission name="android.permission.READ_BLOCKED_NUMBERS" />
<!-- Permission required for CTS test - PackageManagerTest -->
<permission name="android.permission.DOMAIN_VERIFICATION_AGENT"/>
</privapp-permissions>
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index 1da8e18..d915b74 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -25,10 +25,13 @@
import android.content.res.Resources;
import android.os.Build;
import android.os.Trace;
+import android.system.OsConstants;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
+import libcore.io.IoBridge;
+
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
@@ -523,19 +526,19 @@
public static Bitmap decodeFile(String pathName, Options opts) {
validate(opts);
Bitmap bm = null;
- InputStream stream = null;
+ FileDescriptor fd = null;
try {
- stream = new FileInputStream(pathName);
- bm = decodeStream(stream, null, opts);
+ fd = IoBridge.open(pathName, OsConstants.O_RDONLY);
+ bm = decodeFileDescriptor(fd, null, opts);
} catch (Exception e) {
/* do nothing.
If the exception happened on open, bm will be null.
*/
- Log.e("BitmapFactory", "Unable to decode stream: " + e);
+ Log.e("BitmapFactory", "Unable to decode file: " + e);
} finally {
- if (stream != null) {
+ if (fd != null) {
try {
- stream.close();
+ IoBridge.closeAndSignalBlockedThreads(fd);
} catch (IOException e) {
// do nothing here
}
diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java
index e6de597..9159a00 100644
--- a/graphics/java/android/view/PixelCopy.java
+++ b/graphics/java/android/view/PixelCopy.java
@@ -96,7 +96,7 @@
*
* The contents of the source will be scaled to fit exactly inside the bitmap.
* The pixel format of the source buffer will be converted, as part of the copy,
- * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+ * to fit the bitmap's {@link Bitmap.Config}. The most recently queued buffer
* in the SurfaceView's Surface will be used as the source of the copy.
*
* @param source The source from which to copy
@@ -117,7 +117,7 @@
*
* The contents of the source will be scaled to fit exactly inside the bitmap.
* The pixel format of the source buffer will be converted, as part of the copy,
- * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+ * to fit the bitmap's {@link Bitmap.Config}. The most recently queued buffer
* in the SurfaceView's Surface will be used as the source of the copy.
*
* @param source The source from which to copy
@@ -143,7 +143,7 @@
*
* The contents of the source will be scaled to fit exactly inside the bitmap.
* The pixel format of the source buffer will be converted, as part of the copy,
- * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+ * to fit the bitmap's {@link Bitmap.Config}. The most recently queued buffer
* in the Surface will be used as the source of the copy.
*
* @param source The source from which to copy
@@ -164,7 +164,7 @@
*
* The contents of the source rect will be scaled to fit exactly inside the bitmap.
* The pixel format of the source buffer will be converted, as part of the copy,
- * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+ * to fit the bitmap's {@link Bitmap.Config}. The most recently queued buffer
* in the Surface will be used as the source of the copy.
*
* @param source The source from which to copy
@@ -201,7 +201,7 @@
*
* The contents of the source will be scaled to fit exactly inside the bitmap.
* The pixel format of the source buffer will be converted, as part of the copy,
- * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+ * to fit the bitmap's {@link Bitmap.Config}. The most recently queued buffer
* in the Window's Surface will be used as the source of the copy.
*
* Note: This is limited to being able to copy from Window's with a non-null
@@ -231,7 +231,7 @@
*
* The contents of the source rect will be scaled to fit exactly inside the bitmap.
* The pixel format of the source buffer will be converted, as part of the copy,
- * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+ * to fit the bitmap's {@link Bitmap.Config}. The most recently queued buffer
* in the Window's Surface will be used as the source of the copy.
*
* Note: This is limited to being able to copy from Window's with a non-null
diff --git a/keystore/aaid/aidl/android/security/keystore/IKeyAttestationApplicationIdProvider.aidl b/keystore/aaid/aidl/android/security/keystore/IKeyAttestationApplicationIdProvider.aidl
index c360cb8..cfc5980 100644
--- a/keystore/aaid/aidl/android/security/keystore/IKeyAttestationApplicationIdProvider.aidl
+++ b/keystore/aaid/aidl/android/security/keystore/IKeyAttestationApplicationIdProvider.aidl
@@ -20,8 +20,14 @@
/** @hide */
interface IKeyAttestationApplicationIdProvider {
+ const int ERROR_GET_ATTESTATION_APPLICATION_ID_FAILED = 1;
+
/**
* Provides information describing the possible applications identified by a UID.
+ *
+ * In case of not getting package ids from uid return
+ * {@link #ERROR_GET_ATTESTATION_APPLICATION_ID_FAILED} to the caller.
+ *
* @hide
*/
KeyAttestationApplicationId getKeyAttestationApplicationId(int uid);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index e872849..42c8d74 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -70,8 +70,8 @@
import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.KtProtoLog
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
+import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import java.io.PrintWriter
import java.util.concurrent.Executor
import java.util.function.Consumer
@@ -175,6 +175,12 @@
)
}
+ fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
+ toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
+ enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
+ dragToDesktopTransitionHandler.setOnTaskResizeAnimatorListener(listener)
+ }
+
/** Setter needed to avoid cyclic dependency. */
fun setSplitScreenController(controller: SplitScreenController) {
splitScreenController = controller
@@ -236,12 +242,11 @@
/** Move a task with given `taskId` to desktop */
fun moveToDesktop(
- decor: DesktopModeWindowDecoration,
taskId: Int,
wct: WindowContainerTransaction = WindowContainerTransaction()
) {
shellTaskOrganizer.getRunningTaskInfo(taskId)?.let {
- task -> moveToDesktop(decor, task, wct)
+ task -> moveToDesktop(task, wct)
}
}
@@ -283,7 +288,6 @@
* Move a task to desktop
*/
fun moveToDesktop(
- decor: DesktopModeWindowDecoration,
task: RunningTaskInfo,
wct: WindowContainerTransaction = WindowContainerTransaction()
) {
@@ -298,7 +302,7 @@
addMoveToDesktopChanges(wct, task)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- enterDesktopTaskTransitionHandler.moveToDesktop(wct, decor)
+ enterDesktopTaskTransitionHandler.moveToDesktop(wct)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
@@ -311,7 +315,6 @@
fun startDragToDesktop(
taskInfo: RunningTaskInfo,
dragToDesktopValueAnimator: MoveToDesktopAnimator,
- windowDecor: DesktopModeWindowDecoration
) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
@@ -320,8 +323,7 @@
)
dragToDesktopTransitionHandler.startDragToDesktopTransition(
taskInfo.taskId,
- dragToDesktopValueAnimator,
- windowDecor
+ dragToDesktopValueAnimator
)
}
@@ -522,7 +524,7 @@
}
/** Quick-resizes a desktop task, toggling between the stable bounds and the default bounds. */
- fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo, windowDecor: DesktopModeWindowDecoration) {
+ fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo) {
val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
val stableBounds = Rect()
@@ -543,11 +545,7 @@
val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- toggleResizeDesktopTaskTransitionHandler.startTransition(
- wct,
- taskInfo.taskId,
- windowDecor
- )
+ toggleResizeDesktopTaskTransitionHandler.startTransition(wct)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
@@ -558,11 +556,7 @@
*
* @param position the portion of the screen (RIGHT or LEFT) we want to snap the task to.
*/
- fun snapToHalfScreen(
- taskInfo: RunningTaskInfo,
- windowDecor: DesktopModeWindowDecoration,
- position: SnapPosition
- ) {
+ fun snapToHalfScreen(taskInfo: RunningTaskInfo, position: SnapPosition) {
val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
val stableBounds = Rect()
@@ -592,11 +586,7 @@
val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- toggleResizeDesktopTaskTransitionHandler.startTransition(
- wct,
- taskInfo.taskId,
- windowDecor
- )
+ toggleResizeDesktopTaskTransitionHandler.startTransition(wct)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 39610e3..af26e29 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -34,9 +34,9 @@
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
import com.android.wm.shell.transition.Transitions.TransitionHandler
import com.android.wm.shell.util.KtProtoLog
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator.Companion.DRAG_FREEFORM_SCALE
+import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import java.util.function.Supplier
/**
@@ -69,6 +69,7 @@
private var dragToDesktopStateListener: DragToDesktopStateListener? = null
private var splitScreenController: SplitScreenController? = null
private var transitionState: TransitionState? = null
+ private lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener
/** Whether a drag-to-desktop transition is in progress. */
val inProgress: Boolean
@@ -84,6 +85,10 @@
splitScreenController = controller
}
+ fun setOnTaskResizeAnimatorListener(listener: OnTaskResizeAnimationListener) {
+ onTaskResizeAnimationListener = listener
+ }
+
/**
* Starts a transition that performs a transient launch of Home so that Home is brought to the
* front while still keeping the currently focused task that is being dragged resumed. This
@@ -96,7 +101,6 @@
fun startDragToDesktopTransition(
taskId: Int,
dragToDesktopAnimator: MoveToDesktopAnimator,
- windowDecoration: DesktopModeWindowDecoration
) {
if (inProgress) {
KtProtoLog.v(
@@ -128,14 +132,12 @@
TransitionState.FromSplit(
draggedTaskId = taskId,
dragAnimator = dragToDesktopAnimator,
- windowDecoration = windowDecoration,
startTransitionToken = startTransitionToken
)
} else {
TransitionState.FromFullscreen(
draggedTaskId = taskId,
dragAnimator = dragToDesktopAnimator,
- windowDecoration = windowDecoration,
startTransitionToken = startTransitionToken
)
}
@@ -405,7 +407,7 @@
// Accept the merge by applying the merging transaction (applied by #showResizeVeil)
// and finish callback. Show the veil and position the task at the first frame before
// starting the final animation.
- state.windowDecoration.showResizeVeil(t, animStartBounds)
+ onTaskResizeAnimationListener.onAnimationStart(state.draggedTaskId, t, animStartBounds)
finishCallback.onTransitionFinished(null /* wct */)
// Because the task surface was scaled down during the drag, we must use the animated
@@ -429,11 +431,15 @@
animBounds.height()
)
}
- state.windowDecoration.updateResizeVeil(tx, animBounds)
+ onTaskResizeAnimationListener.onBoundsChange(
+ state.draggedTaskId,
+ tx,
+ animBounds
+ )
}
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
- state.windowDecoration.hideResizeVeil()
+ onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId)
startTransitionFinishCb.onTransitionFinished(null /* null */)
clearState()
}
@@ -576,7 +582,6 @@
sealed class TransitionState {
abstract val draggedTaskId: Int
abstract val dragAnimator: MoveToDesktopAnimator
- abstract val windowDecoration: DesktopModeWindowDecoration
abstract val startTransitionToken: IBinder
abstract var startTransitionFinishCb: Transitions.TransitionFinishCallback?
abstract var startTransitionFinishTransaction: SurfaceControl.Transaction?
@@ -589,7 +594,6 @@
data class FromFullscreen(
override val draggedTaskId: Int,
override val dragAnimator: MoveToDesktopAnimator,
- override val windowDecoration: DesktopModeWindowDecoration,
override val startTransitionToken: IBinder,
override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null,
override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null,
@@ -603,7 +607,6 @@
data class FromSplit(
override val draggedTaskId: Int,
override val dragAnimator: MoveToDesktopAnimator,
- override val windowDecoration: DesktopModeWindowDecoration,
override val startTransitionToken: IBinder,
override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null,
override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index ba08b09..07cf202 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -38,7 +38,7 @@
import androidx.annotation.Nullable;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration;
+import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener;
import java.util.ArrayList;
import java.util.List;
@@ -59,8 +59,8 @@
public static final int FREEFORM_ANIMATION_DURATION = 336;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
- private DesktopModeWindowDecoration mDesktopModeWindowDecoration;
+ private OnTaskResizeAnimationListener mOnTaskResizeAnimationListener;
public EnterDesktopTaskTransitionHandler(
Transitions transitions) {
this(transitions, SurfaceControl.Transaction::new);
@@ -73,14 +73,15 @@
mTransactionSupplier = supplier;
}
+ void setOnTaskResizeAnimationListener(OnTaskResizeAnimationListener listener) {
+ mOnTaskResizeAnimationListener = listener;
+ }
+
/**
* Starts Transition of type TRANSIT_MOVE_TO_DESKTOP
* @param wct WindowContainerTransaction for transition
- * @param decor {@link DesktopModeWindowDecoration} of task being animated
*/
- public void moveToDesktop(@NonNull WindowContainerTransaction wct,
- DesktopModeWindowDecoration decor) {
- mDesktopModeWindowDecoration = decor;
+ public void moveToDesktop(@NonNull WindowContainerTransaction wct) {
final IBinder token = mTransitions.startTransition(TRANSIT_MOVE_TO_DESKTOP, wct, this);
mPendingTransitionTokens.add(token);
}
@@ -136,18 +137,18 @@
@NonNull TransitionInfo.Change change,
@NonNull SurfaceControl.Transaction startT,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (mDesktopModeWindowDecoration == null) {
- Slog.e(TAG, "Window Decoration is not available for this transition");
+ final SurfaceControl leash = change.getLeash();
+ final Rect startBounds = change.getStartAbsBounds();
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (mOnTaskResizeAnimationListener == null) {
+ Slog.e(TAG, "onTaskResizeAnimationListener is not available for this transition");
return false;
}
- final SurfaceControl leash = change.getLeash();
- final Rect startBounds = change.getStartAbsBounds();
startT.setPosition(leash, startBounds.left, startBounds.top)
.setWindowCrop(leash, startBounds.width(), startBounds.height())
.show(leash);
- mDesktopModeWindowDecoration.showResizeVeil(startT, startBounds);
-
+ mOnTaskResizeAnimationListener.onAnimationStart(taskInfo.taskId, startT, startBounds);
final ValueAnimator animator = ValueAnimator.ofObject(new RectEvaluator(),
change.getStartAbsBounds(), change.getEndAbsBounds());
animator.setDuration(FREEFORM_ANIMATION_DURATION);
@@ -157,12 +158,12 @@
t.setPosition(leash, animationValue.left, animationValue.top)
.setWindowCrop(leash, animationValue.width(), animationValue.height())
.show(leash);
- mDesktopModeWindowDecoration.updateResizeVeil(t, animationValue);
+ mOnTaskResizeAnimationListener.onBoundsChange(taskInfo.taskId, t, animationValue);
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- mDesktopModeWindowDecoration.hideResizeVeil();
+ mOnTaskResizeAnimationListener.onAnimationEnd(taskInfo.taskId);
mTransitions.getMainExecutor().execute(
() -> finishCallback.onTransitionFinished(null));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
index 0218493..c469e65 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -21,7 +21,6 @@
import android.animation.ValueAnimator
import android.graphics.Rect
import android.os.IBinder
-import android.util.SparseArray
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.window.TransitionInfo
@@ -30,7 +29,7 @@
import androidx.core.animation.addListener
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
+import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import java.util.function.Supplier
/** Handles the animation of quick resizing of desktop tasks. */
@@ -40,7 +39,7 @@
) : Transitions.TransitionHandler {
private val rectEvaluator = RectEvaluator(Rect())
- private val taskToDecorationMap = SparseArray<DesktopModeWindowDecoration>()
+ private lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener
private var boundsAnimator: Animator? = null
@@ -49,13 +48,12 @@
) : this(transitions, Supplier { SurfaceControl.Transaction() })
/** Starts a quick resize transition. */
- fun startTransition(
- wct: WindowContainerTransaction,
- taskId: Int,
- windowDecoration: DesktopModeWindowDecoration
- ) {
+ fun startTransition(wct: WindowContainerTransaction) {
transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this)
- taskToDecorationMap.put(taskId, windowDecoration)
+ }
+
+ fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
+ onTaskResizeAnimationListener = listener
}
override fun startAnimation(
@@ -70,9 +68,6 @@
val taskId = checkNotNull(change.taskInfo).taskId
val startBounds = change.startAbsBounds
val endBounds = change.endAbsBounds
- val windowDecor =
- taskToDecorationMap.removeReturnOld(taskId)
- ?: throw IllegalStateException("Window decoration not found for task $taskId")
val tx = transactionSupplier.get()
boundsAnimator?.cancel()
@@ -90,7 +85,11 @@
)
.setWindowCrop(leash, startBounds.width(), startBounds.height())
.show(leash)
- windowDecor.showResizeVeil(startTransaction, startBounds)
+ onTaskResizeAnimationListener.onAnimationStart(
+ taskId,
+ startTransaction,
+ startBounds
+ )
},
onEnd = {
finishTransaction
@@ -101,7 +100,7 @@
)
.setWindowCrop(leash, endBounds.width(), endBounds.height())
.show(leash)
- windowDecor.hideResizeVeil()
+ onTaskResizeAnimationListener.onAnimationEnd(taskId)
finishCallback.onTransitionFinished(null)
boundsAnimator = null
}
@@ -111,7 +110,7 @@
tx.setPosition(leash, rect.left.toFloat(), rect.top.toFloat())
.setWindowCrop(leash, rect.width(), rect.height())
.show(leash)
- windowDecor.updateResizeVeil(tx, rect)
+ onTaskResizeAnimationListener.onBoundsChange(taskId, tx, rect)
}
start()
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index a80241e..f2bdcae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.freeform;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM;
import android.app.ActivityManager.RunningTaskInfo;
@@ -151,6 +153,9 @@
@Override
public void onFocusTaskChanged(RunningTaskInfo taskInfo) {
+ if (taskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+ return;
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG,
"Freeform Task Focus Changed: #%d focused=%b",
taskInfo.taskId, taskInfo.isFocused);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 97d3457..dffcc6d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -19,6 +19,7 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
import static android.view.WindowManager.TRANSIT_SLEEP;
@@ -591,7 +592,7 @@
cancel("transit_sleep");
return;
}
- if (mKeyguardLocked || (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
+ if (mKeyguardLocked || (info.getFlags() & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.merge: keyguard is locked", mInstanceId);
// We will not accept new changes if we are swiping over the keyguard.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 93d7636..196e04e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -480,7 +480,7 @@
WindowContainerTransaction wct = new WindowContainerTransaction();
if (mCaptionInsets != null) {
wct.addInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
- WindowInsets.Type.captionBar(), mCaptionInsets);
+ WindowInsets.Type.captionBar(), mCaptionInsets, null /* boundingRects */);
} else {
wct.removeInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
WindowInsets.Type.captionBar());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 891eea0..7db3d38 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -211,6 +211,8 @@
mShellCommandHandler.addDumpCallback(this::dump, this);
mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(),
new DesktopModeOnInsetsChangedListener());
+ mDesktopTasksController.ifPresent(c -> c.setOnTaskResizeAnimationListener(
+ new DeskopModeOnTaskResizeAnimationListener()));
}
@Override
@@ -356,7 +358,7 @@
// App sometimes draws before the insets from WindowDecoration#relayout have
// been added, so they must be added here
mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
- mDesktopTasksController.get().moveToDesktop(decoration, mTaskId, wct);
+ mDesktopTasksController.get().moveToDesktop(mTaskId, wct);
closeOtherSplitTask(mTaskId);
}
decoration.closeHandleMenu();
@@ -387,25 +389,23 @@
return;
}
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
- mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(
- taskInfo, decoration));
+ mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(taskInfo));
decoration.closeHandleMenu();
} else if (id == R.id.maximize_menu_maximize_button) {
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
- mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(
- taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId)));
+ mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(taskInfo));
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
} else if (id == R.id.maximize_menu_snap_left_button) {
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen(
- taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId), SnapPosition.LEFT));
+ taskInfo, SnapPosition.LEFT));
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
} else if (id == R.id.maximize_menu_snap_right_button) {
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen(
- taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId), SnapPosition.RIGHT));
+ taskInfo, SnapPosition.RIGHT));
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
}
@@ -558,7 +558,7 @@
}
mDesktopTasksController.ifPresent(c -> {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
- c.toggleDesktopTaskSize(decoration.mTaskInfo, decoration);
+ c.toggleDesktopTaskSize(decoration.mTaskInfo);
});
return true;
}
@@ -761,7 +761,7 @@
relevantDecor.mTaskInfo, relevantDecor.mTaskSurface);
mDesktopTasksController.ifPresent(
c -> c.startDragToDesktop(relevantDecor.mTaskInfo,
- mMoveToDesktopAnimator, relevantDecor));
+ mMoveToDesktopAnimator));
}
}
if (mMoveToDesktopAnimator != null) {
@@ -1020,6 +1020,34 @@
pw.println(innerPrefix + "mWindowDecorByTaskId=" + mWindowDecorByTaskId);
}
+ private class DeskopModeOnTaskResizeAnimationListener
+ implements OnTaskResizeAnimationListener {
+ @Override
+ public void onAnimationStart(int taskId, Transaction t, Rect bounds) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ t.apply();
+ return;
+ }
+ decoration.showResizeVeil(t, bounds);
+ }
+
+ @Override
+ public void onBoundsChange(int taskId, Transaction t, Rect bounds) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) return;
+ decoration.updateResizeVeil(t, bounds);
+ }
+
+ @Override
+ public void onAnimationEnd(int taskId) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) return;
+ decoration.hideResizeVeil();
+ }
+ }
+
+
private class DragStartListenerImpl
implements DragPositioningCallbackUtility.DragStartListener {
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OnTaskResizeAnimationListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OnTaskResizeAnimationListener.kt
new file mode 100644
index 0000000..09c62bf
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OnTaskResizeAnimationListener.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.wm.shell.windowdecor
+
+import android.graphics.Rect
+import android.view.SurfaceControl
+
+import com.android.wm.shell.transition.Transitions.TransitionHandler
+/**
+ * Listener that allows implementations of [TransitionHandler] to notify when an
+ * animation that is resizing a task is starting, updating, and finishing the animation.
+ */
+interface OnTaskResizeAnimationListener {
+ /**
+ * Notifies that a transition animation is about to be started with the given bounds.
+ */
+ fun onAnimationStart(taskId: Int, t: SurfaceControl.Transaction, bounds: Rect)
+
+ /**
+ * Notifies that a transition animation is expanding or shrinking the task to the given bounds.
+ */
+ fun onBoundsChange(taskId: Int, t: SurfaceControl.Transaction, bounds: Rect)
+
+ /**
+ * Notifies that a transition animation is about to be finished.
+ */
+ fun onAnimationEnd(taskId: Int)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index afe837e..35d5940 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -298,10 +298,11 @@
mCaptionInsetsRect.bottom =
mCaptionInsetsRect.top + outResult.mCaptionHeight;
wct.addInsetsSource(mTaskInfo.token,
- mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
+ mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect,
+ null /* boundingRects */);
wct.addInsetsSource(mTaskInfo.token,
mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(),
- mCaptionInsetsRect);
+ mCaptionInsetsRect, null /* boundingRects */);
} else {
wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */,
WindowInsets.Type.captionBar());
@@ -546,7 +547,7 @@
final int captionHeight = loadDimensionPixelSize(mContext.getResources(), captionHeightId);
final Rect captionInsets = new Rect(0, 0, 0, captionHeight);
wct.addInsetsSource(mTaskInfo.token, mOwner, 0 /* index */, WindowInsets.Type.captionBar(),
- captionInsets);
+ captionInsets, null /* boundingRects */);
}
static class RelayoutParams {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
index 91503b1..7e26577 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import android.os.Handler;
import android.os.Looper;
@@ -28,6 +29,7 @@
import android.window.BackProgressAnimator;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Before;
import org.junit.Test;
@@ -102,6 +104,36 @@
assertEquals(mReceivedBackEvent.getProgress(), mTargetProgress, 0 /* delta */);
}
+ @Test
+ public void testResetCallsCancelCallbackImmediately() throws InterruptedException {
+ // Give the animator some progress.
+ final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress);
+ mMainThreadHandler.post(
+ () -> mProgressAnimator.onBackProgressed(backEvent));
+ mTargetProgressCalled.await(1, TimeUnit.SECONDS);
+ assertNotNull(mReceivedBackEvent);
+
+ mTargetProgress = 0;
+ mReceivedBackEvent = null;
+ mTargetProgressCalled = new CountDownLatch(1);
+
+ CountDownLatch cancelCallbackCalled = new CountDownLatch(1);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> mProgressAnimator.onBackCancelled(cancelCallbackCalled::countDown));
+
+ // verify onBackProgressed and onBackCancelled not yet called
+ assertNull(mReceivedBackEvent);
+ assertEquals(1, cancelCallbackCalled.getCount());
+
+ // call reset
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> mProgressAnimator.reset());
+
+ // verify that back event with progress 0 is sent and cancel callback is invoked
+ assertNotNull(mReceivedBackEvent);
+ assertEquals(mReceivedBackEvent.getProgress(), mTargetProgress, 0 /* delta */);
+ assertEquals(0, cancelCallbackCalled.getCount());
+ }
+
private void onGestureProgress(BackEvent backEvent) {
if (mTargetProgress == backEvent.getProgress()) {
mReceivedBackEvent = backEvent;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 9249b0a..79634e6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -303,7 +303,7 @@
fun moveToDesktop_displayFullscreen_windowingModeSetToFreeform() {
val task = setUpFullscreenTask()
task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
- controller.moveToDesktop(desktopModeWindowDecoration, task)
+ controller.moveToDesktop(task)
val wct = getLatestMoveToDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FREEFORM)
@@ -313,7 +313,7 @@
fun moveToDesktop_displayFreeform_windowingModeSetToUndefined() {
val task = setUpFullscreenTask()
task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
- controller.moveToDesktop(desktopModeWindowDecoration, task)
+ controller.moveToDesktop(task)
val wct = getLatestMoveToDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_UNDEFINED)
@@ -321,7 +321,7 @@
@Test
fun moveToDesktop_nonExistentTask_doesNothing() {
- controller.moveToDesktop(desktopModeWindowDecoration, 999)
+ controller.moveToDesktop(999)
verifyWCTNotExecuted()
}
@@ -332,7 +332,7 @@
val fullscreenTask = setUpFullscreenTask()
markTaskHidden(freeformTask)
- controller.moveToDesktop(desktopModeWindowDecoration, fullscreenTask)
+ controller.moveToDesktop(fullscreenTask)
with(getLatestMoveToDesktopWct()) {
// Operations should include home task, freeform task
@@ -354,7 +354,7 @@
val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY)
markTaskHidden(freeformTaskSecond)
- controller.moveToDesktop(desktopModeWindowDecoration, fullscreenTaskDefault)
+ controller.moveToDesktop(fullscreenTaskDefault)
with(getLatestMoveToDesktopWct()) {
// Check that hierarchy operations do not include tasks from second display
@@ -368,7 +368,7 @@
@Test
fun moveToDesktop_splitTaskExitsSplit() {
val task = setUpSplitScreenTask()
- controller.moveToDesktop(desktopModeWindowDecoration, task)
+ controller.moveToDesktop(task)
val wct = getLatestMoveToDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FREEFORM)
@@ -380,7 +380,7 @@
@Test
fun moveToDesktop_fullscreenTaskDoesNotExitSplit() {
val task = setUpFullscreenTask()
- controller.moveToDesktop(desktopModeWindowDecoration, task)
+ controller.moveToDesktop(task)
val wct = getLatestMoveToDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FREEFORM)
@@ -802,7 +802,7 @@
private fun getLatestMoveToDesktopWct(): WindowContainerTransaction {
val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
if (ENABLE_SHELL_TRANSITIONS) {
- verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any())
+ verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture())
} else {
verify(shellTaskOrganizer).applyTransaction(arg.capture())
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index be639e8..98e90d6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -24,7 +24,6 @@
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
-import java.util.function.Supplier
import junit.framework.Assert.assertFalse
import org.junit.Before
import org.junit.Test
@@ -38,6 +37,7 @@
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyZeroInteractions
import org.mockito.kotlin.whenever
+import java.util.function.Supplier
/** Tests of [DragToDesktopTransitionHandler]. */
@SmallTest
@@ -246,7 +246,7 @@
)
)
.thenReturn(token)
- handler.startDragToDesktopTransition(task.taskId, dragAnimator, mock())
+ handler.startDragToDesktopTransition(task.taskId, dragAnimator)
return token
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
new file mode 100644
index 0000000..71eea4b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.freeform;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.quality.Strictness;
+
+import java.util.Optional;
+
+/**
+ * Tests for {@link FreeformTaskListener}
+ * Build/Install/Run:
+ * atest WMShellUnitTests:FreeformTaskListenerTests
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class FreeformTaskListenerTests extends ShellTestCase {
+
+ @Mock
+ private ShellTaskOrganizer mTaskOrganizer;
+ @Mock
+ private ShellInit mShellInit;
+ @Mock
+ private WindowDecorViewModel mWindowDecorViewModel;
+ @Mock
+ private DesktopModeTaskRepository mDesktopModeTaskRepository;
+ private FreeformTaskListener mFreeformTaskListener;
+ private StaticMockitoSession mMockitoSession;
+
+ @Before
+ public void setup() {
+ mMockitoSession = mockitoSession().initMocks(this)
+ .strictness(Strictness.LENIENT).mockStatic(DesktopModeStatus.class).startMocking();
+ when(DesktopModeStatus.isEnabled()).thenReturn(true);
+ mFreeformTaskListener = new FreeformTaskListener(
+ mShellInit,
+ mTaskOrganizer,
+ Optional.of(mDesktopModeTaskRepository),
+ mWindowDecorViewModel);
+ }
+
+ @Test
+ public void testFocusTaskChanged_freeformTaskIsAddedToRepo() {
+ ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isFocused = true;
+
+ mFreeformTaskListener.onFocusTaskChanged(task);
+
+ verify(mDesktopModeTaskRepository).addOrMoveFreeformTaskToTop(task.taskId);
+ }
+
+ @Test
+ public void testFocusTaskChanged_fullscreenTaskIsNotAddedToRepo() {
+ ActivityManager.RunningTaskInfo fullscreenTask = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build();
+ fullscreenTask.isFocused = true;
+
+ mFreeformTaskListener.onFocusTaskChanged(fullscreenTask);
+
+ verify(mDesktopModeTaskRepository, never())
+ .addOrMoveFreeformTaskToTop(fullscreenTask.taskId);
+ }
+
+ @After
+ public void tearDown() {
+ mMockitoSession.finishMocking();
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 7b53f70..228b25c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -259,7 +259,8 @@
any(),
eq(0 /* index */),
eq(WindowInsets.Type.captionBar()),
- eq(new Rect(100, 300, 400, 364)));
+ eq(new Rect(100, 300, 400, 364)),
+ any());
}
verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
@@ -569,9 +570,9 @@
windowDecor.relayout(taskInfo);
verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
- eq(0) /* index */, eq(captionBar()), any());
+ eq(0) /* index */, eq(captionBar()), any(), any());
verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
- eq(0) /* index */, eq(mandatorySystemGestures()), any());
+ eq(0) /* index */, eq(mandatorySystemGestures()), any(), any());
}
@Test
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 46f636e..8748dab 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -117,10 +117,6 @@
return true;
}
-void AssetManager2::PresetApkAssets(ApkAssetsList apk_assets) {
- BuildDynamicRefTable(apk_assets);
-}
-
bool AssetManager2::SetApkAssets(std::initializer_list<ApkAssetsPtr> apk_assets,
bool invalidate_caches) {
return SetApkAssets(ApkAssetsList(apk_assets.begin(), apk_assets.size()), invalidate_caches);
@@ -436,18 +432,13 @@
return false;
}
-void AssetManager2::SetConfigurations(std::vector<ResTable_config> configurations,
- bool force_refresh) {
+void AssetManager2::SetConfigurations(std::vector<ResTable_config> configurations) {
int diff = 0;
- if (force_refresh) {
+ if (configurations_.size() != configurations.size()) {
diff = -1;
} else {
- if (configurations_.size() != configurations.size()) {
- diff = -1;
- } else {
- for (int i = 0; i < configurations_.size(); i++) {
- diff |= configurations_[i].diff(configurations[i]);
- }
+ for (int i = 0; i < configurations_.size(); i++) {
+ diff |= configurations_[i].diff(configurations[i]);
}
}
configurations_ = std::move(configurations);
@@ -784,7 +775,8 @@
bool has_locale = false;
if (result->config.locale == 0) {
if (default_locale_ != 0) {
- ResTable_config conf = {.locale = default_locale_};
+ ResTable_config conf;
+ conf.locale = default_locale_;
// Since we know conf has a locale and only a locale, match will tell us if that locale
// matches
has_locale = conf.match(config);
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 17a8ba6..d9ff35b 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -124,9 +124,6 @@
// new resource IDs.
bool SetApkAssets(ApkAssetsList apk_assets, bool invalidate_caches = true);
bool SetApkAssets(std::initializer_list<ApkAssetsPtr> apk_assets, bool invalidate_caches = true);
- // This one is an optimization - it skips all calculations for applying the currently set
- // configuration, expecting a configuration update later with a forced refresh.
- void PresetApkAssets(ApkAssetsList apk_assets);
const ApkAssetsPtr& GetApkAssets(ApkAssetsCookie cookie) const;
int GetApkAssetsCount() const {
@@ -159,7 +156,7 @@
// Sets/resets the configuration for this AssetManager. This will cause all
// caches that are related to the configuration change to be invalidated.
- void SetConfigurations(std::vector<ResTable_config> configurations, bool force_refresh = false);
+ void SetConfigurations(std::vector<ResTable_config> configurations);
inline const std::vector<ResTable_config>& GetConfigurations() const {
return configurations_;
diff --git a/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java
index 2de8eef..ab5c54b 100644
--- a/media/java/android/media/AudioDevicePort.java
+++ b/media/java/android/media/AudioDevicePort.java
@@ -33,7 +33,7 @@
* device at the boundary of the audio system.
* In addition to base audio port attributes, the device descriptor contains:
* - the device type (e.g AudioManager.DEVICE_OUT_SPEAKER)
- * - the device address (e.g MAC adddress for AD2P sink).
+ * - the device address (e.g MAC address for AD2P sink).
* @see AudioPort
* @hide
*/
diff --git a/media/java/android/media/AudioHalVersionInfo.java b/media/java/android/media/AudioHalVersionInfo.java
index 4cdfc51..2b6f72e 100644
--- a/media/java/android/media/AudioHalVersionInfo.java
+++ b/media/java/android/media/AudioHalVersionInfo.java
@@ -79,6 +79,9 @@
/**
* List of all valid Audio HAL versions. This list need to be in sync with sAudioHALVersions
* defined in frameworks/av/media/libaudiohal/FactoryHal.cpp.
+ *
+ * Note: update {@link android.media.audio.cts.AudioHalVersionInfoTest} CTS accordingly if
+ * there is a change to supported versions.
*/
public static final @NonNull List<AudioHalVersionInfo> VERSIONS =
List.of(AIDL_1_0, HIDL_7_1, HIDL_7_0, HIDL_6_0, HIDL_5_0);
diff --git a/media/java/android/media/OWNERS b/media/java/android/media/OWNERS
index 49890c1..8cc42e0 100644
--- a/media/java/android/media/OWNERS
+++ b/media/java/android/media/OWNERS
@@ -11,3 +11,8 @@
per-file ExifInterface.java,ExifInterfaceUtils.java,IMediaHTTPConnection.aidl,IMediaHTTPService.aidl,JetPlayer.java,MediaDataSource.java,MediaExtractor.java,MediaHTTPConnection.java,MediaHTTPService.java,MediaPlayer.java=set noparent
per-file ExifInterface.java,ExifInterfaceUtils.java,IMediaHTTPConnection.aidl,IMediaHTTPService.aidl,JetPlayer.java,MediaDataSource.java,MediaExtractor.java,MediaHTTPConnection.java,MediaHTTPService.java,MediaPlayer.java=file:platform/frameworks/av:/media/janitors/media_solutions_OWNERS
+
+# Haptics team also works on Ringtone
+per-file *Ringtone* = file:/services/core/java/com/android/server/vibrator/OWNERS
+
+per-file flags/projection.aconfig = file:projection/OWNERS
diff --git a/media/java/android/media/metrics/EditingEndedEvent.java b/media/java/android/media/metrics/EditingEndedEvent.java
index f1c5c9d..9b3477f 100644
--- a/media/java/android/media/metrics/EditingEndedEvent.java
+++ b/media/java/android/media/metrics/EditingEndedEvent.java
@@ -18,6 +18,7 @@
import static com.android.media.editing.flags.Flags.FLAG_ADD_MEDIA_METRICS_EDITING;
import android.annotation.FlaggedApi;
+import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.LongDef;
@@ -60,6 +61,8 @@
private final @FinalState int mFinalState;
+ private final float mFinalProgressPercent;
+
// The special value 0 is reserved for the field being unspecified in the proto.
/** Special value representing that no error occurred. */
@@ -155,10 +158,15 @@
/** Special value for unknown {@linkplain #getTimeSinceCreatedMillis() time since creation}. */
public static final int TIME_SINCE_CREATED_UNKNOWN = -1;
+ /** Special value for unknown {@linkplain #getFinalProgressPercent() final progress}. */
+ public static final int PROGRESS_PERCENT_UNKNOWN = -1;
+
private final @ErrorCode int mErrorCode;
@SuppressWarnings("HidingField") // Hiding field from superclass as for playback events.
private final long mTimeSinceCreatedMillis;
+ @Nullable private final String mExporterName;
+ @Nullable private final String mMuxerName;
private final ArrayList<MediaItemInfo> mInputMediaItemInfos;
@Nullable private final MediaItemInfo mOutputMediaItemInfo;
@@ -207,15 +215,21 @@
private EditingEndedEvent(
@FinalState int finalState,
+ float finalProgressPercent,
@ErrorCode int errorCode,
long timeSinceCreatedMillis,
+ @Nullable String exporterName,
+ @Nullable String muxerName,
ArrayList<MediaItemInfo> inputMediaItemInfos,
@Nullable MediaItemInfo outputMediaItemInfo,
@OperationType long operationTypes,
@NonNull Bundle extras) {
mFinalState = finalState;
+ mFinalProgressPercent = finalProgressPercent;
mErrorCode = errorCode;
mTimeSinceCreatedMillis = timeSinceCreatedMillis;
+ mExporterName = exporterName;
+ mMuxerName = muxerName;
mInputMediaItemInfos = inputMediaItemInfos;
mOutputMediaItemInfo = outputMediaItemInfo;
mOperationTypes = operationTypes;
@@ -228,6 +242,14 @@
return mFinalState;
}
+ /**
+ * Returns the progress of the editing operation in percent at the moment that it ended, or
+ * {@link #PROGRESS_PERCENT_UNKNOWN} if unknown.
+ */
+ public float getFinalProgressPercent() {
+ return mFinalProgressPercent;
+ }
+
/** Returns the error code for a {@linkplain #FINAL_STATE_ERROR failed} editing session. */
@ErrorCode
public int getErrorCode() {
@@ -249,6 +271,24 @@
return mTimeSinceCreatedMillis;
}
+ /**
+ * Returns the name of the library implementing the exporting operation, or {@code null} if
+ * unknown.
+ */
+ @Nullable
+ public String getExporterName() {
+ return mExporterName;
+ }
+
+ /**
+ * Returns the name of the library implementing the media muxing operation, or {@code null} if
+ * unknown.
+ */
+ @Nullable
+ public String getMuxerName() {
+ return mMuxerName;
+ }
+
/** Gets information about the input media items, or an empty list if unspecified. */
@NonNull
public List<MediaItemInfo> getInputMediaItemInfos() {
@@ -284,12 +324,21 @@
+ "finalState = "
+ mFinalState
+ ", "
+ + "finalProgressPercent = "
+ + mFinalProgressPercent
+ + ", "
+ "errorCode = "
+ mErrorCode
+ ", "
+ "timeSinceCreatedMillis = "
+ mTimeSinceCreatedMillis
+ ", "
+ + "exporterName = "
+ + mExporterName
+ + ", "
+ + "muxerName = "
+ + mMuxerName
+ + ", "
+ "inputMediaItemInfos = "
+ mInputMediaItemInfos
+ ", "
@@ -307,29 +356,38 @@
if (o == null || getClass() != o.getClass()) return false;
EditingEndedEvent that = (EditingEndedEvent) o;
return mFinalState == that.mFinalState
+ && mFinalProgressPercent == that.mFinalProgressPercent
&& mErrorCode == that.mErrorCode
&& Objects.equals(mInputMediaItemInfos, that.mInputMediaItemInfos)
&& Objects.equals(mOutputMediaItemInfo, that.mOutputMediaItemInfo)
&& mOperationTypes == that.mOperationTypes
- && mTimeSinceCreatedMillis == that.mTimeSinceCreatedMillis;
+ && mTimeSinceCreatedMillis == that.mTimeSinceCreatedMillis
+ && Objects.equals(mExporterName, that.mExporterName)
+ && Objects.equals(mMuxerName, that.mMuxerName);
}
@Override
public int hashCode() {
return Objects.hash(
mFinalState,
+ mFinalProgressPercent,
mErrorCode,
mInputMediaItemInfos,
mOutputMediaItemInfo,
mOperationTypes,
- mTimeSinceCreatedMillis);
+ mTimeSinceCreatedMillis,
+ mExporterName,
+ mMuxerName);
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mFinalState);
+ dest.writeFloat(mFinalProgressPercent);
dest.writeInt(mErrorCode);
dest.writeLong(mTimeSinceCreatedMillis);
+ dest.writeString(mExporterName);
+ dest.writeString(mMuxerName);
dest.writeTypedList(mInputMediaItemInfos);
dest.writeTypedObject(mOutputMediaItemInfo, /* parcelableFlags= */ 0);
dest.writeLong(mOperationTypes);
@@ -343,8 +401,11 @@
private EditingEndedEvent(@NonNull Parcel in) {
mFinalState = in.readInt();
+ mFinalProgressPercent = in.readFloat();
mErrorCode = in.readInt();
mTimeSinceCreatedMillis = in.readLong();
+ mExporterName = in.readString();
+ mMuxerName = in.readString();
mInputMediaItemInfos = new ArrayList<>();
in.readTypedList(mInputMediaItemInfos, MediaItemInfo.CREATOR);
mOutputMediaItemInfo = in.readTypedObject(MediaItemInfo.CREATOR);
@@ -370,8 +431,11 @@
public static final class Builder {
private final @FinalState int mFinalState;
private final ArrayList<MediaItemInfo> mInputMediaItemInfos;
+ private float mFinalProgressPercent;
private @ErrorCode int mErrorCode;
private long mTimeSinceCreatedMillis;
+ @Nullable private String mExporterName;
+ @Nullable private String mMuxerName;
@Nullable private MediaItemInfo mOutputMediaItemInfo;
private @OperationType long mOperationTypes;
private Bundle mMetricsBundle;
@@ -383,6 +447,7 @@
*/
public Builder(@FinalState int finalState) {
mFinalState = finalState;
+ mFinalProgressPercent = PROGRESS_PERCENT_UNKNOWN;
mErrorCode = ERROR_CODE_NONE;
mTimeSinceCreatedMillis = TIME_SINCE_CREATED_UNKNOWN;
mInputMediaItemInfos = new ArrayList<>();
@@ -390,6 +455,19 @@
}
/**
+ * Sets the progress of the editing operation in percent at the moment that it ended.
+ *
+ * @param finalProgressPercent The progress of the editing operation in percent at the
+ * moment that it ended.
+ * @see #getFinalProgressPercent()
+ */
+ public @NonNull Builder setFinalProgressPercent(
+ @FloatRange(from = 0, to = 100) float finalProgressPercent) {
+ mFinalProgressPercent = finalProgressPercent;
+ return this;
+ }
+
+ /**
* Sets the elapsed time since creating the editing session, in milliseconds.
*
* @param timeSinceCreatedMillis The elapsed time since creating the editing session, in
@@ -402,6 +480,30 @@
return this;
}
+ /**
+ * The name of the library implementing the exporting operation. For example, a Maven
+ * artifact ID like "androidx.media3.media3-transformer:1.3.0-beta01".
+ *
+ * @param exporterName The name of the library implementing the export operation.
+ * @see #getExporterName()
+ */
+ public @NonNull Builder setExporterName(@NonNull String exporterName) {
+ mExporterName = Objects.requireNonNull(exporterName);
+ return this;
+ }
+
+ /**
+ * The name of the library implementing the media muxing operation. For example, a Maven
+ * artifact ID like "androidx.media3.media3-muxer:1.3.0-beta01".
+ *
+ * @param muxerName The name of the library implementing the media muxing operation.
+ * @see #getMuxerName()
+ */
+ public @NonNull Builder setMuxerName(@NonNull String muxerName) {
+ mMuxerName = Objects.requireNonNull(muxerName);
+ return this;
+ }
+
/** Sets the error code for a {@linkplain #FINAL_STATE_ERROR failed} editing session. */
public @NonNull Builder setErrorCode(@ErrorCode int value) {
mErrorCode = value;
@@ -444,8 +546,11 @@
public @NonNull EditingEndedEvent build() {
return new EditingEndedEvent(
mFinalState,
+ mFinalProgressPercent,
mErrorCode,
mTimeSinceCreatedMillis,
+ mExporterName,
+ mMuxerName,
mInputMediaItemInfos,
mOutputMediaItemInfo,
mOperationTypes,
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index e3290d6..2a0648d 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -180,7 +180,7 @@
@SuppressLint("UnflaggedApi")
@TestApi
@NonNull
- public Intent createScreenCaptureIntent(@Nullable LaunchCookie launchCookie) {
+ public Intent createScreenCaptureIntent(@NonNull LaunchCookie launchCookie) {
Intent i = createScreenCaptureIntent();
i.putExtra(EXTRA_LAUNCH_COOKIE, launchCookie);
return i;
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index 59b10c6..76664a6 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -18,6 +18,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
@@ -59,7 +60,7 @@
*/
@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
@SystemService(Context.TV_AD_SERVICE)
-public class TvAdManager {
+public final class TvAdManager {
// TODO: implement more methods and unhide APIs.
private static final String TAG = "TvAdManager";
@@ -237,6 +238,76 @@
*/
public static final String SESSION_DATA_KEY_REQUEST_ID = "request_id";
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = false, prefix = "SESSION_STATE_", value = {
+ SESSION_STATE_STOPPED,
+ SESSION_STATE_RUNNING,
+ SESSION_STATE_ERROR})
+ public @interface SessionState {}
+
+ /**
+ * Stopped (or not started) state of AD service session.
+ */
+ public static final int SESSION_STATE_STOPPED = 1;
+ /**
+ * Running state of AD service session.
+ */
+ public static final int SESSION_STATE_RUNNING = 2;
+ /**
+ * Error state of AD service session.
+ */
+ public static final int SESSION_STATE_ERROR = 3;
+
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = false, prefix = "ERROR_", value = {
+ ERROR_NONE,
+ ERROR_UNKNOWN,
+ ERROR_NOT_SUPPORTED,
+ ERROR_WEAK_SIGNAL,
+ ERROR_RESOURCE_UNAVAILABLE,
+ ERROR_BLOCKED,
+ ERROR_ENCRYPTED,
+ ERROR_UNKNOWN_CHANNEL,
+ })
+ public @interface ErrorCode {}
+
+ /**
+ * No error.
+ */
+ public static final int ERROR_NONE = 0;
+ /**
+ * Unknown error code.
+ */
+ public static final int ERROR_UNKNOWN = 1;
+ /**
+ * Error code for an unsupported channel.
+ */
+ public static final int ERROR_NOT_SUPPORTED = 2;
+ /**
+ * Error code for weak signal.
+ */
+ public static final int ERROR_WEAK_SIGNAL = 3;
+ /**
+ * Error code when resource (e.g. tuner) is unavailable.
+ */
+ public static final int ERROR_RESOURCE_UNAVAILABLE = 4;
+ /**
+ * Error code for blocked contents.
+ */
+ public static final int ERROR_BLOCKED = 5;
+ /**
+ * Error code when the key or module is missing for the encrypted channel.
+ */
+ public static final int ERROR_ENCRYPTED = 6;
+ /**
+ * Error code when the current channel is an unknown channel.
+ */
+ public static final int ERROR_UNKNOWN_CHANNEL = 7;
+
private final ITvAdManager mService;
private final int mUserId;
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
index 6c8a8fd..2bba0f3 100644
--- a/media/java/android/media/tv/ad/TvAdService.java
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -280,7 +280,6 @@
/**
* Requests the bounds of the current video.
- * @hide
*/
@CallSuper
public void requestCurrentVideoBounds() {
@@ -304,7 +303,6 @@
/**
* Requests the URI of the current channel.
- * @hide
*/
@CallSuper
public void requestCurrentChannelUri() {
@@ -328,7 +326,6 @@
/**
* Requests the list of {@link TvTrackInfo}.
- * @hide
*/
@CallSuper
public void requestTrackInfoList() {
@@ -354,7 +351,6 @@
* Requests current TV input ID.
*
* @see android.media.tv.TvInputInfo
- * @hide
*/
@CallSuper
public void requestCurrentTvInputId() {
@@ -393,7 +389,6 @@
* @param data the original bytes to be signed.
*
* @see #onSigningResult(String, byte[])
- * @hide
*/
@CallSuper
public void requestSigning(@NonNull String signingId, @NonNull String algorithm,
@@ -535,28 +530,24 @@
* Receives current video bounds.
*
* @param bounds the rectangle area for rendering the current video.
- * @hide
*/
public void onCurrentVideoBounds(@NonNull Rect bounds) {
}
/**
* Receives current channel URI.
- * @hide
*/
public void onCurrentChannelUri(@Nullable Uri channelUri) {
}
/**
* Receives track list.
- * @hide
*/
public void onTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
}
/**
* Receives current TV input ID.
- * @hide
*/
public void onCurrentTvInputId(@Nullable String inputId) {
}
@@ -569,7 +560,6 @@
* @param result the signed result.
*
* @see #requestSigning(String, String, String, byte[])
- * @hide
*/
public void onSigningResult(@NonNull String signingId, @NonNull byte[] result) {
}
@@ -584,7 +574,6 @@
* "onRequestSigning" can also be added to the params.
*
* @see TvAdView#ERROR_KEY_METHOD_NAME
- * @hide
*/
public void onError(@NonNull String errMsg, @NonNull Bundle params) {
}
@@ -601,7 +590,6 @@
* {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
* See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
* how to parse this data.
- * @hide
*/
public void onTvMessage(@TvInputManager.TvMessageType int type,
@NonNull Bundle data) {
@@ -671,6 +659,30 @@
}
/**
+ * Notifies when the session state is changed.
+ *
+ * @param state the current session state.
+ * @param err the error code for error state. {@link TvAdManager#ERROR_NONE} is
+ * used when the state is not {@link TvAdManager#SESSION_STATE_ERROR}.
+ */
+ @CallSuper
+ public void notifySessionStateChanged(
+ @TvAdManager.SessionState int state,
+ @TvAdManager.ErrorCode int err) {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ if (DEBUG) {
+ Log.d(TAG, "notifySessionStateChanged (state="
+ + state + "; err=" + err + ")");
+ }
+ // TODO: handle session callback
+ }
+ });
+ }
+
+ /**
* Takes care of dispatching incoming input events and tells whether the event was handled.
*/
int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index ee01468..2fac8ce 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -61,10 +61,19 @@
* The name of the method where the error happened, if applicable. For example, if there is an
* error during signing, the request name is "onRequestSigning".
* @see #notifyError(String, Bundle)
- * @hide
*/
public static final String ERROR_KEY_METHOD_NAME = "method_name";
+ /**
+ * The error code of an error.
+ *
+ * <p>It can be {@link TvAdManager#ERROR_WEAK_SIGNAL},
+ * {@link TvAdManager#ERROR_RESOURCE_UNAVAILABLE}, etc.
+ *
+ * @see #notifyError(String, Bundle)
+ */
+ public static final String ERROR_KEY_ERROR_CODE = "error_code";
+
private final TvAdManager mTvAdManager;
private final Handler mHandler = new Handler();
@@ -486,7 +495,6 @@
* Sends current video bounds to related TV AD service.
*
* @param bounds the rectangle area for rendering the current video.
- * @hide
*/
public void sendCurrentVideoBounds(@NonNull Rect bounds) {
if (DEBUG) {
@@ -502,7 +510,6 @@
*
* @param channelUri The current channel URI; {@code null} if there is no currently tuned
* channel.
- * @hide
*/
public void sendCurrentChannelUri(@Nullable Uri channelUri) {
if (DEBUG) {
@@ -515,7 +522,6 @@
/**
* Sends track info list to related TV AD service.
- * @hide
*/
public void sendTrackInfoList(@Nullable List<TvTrackInfo> tracks) {
if (DEBUG) {
@@ -532,7 +538,6 @@
* @param inputId The current TV input ID whose channel is tuned. {@code null} if no channel is
* tuned.
* @see android.media.tv.TvInputInfo
- * @hide
*/
public void sendCurrentTvInputId(@Nullable String inputId) {
if (DEBUG) {
@@ -553,7 +558,6 @@
* @param signingId the ID to identify the request. It's the same as the corresponding ID in
* {@link TvAdService.Session#requestSigning(String, String, String, byte[])}
* @param result the signed result.
- * @hide
*/
public void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) {
if (DEBUG) {
@@ -574,7 +578,7 @@
* can also be added to the params.
*
* @see #ERROR_KEY_METHOD_NAME
- * @hide
+ * @see #ERROR_KEY_ERROR_CODE
*/
public void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
if (DEBUG) {
@@ -597,7 +601,6 @@
* {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
* See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
* how to parse this data.
- * @hide
*/
public void notifyTvMessage(@NonNull @TvInputManager.TvMessageType int type,
@NonNull Bundle data) {
@@ -633,7 +636,6 @@
* @param callback the callback to receive events. MUST NOT be {@code null}.
*
* @see #clearCallback()
- * @hide
*/
public void setCallback(
@NonNull @CallbackExecutor Executor executor,
@@ -649,7 +651,6 @@
* Clears the callback.
*
* @see #setCallback(Executor, TvAdCallback)
- * @hide
*/
public void clearCallback() {
synchronized (mCallbackLock) {
@@ -845,7 +846,6 @@
/**
* Callback used to receive various status updates on the {@link TvAdView}.
- * @hide
*/
public abstract static class TvAdCallback {
@@ -898,5 +898,20 @@
public void onRequestSigning(@NonNull String serviceId, @NonNull String signingId,
@NonNull String algorithm, @NonNull String alias, @NonNull byte[] data) {
}
+
+ /**
+ * This is called when the state of corresponding AD service is changed.
+ *
+ * @param serviceId The ID of the AD service bound to this view.
+ * @param state the current state.
+ * @param err the error code for error state. {@link TvAdManager#ERROR_NONE}
+ * is used when the state is not
+ * {@link TvAdManager#SESSION_STATE_ERROR}.
+ */
+ public void onStateChanged(
+ @NonNull String serviceId,
+ @TvAdManager.SessionState int state,
+ @TvAdManager.ErrorCode int err) {
+ }
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
index 5590219..7cd6bb3 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
@@ -48,8 +48,6 @@
{
Text(
text = label,
- modifier = Modifier.fillMaxWidth(),
- textAlign = TextAlign.Center,
overflow = TextOverflow.Ellipsis,
maxLines = if (secondaryLabel != null) 1 else 2,
)
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 3252aad..1b29e83 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -526,7 +526,7 @@
</string-array>
<!-- USB configuration values for Developer Settings.
- These are lists of USB functions passed to the USB Manager to change USB configuraton.
+ These are lists of USB functions passed to the USB Manager to change USB configuration.
This can be overridden by devices with additional USB configurations.
Do not translate. -->
<string-array name="usb_configuration_values" translatable="false">
diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/model/ZenMode.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/model/ZenMode.kt
new file mode 100644
index 0000000..a696f8c
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/model/ZenMode.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.statusbar.notification.data.model
+
+import android.provider.Settings.Global
+
+/** Validating wrapper for [android.app.NotificationManager.getZenMode] values. */
+@JvmInline
+value class ZenMode(val zenMode: Int) {
+
+ init {
+ require(zenMode in supportedModes) { "Unsupported zenMode=$zenMode" }
+ }
+
+ private companion object {
+
+ val supportedModes =
+ listOf(
+ Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ Global.ZEN_MODE_NO_INTERRUPTIONS,
+ Global.ZEN_MODE_ALARMS,
+ Global.ZEN_MODE_OFF,
+ )
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt
new file mode 100644
index 0000000..6098307
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.settingslib.statusbar.notification.data.repository
+
+import android.app.NotificationManager
+import com.android.settingslib.statusbar.notification.data.model.ZenMode
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeNotificationsSoundPolicyRepository : NotificationsSoundPolicyRepository {
+
+ private val mutableNotificationPolicy = MutableStateFlow<NotificationManager.Policy?>(null)
+ override val notificationPolicy: StateFlow<NotificationManager.Policy?>
+ get() = mutableNotificationPolicy.asStateFlow()
+
+ private val mutableZenMode = MutableStateFlow<ZenMode?>(null)
+ override val zenMode: StateFlow<ZenMode?>
+ get() = mutableZenMode.asStateFlow()
+
+ fun updateNotificationPolicy(policy: NotificationManager.Policy?) {
+ mutableNotificationPolicy.value = policy
+ }
+
+ fun updateZenMode(zenMode: ZenMode?) {
+ mutableZenMode.value = zenMode
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/NotificationsSoundPolicyRepository.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/NotificationsSoundPolicyRepository.kt
new file mode 100644
index 0000000..0fb8c3f
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/NotificationsSoundPolicyRepository.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.settingslib.statusbar.notification.data.repository
+
+import android.app.NotificationManager
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import com.android.settingslib.statusbar.notification.data.model.ZenMode
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Provides state of volume policy and restrictions imposed by notifications. */
+interface NotificationsSoundPolicyRepository {
+
+ /** @see NotificationManager.getNotificationPolicy */
+ val notificationPolicy: StateFlow<NotificationManager.Policy?>
+
+ /** @see NotificationManager.getZenMode */
+ val zenMode: StateFlow<ZenMode?>
+}
+
+class NotificationsSoundPolicyRepositoryImpl(
+ private val context: Context,
+ private val notificationManager: NotificationManager,
+ scope: CoroutineScope,
+ backgroundCoroutineContext: CoroutineContext,
+) : NotificationsSoundPolicyRepository {
+
+ private val notificationBroadcasts =
+ callbackFlow {
+ val receiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ intent?.action?.let { action -> launch { send(action) } }
+ }
+ }
+
+ context.registerReceiver(
+ receiver,
+ IntentFilter().apply {
+ addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED)
+ addAction(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED)
+ }
+ )
+
+ awaitClose { context.unregisterReceiver(receiver) }
+ }
+ .shareIn(
+ started = SharingStarted.WhileSubscribed(),
+ scope = scope,
+ )
+
+ override val notificationPolicy: StateFlow<NotificationManager.Policy?> =
+ notificationBroadcasts
+ .filter { NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED == it }
+ .map { notificationManager.consolidatedNotificationPolicy }
+ .onStart { emit(notificationManager.consolidatedNotificationPolicy) }
+ .flowOn(backgroundCoroutineContext)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ override val zenMode: StateFlow<ZenMode?> =
+ notificationBroadcasts
+ .filter { NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED == it }
+ .map { ZenMode(notificationManager.zenMode) }
+ .onStart { emit(ZenMode(notificationManager.zenMode)) }
+ .flowOn(backgroundCoroutineContext)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/statusbar/notification/data/repository/NotificationsSoundPolicyRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/statusbar/notification/data/repository/NotificationsSoundPolicyRepositoryTest.kt
new file mode 100644
index 0000000..dfc4c0a
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/statusbar/notification/data/repository/NotificationsSoundPolicyRepositoryTest.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.settingslib.statusbar.notification.data.repository
+
+import android.app.NotificationManager
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings.Global
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.statusbar.notification.data.model.ZenMode
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class NotificationsSoundPolicyRepositoryTest {
+
+ @Mock private lateinit var context: Context
+ @Mock private lateinit var notificationManager: NotificationManager
+ @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
+
+ private lateinit var underTest: NotificationsSoundPolicyRepository
+
+ private val testScope: TestScope = TestScope()
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ NotificationsSoundPolicyRepositoryImpl(
+ context,
+ notificationManager,
+ testScope.backgroundScope,
+ testScope.testScheduler,
+ )
+ }
+
+ @Test
+ fun policyChanges_repositoryEmits() {
+ testScope.runTest {
+ val values = mutableListOf<NotificationManager.Policy?>()
+ `when`(notificationManager.notificationPolicy).thenReturn(testPolicy1)
+ underTest.notificationPolicy.onEach { values.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ `when`(notificationManager.notificationPolicy).thenReturn(testPolicy2)
+ triggerIntent(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED)
+ runCurrent()
+
+ assertThat(values)
+ .containsExactlyElementsIn(listOf(null, testPolicy1, testPolicy2))
+ .inOrder()
+ }
+ }
+
+ @Test
+ fun zenModeChanges_repositoryEmits() {
+ testScope.runTest {
+ val values = mutableListOf<ZenMode?>()
+ `when`(notificationManager.zenMode).thenReturn(Global.ZEN_MODE_OFF)
+ underTest.zenMode.onEach { values.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ `when`(notificationManager.zenMode).thenReturn(Global.ZEN_MODE_ALARMS)
+ triggerIntent(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED)
+ runCurrent()
+
+ assertThat(values)
+ .containsExactlyElementsIn(
+ listOf(null, ZenMode(Global.ZEN_MODE_OFF), ZenMode(Global.ZEN_MODE_ALARMS))
+ )
+ .inOrder()
+ }
+ }
+
+ private fun triggerIntent(action: String) {
+ verify(context).registerReceiver(receiverCaptor.capture(), any())
+ receiverCaptor.value.onReceive(context, Intent(action))
+ }
+
+ private companion object {
+ val testPolicy1 =
+ NotificationManager.Policy(
+ /* priorityCategories = */ 1,
+ /* priorityCallSenders =*/ 1,
+ /* priorityMessageSenders = */ 1,
+ )
+ val testPolicy2 =
+ NotificationManager.Policy(
+ /* priorityCategories = */ 2,
+ /* priorityCallSenders =*/ 2,
+ /* priorityMessageSenders = */ 2,
+ )
+ }
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 8ae117e..e424797 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -247,6 +247,7 @@
Settings.Secure.BLUETOOTH_LE_BROADCAST_FALLBACK_ACTIVE_DEVICE_ADDRESS,
Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP,
Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER,
+ Settings.Secure.CONTEXTUAL_SCREEN_TIMEOUT_ENABLED,
Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED,
Settings.Secure.HEARING_AID_RINGTONE_ROUTING,
Settings.Secure.HEARING_AID_CALL_ROUTING,
@@ -264,6 +265,7 @@
Settings.Secure.EVEN_DIMMER_ACTIVATED,
Settings.Secure.EVEN_DIMMER_MIN_NITS,
Settings.Secure.STYLUS_POINTER_ICON_ENABLED,
- Settings.Secure.CAMERA_EXTENSIONS_FALLBACK
+ Settings.Secure.CAMERA_EXTENSIONS_FALLBACK,
+ Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 5adae375..a32eead 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -207,6 +207,7 @@
VALIDATORS.put(Secure.ASSIST_GESTURE_WAKE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_TOUCH_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_LONG_PRESS_HOME_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SEARCH_LONG_PRESS_HOME_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.VR_DISPLAY_MODE, new DiscreteValueValidator(new String[] {"0", "1"}));
@@ -397,6 +398,7 @@
VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_APP, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_USER, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.LOCK_SCREEN_WEATHER_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.CONTEXTUAL_SCREEN_TIMEOUT_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.HEARING_AID_RINGTONE_ROUTING,
new DiscreteValueValidator(new String[] {"0", "1", "2"}));
VALIDATORS.put(Secure.HEARING_AID_CALL_ROUTING,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 612badd..d27ff17 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1951,6 +1951,9 @@
dumpSetting(s, p,
Settings.Secure.SEARCH_LONG_PRESS_HOME_ENABLED,
SecureSettingsProto.Assist.SEARCH_LONG_PRESS_HOME_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED,
+ SecureSettingsProto.Assist.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED);
p.end(assistToken);
final long assistHandlesToken = p.start(SecureSettingsProto.ASSIST_HANDLES);
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 926e181..c086baa 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -862,6 +862,8 @@
<!-- Permission required for CTS test - CtsTelephonyProviderTestCases -->
<uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_BLOCKED_NUMBERS" />
+ <uses-permission android:name="android.permission.READ_BLOCKED_NUMBERS" />
<uses-permission android:name="android.permission.LOG_FOREGROUND_RESOURCE_USE" />
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 967a36b..78cacec 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -39,7 +39,6 @@
hyunyoungs@google.com
ikateryna@google.com
iyz@google.com
-jaggies@google.com
jamesoleary@google.com
jbolinger@google.com
jdemeulenaere@google.com
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 56576f1..c2072ac 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -420,3 +420,23 @@
}
}
+flag {
+ name: "get_connected_device_name_unsynchronized"
+ namespace: "systemui"
+ description: "Decide whether to fetch the connected bluetooth device name outside a synchronized block."
+ bug: "323995015"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "update_user_switcher_background"
+ namespace: "systemui"
+ description: "Decide whether to update user switcher in background thread."
+ bug: "322745650"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
new file mode 100644
index 0000000..b8c4fae
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalMaterial3Api::class)
+
+package com.android.compose
+
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.interaction.DragInteraction
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Slider
+import androidx.compose.material3.SliderState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.RoundRect
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.drawscope.clipPath
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import com.android.compose.modifiers.padding
+import com.android.compose.theme.LocalAndroidColorScheme
+
+/** Indicator corner radius used when the user drags the [PlatformSlider]. */
+private val DefaultPlatformSliderDraggingCornerRadius = 8.dp
+
+/**
+ * Platform slider implementation that displays a slider with an [icon] and a [label] at the start.
+ *
+ * @param onValueChangeFinished is called when the slider settles on a [value]. This callback
+ * shouldn't be used to react to value changes. Use [onValueChange] instead
+ * @param interactionSource - the [MutableInteractionSource] representing the stream of Interactions
+ * for this slider. You can create and pass in your own remembered instance to observe
+ * Interactions and customize the appearance / behavior of this slider in different states.
+ * @param colors - slider color scheme.
+ * @param draggingCornersRadius - radius of the slider indicator when the user drags it
+ * @param icon - icon at the start of the slider. Icon is limited to a square space at the start of
+ * the slider
+ * @param label - control shown next to the icon.
+ */
+@Composable
+fun PlatformSlider(
+ value: Float,
+ onValueChange: (Float) -> Unit,
+ modifier: Modifier = Modifier,
+ onValueChangeFinished: (() -> Unit)? = null,
+ valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
+ enabled: Boolean = true,
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+ colors: PlatformSliderColors =
+ if (isSystemInDarkTheme()) darkThemePlatformSliderColors()
+ else lightThemePlatformSliderColors(),
+ draggingCornersRadius: Dp = DefaultPlatformSliderDraggingCornerRadius,
+ icon: (@Composable (isDragging: Boolean) -> Unit)? = null,
+ label: (@Composable (isDragging: Boolean) -> Unit)? = null,
+) {
+ val sliderHeight: Dp = 64.dp
+ val iconWidth: Dp = sliderHeight
+ var isDragging by remember { mutableStateOf(false) }
+ LaunchedEffect(interactionSource) {
+ interactionSource.interactions.collect { interaction ->
+ when (interaction) {
+ is DragInteraction.Start -> {
+ isDragging = true
+ }
+ is DragInteraction.Cancel,
+ is DragInteraction.Stop -> {
+ isDragging = false
+ }
+ }
+ }
+ }
+ val paddingStart by
+ animateDpAsState(
+ targetValue =
+ if ((!isDragging && value == 0f) || icon == null) {
+ 16.dp
+ } else {
+ 0.dp
+ },
+ label = "LabelIconSpacingAnimation"
+ )
+
+ Box(modifier = modifier.height(sliderHeight)) {
+ Slider(
+ modifier = Modifier.fillMaxSize(),
+ value = value,
+ onValueChange = onValueChange,
+ valueRange = valueRange,
+ onValueChangeFinished = onValueChangeFinished,
+ interactionSource = interactionSource,
+ track = {
+ Track(
+ sliderState = it,
+ enabled = enabled,
+ colors = colors,
+ iconWidth = iconWidth,
+ draggingCornersRadius = draggingCornersRadius,
+ sliderHeight = sliderHeight,
+ isDragging = isDragging,
+ modifier = Modifier,
+ )
+ },
+ thumb = { Spacer(Modifier.width(iconWidth).height(sliderHeight)) },
+ )
+
+ if (icon != null || label != null) {
+ Row(modifier = Modifier.fillMaxSize()) {
+ icon?.let { iconComposable ->
+ Box(
+ modifier = Modifier.fillMaxHeight().aspectRatio(1f),
+ contentAlignment = Alignment.Center,
+ ) {
+ iconComposable(isDragging)
+ }
+ }
+
+ label?.let { labelComposable ->
+ Box(
+ modifier =
+ Modifier.fillMaxHeight()
+ .weight(1f)
+ .padding(start = { paddingStart.roundToPx() }),
+ contentAlignment = Alignment.CenterStart,
+ ) {
+ labelComposable(isDragging)
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun Track(
+ sliderState: SliderState,
+ enabled: Boolean,
+ colors: PlatformSliderColors,
+ iconWidth: Dp,
+ draggingCornersRadius: Dp,
+ sliderHeight: Dp,
+ isDragging: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
+ val iconWidthPx: Float
+ val halfIconWidthPx: Float
+ val targetIndicatorRadiusPx: Float
+ val halfSliderHeightPx: Float
+ with(LocalDensity.current) {
+ halfSliderHeightPx = sliderHeight.toPx() / 2
+ iconWidthPx = iconWidth.toPx()
+ halfIconWidthPx = iconWidthPx / 2
+ targetIndicatorRadiusPx =
+ if (isDragging) draggingCornersRadius.toPx() else halfSliderHeightPx
+ }
+
+ val indicatorRadiusPx: Float by
+ animateFloatAsState(
+ targetValue = targetIndicatorRadiusPx,
+ label = "PlatformSliderCornersAnimation",
+ )
+
+ val trackColor = colors.getTrackColor(enabled)
+ val indicatorColor = colors.getIndicatorColor(enabled)
+ val trackCornerRadius = CornerRadius(halfSliderHeightPx, halfSliderHeightPx)
+ val indicatorCornerRadius = CornerRadius(indicatorRadiusPx, indicatorRadiusPx)
+ Canvas(modifier.fillMaxSize()) {
+ val trackPath = Path()
+ trackPath.addRoundRect(
+ RoundRect(
+ left = -halfIconWidthPx,
+ top = 0f,
+ right = size.width + halfIconWidthPx,
+ bottom = size.height,
+ cornerRadius = trackCornerRadius,
+ )
+ )
+ drawPath(path = trackPath, color = trackColor)
+
+ clipPath(trackPath) {
+ val indicatorPath = Path()
+ if (isRtl) {
+ indicatorPath.addRoundRect(
+ RoundRect(
+ left =
+ size.width -
+ size.width * sliderState.coercedNormalizedValue -
+ halfIconWidthPx,
+ top = 0f,
+ right = size.width + iconWidthPx,
+ bottom = size.height,
+ topLeftCornerRadius = indicatorCornerRadius,
+ topRightCornerRadius = trackCornerRadius,
+ bottomRightCornerRadius = trackCornerRadius,
+ bottomLeftCornerRadius = indicatorCornerRadius,
+ )
+ )
+ } else {
+ indicatorPath.addRoundRect(
+ RoundRect(
+ left = -halfIconWidthPx,
+ top = 0f,
+ right = size.width * sliderState.coercedNormalizedValue + halfIconWidthPx,
+ bottom = size.height,
+ topLeftCornerRadius = trackCornerRadius,
+ topRightCornerRadius = indicatorCornerRadius,
+ bottomRightCornerRadius = indicatorCornerRadius,
+ bottomLeftCornerRadius = trackCornerRadius,
+ )
+ )
+ }
+ drawPath(path = indicatorPath, color = indicatorColor)
+ }
+ }
+}
+
+/** [SliderState.value] normalized using [SliderState.valueRange]. The result belongs to [0, 1] */
+private val SliderState.coercedNormalizedValue: Float
+ get() {
+ val dif = valueRange.endInclusive - valueRange.start
+ return if (dif == 0f) {
+ 0f
+ } else {
+ val coercedValue = value.coerceIn(valueRange.start, valueRange.endInclusive)
+ (coercedValue - valueRange.start) / dif
+ }
+ }
+
+/**
+ * [PlatformSlider] color scheme.
+ *
+ * @param trackColor fills the track of the slider. This is a "background" of the slider
+ * @param indicatorColor fills the slider from the start to the value
+ * @param iconColor is the default icon color
+ * @param labelColor is the default icon color
+ * @param disabledTrackColor is the [trackColor] when the PlatformSlider#enabled == false
+ * @param disabledIndicatorColor is the [indicatorColor] when the PlatformSlider#enabled == false
+ * @param disabledIconColor is the [iconColor] when the PlatformSlider#enabled == false
+ * @param disabledLabelColor is the [labelColor] when the PlatformSlider#enabled == false
+ */
+data class PlatformSliderColors(
+ val trackColor: Color,
+ val indicatorColor: Color,
+ val iconColor: Color,
+ val labelColor: Color,
+ val disabledTrackColor: Color,
+ val disabledIndicatorColor: Color,
+ val disabledIconColor: Color,
+ val disabledLabelColor: Color,
+)
+
+/** [PlatformSliderColors] for the light theme */
+@Composable
+private fun lightThemePlatformSliderColors() =
+ PlatformSliderColors(
+ trackColor = MaterialTheme.colorScheme.tertiaryContainer,
+ indicatorColor = LocalAndroidColorScheme.current.tertiaryFixedDim,
+ iconColor = MaterialTheme.colorScheme.onTertiaryContainer,
+ labelColor = MaterialTheme.colorScheme.onTertiaryContainer,
+ disabledTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
+ disabledIndicatorColor = MaterialTheme.colorScheme.surfaceContainerHighest,
+ disabledIconColor = MaterialTheme.colorScheme.outline,
+ disabledLabelColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
+
+/** [PlatformSliderColors] for the dark theme */
+@Composable
+private fun darkThemePlatformSliderColors() =
+ PlatformSliderColors(
+ trackColor = MaterialTheme.colorScheme.onTertiary,
+ indicatorColor = LocalAndroidColorScheme.current.onTertiaryFixedVariant,
+ iconColor = MaterialTheme.colorScheme.onTertiaryContainer,
+ labelColor = MaterialTheme.colorScheme.onTertiaryContainer,
+ disabledTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
+ disabledIndicatorColor = MaterialTheme.colorScheme.surfaceContainerHighest,
+ disabledIconColor = MaterialTheme.colorScheme.outline,
+ disabledLabelColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
+
+private fun PlatformSliderColors.getTrackColor(isEnabled: Boolean): Color =
+ if (isEnabled) trackColor else disabledTrackColor
+
+private fun PlatformSliderColors.getIndicatorColor(isEnabled: Boolean): Color =
+ if (isEnabled) indicatorColor else disabledIndicatorColor
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeAwareExtensions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeAwareExtensions.kt
new file mode 100644
index 0000000..a7de1ee
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeAwareExtensions.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui.composable
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.Edge as ComposeAwareEdge
+import com.android.compose.animation.scene.SceneKey as ComposeAwareSceneKey
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.TransitionKey as ComposeAwareTransitionKey
+import com.android.compose.animation.scene.UserAction as ComposeAwareUserAction
+import com.android.compose.animation.scene.UserActionDistance as ComposeAwareUserActionDistance
+import com.android.compose.animation.scene.UserActionResult as ComposeAwareUserActionResult
+import com.android.systemui.scene.shared.model.Direction
+import com.android.systemui.scene.shared.model.Edge
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.TransitionKey
+import com.android.systemui.scene.shared.model.UserAction
+import com.android.systemui.scene.shared.model.UserActionDistance
+import com.android.systemui.scene.shared.model.UserActionResult
+
+// TODO(b/293899074): remove this file once we can use the types from SceneTransitionLayout.
+
+fun SceneKey.asComposeAware(): ComposeAwareSceneKey {
+ return ComposeAwareSceneKey(
+ debugName = toString(),
+ identity = this,
+ )
+}
+
+fun TransitionKey.asComposeAware(): ComposeAwareTransitionKey {
+ return ComposeAwareTransitionKey(
+ debugName = debugName,
+ identity = this,
+ )
+}
+
+fun UserAction.asComposeAware(): ComposeAwareUserAction {
+ return when (this) {
+ is UserAction.Swipe ->
+ Swipe(
+ pointerCount = pointerCount,
+ fromSource =
+ when (this.fromEdge) {
+ null -> null
+ Edge.LEFT -> ComposeAwareEdge.Left
+ Edge.TOP -> ComposeAwareEdge.Top
+ Edge.RIGHT -> ComposeAwareEdge.Right
+ Edge.BOTTOM -> ComposeAwareEdge.Bottom
+ },
+ direction =
+ when (this.direction) {
+ Direction.LEFT -> SwipeDirection.Left
+ Direction.UP -> SwipeDirection.Up
+ Direction.RIGHT -> SwipeDirection.Right
+ Direction.DOWN -> SwipeDirection.Down
+ }
+ )
+ is UserAction.Back -> Back
+ }
+}
+
+fun UserActionResult.asComposeAware(): ComposeAwareUserActionResult {
+ val composeUnaware = this
+ return ComposeAwareUserActionResult(
+ toScene = composeUnaware.toScene.asComposeAware(),
+ transitionKey = composeUnaware.transitionKey?.asComposeAware(),
+ distance = composeUnaware.distance?.asComposeAware(),
+ )
+}
+
+fun UserActionDistance.asComposeAware(): ComposeAwareUserActionDistance {
+ val composeUnware = this
+ return object : ComposeAwareUserActionDistance {
+ override fun Density.absoluteDistance(
+ fromSceneSize: IntSize,
+ orientation: Orientation,
+ ): Float {
+ return composeUnware.absoluteDistance(
+ fromSceneWidth = fromSceneSize.width,
+ fromSceneHeight = fromSceneSize.height,
+ isHorizontal = orientation == Orientation.Horizontal,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeUnawareExtensions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeUnawareExtensions.kt
new file mode 100644
index 0000000..4c03664
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeUnawareExtensions.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui.composable
+
+import com.android.compose.animation.scene.ObservableTransitionState as ComposeAwareObservableTransitionState
+import com.android.compose.animation.scene.SceneKey as ComposeAwareSceneKey
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+
+fun ComposeAwareSceneKey.asComposeUnaware(): SceneKey {
+ return this.identity as SceneKey
+}
+
+fun ComposeAwareObservableTransitionState.asComposeUnaware(): ObservableTransitionState {
+ return when (this) {
+ is ComposeAwareObservableTransitionState.Idle ->
+ ObservableTransitionState.Idle(scene.asComposeUnaware())
+ is ComposeAwareObservableTransitionState.Transition ->
+ ObservableTransitionState.Transition(
+ fromScene = fromScene.asComposeUnaware(),
+ toScene = toScene.asComposeUnaware(),
+ progress = progress,
+ isInitiatedByUserInput = isInitiatedByUserInput,
+ isUserInputOngoing = isUserInputOngoing,
+ )
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
new file mode 100644
index 0000000..60c0b77
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.ui.composable
+
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.observableTransitionState
+import com.android.systemui.scene.shared.model.SceneDataSource
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.TransitionKey
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * An implementation of [SceneDataSource] that's backed by a [MutableSceneTransitionLayoutState].
+ */
+class SceneTransitionLayoutDataSource(
+ private val state: MutableSceneTransitionLayoutState,
+
+ /**
+ * The [CoroutineScope] of the @Composable that's using this, it's critical that this is *not*
+ * the application scope.
+ */
+ private val coroutineScope: CoroutineScope,
+) : SceneDataSource {
+ override val currentScene: StateFlow<SceneKey> =
+ state
+ .observableTransitionState()
+ .flatMapLatest { observableTransitionState ->
+ when (observableTransitionState) {
+ is ObservableTransitionState.Idle -> flowOf(observableTransitionState.scene)
+ is ObservableTransitionState.Transition ->
+ observableTransitionState.isUserInputOngoing.map { isUserInputOngoing ->
+ if (isUserInputOngoing) {
+ observableTransitionState.fromScene
+ } else {
+ observableTransitionState.toScene
+ }
+ }
+ }
+ }
+ .map { it.asComposeUnaware() }
+ .stateIn(
+ scope = coroutineScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = state.transitionState.currentScene.asComposeUnaware(),
+ )
+
+ override fun changeScene(
+ toScene: SceneKey,
+ transitionKey: TransitionKey?,
+ ) {
+ state.setTargetScene(
+ targetScene = toScene.asComposeAware(),
+ transitionKey = transitionKey?.asComposeAware(),
+ coroutineScope = coroutineScope,
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt
new file mode 100644
index 0000000..2e9ee5c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntryBiometricSettingsInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val biometricSettingsRepository = kosmos.biometricSettingsRepository
+ private val underTest = kosmos.deviceEntryBiometricSettingsInteractor
+
+ @Test
+ fun isCoex_true() = runTest {
+ val isCoex by collectLastValue(underTest.fingerprintAndFaceEnrolledAndEnabled)
+ biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ assertThat(isCoex).isTrue()
+ }
+
+ @Test
+ fun isCoex_faceOnly() = runTest {
+ val isCoex by collectLastValue(underTest.fingerprintAndFaceEnrolledAndEnabled)
+ biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ assertThat(isCoex).isFalse()
+ }
+
+ @Test
+ fun isCoex_fingerprintOnly() = runTest {
+ val isCoex by collectLastValue(underTest.fingerprintAndFaceEnrolledAndEnabled)
+ biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ assertThat(isCoex).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 6fc5be1..15cf83c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -53,7 +53,7 @@
val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
val primaryBouncerInteractor = kosmos.mockPrimaryBouncerInteractor
- val sysuiStatusBarStateController = kosmos.sysuiStatusBarStateController
+ private val sysuiStatusBarStateController = kosmos.sysuiStatusBarStateController
val underTest by lazy { kosmos.primaryBouncerToGoneTransitionViewModel }
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegatorTest.kt
new file mode 100644
index 0000000..ed4b1e6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegatorTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.shared.model
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.initialSceneKey
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SceneDataSourceDelegatorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val initialSceneKey = kosmos.initialSceneKey
+ private val fakeSceneDataSource = kosmos.fakeSceneDataSource
+
+ private val underTest = kosmos.sceneDataSourceDelegator
+
+ @Test
+ fun currentScene_withoutDelegate_startsWithInitialScene() =
+ testScope.runTest {
+ val currentScene by collectLastValue(underTest.currentScene)
+ underTest.setDelegate(null)
+
+ assertThat(currentScene).isEqualTo(initialSceneKey)
+ }
+
+ @Test
+ fun currentScene_withoutDelegate_doesNothing() =
+ testScope.runTest {
+ val currentScene by collectLastValue(underTest.currentScene)
+ underTest.setDelegate(null)
+ assertThat(currentScene).isNotEqualTo(SceneKey.Bouncer)
+
+ underTest.changeScene(toScene = SceneKey.Bouncer)
+
+ assertThat(currentScene).isEqualTo(initialSceneKey)
+ }
+
+ @Test
+ fun currentScene_withDelegate_startsWithInitialScene() =
+ testScope.runTest {
+ val currentScene by collectLastValue(underTest.currentScene)
+ assertThat(currentScene).isEqualTo(initialSceneKey)
+ }
+
+ @Test
+ fun currentScene_withDelegate_changesScenes() =
+ testScope.runTest {
+ val currentScene by collectLastValue(underTest.currentScene)
+ assertThat(currentScene).isNotEqualTo(SceneKey.Bouncer)
+
+ underTest.changeScene(toScene = SceneKey.Bouncer)
+
+ assertThat(currentScene).isEqualTo(SceneKey.Bouncer)
+ }
+
+ @Test
+ fun currentScene_reflectsDelegate() =
+ testScope.runTest {
+ val currentScene by collectLastValue(underTest.currentScene)
+
+ fakeSceneDataSource.changeScene(toScene = SceneKey.Bouncer)
+
+ assertThat(currentScene).isEqualTo(SceneKey.Bouncer)
+ }
+}
diff --git a/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml b/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml
index 50d5607..41fb57a 100644
--- a/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml
+++ b/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml
@@ -32,4 +32,24 @@
android:importantForAccessibility="no"
sysui:ignoreRightInset="true"
/>
+ <!-- Keyguard messages -->
+ <LinearLayout
+ android:id="@+id/alternate_bouncer_message_area_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:layout_marginTop="@dimen/status_bar_height"
+ android:layout_gravity="top|center_horizontal"
+ android:gravity="center_horizontal">
+ <com.android.keyguard.AuthKeyguardMessageArea
+ android:id="@+id/alternate_bouncer_message_area"
+ style="@style/Keyguard.TextView"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/keyguard_lock_padding"
+ android:gravity="center"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:focusable="true"/>
+ </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt
index b015f70..8c68eac 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt
@@ -20,23 +20,37 @@
import com.android.keyguard.logging.BiometricMessageDeferralLogger
import com.android.keyguard.logging.FaceMessageDeferralLogger
import com.android.systemui.Dumpable
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.res.R
import java.io.PrintWriter
import java.util.Objects
import javax.inject.Inject
+@SysUISingleton
+class FaceHelpMessageDeferralFactory
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val logBuffer: FaceMessageDeferralLogger,
+ private val dumpManager: DumpManager
+) {
+ fun create(): FaceHelpMessageDeferral {
+ return FaceHelpMessageDeferral(
+ resources = resources,
+ logBuffer = logBuffer,
+ dumpManager = dumpManager,
+ )
+ }
+}
+
/**
* Provides whether a face acquired help message should be shown immediately when its received or
* should be shown when face auth times out. See [updateMessage] and [getDeferredMessage].
*/
-@SysUISingleton
-class FaceHelpMessageDeferral
-@Inject
-constructor(
- @Main resources: Resources,
+class FaceHelpMessageDeferral(
+ resources: Resources,
logBuffer: FaceMessageDeferralLogger,
dumpManager: DumpManager
) :
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
index e6939f0..ff9cdbd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
@@ -39,6 +39,8 @@
configurationInteractor: ConfigurationInteractor,
displayStateInteractor: DisplayStateInteractor,
) {
+ val isUdfps: Flow<Boolean> = repository.sensorType.map { it.isUdfps() }
+
/**
* Devices with multiple physical displays use unique display ids to determine which sensor is
* on the active physical display. This value represents a unique physical display id.
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
index ef4554c..8197145 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
@@ -92,6 +92,7 @@
private const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update"
private const val TAG = "BouncerMessageInteractor"
+/** Handles business logic for the primary bouncer message area. */
@SysUISingleton
class BouncerMessageInteractor
@Inject
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractor.kt
new file mode 100644
index 0000000..55d2bfc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractor.kt
@@ -0,0 +1,174 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import android.content.res.Resources
+import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceMessage
+import com.android.systemui.deviceentry.shared.model.FaceTimeoutMessage
+import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage
+import com.android.systemui.deviceentry.shared.model.FingerprintMessage
+import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.res.R
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.filterNot
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/**
+ * BiometricMessage business logic. Filters biometric error/acquired/fail/success events for
+ * authentication events that should never surface a message to the user at the current device
+ * state.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class BiometricMessageInteractor
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
+ fingerprintPropertyInteractor: FingerprintPropertyInteractor,
+ faceAuthInteractor: DeviceEntryFaceAuthInteractor,
+ biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
+) {
+ private val faceHelp: Flow<HelpFaceAuthenticationStatus> =
+ faceAuthInteractor.authenticationStatus.filterIsInstance<HelpFaceAuthenticationStatus>()
+ private val faceError: Flow<ErrorFaceAuthenticationStatus> =
+ faceAuthInteractor.authenticationStatus.filterIsInstance<ErrorFaceAuthenticationStatus>()
+ private val faceFailure: Flow<FailedFaceAuthenticationStatus> =
+ faceAuthInteractor.authenticationStatus.filterIsInstance<FailedFaceAuthenticationStatus>()
+
+ /**
+ * The acquisition message ids to show message when both fingerprint and face are enrolled and
+ * enabled for device entry.
+ */
+ private val coExFaceAcquisitionMsgIdsToShow: Set<Int> =
+ resources.getIntArray(R.array.config_face_help_msgs_when_fingerprint_enrolled).toSet()
+
+ private fun ErrorFingerprintAuthenticationStatus.shouldSuppressError(): Boolean {
+ return isCancellationError() || isPowerPressedError()
+ }
+
+ private fun ErrorFaceAuthenticationStatus.shouldSuppressError(): Boolean {
+ return isCancellationError() || isUnableToProcessError()
+ }
+
+ private val fingerprintErrorMessage: Flow<FingerprintMessage> =
+ fingerprintAuthInteractor.fingerprintError
+ .filterNot { it.shouldSuppressError() }
+ .sample(biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed, ::Pair)
+ .filter { (errorStatus, fingerprintAuthAllowed) ->
+ fingerprintAuthAllowed || errorStatus.isLockoutError()
+ }
+ .map { (errorStatus, _) ->
+ when {
+ errorStatus.isLockoutError() -> FingerprintLockoutMessage(errorStatus.msg)
+ else -> FingerprintMessage(errorStatus.msg)
+ }
+ }
+
+ private val fingerprintHelpMessage: Flow<FingerprintMessage> =
+ fingerprintAuthInteractor.fingerprintHelp
+ .sample(biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed, ::Pair)
+ .filter { (_, fingerprintAuthAllowed) -> fingerprintAuthAllowed }
+ .map { (helpStatus, _) ->
+ FingerprintMessage(
+ helpStatus.msg,
+ )
+ }
+
+ private val fingerprintFailMessage: Flow<FingerprintMessage> =
+ fingerprintPropertyInteractor.isUdfps.flatMapLatest { isUdfps ->
+ fingerprintAuthInteractor.fingerprintFailure
+ .sample(biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed)
+ .filter { fingerprintAuthAllowed -> fingerprintAuthAllowed }
+ .map {
+ FingerprintMessage(
+ if (isUdfps) {
+ resources.getString(
+ com.android.internal.R.string.fingerprint_udfps_error_not_match
+ )
+ } else {
+ resources.getString(
+ com.android.internal.R.string.fingerprint_error_not_match
+ )
+ },
+ )
+ }
+ }
+
+ val fingerprintMessage: Flow<FingerprintMessage> =
+ merge(
+ fingerprintErrorMessage,
+ fingerprintFailMessage,
+ fingerprintHelpMessage,
+ )
+
+ private val faceHelpMessage: Flow<FaceMessage> =
+ biometricSettingsInteractor.fingerprintAndFaceEnrolledAndEnabled
+ .flatMapLatest { fingerprintAndFaceEnrolledAndEnabled ->
+ if (fingerprintAndFaceEnrolledAndEnabled) {
+ faceHelp.filter { faceAuthHelpStatus ->
+ coExFaceAcquisitionMsgIdsToShow.contains(faceAuthHelpStatus.msgId)
+ }
+ } else {
+ faceHelp
+ }
+ }
+ .sample(biometricSettingsInteractor.faceAuthCurrentlyAllowed, ::Pair)
+ .filter { (_, faceAuthCurrentlyAllowed) -> faceAuthCurrentlyAllowed }
+ .map { (status, _) -> FaceMessage(status.msg) }
+
+ private val faceFailureMessage: Flow<FaceMessage> =
+ faceFailure
+ .sample(biometricSettingsInteractor.faceAuthCurrentlyAllowed)
+ .filter { faceAuthCurrentlyAllowed -> faceAuthCurrentlyAllowed }
+ .map { FaceMessage(resources.getString(R.string.keyguard_face_failed)) }
+
+ private val faceErrorMessage: Flow<FaceMessage> =
+ faceError
+ .filterNot { it.shouldSuppressError() }
+ .sample(biometricSettingsInteractor.faceAuthCurrentlyAllowed, ::Pair)
+ .filter { (errorStatus, faceAuthCurrentlyAllowed) ->
+ faceAuthCurrentlyAllowed || errorStatus.isLockoutError()
+ }
+ .map { (status, _) ->
+ when {
+ status.isTimeoutError() -> FaceTimeoutMessage(status.msg)
+ else -> FaceMessage(status.msg)
+ }
+ }
+
+ // TODO(b/317215391): support showing face acquired messages on timeout + face lockout errors
+ val faceMessage: Flow<FaceMessage> =
+ merge(
+ faceHelpMessage,
+ faceFailureMessage,
+ faceErrorMessage,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt
new file mode 100644
index 0000000..4515fcb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/** Encapsulates business logic for device entry biometric settings. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class DeviceEntryBiometricSettingsInteractor
+@Inject
+constructor(
+ repository: BiometricSettingsRepository,
+) {
+ val fingerprintAuthCurrentlyAllowed: Flow<Boolean> =
+ repository.isFingerprintAuthCurrentlyAllowed
+ val faceAuthCurrentlyAllowed: Flow<Boolean> = repository.isFaceAuthCurrentlyAllowed
+
+ /** Whether both fingerprint and face are enrolled and enabled for device entry. */
+ val fingerprintAndFaceEnrolledAndEnabled: Flow<Boolean> =
+ combine(
+ repository.isFingerprintEnrolledAndEnabled,
+ repository.isFaceAuthEnrolledAndEnabled,
+ ) { fpEnabled, faceEnabled ->
+ fpEnabled && faceEnabled
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
index 2461c26..a5f6f7c 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
@@ -18,8 +18,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterIsInstance
@@ -30,9 +32,6 @@
constructor(
repository: DeviceEntryFingerprintAuthRepository,
) {
- val fingerprintFailure: Flow<FailFingerprintAuthenticationStatus> =
- repository.authenticationStatus.filterIsInstance<FailFingerprintAuthenticationStatus>()
-
/** Whether fingerprint authentication is currently running or not */
val isRunning: Flow<Boolean> = repository.isRunning
@@ -41,4 +40,11 @@
repository.authenticationStatus
val isLockedOut: Flow<Boolean> = repository.isLockedOut
+
+ val fingerprintFailure: Flow<FailFingerprintAuthenticationStatus> =
+ repository.authenticationStatus.filterIsInstance<FailFingerprintAuthenticationStatus>()
+ val fingerprintError: Flow<ErrorFingerprintAuthenticationStatus> =
+ repository.authenticationStatus.filterIsInstance<ErrorFingerprintAuthenticationStatus>()
+ val fingerprintHelp: Flow<HelpFingerprintAuthenticationStatus> =
+ repository.authenticationStatus.filterIsInstance<HelpFingerprintAuthenticationStatus>()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
similarity index 88%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
index 6382338..79455eb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -11,10 +11,10 @@
* 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
+ * limitations under the License.
*/
-package com.android.systemui.keyguard.domain.interactor
+package com.android.systemui.deviceentry.domain.interactor
import android.content.Context
import android.content.Intent
@@ -22,7 +22,10 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.shared.model.BiometricMessage
+import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.plugins.ActivityStarter
@@ -40,7 +43,6 @@
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.launch
/** Business logic for handling authentication events when an app is occluding the lockscreen. */
@@ -77,16 +79,15 @@
private val fingerprintLockoutEvents: Flow<Unit> =
fingerprintAuthRepository.authenticationStatus
.ifKeyguardOccludedByApp()
- .filter { it is ErrorFingerprintAuthenticationStatus && it.isLockoutMessage() }
+ .filter { it is ErrorFingerprintAuthenticationStatus && it.isLockoutError() }
.map {} // maps FingerprintAuthenticationStatus => Unit
val message: Flow<BiometricMessage?> =
- merge(
- biometricMessageInteractor.fingerprintErrorMessage.filterNot {
- it.isFingerprintLockoutMessage()
- },
- biometricMessageInteractor.fingerprintFailMessage,
- biometricMessageInteractor.fingerprintHelpMessage,
- )
+ biometricMessageInteractor.fingerprintMessage
+ .filterNot { fingerprintMessage ->
+ // On lockout, the device will show the bouncer. Let's not show the message
+ // before the transition or else it'll look flickery.
+ fingerprintMessage is FingerprintLockoutMessage
+ }
.ifKeyguardOccludedByApp(/* elseFlow */ flowOf(null))
init {
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/BiometricMessageModels.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/BiometricMessageModels.kt
new file mode 100644
index 0000000..118215c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/BiometricMessageModels.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.shared.model
+
+/**
+ * BiometricMessage provided by
+ * [com.android.systemui.deviceentry.domain.interactor.BiometricMessageInteractor]
+ */
+sealed class BiometricMessage(
+ val message: String?,
+)
+
+/** Face biometric message */
+open class FaceMessage(faceMessage: String?) : BiometricMessage(faceMessage)
+
+data class FaceTimeoutMessage(
+ private val faceTimeoutMessage: String?,
+) : FaceMessage(faceTimeoutMessage)
+
+/** Fingerprint biometric message */
+open class FingerprintMessage(fingerprintMessage: String?) : BiometricMessage(fingerprintMessage)
+
+data class FingerprintLockoutMessage(
+ private val fingerprintLockoutMessage: String?,
+) : FingerprintMessage(fingerprintLockoutMessage)
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/FaceAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/FaceAuthenticationModels.kt
index f006b34..5f1667a 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/FaceAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/FaceAuthenticationModels.kt
@@ -71,11 +71,16 @@
*/
fun isCancellationError() = msgId == FaceManager.FACE_ERROR_CANCELED
+ fun isUnableToProcessError() = msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS
+
/** Method that checks if [msgId] is a hardware error. */
fun isHardwareError() =
msgId == FaceManager.FACE_ERROR_HW_UNAVAILABLE ||
msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS
+ /** Method that checks if [msgId] is a timeout error. */
+ fun isTimeoutError() = msgId == FaceManager.FACE_ERROR_TIMEOUT
+
companion object {
/**
* Error message that is created when cancel confirmation is not received from FaceManager
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt
deleted file mode 100644
index 508f71a..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.domain.interactor
-
-import android.content.res.Resources
-import android.hardware.biometrics.BiometricSourceType
-import android.hardware.biometrics.BiometricSourceType.FINGERPRINT
-import android.hardware.fingerprint.FingerprintManager
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED
-import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
-import com.android.systemui.biometrics.shared.model.FingerprintSensorType
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
-import com.android.systemui.keyguard.util.IndicationHelper
-import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterNot
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.map
-
-/**
- * BiometricMessage business logic. Filters biometric error/acquired/fail/success events for
- * authentication events that should never surface a message to the user at the current device
- * state.
- */
-@ExperimentalCoroutinesApi
-@SysUISingleton
-class BiometricMessageInteractor
-@Inject
-constructor(
- @Main private val resources: Resources,
- private val fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
- private val fingerprintPropertyRepository: FingerprintPropertyRepository,
- private val indicationHelper: IndicationHelper,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-) {
- val fingerprintErrorMessage: Flow<BiometricMessage> =
- fingerprintAuthRepository.authenticationStatus
- .filter {
- it is ErrorFingerprintAuthenticationStatus &&
- !indicationHelper.shouldSuppressErrorMsg(FINGERPRINT, it.msgId)
- }
- .map {
- val errorStatus = it as ErrorFingerprintAuthenticationStatus
- BiometricMessage(
- FINGERPRINT,
- BiometricMessageType.ERROR,
- errorStatus.msgId,
- errorStatus.msg,
- )
- }
-
- val fingerprintHelpMessage: Flow<BiometricMessage> =
- fingerprintAuthRepository.authenticationStatus
- .filter { it is HelpFingerprintAuthenticationStatus }
- .filterNot { isPrimaryAuthRequired() }
- .map {
- val helpStatus = it as HelpFingerprintAuthenticationStatus
- BiometricMessage(
- FINGERPRINT,
- BiometricMessageType.HELP,
- helpStatus.msgId,
- helpStatus.msg,
- )
- }
-
- val fingerprintFailMessage: Flow<BiometricMessage> =
- isUdfps().flatMapLatest { isUdfps ->
- fingerprintAuthRepository.authenticationStatus
- .filter { it is FailFingerprintAuthenticationStatus }
- .filterNot { isPrimaryAuthRequired() }
- .map {
- BiometricMessage(
- FINGERPRINT,
- BiometricMessageType.FAIL,
- BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
- if (isUdfps) {
- resources.getString(
- com.android.internal.R.string.fingerprint_udfps_error_not_match
- )
- } else {
- resources.getString(
- com.android.internal.R.string.fingerprint_error_not_match
- )
- },
- )
- }
- }
-
- private fun isUdfps() =
- fingerprintPropertyRepository.sensorType.map {
- it == FingerprintSensorType.UDFPS_OPTICAL ||
- it == FingerprintSensorType.UDFPS_ULTRASONIC
- }
-
- private fun isPrimaryAuthRequired(): Boolean {
- // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
- // as long as primary auth, i.e. PIN/pattern/password, is required), so it's ok to
- // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
- // check of whether non-strong biometric is allowed since strong biometrics can still be
- // used.
- return !keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
- }
-}
-
-data class BiometricMessage(
- val source: BiometricSourceType,
- val type: BiometricMessageType,
- val id: Int,
- val message: String?,
-) {
- fun isFingerprintLockoutMessage(): Boolean {
- return source == FINGERPRINT &&
- type == BiometricMessageType.ERROR &&
- (id == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT ||
- id == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
- }
-}
-
-enum class BiometricMessageType {
- HELP,
- ERROR,
- FAIL,
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
index 474de77..d8b7b4a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.shared.model
+import android.hardware.biometrics.BiometricFingerprintConstants
import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD
import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
import android.hardware.fingerprint.FingerprintManager
@@ -61,8 +62,14 @@
// present to break equality check if the same error occurs repeatedly.
val createdAt: Long = elapsedRealtime(),
) : FingerprintAuthenticationStatus() {
- fun isLockoutMessage(): Boolean {
- return msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT ||
+ fun isCancellationError(): Boolean =
+ msgId == BiometricFingerprintConstants.FINGERPRINT_ERROR_CANCELED ||
+ msgId == BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED
+
+ fun isPowerPressedError(): Boolean =
+ msgId == BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED
+
+ fun isLockoutError(): Boolean =
+ msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT ||
msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerMessageAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerMessageAreaViewBinder.kt
new file mode 100644
index 0000000..9186dde
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerMessageAreaViewBinder.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.keyguard.AuthKeyguardMessageArea
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerMessageAreaViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Binds the alternate bouncer message view to its view-model. */
+@ExperimentalCoroutinesApi
+object AlternateBouncerMessageAreaViewBinder {
+
+ /** Binds the view to the view-model, continuing to update the former based on the latter. */
+ @JvmStatic
+ fun bind(
+ view: AuthKeyguardMessageArea,
+ viewModel: AlternateBouncerMessageAreaViewModel,
+ ) {
+ view.setIsVisible(true)
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.message.collect { biometricMsg ->
+ if (biometricMsg == null) {
+ view.setMessage("", true)
+ } else {
+ view.setMessage(biometricMsg.message, true)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
index b2a3549..d400210 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
@@ -62,6 +62,11 @@
alternateBouncerDependencies.udfpsAccessibilityOverlayViewModel,
)
+ AlternateBouncerMessageAreaViewBinder.bind(
+ view = view.requireViewById(R.id.alternate_bouncer_message_area),
+ viewModel = alternateBouncerDependencies.messageAreaViewModel,
+ )
+
val scrim = view.requireViewById(R.id.alternate_bouncer_scrim) as ScrimView
val viewModel = alternateBouncerDependencies.viewModel
val swipeUpAnywhereGestureHandler =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerDependencies.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerDependencies.kt
index 6846886..065c20a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerDependencies.kt
@@ -36,4 +36,5 @@
val udfpsIconViewModel: AlternateBouncerUdfpsIconViewModel,
val udfpsAccessibilityOverlayViewModel:
Lazy<AlternateBouncerUdfpsAccessibilityOverlayViewModel>,
+ val messageAreaViewModel: AlternateBouncerMessageAreaViewModel,
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt
new file mode 100644
index 0000000..49c64bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.deviceentry.domain.interactor.BiometricMessageInteractor
+import com.android.systemui.deviceentry.shared.model.BiometricMessage
+import com.android.systemui.deviceentry.shared.model.FaceMessage
+import com.android.systemui.deviceentry.shared.model.FaceTimeoutMessage
+import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage
+import com.android.systemui.deviceentry.shared.model.FingerprintMessage
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterNot
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.merge
+
+/** View model for the alternate bouncer message area. */
+@ExperimentalCoroutinesApi
+class AlternateBouncerMessageAreaViewModel
+@Inject
+constructor(
+ biometricMessageInteractor: BiometricMessageInteractor,
+ alternateBouncerInteractor: AlternateBouncerInteractor,
+) {
+
+ private val faceHelp: Flow<FaceMessage> =
+ biometricMessageInteractor.faceMessage.filterNot { faceMessage ->
+ faceMessage !is FaceTimeoutMessage
+ }
+ private val fingerprintMessages: Flow<FingerprintMessage> =
+ biometricMessageInteractor.fingerprintMessage.filterNot { fingerprintMessage ->
+ // On lockout, the device will show the bouncer. Let's not show the message
+ // before the transition or else it'll look flickery.
+ fingerprintMessage is FingerprintLockoutMessage
+ }
+
+ val message: Flow<BiometricMessage?> =
+ alternateBouncerInteractor.isVisible.flatMapLatest { isVisible ->
+ if (isVisible) {
+ merge(
+ faceHelp,
+ fingerprintMessages,
+ )
+ } else {
+ flowOf(null)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index a3d5453..c9cf0c3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -34,9 +34,11 @@
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
@@ -175,19 +177,33 @@
flowOf(BurnInOffsets(x = 0, y = 0, progress = 0f))
}
}
+
+ private val isUnlocked: Flow<Boolean> =
+ deviceEntryInteractor.isUnlocked.flatMapLatest { isUnlocked ->
+ if (!isUnlocked) {
+ flowOf(false)
+ } else {
+ flow {
+ // delay in case device ends up transitioning away from the lock screen;
+ // we don't want to animate to the unlocked icon and just let the
+ // icon fade with the transition to GONE
+ delay(UNLOCKED_DELAY_MS)
+ emit(true)
+ }
+ }
+ }
+
val iconType: Flow<DeviceEntryIconView.IconType> =
combine(
deviceEntryUdfpsInteractor.isListeningForUdfps,
- deviceEntryInteractor.isUnlocked,
+ keyguardInteractor.isKeyguardDismissible,
) { isListeningForUdfps, isUnlocked ->
- if (isUnlocked) {
+ if (isListeningForUdfps) {
+ DeviceEntryIconView.IconType.FINGERPRINT
+ } else if (isUnlocked) {
DeviceEntryIconView.IconType.UNLOCK
} else {
- if (isListeningForUdfps) {
- DeviceEntryIconView.IconType.FINGERPRINT
- } else {
- DeviceEntryIconView.IconType.LOCK
- }
+ DeviceEntryIconView.IconType.LOCK
}
}
val isLongPressEnabled: Flow<Boolean> =
@@ -229,6 +245,10 @@
DeviceEntryIconView.AccessibilityHintType.NONE
}
}
+
+ companion object {
+ const val UNLOCKED_DELAY_MS = 50L
+ }
}
data class BurnInOffsets(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludingAppDeviceEntryMessageViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludingAppDeviceEntryMessageViewModel.kt
index 3a162d7..846bcbf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludingAppDeviceEntryMessageViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludingAppDeviceEntryMessageViewModel.kt
@@ -18,8 +18,8 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.domain.interactor.BiometricMessage
-import com.android.systemui.keyguard.domain.interactor.OccludingAppDeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.OccludingAppDeviceEntryInteractor
+import com.android.systemui.deviceentry.shared.model.BiometricMessage
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt
new file mode 100644
index 0000000..f7b45e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.shared.model
+
+import kotlinx.coroutines.flow.StateFlow
+
+/** Defines interface for classes that provide access to scene state. */
+interface SceneDataSource {
+
+ /**
+ * The current scene, as seen by the real data source in the UI layer.
+ *
+ * During a transition between two scenes, the original scene will still be reflected in
+ * [currentScene] until a time when the UI layer decides to commit the change, which is when
+ * [currentScene] will have the value of the target/new scene.
+ */
+ val currentScene: StateFlow<SceneKey>
+
+ /**
+ * Asks for an asynchronous scene switch to [toScene], which will use the corresponding
+ * installed transition or the one specified by [transitionKey], if provided.
+ */
+ fun changeScene(
+ toScene: SceneKey,
+ transitionKey: TransitionKey? = null,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt
new file mode 100644
index 0000000..a50830c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.shared.model
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Delegates calls to a runtime-provided [SceneDataSource] or to a no-op implementation if a
+ * delegate isn't set.
+ */
+@SysUISingleton
+class SceneDataSourceDelegator
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ config: SceneContainerConfig,
+) : SceneDataSource {
+
+ private val noOpDelegate = NoOpSceneDataSource(config.initialSceneKey)
+ private val delegateMutable = MutableStateFlow<SceneDataSource>(noOpDelegate)
+
+ override val currentScene: StateFlow<SceneKey> =
+ delegateMutable
+ .flatMapLatest { delegate -> delegate.currentScene }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = config.initialSceneKey,
+ )
+
+ override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) {
+ delegateMutable.value.changeScene(
+ toScene = toScene,
+ transitionKey = transitionKey,
+ )
+ }
+
+ /**
+ * Binds the current, dependency injection provided [SceneDataSource] to the given object.
+ *
+ * In other words: once this is invoked, the state and functionality of the [SceneDataSource]
+ * will be served by the given [delegate].
+ *
+ * If `null` is passed in, the delegator will use a no-op implementation of [SceneDataSource].
+ *
+ * This removes any previously set delegate.
+ */
+ fun setDelegate(delegate: SceneDataSource?) {
+ delegateMutable.value = delegate ?: noOpDelegate
+ }
+
+ private class NoOpSceneDataSource(
+ initialSceneKey: SceneKey,
+ ) : SceneDataSource {
+ override val currentScene: StateFlow<SceneKey> =
+ MutableStateFlow(initialSceneKey).asStateFlow()
+ override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) = Unit
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKey.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKey.kt
new file mode 100644
index 0000000..87332ae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKey.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.shared.model
+
+/**
+ * Key for a transition. This can be used to specify which transition spec should be used when
+ * starting the transition between two scenes.
+ */
+data class TransitionKey(
+ val debugName: String,
+ val identity: Any = Object(),
+)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/UserActionDistance.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/UserActionDistance.kt
new file mode 100644
index 0000000..b93f837
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/UserActionDistance.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.shared.model
+
+interface UserActionDistance {
+
+ /**
+ * Return the **absolute** distance of the user action (in pixels) given the size of the scene
+ * we are animating from and the orientation.
+ */
+ fun absoluteDistance(fromSceneWidth: Int, fromSceneHeight: Int, isHorizontal: Boolean): Float
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/UserActionResult.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/UserActionResult.kt
new file mode 100644
index 0000000..e1b96e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/UserActionResult.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.shared.model
+
+data class UserActionResult(
+
+ /** The scene we should be transitioning due to the [UserAction]. */
+ val toScene: SceneKey,
+
+ /**
+ * The distance the action takes to animate from 0% to 100%.
+ *
+ * If `null`, a default distance will be used depending on the [UserAction] performed.
+ */
+ val distance: UserActionDistance? = null,
+
+ /**
+ * The key of the transition that should be used, if a specific one should be used.
+ *
+ * If `null`, the transition used will be the corresponding transition from the collection
+ * passed into the UI layer.
+ */
+ val transitionKey: TransitionKey? = null,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 04d9b0c..14230ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -87,6 +87,7 @@
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FaceHelpMessageDeferral;
+import com.android.systemui.biometrics.FaceHelpMessageDeferralFactory;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -270,7 +271,7 @@
ScreenLifecycle screenLifecycle,
KeyguardBypassController keyguardBypassController,
AccessibilityManager accessibilityManager,
- FaceHelpMessageDeferral faceHelpMessageDeferral,
+ FaceHelpMessageDeferralFactory faceHelpMessageDeferral,
KeyguardLogger keyguardLogger,
AlternateBouncerInteractor alternateBouncerInteractor,
AlarmManager alarmManager,
@@ -308,7 +309,7 @@
mIndicationHelper = indicationHelper;
mKeyguardInteractor = keyguardInteractor;
- mFaceAcquiredMessageDeferral = faceHelpMessageDeferral;
+ mFaceAcquiredMessageDeferral = faceHelpMessageDeferral.create();
mCoExFaceAcquisitionMsgIdsToShow = new HashSet<>();
int[] msgIds = context.getResources().getIntArray(
com.android.systemui.res.R.array.config_face_help_msgs_when_fingerprint_enrolled);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt
index e7012ea51..17fc5c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt
@@ -20,35 +20,14 @@
import android.app.Notification
import android.content.Context
-import android.content.pm.ApplicationInfo
import android.text.TextUtils
-import android.util.Log
import androidx.annotation.MainThread
import com.android.systemui.res.R
-/**
- * Returns accessibility content description for a given notification.
- *
- * NOTE: This is a relatively slow call.
- */
+/** Returns accessibility content description for a given notification. */
@MainThread
fun contentDescForNotification(c: Context, n: Notification?): CharSequence {
- var appName = ""
- try {
- val builder = Notification.Builder.recoverBuilder(c, n)
- appName = builder.loadHeaderAppName()
- } catch (e: RuntimeException) {
- Log.e("ContentDescription", "Unable to recover builder", e)
- // Trying to get the app name from the app info instead.
- val appInfo =
- n?.extras?.getParcelable(
- Notification.EXTRA_BUILDER_APPLICATION_INFO,
- ApplicationInfo::class.java
- )
- if (appInfo != null) {
- appName = appInfo.loadLabel(c.packageManager).toString()
- }
- }
+ val appName = n?.loadHeaderAppName(c) ?: ""
val title = n?.extras?.getCharSequence(Notification.EXTRA_TITLE)
val text = n?.extras?.getCharSequence(Notification.EXTRA_TEXT)
val ticker = n?.tickerText
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index de3a626..c8d6abe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -311,12 +311,13 @@
boundViewsByNotifKey[it.notifKey]?.first
}
val childCount = view.childCount
+ val toRemove = mutableListOf<View>()
for (i in 0 until childCount) {
val actual = view.getChildAt(i)
val expected = expectedChildren.getOrNull(i)
if (expected == null) {
Log.wtf(TAG, "[$logTag] Unexpected child $actual")
- view.removeView(actual)
+ toRemove.add(actual)
continue
}
if (actual === expected) {
@@ -325,6 +326,9 @@
view.removeView(expected)
view.addView(expected, i)
}
+ for (child in toRemove) {
+ view.removeView(child)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 15fde0e..634de7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -826,16 +826,11 @@
}
}
if (row.isPinned()) {
- if (NotificationsImprovedHunAnimation.isEnabled()) {
- // Make sure row yTranslation is at the HUN yTranslation,
- // which accounts for AmbientState.stackTopMargin in split-shade.
- childState.setYTranslation(headsUpTranslation);
- } else {
- // Make sure row yTranslation is at maximum the HUN yTranslation,
- // which accounts for AmbientState.stackTopMargin in split-shade.
- childState.setYTranslation(
- Math.max(childState.getYTranslation(), headsUpTranslation));
- }
+ // Make sure row yTranslation is at at least the HUN yTranslation,
+ // which accounts for AmbientState.stackTopMargin in split-shade.
+ // Once we start opening the shade, we keep the previously calculated translation.
+ childState.setYTranslation(
+ Math.max(childState.getYTranslation(), headsUpTranslation));
childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
childState.hidden = false;
ExpandableViewState topState =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 2206be5..38b3718 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -20,6 +20,7 @@
import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import static com.android.systemui.Flags.updateUserSwitcherBackground;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -43,6 +44,7 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.battery.BatteryMeterViewController;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -122,6 +124,7 @@
private final SecureSettings mSecureSettings;
private final CommandQueue mCommandQueue;
private final Executor mMainExecutor;
+ private final Executor mBackgroundExecutor;
private final Object mLock = new Object();
private final KeyguardLogger mLogger;
@@ -296,6 +299,7 @@
SecureSettings secureSettings,
CommandQueue commandQueue,
@Main Executor mainExecutor,
+ @Background Executor backgroundExecutor,
KeyguardLogger logger,
NotificationMediaManager notificationMediaManager,
StatusOverlayHoverListenerFactory statusOverlayHoverListenerFactory
@@ -323,6 +327,7 @@
mSecureSettings = secureSettings;
mCommandQueue = commandQueue;
mMainExecutor = mainExecutor;
+ mBackgroundExecutor = backgroundExecutor;
mLogger = logger;
mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
@@ -607,8 +612,18 @@
* Updates visibility of the user switcher button based on {@link android.os.UserManager} state.
*/
private void updateUserSwitcher() {
- mView.setUserSwitcherEnabled(mUserManager.isUserSwitcherEnabled(getResources().getBoolean(
- R.bool.qs_show_user_switcher_for_single_user)));
+ if (updateUserSwitcherBackground()) {
+ mBackgroundExecutor.execute(() -> {
+ final boolean isUserSwitcherEnabled = mUserManager.isUserSwitcherEnabled(
+ getResources().getBoolean(R.bool.qs_show_user_switcher_for_single_user));
+ mMainExecutor.execute(() -> {
+ mView.setUserSwitcherEnabled(isUserSwitcherEnabled);
+ });
+ });
+ } else {
+ mView.setUserSwitcherEnabled(mUserManager.isUserSwitcherEnabled(
+ getResources().getBoolean(R.bool.qs_show_user_switcher_for_single_user)));
+ }
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 7f7eb04..9d70f42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -439,8 +439,11 @@
}
mBypassController = bypassController;
mNotificationContainer = notificationContainer;
- mKeyguardMessageAreaController = mKeyguardMessageAreaFactory.create(
- centralSurfaces.getKeyguardMessageArea());
+ if (!DeviceEntryUdfpsRefactor.isEnabled()) {
+ mKeyguardMessageAreaController = mKeyguardMessageAreaFactory.create(
+ centralSurfaces.getKeyguardMessageArea());
+ }
+
mCentralSurfacesRegistered = true;
registerListeners();
@@ -863,6 +866,7 @@
final boolean isShowingAlternateBouncer = mAlternateBouncerInteractor.isVisibleState();
if (mKeyguardMessageAreaController != null) {
+ DeviceEntryUdfpsRefactor.assertInLegacyMode();
mKeyguardMessageAreaController.setIsVisible(isShowingAlternateBouncer);
mKeyguardMessageAreaController.setMessage("");
}
@@ -1447,6 +1451,7 @@
public void setKeyguardMessage(String message, ColorStateList colorState) {
if (mAlternateBouncerInteractor.isVisibleState()) {
if (mKeyguardMessageAreaController != null) {
+ DeviceEntryUdfpsRefactor.assertInLegacyMode();
mKeyguardMessageAreaController.setMessage(message);
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index fc2f6e9..c089092 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -35,6 +35,7 @@
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfile;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.systemui.Flags;
import com.android.systemui.bluetooth.BluetoothLogger;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -240,9 +241,21 @@
@WorkerThread
@Override
public String getConnectedDeviceName() {
- synchronized (mConnectedDevices) {
- if (mConnectedDevices.size() == 1) {
- return mConnectedDevices.get(0).getName();
+ if (Flags.getConnectedDeviceNameUnsynchronized()) {
+ CachedBluetoothDevice connectedDevice = null;
+ // Calling the getName() API for CachedBluetoothDevice outside the synchronized block
+ // so that the main thread is not blocked.
+ synchronized (mConnectedDevices) {
+ if (mConnectedDevices.size() == 1) {
+ connectedDevice = mConnectedDevices.get(0);
+ }
+ }
+ return connectedDevice != null ? connectedDevice.getName() : null;
+ } else {
+ synchronized (mConnectedDevices) {
+ if (mConnectedDevices.size() == 1) {
+ return mConnectedDevices.get(0).getName();
+ }
}
}
return null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/tv/OWNERS
new file mode 100644
index 0000000..43450c4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/OWNERS
@@ -0,0 +1,5 @@
+# Android TV Core Framework
+rgl@google.com
+valiiftime@google.com
+galinap@google.com
+robhor@google.com
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index 1af5c46..67d6a7f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -16,11 +16,14 @@
package com.android.systemui.volume.dagger
+import android.app.NotificationManager
import android.content.Context
import android.media.AudioManager
import com.android.settingslib.media.data.repository.SpatializerRepository
import com.android.settingslib.media.data.repository.SpatializerRepositoryImpl
import com.android.settingslib.media.domain.interactor.SpatializerInteractor
+import com.android.settingslib.statusbar.notification.data.repository.NotificationsSoundPolicyRepository
+import com.android.settingslib.statusbar.notification.data.repository.NotificationsSoundPolicyRepositoryImpl
import com.android.settingslib.volume.data.repository.AudioRepository
import com.android.settingslib.volume.data.repository.AudioRepositoryImpl
import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
@@ -68,5 +71,19 @@
@Provides
fun provideSpatializerInetractor(repository: SpatializerRepository): SpatializerInteractor =
SpatializerInteractor(repository)
+
+ @Provides
+ fun provideNotificationsSoundPolicyRepository(
+ context: Context,
+ notificationManager: NotificationManager,
+ @Background coroutineContext: CoroutineContext,
+ @Application coroutineScope: CoroutineScope,
+ ): NotificationsSoundPolicyRepository =
+ NotificationsSoundPolicyRepositoryImpl(
+ context,
+ notificationManager,
+ coroutineScope,
+ coroutineContext,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt
index 1c17fc3..bb4a2c4 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt
@@ -77,17 +77,17 @@
controller.setSuggestionCardIds(storeLocations.toSet())
}
- private val binder: IWalletContextualLocationsService.Stub
- = object : IWalletContextualLocationsService.Stub() {
- override fun addWalletCardsUpdatedListener(listener: IWalletCardsUpdatedListener) {
- addWalletCardsUpdatedListenerInternal(listener)
+ private val binder: IWalletContextualLocationsService.Stub =
+ object : IWalletContextualLocationsService.Stub() {
+ override fun addWalletCardsUpdatedListener(listener: IWalletCardsUpdatedListener) {
+ addWalletCardsUpdatedListenerInternal(listener)
+ }
+ override fun onWalletContextualLocationsStateUpdated(storeLocations: List<String>) {
+ onWalletContextualLocationsStateUpdatedInternal(storeLocations)
+ }
}
- override fun onWalletContextualLocationsStateUpdated(storeLocations: List<String>) {
- onWalletContextualLocationsStateUpdatedInternal(storeLocations)
- }
- }
companion object {
private const val TAG = "WalletContextualLocationsService"
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
index e3be3822..f07932c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
@@ -19,7 +19,6 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.core.animation.doOnEnd
-import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.doOnEnd
@@ -31,7 +30,6 @@
@RunWith(AndroidTestingRunner::class)
@SmallTest
@RunWithLooper
-@FlakyTest(bugId = 302149604)
class AnimatorTestRuleOrderTest : SysuiTestCase() {
@get:Rule val animatorTestRule = AnimatorTestRule(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt
new file mode 100644
index 0000000..a53f8d4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt
@@ -0,0 +1,352 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import android.content.res.mainResources
+import android.hardware.face.FaceManager
+import android.hardware.fingerprint.FingerprintManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceTimeoutMessage
+import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage
+import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BiometricMessageInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.biometricMessageInteractor
+
+ private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+ private val fingerprintAuthRepository = kosmos.deviceEntryFingerprintAuthRepository
+ private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
+ private val biometricSettingsRepository = kosmos.biometricSettingsRepository
+
+ @Test
+ fun fingerprintErrorMessage() =
+ testScope.runTest {
+ val fingerprintErrorMessage by collectLastValue(underTest.fingerprintMessage)
+
+ // GIVEN fingerprint is allowed
+ biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+
+ // WHEN authentication status error is FINGERPRINT_ERROR_HW_UNAVAILABLE
+ fingerprintAuthRepository.setAuthenticationStatus(
+ ErrorFingerprintAuthenticationStatus(
+ msgId = FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+ msg = "test"
+ )
+ )
+
+ // THEN fingerprintErrorMessage is updated
+ assertThat(fingerprintErrorMessage?.message).isEqualTo("test")
+ }
+
+ @Test
+ fun fingerprintLockoutErrorMessage() =
+ testScope.runTest {
+ val fingerprintErrorMessage by collectLastValue(underTest.fingerprintMessage)
+
+ // GIVEN fingerprint is allowed
+ biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+
+ // WHEN authentication status error is FINGERPRINT_ERROR_HW_UNAVAILABLE
+ fingerprintAuthRepository.setAuthenticationStatus(
+ ErrorFingerprintAuthenticationStatus(
+ msgId = FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
+ msg = "lockout"
+ )
+ )
+
+ // THEN fingerprintError is updated
+ assertThat(fingerprintErrorMessage).isInstanceOf(FingerprintLockoutMessage::class.java)
+ assertThat(fingerprintErrorMessage?.message).isEqualTo("lockout")
+ }
+
+ @Test
+ fun fingerprintErrorMessage_suppressedError() =
+ testScope.runTest {
+ val fingerprintErrorMessage by collectLastValue(underTest.fingerprintMessage)
+
+ // WHEN authentication status error is FINGERPRINT_ERROR_HW_UNAVAILABLE
+ fingerprintAuthRepository.setAuthenticationStatus(
+ ErrorFingerprintAuthenticationStatus(
+ msgId = FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+ msg = "test"
+ )
+ )
+
+ // THEN fingerprintErrorMessage isn't update - it's still null
+ assertThat(fingerprintErrorMessage).isNull()
+ }
+
+ @Test
+ fun fingerprintHelpMessage() =
+ testScope.runTest {
+ val fingerprintHelpMessage by collectLastValue(underTest.fingerprintMessage)
+
+ // GIVEN fingerprint is allowed
+ biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+
+ // WHEN authentication status help is FINGERPRINT_ACQUIRED_IMAGER_DIRTY
+ fingerprintAuthRepository.setAuthenticationStatus(
+ HelpFingerprintAuthenticationStatus(
+ msgId = FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY,
+ msg = "test"
+ )
+ )
+
+ // THEN fingerprintHelpMessage is updated
+ assertThat(fingerprintHelpMessage?.message).isEqualTo("test")
+ }
+
+ @Test
+ fun fingerprintHelpMessage_primaryAuthRequired() =
+ testScope.runTest {
+ val fingerprintHelpMessage by collectLastValue(underTest.fingerprintMessage)
+
+ // GIVEN fingerprint cannot currently be used
+ biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(false)
+
+ // WHEN authentication status help is FINGERPRINT_ACQUIRED_IMAGER_DIRTY
+ fingerprintAuthRepository.setAuthenticationStatus(
+ HelpFingerprintAuthenticationStatus(
+ msgId = FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY,
+ msg = "test"
+ )
+ )
+
+ // THEN fingerprintHelpMessage isn't update - it's still null
+ assertThat(fingerprintHelpMessage).isNull()
+ }
+
+ @Test
+ fun fingerprintFailMessage_nonUdfps() =
+ testScope.runTest {
+ val fingerprintFailMessage by collectLastValue(underTest.fingerprintMessage)
+
+ // GIVEN rear fingerprint (not UDFPS)
+ fingerprintPropertyRepository.setProperties(
+ 0,
+ SensorStrength.STRONG,
+ FingerprintSensorType.REAR,
+ mapOf()
+ )
+
+ // GIVEN fingerprint is allowed
+ biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+
+ // WHEN authentication status fail
+ fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+
+ // THEN fingerprintFailMessage is updated
+ assertThat(fingerprintFailMessage?.message)
+ .isEqualTo(
+ kosmos.mainResources.getString(
+ com.android.internal.R.string.fingerprint_error_not_match
+ )
+ )
+ }
+
+ @Test
+ fun fingerprintFailMessage_udfps() =
+ testScope.runTest {
+ val fingerprintFailMessage by collectLastValue(underTest.fingerprintMessage)
+
+ // GIVEN UDFPS
+ fingerprintPropertyRepository.setProperties(
+ 0,
+ SensorStrength.STRONG,
+ FingerprintSensorType.UDFPS_OPTICAL,
+ mapOf()
+ )
+
+ // GIVEN fingerprint is allowed
+ biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+
+ // WHEN authentication status fail
+ fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+
+ // THEN fingerprintFailMessage is updated to udfps message
+ assertThat(fingerprintFailMessage?.message)
+ .isEqualTo(
+ kosmos.mainResources.getString(
+ com.android.internal.R.string.fingerprint_udfps_error_not_match
+ )
+ )
+ }
+
+ @Test
+ fun faceFailedMessage_primaryAuthRequired() =
+ testScope.runTest {
+ val faceFailedMessage by collectLastValue(underTest.faceMessage)
+
+ // GIVEN face is not allowed
+ biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false)
+
+ // WHEN authentication status fail
+ faceAuthRepository.setAuthenticationStatus(FailedFaceAuthenticationStatus())
+
+ // THEN fingerprintFailedMessage doesn't update - it's still null
+ assertThat(faceFailedMessage).isNull()
+ }
+
+ @Test
+ fun faceFailedMessage_faceOnly() =
+ testScope.runTest {
+ val faceFailedMessage by collectLastValue(underTest.faceMessage)
+
+ // GIVEN face is allowed
+ biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
+
+ // GIVEN face only enrolled
+ biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+
+ // WHEN authentication status fail
+ faceAuthRepository.setAuthenticationStatus(FailedFaceAuthenticationStatus())
+
+ // THEN fingerprintFailedMessage is updated
+ assertThat(faceFailedMessage).isNotNull()
+ }
+
+ @Test
+ fun faceHelpMessage_faceOnly() =
+ testScope.runTest {
+ val faceHelpMessage by collectLastValue(underTest.faceMessage)
+
+ // GIVEN face is allowed
+ biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
+
+ // GIVEN face only enrolled
+ biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+
+ // WHEN authentication status help
+ faceAuthRepository.setAuthenticationStatus(
+ HelpFaceAuthenticationStatus(
+ msg = "Move left",
+ msgId = FaceManager.FACE_ACQUIRED_TOO_RIGHT,
+ )
+ )
+
+ // THEN fingerprintFailedMessage is updated
+ assertThat(faceHelpMessage).isNotNull()
+ }
+
+ @Test
+ fun faceHelpMessage_coEx() =
+ testScope.runTest {
+ val faceHelpMessage by collectLastValue(underTest.faceMessage)
+
+ // GIVEN face and fingerprint are allowed
+ biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
+ biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+
+ // GIVEN face only enrolled
+ biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+
+ // WHEN authentication status help
+ faceAuthRepository.setAuthenticationStatus(
+ HelpFaceAuthenticationStatus(
+ msg = "Move left",
+ msgId = FaceManager.FACE_ACQUIRED_TOO_RIGHT,
+ )
+ )
+
+ // THEN fingerprintFailedMessage is NOT updated
+ assertThat(faceHelpMessage).isNull()
+ }
+
+ @Test
+ fun faceErrorMessage_suppressedError() =
+ testScope.runTest {
+ val faceErrorMessage by collectLastValue(underTest.faceMessage)
+
+ // WHEN authentication status error is FACE_ERROR_HW_UNAVAILABLE
+ faceAuthRepository.setAuthenticationStatus(
+ ErrorFaceAuthenticationStatus(
+ msgId = FaceManager.FACE_ERROR_HW_UNAVAILABLE,
+ msg = "test"
+ )
+ )
+
+ // THEN faceErrorMessage isn't updated - it's still null since it was suppressed
+ assertThat(faceErrorMessage).isNull()
+ }
+
+ @Test
+ fun faceErrorMessage() =
+ testScope.runTest {
+ val faceErrorMessage by collectLastValue(underTest.faceMessage)
+
+ // WHEN authentication status error is FACE_ERROR_HW_UNAVAILABLE
+ faceAuthRepository.setAuthenticationStatus(
+ ErrorFaceAuthenticationStatus(
+ msgId = FaceManager.FACE_ERROR_HW_UNAVAILABLE,
+ msg = "test"
+ )
+ )
+
+ // GIVEN face is allowed
+ biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
+
+ // THEN faceErrorMessage is updated
+ assertThat(faceErrorMessage?.message).isEqualTo("test")
+ }
+
+ @Test
+ fun faceTimeoutErrorMessage() =
+ testScope.runTest {
+ val faceErrorMessage by collectLastValue(underTest.faceMessage)
+
+ // WHEN authentication status error is FACE_ERROR_HW_UNAVAILABLE
+ faceAuthRepository.setAuthenticationStatus(
+ ErrorFaceAuthenticationStatus(msgId = FaceManager.FACE_ERROR_TIMEOUT, msg = "test")
+ )
+
+ // GIVEN face is allowed
+ biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
+
+ // THEN faceErrorMessage is updated
+ assertThat(faceErrorMessage).isInstanceOf(FaceTimeoutMessage::class.java)
+ assertThat(faceErrorMessage?.message).isEqualTo("test")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
index bdf0e06..43c860c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
new file mode 100644
index 0000000..f36f032
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
@@ -0,0 +1,259 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import android.content.Intent
+import android.content.mockedContext
+import android.hardware.fingerprint.FingerprintManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class OccludingAppDeviceEntryInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.occludingAppDeviceEntryInteractor
+
+ private val fingerprintAuthRepository = kosmos.deviceEntryFingerprintAuthRepository
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val bouncerRepository = kosmos.keyguardBouncerRepository
+ private val powerRepository = kosmos.fakePowerRepository
+ private val biometricSettingsRepository = kosmos.biometricSettingsRepository
+ private val mockedContext = kosmos.mockedContext
+ private val mockedActivityStarter = kosmos.activityStarter
+
+ @Test
+ fun fingerprintSuccess_goToHomeScreen() =
+ testScope.runTest {
+ givenOnOccludingApp(true)
+ fingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ verifyGoToHomeScreen()
+ }
+
+ @Test
+ fun fingerprintSuccess_notInteractive_doesNotGoToHomeScreen() =
+ testScope.runTest {
+ givenOnOccludingApp(true)
+ powerRepository.setInteractive(false)
+ fingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ verifyNeverGoToHomeScreen()
+ }
+
+ @Test
+ fun fingerprintSuccess_dreaming_doesNotGoToHomeScreen() =
+ testScope.runTest {
+ givenOnOccludingApp(true)
+ keyguardRepository.setDreaming(true)
+ fingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ verifyNeverGoToHomeScreen()
+ }
+
+ @Test
+ fun fingerprintSuccess_notOnOccludingApp_doesNotGoToHomeScreen() =
+ testScope.runTest {
+ givenOnOccludingApp(false)
+ fingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ verifyNeverGoToHomeScreen()
+ }
+
+ @Test
+ fun lockout_goToHomeScreenOnDismissAction() =
+ testScope.runTest {
+ givenOnOccludingApp(true)
+ fingerprintAuthRepository.setAuthenticationStatus(
+ ErrorFingerprintAuthenticationStatus(
+ FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
+ "lockoutTest"
+ )
+ )
+ runCurrent()
+ verifyGoToHomeScreenOnDismiss()
+ }
+
+ @Test
+ fun lockout_notOnOccludingApp_neverGoToHomeScreen() =
+ testScope.runTest {
+ givenOnOccludingApp(false)
+ fingerprintAuthRepository.setAuthenticationStatus(
+ ErrorFingerprintAuthenticationStatus(
+ FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
+ "lockoutTest"
+ )
+ )
+ runCurrent()
+ verifyNeverGoToHomeScreen()
+ }
+
+ @Test
+ fun message_fpFailOnOccludingApp_thenNotOnOccludingApp() =
+ testScope.runTest {
+ val message by collectLastValue(underTest.message)
+
+ givenOnOccludingApp(true)
+ givenFingerprintAllowed(true)
+ runCurrent()
+ // WHEN a fp failure come in
+ fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+
+ // GIVEN fingerprint shouldn't run
+ givenOnOccludingApp(false)
+ runCurrent()
+ // WHEN another fp failure arrives
+ fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+
+ // THEN message set to null
+ assertThat(message).isNull()
+ }
+
+ @Test
+ fun message_fpErrorHelpFailOnOccludingApp() =
+ testScope.runTest {
+ val message by collectLastValue(underTest.message)
+
+ givenOnOccludingApp(true)
+ givenFingerprintAllowed(true)
+ runCurrent()
+
+ // ERROR message
+ fingerprintAuthRepository.setAuthenticationStatus(
+ ErrorFingerprintAuthenticationStatus(
+ FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+ "testError",
+ )
+ )
+ assertThat(message?.message).isEqualTo("testError")
+
+ // HELP message
+ fingerprintAuthRepository.setAuthenticationStatus(
+ HelpFingerprintAuthenticationStatus(
+ FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL,
+ "testHelp",
+ )
+ )
+ assertThat(message?.message).isEqualTo("testHelp")
+
+ // FAIL message
+ fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+ assertThat(message?.message).isNotEqualTo("testHelp")
+ }
+
+ @Test
+ fun message_fpError_lockoutFilteredOut() =
+ testScope.runTest {
+ val message by collectLastValue(underTest.message)
+
+ givenOnOccludingApp(true)
+ givenFingerprintAllowed(true)
+ runCurrent()
+
+ // permanent lockout error message
+ fingerprintAuthRepository.setAuthenticationStatus(
+ ErrorFingerprintAuthenticationStatus(
+ FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT,
+ "testPermanentLockoutMessageFiltered",
+ )
+ )
+ assertThat(message).isNull()
+
+ // temporary lockout error message
+ fingerprintAuthRepository.setAuthenticationStatus(
+ ErrorFingerprintAuthenticationStatus(
+ FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
+ "testLockoutMessageFiltered",
+ )
+ )
+ assertThat(message).isNull()
+ }
+
+ private fun givenOnOccludingApp(isOnOccludingApp: Boolean) {
+ powerRepository.setInteractive(true)
+ keyguardRepository.setKeyguardOccluded(isOnOccludingApp)
+ keyguardRepository.setKeyguardShowing(isOnOccludingApp)
+ keyguardRepository.setDreaming(false)
+ bouncerRepository.setPrimaryShow(!isOnOccludingApp)
+ bouncerRepository.setAlternateVisible(!isOnOccludingApp)
+ }
+
+ private fun givenFingerprintAllowed(allowed: Boolean) {
+ biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(allowed)
+ }
+
+ private fun verifyGoToHomeScreen() {
+ val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(mockedContext).startActivity(intentCaptor.capture())
+
+ assertThat(intentCaptor.value.hasCategory(Intent.CATEGORY_HOME)).isTrue()
+ assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_MAIN)
+ }
+
+ private fun verifyNeverGoToHomeScreen() {
+ verify(mockedContext, never()).startActivity(any())
+ verify(mockedActivityStarter, never())
+ .dismissKeyguardThenExecute(any(OnDismissAction::class.java), isNull(), eq(false))
+ }
+
+ private fun verifyGoToHomeScreenOnDismiss() {
+ val onDimissActionCaptor = ArgumentCaptor.forClass(OnDismissAction::class.java)
+ verify(mockedActivityStarter)
+ .dismissKeyguardThenExecute(onDimissActionCaptor.capture(), isNull(), eq(false))
+ onDimissActionCaptor.value.onDismiss()
+
+ verifyGoToHomeScreen()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
index e158e47..e97edcb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -51,6 +52,7 @@
}
private val deviceEntryIconTransition = kosmos.fakeDeviceEntryIconViewModelTransition
private val testScope = kosmos.testScope
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
private val accessibilityRepository = kosmos.fakeAccessibilityRepository
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
@@ -101,13 +103,11 @@
)
assertThat(visible).isFalse()
}
-
- @Test
- fun deviceUnlocked_overlayNotVisible() =
+ fun fpNotRunning_overlayNotVisible() =
testScope.runTest {
val visible by collectLastValue(underTest.visible)
setupVisibleStateOnLockscreen()
- deviceEntryRepository.setUnlocked(true)
+ deviceEntryFingerprintAuthRepository.setIsRunning(false)
assertThat(visible).isFalse()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractorTest.kt
deleted file mode 100644
index 3389fa9..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractorTest.kt
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.domain.interactor
-
-import android.hardware.biometrics.BiometricSourceType.FINGERPRINT
-import android.hardware.fingerprint.FingerprintManager
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.biometrics.shared.model.FingerprintSensorType
-import com.android.systemui.biometrics.shared.model.SensorStrength
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
-import com.android.systemui.keyguard.util.IndicationHelper
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class BiometricMessageInteractorTest : SysuiTestCase() {
-
- private lateinit var underTest: BiometricMessageInteractor
- private lateinit var testScope: TestScope
- private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
- private lateinit var fingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository
-
- @Mock private lateinit var indicationHelper: IndicationHelper
- @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- testScope = TestScope()
- fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
- fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
- underTest =
- BiometricMessageInteractor(
- mContext.resources,
- fingerprintAuthRepository,
- fingerprintPropertyRepository,
- indicationHelper,
- keyguardUpdateMonitor,
- )
- }
-
- @Test
- fun fingerprintErrorMessage() =
- testScope.runTest {
- val fingerprintErrorMessage by collectLastValue(underTest.fingerprintErrorMessage)
-
- // GIVEN FINGERPRINT_ERROR_HW_UNAVAILABLE should NOT be suppressed
- whenever(
- indicationHelper.shouldSuppressErrorMsg(
- FINGERPRINT,
- FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE
- )
- )
- .thenReturn(false)
-
- // WHEN authentication status error is FINGERPRINT_ERROR_HW_UNAVAILABLE
- fingerprintAuthRepository.setAuthenticationStatus(
- ErrorFingerprintAuthenticationStatus(
- msgId = FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE,
- msg = "test"
- )
- )
-
- // THEN fingerprintErrorMessage is updated
- assertThat(fingerprintErrorMessage?.source).isEqualTo(FINGERPRINT)
- assertThat(fingerprintErrorMessage?.type).isEqualTo(BiometricMessageType.ERROR)
- assertThat(fingerprintErrorMessage?.id)
- .isEqualTo(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE)
- assertThat(fingerprintErrorMessage?.message).isEqualTo("test")
- }
-
- @Test
- fun fingerprintErrorMessage_suppressedError() =
- testScope.runTest {
- val fingerprintErrorMessage by collectLastValue(underTest.fingerprintErrorMessage)
-
- // GIVEN FINGERPRINT_ERROR_HW_UNAVAILABLE should be suppressed
- whenever(
- indicationHelper.shouldSuppressErrorMsg(
- FINGERPRINT,
- FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE
- )
- )
- .thenReturn(true)
-
- // WHEN authentication status error is FINGERPRINT_ERROR_HW_UNAVAILABLE
- fingerprintAuthRepository.setAuthenticationStatus(
- ErrorFingerprintAuthenticationStatus(
- msgId = FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE,
- msg = "test"
- )
- )
-
- // THEN fingerprintErrorMessage isn't update - it's still null
- assertThat(fingerprintErrorMessage).isNull()
- }
-
- @Test
- fun fingerprintHelpMessage() =
- testScope.runTest {
- val fingerprintHelpMessage by collectLastValue(underTest.fingerprintHelpMessage)
-
- // GIVEN primary auth is NOT required
- whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
- .thenReturn(true)
-
- // WHEN authentication status help is FINGERPRINT_ACQUIRED_IMAGER_DIRTY
- fingerprintAuthRepository.setAuthenticationStatus(
- HelpFingerprintAuthenticationStatus(
- msgId = FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY,
- msg = "test"
- )
- )
-
- // THEN fingerprintHelpMessage is updated
- assertThat(fingerprintHelpMessage?.source).isEqualTo(FINGERPRINT)
- assertThat(fingerprintHelpMessage?.type).isEqualTo(BiometricMessageType.HELP)
- assertThat(fingerprintHelpMessage?.id)
- .isEqualTo(FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY)
- assertThat(fingerprintHelpMessage?.message).isEqualTo("test")
- }
-
- @Test
- fun fingerprintHelpMessage_primaryAuthRequired() =
- testScope.runTest {
- val fingerprintHelpMessage by collectLastValue(underTest.fingerprintHelpMessage)
-
- // GIVEN primary auth is required
- whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
- .thenReturn(false)
-
- // WHEN authentication status help is FINGERPRINT_ACQUIRED_IMAGER_DIRTY
- fingerprintAuthRepository.setAuthenticationStatus(
- HelpFingerprintAuthenticationStatus(
- msgId = FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY,
- msg = "test"
- )
- )
-
- // THEN fingerprintHelpMessage isn't update - it's still null
- assertThat(fingerprintHelpMessage).isNull()
- }
-
- @Test
- fun fingerprintFailMessage_nonUdfps() =
- testScope.runTest {
- val fingerprintFailMessage by collectLastValue(underTest.fingerprintFailMessage)
-
- // GIVEN primary auth is NOT required
- whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
- .thenReturn(true)
-
- // GIVEN rear fingerprint (not UDFPS)
- fingerprintPropertyRepository.setProperties(
- 0,
- SensorStrength.STRONG,
- FingerprintSensorType.REAR,
- mapOf()
- )
-
- // WHEN authentication status fail
- fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
-
- // THEN fingerprintFailMessage is updated
- assertThat(fingerprintFailMessage?.source).isEqualTo(FINGERPRINT)
- assertThat(fingerprintFailMessage?.type).isEqualTo(BiometricMessageType.FAIL)
- assertThat(fingerprintFailMessage?.id)
- .isEqualTo(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED)
- assertThat(fingerprintFailMessage?.message)
- .isEqualTo(
- mContext.resources.getString(
- com.android.internal.R.string.fingerprint_error_not_match
- )
- )
- }
-
- @Test
- fun fingerprintFailMessage_udfps() =
- testScope.runTest {
- val fingerprintFailMessage by collectLastValue(underTest.fingerprintFailMessage)
-
- // GIVEN primary auth is NOT required
- whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
- .thenReturn(true)
-
- // GIVEN UDFPS
- fingerprintPropertyRepository.setProperties(
- 0,
- SensorStrength.STRONG,
- FingerprintSensorType.UDFPS_OPTICAL,
- mapOf()
- )
-
- // WHEN authentication status fail
- fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
-
- // THEN fingerprintFailMessage is updated to udfps message
- assertThat(fingerprintFailMessage?.source).isEqualTo(FINGERPRINT)
- assertThat(fingerprintFailMessage?.type).isEqualTo(BiometricMessageType.FAIL)
- assertThat(fingerprintFailMessage?.id)
- .isEqualTo(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED)
- assertThat(fingerprintFailMessage?.message)
- .isEqualTo(
- mContext.resources.getString(
- com.android.internal.R.string.fingerprint_udfps_error_not_match
- )
- )
- }
-
- @Test
- fun fingerprintFailedMessage_primaryAuthRequired() =
- testScope.runTest {
- val fingerprintFailedMessage by collectLastValue(underTest.fingerprintFailMessage)
-
- // GIVEN primary auth is required
- whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
- .thenReturn(false)
-
- // WHEN authentication status fail
- fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
-
- // THEN fingerprintFailedMessage isn't update - it's still null
- assertThat(fingerprintFailedMessage).isNull()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
deleted file mode 100644
index 7358b9d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
+++ /dev/null
@@ -1,371 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.domain.interactor
-
-import android.content.Context
-import android.content.Intent
-import android.hardware.biometrics.BiometricSourceType
-import android.hardware.fingerprint.FingerprintManager
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
-import com.android.systemui.keyguard.util.IndicationHelper
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.ActivityStarter.OnDismissAction
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-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.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.ArgumentMatchers.isNull
-import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class OccludingAppDeviceEntryInteractorTest : SysuiTestCase() {
-
- private lateinit var underTest: OccludingAppDeviceEntryInteractor
- private lateinit var testScope: TestScope
- private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
- private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
- private lateinit var fingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository
- private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
- private lateinit var configurationRepository: FakeConfigurationRepository
- private lateinit var featureFlags: FakeFeatureFlags
- private lateinit var trustRepository: FakeTrustRepository
- private lateinit var powerRepository: FakePowerRepository
- private lateinit var powerInteractor: PowerInteractor
-
- @Mock private lateinit var indicationHelper: IndicationHelper
- @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- @Mock private lateinit var mockedContext: Context
- @Mock private lateinit var activityStarter: ActivityStarter
- @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- testScope = TestScope()
- biometricSettingsRepository = FakeBiometricSettingsRepository()
- fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
- fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
- keyguardRepository = FakeKeyguardRepository()
- bouncerRepository = FakeKeyguardBouncerRepository()
- configurationRepository = FakeConfigurationRepository()
- featureFlags = FakeFeatureFlags()
- trustRepository = FakeTrustRepository()
- powerRepository = FakePowerRepository()
- powerInteractor =
- PowerInteractor(
- powerRepository,
- falsingCollector = mock(),
- screenOffAnimationController = mock(),
- statusBarStateController = mock(),
- )
-
- underTest =
- OccludingAppDeviceEntryInteractor(
- BiometricMessageInteractor(
- mContext.resources,
- fingerprintAuthRepository,
- fingerprintPropertyRepository,
- indicationHelper,
- keyguardUpdateMonitor,
- ),
- fingerprintAuthRepository,
- KeyguardInteractorFactory.create(
- featureFlags = featureFlags,
- repository = keyguardRepository,
- bouncerRepository = bouncerRepository,
- configurationRepository = configurationRepository,
- sceneInteractor =
- mock { whenever(transitioningTo).thenReturn(MutableStateFlow(null)) },
- powerInteractor = powerInteractor,
- )
- .keyguardInteractor,
- PrimaryBouncerInteractor(
- bouncerRepository,
- primaryBouncerView = mock(),
- mainHandler = mock(),
- keyguardStateController = mock(),
- keyguardSecurityModel = mock(),
- primaryBouncerCallbackInteractor = mock(),
- falsingCollector = mock(),
- dismissCallbackRegistry = mock(),
- context,
- keyguardUpdateMonitor,
- trustRepository,
- testScope.backgroundScope,
- mSelectedUserInteractor,
- deviceEntryFaceAuthInteractor = mock(),
- ),
- AlternateBouncerInteractor(
- statusBarStateController = mock(),
- keyguardStateController = mock(),
- bouncerRepository,
- FakeFingerprintPropertyRepository(),
- biometricSettingsRepository,
- FakeSystemClock(),
- keyguardUpdateMonitor,
- scope = testScope.backgroundScope,
- ),
- testScope.backgroundScope,
- mockedContext,
- activityStarter,
- powerInteractor,
- )
- }
-
- @Test
- fun fingerprintSuccess_goToHomeScreen() =
- testScope.runTest {
- givenOnOccludingApp(true)
- fingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
- runCurrent()
- verifyGoToHomeScreen()
- }
-
- @Test
- fun fingerprintSuccess_notInteractive_doesNotGoToHomeScreen() =
- testScope.runTest {
- givenOnOccludingApp(true)
- powerRepository.setInteractive(false)
- fingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
- runCurrent()
- verifyNeverGoToHomeScreen()
- }
-
- @Test
- fun fingerprintSuccess_dreaming_doesNotGoToHomeScreen() =
- testScope.runTest {
- givenOnOccludingApp(true)
- keyguardRepository.setDreaming(true)
- fingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
- runCurrent()
- verifyNeverGoToHomeScreen()
- }
-
- @Test
- fun fingerprintSuccess_notOnOccludingApp_doesNotGoToHomeScreen() =
- testScope.runTest {
- givenOnOccludingApp(false)
- fingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
- runCurrent()
- verifyNeverGoToHomeScreen()
- }
-
- @Test
- fun lockout_goToHomeScreenOnDismissAction() =
- testScope.runTest {
- givenOnOccludingApp(true)
- fingerprintAuthRepository.setAuthenticationStatus(
- ErrorFingerprintAuthenticationStatus(
- FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
- "lockoutTest"
- )
- )
- runCurrent()
- verifyGoToHomeScreenOnDismiss()
- }
-
- @Test
- fun lockout_notOnOccludingApp_neverGoToHomeScreen() =
- testScope.runTest {
- givenOnOccludingApp(false)
- fingerprintAuthRepository.setAuthenticationStatus(
- ErrorFingerprintAuthenticationStatus(
- FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
- "lockoutTest"
- )
- )
- runCurrent()
- verifyNeverGoToHomeScreen()
- }
-
- @Test
- fun message_fpFailOnOccludingApp_thenNotOnOccludingApp() =
- testScope.runTest {
- val message by collectLastValue(underTest.message)
-
- givenOnOccludingApp(true)
- givenPrimaryAuthRequired(false)
- runCurrent()
- // WHEN a fp failure come in
- fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
- // THEN message set to failure
- assertThat(message?.type).isEqualTo(BiometricMessageType.FAIL)
-
- // GIVEN fingerprint shouldn't run
- givenOnOccludingApp(false)
- runCurrent()
- // WHEN another fp failure arrives
- fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
-
- // THEN message set to null
- assertThat(message).isNull()
- }
-
- @Test
- fun message_fpErrorHelpFailOnOccludingApp() =
- testScope.runTest {
- val message by collectLastValue(underTest.message)
-
- givenOnOccludingApp(true)
- givenPrimaryAuthRequired(false)
- runCurrent()
-
- // ERROR message
- fingerprintAuthRepository.setAuthenticationStatus(
- ErrorFingerprintAuthenticationStatus(
- FingerprintManager.FINGERPRINT_ERROR_CANCELED,
- "testError",
- )
- )
- assertThat(message?.source).isEqualTo(BiometricSourceType.FINGERPRINT)
- assertThat(message?.id).isEqualTo(FingerprintManager.FINGERPRINT_ERROR_CANCELED)
- assertThat(message?.message).isEqualTo("testError")
- assertThat(message?.type).isEqualTo(BiometricMessageType.ERROR)
-
- // HELP message
- fingerprintAuthRepository.setAuthenticationStatus(
- HelpFingerprintAuthenticationStatus(
- FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL,
- "testHelp",
- )
- )
- assertThat(message?.source).isEqualTo(BiometricSourceType.FINGERPRINT)
- assertThat(message?.id).isEqualTo(FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL)
- assertThat(message?.message).isEqualTo("testHelp")
- assertThat(message?.type).isEqualTo(BiometricMessageType.HELP)
-
- // FAIL message
- fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
- assertThat(message?.source).isEqualTo(BiometricSourceType.FINGERPRINT)
- assertThat(message?.id)
- .isEqualTo(KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED)
- assertThat(message?.type).isEqualTo(BiometricMessageType.FAIL)
- }
-
- @Test
- fun message_fpError_lockoutFilteredOut() =
- testScope.runTest {
- val message by collectLastValue(underTest.message)
-
- givenOnOccludingApp(true)
- givenPrimaryAuthRequired(false)
- runCurrent()
-
- // permanent lockout error message
- fingerprintAuthRepository.setAuthenticationStatus(
- ErrorFingerprintAuthenticationStatus(
- FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT,
- "testPermanentLockoutMessageFiltered",
- )
- )
- assertThat(message).isNull()
-
- // temporary lockout error message
- fingerprintAuthRepository.setAuthenticationStatus(
- ErrorFingerprintAuthenticationStatus(
- FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
- "testLockoutMessageFiltered",
- )
- )
- assertThat(message).isNull()
- }
-
- private fun givenOnOccludingApp(isOnOccludingApp: Boolean) {
- powerRepository.setInteractive(true)
- keyguardRepository.setKeyguardOccluded(isOnOccludingApp)
- keyguardRepository.setKeyguardShowing(isOnOccludingApp)
- keyguardRepository.setDreaming(false)
- bouncerRepository.setPrimaryShow(!isOnOccludingApp)
- bouncerRepository.setAlternateVisible(!isOnOccludingApp)
- }
-
- private fun givenPrimaryAuthRequired(required: Boolean) {
- whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
- .thenReturn(!required)
- }
-
- private fun verifyGoToHomeScreen() {
- val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
- verify(mockedContext).startActivity(intentCaptor.capture())
-
- assertThat(intentCaptor.value.hasCategory(Intent.CATEGORY_HOME)).isTrue()
- assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_MAIN)
- }
-
- private fun verifyNeverGoToHomeScreen() {
- verify(mockedContext, never()).startActivity(any())
- verify(activityStarter, never())
- .dismissKeyguardThenExecute(any(OnDismissAction::class.java), isNull(), eq(false))
- }
-
- private fun verifyGoToHomeScreenOnDismiss() {
- val onDimissActionCaptor = ArgumentCaptor.forClass(OnDismissAction::class.java)
- verify(activityStarter)
- .dismissKeyguardThenExecute(onDimissActionCaptor.capture(), isNull(), eq(false))
- onDimissActionCaptor.value.onDismiss()
-
- verifyGoToHomeScreen()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
index 459040a..2bd0d79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
@@ -58,6 +58,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FaceHelpMessageDeferral;
+import com.android.systemui.biometrics.FaceHelpMessageDeferralFactory;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -136,6 +137,8 @@
@Mock
protected AccessibilityManager mAccessibilityManager;
@Mock
+ protected FaceHelpMessageDeferralFactory mFaceHelpMessageDeferralFactory;
+ @Mock
protected FaceHelpMessageDeferral mFaceHelpMessageDeferral;
@Mock
protected AlternateBouncerInteractor mAlternateBouncerInteractor;
@@ -224,6 +227,8 @@
.thenReturn(mDisclosureWithOrganization);
when(mUserTracker.getUserId()).thenReturn(mCurrentUserId);
+ when(mFaceHelpMessageDeferralFactory.create()).thenReturn(mFaceHelpMessageDeferral);
+
mIndicationHelper = new IndicationHelper(mKeyguardUpdateMonitor);
mWakeLock = new WakeLockFake();
@@ -257,7 +262,7 @@
mUserManager, mExecutor, mExecutor, mFalsingManager,
mAuthController, mLockPatternUtils, mScreenLifecycle,
mKeyguardBypassController, mAccessibilityManager,
- mFaceHelpMessageDeferral, mock(KeyguardLogger.class),
+ mFaceHelpMessageDeferralFactory, mock(KeyguardLogger.class),
mAlternateBouncerInteractor,
mAlarmManager,
mUserTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index f266f03..91a9da3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -85,6 +85,7 @@
private val bigGap = px(R.dimen.notification_section_divider_height)
private val smallGap = px(R.dimen.notification_section_divider_height_lockscreen)
+ private val scrimPadding = px(R.dimen.notification_side_paddings)
@Before
fun setUp() {
@@ -119,6 +120,18 @@
}
@Test
+ fun resetViewStates_defaultHunWhenShadeIsOpening_yTranslationIsInset() {
+ whenever(notificationRow.isPinned).thenReturn(true)
+ whenever(notificationRow.isHeadsUp).thenReturn(true)
+
+ // scroll the panel over the HUN inset
+ ambientState.stackY = stackScrollAlgorithm.mHeadsUpInset + bigGap
+
+ // the HUN translation should be the panel scroll position + the scrim padding
+ resetViewStates_hunYTranslationIs(ambientState.stackY + scrimPadding)
+ }
+
+ @Test
@DisableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun resetViewStates_hunAnimatingAway_yTranslationIsInset() {
whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 2d120cd..76c9740 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -147,6 +147,7 @@
private KeyguardStatusBarView mKeyguardStatusBarView;
private KeyguardStatusBarViewController mController;
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+ private final FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock());
private final TestScope mTestScope = TestScopeProvider.getTestScope();
private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
@@ -214,6 +215,7 @@
mSecureSettings,
mCommandQueue,
mFakeExecutor,
+ mBackgroundExecutor,
mLogger,
mNotificationMediaManager,
mStatusOverlayHoverListenerFactory
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index 4293a27..269b70f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -257,6 +257,10 @@
/* privacyIndicatorBounds = */ PrivacyIndicatorBounds(),
/* displayShape = */ DisplayShape.NONE,
/* compatInsetsTypes = */ 0,
- /* compatIgnoreVisibility = */ false
+ /* compatIgnoreVisibility = */ false,
+ /* typeBoundingRectsMap = */ arrayOf(),
+ /* typeMaxBoundingRectsMap = */ arrayOf(),
+ /* frameWidth = */ 0,
+ /* frameHeight = */ 0
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt
index af1d788..e6b4d06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt
@@ -12,12 +12,9 @@
import com.android.systemui.flags.Flags
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.TestCoroutineScope
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -41,11 +38,12 @@
private lateinit var underTest: WalletContextualLocationsService
private lateinit var testScope: TestScope
private var listenerRegisteredCount: Int = 0
- private val listener: IWalletCardsUpdatedListener.Stub = object : IWalletCardsUpdatedListener.Stub() {
- override fun registerNewWalletCards(cards: List<WalletCard?>) {
- listenerRegisteredCount++
+ private val listener: IWalletCardsUpdatedListener.Stub =
+ object : IWalletCardsUpdatedListener.Stub() {
+ override fun registerNewWalletCards(cards: List<WalletCard?>) {
+ listenerRegisteredCount++
+ }
}
- }
@Before
@kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -60,50 +58,56 @@
featureFlags.set(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS, true)
listenerRegisteredCount = 0
- underTest = WalletContextualLocationsService(controller, featureFlags, testScope.backgroundScope)
+ underTest =
+ WalletContextualLocationsService(controller, featureFlags, testScope.backgroundScope)
}
@Test
@kotlinx.coroutines.ExperimentalCoroutinesApi
- fun addListener() = testScope.runTest {
- underTest.addWalletCardsUpdatedListenerInternal(listener)
- assertThat(listenerRegisteredCount).isEqualTo(1)
- }
+ fun addListener() =
+ testScope.runTest {
+ underTest.addWalletCardsUpdatedListenerInternal(listener)
+ assertThat(listenerRegisteredCount).isEqualTo(1)
+ }
@Test
@kotlinx.coroutines.ExperimentalCoroutinesApi
- fun addStoreLocations() = testScope.runTest {
- underTest.onWalletContextualLocationsStateUpdatedInternal(ArrayList<String>())
- verify(controller, times(1)).setSuggestionCardIds(anySet())
- }
+ fun addStoreLocations() =
+ testScope.runTest {
+ underTest.onWalletContextualLocationsStateUpdatedInternal(ArrayList<String>())
+ verify(controller, times(1)).setSuggestionCardIds(anySet())
+ }
@Test
@kotlinx.coroutines.ExperimentalCoroutinesApi
- fun updateListenerAndLocationsState() = testScope.runTest {
- // binds to the service and adds a listener
- val underTestStub = getInterface
- underTestStub.addWalletCardsUpdatedListener(listener)
- assertThat(listenerRegisteredCount).isEqualTo(1)
+ fun updateListenerAndLocationsState() =
+ testScope.runTest {
+ // binds to the service and adds a listener
+ val underTestStub = getInterface
+ underTestStub.addWalletCardsUpdatedListener(listener)
+ assertThat(listenerRegisteredCount).isEqualTo(1)
- // sends a list of card IDs to the controller
- underTestStub.onWalletContextualLocationsStateUpdated(ArrayList<String>())
- verify(controller, times(1)).setSuggestionCardIds(anySet())
+ // sends a list of card IDs to the controller
+ underTestStub.onWalletContextualLocationsStateUpdated(ArrayList<String>())
+ verify(controller, times(1)).setSuggestionCardIds(anySet())
- // adds another listener
- fakeWalletCards.update{ updatedFakeWalletCards }
- runCurrent()
- assertThat(listenerRegisteredCount).isEqualTo(2)
+ // adds another listener
+ fakeWalletCards.update { updatedFakeWalletCards }
+ runCurrent()
+ assertThat(listenerRegisteredCount).isEqualTo(2)
- // sends another list of card IDs to the controller
- underTestStub.onWalletContextualLocationsStateUpdated(ArrayList<String>())
- verify(controller, times(2)).setSuggestionCardIds(anySet())
- }
+ // sends another list of card IDs to the controller
+ underTestStub.onWalletContextualLocationsStateUpdated(ArrayList<String>())
+ verify(controller, times(2)).setSuggestionCardIds(anySet())
+ }
private val fakeWalletCards: MutableStateFlow<List<WalletCard>>
get() {
val intent = Intent(getContext(), WalletContextualLocationsService::class.java)
- val pi: PendingIntent = PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE)
- val icon: Icon = Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888))
+ val pi: PendingIntent =
+ PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE)
+ val icon: Icon =
+ Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888))
val walletCards: ArrayList<WalletCard> = ArrayList<WalletCard>()
walletCards.add(WalletCard.Builder("card1", icon, "card", pi).build())
walletCards.add(WalletCard.Builder("card2", icon, "card", pi).build())
@@ -113,8 +117,10 @@
private val updatedFakeWalletCards: List<WalletCard>
get() {
val intent = Intent(getContext(), WalletContextualLocationsService::class.java)
- val pi: PendingIntent = PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE)
- val icon: Icon = Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888))
+ val pi: PendingIntent =
+ PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE)
+ val icon: Icon =
+ Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888))
val walletCards: ArrayList<WalletCard> = ArrayList<WalletCard>()
walletCards.add(WalletCard.Builder("card3", icon, "card", pi).build())
return walletCards
@@ -125,4 +131,4 @@
val intent = Intent()
return IWalletContextualLocationsService.Stub.asInterface(underTest.onBind(intent))
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
index 5e254bf..f6ddfcc 100644
--- a/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
@@ -18,5 +18,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testCase
+import com.android.systemui.util.mockito.mock
var Kosmos.applicationContext: Context by Kosmos.Fixture { testCase.context }
+val Kosmos.mockedContext: Context by Kosmos.Fixture { mock<Context>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorKosmos.kt
index 244ef8d..3c61353 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorKosmos.kt
@@ -34,7 +34,8 @@
import com.android.systemui.util.mockito.mock
var Kosmos.mockPrimaryBouncerInteractor by Kosmos.Fixture { mock<PrimaryBouncerInteractor>() }
-var Kosmos.primaryBouncerInteractor by
+
+val Kosmos.primaryBouncerInteractor by
Kosmos.Fixture {
PrimaryBouncerInteractor(
repository = keyguardBouncerRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorKosmos.kt
new file mode 100644
index 0000000..77f48db
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import android.content.res.mainResources
+import com.android.systemui.biometrics.domain.interactor.fingerprintPropertyInteractor
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.biometricMessageInteractor by
+ Kosmos.Fixture {
+ BiometricMessageInteractor(
+ resources = mainResources,
+ fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
+ fingerprintPropertyInteractor = fingerprintPropertyInteractor,
+ faceAuthInteractor = deviceEntryFaceAuthInteractor,
+ biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorKosmos.kt
new file mode 100644
index 0000000..4fcf43a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.deviceEntryBiometricSettingsInteractor by
+ Kosmos.Fixture {
+ DeviceEntryBiometricSettingsInteractor(
+ repository = biometricSettingsRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt
new file mode 100644
index 0000000..c3f677e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import android.content.mockedContext
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.power.domain.interactor.powerInteractor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.occludingAppDeviceEntryInteractor by
+ Kosmos.Fixture {
+ OccludingAppDeviceEntryInteractor(
+ biometricMessageInteractor = biometricMessageInteractor,
+ fingerprintAuthRepository = deviceEntryFingerprintAuthRepository,
+ keyguardInteractor = keyguardInteractor,
+ primaryBouncerInteractor = primaryBouncerInteractor,
+ alternateBouncerInteractor = alternateBouncerInteractor,
+ scope = applicationCoroutineScope,
+ context = mockedContext,
+ activityStarter = activityStarter,
+ powerInteractor = powerInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
new file mode 100644
index 0000000..c208aad
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.shared.model
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeSceneDataSource(
+ initialSceneKey: SceneKey,
+) : SceneDataSource {
+
+ private val _currentScene = MutableStateFlow(initialSceneKey)
+ override val currentScene: StateFlow<SceneKey> = _currentScene.asStateFlow()
+
+ var isPaused = false
+ private set
+ var pendingScene: SceneKey? = null
+ private set
+
+ override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) {
+ if (isPaused) {
+ pendingScene = toScene
+ } else {
+ _currentScene.value = toScene
+ }
+ }
+
+ /**
+ * Pauses scene changes.
+ *
+ * Any following calls to [changeScene] will be conflated and the last one will be remembered.
+ */
+ fun pause() {
+ check(!isPaused) { "Can't pause what's already paused!" }
+
+ isPaused = true
+ }
+
+ /**
+ * Unpauses scene changes.
+ *
+ * If there were any calls to [changeScene] since [pause] was called, the latest of the bunch
+ * will be replayed.
+ *
+ * If [force] is `true`, there will be no check that [isPaused] is true.
+ *
+ * If [expectedScene] is provided, will assert that it's indeed the latest called.
+ */
+ fun unpause(
+ force: Boolean = false,
+ expectedScene: SceneKey? = null,
+ ) {
+ check(force || isPaused) { "Can't unpause what's already not paused!" }
+
+ isPaused = false
+ pendingScene?.let { _currentScene.value = it }
+ pendingScene = null
+
+ check(expectedScene == null || currentScene.value == expectedScene) {
+ """
+ Unexpected scene while unpausing.
+ Expected $expectedScene but was $currentScene.
+ """
+ .trimIndent()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt
new file mode 100644
index 0000000..f519686
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.shared.model
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.scene.initialSceneKey
+import com.android.systemui.scene.sceneContainerConfig
+
+val Kosmos.fakeSceneDataSource by Fixture {
+ FakeSceneDataSource(
+ initialSceneKey = initialSceneKey,
+ )
+}
+
+val Kosmos.sceneDataSourceDelegator by Fixture {
+ SceneDataSourceDelegator(
+ applicationScope = applicationCoroutineScope,
+ config = sceneContainerConfig,
+ )
+ .apply { setDelegate(fakeSceneDataSource) }
+}
+
+val Kosmos.sceneDataSource by Fixture { sceneDataSourceDelegator }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/NotificationsDataKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/NotificationsDataKosmos.kt
new file mode 100644
index 0000000..a61f7ec
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/NotificationsDataKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.data
+
+import com.android.settingslib.statusbar.notification.data.repository.FakeNotificationsSoundPolicyRepository
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.notificationsSoundPolicyRepository by
+ Kosmos.Fixture { FakeNotificationsSoundPolicyRepository() }
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 1ac69f6..35ce481 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -58,11 +58,14 @@
visibility: ["//visibility:public"],
}
-java_host_for_device {
- name: "core-xml-for-device",
- libs: [
- "core-xml-for-host",
+java_library {
+ // Prefixed with "200" to ensure it's sorted early in Tradefed classpath
+ // so that we provide a concrete implementation before Mainline stubs
+ name: "200-kxml2-android",
+ static_libs: [
+ "kxml2-android",
],
+ visibility: ["//frameworks/base"],
}
java_host_for_device {
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index 0319848..a5b28ad 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -2,6 +2,9 @@
"presubmit": [
{
"name": "RavenwoodMockitoTest_device"
+ },
+ {
+ "name": "RavenwoodBivalentTest_device"
}
],
"ravenwood-presubmit": [
@@ -16,6 +19,14 @@
{
"name": "CtsUtilTestCasesRavenwood",
"host": true
+ },
+ {
+ "name": "RavenwoodCoreTest",
+ "host": true
+ },
+ {
+ "name": "RavenwoodBivalentTest",
+ "host": true
}
]
}
diff --git a/ravenwood/bivalenttest/Android.bp b/ravenwood/bivalenttest/Android.bp
new file mode 100644
index 0000000..acf8533
--- /dev/null
+++ b/ravenwood/bivalenttest/Android.bp
@@ -0,0 +1,47 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_ravenwood_test {
+ name: "RavenwoodBivalentTest",
+
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ ],
+ srcs: [
+ "test/**/*.java",
+ ],
+ sdk_version: "test_current",
+ auto_gen_config: true,
+}
+
+android_test {
+ name: "RavenwoodBivalentTest_device",
+
+ srcs: [
+ "test/**/*.java",
+ ],
+ static_libs: [
+ "junit",
+ "truth",
+
+ "androidx.annotation_annotation",
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+
+ "ravenwood-junit",
+ ],
+ test_suites: [
+ "device-tests",
+ ],
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/ravenwood/bivalenttest/AndroidManifest.xml b/ravenwood/bivalenttest/AndroidManifest.xml
new file mode 100644
index 0000000..474c03f
--- /dev/null
+++ b/ravenwood/bivalenttest/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.ravenwood.bivalenttest">
+
+ <application android:debuggable="true" >
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.ravenwood.bivalenttest"
+ />
+</manifest>
diff --git a/ravenwood/bivalenttest/AndroidTest.xml b/ravenwood/bivalenttest/AndroidTest.xml
new file mode 100644
index 0000000..ac4695b
--- /dev/null
+++ b/ravenwood/bivalenttest/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Frameworks Services Tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="RavenwoodBivalentTest_device.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.ravenwood.bivalenttest" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/ravenwood/bivalenttest/README.md b/ravenwood/bivalenttest/README.md
new file mode 100644
index 0000000..7153555
--- /dev/null
+++ b/ravenwood/bivalenttest/README.md
@@ -0,0 +1,3 @@
+# Ravenwood bivalent test
+
+This test contains bivalent tests for Ravenwood itself.
\ No newline at end of file
diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java b/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java
new file mode 100644
index 0000000..4b650b4
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.platform.test.ravenwood.bivalenttest;
+
+import android.platform.test.annotations.DisabledOnNonRavenwood;
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodRuleTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+ @Test
+ @DisabledOnRavenwood
+ public void testDeviceOnly() {
+ Assert.assertFalse(RavenwoodRule.isOnRavenwood());
+ }
+
+ @Test
+ @DisabledOnNonRavenwood
+ public void testRavenwoodOnly() {
+ Assert.assertTrue(RavenwoodRule.isOnRavenwood());
+ }
+
+ // TODO: Add more tests
+}
diff --git a/ravenwood/coretest/Android.bp b/ravenwood/coretest/Android.bp
index 9b7f8f7..a78c5c1 100644
--- a/ravenwood/coretest/Android.bp
+++ b/ravenwood/coretest/Android.bp
@@ -12,9 +12,9 @@
static_libs: [
"androidx.annotation_annotation",
+ "androidx.test.ext.junit",
"androidx.test.rules",
],
-
srcs: [
"test/**/*.java",
],
diff --git a/ravenwood/coretest/README.md b/ravenwood/coretest/README.md
new file mode 100644
index 0000000..b60bfbf
--- /dev/null
+++ b/ravenwood/coretest/README.md
@@ -0,0 +1,3 @@
+# Ravenwood core test
+
+This test contains (non-bivalent) tests for Ravenwood itself -- e.g. tests for the ravenwood rules.
\ No newline at end of file
diff --git a/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java b/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java
index e58c282..2cd585f 100644
--- a/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java
+++ b/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java
@@ -17,7 +17,7 @@
import android.platform.test.ravenwood.RavenwoodRule;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.runner.AndroidJUnit4; // Intentionally use the deprecated one.
import org.junit.Assume;
import org.junit.Rule;
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index 0229611..2eef0cd 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -89,6 +89,7 @@
# Internals
class com.android.internal.util.FileRotator stubclass
+class com.android.internal.util.FastXmlSerializer stubclass
class com.android.internal.util.HexDump stubclass
class com.android.internal.util.MessageUtils stubclass
class com.android.internal.util.Preconditions stubclass
diff --git a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java
new file mode 100644
index 0000000..8ca34ba
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.test.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Tests marked with this annotation are only executed when running on Ravenwood, but not
+ * on a device.
+ *
+ * This is basically equivalent to the opposite of {@link DisabledOnRavenwood}, but in order to
+ * avoid complex structure, and there's no equivalent to the opposite {@link EnabledOnRavenwood},
+ * which means if a test class has this annotation, you can't negate it in subclasses or
+ * on a per-method basis.
+ *
+ * The {@code RAVENWOOD_RUN_DISABLED_TESTS} environmental variable won't work because it won't be
+ * propagated to the device. (We may support it in the future, possibly using a debug. sysprop.)
+ *
+ * @hide
+ */
+@Inherited
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DisabledOnNonRavenwood {
+ /**
+ * General free-form description of why this test is being ignored.
+ */
+ String reason() default "";
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
index 8d76970..9a4d488 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
@@ -18,6 +18,7 @@
import static android.platform.test.ravenwood.RavenwoodRule.ENABLE_PROBE_IGNORED;
import static android.platform.test.ravenwood.RavenwoodRule.IS_ON_RAVENWOOD;
+import static android.platform.test.ravenwood.RavenwoodRule.shouldEnableOnDevice;
import static android.platform.test.ravenwood.RavenwoodRule.shouldEnableOnRavenwood;
import static android.platform.test.ravenwood.RavenwoodRule.shouldStillIgnoreInProbeIgnoreMode;
@@ -42,6 +43,7 @@
public Statement apply(Statement base, Description description) {
// No special treatment when running outside Ravenwood; run tests as-is
if (!IS_ON_RAVENWOOD) {
+ Assume.assumeTrue(shouldEnableOnDevice(description));
return base;
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 764573d..b90f112 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.fail;
+import android.platform.test.annotations.DisabledOnNonRavenwood;
import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.EnabledOnRavenwood;
import android.platform.test.annotations.IgnoreUnderRavenwood;
@@ -211,6 +212,15 @@
return IS_ON_RAVENWOOD;
}
+ static boolean shouldEnableOnDevice(Description description) {
+ if (description.isTest()) {
+ if (description.getAnnotation(DisabledOnNonRavenwood.class) != null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/**
* Determine if the given {@link Description} should be enabled when running on the
* Ravenwood test environment.
@@ -271,6 +281,7 @@
public Statement apply(Statement base, Description description) {
// No special treatment when running outside Ravenwood; run tests as-is
if (!IS_ON_RAVENWOOD) {
+ Assume.assumeTrue(shouldEnableOnDevice(description));
return base;
}
diff --git a/ravenwood/minimum-test/README.md b/ravenwood/minimum-test/README.md
new file mode 100644
index 0000000..6b0abe9
--- /dev/null
+++ b/ravenwood/minimum-test/README.md
@@ -0,0 +1,3 @@
+# Ravenwood minimum test
+
+This directory contains a minimum possible ravenwood test -- no extra dependencies, etc.
\ No newline at end of file
diff --git a/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java b/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java
index 03cfad5..b477117 100644
--- a/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java
+++ b/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java
@@ -28,7 +28,7 @@
@RunWith(AndroidJUnit4.class)
public class RavenwoodMinimumTest {
@Rule
- public RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
.setProcessApp()
.build();
diff --git a/ravenwood/mockito/AndroidTest.xml b/ravenwood/mockito/AndroidTest.xml
index 96bc275..5ba9b1f 100644
--- a/ravenwood/mockito/AndroidTest.xml
+++ b/ravenwood/mockito/AndroidTest.xml
@@ -22,8 +22,6 @@
<option name="test-file-name" value="RavenwoodMockitoTest_device.apk" />
</target_preparer>
- <option name="test-tag" value="FrameworksMockingServicesTests" />
-
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.ravenwood.mockitotest" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
diff --git a/ravenwood/mockito/README.md b/ravenwood/mockito/README.md
new file mode 100644
index 0000000..4ceb795
--- /dev/null
+++ b/ravenwood/mockito/README.md
@@ -0,0 +1,3 @@
+# Ravenwood mockito test
+
+This directory contains a sample bivalent test using Mockito.
\ No newline at end of file
diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
index 5a548fd..82ab098 100644
--- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
+++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
@@ -33,6 +33,14 @@
]
},
{
+ "name": "CtsVirtualDevicesCameraTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
"name": "CtsHardwareTestCases",
"options": [
{
@@ -54,11 +62,33 @@
"exclude-annotation": "android.support.test.filters.FlakyTest"
}
]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsMediaAudioTestCases",
+ "options": [
+ {
+ "include-filter": "android.media.audio.cts.AudioFocusWithVdmTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
},
{
- "name": "CtsVirtualDevicesCameraTestCases",
+ "name": "CtsPermissionTestCases",
"options": [
{
+ "include-filter": "android.permissionmultidevice.cts.DeviceAwarePermissionGrantTest"
+ },
+ {
+ "include-filter": "android.permission.cts.DevicePermissionsTest"
+ },
+ {
+ "include-filter": "android.permission.cts.PermissionUpdateListenerTest"
+ },
+ {
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
diff --git a/services/core/java/com/android/server/appop/LegacyAppOpStateParser.java b/services/core/java/com/android/server/appop/LegacyAppOpStateParser.java
index a6d5050..9ed3a99 100644
--- a/services/core/java/com/android/server/appop/LegacyAppOpStateParser.java
+++ b/services/core/java/com/android/server/appop/LegacyAppOpStateParser.java
@@ -49,15 +49,7 @@
*/
public int readState(AtomicFile file, SparseArray<SparseIntArray> uidModes,
SparseArray<ArrayMap<String, SparseIntArray>> userPackageModes) {
- FileInputStream stream;
- try {
- stream = file.openRead();
- } catch (FileNotFoundException e) {
- Slog.i(TAG, "No existing app ops " + file.getBaseFile() + "; starting empty");
- return NO_FILE_VERSION;
- }
-
- try {
+ try (FileInputStream stream = file.openRead()) {
TypedXmlPullParser parser = Xml.resolvePullParser(stream);
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG
@@ -95,6 +87,9 @@
}
}
return versionAtBoot;
+ } catch (FileNotFoundException e) {
+ Slog.i(TAG, "No existing app ops " + file.getBaseFile() + "; starting empty");
+ return NO_FILE_VERSION;
} catch (XmlPullParserException e) {
throw new RuntimeException(e);
} catch (IOException e) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 53255a0..c59f4f7 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -32,6 +32,7 @@
import static android.media.AudioManager.STREAM_SYSTEM;
import static android.media.audio.Flags.autoPublicVolumeApiHardening;
import static android.media.audio.Flags.automaticBtDeviceType;
+import static android.media.audio.Flags.featureSpatialAudioHeadtrackingLowLatency;
import static android.media.audio.Flags.focusFreezeTestApi;
import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
import static android.os.Process.FIRST_APPLICATION_UID;
@@ -4519,6 +4520,10 @@
pw.println("\nFun with Flags: ");
pw.println("\tandroid.media.audio.autoPublicVolumeApiHardening:"
+ autoPublicVolumeApiHardening());
+ pw.println("\tandroid.media.audio.Flags.automaticBtDeviceType:"
+ + automaticBtDeviceType());
+ pw.println("\tandroid.media.audio.featureSpatialAudioHeadtrackingLowLatency:"
+ + featureSpatialAudioHeadtrackingLowLatency());
pw.println("\tandroid.media.audio.focusFreezeTestApi:"
+ focusFreezeTestApi());
pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:"
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 8d76731..3b5fa7f 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -26,6 +26,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.media.AudioAttributes;
@@ -1611,6 +1612,9 @@
pw.println("\tsupports binaural:" + mBinauralSupported + " / transaural:"
+ mTransauralSupported);
pw.println("\tmSpatOutput:" + mSpatOutput);
+ pw.println("\thas FEATURE_AUDIO_SPATIAL_HEADTRACKING_LOW_LATENCY:"
+ + mAudioService.mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_AUDIO_SPATIAL_HEADTRACKING_LOW_LATENCY));
}
private static String spatStateString(int state) {
diff --git a/services/core/java/com/android/server/biometrics/OWNERS b/services/core/java/com/android/server/biometrics/OWNERS
index 1bf2aef..4703efb 100644
--- a/services/core/java/com/android/server/biometrics/OWNERS
+++ b/services/core/java/com/android/server/biometrics/OWNERS
@@ -2,7 +2,6 @@
graciecheng@google.com
ilyamaty@google.com
-jaggies@google.com
jbolinger@google.com
jeffpu@google.com
joshmccloskey@google.com
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index c8c0482..a100fe0 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -77,6 +77,7 @@
@GuardedBy("ImfLock.class") private int mCurSeq;
@GuardedBy("ImfLock.class") private boolean mVisibleBound;
@GuardedBy("ImfLock.class") private boolean mSupportsStylusHw;
+ @GuardedBy("ImfLock.class") private boolean mSupportsConnectionlessStylusHw;
@Nullable private CountDownLatch mLatchForTesting;
@@ -243,10 +244,17 @@
/**
* Returns {@code true} if current IME supports Stylus Handwriting.
*/
+ @GuardedBy("ImfLock.class")
boolean supportsStylusHandwriting() {
return mSupportsStylusHw;
}
+ /** Returns whether the current IME supports connectionless stylus handwriting sessions. */
+ @GuardedBy("ImfLock.class")
+ boolean supportsConnectionlessStylusHandwriting() {
+ return mSupportsConnectionlessStylusHw;
+ }
+
/**
* Used to bring IME service up to visible adjustment while it is being shown.
*/
@@ -298,6 +306,15 @@
if (supportsStylusHwChanged) {
InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
}
+ boolean supportsConnectionlessStylusHwChanged =
+ mSupportsConnectionlessStylusHw
+ != info.supportsConnectionlessStylusHandwriting();
+ if (supportsConnectionlessStylusHwChanged) {
+ mSupportsConnectionlessStylusHw =
+ info.supportsConnectionlessStylusHandwriting();
+ InputMethodManager
+ .invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches();
+ }
mService.initializeImeLocked(mCurMethod, mCurToken);
mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
mService.reRequestCurrentClientSessionLocked();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 96c0c8a..5db18ad 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -123,6 +123,7 @@
import android.view.WindowManager.DisplayImePolicy;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
@@ -145,6 +146,7 @@
import com.android.internal.infra.AndroidFuture;
import com.android.internal.inputmethod.DirectBootAwareness;
import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
+import com.android.internal.inputmethod.IConnectionlessHandwritingCallback;
import com.android.internal.inputmethod.IImeTracker;
import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
import com.android.internal.inputmethod.IInputContentUriToken;
@@ -204,6 +206,7 @@
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -1980,7 +1983,8 @@
}
@Override
- public boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
+ public boolean isStylusHandwritingAvailableAsUser(
+ @UserIdInt int userId, boolean connectionless) {
if (UserHandle.getCallingUserId() != userId) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
@@ -1993,14 +1997,17 @@
// Check if selected IME of current user supports handwriting.
if (userId == mSettings.getUserId()) {
- return mBindingController.supportsStylusHandwriting();
+ return mBindingController.supportsStylusHandwriting()
+ && (!connectionless
+ || mBindingController.supportsConnectionlessStylusHandwriting());
}
//TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList.
//TODO(b/210039666): use cache.
final InputMethodSettings settings = queryMethodMapForUser(userId);
final InputMethodInfo imi = settings.getMethodMap().get(
settings.getSelectedInputMethod());
- return imi != null && imi.supportsStylusHandwriting();
+ return imi != null && imi.supportsStylusHandwriting()
+ && (!connectionless || imi.supportsConnectionlessStylusHandwriting());
}
}
@@ -3376,6 +3383,15 @@
startStylusHandwriting(client, false /* usesDelegation */);
}
+ @BinderThread
+ @Override
+ public void startConnectionlessStylusHandwriting(IInputMethodClient client, int userId,
+ @Nullable CursorAnchorInfo cursorAnchorInfo, @Nullable String delegatePackageName,
+ @Nullable String delegatorPackageName,
+ @NonNull IConnectionlessHandwritingCallback callback) {
+ // TODO(b/300979854)
+ }
+
private void startStylusHandwriting(IInputMethodClient client, boolean usesDelegation) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.startStylusHandwriting");
try {
@@ -5988,7 +6004,23 @@
@BinderThread
private void dumpAsProtoNoCheck(FileDescriptor fd) {
final ProtoOutputStream proto = new ProtoOutputStream(fd);
+ // Dump in the format of an ImeTracing trace with a single entry.
+ final long magicNumber =
+ ((long) InputMethodManagerServiceTraceFileProto.MAGIC_NUMBER_H << 32)
+ | InputMethodManagerServiceTraceFileProto.MAGIC_NUMBER_L;
+ final long timeOffsetNs = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
+ - SystemClock.elapsedRealtimeNanos();
+ proto.write(InputMethodManagerServiceTraceFileProto.MAGIC_NUMBER,
+ magicNumber);
+ proto.write(InputMethodManagerServiceTraceFileProto.REAL_TO_ELAPSED_TIME_OFFSET_NANOS,
+ timeOffsetNs);
+ final long token = proto.start(InputMethodManagerServiceTraceFileProto.ENTRY);
+ proto.write(InputMethodManagerServiceTraceProto.ELAPSED_REALTIME_NANOS,
+ SystemClock.elapsedRealtimeNanos());
+ proto.write(InputMethodManagerServiceTraceProto.WHERE,
+ "InputMethodManagerService.mPriorityDumper#dumpAsProtoNoCheck");
dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
+ proto.end(token);
proto.flush();
}
};
diff --git a/services/core/java/com/android/server/locksettings/OWNERS b/services/core/java/com/android/server/locksettings/OWNERS
index 5d49863..48da270 100644
--- a/services/core/java/com/android/server/locksettings/OWNERS
+++ b/services/core/java/com/android/server/locksettings/OWNERS
@@ -1,4 +1,3 @@
# Bug component: 1333694
ebiggers@google.com
-jaggies@google.com
rubinxu@google.com
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index 393e7ef..34bb219 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -55,8 +55,15 @@
@GuardedBy("mLock")
private boolean mIsClosed;
- public MediaSession2Record(Session2Token sessionToken, MediaSessionService service,
- Looper handlerLooper, int policies) {
+ private final int mPid;
+ private final ForegroundServiceDelegationOptions mForegroundServiceDelegationOptions;
+
+ public MediaSession2Record(
+ Session2Token sessionToken,
+ MediaSessionService service,
+ Looper handlerLooper,
+ int pid,
+ int policies) {
// The lock is required to prevent `Controller2Callback` from using partially initialized
// `MediaSession2Record.this`.
synchronized (mLock) {
@@ -66,7 +73,27 @@
mController = new MediaController2.Builder(service.getContext(), sessionToken)
.setControllerCallback(mHandlerExecutor, new Controller2Callback())
.build();
+ mPid = pid;
mPolicies = policies;
+ mForegroundServiceDelegationOptions =
+ new ForegroundServiceDelegationOptions.Builder()
+ .setClientPid(mPid)
+ .setClientUid(getUid())
+ .setClientPackageName(getPackageName())
+ .setClientAppThread(null)
+ .setSticky(false)
+ .setClientInstanceName(
+ "MediaSessionFgsDelegate_"
+ + getUid()
+ + "_"
+ + mPid
+ + "_"
+ + getPackageName())
+ .setForegroundServiceTypes(0)
+ .setDelegationService(
+ ForegroundServiceDelegationOptions
+ .DELEGATION_SERVICE_MEDIA_PLAYBACK)
+ .build();
}
}
@@ -91,8 +118,7 @@
@Override
public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
- // TODO: Implement when MediaSession2 knows about its owner pid.
- return null;
+ return mForegroundServiceDelegationOptions;
}
@Override
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 7fabdf2..8cb5cef 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -171,12 +171,27 @@
private final MediaCommunicationManager.SessionCallback mSession2TokenCallback =
new MediaCommunicationManager.SessionCallback() {
@Override
+ // TODO (b/324266224): Deprecate this method once other overload is published.
public void onSession2TokenCreated(Session2Token token) {
+ addSession(token, Process.INVALID_PID);
+ }
+
+ @Override
+ public void onSession2TokenCreated(Session2Token token, int pid) {
+ addSession(token, pid);
+ }
+
+ private void addSession(Session2Token token, int pid) {
if (DEBUG) {
Log.d(TAG, "Session2 is created " + token);
}
- MediaSession2Record record = new MediaSession2Record(token,
- MediaSessionService.this, mRecordThread.getLooper(), 0);
+ MediaSession2Record record =
+ new MediaSession2Record(
+ token,
+ MediaSessionService.this,
+ mRecordThread.getLooper(),
+ pid,
+ /* policies= */ 0);
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(record.getUserId());
if (user != null) {
@@ -583,7 +598,8 @@
}
ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
record.getForegroundServiceDelegationOptions();
- if (foregroundServiceDelegationOptions == null) {
+ if (foregroundServiceDelegationOptions == null
+ || foregroundServiceDelegationOptions.mClientPid == Process.INVALID_PID) {
// This record doesn't support FGS delegation. In practice, this is MediaSession2.
return;
}
diff --git a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
index 2cd8fe0..f60f55c 100644
--- a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
+++ b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
@@ -47,6 +47,7 @@
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
+import java.util.regex.Pattern;
/**
* System service manages media metrics.
@@ -79,6 +80,10 @@
private static final String FAILED_TO_GET = "failed_to_get";
private static final MediaItemInfo EMPTY_MEDIA_ITEM_INFO = new MediaItemInfo.Builder().build();
+ private static final Pattern PATTERN_KNOWN_EDITING_LIBRARY_NAMES =
+ Pattern.compile(
+ "androidx\\.media3:media3-(transformer|muxer):"
+ + "[\\d.]+(-(alpha|beta|rc)\\d\\d)?");
private static final int DURATION_BUCKETS_BELOW_ONE_MINUTE = 8;
private static final int DURATION_BUCKETS_COUNT = 13;
private static final String AUDIO_MIME_TYPE_PREFIX = "audio/";
@@ -415,8 +420,11 @@
.setAtomId(798)
.writeString(sessionId)
.writeInt(event.getFinalState())
+ .writeFloat(event.getFinalProgressPercent())
.writeInt(event.getErrorCode())
.writeLong(event.getTimeSinceCreatedMillis())
+ .writeString(getFilteredLibraryName(event.getExporterName()))
+ .writeString(getFilteredLibraryName(event.getMuxerName()))
.writeInt(getThroughputFps(event))
.writeInt(event.getInputMediaItemInfos().size())
.writeInt(inputMediaItemInfo.getSourceType())
@@ -629,6 +637,16 @@
}
}
+ private static String getFilteredLibraryName(String libraryName) {
+ if (TextUtils.isEmpty(libraryName)) {
+ return "";
+ }
+ if (!PATTERN_KNOWN_EDITING_LIBRARY_NAMES.matcher(libraryName).matches()) {
+ return "";
+ }
+ return libraryName;
+ }
+
private static int getThroughputFps(EditingEndedEvent event) {
MediaItemInfo outputMediaItemInfo = event.getOutputMediaItemInfo();
if (outputMediaItemInfo == null) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 3507d2d..ea4e67a 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -95,9 +95,11 @@
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_NULL;
import static android.os.UserHandle.USER_SYSTEM;
import static android.service.notification.Flags.redactSensitiveNotificationsFromUntrustedListeners;
+import static android.service.notification.Flags.callstyleCallbackApi;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
@@ -160,6 +162,7 @@
import android.Manifest.permission;
import android.annotation.DurationMillisLong;
import android.annotation.ElapsedRealtimeLong;
+import android.annotation.EnforcePermission;
import android.annotation.FlaggedApi;
import android.annotation.MainThread;
import android.annotation.NonNull;
@@ -176,6 +179,7 @@
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
import android.app.IActivityManager;
+import android.app.ICallNotificationEventCallback;
import android.app.INotificationManager;
import android.app.ITransientNotification;
import android.app.ITransientNotificationCallback;
@@ -255,6 +259,7 @@
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.Process;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
@@ -729,6 +734,9 @@
private NotificationUsageStats mUsageStats;
private boolean mLockScreenAllowSecureNotifications = true;
boolean mSystemExemptFromDismissal = false;
+ final ArrayMap<String, ArrayMap<Integer,
+ RemoteCallbackList<ICallNotificationEventCallback>>>
+ mCallNotificationEventCallbacks = new ArrayMap<>();
private static final int MY_UID = Process.myUid();
private static final int MY_PID = Process.myPid();
@@ -4888,6 +4896,94 @@
}
/**
+ * Register a listener to be notified when a call notification is posted or removed
+ * for a specific package and user.
+ * @param packageName Which package to monitor
+ * @param userHandle Which user to monitor
+ * @param listener Listener to register
+ */
+ @Override
+ @EnforcePermission(allOf = {
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.ACCESS_NOTIFICATIONS})
+ public void registerCallNotificationEventListener(String packageName, UserHandle userHandle,
+ ICallNotificationEventCallback listener) {
+ registerCallNotificationEventListener_enforcePermission();
+
+ final int userId = userHandle.getIdentifier() != UserHandle.USER_CURRENT
+ ? userHandle.getIdentifier() : mAmi.getCurrentUserId();
+
+ synchronized (mCallNotificationEventCallbacks) {
+ ArrayMap<Integer, RemoteCallbackList<ICallNotificationEventCallback>>
+ callbacksForPackage =
+ mCallNotificationEventCallbacks.getOrDefault(packageName, new ArrayMap<>());
+ RemoteCallbackList<ICallNotificationEventCallback> callbackList =
+ callbacksForPackage.getOrDefault(userId, new RemoteCallbackList<>());
+
+ if (callbackList.register(listener)) {
+ callbacksForPackage.put(userId, callbackList);
+ mCallNotificationEventCallbacks.put(packageName, callbacksForPackage);
+ } else {
+ Log.e(TAG,
+ "registerCallNotificationEventListener failed to register listener: "
+ + packageName + " " + userHandle + " " + listener);
+ return;
+ }
+ }
+
+ synchronized (mNotificationLock) {
+ for (NotificationRecord r : mNotificationList) {
+ if (r.getNotification().isStyle(Notification.CallStyle.class)
+ && notificationMatchesUserId(r, userId, false)
+ && r.getSbn().getPackageName().equals(packageName)) {
+ try {
+ listener.onCallNotificationPosted(packageName, r.getUser());
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Unregister a listener that was previously
+ * registered with {@link #registerCallNotificationEventListener}
+ *
+ * @param packageName Which package to stop monitoring
+ * @param userHandle Which user to stop monitoring
+ * @param listener Listener to unregister
+ */
+ @Override
+ @EnforcePermission(allOf = {
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.ACCESS_NOTIFICATIONS})
+ public void unregisterCallNotificationEventListener(String packageName,
+ UserHandle userHandle, ICallNotificationEventCallback listener) {
+ unregisterCallNotificationEventListener_enforcePermission();
+ synchronized (mCallNotificationEventCallbacks) {
+ final int userId = userHandle.getIdentifier() != UserHandle.USER_CURRENT
+ ? userHandle.getIdentifier() : mAmi.getCurrentUserId();
+
+ ArrayMap<Integer, RemoteCallbackList<ICallNotificationEventCallback>>
+ callbacksForPackage = mCallNotificationEventCallbacks.get(packageName);
+ if (callbacksForPackage == null) {
+ return;
+ }
+ RemoteCallbackList<ICallNotificationEventCallback> callbackList =
+ callbacksForPackage.get(userId);
+ if (callbackList == null) {
+ return;
+ }
+ if (!callbackList.unregister(listener)) {
+ Log.e(TAG,
+ "unregisterCallNotificationEventListener listener not found for: "
+ + packageName + " " + userHandle + " " + listener);
+ }
+ }
+ }
+
+ /**
* Register a listener binder directly with the notification manager.
*
* Only works with system callers. Apps should extend
@@ -6309,6 +6405,10 @@
Objects.requireNonNull(user);
verifyPrivilegedListener(token, user, false);
+
+ final NotificationChannel originalChannel = mPreferencesHelper.getNotificationChannel(
+ pkg, getUidForPackageAndUser(pkg, user), channel.getId(), true);
+ verifyPrivilegedListenerUriPermission(Binder.getCallingUid(), channel, originalChannel);
updateNotificationChannelInt(pkg, getUidForPackageAndUser(pkg, user), channel, true);
}
@@ -6406,6 +6506,24 @@
}
}
+ private void verifyPrivilegedListenerUriPermission(int sourceUid,
+ @NonNull NotificationChannel updateChannel,
+ @Nullable NotificationChannel originalChannel) {
+ // Check that the NLS has the required permissions to access the channel
+ final Uri soundUri = updateChannel.getSound();
+ final Uri originalSoundUri =
+ (originalChannel != null) ? originalChannel.getSound() : null;
+ if (soundUri != null && !Objects.equals(originalSoundUri, soundUri)) {
+ Binder.withCleanCallingIdentity(() -> {
+ mUgmInternal.checkGrantUriPermission(sourceUid, null,
+ ContentProvider.getUriWithoutUserId(soundUri),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ ContentProvider.getUserIdFromUri(soundUri,
+ UserHandle.getUserId(sourceUid)));
+ });
+ }
+ }
+
private int getUidForPackageAndUser(String pkg, UserHandle user) throws RemoteException {
int uid = INVALID_UID;
final long identity = Binder.clearCallingIdentity();
@@ -8676,6 +8794,11 @@
}
});
}
+
+ if (callstyleCallbackApi()) {
+ notifyCallNotificationEventListenerOnRemoved(r);
+ }
+
// ATTENTION: in a future release we will bail out here
// so that we do not play sounds, show lights, etc. for invalid
// notifications
@@ -10033,6 +10156,9 @@
mGroupHelper.onNotificationRemoved(r.getSbn());
}
});
+ if (callstyleCallbackApi()) {
+ notifyCallNotificationEventListenerOnRemoved(r);
+ }
}
if (Flags.refactorAttentionHelper()) {
@@ -11707,6 +11833,10 @@
mNotificationRecordLogger.logNotificationPosted(report);
}
});
+
+ if (callstyleCallbackApi()) {
+ notifyCallNotificationEventListenerOnPosted(r);
+ }
}
@FlaggedApi(FLAG_LIFETIME_EXTENSION_REFACTOR)
@@ -12763,6 +12893,91 @@
}
}
+ @GuardedBy("mNotificationLock")
+ private void broadcastToCallNotificationEventCallbacks(
+ final RemoteCallbackList<ICallNotificationEventCallback> callbackList,
+ final NotificationRecord r,
+ boolean isPosted) {
+ if (callbackList != null) {
+ int numCallbacks = callbackList.beginBroadcast();
+ try {
+ for (int i = 0; i < numCallbacks; i++) {
+ if (isPosted) {
+ callbackList.getBroadcastItem(i)
+ .onCallNotificationPosted(r.getSbn().getPackageName(), r.getUser());
+ } else {
+ callbackList.getBroadcastItem(i)
+ .onCallNotificationRemoved(r.getSbn().getPackageName(),
+ r.getUser());
+ }
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ callbackList.finishBroadcast();
+ }
+ }
+
+ @GuardedBy("mNotificationLock")
+ void notifyCallNotificationEventListenerOnPosted(final NotificationRecord r) {
+ if (!r.getNotification().isStyle(Notification.CallStyle.class)) {
+ return;
+ }
+
+ synchronized (mCallNotificationEventCallbacks) {
+ ArrayMap<Integer, RemoteCallbackList<ICallNotificationEventCallback>>
+ callbacksForPackage =
+ mCallNotificationEventCallbacks.get(r.getSbn().getPackageName());
+ if (callbacksForPackage == null) {
+ return;
+ }
+
+ if (!r.getUser().equals(UserHandle.ALL)) {
+ broadcastToCallNotificationEventCallbacks(
+ callbacksForPackage.get(r.getUser().getIdentifier()), r, true);
+ // Also notify the listeners registered for USER_ALL
+ broadcastToCallNotificationEventCallbacks(callbacksForPackage.get(USER_ALL), r,
+ true);
+ } else {
+ // Notify listeners registered for any userId
+ for (RemoteCallbackList<ICallNotificationEventCallback> callbackList
+ : callbacksForPackage.values()) {
+ broadcastToCallNotificationEventCallbacks(callbackList, r, true);
+ }
+ }
+ }
+ }
+
+ @GuardedBy("mNotificationLock")
+ void notifyCallNotificationEventListenerOnRemoved(final NotificationRecord r) {
+ if (!r.getNotification().isStyle(Notification.CallStyle.class)) {
+ return;
+ }
+
+ synchronized (mCallNotificationEventCallbacks) {
+ ArrayMap<Integer, RemoteCallbackList<ICallNotificationEventCallback>>
+ callbacksForPackage =
+ mCallNotificationEventCallbacks.get(r.getSbn().getPackageName());
+ if (callbacksForPackage == null) {
+ return;
+ }
+
+ if (!r.getUser().equals(UserHandle.ALL)) {
+ broadcastToCallNotificationEventCallbacks(
+ callbacksForPackage.get(r.getUser().getIdentifier()), r, false);
+ // Also notify the listeners registered for USER_ALL
+ broadcastToCallNotificationEventCallbacks(callbacksForPackage.get(USER_ALL), r,
+ false);
+ } else {
+ // Notify listeners registered for any userId
+ for (RemoteCallbackList<ICallNotificationEventCallback> callbackList
+ : callbacksForPackage.values()) {
+ broadcastToCallNotificationEventCallbacks(callbackList, r, false);
+ }
+ }
+ }
+ }
+
// TODO (b/194833441): remove when we've fully migrated to a permission
class RoleObserver implements OnRoleHoldersChangedListener {
// Role name : user id : list of approved packages
diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
index a90efe6..b9a267f 100644
--- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java
+++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
@@ -26,6 +26,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.app.Flags;
import android.app.NotificationManager;
import android.content.pm.PackageManager;
@@ -33,6 +34,7 @@
import android.service.notification.DNDPolicyProto;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
+import android.service.notification.ZenModeConfig.ZenRule;
import android.service.notification.ZenModeDiff;
import android.service.notification.ZenPolicy;
import android.util.ArrayMap;
@@ -46,6 +48,9 @@
import com.android.internal.util.FrameworkStatsLog;
import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.Objects;
/**
@@ -58,6 +63,9 @@
// Placeholder int for unknown zen mode, to distinguish from "off".
static final int ZEN_MODE_UNKNOWN = -1;
+ // Special rule type for manual rule. Keep in sync with ActiveRuleType in dnd_enums.proto.
+ protected static final int ACTIVE_RULE_TYPE_MANUAL = 999;
+
// Object for tracking config changes and policy changes associated with an overall zen
// mode change.
ZenModeEventLogger.ZenStateChanges mChangeState = new ZenModeEventLogger.ZenStateChanges();
@@ -192,7 +200,8 @@
/* bool user_action = 6 */ mChangeState.getIsUserAction(),
/* int32 package_uid = 7 */ mChangeState.getPackageUid(),
/* DNDPolicyProto current_policy = 8 */ mChangeState.getDNDPolicyProto(),
- /* bool are_channels_bypassing = 9 */ mChangeState.getAreChannelsBypassing());
+ /* bool are_channels_bypassing = 9 */ mChangeState.getAreChannelsBypassing(),
+ /* ActiveRuleType active_rule_types = 10 */ mChangeState.getActiveRuleTypes());
}
/**
@@ -371,33 +380,43 @@
}
/**
+ * Get a list of the active rules in the provided config. This is a helper function for
+ * other methods that then use this information to get the number and type of active
+ * rules available.
+ */
+ @SuppressLint("WrongConstant") // special case for log-only type on manual rule
+ @NonNull List<ZenRule> activeRulesList(ZenModeConfig config) {
+ ArrayList<ZenRule> rules = new ArrayList<>();
+ if (config == null) {
+ return rules;
+ }
+
+ if (config.manualRule != null) {
+ // If the manual rule is non-null, then it's active. We make a copy and set the rule
+ // type so that the correct value gets logged.
+ ZenRule rule = config.manualRule.copy();
+ rule.type = ACTIVE_RULE_TYPE_MANUAL;
+ rules.add(rule);
+ }
+
+ if (config.automaticRules != null) {
+ for (ZenModeConfig.ZenRule rule : config.automaticRules.values()) {
+ if (rule != null && rule.isAutomaticActive()) {
+ rules.add(rule);
+ }
+ }
+ }
+ return rules;
+ }
+
+ /**
* Get the number of active rules represented in a zen mode config. Because this is based
* on a config, this does not take into account the zen mode at the time of the config,
* which means callers need to take the zen mode into account for whether the rules are
* actually active.
*/
int numActiveRulesInConfig(ZenModeConfig config) {
- // If the config is null, return early
- if (config == null) {
- return 0;
- }
-
- int rules = 0;
- // Loop through the config and check:
- // - does a manual rule exist? (if it's non-null, it's active)
- // - how many automatic rules are active, as defined by isAutomaticActive()?
- if (config.manualRule != null) {
- rules++;
- }
-
- if (config.automaticRules != null) {
- for (ZenModeConfig.ZenRule rule : config.automaticRules.values()) {
- if (rule != null && rule.isAutomaticActive()) {
- rules++;
- }
- }
- }
- return rules;
+ return activeRulesList(config).size();
}
// Determine the number of (automatic & manual) rules active after the change takes place.
@@ -412,6 +431,34 @@
}
/**
+ * Return a list of the types of each of the active rules in the configuration.
+ * Only available when {@code MODES_API} is active; otherwise returns an empty list.
+ */
+ int[] getActiveRuleTypes() {
+ if (!Flags.modesApi() || mNewZenMode == ZEN_MODE_OFF) {
+ return new int[0];
+ }
+
+ ArrayList<Integer> activeTypes = new ArrayList<>();
+ List<ZenRule> activeRules = activeRulesList(mNewConfig);
+ if (activeRules.size() == 0) {
+ return new int[0];
+ }
+
+ for (ZenRule rule : activeRules) {
+ activeTypes.add(rule.type);
+ }
+
+ // Sort the list of active types to have a consistent order in the atom
+ Collections.sort(activeTypes);
+ int[] out = new int[activeTypes.size()];
+ for (int i = 0; i < activeTypes.size(); i++) {
+ out[i] = activeTypes.get(i);
+ }
+ return out;
+ }
+
+ /**
* Return our best guess as to whether the changes observed are due to a user action.
* Note that this (before {@code MODES_API}) won't be 100% accurate as we can't necessarily
* distinguish between a system uid call indicating "user interacted with Settings" vs "a
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 1c20b2d..54de197 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -16,6 +16,7 @@
package com.android.server.notification;
+import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DEACTIVATED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DISABLED;
@@ -1264,7 +1265,7 @@
: new ZenDeviceEffects.Builder().build();
if (isFromApp) {
- // Don't allow apps to toggle hidden effects.
+ // Don't allow apps to toggle hidden (non-public-API) effects.
newEffects = new ZenDeviceEffects.Builder(newEffects)
.setShouldDisableAutoBrightness(oldEffects.shouldDisableAutoBrightness())
.setShouldDisableTapToWake(oldEffects.shouldDisableTapToWake())
@@ -1272,6 +1273,7 @@
.setShouldDisableTouch(oldEffects.shouldDisableTouch())
.setShouldMinimizeRadioUsage(oldEffects.shouldMinimizeRadioUsage())
.setShouldMaximizeDoze(oldEffects.shouldMaximizeDoze())
+ .setExtraEffects(oldEffects.getExtraEffects())
.build();
}
@@ -1311,6 +1313,9 @@
if (oldEffects.shouldMaximizeDoze() != newEffects.shouldMaximizeDoze()) {
userModifiedFields |= ZenDeviceEffects.FIELD_MAXIMIZE_DOZE;
}
+ if (!Objects.equals(oldEffects.getExtraEffects(), newEffects.getExtraEffects())) {
+ userModifiedFields |= ZenDeviceEffects.FIELD_EXTRA_EFFECTS;
+ }
zenRule.zenDeviceEffectsUserModifiedFields = userModifiedFields;
}
}
@@ -2166,7 +2171,8 @@
/* optional DNDPolicyProto policy = 7 */ config.toZenPolicy().toProto(),
/* optional int32 rule_modified_fields = 8 */ 0,
/* optional int32 policy_modified_fields = 9 */ 0,
- /* optional int32 device_effects_modified_fields = 10 */ 0));
+ /* optional int32 device_effects_modified_fields = 10 */ 0,
+ /* optional ActiveRuleType rule_type = 11 */ TYPE_UNKNOWN));
if (config.manualRule != null) {
ruleToProtoLocked(user, config.manualRule, true, events);
}
@@ -2192,8 +2198,10 @@
pkg = rule.enabler;
}
+ int ruleType = rule.type;
if (isManualRule) {
id = ZenModeConfig.MANUAL_RULE_ID;
+ ruleType = ZenModeEventLogger.ACTIVE_RULE_TYPE_MANUAL;
}
SysUiStatsEvent.Builder data;
@@ -2212,7 +2220,8 @@
/* optional int32 rule_modified_fields = 8 */ rule.userModifiedFields,
/* optional int32 policy_modified_fields = 9 */ rule.zenPolicyUserModifiedFields,
/* optional int32 device_effects_modified_fields = 10 */
- rule.zenDeviceEffectsUserModifiedFields));
+ rule.zenDeviceEffectsUserModifiedFields,
+ /* optional ActiveRuleType rule_type = 11 */ ruleType));
}
private int getPackageUid(String pkg, int user) {
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index 25a39cc..86d05d9 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -257,7 +257,7 @@
private boolean matchesActorSignature(@NonNull AndroidPackage targetPackage,
@NonNull AndroidPackage overlayPackage, int userId) {
String targetOverlayableName = overlayPackage.getOverlayTargetOverlayableName();
- if (targetOverlayableName != null) {
+ if (targetOverlayableName != null && !mPackageManager.getNamedActors().isEmpty()) {
try {
OverlayableInfo overlayableInfo = mPackageManager.getOverlayableForTarget(
targetPackage.getPackageName(), targetOverlayableName, userId);
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index b9464d9..8729522 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -362,7 +362,7 @@
defaultPackages.add(packageName);
}
}
- return defaultPackages.toArray(new String[defaultPackages.size()]);
+ return defaultPackages.toArray(new String[0]);
}
private final class OverlayManagerPackageMonitor extends PackageMonitor {
@@ -1160,7 +1160,7 @@
// state may lead to contradictions within OMS. Better then to lag
// behind until all pending intents have been processed.
private final ArrayMap<String, PackageStateUsers> mCache = new ArrayMap<>();
- private final Set<Integer> mInitializedUsers = new ArraySet<>();
+ private final ArraySet<Integer> mInitializedUsers = new ArraySet<>();
PackageManagerHelperImpl(Context context) {
mContext = context;
@@ -1176,8 +1176,7 @@
*/
@NonNull
public ArrayMap<String, PackageState> initializeForUser(final int userId) {
- if (!mInitializedUsers.contains(userId)) {
- mInitializedUsers.add(userId);
+ if (mInitializedUsers.add(userId)) {
mPackageManagerInternal.forEachPackageState((packageState -> {
if (packageState.getPkg() != null
&& packageState.getUserStateOrDefault(userId).isInstalled()) {
@@ -1545,8 +1544,7 @@
final OverlayPaths frameworkOverlays =
mImpl.getEnabledOverlayPaths("android", userId, false);
for (final String targetPackageName : targetPackageNames) {
- final OverlayPaths.Builder list = new OverlayPaths.Builder();
- list.addAll(frameworkOverlays);
+ final var list = new OverlayPaths.Builder(frameworkOverlays);
if (!"android".equals(targetPackageName)) {
list.addAll(mImpl.getEnabledOverlayPaths(targetPackageName, userId, true));
}
@@ -1558,17 +1556,21 @@
final HashSet<String> invalidPackages = new HashSet<>();
pm.setEnabledOverlayPackages(userId, pendingChanges, updatedPackages, invalidPackages);
- for (final String targetPackageName : targetPackageNames) {
- if (DEBUG) {
- Slog.d(TAG, "-> Updating overlay: target=" + targetPackageName + " overlays=["
- + pendingChanges.get(targetPackageName)
- + "] userId=" + userId);
- }
+ if (DEBUG || !invalidPackages.isEmpty()) {
+ for (final String targetPackageName : targetPackageNames) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "-> Updating overlay: target=" + targetPackageName + " overlays=["
+ + pendingChanges.get(targetPackageName)
+ + "] userId=" + userId);
+ }
- if (invalidPackages.contains(targetPackageName)) {
- Slog.e(TAG, TextUtils.formatSimple(
- "Failed to change enabled overlays for %s user %d", targetPackageName,
- userId));
+ if (invalidPackages.contains(targetPackageName)) {
+ Slog.e(TAG, TextUtils.formatSimple(
+ "Failed to change enabled overlays for %s user %d",
+ targetPackageName,
+ userId));
+ }
}
}
return new ArrayList<>(updatedPackages);
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 972c78d..c1b6ccc 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -772,24 +772,20 @@
OverlayPaths getEnabledOverlayPaths(@NonNull final String targetPackageName,
final int userId, boolean includeImmutableOverlays) {
- final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName,
- userId);
- final OverlayPaths.Builder paths = new OverlayPaths.Builder();
- final int n = overlays.size();
- for (int i = 0; i < n; i++) {
- final OverlayInfo oi = overlays.get(i);
+ final var paths = new OverlayPaths.Builder();
+ mSettings.forEachMatching(userId, null, targetPackageName, oi -> {
if (!oi.isEnabled()) {
- continue;
+ return;
}
if (!includeImmutableOverlays && !oi.isMutable) {
- continue;
+ return;
}
if (oi.isFabricated()) {
paths.addNonApkPath(oi.baseCodePath);
} else {
paths.addApkPath(oi.baseCodePath);
}
- }
+ });
return paths.build();
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index eae614a..b8b49f3e 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -47,6 +47,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -182,6 +183,23 @@
return CollectionUtils.map(items, SettingsItem::getOverlayInfo);
}
+ void forEachMatching(int userId, String overlayName, String targetPackageName,
+ @NonNull Consumer<OverlayInfo> consumer) {
+ for (int i = 0, n = mItems.size(); i < n; i++) {
+ final SettingsItem item = mItems.get(i);
+ if (item.getUserId() != userId) {
+ continue;
+ }
+ if (overlayName != null && !item.mOverlay.getPackageName().equals(overlayName)) {
+ continue;
+ }
+ if (targetPackageName != null && !item.mTargetPackageName.equals(targetPackageName)) {
+ continue;
+ }
+ consumer.accept(item.getOverlayInfo());
+ }
+ }
+
ArrayMap<String, List<OverlayInfo>> getOverlaysForUser(final int userId) {
final List<SettingsItem> items = selectWhereUser(userId);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index db5acc2..a1dac04 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -4191,7 +4191,7 @@
+ "; old: " + pkgSetting.getPathString() + " @ "
+ pkgSetting.getVersionCode()
+ "; new: " + parsedPackage.getPath() + " @ "
- + parsedPackage.getPath());
+ + parsedPackage.getLongVersionCode());
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 796edde..f222fe9 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1466,7 +1466,7 @@
if (userType != null && !userType.equals(profile.userType)) {
continue;
}
- if (excludeHidden && isProfileHidden(userId)) {
+ if (excludeHidden && isProfileHidden(profile.id)) {
continue;
}
result.add(profile.id);
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 00036e4..af4da81 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -842,7 +842,7 @@
private int mBatteryChargeUah;
private int mBatteryHealth;
private int mBatteryTemperature;
- private int mBatteryVoltageMv = -1;
+ private int mBatteryVoltageMv;
@NonNull
private final BatteryStatsHistory mHistory;
diff --git a/services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java b/services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java
index d5bc912..b69ccb3 100644
--- a/services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java
+++ b/services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-
package com.android.server.security;
import android.content.Context;
@@ -23,6 +22,7 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Binder;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.os.UserHandle;
import android.security.keystore.IKeyAttestationApplicationIdProvider;
import android.security.keystore.KeyAttestationApplicationId;
@@ -57,7 +57,10 @@
try {
String[] packageNames = mPackageManager.getPackagesForUid(uid);
if (packageNames == null) {
- throw new RemoteException("No packages for uid");
+ throw new ServiceSpecificException(
+ IKeyAttestationApplicationIdProvider
+ .ERROR_GET_ATTESTATION_APPLICATION_ID_FAILED,
+ "No package for uid: " + uid);
}
int userId = UserHandle.getUserId(uid);
keyAttestationPackageInfos = new KeyAttestationPackageInfo[packageNames.length];
diff --git a/services/core/java/com/android/server/selinux/OWNERS b/services/core/java/com/android/server/selinux/OWNERS
new file mode 100644
index 0000000..6ca4da2
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 1117393
+
+sandrom@google.com
diff --git a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
index 0de73a5..b0dcf95 100644
--- a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
+++ b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
@@ -30,7 +30,9 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.selinux.RateLimiter;
+import java.time.Duration;
import java.util.List;
import java.util.Map;
@@ -131,19 +133,36 @@
private final Handler mMobileDataStatsHandler;
+ private final RateLimiter mRateLimiter;
+
AggregatedMobileDataStatsPuller(NetworkStatsManager networkStatsManager) {
+ if (DEBUG) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
+ TAG + "-AggregatedMobileDataStatsPullerInit");
+ }
+ }
+
+ mRateLimiter = new RateLimiter(/* window= */ Duration.ofSeconds(1));
+
mUidStats = new ArrayMap<>();
mUidPreviousState = new SparseIntArray();
mNetworkStatsManager = networkStatsManager;
- if (mNetworkStatsManager != null) {
- updateNetworkStats(mNetworkStatsManager);
- }
-
HandlerThread mMobileDataStatsHandlerThread = new HandlerThread("MobileDataStatsHandler");
mMobileDataStatsHandlerThread.start();
mMobileDataStatsHandler = new Handler(mMobileDataStatsHandlerThread.getLooper());
+
+ if (mNetworkStatsManager != null) {
+ mMobileDataStatsHandler.post(
+ () -> {
+ updateNetworkStats(mNetworkStatsManager);
+ });
+ }
+ if (DEBUG) {
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
}
public void noteUidProcessState(int uid, int state, long unusedElapsedRealtime,
@@ -180,18 +199,20 @@
}
private void noteUidProcessStateImpl(int uid, int state) {
- // noteUidProcessStateLocked can be called back to back several times while
- // the updateNetworkStatsLocked loops over several stats for multiple uids
- // and during the first call in a batch of proc state change event it can
- // contain info for uid with unknown previous state yet which can happen due to a few
- // reasons:
- // - app was just started
- // - app was started before the ActivityManagerService
- // as result stats would be created with state == ActivityManager.PROCESS_STATE_UNKNOWN
- if (mNetworkStatsManager != null) {
- updateNetworkStats(mNetworkStatsManager);
- } else {
- Slog.w(TAG, "noteUidProcessStateLocked() can not get mNetworkStatsManager");
+ if (mRateLimiter.tryAcquire()) {
+ // noteUidProcessStateImpl can be called back to back several times while
+ // the updateNetworkStats loops over several stats for multiple uids
+ // and during the first call in a batch of proc state change event it can
+ // contain info for uid with unknown previous state yet which can happen due to a few
+ // reasons:
+ // - app was just started
+ // - app was started before the ActivityManagerService
+ // as result stats would be created with state == ActivityManager.PROCESS_STATE_UNKNOWN
+ if (mNetworkStatsManager != null) {
+ updateNetworkStats(mNetworkStatsManager);
+ } else {
+ Slog.w(TAG, "noteUidProcessStateLocked() can not get mNetworkStatsManager");
+ }
}
mUidPreviousState.put(uid, state);
}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
index f4fb1a1..1bc635b 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -17,6 +17,7 @@
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
@@ -31,8 +32,12 @@
import android.webkit.WebViewProviderInfo;
import android.webkit.WebViewProviderResponse;
+import com.android.server.LocalServices;
+import com.android.server.PinnerService;
+
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
@@ -83,6 +88,8 @@
private static final int VALIDITY_INCORRECT_SIGNATURE = 3;
private static final int VALIDITY_NO_LIBRARY_FLAG = 4;
+ private static final String PIN_GROUP = "webview";
+
private final SystemInterface mSystemInterface;
private final Context mContext;
@@ -349,6 +356,39 @@
return newPackage;
}
+ private void pinWebviewIfRequired(ApplicationInfo appInfo) {
+ PinnerService pinnerService = LocalServices.getService(PinnerService.class);
+ if (pinnerService == null) {
+ // This happens in unit tests which do not have services.
+ return;
+ }
+ int webviewPinQuota = pinnerService.getWebviewPinQuota();
+ if (webviewPinQuota <= 0) {
+ return;
+ }
+
+ pinnerService.unpinGroup(PIN_GROUP);
+
+ ArrayList<String> apksToPin = new ArrayList<>();
+ boolean pinSharedFirst = appInfo.metaData.getBoolean("PIN_SHARED_LIBS_FIRST", true);
+ for (String sharedLib : appInfo.sharedLibraryFiles) {
+ apksToPin.add(sharedLib);
+ }
+ apksToPin.add(appInfo.sourceDir);
+ if (!pinSharedFirst) {
+ // We want to prioritize pinning of the native library that is most likely used by apps
+ // which in some build flavors live in the main apk and as a shared library for others.
+ Collections.reverse(apksToPin);
+ }
+ for (String apk : apksToPin) {
+ if (webviewPinQuota <= 0) {
+ break;
+ }
+ int bytesPinned = pinnerService.pinFile(apk, webviewPinQuota, appInfo, PIN_GROUP);
+ webviewPinQuota -= bytesPinned;
+ }
+ }
+
/**
* This is called when we change WebView provider, either when the current provider is
* updated or a new provider is chosen / takes precedence.
@@ -357,6 +397,7 @@
synchronized (mLock) {
mAnyWebViewInstalled = true;
if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
+ pinWebviewIfRequired(newPackage.applicationInfo);
mCurrentWebViewPackage = newPackage;
// The relro creations might 'finish' (not start at all) before
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 640c9dc..dcc68a7 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3522,10 +3522,15 @@
if (displayContent == null) {
return false;
}
- hasRestrictedWindow = displayContent.forAllWindows(windowState -> {
- return windowState.isOnScreen() && UserManager.isUserTypePrivateProfile(
- getUserManager().getProfileType(windowState.mShowUserId));
- }, true /* traverseTopToBottom */);
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ hasRestrictedWindow = displayContent.forAllWindows(windowState -> {
+ return windowState.isOnScreen() && UserManager.isUserTypePrivateProfile(
+ getUserManager().getProfileType(windowState.mShowUserId));
+ }, true /* traverseTopToBottom */);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
}
return DevicePolicyCache.getInstance().isScreenCaptureAllowed(userId)
&& !hasRestrictedWindow;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 80894b2..61fde5e 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -465,7 +465,9 @@
}
}
final InsetsSource source = new InsetsSource(id, provider.getType());
- source.setFrame(provider.getArbitraryRectangle()).updateSideHint(getBounds());
+ source.setFrame(provider.getArbitraryRectangle())
+ .updateSideHint(getBounds())
+ .setBoundingRects(provider.getBoundingRects());
mLocalInsetsSources.put(id, source);
mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 85eac29..8e7ad30 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -48,6 +48,7 @@
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCK;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCK_TASK;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MICROPHONE;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MOBILE_NETWORK;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MODIFY_USERS;
@@ -446,6 +447,7 @@
import android.security.keystore.ParcelableKeyGenParameterSpec;
import android.stats.devicepolicy.DevicePolicyEnums;
import android.telecom.TelecomManager;
+import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
@@ -22343,6 +22345,7 @@
MANAGE_DEVICE_POLICY_LOCK,
MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
MANAGE_DEVICE_POLICY_LOCK_TASK,
+ MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS,
MANAGE_DEVICE_POLICY_MICROPHONE,
MANAGE_DEVICE_POLICY_MOBILE_NETWORK,
MANAGE_DEVICE_POLICY_MODIFY_USERS,
@@ -22423,7 +22426,7 @@
MANAGE_DEVICE_POLICY_LOCATION,
MANAGE_DEVICE_POLICY_LOCK,
MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
- MANAGE_DEVICE_POLICY_CERTIFICATES,
+ MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS,
MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION,
MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
MANAGE_DEVICE_POLICY_PACKAGE_STATE,
@@ -24012,4 +24015,32 @@
Slogf.d(LOG_TAG, "Unable to get stacktrace");
}
}
+
+ @Override
+ public int[] getSubscriptionIds(String callerPackageName) {
+ final CallerIdentity caller = getCallerIdentity(callerPackageName);
+ enforceCanQuery(
+ MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS,
+ caller.getPackageName(),
+ caller.getUserId());
+ return getSubscriptionIdsInternal(callerPackageName).toArray();
+ }
+
+ private IntArray getSubscriptionIdsInternal(String callerPackageName) {
+ SubscriptionManager subscriptionManager =
+ mContext.getSystemService(SubscriptionManager.class);
+ return mInjector.binderWithCleanCallingIdentity(() -> {
+ IntArray adminOwnedSubscriptions = new IntArray();
+ List<SubscriptionInfo> subs = subscriptionManager.getAvailableSubscriptionInfoList();
+ int subCount = (subs != null) ? subs.size() : 0;
+ for (int i = 0; i < subCount; i++) {
+ SubscriptionInfo sub = subs.get(i);
+ if (sub.getGroupOwner()
+ .equals(callerPackageName)) {
+ adminOwnedSubscriptions.add(sub.getSubscriptionId());
+ }
+ }
+ return adminOwnedSubscriptions;
+ });
+ }
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c55d709..dbbfb03 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -48,7 +48,6 @@
import android.content.res.Configuration;
import android.content.res.Resources.Theme;
import android.credentials.CredentialManager;
-import android.credentials.flags.Flags;
import android.database.sqlite.SQLiteCompatibilityWalFlags;
import android.database.sqlite.SQLiteGlobal;
import android.graphics.GraphicsStatsService;
@@ -464,6 +463,11 @@
private static final String DEVICE_LOCK_APEX_PATH =
"/apex/com.android.devicelock/javalib/service-devicelock.jar";
+ private static final String PROFILING_SERVICE_LIFECYCLE_CLASS =
+ "android.os.profiling.ProfilingService$Lifecycle";
+ private static final String PROFILING_SERVICE_JAR_PATH =
+ "/apex/com.android.profiling/javalib/service-profiling.jar";
+
private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector";
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
@@ -2774,6 +2778,14 @@
mSystemServiceManager.startService(ON_DEVICE_PERSONALIZATION_SYSTEM_SERVICE_CLASS);
t.traceEnd();
+ // Profiling
+ if (android.server.Flags.telemetryApisService()) {
+ t.traceBegin("StartProfilingCompanion");
+ mSystemServiceManager.startServiceFromJar(PROFILING_SERVICE_LIFECYCLE_CLASS,
+ PROFILING_SERVICE_JAR_PATH);
+ t.traceEnd();
+ }
+
if (safeMode) {
mActivityManagerService.enterSafeMode();
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 2f5c109..67f66de 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -2958,7 +2958,7 @@
/** These permissions are supported for virtual devices. */
// TODO: b/298661870 - Use new API to get the list of device aware permissions.
val DEVICE_AWARE_PERMISSIONS =
- if (Flags.deviceAwarePermissionApisEnabled()) {
+ if (Flags.deviceAwarePermissionsEnabled()) {
setOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
} else {
emptySet<String>()
diff --git a/services/tests/mockingservicestests/src/com/android/server/OWNERS b/services/tests/mockingservicestests/src/com/android/server/OWNERS
index c0f0ce0..b363f54 100644
--- a/services/tests/mockingservicestests/src/com/android/server/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/OWNERS
@@ -1,3 +1,4 @@
per-file *Alarm* = file:/apex/jobscheduler/OWNERS
per-file *AppStateTracker* = file:/apex/jobscheduler/OWNERS
per-file *DeviceIdleController* = file:/apex/jobscheduler/OWNERS
+per-file SensitiveContentProtectionManagerServiceTest.java = file:/core/java/android/permission/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 656bc71..7bbcd50 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -722,6 +722,17 @@
Mockito.verify(mKeyguardManager, never()).addKeyguardLockedStateListener(any(), any());
}
+ @Test
+ public void testGetProfileIdsExcludingHidden() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_HIDING_PROFILES);
+ UserInfo privateProfileUser =
+ mUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+ for (int id : mUms.getProfileIdsExcludingHidden(0, true)) {
+ assertThat(id).isNotEqualTo(privateProfileUser.id);
+ }
+ }
+
/**
* Returns true if the user's XML file has Default restrictions
* @param userId Id of the user.
diff --git a/services/tests/mockingservicestests/src/com/android/server/selinux/OWNERS b/services/tests/mockingservicestests/src/com/android/server/selinux/OWNERS
new file mode 100644
index 0000000..49a0934
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/selinux/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/selinux/OWNERS
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
index 395b3aa..d36b553 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
@@ -95,24 +95,24 @@
assertThat(item = iterator.next()).isNotNull();
assertHistoryItem(item,
BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
- null, 0, 3_600_000, 90, 1_000_000);
+ null, 0, -1, 3_600_000, 90, 1_000_000);
assertThat(item = iterator.next()).isNotNull();
assertHistoryItem(item,
BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
- null, 0, 2_400_000, 80, 2_000_000);
+ null, 0, 3700, 2_400_000, 80, 2_000_000);
assertThat(item = iterator.next()).isNotNull();
assertHistoryItem(item,
BatteryStats.HistoryItem.CMD_UPDATE,
BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_START,
- "foo", APP_UID, 2_400_000, 80, 3_000_000);
+ "foo", APP_UID, 3700, 2_400_000, 80, 3_000_000);
assertThat(item = iterator.next()).isNotNull();
assertHistoryItem(item,
BatteryStats.HistoryItem.CMD_UPDATE,
BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_FINISH,
- "foo", APP_UID, 2_400_000, 80, 3_001_000);
+ "foo", APP_UID, 3700, 2_400_000, 80, 3_001_000);
assertThat(iterator.hasNext()).isFalse();
assertThat(iterator.next()).isNull();
@@ -140,7 +140,7 @@
mMockClock.currentTime = 3000;
mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
- 100, /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0, 1_000_000,
+ 100, /* plugType */ 0, 90, 72, -1, 3_600_000, 4_000_000, 0, 1_000_000,
1_000_000, 1_000_000);
mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
100, /* plugType */ 0, 80, 72, 3700, 2_400_000, 4_000_000, 0, 2_000_000,
@@ -303,7 +303,7 @@
}
private void assertHistoryItem(BatteryStats.HistoryItem item, int command, int eventCode,
- String tag, int uid, int batteryChargeUah, int batteryLevel,
+ String tag, int uid, int voltageMv, int batteryChargeUah, int batteryLevel,
long elapsedTimeMs) {
assertThat(item.cmd).isEqualTo(command);
assertThat(item.eventCode).isEqualTo(eventCode);
@@ -313,6 +313,7 @@
assertThat(item.eventTag.string).isEqualTo(tag);
assertThat(item.eventTag.uid).isEqualTo(uid);
}
+ assertThat((int) item.batteryVoltage).isEqualTo(voltageMv);
assertThat(item.batteryChargeUah).isEqualTo(batteryChargeUah);
assertThat(item.batteryLevel).isEqualTo(batteryLevel);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index df2069e..7f7cc35 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -1729,6 +1729,21 @@
mUserManager.removeUser(userInfo.id);
}
+ @Test
+ @RequiresFlagsEnabled(android.multiuser.Flags.FLAG_ENABLE_HIDING_PROFILES)
+ public void testGetProfileIdsExcludingHidden() throws Exception {
+ int mainUserId = mUserManager.getMainUser().getIdentifier();
+ final UserInfo profile = createProfileForUser("Profile",
+ UserManager.USER_TYPE_PROFILE_PRIVATE, mainUserId);
+
+ final int[] allProfiles = mUserManager.getProfileIds(mainUserId, /* enabledOnly */ false);
+ final int[] profilesExcludingHidden = mUserManager.getProfileIdsExcludingHidden(
+ mainUserId, /* enabledOnly */ false);
+
+ assertThat(allProfiles).asList().contains(profile.id);
+ assertThat(profilesExcludingHidden).asList().doesNotContain(profile.id);
+ }
+
private String generateLongString() {
String partialString = "Test Name Test Name Test Name Test Name Test Name Test Name Test "
+ "Name Test Name Test Name Test Name "; //String of length 100
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 046e057..6aacfd7 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -163,6 +163,7 @@
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
import android.app.IActivityManager;
+import android.app.ICallNotificationEventCallback;
import android.app.INotificationManager;
import android.app.ITransientNotification;
import android.app.IUriGrantsManager;
@@ -303,7 +304,6 @@
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
@@ -4003,6 +4003,69 @@
}
@Test
+ public void testUpdateNotificationChannelFromPrivilegedListener_noSoundUriPermission()
+ throws Exception {
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
+ .thenReturn(singletonList(mock(AssociationInfo.class)));
+ when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
+ eq(mTestNotificationChannel.getId()), anyBoolean()))
+ .thenReturn(mTestNotificationChannel);
+
+ final Uri soundUri = Uri.parse("content://media/test/sound/uri");
+ final NotificationChannel updatedNotificationChannel = new NotificationChannel(
+ TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
+ updatedNotificationChannel.setSound(soundUri,
+ updatedNotificationChannel.getAudioAttributes());
+
+ doThrow(new SecurityException("no access")).when(mUgmInternal)
+ .checkGrantUriPermission(eq(Process.myUid()), any(), eq(soundUri),
+ anyInt(), eq(Process.myUserHandle().getIdentifier()));
+
+ assertThrows(SecurityException.class,
+ () -> mBinderService.updateNotificationChannelFromPrivilegedListener(null, PKG,
+ Process.myUserHandle(), updatedNotificationChannel));
+
+ verify(mPreferencesHelper, never()).updateNotificationChannel(
+ anyString(), anyInt(), any(), anyBoolean(), anyInt(), anyBoolean());
+
+ verify(mListeners, never()).notifyNotificationChannelChanged(eq(PKG),
+ eq(Process.myUserHandle()), eq(mTestNotificationChannel),
+ eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED));
+ }
+
+ @Test
+ public void testUpdateNotificationChannelFromPrivilegedListener_noSoundUriPermission_sameSound()
+ throws Exception {
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
+ .thenReturn(singletonList(mock(AssociationInfo.class)));
+ when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
+ eq(mTestNotificationChannel.getId()), anyBoolean()))
+ .thenReturn(mTestNotificationChannel);
+
+ final Uri soundUri = Settings.System.DEFAULT_NOTIFICATION_URI;
+ final NotificationChannel updatedNotificationChannel = new NotificationChannel(
+ TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
+ updatedNotificationChannel.setSound(soundUri,
+ updatedNotificationChannel.getAudioAttributes());
+
+ doThrow(new SecurityException("no access")).when(mUgmInternal)
+ .checkGrantUriPermission(eq(Process.myUid()), any(), eq(soundUri),
+ anyInt(), eq(Process.myUserHandle().getIdentifier()));
+
+ mBinderService.updateNotificationChannelFromPrivilegedListener(
+ null, PKG, Process.myUserHandle(), updatedNotificationChannel);
+
+ verify(mPreferencesHelper, times(1)).updateNotificationChannel(
+ anyString(), anyInt(), any(), anyBoolean(), anyInt(), anyBoolean());
+
+ verify(mListeners, never()).notifyNotificationChannelChanged(eq(PKG),
+ eq(Process.myUserHandle()), eq(mTestNotificationChannel),
+ eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED));
+ }
+
+ @Test
public void testGetNotificationChannelFromPrivilegedListener_cdm_success() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
when(mCompanionMgr.getAssociations(PKG, mUserId))
@@ -14368,7 +14431,6 @@
}
@Test
- @Ignore("b/324348078")
public void cancelNotificationsFromListener_rapidClear_old_cancelOne() throws RemoteException {
mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
.FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
@@ -14458,7 +14520,6 @@
}
@Test
- @Ignore("b/324348078")
public void cancelNotificationsFromListener_rapidClear_old_cancelAll() throws RemoteException {
mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
.FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
@@ -14532,6 +14593,120 @@
assertFalse(mBinderService.getPrivateNotificationsAllowed());
}
+ @Test
+ @EnableFlags(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
+ public void testCallNotificationListener_NotifiedOnPostCallStyle() throws Exception {
+ ICallNotificationEventCallback listener = mock(
+ ICallNotificationEventCallback.class);
+ when(listener.asBinder()).thenReturn(mock(IBinder.class));
+ mBinderService.registerCallNotificationEventListener(PKG, UserHandle.CURRENT, listener);
+ waitForIdle();
+
+ final UserHandle userHandle = UserHandle.getUserHandleForUid(mUid);
+ final NotificationRecord r = createAndPostCallStyleNotification(PKG, userHandle,
+ "testCallNotificationListener_NotifiedOnPostCallStyle");
+
+ verify(listener, times(1)).onCallNotificationPosted(PKG, userHandle);
+
+ mBinderService.cancelNotificationWithTag(PKG, PKG, r.getSbn().getTag(), r.getSbn().getId(),
+ r.getSbn().getUserId());
+ waitForIdle();
+
+ verify(listener, times(1)).onCallNotificationRemoved(PKG, userHandle);
+ }
+
+ @Test
+ @EnableFlags(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
+ public void testCallNotificationListener_NotNotifiedOnPostNonCallStyle() throws Exception {
+ ICallNotificationEventCallback listener = mock(
+ ICallNotificationEventCallback.class);
+ when(listener.asBinder()).thenReturn(mock(IBinder.class));
+ mBinderService.registerCallNotificationEventListener(PKG,
+ UserHandle.getUserHandleForUid(mUid), listener);
+ waitForIdle();
+
+ Notification.Builder nb = new Notification.Builder(mContext,
+ mTestNotificationChannel.getId()).setSmallIcon(android.R.drawable.sym_def_app_icon);
+ final NotificationRecord r = createAndPostNotification(nb,
+ "testCallNotificationListener_NotNotifiedOnPostNonCallStyle");
+
+ verify(listener, never()).onCallNotificationPosted(anyString(), any());
+
+ mBinderService.cancelNotificationWithTag(PKG, PKG, r.getSbn().getTag(), r.getSbn().getId(),
+ r.getSbn().getUserId());
+ waitForIdle();
+
+ verify(listener, never()).onCallNotificationRemoved(anyString(), any());
+ }
+
+ @Test
+ @EnableFlags(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
+ public void testCallNotificationListener_registerForUserAll_notifiedOnAnyUserId()
+ throws Exception {
+ ICallNotificationEventCallback listener = mock(
+ ICallNotificationEventCallback.class);
+ when(listener.asBinder()).thenReturn(mock(IBinder.class));
+ mBinderService.registerCallNotificationEventListener(PKG, UserHandle.ALL, listener);
+ waitForIdle();
+
+ final UserHandle otherUser = UserHandle.of(2);
+ final NotificationRecord r = createAndPostCallStyleNotification(PKG,
+ otherUser, "testCallNotificationListener_registerForUserAll_notifiedOnAnyUserId");
+
+ verify(listener, times(1)).onCallNotificationPosted(PKG, otherUser);
+
+ mBinderService.cancelNotificationWithTag(PKG, PKG, r.getSbn().getTag(), r.getSbn().getId(),
+ r.getSbn().getUserId());
+ waitForIdle();
+
+ verify(listener, times(1)).onCallNotificationRemoved(PKG, otherUser);
+ }
+
+ @Test
+ @EnableFlags(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
+ public void testCallNotificationListener_differentPackage_notNotified() throws Exception {
+ final String packageName = "package";
+ ICallNotificationEventCallback listener = mock(
+ ICallNotificationEventCallback.class);
+ when(listener.asBinder()).thenReturn(mock(IBinder.class));
+ mBinderService.registerCallNotificationEventListener(packageName, UserHandle.ALL, listener);
+ waitForIdle();
+
+ final NotificationRecord r = createAndPostCallStyleNotification(PKG,
+ UserHandle.of(mUserId),
+ "testCallNotificationListener_differentPackage_notNotified");
+
+ verify(listener, never()).onCallNotificationPosted(anyString(), any());
+
+ mBinderService.cancelNotificationWithTag(PKG, PKG, r.getSbn().getTag(), r.getSbn().getId(),
+ r.getSbn().getUserId());
+ waitForIdle();
+
+ verify(listener, never()).onCallNotificationRemoved(anyString(), any());
+ }
+
+ private NotificationRecord createAndPostCallStyleNotification(String packageName,
+ UserHandle userHandle, String testName) throws Exception {
+ Person person = new Person.Builder().setName("caller").build();
+ Notification.Builder nb = new Notification.Builder(mContext,
+ mTestNotificationChannel.getId())
+ .setFlag(FLAG_USER_INITIATED_JOB, true)
+ .setStyle(Notification.CallStyle.forOngoingCall(
+ person, mock(PendingIntent.class)))
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, 1,
+ testName, mUid, 0, nb.build(), userHandle, null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mService.addEnqueuedNotification(r);
+ mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null)).run();
+ waitForIdle();
+
+ return mService.findNotificationLocked(
+ packageName, r.getSbn().getTag(), r.getSbn().getId(), r.getSbn().getUserId());
+ }
+
private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName)
throws RemoteException {
StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, testName, mUid, 0,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
index f604f1e..3ac7890 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
@@ -18,6 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
import android.app.Flags;
import android.os.Parcel;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -27,6 +29,8 @@
import com.android.server.UiServiceTestCase;
+import com.google.common.collect.ImmutableSet;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -52,6 +56,10 @@
.setShouldMaximizeDoze(true)
.setShouldUseNightMode(false)
.setShouldSuppressAmbientDisplay(false).setShouldSuppressAmbientDisplay(true)
+ .addExtraEffect("WILL BE GONE")
+ .setExtraEffects(ImmutableSet.of("1", "2"))
+ .addExtraEffects(ImmutableSet.of("3", "4"))
+ .addExtraEffect("5")
.build();
assertThat(deviceEffects.shouldDimWallpaper()).isTrue();
@@ -64,6 +72,7 @@
assertThat(deviceEffects.shouldMinimizeRadioUsage()).isFalse();
assertThat(deviceEffects.shouldUseNightMode()).isFalse();
assertThat(deviceEffects.shouldSuppressAmbientDisplay()).isTrue();
+ assertThat(deviceEffects.getExtraEffects()).containsExactly("1", "2", "3", "4", "5");
}
@Test
@@ -73,11 +82,13 @@
.setShouldDisableTiltToWake(true)
.setShouldUseNightMode(true)
.setShouldSuppressAmbientDisplay(true)
+ .addExtraEffect("1")
.build();
ZenDeviceEffects modified = new ZenDeviceEffects.Builder(original)
.setShouldDisplayGrayscale(true)
.setShouldUseNightMode(false)
+ .addExtraEffect("2")
.build();
assertThat(modified.shouldDimWallpaper()).isTrue(); // from original
@@ -85,6 +96,32 @@
assertThat(modified.shouldDisplayGrayscale()).isTrue(); // updated
assertThat(modified.shouldUseNightMode()).isFalse(); // updated
assertThat(modified.shouldSuppressAmbientDisplay()).isTrue(); // from original
+ assertThat(modified.getExtraEffects()).containsExactly("1", "2"); // updated
+ }
+
+ @Test
+ public void builder_add_merges() {
+ ZenDeviceEffects zde1 = new ZenDeviceEffects.Builder()
+ .setShouldDimWallpaper(true)
+ .addExtraEffect("one")
+ .build();
+ ZenDeviceEffects zde2 = new ZenDeviceEffects.Builder()
+ .setShouldDisableTouch(true)
+ .addExtraEffect("two")
+ .build();
+ ZenDeviceEffects zde3 = new ZenDeviceEffects.Builder()
+ .setShouldMinimizeRadioUsage(true)
+ .addExtraEffect("three")
+ .build();
+
+ ZenDeviceEffects add = new ZenDeviceEffects.Builder().add(zde1).add(zde2).add(zde3).build();
+
+ assertThat(add).isEqualTo(new ZenDeviceEffects.Builder()
+ .setShouldDimWallpaper(true)
+ .setShouldDisableTouch(true)
+ .setShouldMinimizeRadioUsage(true)
+ .setExtraEffects(ImmutableSet.of("one", "two", "three"))
+ .build());
}
@Test
@@ -95,6 +132,7 @@
.setShouldMinimizeRadioUsage(true)
.setShouldUseNightMode(true)
.setShouldSuppressAmbientDisplay(true)
+ .setExtraEffects(ImmutableSet.of("1", "2", "3"))
.build();
Parcel parcel = Parcel.obtain();
@@ -113,6 +151,7 @@
assertThat(copy.shouldUseNightMode()).isTrue();
assertThat(copy.shouldSuppressAmbientDisplay()).isTrue();
assertThat(copy.shouldDisplayGrayscale()).isFalse();
+ assertThat(copy.getExtraEffects()).containsExactly("1", "2", "3");
}
@Test
@@ -128,4 +167,36 @@
.build();
assertThat(effects.hasEffects()).isTrue();
}
+
+ @Test
+ public void hasEffects_extras_returnsTrue() {
+ ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
+ .addExtraEffect("extra")
+ .build();
+ assertThat(effects.hasEffects()).isTrue();
+ }
+
+ @Test
+ public void validate_extrasLength() {
+ ZenDeviceEffects okay = new ZenDeviceEffects.Builder()
+ .addExtraEffect("short")
+ .addExtraEffect("anotherShort")
+ .build();
+
+ ZenDeviceEffects pushingIt = new ZenDeviceEffects.Builder()
+ .addExtraEffect("0123456789".repeat(60))
+ .addExtraEffect("1234567890".repeat(60))
+ .build();
+
+ ZenDeviceEffects excessive = new ZenDeviceEffects.Builder()
+ .addExtraEffect("0123456789".repeat(60))
+ .addExtraEffect("1234567890".repeat(60))
+ .addExtraEffect("2345678901".repeat(60))
+ .addExtraEffect("3456789012".repeat(30))
+ .build();
+
+ okay.validate(); // No exception.
+ pushingIt.validate(); // No exception.
+ assertThrows(Exception.class, () -> excessive.validate());
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index 539bb37..12f9e26 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -49,6 +49,8 @@
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.UiServiceTestCase;
+import com.google.common.collect.ImmutableSet;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -86,7 +88,8 @@
private final int CREATION_TIME = 123;
@Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
+ SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
@Before
public final void setUp() {
@@ -496,6 +499,7 @@
.setShouldDisableTouch(true)
.setShouldMinimizeRadioUsage(false)
.setShouldMaximizeDoze(true)
+ .setExtraEffects(ImmutableSet.of("one", "two"))
.build();
rule.creationTime = CREATION_TIME;
@@ -543,6 +547,28 @@
}
@Test
+ public void testRuleXml_weirdEffects() throws Exception {
+ ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+ rule.zenDeviceEffects = new ZenDeviceEffects.Builder()
+ .setShouldMaximizeDoze(true)
+ .addExtraEffect("one,stillOne,,andStillOne,,,andYetStill")
+ .addExtraEffect(",two,stillTwo,")
+ .addExtraEffect("three\\andThree")
+ .addExtraEffect("four\\,andFour")
+ .build();
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ writeRuleXml(rule, baos);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ZenModeConfig.ZenRule fromXml = readRuleXml(bais);
+
+ assertThat(fromXml.zenDeviceEffects.getExtraEffects()).isNotNull();
+ assertThat(fromXml.zenDeviceEffects.getExtraEffects())
+ .containsExactly("one,stillOne,,andStillOne,,,andYetStill", ",two,stillTwo,",
+ "three\\andThree", "four\\,andFour");
+ }
+
+ @Test
public void testRuleXml_pkg_component() throws Exception {
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
rule.configurationActivity = new ComponentName("a", "a");
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java
index 5b35e34..ff1308c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java
@@ -132,4 +132,9 @@
checkInRange(i);
return mChanges.get(i).getAreChannelsBypassing();
}
+
+ public int[] getActiveRuleTypes(int i) throws IllegalArgumentException {
+ checkInRange(i);
+ return mChanges.get(i).getActiveRuleTypes();
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 0d88b98d..f9ba33b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -17,6 +17,7 @@
package com.android.server.notification;
import static android.app.AutomaticZenRule.TYPE_BEDTIME;
+import static android.app.AutomaticZenRule.TYPE_IMMERSIVE;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DEACTIVATED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DISABLED;
@@ -68,6 +69,7 @@
import static com.android.os.dnd.DNDProtoEnums.ROOT_CONFIG;
import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW;
import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW;
+import static com.android.server.notification.ZenModeEventLogger.ACTIVE_RULE_TYPE_MANUAL;
import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE;
import static com.google.common.collect.Iterables.getOnlyElement;
@@ -161,6 +163,7 @@
import com.android.server.notification.ManagedServices.UserProfiles;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.truth.Correspondence;
import com.google.common.util.concurrent.SettableFuture;
import com.google.protobuf.InvalidProtocolBufferException;
@@ -2581,6 +2584,7 @@
mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
ZenDeviceEffects original = new ZenDeviceEffects.Builder()
.setShouldDisableTapToWake(true)
+ .addExtraEffect("extra")
.build();
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
@@ -2592,6 +2596,7 @@
ZenDeviceEffects updateFromApp = new ZenDeviceEffects.Builder()
.setShouldUseNightMode(true) // Good
.setShouldMaximizeDoze(true) // Bad
+ .addExtraEffect("should be rejected") // Bad
.build();
mZenModeHelper.updateAutomaticZenRule(ruleId,
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
@@ -2605,6 +2610,7 @@
new ZenDeviceEffects.Builder()
.setShouldUseNightMode(true) // From update.
.setShouldDisableTapToWake(true) // From original.
+ .addExtraEffect("extra")
.build());
}
@@ -3550,6 +3556,89 @@
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testZenModeEventLog_activeRuleTypes() {
+ mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
+ setupZenConfig();
+
+ // Event 1: turn on manual zen mode. Manual rule will have ACTIVE_RULE_TYPE_MANUAL
+ mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
+
+ // Create bedtime rule
+ AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime Mode (TM)", CONDITION_ID)
+ .setType(TYPE_BEDTIME)
+ .build();
+ String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule("pkg", bedtime, UPDATE_ORIGIN_APP,
+ "reason", CUSTOM_PKG_UID);
+
+ // Create immersive rule
+ AutomaticZenRule immersive = new AutomaticZenRule.Builder("Immersed", CONDITION_ID)
+ .setType(TYPE_IMMERSIVE)
+ .build();
+ String immersiveId = mZenModeHelper.addAutomaticZenRule("pkg", immersive, UPDATE_ORIGIN_APP,
+ "reason", CUSTOM_PKG_UID);
+
+ // Event 2: Activate bedtime rule
+ mZenModeHelper.setAutomaticZenRuleState(bedtimeRuleId,
+ new Condition(bedtime.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE),
+ UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+
+ // Event 3: Turn immersive on
+ mZenModeHelper.setAutomaticZenRuleState(immersiveId,
+ new Condition(immersive.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE),
+ UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+
+ // Event 4: Turn off bedtime mode, leaving just unknown + immersive
+ mZenModeHelper.setAutomaticZenRuleState(bedtimeRuleId,
+ new Condition(bedtime.getConditionId(), "", STATE_FALSE, SOURCE_SCHEDULE),
+ UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+
+ // Total of 4 events
+ assertEquals(4, mZenModeEventLogger.numLoggedChanges());
+
+ // First event: DND_TURNED_ON; active rules: 1; type is ACTIVE_RULE_TYPE_MANUAL
+ assertThat(mZenModeEventLogger.getEventId(0)).isEqualTo(
+ ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId());
+ assertThat(mZenModeEventLogger.getChangedRuleType(0)).isEqualTo(
+ DNDProtoEnums.MANUAL_RULE);
+ assertThat(mZenModeEventLogger.getNumRulesActive(0)).isEqualTo(1);
+ int[] ruleTypes0 = mZenModeEventLogger.getActiveRuleTypes(0);
+ assertThat(ruleTypes0.length).isEqualTo(1);
+ assertThat(ruleTypes0[0]).isEqualTo(ACTIVE_RULE_TYPE_MANUAL);
+
+ // Second event: active rules: 2; types are TYPE_MANUAL and TYPE_BEDTIME
+ assertThat(mZenModeEventLogger.getChangedRuleType(1)).isEqualTo(
+ DNDProtoEnums.AUTOMATIC_RULE);
+ assertThat(mZenModeEventLogger.getNumRulesActive(1)).isEqualTo(2);
+ int[] ruleTypes1 = mZenModeEventLogger.getActiveRuleTypes(1);
+ assertThat(ruleTypes1.length).isEqualTo(2);
+ assertThat(ruleTypes1[0]).isEqualTo(TYPE_BEDTIME);
+ assertThat(ruleTypes1[1]).isEqualTo(ACTIVE_RULE_TYPE_MANUAL);
+
+ // Third event: active rules: 3
+ assertThat(mZenModeEventLogger.getEventId(2)).isEqualTo(
+ ZenModeEventLogger.ZenStateChangedEvent.DND_ACTIVE_RULES_CHANGED.getId());
+ assertThat(mZenModeEventLogger.getChangedRuleType(2)).isEqualTo(
+ DNDProtoEnums.AUTOMATIC_RULE);
+ int[] ruleTypes2 = mZenModeEventLogger.getActiveRuleTypes(2);
+ assertThat(ruleTypes2.length).isEqualTo(3);
+ assertThat(ruleTypes2[0]).isEqualTo(TYPE_BEDTIME);
+ assertThat(ruleTypes2[1]).isEqualTo(TYPE_IMMERSIVE);
+ assertThat(ruleTypes2[2]).isEqualTo(ACTIVE_RULE_TYPE_MANUAL);
+
+ // Fourth event: active rules 2, types are TYPE_MANUAL and TYPE_IMMERSIVE
+ assertThat(mZenModeEventLogger.getEventId(3)).isEqualTo(
+ ZenModeEventLogger.ZenStateChangedEvent.DND_ACTIVE_RULES_CHANGED.getId());
+ assertThat(mZenModeEventLogger.getChangedRuleType(3)).isEqualTo(
+ DNDProtoEnums.AUTOMATIC_RULE);
+ int[] ruleTypes3 = mZenModeEventLogger.getActiveRuleTypes(3);
+ assertThat(ruleTypes3.length).isEqualTo(2);
+ assertThat(ruleTypes3[0]).isEqualTo(TYPE_IMMERSIVE);
+ assertThat(ruleTypes3[1]).isEqualTo(ACTIVE_RULE_TYPE_MANUAL);
+ }
+
+ @Test
@DisableFlags(Flags.FLAG_MODES_API)
public void testUpdateConsolidatedPolicy_preModesApiDefaultRulesOnly_takesGlobalDefault() {
setupZenConfig();
@@ -4701,19 +4790,26 @@
verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
String ruleId = addRuleWithEffects(
- new ZenDeviceEffects.Builder().setShouldDisplayGrayscale(true).build());
+ new ZenDeviceEffects.Builder()
+ .setShouldDisplayGrayscale(true)
+ .addExtraEffect("ONE")
+ .build());
mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
verify(mDeviceEffectsApplier).apply(
eq(new ZenDeviceEffects.Builder()
.setShouldDisplayGrayscale(true)
+ .addExtraEffect("ONE")
.build()),
eq(UPDATE_ORIGIN_APP));
// Now create and activate a second rule that adds more effects.
String secondRuleId = addRuleWithEffects(
- new ZenDeviceEffects.Builder().setShouldDimWallpaper(true).build());
+ new ZenDeviceEffects.Builder()
+ .setShouldDimWallpaper(true)
+ .addExtraEffect("TWO")
+ .build());
mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
@@ -4722,6 +4818,7 @@
eq(new ZenDeviceEffects.Builder()
.setShouldDisplayGrayscale(true)
.setShouldDimWallpaper(true)
+ .setExtraEffects(ImmutableSet.of("ONE", "TWO"))
.build()),
eq(UPDATE_ORIGIN_APP));
}
@@ -4732,7 +4829,10 @@
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
- ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
+ ZenDeviceEffects zde = new ZenDeviceEffects.Builder()
+ .setShouldUseNightMode(true)
+ .addExtraEffect("extra_effect")
+ .build();
String ruleId = addRuleWithEffects(zde);
mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
CUSTOM_PKG_UID);
@@ -4798,7 +4898,7 @@
.setDeviceEffects(effects)
.build();
return mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- UPDATE_ORIGIN_APP, "reasons", CUSTOM_PKG_UID);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", Process.SYSTEM_UID);
}
@Test
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index bdbb6c6..7db707a 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -1797,7 +1797,6 @@
cancelVibrate(service); // Clean up long effect.
}
- @FlakyTest
@Test
public void onExternalVibration_withNewSameImportanceButRepeating_cancelsOngoingVibration()
throws Exception {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index aa9c0c8..03b695d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -48,6 +48,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -901,7 +902,8 @@
new Binder(),
0 /* index */,
WindowInsets.Type.systemOverlays(),
- new Rect(0, 0, 1080, 200));
+ new Rect(0, 0, 1080, 200),
+ null /* boundingRects */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSources
@@ -910,6 +912,31 @@
}
@Test
+ public void testAddInsetsSource_withBoundingRects() {
+ final Task rootTask = createTask(mDisplayContent);
+
+ final Task navigationBarInsetsReceiverTask = createTaskInRootTask(rootTask, 0);
+ navigationBarInsetsReceiverTask.getConfiguration().windowConfiguration.setBounds(new Rect(
+ 0, 200, 1080, 700));
+
+ final Rect[] boundingRects = new Rect[]{
+ new Rect(0, 0, 10, 10), new Rect(100, 100, 200, 100)
+ };
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.addInsetsSource(
+ navigationBarInsetsReceiverTask.mRemoteToken.toWindowContainerToken(),
+ new Binder(),
+ 0 /* index */,
+ WindowInsets.Type.systemOverlays(),
+ new Rect(0, 0, 1080, 200),
+ boundingRects);
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+
+ assertArrayEquals(boundingRects, navigationBarInsetsReceiverTask.mLocalInsetsSources
+ .valueAt(0).getBoundingRects());
+ }
+
+ @Test
public void testRemoveInsetsSource() {
final Task rootTask = createTask(mDisplayContent);
@@ -923,7 +950,8 @@
owner,
0 /* index */,
WindowInsets.Type.systemOverlays(),
- new Rect(0, 0, 1080, 200));
+ new Rect(0, 0, 1080, 200),
+ null /* boundingRects */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
final WindowContainerTransaction wct2 = new WindowContainerTransaction();
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 874c10c..a52614d 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -269,6 +269,27 @@
"android.telecom.extra.DIAGNOSTIC_MESSAGE";
/**
+ * Boolean indicating that the call is a verified business call.
+ *
+ * {@link Connection#setExtras(Bundle)} or {@link Connection#putExtras(Bundle)}
+ * should be used to notify Telecom this extra has been set.
+ */
+ @FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER)
+ public static final String EXTRA_IS_BUSINESS_CALL =
+ "android.telecom.extra.IS_BUSINESS_CALL";
+
+ /**
+ * String value indicating the asserted display name reported via
+ * ImsCallProfile#EXTRA_ASSERTED_DISPLAY_NAME.
+ *
+ * {@link Connection#setExtras(Bundle)} or {@link Connection#putExtras(Bundle)}
+ * should be used to notify Telecom this extra has been set.
+ */
+ @FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER)
+ public static final String EXTRA_ASSERTED_DISPLAY_NAME =
+ "android.telecom.extra.ASSERTED_DISPLAY_NAME";
+
+ /**
* Reject reason used with {@link #reject(int)} to indicate that the user is rejecting this
* call because they have declined to answer it. This typically means that they are unable
* to answer the call at this time and would prefer it be sent to voicemail.
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index f7793f3..697c8ec 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9778,6 +9778,13 @@
public static final String KEY_SUPPORTS_CALL_COMPOSER_BOOL = "supports_call_composer_bool";
/**
+ * Indicates if the carrier supports a business call composer.
+ */
+ @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_BUSINESS_CALL_COMPOSER)
+ public static final String KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL =
+ "supports_business_call_composer_bool";
+
+ /**
* Indicates the carrier server url that serves the call composer picture.
*/
public static final String KEY_CALL_COMPOSER_PICTURE_SERVER_URL_STRING =
@@ -10861,6 +10868,7 @@
sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
+ sDefaults.putBoolean(KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL, false);
sDefaults.putString(KEY_CALL_COMPOSER_PICTURE_SERVER_URL_STRING, "");
sDefaults.putBoolean(KEY_USE_ACS_FOR_RCS_BOOL, false);
sDefaults.putBoolean(KEY_NETWORK_TEMP_NOT_METERED_SUPPORTED_BOOL, true);
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 7118207..cd641b8 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1156,6 +1156,28 @@
*/
public static final String TRANSFER_STATUS = SimInfo.COLUMN_TRANSFER_STATUS;
+ /**
+ * TelephonyProvider column name for satellite entitlement status. The value of this column is
+ * set based on entitlement query result for satellite configuration.
+ * By default, it's disabled.
+ * <P>Type: INTEGER (int)</P>
+ *
+ * @hide
+ */
+ public static final String SATELLITE_ENTITLEMENT_STATUS =
+ SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS;
+
+ /**
+ * TelephonyProvider column name for satellite entitlement plmns. The value of this column is
+ * set based on entitlement query result for satellite configuration.
+ * By default, it's empty.
+ * <P>Type: TEXT </P>
+ *
+ * @hide
+ */
+ public static final String SATELLITE_ENTITLEMENT_PLMNS =
+ SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"USAGE_SETTING_"},
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 61c7a42..041822b 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -10629,20 +10629,27 @@
}
/**
- * Call composer status OFF from user setting.
+ * Call composer status <b>OFF</b> from user setting.
*/
public static final int CALL_COMPOSER_STATUS_OFF = 0;
/**
- * Call composer status ON from user setting.
+ * Call composer status <b>ON</b> from user setting.
*/
public static final int CALL_COMPOSER_STATUS_ON = 1;
+ /**
+ * Call composer status <b>Business Only</b> from user setting.
+ */
+ @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_BUSINESS_CALL_COMPOSER)
+ public static final int CALL_COMPOSER_STATUS_BUSINESS_ONLY = 2;
+
/** @hide */
@IntDef(prefix = {"CALL_COMPOSER_STATUS_"},
value = {
CALL_COMPOSER_STATUS_ON,
CALL_COMPOSER_STATUS_OFF,
+ CALL_COMPOSER_STATUS_BUSINESS_ONLY
})
@Retention(RetentionPolicy.SOURCE)
public @interface CallComposerStatus {}
@@ -10663,9 +10670,16 @@
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
public void setCallComposerStatus(@CallComposerStatus int status) {
- if (status > CALL_COMPOSER_STATUS_ON
- || status < CALL_COMPOSER_STATUS_OFF) {
- throw new IllegalArgumentException("requested status is invalid");
+ if (com.android.server.telecom.flags.Flags.businessCallComposer()) {
+ if (status > CALL_COMPOSER_STATUS_BUSINESS_ONLY
+ || status < CALL_COMPOSER_STATUS_OFF) {
+ throw new IllegalArgumentException("requested status is invalid");
+ }
+ } else {
+ if (status > CALL_COMPOSER_STATUS_ON
+ || status < CALL_COMPOSER_STATUS_OFF) {
+ throw new IllegalArgumentException("requested status is invalid");
+ }
}
try {
ITelephony telephony = getITelephony();
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 7935d24..e3ce766 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -1024,11 +1024,22 @@
/**
* Attempt to download the given {@link DownloadableSubscription}.
*
- * <p>Requires the {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission,
- * or the calling app must be authorized to manage both the currently-active subscription on the
+ * <p>Requires the {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS}
+ * or the calling app must be authorized to manage both the currently-active
+ * subscription on the
* current eUICC and the subscription to be downloaded according to the subscription metadata.
* Without the former, an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be
- * returned in the callback intent to prompt the user to accept the download.
+ * eturned in the callback intent to prompt the user to accept the download.
+ *
+ * <p> Starting from Android {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM},
+ * if the caller has the
+ * {@code android.Manifest.permission#MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS} permission or
+ * is a profile owner or device owner, and
+ * {@code switchAfterDownload} is {@code false}, then the downloaded subscription
+ * will be managed by that caller. If {@code switchAfterDownload} is true,
+ * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be
+ * returned in the callback intent to prompt the user to accept the download and the
+ * subscription will not be managed.
*
* <p>On a multi-active SIM device, requires the
* {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission, or a calling app
@@ -1061,7 +1072,9 @@
* @throws UnsupportedOperationException If the device does not have
* {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
- @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+ @RequiresPermission(anyOf = {
+ Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS,
+ Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS})
public void downloadSubscription(DownloadableSubscription subscription,
boolean switchAfterDownload, PendingIntent callbackIntent) {
if (!isEnabled()) {
@@ -1243,6 +1256,12 @@
* <p>Requires that the calling app has carrier privileges according to the metadata of the
* profile to be deleted, or the
* {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
+ * Starting from Android {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, if the
+ * caller is a device owner, profile owner, or holds the
+ * {@code android.Manifest.permission#MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS} permission,
+ * then the caller can delete a subscription that was downloaded by that caller.
+ * If such a caller tries to delete any other subscription then the
+ * operation will fail with {@link #EMBEDDED_SUBSCRIPTION_RESULT_ERROR}.
*
* @param subscriptionId the ID of the subscription to delete.
* @param callbackIntent a PendingIntent to launch when the operation completes.
@@ -1250,7 +1269,9 @@
* @throws UnsupportedOperationException If the device does not have
* {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
- @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+ @RequiresPermission(anyOf = {
+ Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS,
+ Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS})
public void deleteSubscription(int subscriptionId, PendingIntent callbackIntent) {
if (!isEnabled()) {
sendUnavailableError(callbackIntent);
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index d07edeb..cebfe01 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -16,6 +16,7 @@
package android.telephony.ims;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -299,6 +300,16 @@
"android.telephony.ims.extra.IS_BUSINESS_CALL";
/**
+ * The vendor IMS stack populates this {@code string} extra; it is used to hold the display name
+ * passed via the P-Asserted-Identity SIP header’s display-name field
+ *
+ * Reference: RFC3325
+ */
+ @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_BUSINESS_CALL_COMPOSER)
+ public static final String EXTRA_ASSERTED_DISPLAY_NAME =
+ "android.telephony.ims.extra.ASSERTED_DISPLAY_NAME";
+
+ /**
* Values for EXTRA_OIR / EXTRA_CNAP
*/
/**
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index 746246c..9789082 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -17,6 +17,7 @@
package android.telephony.ims.feature;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -59,6 +60,7 @@
import com.android.ims.internal.IImsMultiEndpoint;
import com.android.ims.internal.IImsUt;
import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.server.telecom.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -513,7 +515,8 @@
CAPABILITY_TYPE_VIDEO,
CAPABILITY_TYPE_UT,
CAPABILITY_TYPE_SMS,
- CAPABILITY_TYPE_CALL_COMPOSER
+ CAPABILITY_TYPE_CALL_COMPOSER,
+ CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY
})
@Retention(RetentionPolicy.SOURCE)
public @interface MmTelCapability {}
@@ -550,11 +553,19 @@
*/
public static final int CAPABILITY_TYPE_CALL_COMPOSER = 1 << 4;
+
+ /**
+ * This MmTelFeature supports Business-only Call Composer
+ */
+ @FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER)
+ public static final int CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY = 1 << 5;
+
/**
* This is used to check the upper range of MmTel capability
* @hide
*/
- public static final int CAPABILITY_TYPE_MAX = CAPABILITY_TYPE_CALL_COMPOSER + 1;
+ public static final int CAPABILITY_TYPE_MAX =
+ CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY + 1;
/**
* @hide
@@ -601,6 +612,8 @@
builder.append(isCapable(CAPABILITY_TYPE_SMS));
builder.append(" CALL_COMPOSER: ");
builder.append(isCapable(CAPABILITY_TYPE_CALL_COMPOSER));
+ builder.append(" BUSINESS_COMPOSER_ONLY: ");
+ builder.append(isCapable(CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY));
builder.append("]");
return builder.toString();
}
diff --git a/test-mock/api/test-current.txt b/test-mock/api/test-current.txt
index 9ed0108..14f1b64 100644
--- a/test-mock/api/test-current.txt
+++ b/test-mock/api/test-current.txt
@@ -3,6 +3,7 @@
public class MockContext extends android.content.Context {
method public int getDisplayId();
+ method public void updateDisplay(int);
}
@Deprecated public class MockPackageManager extends android.content.pm.PackageManager {
diff --git a/tests/UsbManagerTests/Android.bp b/tests/UsbManagerTests/Android.bp
index 70c7dad..a16a7ea 100644
--- a/tests/UsbManagerTests/Android.bp
+++ b/tests/UsbManagerTests/Android.bp
@@ -33,6 +33,8 @@
"platform-test-annotations",
"truth",
"UsbManagerTestLib",
+ "flag-junit",
+ "TestParameterInjector",
],
jni_libs: [
// Required for ExtendedMockito
diff --git a/tests/UsbManagerTests/src/android/hardware/usb/UsbPortStatusTest.java b/tests/UsbManagerTests/src/android/hardware/usb/UsbPortStatusTest.java
new file mode 100644
index 0000000..dabfcae
--- /dev/null
+++ b/tests/UsbManagerTests/src/android/hardware/usb/UsbPortStatusTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.usb;
+
+import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED;
+import static android.hardware.usb.UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
+import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE;
+import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST;
+import static android.hardware.usb.UsbPortStatus.DATA_ROLE_NONE;
+import static android.hardware.usb.UsbPortStatus.MODE_NONE;
+import static android.hardware.usb.UsbPortStatus.POWER_ROLE_NONE;
+import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK;
+import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hardware.usb.flags.Flags;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link android.hardware.usb.UsbPortStatus} */
+@RunWith(TestParameterInjector.class)
+public class UsbPortStatusTest {
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_IS_PD_COMPLIANT_API)
+ public void testIsPdCompliant(
+ @TestParameter boolean isSinkDeviceRoleSupported,
+ @TestParameter boolean isSinkHostRoleSupported,
+ @TestParameter boolean isSourceDeviceRoleSupported,
+ @TestParameter boolean isSourceHostRoleSupported) {
+ int supportedRoleCombinations = getSupportedRoleCombinations(
+ isSinkDeviceRoleSupported,
+ isSinkHostRoleSupported,
+ isSourceDeviceRoleSupported,
+ isSinkHostRoleSupported);
+ UsbPortStatus usbPortStatus = new UsbPortStatus(
+ MODE_NONE,
+ POWER_ROLE_NONE,
+ DATA_ROLE_NONE,
+ supportedRoleCombinations,
+ CONTAMINANT_PROTECTION_NONE,
+ CONTAMINANT_DETECTION_NOT_SUPPORTED);
+ boolean expectedResult = isSinkDeviceRoleSupported
+ && isSinkHostRoleSupported
+ && isSourceDeviceRoleSupported
+ && isSourceHostRoleSupported;
+
+ assertThat(usbPortStatus.isPdCompliant()).isEqualTo(expectedResult);
+ }
+
+ private int getSupportedRoleCombinations(
+ boolean isSinkDeviceRoleSupported,
+ boolean isSinkHostRoleSupported,
+ boolean isSourceDeviceRoleSupported,
+ boolean isSourceHostRoleSupported) {
+ int result = UsbPort.combineRolesAsBit(POWER_ROLE_NONE, DATA_ROLE_NONE);
+
+ if (isSinkDeviceRoleSupported) {
+ result |= UsbPort.combineRolesAsBit(POWER_ROLE_SINK, DATA_ROLE_DEVICE);
+ }
+ if (isSinkHostRoleSupported) {
+ result |= UsbPort.combineRolesAsBit(POWER_ROLE_SINK, DATA_ROLE_HOST);
+ }
+ if (isSourceDeviceRoleSupported) {
+ result |= UsbPort.combineRolesAsBit(POWER_ROLE_SOURCE, DATA_ROLE_DEVICE);
+ }
+ if (isSourceHostRoleSupported) {
+ result |= UsbPort.combineRolesAsBit(POWER_ROLE_SOURCE, DATA_ROLE_HOST);
+ }
+
+ return result;
+ }
+}
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index 57bcc04..c6dd29c 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -180,7 +180,6 @@
"framework-minus-apex.ravenwood",
],
static_libs: [
- "core-xml-for-device",
"hoststubgen-helper-libcore-runtime.ravenwood",
],
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 06eeb47c..1089f82 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -382,7 +382,7 @@
stubOutStream.putNextEntry(newEntry)
convertClass(classInternalName, /*forImpl=*/false, bis,
stubOutStream, filter, packageRedirector, enableChecker, classes,
- errors, stats)
+ errors, null)
stubOutStream.closeEntry()
}
}
@@ -415,7 +415,7 @@
enableChecker: Boolean,
classes: ClassNodes,
errors: HostStubGenErrors,
- stats: HostStubGenStats,
+ stats: HostStubGenStats?,
) {
val cr = ClassReader(input)
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
index fe4072f..50518e1 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
@@ -17,6 +17,7 @@
import com.android.hoststubgen.asm.toHumanReadableClassName
import com.android.hoststubgen.filters.FilterPolicyWithReason
+import org.objectweb.asm.Opcodes
import java.io.PrintWriter
open class HostStubGenStats {
@@ -28,12 +29,26 @@
private val stats = mutableMapOf<String, Stats>()
- fun onVisitPolicyForMethod(fullClassName: String, policy: FilterPolicyWithReason) {
+ fun onVisitPolicyForMethod(fullClassName: String, methodName: String, descriptor: String,
+ policy: FilterPolicyWithReason, access: Int) {
+ // Ignore methods that aren't public
+ if ((access and Opcodes.ACC_PUBLIC) == 0) return
+ // Ignore methods that are abstract
+ if ((access and Opcodes.ACC_ABSTRACT) != 0) return
+ // Ignore methods where policy isn't relevant
if (policy.isIgnoredForStats) return
val packageName = resolvePackageName(fullClassName)
val className = resolveClassName(fullClassName)
+ // Ignore methods for certain generated code
+ if (className.endsWith("Proto")
+ or className.endsWith("ProtoEnums")
+ or className.endsWith("LogTags")
+ or className.endsWith("StatsLog")) {
+ return
+ }
+
val packageStats = stats.getOrPut(packageName) { Stats() }
val classStats = packageStats.children.getOrPut(className) { Stats() }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
index 53eb5a8..eb03f66 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
@@ -72,6 +72,6 @@
|| reason.contains("is-enum")
|| reason.contains("is-synthetic-method")
|| reason.contains("special-class")
- || reason.contains("substitute-from")
+ || reason.contains("substitute-to")
}
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
index c20aa8b..45e140c 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
@@ -51,7 +51,7 @@
*/
data class Options (
val errors: HostStubGenErrors,
- val stats: HostStubGenStats,
+ val stats: HostStubGenStats?,
val enablePreTrace: Boolean,
val enablePostTrace: Boolean,
val enableNonStubMethodCallDetection: Boolean,
@@ -178,6 +178,7 @@
}
val p = filter.getPolicyForMethod(currentClassName, name, descriptor)
log.d("visitMethod: %s%s [%x] [%s] Policy: %s", name, descriptor, access, signature, p)
+ options.stats?.onVisitPolicyForMethod(currentClassName, name, descriptor, p, access)
log.withIndent {
// If it's a substitute-from method, then skip (== remove).
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
index beca945..416b782 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
@@ -141,11 +141,6 @@
substituted: Boolean,
superVisitor: MethodVisitor?,
): MethodVisitor? {
- // Record statistics about visiting this method when visible.
- if ((access and Opcodes.ACC_PRIVATE) == 0) {
- options.stats.onVisitPolicyForMethod(currentClassName, policy)
- }
-
// Inject method log, if needed.
var innerVisitor = superVisitor
diff --git a/tools/protologtool/OWNERS b/tools/protologtool/OWNERS
new file mode 100644
index 0000000..18cf2be
--- /dev/null
+++ b/tools/protologtool/OWNERS
@@ -0,0 +1,3 @@
+# ProtoLog owners
+# Bug component: 1157642
+include platform/development:/tools/winscope/OWNERS