diff options
298 files changed, 7484 insertions, 1073 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 79b4fd6ee9ae..7e6c30f0719a 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -54,6 +54,7 @@ aconfig_srcjars = [ ":android.service.controls.flags-aconfig-java{.generated_srcjars}", ":android.service.dreams.flags-aconfig-java{.generated_srcjars}", ":android.service.notification.flags-aconfig-java{.generated_srcjars}", + ":android.service.appprediction.flags-aconfig-java{.generated_srcjars}", ":android.service.voice.flags-aconfig-java{.generated_srcjars}", ":android.speech.flags-aconfig-java{.generated_srcjars}", ":android.systemserver.flags-aconfig-java{.generated_srcjars}", @@ -125,6 +126,7 @@ stubs_defaults { "android.provider.flags-aconfig", "android.security.flags-aconfig", "android.server.app.flags-aconfig", + "android.service.appprediction.flags-aconfig", "android.service.autofill.flags-aconfig", "android.service.chooser.flags-aconfig", "android.service.controls.flags-aconfig", @@ -726,6 +728,19 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// App prediction +aconfig_declarations { + name: "android.service.appprediction.flags-aconfig", + package: "android.service.appprediction.flags", + srcs: ["core/java/android/service/appprediction/flags/*.aconfig"], +} + +java_aconfig_library { + name: "android.service.appprediction.flags-aconfig-java", + aconfig_declarations: "android.service.appprediction.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Controls aconfig_declarations { name: "android.service.controls.flags-aconfig", @@ -40,4 +40,6 @@ per-file *Ravenwood* = file:ravenwood/OWNERS per-file PERFORMANCE_OWNERS = file:/PERFORMANCE_OWNERS -per-file PACKAGE_MANAGER_OWNERS = file:/PACKAGE_MANAGER_OWNERS
\ No newline at end of file +per-file PACKAGE_MANAGER_OWNERS = file:/PACKAGE_MANAGER_OWNERS + +per-file WEAR_OWNERS = file:/WEAR_OWNERS diff --git a/Ravenwood.bp b/Ravenwood.bp index 93febca49421..2babf6a56ab4 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -96,14 +96,19 @@ java_library { android_ravenwood_libgroup { name: "ravenwood-runtime", libs: [ + // 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-runtime.ravenwood", "hoststubgen-helper-framework-runtime.ravenwood", - "all-updatable-modules-system-stubs", + "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/WEAR_OWNERS b/WEAR_OWNERS index 4127f996da03..da8c83ebcc98 100644 --- a/WEAR_OWNERS +++ b/WEAR_OWNERS @@ -10,3 +10,4 @@ sadrul@google.com rwmyers@google.com nalmalki@google.com shijianli@google.com +latkin@google.com diff --git a/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java b/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java index 123b2eeba5dd..0fd24493f2d5 100644 --- a/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java +++ b/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java @@ -21,8 +21,11 @@ import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_UP; import static android.view.MotionEvent.TOOL_TYPE_FINGER; import static android.view.MotionEvent.TOOL_TYPE_STYLUS; +import static android.view.inputmethod.Flags.initiationWithoutInputConnection; +import static org.junit.Assume.assumeFalse; + import android.app.Instrumentation; import android.content.Context; import android.perftests.utils.BenchmarkState; @@ -186,6 +189,7 @@ public class HandwritingInitiatorPerfTest { @Test public void onInputConnectionCreated() { + assumeFalse(initiationWithoutInputConnection()); final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); final View view = new View(mContext); final EditorInfo editorInfo = new EditorInfo(); @@ -199,6 +203,7 @@ public class HandwritingInitiatorPerfTest { @Test public void onInputConnectionClosed() { + assumeFalse(initiationWithoutInputConnection()); final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); final View view = new View(mContext); while (state.keepRunning()) { diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index 52c0ac1d981d..7de67993bc62 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -124,15 +124,6 @@ public class JobInfo implements Parcelable { @Overridable // Aid in testing public static final long ENFORCE_MINIMUM_TIME_WINDOWS = 311402873L; - /** - * Require that minimum latencies and override deadlines are nonnegative. - * - * @hide - */ - @ChangeId - @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - public static final long REJECT_NEGATIVE_DELAYS_AND_DEADLINES = 323349338L; - /** @hide */ @IntDef(prefix = { "NETWORK_TYPE_" }, value = { NETWORK_TYPE_NONE, @@ -701,14 +692,14 @@ public class JobInfo implements Parcelable { * @see JobInfo.Builder#setMinimumLatency(long) */ public long getMinLatencyMillis() { - return Math.max(0, minLatencyMillis); + return minLatencyMillis; } /** * @see JobInfo.Builder#setOverrideDeadline(long) */ public long getMaxExecutionDelayMillis() { - return Math.max(0, maxExecutionDelayMillis); + return maxExecutionDelayMillis; } /** @@ -827,7 +818,7 @@ public class JobInfo implements Parcelable { * @hide */ public boolean hasEarlyConstraint() { - return hasEarlyConstraint && minLatencyMillis > 0; + return hasEarlyConstraint; } /** @@ -836,7 +827,7 @@ public class JobInfo implements Parcelable { * @hide */ public boolean hasLateConstraint() { - return hasLateConstraint && maxExecutionDelayMillis >= 0; + return hasLateConstraint; } @Override @@ -1878,13 +1869,6 @@ public class JobInfo implements Parcelable { * Because it doesn't make sense setting this property on a periodic job, doing so will * throw an {@link java.lang.IllegalArgumentException} when * {@link android.app.job.JobInfo.Builder#build()} is called. - * - * Negative latencies also don't make sense for a job and are indicative of an error, - * so starting in Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, - * setting a negative deadline will result in - * {@link android.app.job.JobInfo.Builder#build()} throwing an - * {@link java.lang.IllegalArgumentException}. - * * @param minLatencyMillis Milliseconds before which this job will not be considered for * execution. * @see JobInfo#getMinLatencyMillis() @@ -1908,13 +1892,6 @@ public class JobInfo implements Parcelable { * throw an {@link java.lang.IllegalArgumentException} when * {@link android.app.job.JobInfo.Builder#build()} is called. * - * <p> - * Negative deadlines also don't make sense for a job and are indicative of an error, - * so starting in Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, - * setting a negative deadline will result in - * {@link android.app.job.JobInfo.Builder#build()} throwing an - * {@link java.lang.IllegalArgumentException}. - * * <p class="note"> * Since a job will run once the deadline has passed regardless of the status of other * constraints, setting a deadline of 0 (or a {@link #setMinimumLatency(long) delay} equal @@ -2212,15 +2189,13 @@ public class JobInfo implements Parcelable { public JobInfo build() { return build(Compatibility.isChangeEnabled(DISALLOW_DEADLINES_FOR_PREFETCH_JOBS), Compatibility.isChangeEnabled(REJECT_NEGATIVE_NETWORK_ESTIMATES), - Compatibility.isChangeEnabled(ENFORCE_MINIMUM_TIME_WINDOWS), - Compatibility.isChangeEnabled(REJECT_NEGATIVE_DELAYS_AND_DEADLINES)); + Compatibility.isChangeEnabled(ENFORCE_MINIMUM_TIME_WINDOWS)); } /** @hide */ public JobInfo build(boolean disallowPrefetchDeadlines, boolean rejectNegativeNetworkEstimates, - boolean enforceMinimumTimeWindows, - boolean rejectNegativeDelaysAndDeadlines) { + boolean enforceMinimumTimeWindows) { // This check doesn't need to be inside enforceValidity. It's an unnecessary legacy // check that would ideally be phased out instead. if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) { @@ -2230,7 +2205,7 @@ public class JobInfo implements Parcelable { } JobInfo jobInfo = new JobInfo(this); jobInfo.enforceValidity(disallowPrefetchDeadlines, rejectNegativeNetworkEstimates, - enforceMinimumTimeWindows, rejectNegativeDelaysAndDeadlines); + enforceMinimumTimeWindows); return jobInfo; } @@ -2250,8 +2225,7 @@ public class JobInfo implements Parcelable { */ public final void enforceValidity(boolean disallowPrefetchDeadlines, boolean rejectNegativeNetworkEstimates, - boolean enforceMinimumTimeWindows, - boolean rejectNegativeDelaysAndDeadlines) { + boolean enforceMinimumTimeWindows) { // Check that network estimates require network type and are reasonable values. if ((networkDownloadBytes > 0 || networkUploadBytes > 0 || minimumNetworkChunkBytes > 0) && networkRequest == null) { @@ -2285,17 +2259,6 @@ public class JobInfo implements Parcelable { throw new IllegalArgumentException("Minimum chunk size must be positive"); } - if (rejectNegativeDelaysAndDeadlines) { - if (minLatencyMillis < 0) { - throw new IllegalArgumentException( - "Minimum latency is negative: " + minLatencyMillis); - } - if (maxExecutionDelayMillis < 0) { - throw new IllegalArgumentException( - "Override deadline is negative: " + maxExecutionDelayMillis); - } - } - final boolean hasDeadline = maxExecutionDelayMillis != 0L; // Check that a deadline was not set on a periodic job. if (isPeriodic) { diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index f819f15b430f..a83c099b764d 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -4850,7 +4850,7 @@ public class JobSchedulerService extends com.android.server.SystemService Slog.w(TAG, "Uid " + uid + " set bias on its job"); return new JobInfo.Builder(job) .setBias(JobInfo.BIAS_DEFAULT) - .build(false, false, false, false); + .build(false, false, false); } } @@ -4874,9 +4874,7 @@ public class JobSchedulerService extends com.android.server.SystemService JobInfo.DISALLOW_DEADLINES_FOR_PREFETCH_JOBS, callingUid), rejectNegativeNetworkEstimates, CompatChanges.isChangeEnabled( - JobInfo.ENFORCE_MINIMUM_TIME_WINDOWS, callingUid), - CompatChanges.isChangeEnabled( - JobInfo.REJECT_NEGATIVE_DELAYS_AND_DEADLINES, callingUid)); + JobInfo.ENFORCE_MINIMUM_TIME_WINDOWS, callingUid)); if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) { getContext().enforceCallingOrSelfPermission( android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java index d8934d8f83b8..53b14d616ecc 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java @@ -1495,7 +1495,7 @@ public final class JobStore { // return value), the deadline is dropped. Periodic jobs require all constraints // to be met, so there's no issue with their deadlines. // The same logic applies for other target SDK-based validation checks. - builtJob = jobBuilder.build(false, false, false, false); + builtJob = jobBuilder.build(false, false, false); } catch (Exception e) { Slog.w(TAG, "Unable to build job from XML, ignoring: " + jobBuilder.summarize(), e); return null; diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index edd86e3454a5..a0b9c5fb7a60 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -652,7 +652,7 @@ public final class JobStatus { .build()); // Don't perform validation checks at this point since we've already passed the // initial validation check. - job = builder.build(false, false, false, false); + job = builder.build(false, false, false); } this.job = job; diff --git a/cmds/uinput/README.md b/cmds/uinput/README.md index b6e4e0de8cf7..f1775864aca0 100644 --- a/cmds/uinput/README.md +++ b/cmds/uinput/README.md @@ -154,7 +154,8 @@ will be unregistered. There is no explicit command for unregistering a device. #### `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 a78a46504684..bd61000186e5 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 @@ UinputDevice::~UinputDevice() { ::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 closeUinputDevice(JNIEnv* /* env */, jclass /* clazz */, jlong ptr) } } -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 @@ static JNINativeMethod sMethods[] = { "(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 9769a75bd9ef..72c8647ae743 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 @@ public: 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 25d3a341bf6e..b452fc7094ba 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 @@ public class Device { 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 @@ public class Device { 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 @@ public class Device { } 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 @@ public class Device { * @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 class Device { */ 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 @@ public class Device { private class DeviceHandler extends Handler { private long mPtr; + private long mLastInjectTimestampMicros = -1; private int mBarrierToken; DeviceHandler(Looper looper) { @@ -160,7 +191,7 @@ public class Device { @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 @@ public class Device { } 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 @@ public class Device { 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 7652f2403f6e..da991624e685 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 @@ public class EvemuParser implements EventParser { * 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 @@ public class EvemuParser implements EventParser { 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 @@ public class EvemuParser implements EventParser { 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 @@ public class EvemuParser implements EventParser { 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 0f16a27aac1d..9e7ee0937efe 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 @@ public class Event { 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 @@ public class Event { } /** + * 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 @@ public class Event { + ", busId=" + mBusId + ", events=" + Arrays.toString(mInjections) + ", configuration=" + mConfiguration - + ", duration=" + mDurationMillis + "ms" + + ", duration=" + mDurationNanos + "ns" + ", ff_effects_max=" + mFfEffectsMax + ", port=" + mInputPort + "}"; @@ -211,6 +221,10 @@ public class Event { 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 @@ public class Event { 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 @@ public class Event { } } 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 ed3ff33f7e52..6994f0cb0e4b 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 @@ public class JsonStyleParser implements EventParser { 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 04df27987d58..760e981c8465 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 @@ public class Uinput { 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 a05cc676ddfa..5239fbc7e0a8 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 @@ public class EvemuParserTest { } 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 @@ public class EvemuParserTest { 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 @@ public class EvemuParserTest { 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 @@ public class EvemuParserTest { 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 @@ public class EvemuParserTest { 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 18153a5d4d34..ec8bc96ef100 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -653,6 +653,7 @@ package android { 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 +1603,7 @@ package android { 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 @@ -5448,7 +5450,6 @@ package android.app { } @FlaggedApi("android.security.content_uri_permission_apis") public final class ComponentCaller { - ctor public ComponentCaller(@NonNull android.os.IBinder, @Nullable android.os.IBinder); method public int checkContentUriPermission(@NonNull android.net.Uri, int); method @Nullable public String getPackage(); method public int getUid(); @@ -7425,6 +7426,7 @@ package android.app { method public int onStartCommand(android.content.Intent, int, int); method public void onTaskRemoved(android.content.Intent); method public void onTimeout(int); + method @FlaggedApi("android.app.introduce_new_service_ontimeout_callback") public void onTimeout(int, int); method public void onTrimMemory(int); method public boolean onUnbind(android.content.Intent); method public final void startForeground(int, android.app.Notification); @@ -10045,11 +10047,22 @@ package android.content { method public CharSequence coerceToText(android.content.Context); method public String getHtmlText(); method public android.content.Intent getIntent(); + method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @Nullable public android.app.PendingIntent getPendingIntent(); method public CharSequence getText(); method @Nullable public android.view.textclassifier.TextLinks getTextLinks(); method public android.net.Uri getUri(); } + @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") public static final class ClipData.Item.Builder { + ctor public ClipData.Item.Builder(); + method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item build(); + method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setHtmlText(@Nullable String); + method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setIntent(@Nullable android.content.Intent); + method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setPendingIntent(@Nullable android.app.PendingIntent); + method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setText(@Nullable CharSequence); + method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setUri(@Nullable android.net.Uri); + } + public class ClipDescription implements android.os.Parcelable { ctor public ClipDescription(CharSequence, String[]); ctor public ClipDescription(android.content.ClipDescription); @@ -10714,6 +10727,7 @@ package android.content { 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 @@ -11304,10 +11318,14 @@ package android.content { 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"; @@ -11336,6 +11354,7 @@ package android.content { field public static final String EXTRA_LOCALE_LIST = "android.intent.extra.LOCALE_LIST"; field public static final String EXTRA_LOCAL_ONLY = "android.intent.extra.LOCAL_ONLY"; field public static final String EXTRA_LOCUS_ID = "android.intent.extra.LOCUS_ID"; + field @FlaggedApi("android.service.chooser.enable_sharesheet_metadata_extra") public static final String EXTRA_METADATA_TEXT = "android.intent.extra.METADATA_TEXT"; field public static final String EXTRA_MIME_TYPES = "android.intent.extra.MIME_TYPES"; field public static final String EXTRA_NOT_UNKNOWN_SOURCE = "android.intent.extra.NOT_UNKNOWN_SOURCE"; field public static final String EXTRA_ORIGINATING_URI = "android.intent.extra.ORIGINATING_URI"; @@ -13026,6 +13045,7 @@ package android.content.pm { 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"; @@ -18847,6 +18867,7 @@ package android.hardware.biometrics { method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable public android.hardware.biometrics.PromptContentView getContentView(); method @Nullable public CharSequence getDescription(); method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.graphics.Bitmap getLogoBitmap(); + method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public String getLogoDescription(); method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @DrawableRes @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public int getLogoRes(); method @Nullable public CharSequence getNegativeButtonText(); method @Nullable public CharSequence getSubtitle(); @@ -18898,6 +18919,7 @@ package android.hardware.biometrics { method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence); method @Deprecated @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDeviceCredentialAllowed(boolean); method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.hardware.biometrics.BiometricPrompt.Builder setLogoBitmap(@NonNull android.graphics.Bitmap); + method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.hardware.biometrics.BiometricPrompt.Builder setLogoDescription(@NonNull String); method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.hardware.biometrics.BiometricPrompt.Builder setLogoRes(@DrawableRes int); method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener); method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence); @@ -25869,8 +25891,11 @@ package android.media.metrics { @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); @@ -25905,6 +25930,7 @@ package android.media.metrics { 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 } @@ -25914,7 +25940,10 @@ package android.media.metrics { 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); } @@ -27792,7 +27821,7 @@ package android.media.tv { 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); @@ -27803,6 +27832,14 @@ package android.media.tv.ad { 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"; @@ -27815,6 +27852,9 @@ package android.media.tv.ad { 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 { @@ -27837,7 +27877,12 @@ package android.media.tv.ad { 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); @@ -27847,12 +27892,20 @@ package android.media.tv.ad { 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); } @@ -27871,9 +27924,12 @@ package android.media.tv.ad { 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); @@ -27883,16 +27939,34 @@ package android.media.tv.ad { 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 { @@ -35466,6 +35540,7 @@ package android.provider { 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 @@ -35512,6 +35587,7 @@ package android.provider { 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"; @@ -40211,6 +40287,21 @@ package android.service.carrier { 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(); @@ -40225,6 +40316,19 @@ package android.service.chooser { 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(); @@ -41989,8 +42093,10 @@ package android.telecom { 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"; @@ -43618,6 +43724,7 @@ package android.telephony { 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"; @@ -45883,6 +45990,7 @@ package android.telephony { 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 @@ -46919,6 +47027,7 @@ package android.telephony.ims.feature { 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 @@ -52894,9 +53003,11 @@ package android.view { field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100 field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40 field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80 + field @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") public static final int DRAG_FLAG_GLOBAL_SAME_APPLICATION = 4096; // 0x1000 field public static final int DRAG_FLAG_GLOBAL_URI_READ = 1; // 0x1 field public static final int DRAG_FLAG_GLOBAL_URI_WRITE = 2; // 0x2 field public static final int DRAG_FLAG_OPAQUE = 512; // 0x200 + field @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") public static final int DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG = 8192; // 0x2000 field @Deprecated public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0 field @Deprecated public static final int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000 field @Deprecated public static final int DRAWING_CACHE_QUALITY_LOW = 524288; // 0x80000 @@ -53986,8 +54097,11 @@ package android.view { 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(); @@ -54022,8 +54136,11 @@ package android.view { 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); @@ -55996,6 +56113,7 @@ package android.view.inputmethod { 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); @@ -56025,6 +56143,7 @@ package android.view.inputmethod { 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(); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 1273da71b748..af8b708d7665 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -412,6 +412,19 @@ package android.os { 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 18f955f17fb6..e44eb24f131b 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -282,6 +282,7 @@ package android { 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 @@ package android { 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"; @@ -2205,6 +2207,7 @@ package android.app.prediction { method public void notifyLaunchLocationShown(@NonNull String, @NonNull java.util.List<android.app.prediction.AppTargetId>); method public void registerPredictionUpdates(@NonNull java.util.concurrent.Executor, @NonNull android.app.prediction.AppPredictor.Callback); method public void requestPredictionUpdate(); + method @FlaggedApi("android.service.appprediction.flags.service_features_api") public void requestServiceFeatures(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.os.Bundle>); method @Nullable public void sortTargets(@NonNull java.util.List<android.app.prediction.AppTarget>, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.prediction.AppTarget>>); method public void unregisterPredictionUpdates(@NonNull android.app.prediction.AppPredictor.Callback); } @@ -6641,6 +6644,7 @@ package android.hardware.usb { 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); @@ -11456,6 +11460,29 @@ package android.printservice.recommendation { 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>); } @@ -12059,6 +12086,7 @@ package android.service.appprediction { method @MainThread public void onDestroyPredictionSession(@NonNull android.app.prediction.AppPredictionSessionId); method @MainThread public abstract void onLaunchLocationShown(@NonNull android.app.prediction.AppPredictionSessionId, @NonNull String, @NonNull java.util.List<android.app.prediction.AppTargetId>); method @MainThread public abstract void onRequestPredictionUpdate(@NonNull android.app.prediction.AppPredictionSessionId); + method @FlaggedApi("android.service.appprediction.flags.service_features_api") @MainThread public void onRequestServiceFeatures(@NonNull android.app.prediction.AppPredictionSessionId, @NonNull java.util.function.Consumer<android.os.Bundle>); method @MainThread public abstract void onSortAppTargets(@NonNull android.app.prediction.AppPredictionSessionId, @NonNull java.util.List<android.app.prediction.AppTarget>, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<java.util.List<android.app.prediction.AppTarget>>); method @MainThread public void onStartPredictionUpdates(); method @MainThread public void onStopPredictionUpdates(); @@ -16116,6 +16144,7 @@ package android.telephony.ims { 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 a7f80ddc1598..5b9032241349 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2089,7 +2089,7 @@ package android.media.metrics { 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); } } @@ -3901,7 +3901,7 @@ package android.view.inputmethod { } 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 2c00c99cec91..b25d5ebf61a0 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -146,6 +146,8 @@ import android.os.Parcel; 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; @@ -1232,6 +1234,15 @@ public final class ActivityThread extends ClientTransactionHandler } @Override + public final void scheduleTimeoutServiceForType(IBinder token, int startId, int fgsType) { + if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { + Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, + "scheduleTimeoutServiceForType. token=" + token); + } + sendMessage(H.TIMEOUT_SERVICE_FOR_TYPE, token, startId, fgsType); + } + + @Override public final void bindApplication( String processName, ApplicationInfo appInfo, @@ -2288,6 +2299,8 @@ public final class ActivityThread extends ClientTransactionHandler public static final int INSTRUMENT_WITHOUT_RESTART = 170; public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171; + public static final int TIMEOUT_SERVICE_FOR_TYPE = 172; + String codeToString(int code) { if (DEBUG_MESSAGES) { switch (code) { @@ -2341,6 +2354,7 @@ public final class ActivityThread extends ClientTransactionHandler case DUMP_RESOURCES: return "DUMP_RESOURCES"; case TIMEOUT_SERVICE: return "TIMEOUT_SERVICE"; case PING: return "PING"; + case TIMEOUT_SERVICE_FOR_TYPE: return "TIMEOUT_SERVICE_FOR_TYPE"; } } return Integer.toString(code); @@ -2427,6 +2441,14 @@ public final class ActivityThread extends ClientTransactionHandler case PING: ((RemoteCallback) msg.obj).sendResult(null); break; + case TIMEOUT_SERVICE_FOR_TYPE: + if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, + "serviceTimeoutForType: " + msg.obj); + } + handleTimeoutServiceForType((IBinder) msg.obj, msg.arg1, msg.arg2); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; case CONFIGURATION_CHANGED: mConfigurationController.handleConfigurationChanged((Configuration) msg.obj); break; @@ -5136,6 +5158,26 @@ public final class ActivityThread extends ClientTransactionHandler Slog.wtf(TAG, "handleTimeoutService: token=" + token + " not found."); } } + + private void handleTimeoutServiceForType(IBinder token, int startId, int fgsType) { + Service s = mServices.get(token); + if (s != null) { + try { + if (localLOGV) Slog.v(TAG, "Timeout service " + s); + + s.callOnTimeLimitExceeded(startId, fgsType); + } catch (Exception e) { + if (!mInstrumentation.onException(s, e)) { + throw new RuntimeException( + "Unable to call onTimeLimitExceeded on service " + s + ": " + e, e); + } + Slog.i(TAG, "handleTimeoutServiceForType: exception for " + token, e); + } + } else { + Slog.wtf(TAG, "handleTimeoutServiceForType: token=" + token + " not found."); + } + } + /** * Resume the activity. * @param r Target activity record. @@ -8548,6 +8590,9 @@ public final class ActivityThread extends ClientTransactionHandler 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/ComponentCaller.java b/core/java/android/app/ComponentCaller.java index a440dbc48db6..44e8a0a3a20c 100644 --- a/core/java/android/app/ComponentCaller.java +++ b/core/java/android/app/ComponentCaller.java @@ -42,6 +42,9 @@ public final class ComponentCaller { private final IBinder mActivityToken; private final IBinder mCallerToken; + /** + * @hide + */ public ComponentCaller(@NonNull IBinder activityToken, @Nullable IBinder callerToken) { mActivityToken = activityToken; mCallerToken = callerToken; diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index ceeaf5dea7d3..cc0aafde1f5c 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -942,6 +942,8 @@ interface IActivityManager { /** Returns if the service is a short-service is still "alive" and past the timeout. */ boolean shouldServiceTimeOut(in ComponentName className, in IBinder token); + /** Returns if the service has a time-limit restricted type and is past the time limit. */ + boolean hasServiceTimeLimitExceeded(in ComponentName className, in IBinder token); void registerUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)") diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index 59e0e9962fac..a04620cafd75 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -178,5 +178,6 @@ oneway interface IApplicationThread { in TranslationSpec targetSpec, in List<AutofillId> viewIds, in UiTranslationSpec uiTranslationSpec); void scheduleTimeoutService(IBinder token, int startId); + void scheduleTimeoutServiceForType(IBinder token, int startId, int fgsType); void schedulePing(in RemoteCallback pong); } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index aa9de814b4c5..d6e8ae3e5dff 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -3020,6 +3020,43 @@ public class Notification implements Parcelable } /** + * @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 @@ public class Notification implements Parcelable */ @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/Service.java b/core/java/android/app/Service.java index a1554572c7df..d4702998ce3e 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -20,6 +20,7 @@ import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static android.text.TextUtils.formatSimple; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1161,4 +1162,37 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac */ public void onTimeout(int startId) { } + + /** @hide */ + public final void callOnTimeLimitExceeded(int startId, int fgsType) { + // Note, because all the service callbacks (and other similar callbacks, e.g. activity + // callbacks) are delivered using the main handler, it's possible the service is already + // stopped when before this method is called, so we do a double check here. + if (mToken == null) { + Log.w(TAG, "Service already destroyed, skipping onTimeLimitExceeded()"); + return; + } + try { + if (!mActivityManager.hasServiceTimeLimitExceeded( + new ComponentName(this, mClassName), mToken)) { + Log.w(TAG, "Service no longer relevant, skipping onTimeLimitExceeded()"); + return; + } + } catch (RemoteException ex) { + } + if (Flags.introduceNewServiceOntimeoutCallback()) { + onTimeout(startId, fgsType); + } + } + + /** + * Callback called when a particular foreground service type has timed out. + * + * @param startId the startId passed to {@link #onStartCommand(Intent, int, int)} when + * the service started. + * @param fgsType the foreground service type which caused the timeout. + */ + @FlaggedApi(Flags.FLAG_INTRODUCE_NEW_SERVICE_ONTIMEOUT_CALLBACK) + public void onTimeout(int startId, int fgsType) { + } } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index ba9c8952892b..9d35dc3627c3 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -186,6 +186,7 @@ import android.os.IncidentManager; 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 @@ public final class SystemServiceRegistry { 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/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig index c0b299b77806..ff23f09f78a5 100644 --- a/core/java/android/app/activity_manager.aconfig +++ b/core/java/android/app/activity_manager.aconfig @@ -27,3 +27,10 @@ flag { description: "API to add OnUidImportanceListener with targetted UIDs" bug: "286258140" } + +flag { + namespace: "backstage_power" + name: "introduce_new_service_ontimeout_callback" + description: "Add a new callback in Service to indicate a FGS has reached its timeout." + bug: "317799821" +} diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java index d628b7f92c6c..0c1a28a9eee1 100644 --- a/core/java/android/app/prediction/AppPredictor.java +++ b/core/java/android/app/prediction/AppPredictor.java @@ -16,6 +16,7 @@ package android.app.prediction; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -24,9 +25,12 @@ import android.app.prediction.IPredictionCallback.Stub; import android.content.Context; import android.content.pm.ParceledListSlice; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; +import android.os.IRemoteCallback; import android.os.RemoteException; import android.os.ServiceManager; +import android.service.appprediction.flags.Flags; import android.util.ArrayMap; import android.util.Log; @@ -263,6 +267,34 @@ public final class AppPredictor { } /** + * Requests a Bundle which includes service features info or {@code null} if the service is not + * available. + * + * @param callbackExecutor The callback executor to use when calling the callback. It cannot be + * null. + * @param callback The callback to return the Bundle which includes service features info. It + * cannot be null. + * + * @throws IllegalStateException If this AppPredictor has already been destroyed. + * @throws RuntimeException If there is a failure communicating with the remote service. + */ + @FlaggedApi(Flags.FLAG_SERVICE_FEATURES_API) + public void requestServiceFeatures(@NonNull Executor callbackExecutor, + @NonNull Consumer<Bundle> callback) { + if (mIsClosed.get()) { + throw new IllegalStateException("This client has already been destroyed."); + } + + try { + mPredictionManager.requestServiceFeatures(mSessionId, + new RemoteCallbackWrapper(callbackExecutor, callback)); + } catch (RemoteException e) { + Log.e(TAG, "Failed to request service feature info", e); + e.rethrowAsRuntimeException(); + } + } + + /** * Destroys the client and unregisters the callback. Any method on this class after this call * with throw {@link IllegalStateException}. */ @@ -347,6 +379,28 @@ public final class AppPredictor { } } + static class RemoteCallbackWrapper extends IRemoteCallback.Stub { + + private final Consumer<Bundle> mCallback; + private final Executor mExecutor; + + RemoteCallbackWrapper(@NonNull Executor callbackExecutor, + @NonNull Consumer<Bundle> callback) { + mExecutor = callbackExecutor; + mCallback = callback; + } + + @Override + public void sendResult(Bundle result) { + final long identity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.accept(result)); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + private static class Token { static final IBinder sBinder = new Binder(TAG); } diff --git a/core/java/android/app/prediction/IPredictionManager.aidl b/core/java/android/app/prediction/IPredictionManager.aidl index 863fc6f952dd..94b4f5b2f30e 100644 --- a/core/java/android/app/prediction/IPredictionManager.aidl +++ b/core/java/android/app/prediction/IPredictionManager.aidl @@ -22,6 +22,7 @@ import android.app.prediction.AppPredictionContext; import android.app.prediction.AppPredictionSessionId; import android.app.prediction.IPredictionCallback; import android.content.pm.ParceledListSlice; +import android.os.IRemoteCallback; /** * @hide @@ -48,4 +49,6 @@ interface IPredictionManager { void requestPredictionUpdate(in AppPredictionSessionId sessionId); void onDestroyPredictionSession(in AppPredictionSessionId sessionId); + + void requestServiceFeatures(in AppPredictionSessionId sessionId, in IRemoteCallback callback); } diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java index 67759f4aa76d..eb357fe09a31 100644 --- a/core/java/android/content/ClipData.java +++ b/core/java/android/content/ClipData.java @@ -21,7 +21,13 @@ import static android.content.ContentResolver.SCHEME_ANDROID_RESOURCE; import static android.content.ContentResolver.SCHEME_CONTENT; import static android.content.ContentResolver.SCHEME_FILE; +import static com.android.window.flags.Flags.FLAG_DELEGATE_UNHANDLED_DRAGS; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.app.PendingIntent; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo; import android.content.res.AssetFileDescriptor; @@ -207,6 +213,7 @@ public class ClipData implements Parcelable { final CharSequence mText; final String mHtmlText; final Intent mIntent; + final PendingIntent mPendingIntent; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) Uri mUri; private TextLinks mTextLinks; @@ -214,12 +221,91 @@ public class ClipData implements Parcelable { // if the data is obtained from {@link #copyForTransferWithActivityInfo} private ActivityInfo mActivityInfo; + /** + * A builder for a ClipData Item. + */ + @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS) + @SuppressLint("PackageLayering") + public static final class Builder { + private CharSequence mText; + private String mHtmlText; + private Intent mIntent; + private PendingIntent mPendingIntent; + private Uri mUri; + + /** + * Sets the text for the item to be constructed. + */ + @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS) + @NonNull + public Builder setText(@Nullable CharSequence text) { + mText = text; + return this; + } + + /** + * Sets the HTML text for the item to be constructed. + */ + @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS) + @NonNull + public Builder setHtmlText(@Nullable String htmlText) { + mHtmlText = htmlText; + return this; + } + + /** + * Sets the Intent for the item to be constructed. + */ + @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS) + @NonNull + public Builder setIntent(@Nullable Intent intent) { + mIntent = intent; + return this; + } + + /** + * Sets the PendingIntent for the item to be constructed. To prevent receiving apps from + * improperly manipulating the intent to launch another activity as this caller, the + * provided PendingIntent must be immutable (see {@link PendingIntent#FLAG_IMMUTABLE}). + * The system will clean up the PendingIntent when it is no longer used. + */ + @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS) + @NonNull + public Builder setPendingIntent(@Nullable PendingIntent pendingIntent) { + if (pendingIntent != null && !pendingIntent.isImmutable()) { + throw new IllegalArgumentException("Expected pending intent to be immutable"); + } + mPendingIntent = pendingIntent; + return this; + } + + /** + * Sets the URI for the item to be constructed. + */ + @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS) + @NonNull + public Builder setUri(@Nullable Uri uri) { + mUri = uri; + return this; + } + + /** + * Constructs a new Item with the properties set on this builder. + */ + @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS) + @NonNull + public Item build() { + return new Item(mText, mHtmlText, mIntent, mPendingIntent, mUri); + } + } + /** @hide */ public Item(Item other) { mText = other.mText; mHtmlText = other.mHtmlText; mIntent = other.mIntent; + mPendingIntent = other.mPendingIntent; mUri = other.mUri; mActivityInfo = other.mActivityInfo; mTextLinks = other.mTextLinks; @@ -229,10 +315,7 @@ public class ClipData implements Parcelable { * Create an Item consisting of a single block of (possibly styled) text. */ public Item(CharSequence text) { - mText = text; - mHtmlText = null; - mIntent = null; - mUri = null; + this(text, null, null, null, null); } /** @@ -245,30 +328,21 @@ public class ClipData implements Parcelable { * </p> */ public Item(CharSequence text, String htmlText) { - mText = text; - mHtmlText = htmlText; - mIntent = null; - mUri = null; + this(text, htmlText, null, null, null); } /** * Create an Item consisting of an arbitrary Intent. */ public Item(Intent intent) { - mText = null; - mHtmlText = null; - mIntent = intent; - mUri = null; + this(null, null, intent, null, null); } /** * Create an Item consisting of an arbitrary URI. */ public Item(Uri uri) { - mText = null; - mHtmlText = null; - mIntent = null; - mUri = uri; + this(null, null, null, null, uri); } /** @@ -276,10 +350,7 @@ public class ClipData implements Parcelable { * text, Intent, and/or URI. */ public Item(CharSequence text, Intent intent, Uri uri) { - mText = text; - mHtmlText = null; - mIntent = intent; - mUri = uri; + this(text, null, intent, null, uri); } /** @@ -289,6 +360,14 @@ public class ClipData implements Parcelable { * will not be done from HTML formatted text into plain text. */ public Item(CharSequence text, String htmlText, Intent intent, Uri uri) { + this(text, htmlText, intent, null, uri); + } + + /** + * Builder ctor. + */ + private Item(CharSequence text, String htmlText, Intent intent, PendingIntent pendingIntent, + Uri uri) { if (htmlText != null && text == null) { throw new IllegalArgumentException( "Plain text must be supplied if HTML text is supplied"); @@ -296,6 +375,7 @@ public class ClipData implements Parcelable { mText = text; mHtmlText = htmlText; mIntent = intent; + mPendingIntent = pendingIntent; mUri = uri; } @@ -321,6 +401,15 @@ public class ClipData implements Parcelable { } /** + * Returns the pending intent in this Item. + */ + @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS) + @Nullable + public PendingIntent getPendingIntent() { + return mPendingIntent; + } + + /** * Retrieve the raw URI contained in this Item. */ public Uri getUri() { @@ -777,7 +866,7 @@ public class ClipData implements Parcelable { throw new NullPointerException("item is null"); } mIcon = null; - mItems = new ArrayList<Item>(); + mItems = new ArrayList<>(); mItems.add(item); mClipDescription.setIsStyledText(isStyledText()); } @@ -794,7 +883,7 @@ public class ClipData implements Parcelable { throw new NullPointerException("item is null"); } mIcon = null; - mItems = new ArrayList<Item>(); + mItems = new ArrayList<>(); mItems.add(item); mClipDescription.setIsStyledText(isStyledText()); } @@ -826,7 +915,7 @@ public class ClipData implements Parcelable { public ClipData(ClipData other) { mClipDescription = other.mClipDescription; mIcon = other.mIcon; - mItems = new ArrayList<Item>(other.mItems); + mItems = new ArrayList<>(other.mItems); } /** @@ -1042,6 +1131,35 @@ public class ClipData implements Parcelable { } /** + * Checks if this clip data has a pending intent that is an activity type. + * @hide + */ + public boolean hasActivityPendingIntents() { + final int size = mItems.size(); + for (int i = 0; i < size; i++) { + final Item item = mItems.get(i); + if (item.mPendingIntent != null && item.mPendingIntent.isActivity()) { + return true; + } + } + return false; + } + + /** + * Cleans up all pending intents in the ClipData. + * @hide + */ + public void cleanUpPendingIntents() { + final int size = mItems.size(); + for (int i = 0; i < size; i++) { + final Item item = mItems.get(i); + if (item.mPendingIntent != null) { + item.mPendingIntent.cancel(); + } + } + } + + /** * Prepare this {@link ClipData} to leave an app process. * * @hide @@ -1243,6 +1361,7 @@ public class ClipData implements Parcelable { TextUtils.writeToParcel(item.mText, dest, flags); dest.writeString8(item.mHtmlText); dest.writeTypedObject(item.mIntent, flags); + dest.writeTypedObject(item.mPendingIntent, flags); dest.writeTypedObject(item.mUri, flags); dest.writeTypedObject(mParcelItemActivityInfos ? item.mActivityInfo : null, flags); dest.writeTypedObject(item.mTextLinks, flags); @@ -1262,10 +1381,11 @@ public class ClipData implements Parcelable { CharSequence text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); String htmlText = in.readString8(); Intent intent = in.readTypedObject(Intent.CREATOR); + PendingIntent pendingIntent = in.readTypedObject(PendingIntent.CREATOR); Uri uri = in.readTypedObject(Uri.CREATOR); ActivityInfo info = in.readTypedObject(ActivityInfo.CREATOR); TextLinks textLinks = in.readTypedObject(TextLinks.CREATOR); - Item item = new Item(text, htmlText, intent, uri); + Item item = new Item(text, htmlText, intent, pendingIntent, uri); item.setActivityInfo(info); item.setTextLinks(textLinks); mItems.add(item); @@ -1273,7 +1393,7 @@ public class ClipData implements Parcelable { } public static final @android.annotation.NonNull Parcelable.Creator<ClipData> CREATOR = - new Parcelable.Creator<ClipData>() { + new Parcelable.Creator<>() { @Override public ClipData createFromParcel(Parcel source) { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index b8d754348211..c89c73523a9c 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -6553,6 +6553,15 @@ public abstract class Context { 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. * diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 08871d4644c0..e8031a374310 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -19,6 +19,7 @@ package android.content; import static android.app.sdksandbox.SdkSandboxManager.ACTION_START_SANDBOXED_ACTIVITY; import static android.content.ContentProvider.maybeAddUserId; import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE; +import static android.service.chooser.Flags.FLAG_ENABLE_SHARESHEET_METADATA_EXTRA; import android.Manifest; import android.accessibilityservice.AccessibilityService; @@ -55,6 +56,7 @@ import android.net.Uri; 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; @@ -72,7 +74,9 @@ import android.provider.DocumentsContract; 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; @@ -1059,7 +1063,7 @@ public class Intent implements Parcelable, Cloneable { } 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. @@ -6064,6 +6068,62 @@ public class Intent implements Parcelable, Cloneable { 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}. * @@ -6156,6 +6216,17 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS"; /** + * A CharSequence of additional text describing the content being shared. This text will be + * displayed to the user as a part of the sharesheet when included in an + * {@link #ACTION_CHOOSER} {@link Intent}. + * + * <p>e.g. When sharing a photo, metadata could inform the user that location data is included + * in the photo they are sharing.</p> + */ + @FlaggedApi(FLAG_ENABLE_SHARESHEET_METADATA_EXTRA) + public static final String EXTRA_METADATA_TEXT = "android.intent.extra.METADATA_TEXT"; + + /** * A {@link IntentSender} to start after instant app installation success. * @hide */ @@ -6296,6 +6367,25 @@ public class Intent implements Parcelable, Cloneable { "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 d91358105b0b..e2907224d66e 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -677,7 +677,7 @@ public class IntentFilter implements Parcelable { * 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 @@ public class IntentFilter implements Parcelable { } // 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 @@ public class IntentFilter implements Parcelable { * * @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 fb9560889507..c6f52202834b 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 407ffbb7288f..49c8a7cad57a 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 abstract class PackageManager { 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/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java index 2b378b1f09d0..5b0cee75e591 100644 --- a/core/java/android/content/pm/ServiceInfo.java +++ b/core/java/android/content/pm/ServiceInfo.java @@ -142,6 +142,28 @@ public class ServiceInfo extends ComponentInfo * the {@link android.R.attr#foregroundServiceType} attribute. * Data(photo, file, account) upload/download, backup/restore, import/export, fetch, * transfer over network between device and cloud. + * + * <p>This type has time limit of 6 hours starting from Android version + * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}. + * A foreground service of this type must be stopped within the timeout by + * {@link android.app.Service#stopSelf()}, + * {@link android.content.Context#stopService(android.content.Intent)} or their overloads). + * {@link android.app.Service#stopForeground(int)} will also work, which will demote the + * service to a "background" service, which will soon be stopped by the system. + * + * <p>If the service isn't stopped within the timeout, + * {@link android.app.Service#onTimeout(int, int)} will be called. + * + * <p>Also note, even though + * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC} can be used on + * Android versions prior to {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, since + * {@link android.app.Service#onTimeout(int, int)} did not exist on such versions, it will + * never be called. + * + * Because of this, developers must make sure to stop the foreground service even if + * {@link android.app.Service#onTimeout(int, int)} is not called on such versions. + * + * @see android.app.Service#onTimeout(int, int) */ @RequiresPermission( value = Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC, @@ -483,6 +505,27 @@ public class ServiceInfo extends ComponentInfo * Constant corresponding to {@code mediaProcessing} in * the {@link android.R.attr#foregroundServiceType} attribute. * Media processing use cases such as video or photo editing and processing. + * + * This type has time limit of 6 hours. + * A foreground service of this type must be stopped within the timeout by + * {@link android.app.Service#stopSelf()}, + * {@link android.content.Context#stopService(android.content.Intent)} or their overloads). + * {@link android.app.Service#stopForeground(int)} will also work, which will demote the + * service to a "background" service, which will soon be stopped by the system. + * + * <p>If the service isn't stopped within the timeout, + * {@link android.app.Service#onTimeout(int, int)} will be called. + * + * <p>Also note, even though + * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING} was added in + * Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, it can be also used + * on prior android versions (just like other new foreground service types can be used). + * However, because {@link android.app.Service#onTimeout(int, int)} did not exist on prior + * versions, it will never be called on such versions. + * Because of this, developers must make sure to stop the foreground service even if + * {@link android.app.Service#onTimeout(int, int)} is not called on such versions. + * + * @see android.app.Service#onTimeout(int, int) */ @RequiresPermission( value = Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING diff --git a/core/java/android/content/pm/overlay/OverlayPaths.java b/core/java/android/content/pm/overlay/OverlayPaths.java index a4db733af013..bd74b0b9293c 100644 --- a/core/java/android/content/pm/overlay/OverlayPaths.java +++ b/core/java/android/content/pm/overlay/OverlayPaths.java @@ -49,6 +49,13 @@ public class OverlayPaths { 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/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index bdaf9d789960..d4c58b239c84 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -200,6 +200,25 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan return this; } + /** + * Optional: Sets logo description text that will be shown on the prompt. + * + * <p> Note that using this method is not recommended in most scenarios because the calling + * application's name will be used by default. Setting the logo description is intended for + * large bundled applications that perform a wide range of functions and need to show + * distinct description for each function. + * + * @param logoDescription The logo description text that will be shown on the prompt. + * @return This builder. + */ + @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) + @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO) + @NonNull + public BiometricPrompt.Builder setLogoDescription(@NonNull String logoDescription) { + mPromptInfo.setLogoDescription(logoDescription); + return this; + } + /** * Required: Sets the title that will be shown on the prompt. @@ -743,7 +762,20 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan return mPromptInfo.getLogoBitmap(); } - + /** + * Gets the logo description for the prompt, as set by + * {@link Builder#setDescription(CharSequence)}. + * Currently for system applications use only. + * + * @return The logo description of the prompt, or null if the prompt has no logo description + * set. + */ + @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) + @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO) + @Nullable + public String getLogoDescription() { + return mPromptInfo.getLogoDescription(); + } /** * Gets the title for the prompt, as set by {@link Builder#setTitle(CharSequence)}. diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java index 0f9cadc52608..2236660ee388 100644 --- a/core/java/android/hardware/biometrics/PromptInfo.java +++ b/core/java/android/hardware/biometrics/PromptInfo.java @@ -34,6 +34,7 @@ public class PromptInfo implements Parcelable { @DrawableRes private int mLogoRes = -1; @Nullable private Bitmap mLogoBitmap; + @Nullable private String mLogoDescription; @NonNull private CharSequence mTitle; private boolean mUseDefaultTitle; @Nullable private CharSequence mSubtitle; @@ -62,6 +63,7 @@ public class PromptInfo implements Parcelable { PromptInfo(Parcel in) { mLogoRes = in.readInt(); mLogoBitmap = in.readTypedObject(Bitmap.CREATOR); + mLogoDescription = in.readString(); mTitle = in.readCharSequence(); mUseDefaultTitle = in.readBoolean(); mSubtitle = in.readCharSequence(); @@ -106,6 +108,7 @@ public class PromptInfo implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mLogoRes); dest.writeTypedObject(mLogoBitmap, 0); + dest.writeString(mLogoDescription); dest.writeCharSequence(mTitle); dest.writeBoolean(mUseDefaultTitle); dest.writeCharSequence(mSubtitle); @@ -173,6 +176,8 @@ public class PromptInfo implements Parcelable { return true; } else if (mLogoBitmap != null) { return true; + } else if (mLogoDescription != null) { + return true; } return false; } @@ -189,6 +194,10 @@ public class PromptInfo implements Parcelable { checkOnlyOneLogoSet(); } + public void setLogoDescription(@NonNull String logoDescription) { + mLogoDescription = logoDescription; + } + public void setTitle(CharSequence title) { mTitle = title; } @@ -282,6 +291,10 @@ public class PromptInfo implements Parcelable { return mLogoBitmap; } + public String getLogoDescription() { + return mLogoDescription; + } + public CharSequence getTitle() { return mTitle; } diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java index 2ba59b0d659a..5a349050a28f 100644 --- a/services/core/java/com/android/server/devicestate/DeviceState.java +++ b/core/java/android/hardware/devicestate/DeviceState.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.devicestate; +package android.hardware.devicestate; import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE; import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE; @@ -22,7 +22,6 @@ import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STA import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; -import android.hardware.devicestate.DeviceStateManager; import com.android.internal.util.Preconditions; @@ -38,9 +37,9 @@ import java.util.Objects; * state of the system. This is useful for variable-state devices, like foldable or rollable * devices, that can be configured by users into differing hardware states, which each may have a * different expected use case. + * @hide * - * @see DeviceStateProvider - * @see DeviceStateManagerService + * @see DeviceStateManager */ public final class DeviceState { /** diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java index 4a5c4c8acd25..8616b6b15de6 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 android.os.Parcelable; import com.android.internal.annotations.Immutable; -import java.lang.StringBuilder; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -580,6 +577,21 @@ public final class UsbPortStatus implements Parcelable { } /** + * 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 000000000000..cc56a311e9a4 --- /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 a6b2ed09a64d..e09094203ad4 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -1899,7 +1899,7 @@ public abstract class BatteryStats { 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 @@ public abstract class BatteryStats { 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 000000000000..cc77f5b82c90 --- /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/UserManager.java b/core/java/android/os/UserManager.java index ce0379887d36..de32423eab2e 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1427,8 +1427,8 @@ public class UserManager { public static final String DISALLOW_RECORD_AUDIO = "no_record_audio"; /** - * Specifies if a user is not allowed to run in the background and should be stopped during - * user switch. The default value is <code>false</code>. + * Specifies if a user is not allowed to run in the background and should be stopped and locked + * during user switch. The default value is <code>false</code>. * * <p>This restriction can be set by device owners and profile owners. * diff --git a/core/java/android/os/storage/OWNERS b/core/java/android/os/storage/OWNERS index 6941857e8fef..2cb16d337bba 100644 --- a/core/java/android/os/storage/OWNERS +++ b/core/java/android/os/storage/OWNERS @@ -10,7 +10,6 @@ gargshivam@google.com 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/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 8c7050176506..9d7fb7018d52 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -94,3 +94,11 @@ flag { 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 5d00b29eb3c8..4075e9009acd 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 @@ public class BlockedNumberContract { * <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 @@ public class BlockedNumberContract { * {@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 @@ public class BlockedNumberContract { * {@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 7d127ad660c5..c13dd363d79e 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -19,6 +19,7 @@ package android.provider; 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.telephony.PhoneNumberUtils; 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 @@ public class CallLog { 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 @@ public class CallLog { } /** + * @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 @@ public class CallLog { 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 @@ public class CallLog { 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 class CallLog { */ 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 @@ public class CallLog { } 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 @@ public class CallLog { 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 b026ce94b3fd..0db68b4d0b0c 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -12286,6 +12286,14 @@ public final class Settings { "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 658cec86f7d5..88bd87e6c27c 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -4956,6 +4956,26 @@ public final class Telephony { */ 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 @@ public final class Telephony { 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/appprediction/AppPredictionService.java b/core/java/android/service/appprediction/AppPredictionService.java index a2ffa5d34219..2402cfda3fe1 100644 --- a/core/java/android/service/appprediction/AppPredictionService.java +++ b/core/java/android/service/appprediction/AppPredictionService.java @@ -18,6 +18,7 @@ package android.service.appprediction; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.annotation.CallSuper; +import android.annotation.FlaggedApi; import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; @@ -31,12 +32,15 @@ import android.app.prediction.AppTargetId; import android.app.prediction.IPredictionCallback; import android.content.Intent; import android.content.pm.ParceledListSlice; +import android.os.Bundle; import android.os.CancellationSignal; import android.os.Handler; import android.os.IBinder; +import android.os.IRemoteCallback; import android.os.Looper; import android.os.RemoteException; import android.service.appprediction.IPredictionService.Stub; +import android.service.appprediction.flags.Flags; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; @@ -134,6 +138,16 @@ public abstract class AppPredictionService extends Service { obtainMessage(AppPredictionService::doDestroyPredictionSession, AppPredictionService.this, sessionId)); } + + @FlaggedApi(Flags.FLAG_SERVICE_FEATURES_API) + @Override + public void requestServiceFeatures(AppPredictionSessionId sessionId, + IRemoteCallback callback) { + mHandler.sendMessage( + obtainMessage(AppPredictionService::onRequestServiceFeatures, + AppPredictionService.this, sessionId, + new RemoteCallbackWrapper(callback, null))); + } }; @CallSuper @@ -277,6 +291,18 @@ public abstract class AppPredictionService extends Service { public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) {} /** + * Called by the client app to request {@link AppPredictionService} features info. + * + * @param sessionId the session's Id. It is @NonNull. + * @param callback the callback to return the Bundle which includes service features info. It + * is @NonNull. + */ + @FlaggedApi(Flags.FLAG_SERVICE_FEATURES_API) + @MainThread + public void onRequestServiceFeatures(@NonNull AppPredictionSessionId sessionId, + @NonNull Consumer<Bundle> callback) {} + + /** * Used by the prediction factory to send back results the client app. The can be called * in response to {@link #onRequestPredictionUpdate(AppPredictionSessionId)} or proactively as * a result of changes in predictions. @@ -357,4 +383,50 @@ public abstract class AppPredictionService extends Service { } } } + + private static final class RemoteCallbackWrapper implements Consumer<Bundle>, + IBinder.DeathRecipient { + + private IRemoteCallback mCallback; + private final Consumer<RemoteCallbackWrapper> mOnBinderDied; + + RemoteCallbackWrapper(IRemoteCallback callback, + @Nullable Consumer<RemoteCallbackWrapper> onBinderDied) { + mCallback = callback; + mOnBinderDied = onBinderDied; + if (mOnBinderDied != null) { + try { + mCallback.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to link to death: " + e); + } + } + } + + public void destroy() { + if (mCallback != null && mOnBinderDied != null) { + mCallback.asBinder().unlinkToDeath(this, 0); + } + } + + @Override + public void accept(Bundle bundle) { + try { + if (mCallback != null) { + mCallback.sendResult(bundle); + } + } catch (RemoteException e) { + Slog.e(TAG, "Error sending result:" + e); + } + } + + @Override + public void binderDied() { + destroy(); + mCallback = null; + if (mOnBinderDied != null) { + mOnBinderDied.accept(this); + } + } + } } diff --git a/core/java/android/service/appprediction/IPredictionService.aidl b/core/java/android/service/appprediction/IPredictionService.aidl index 0f3df8561743..e144dfa4b22c 100644 --- a/core/java/android/service/appprediction/IPredictionService.aidl +++ b/core/java/android/service/appprediction/IPredictionService.aidl @@ -22,6 +22,7 @@ import android.app.prediction.AppPredictionContext; import android.app.prediction.AppPredictionSessionId; import android.app.prediction.IPredictionCallback; import android.content.pm.ParceledListSlice; +import android.os.IRemoteCallback; /** * Interface from the system to a prediction service. @@ -50,4 +51,6 @@ oneway interface IPredictionService { void requestPredictionUpdate(in AppPredictionSessionId sessionId); void onDestroyPredictionSession(in AppPredictionSessionId sessionId); + + void requestServiceFeatures(in AppPredictionSessionId sessionId, in IRemoteCallback callback); } diff --git a/core/java/android/service/appprediction/flags/flags.aconfig b/core/java/android/service/appprediction/flags/flags.aconfig new file mode 100644 index 000000000000..c7e47d4b3627 --- /dev/null +++ b/core/java/android/service/appprediction/flags/flags.aconfig @@ -0,0 +1,8 @@ +package: "android.service.appprediction.flags" + +flag { + name: "service_features_api" + namespace: "systemui" + description: "Guards the new requestServiceFeatures api" + bug: "292565550" +}
\ No newline at end of file diff --git a/core/java/android/service/chooser/AdditionalContentContract.java b/core/java/android/service/chooser/AdditionalContentContract.java new file mode 100644 index 000000000000..f679e8a6ff6b --- /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 000000000000..4603be114508 --- /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/chooser/flags.aconfig b/core/java/android/service/chooser/flags.aconfig index 3cc7f5a6ee04..add575b23792 100644 --- a/core/java/android/service/chooser/flags.aconfig +++ b/core/java/android/service/chooser/flags.aconfig @@ -8,6 +8,13 @@ flag { } flag { + name: "enable_sharesheet_metadata_extra" + namespace: "intentresolver" + description: "This flag enables sharesheet metadata to be displayed to users." + bug: "318942069" +} + +flag { name: "support_nfc_resolver" namespace: "systemui" description: "This flag controls the new NFC 'resolver' activity" @@ -20,3 +27,10 @@ flag { description: "This flag controls content toggling in Chooser" bug: "302691505" } + +flag { + name: "enable_chooser_result" + namespace: "intentresolver" + description: "Provides additional callbacks with information about user actions in ChooserResult" + bug: "263474465" +} diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java index 6410609a589d..2028c4057c01 100644 --- a/core/java/android/text/BoringLayout.java +++ b/core/java/android/text/BoringLayout.java @@ -711,18 +711,21 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback 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 8ddb42d6b3be..e5d199ad8e46 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -464,11 +464,12 @@ public abstract class Layout { @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 @@ public abstract class Layout { 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 f37c4c2a060c..f68fcab94952 100644 --- a/core/java/android/text/flags/flags.aconfig +++ b/core/java/android/text/flags/flags.aconfig @@ -110,3 +110,12 @@ flag { 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 2ebe2e9e2761..f67844de44ea 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/tracing/perfetto/TracingContext.java b/core/java/android/tracing/perfetto/TracingContext.java index 0bce26e007a1..c1a61a7c2c34 100644 --- a/core/java/android/tracing/perfetto/TracingContext.java +++ b/core/java/android/tracing/perfetto/TracingContext.java @@ -105,6 +105,5 @@ public class TracingContext<DataSourceInstanceType extends DataSourceInstance, return res; } - // private static native void nativeFlush(long nativeDataSourcePointer); private static native void nativeFlush(TracingContext thiz, long ctxPointer); } diff --git a/core/java/android/util/Xml.java b/core/java/android/util/Xml.java index ec6e90b4153e..50d419f75a09 100644 --- a/core/java/android/util/Xml.java +++ b/core/java/android/util/Xml.java @@ -53,9 +53,12 @@ import java.io.UnsupportedEncodingException; 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 @@ public class Xml { * * @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 class Xml { 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 class Xml { */ 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 class Xml { */ 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 @@ public class Xml { @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 @@ public class Xml { * @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 @@ public class Xml { * * @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 @@ public class Xml { 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 @@ public class Xml { * @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 @@ public class Xml { * * @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 @@ public class Xml { * 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 @@ public class Xml { ? (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/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java index 0ce1d4711c6d..eb289204e481 100644 --- a/core/java/android/view/HandwritingInitiator.java +++ b/core/java/android/view/HandwritingInitiator.java @@ -23,8 +23,10 @@ import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; +import android.view.inputmethod.Flags; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; +import android.widget.Editor; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; @@ -80,6 +82,16 @@ public class HandwritingInitiator { * connections and only set mConnectedView to null when mConnectionCount is zero. */ private int mConnectionCount = 0; + + /** + * The reference to the View that currently has focus. + * This replaces mConnecteView when {@code Flags#intitiationWithoutInputConnection()} is + * enabled. + */ + @Nullable + @VisibleForTesting + public WeakReference<View> mFocusedView = null; + private final InputMethodManager mImm; private final int[] mTempLocation = new int[2]; @@ -112,9 +124,15 @@ public class HandwritingInitiator { * * If the stylus is hovering on an unconnected editor that supports handwriting, we always show * the hover icon. + * TODO(b/308827131): Rename to FocusedView after Flag is flipped. */ private boolean mShowHoverIconForConnectedView = true; + /** When flag is enabled, touched editors don't wait for InputConnection for initiation. + * However, delegation still waits for InputConnection. + */ + private final boolean mInitiateWithoutConnection = Flags.initiationWithoutInputConnection(); + @VisibleForTesting public HandwritingInitiator(@NonNull ViewConfiguration viewConfiguration, @NonNull InputMethodManager inputMethodManager) { @@ -201,8 +219,8 @@ public class HandwritingInitiator { View candidateView = findBestCandidateView(mState.mStylusDownX, mState.mStylusDownY, /* isHover */ false); if (candidateView != null) { - if (candidateView == getConnectedView()) { - if (!candidateView.hasFocus()) { + if (candidateView == getConnectedOrFocusedView()) { + if (!mInitiateWithoutConnection && !candidateView.hasFocus()) { requestFocusWithoutReveal(candidateView); } startHandwriting(candidateView); @@ -217,8 +235,17 @@ public class HandwritingInitiator { candidateView.getHandwritingDelegatorCallback().run(); mState.mHasPreparedHandwritingDelegation = true; } else { - mState.mPendingConnectedView = new WeakReference<>(candidateView); - requestFocusWithoutReveal(candidateView); + if (!mInitiateWithoutConnection) { + mState.mPendingConnectedView = new WeakReference<>(candidateView); + } + if (!candidateView.hasFocus()) { + requestFocusWithoutReveal(candidateView); + } + if (mInitiateWithoutConnection + && updateFocusedView(candidateView, + /* fromTouchEvent */ true)) { + startHandwriting(candidateView); + } } } } @@ -244,11 +271,7 @@ public class HandwritingInitiator { */ public void onDelegateViewFocused(@NonNull View view) { if (view == getConnectedView()) { - if (tryAcceptStylusHandwritingDelegation(view)) { - // A handwriting delegate view is accepted and handwriting starts; hide the - // hover icon. - mShowHoverIconForConnectedView = false; - } + tryAcceptStylusHandwritingDelegation(view); } } @@ -260,6 +283,10 @@ public class HandwritingInitiator { * @see #onInputConnectionClosed(View) */ public void onInputConnectionCreated(@NonNull View view) { + if (mInitiateWithoutConnection && !view.isHandwritingDelegate()) { + // When flag is enabled, only delegation continues to wait for InputConnection. + return; + } if (!view.isAutoHandwritingEnabled()) { clearConnectedView(); return; @@ -274,12 +301,15 @@ public class HandwritingInitiator { // A new view just gain focus. By default, we should show hover icon for it. mShowHoverIconForConnectedView = true; if (view.isHandwritingDelegate() && tryAcceptStylusHandwritingDelegation(view)) { - // A handwriting delegate view is accepted and handwriting starts; hide the - // hover icon. + // tryAcceptStylusHandwritingDelegation should set boolean below, however, we + // cannot mock IMM to return true for acceptStylusDelegation(). + // TODO(b/324670412): we should move any dependent tests to integration and remove + // the assignment below. mShowHoverIconForConnectedView = false; return; } - if (mState != null && mState.mPendingConnectedView != null + if (!mInitiateWithoutConnection && mState != null + && mState.mPendingConnectedView != null && mState.mPendingConnectedView.get() == view) { startHandwriting(view); } @@ -293,6 +323,9 @@ public class HandwritingInitiator { * @param view the view that closed the InputConnection. */ public void onInputConnectionClosed(@NonNull View view) { + if (mInitiateWithoutConnection && !view.isHandwritingDelegate()) { + return; + } final View connectedView = getConnectedView(); if (connectedView == null) return; if (connectedView == view) { @@ -306,6 +339,48 @@ public class HandwritingInitiator { } } + @Nullable + private View getFocusedView() { + if (mFocusedView == null) return null; + return mFocusedView.get(); + } + + /** + * Clear the tracked focused view tracked for handwriting initiation. + * @param view the focused view. + */ + public void clearFocusedView(View view) { + if (view == null || mFocusedView == null) { + return; + } + if (mFocusedView.get() == view) { + mFocusedView = null; + } + } + + /** + * Called when new {@link Editor} is focused. + * @return {@code true} if handwriting can initiate for given view. + */ + @VisibleForTesting + public boolean updateFocusedView(@NonNull View view, boolean fromTouchEvent) { + if (!view.shouldInitiateHandwriting()) { + mFocusedView = null; + return false; + } + + final View focusedView = getFocusedView(); + if (focusedView != view) { + mFocusedView = new WeakReference<>(view); + // A new view just gain focus. By default, we should show hover icon for it. + mShowHoverIconForConnectedView = true; + } + if (!fromTouchEvent) { + tryAcceptStylusHandwritingDelegation(view); + } + return true; + } + /** Starts a stylus handwriting session for the view. */ @VisibleForTesting public void startHandwriting(@NonNull View view) { @@ -324,6 +399,9 @@ public class HandwritingInitiator { */ @VisibleForTesting public boolean tryAcceptStylusHandwritingDelegation(@NonNull View view) { + if (!view.isHandwritingDelegate() || (mState != null && mState.mHasInitiatedHandwriting)) { + return false; + } String delegatorPackageName = view.getAllowedHandwritingDelegatorPackageName(); if (delegatorPackageName == null) { @@ -337,6 +415,9 @@ public class HandwritingInitiator { if (view instanceof TextView) { ((TextView) view).hideHint(); } + // A handwriting delegate view is accepted and handwriting starts; hide the + // hover icon. + mShowHoverIconForConnectedView = false; return true; } return false; @@ -377,16 +458,25 @@ public class HandwritingInitiator { return PointerIcon.getSystemIcon(context, PointerIcon.TYPE_HANDWRITING); } - if (hoverView != getConnectedView()) { + if (hoverView != getConnectedOrFocusedView()) { // The stylus is hovering on another view that supports handwriting. We should show - // hover icon. Also reset the mShowHoverIconForConnectedView so that hover - // icon is displayed again next time when the stylus hovers on connected view. + // hover icon. Also reset the mShowHoverIconForFocusedView so that hover + // icon is displayed again next time when the stylus hovers on focused view. mShowHoverIconForConnectedView = true; return PointerIcon.getSystemIcon(context, PointerIcon.TYPE_HANDWRITING); } return null; } + // TODO(b/308827131): Remove once Flag is flipped. + private View getConnectedOrFocusedView() { + if (mInitiateWithoutConnection) { + return mFocusedView == null ? null : mFocusedView.get(); + } else { + return mConnectedView == null ? null : mConnectedView.get(); + } + } + private View getCachedHoverTarget() { if (mCachedHoverTarget == null) { return null; @@ -458,20 +548,21 @@ public class HandwritingInitiator { */ @Nullable private View findBestCandidateView(float x, float y, boolean isHover) { + // TODO(b/308827131): Rename to FocusedView after Flag is flipped. // If the connectedView is not null and do not set any handwriting area, it will check // whether the connectedView's boundary contains the initial stylus position. If true, // directly return the connectedView. - final View connectedView = getConnectedView(); - if (connectedView != null) { + final View connectedOrFocusedView = getConnectedOrFocusedView(); + if (connectedOrFocusedView != null) { Rect handwritingArea = mTempRect; - if (getViewHandwritingArea(connectedView, handwritingArea) - && isInHandwritingArea(handwritingArea, x, y, connectedView, isHover) - && shouldTriggerStylusHandwritingForView(connectedView)) { + if (getViewHandwritingArea(connectedOrFocusedView, handwritingArea) + && isInHandwritingArea(handwritingArea, x, y, connectedOrFocusedView, isHover) + && shouldTriggerStylusHandwritingForView(connectedOrFocusedView)) { if (!isHover && mState != null) { mState.mStylusDownWithinEditorBounds = contains(handwritingArea, x, y, 0f, 0f, 0f, 0f); } - return connectedView; + return connectedOrFocusedView; } } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index b5b81d17013a..29cc8594deec 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -73,6 +73,7 @@ import android.window.IScreenRecordingCallback; import android.window.ISurfaceSyncGroupCompletedListener; import android.window.ITaskFpsCallback; import android.window.ITrustedPresentationListener; +import android.window.IUnhandledDragListener; import android.window.InputTransferToken; import android.window.ScreenCapture; import android.window.TrustedPresentationThresholds; @@ -1091,4 +1092,10 @@ interface IWindowManager @EnforcePermission("DETECT_SCREEN_RECORDING") void unregisterScreenRecordingCallback(IScreenRecordingCallback callback); + + /** + * Sets the listener to be called back when a cross-window drag and drop operation is unhandled + * (ie. not handled by any window which can handle the drag). + */ + void setUnhandledDragListener(IUnhandledDragListener listener); } diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java index 83bdb087a539..fe98faba98c3 100644 --- a/core/java/android/view/InsetsFrameProvider.java +++ b/core/java/android/view/InsetsFrameProvider.java @@ -18,6 +18,7 @@ package android.view; 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 @@ public class InsetsFrameProvider implements Parcelable { 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 @@ public class InsetsFrameProvider implements Parcelable { 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 @@ public class InsetsFrameProvider implements Parcelable { sb.append(", mMinimalInsetsSizeInDisplayCutoutSafe=") .append(mMinimalInsetsSizeInDisplayCutoutSafe); } + if (mBoundingRects != null) { + sb.append(", mBoundingRects=").append(Arrays.toString(mBoundingRects)); + } sb.append("}"); return sb.toString(); } @@ -257,6 +283,7 @@ public class InsetsFrameProvider implements Parcelable { 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 @@ public class InsetsFrameProvider implements Parcelable { 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 @@ public class InsetsFrameProvider implements Parcelable { && 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 bc33d5e2f6b1..f9eba2948913 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -38,6 +38,8 @@ import android.view.WindowInsets.Type.InsetsType; 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 class InsetsSource implements Parcelable { }) 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 @@ public class InsetsSource implements Parcelable { /** 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 @@ public class InsetsSource implements Parcelable { 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 @@ public class InsetsSource implements Parcelable { : null; mFlags = other.mFlags; mSideHint = other.mSideHint; + mBoundingRects = other.mBoundingRects != null + ? other.mBoundingRects.clone() + : null; } public void set(InsetsSource other) { @@ -155,6 +168,9 @@ public class InsetsSource implements Parcelable { : 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 @@ public class InsetsSource implements Parcelable { 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 @@ public class InsetsSource implements Parcelable { } /** + * 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 @@ public class InsetsSource implements Parcelable { } /** + * 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 @@ public class InsetsSource implements Parcelable { 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 @@ public class InsetsSource implements Parcelable { 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 @@ public class InsetsSource implements Parcelable { mVisible = in.readBoolean(); mFlags = in.readInt(); mSideHint = in.readInt(); + mBoundingRects = in.createTypedArray(Rect.CREATOR); } @Override @@ -533,6 +645,7 @@ public class InsetsSource implements Parcelable { dest.writeBoolean(mVisible); dest.writeInt(mFlags); dest.writeInt(mSideHint); + dest.writeTypedArray(mBoundingRects, flags); } @Override @@ -543,6 +656,7 @@ public class InsetsSource implements Parcelable { + " 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 c88da9e2eb4f..21eec67bfe10 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -128,6 +128,8 @@ public class InsetsState implements Parcelable { 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 @@ public class InsetsState implements Parcelable { } 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 @@ public class InsetsState implements Parcelable { } 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 @@ public class InsetsState implements Parcelable { 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 @@ public class InsetsState implements Parcelable { 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 @@ public class InsetsState implements Parcelable { // 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 @@ public class InsetsState implements Parcelable { 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 c22986b3473b..dc0b1a77de80 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -43,6 +43,7 @@ import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFI import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS; import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP; import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION; +import static com.android.window.flags.Flags.FLAG_DELEGATE_UNHANDLED_DRAGS; import static java.lang.Math.max; @@ -68,6 +69,7 @@ import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.UiContext; import android.annotation.UiThread; +import android.app.PendingIntent; import android.compat.annotation.UnsupportedAppUsage; import android.content.AutofillOptions; import android.content.ClipData; @@ -5329,6 +5331,34 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public static final int DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION = 1 << 11; /** + * Flag indicating that a drag can cross window boundaries (within the same application). When + * {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int)} is called + * with this flag set, only visible windows belonging to the same application (ie. share the + * same UID) with targetSdkVersion >= {@link android.os.Build.VERSION_CODES#N API 24} will be + * able to participate in the drag operation and receive the dragged content. + * + * If both DRAG_FLAG_GLOBAL_SAME_APPLICATION and DRAG_FLAG_GLOBAL are set, then + * DRAG_FLAG_GLOBAL_SAME_APPLICATION takes precedence and the drag will only go to visible + * windows from the same application. + */ + @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS) + public static final int DRAG_FLAG_GLOBAL_SAME_APPLICATION = 1 << 12; + + /** + * Flag indicating that an unhandled drag should be delegated to the system to be started if no + * visible window wishes to handle the drop. When using this flag, the caller must provide + * ClipData with an Item that contains an immutable PendingIntent to an activity to be launched + * (not a broadcast, service, etc). See + * {@link ClipData.Item.Builder#setPendingIntent(PendingIntent)}. + * + * The system can decide to launch the intent or not based on factors like the current screen + * size or windowing mode. If the system does not launch the intent, it will be canceled via the + * normal drag and drop flow. + */ + @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS) + public static final int DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG = 1 << 13; + + /** * Vertical scroll factor cached by {@link #getVerticalScrollFactor}. */ private float mVerticalScrollFactor; @@ -6364,6 +6394,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, case R.styleable.View_handwritingBoundsOffsetBottom: mHandwritingBoundsOffsetBottom = a.getDimension(attr, 0); break; + case R.styleable.View_contentSensitivity: + setContentSensitivity(a.getInt(attr, CONTENT_SENSITIVITY_AUTO)); + break; } } @@ -22269,6 +22302,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 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; @@ -28496,9 +28532,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Log.w(VIEW_LOG_TAG, "startDragAndDrop called with an invalid surface."); return false; } + if ((flags & DRAG_FLAG_GLOBAL) != 0 && ((flags & DRAG_FLAG_GLOBAL_SAME_APPLICATION) != 0)) { + Log.w(VIEW_LOG_TAG, "startDragAndDrop called with both DRAG_FLAG_GLOBAL " + + "and DRAG_FLAG_GLOBAL_SAME_APPLICATION, the drag will default to " + + "DRAG_FLAG_GLOBAL_SAME_APPLICATION"); + flags &= ~DRAG_FLAG_GLOBAL; + } if (data != null) { - data.prepareToLeaveProcess((flags & View.DRAG_FLAG_GLOBAL) != 0); + if (com.android.window.flags.Flags.delegateUnhandledDrags()) { + data.prepareToLeaveProcess( + (flags & (DRAG_FLAG_GLOBAL_SAME_APPLICATION | DRAG_FLAG_GLOBAL)) != 0); + if ((flags & DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG) != 0) { + if (!data.hasActivityPendingIntents()) { + // Reset the flag if there is no launchable activity intent + flags &= ~DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG; + Log.w(VIEW_LOG_TAG, "startDragAndDrop called with " + + "DRAG_FLAG_START_INTENT_ON_UNHANDLED_DRAG but the clip data " + + "contains non-activity PendingIntents"); + } + } + } else { + data.prepareToLeaveProcess((flags & DRAG_FLAG_GLOBAL) != 0); + } } Rect bounds = new Rect(); @@ -28524,6 +28580,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (token != null) { root.setLocalDragState(myLocalState); mAttachInfo.mDragToken = token; + mAttachInfo.mDragData = data; mAttachInfo.mViewRootImpl.setDragStartedViewForAccessibility(this); setAccessibilityDragStarted(true); } @@ -28601,8 +28658,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mAttachInfo.mDragSurface != null) { mAttachInfo.mDragSurface.release(); } + if (mAttachInfo.mDragData != null) { + mAttachInfo.mDragData.cleanUpPendingIntents(); + } mAttachInfo.mDragSurface = surface; mAttachInfo.mDragToken = token; + mAttachInfo.mDragData = data; // Cache the local state object for delivery with DragEvents root.setLocalDragState(myLocalState); if (a11yEnabled) { @@ -31516,11 +31577,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, IBinder mDragToken; /** + * Used to track the data of the current drag operation for cleanup later. + */ + ClipData mDragData; + + /** * The drag shadow surface for the current drag operation. */ public Surface mDragSurface; - /** * The view that currently has a tooltip displayed. */ diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 07c97950e9bb..28a73344b731 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -8599,6 +8599,10 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mDragSurface.release(); mAttachInfo.mDragSurface = null; } + if (mAttachInfo.mDragData != null) { + mAttachInfo.mDragData.cleanUpPendingIntents(); + mAttachInfo.mDragData = null; + } } } } diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 921afaa99277..fbebe1e726b7 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.ime; 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.compat.annotation.UnsupportedAppUsage; 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 com.android.internal.util.Preconditions; 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 @@ public final class WindowInsets { 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 @@ public final class WindowInsets { @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 @@ public final class WindowInsets { 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 @@ public final class WindowInsets { 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 @@ public final class WindowInsets { 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 @@ public final class WindowInsets { 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 @@ public final class WindowInsets { @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 @@ public final class WindowInsets { } /** + * 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 @@ public final class WindowInsets { 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 @@ public final class WindowInsets { (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 @@ public final class WindowInsets { 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 @@ public final class WindowInsets { 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 @@ public final class WindowInsets { } /** + * 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 @@ public final class WindowInsets { ? 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 @@ public final class WindowInsets { && 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 @@ public final class WindowInsets { 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 @@ public final class WindowInsets { 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 @@ public final class WindowInsets { 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 @@ public final class WindowInsets { 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 @@ public final class WindowInsets { mTypeInsetsMap = new Insets[SIZE]; mTypeMaxInsetsMap = new Insets[SIZE]; mTypeVisibilityMap = new boolean[SIZE]; + mTypeBoundingRectsMap = new Rect[SIZE][]; + mTypeMaxBoundingRectsMap = new Rect[SIZE][]; } /** @@ -1165,6 +1411,10 @@ public final class WindowInsets { 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 @@ public final class WindowInsets { } /** + * 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 @@ public final class WindowInsets { 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 ae00b7072684..2fb5213279a6 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -520,11 +520,16 @@ public final class WindowManagerImpl implements WindowManager { 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/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java index 89da04128bc8..474c61f0abbb 100644 --- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java +++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java @@ -530,13 +530,14 @@ final class IInputMethodManagerGlobalInvoker { @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 b60efc1ee508..7c9678f11e0e 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -204,6 +204,9 @@ public final class InputMethodInfo implements Parcelable { */ 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 @@ public final class InputMethodInfo implements Parcelable { 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 @@ public final class InputMethodInfo implements Parcelable { mSubtypes = source.mSubtypes; mHandledConfigChanges = source.mHandledConfigChanges; mSupportsStylusHandwriting = source.mSupportsStylusHandwriting; + mSupportsConnectionlessStylusHandwriting = source.mSupportsConnectionlessStylusHandwriting; mForceDefault = source.mForceDefault; mStylusHandwritingSettingsActivityAttr = source.mStylusHandwritingSettingsActivityAttr; } @@ -463,6 +470,7 @@ public final class InputMethodInfo implements Parcelable { mSubtypes = new InputMethodSubtypeArray(source); mHandledConfigChanges = source.readInt(); mSupportsStylusHandwriting = source.readBoolean(); + mSupportsConnectionlessStylusHandwriting = source.readBoolean(); mStylusHandwritingSettingsActivityAttr = source.readString8(); mForceDefault = false; } @@ -479,6 +487,7 @@ public final class InputMethodInfo implements Parcelable { false /* inlineSuggestionsEnabled */, false /* isVrOnly */, false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */, false /* supportsStylusHandwriting */, + false /* supportConnectionlessStylusHandwriting */, null /* stylusHandwritingSettingsActivityAttr */, false /* inlineSuggestionsEnabled */); } @@ -488,9 +497,11 @@ public final class InputMethodInfo implements Parcelable { * @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 @@ public final class InputMethodInfo implements Parcelable { true /* supportsSwitchingToNextInputMethod */, false /* inlineSuggestionsEnabled */, false /* isVrOnly */, false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */, - supportStylusHandwriting, stylusHandwritingSettingsActivityAttr, - false /* inlineSuggestionsEnabled */); + supportStylusHandwriting, supportConnectionlessStylusHandwriting, + stylusHandwritingSettingsActivityAttr, false /* inlineSuggestionsEnabled */); } /** @@ -517,6 +528,7 @@ public final class InputMethodInfo implements Parcelable { false /* inlineSuggestionsEnabled */, false /* isVrOnly */, false /* isVirtualDeviceOnly */, handledConfigChanges, false /* supportsStylusHandwriting */, + false /* supportConnectionlessStylusHandwriting */, null /* stylusHandwritingSettingsActivityAttr */, false /* inlineSuggestionsEnabled */); } @@ -533,6 +545,7 @@ public final class InputMethodInfo implements Parcelable { true /* supportsSwitchingToNextInputMethod */, false /* inlineSuggestionsEnabled */, false /* isVrOnly */, false /* isVirtualDeviceOnly */, 0 /* handledconfigChanges */, false /* supportsStylusHandwriting */, + false /* supportConnectionlessStylusHandwriting */, null /* stylusHandwritingSettingsActivityAttr */, false /* inlineSuggestionsEnabled */); } @@ -549,6 +562,7 @@ public final class InputMethodInfo implements Parcelable { supportsSwitchingToNextInputMethod, false /* inlineSuggestionsEnabled */, isVrOnly, false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */, false /* supportsStylusHandwriting */, + false /* supportConnectionlessStylusHandwriting */, null /* stylusHandwritingSettingsActivityAttr */, false /* inlineSuggestionsEnabled */); } @@ -562,7 +576,8 @@ public final class InputMethodInfo implements Parcelable { 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 @@ public final class InputMethodInfo implements Parcelable { mIsVirtualDeviceOnly = isVirtualDeviceOnly; mHandledConfigChanges = handledConfigChanges; mSupportsStylusHandwriting = supportsStylusHandwriting; + mSupportsConnectionlessStylusHandwriting = supportsConnectionlessStylusHandwriting; mStylusHandwritingSettingsActivityAttr = stylusHandwritingSettingsActivityAttr; } @@ -763,6 +779,16 @@ public final class InputMethodInfo implements Parcelable { } /** + * 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 @@ public final class InputMethodInfo implements Parcelable { + " mSuppressesSpellChecker=" + mSuppressesSpellChecker + " mShowInInputMethodPicker=" + mShowInInputMethodPicker + " mSupportsStylusHandwriting=" + mSupportsStylusHandwriting + + " mSupportsConnectionlessStylusHandwriting=" + + mSupportsConnectionlessStylusHandwriting + " mStylusHandwritingSettingsActivityAttr=" + mStylusHandwritingSettingsActivityAttr); pw.println(prefix + "mIsDefaultResId=0x" @@ -947,6 +975,7 @@ public final class InputMethodInfo implements Parcelable { 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 3d70c5ba0715..6772efb27d06 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -18,6 +18,7 @@ package android.view.inputmethod; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR; +import static android.view.inputmethod.Flags.initiationWithoutInputConnection; import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_IMMEDIATE; import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_MONITOR; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.DISPLAY_ID; @@ -565,8 +566,15 @@ public final class InputMethodManager { @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; @@ -690,6 +698,17 @@ public final class InputMethodManager { 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(); @@ -1583,7 +1602,7 @@ public final class InputMethodManager { @Override public Boolean recompute(Integer userId) { return IInputMethodManagerGlobalInvoker.isStylusHandwritingAvailableAsUser( - userId); + userId, /* connectionless= */ false); } }; } @@ -1593,6 +1612,30 @@ public final class InputMethodManager { } /** + * 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 @@ -1950,6 +1993,10 @@ public final class InputMethodManager { if (mServedView != null) { clearedView = mServedView; mServedView = null; + if (initiationWithoutInputConnection() && clearedView.getViewRootImpl() != null) { + clearedView.getViewRootImpl().getHandwritingInitiator() + .clearFocusedView(clearedView); + } } if (clearedView != null) { if (DEBUG) { @@ -2932,6 +2979,10 @@ public final class InputMethodManager { switch (res.result) { case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW: mRestartOnNextWindowFocus = true; + if (initiationWithoutInputConnection()) { + mServedView.getViewRootImpl().getHandwritingInitiator().clearFocusedView( + mServedView); + } mServedView = null; break; } @@ -3094,6 +3145,11 @@ public final class InputMethodManager { return false; } mServedView = mNextServedView; + if (initiationWithoutInputConnection() && mServedView.onCheckIsTextEditor() + && mServedView.isHandwritingDelegate()) { + mServedView.getViewRootImpl().getHandwritingInitiator().onDelegateViewFocused( + mServedView); + } if (mServedInputConnection != null) { mServedInputConnection.finishComposingTextFromImm(); } diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig index 7f1cc8e46d3c..55986e7ada47 100644 --- a/core/java/android/view/inputmethod/flags.aconfig +++ b/core/java/android/view/inputmethod/flags.aconfig @@ -62,3 +62,19 @@ flag { bug: "311791923" is_fixed_read_only: true } + +flag { + name: "initiation_without_input_connection" + namespace: "input_method" + description: "Feature flag for initiating handwriting without InputConnection" + 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 e7280d0e7867..40e28cbbbd05 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 @@ public class BackProgressAnimator { 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 @@ public class BackProgressAnimator { * 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 @@ public class BackProgressAnimator { * @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 @@ public class BackProgressAnimator { 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/IUnhandledDragCallback.aidl b/core/java/android/window/IUnhandledDragCallback.aidl new file mode 100644 index 000000000000..7806b1fe771a --- /dev/null +++ b/core/java/android/window/IUnhandledDragCallback.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.view.DragEvent; + +/** + * A callback for notifying the system when the unhandled drop is complete. + * {@hide} + */ +oneway interface IUnhandledDragCallback { + /** + * Called when the IUnhandledDropListener has fully handled the drop, and the drag can be + * cleaned up. If handled is `true`, then cleanup of the drag and drag surface will be + * immediate, otherwise, the system will treat the drag as a cancel back to the start of the + * drag. + */ + void notifyUnhandledDropComplete(boolean handled); +} diff --git a/core/java/android/window/IUnhandledDragListener.aidl b/core/java/android/window/IUnhandledDragListener.aidl new file mode 100644 index 000000000000..52e98952971d --- /dev/null +++ b/core/java/android/window/IUnhandledDragListener.aidl @@ -0,0 +1,35 @@ +/* + * 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.window; + +import android.view.DragEvent; +import android.window.IUnhandledDragCallback; + +/** + * An interface to a handler for global drags that are not consumed (ie. not handled by any window). + * {@hide} + */ +oneway interface IUnhandledDragListener { + /** + * Called when the user finishes the drag gesture but no windows have reported handling the + * drop. The DragEvent is populated with the drag surface for the listener to animate. The + * listener *MUST* call the provided callback exactly once when it has finished handling the + * drop. If the listener calls the callback with `true` then it is responsible for removing + * and releasing the drag surface passed through the DragEvent. + */ + void onUnhandledDrop(in DragEvent event, in IUnhandledDragCallback callback); +} diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index efc71d7b43de..76a34aec7e58 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -676,18 +676,20 @@ public final class WindowContainerTransaction implements Parcelable { * 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 5c911f4a632a..45d7767380a1 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -371,11 +371,11 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } 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/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java index 0b7593a1a5a2..c5c17cffa48b 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistory.java +++ b/core/java/com/android/internal/os/BatteryStatsHistory.java @@ -1146,7 +1146,7 @@ public class BatteryStatsHistory { 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 @@ public class BatteryStatsHistory { 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 739ee48295a9..b2a6a934ba3f 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java +++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java @@ -309,7 +309,12 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor 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/os/TimeoutRecord.java b/core/java/com/android/internal/os/TimeoutRecord.java index e9a8d4b75f16..1f4abc19571a 100644 --- a/core/java/com/android/internal/os/TimeoutRecord.java +++ b/core/java/com/android/internal/os/TimeoutRecord.java @@ -45,6 +45,7 @@ public class TimeoutRecord { TimeoutKind.APP_REGISTERED, TimeoutKind.SHORT_FGS_TIMEOUT, TimeoutKind.JOB_SERVICE, + TimeoutKind.FGS_TIMEOUT, }) @Retention(RetentionPolicy.SOURCE) @@ -59,6 +60,7 @@ public class TimeoutRecord { int SHORT_FGS_TIMEOUT = 8; int JOB_SERVICE = 9; int APP_START = 10; + int FGS_TIMEOUT = 11; } /** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */ @@ -186,6 +188,12 @@ public class TimeoutRecord { return TimeoutRecord.endingNow(TimeoutKind.SHORT_FGS_TIMEOUT, reason); } + /** Record for a "foreground service" timeout. */ + @NonNull + public static TimeoutRecord forFgsTimeout(String reason) { + return TimeoutRecord.endingNow(TimeoutKind.FGS_TIMEOUT, reason); + } + /** Record for a job related timeout. */ @NonNull public static TimeoutRecord forJobService(String reason) { diff --git a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java index f62ff38528f7..e11067dbd722 100644 --- a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java +++ b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java @@ -22,6 +22,7 @@ import static com.android.internal.os.TimeoutRecord.TimeoutKind; import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__BROADCAST_OF_INTENT; import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__CONTENT_PROVIDER_NOT_RESPONDING; import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__EXECUTING_SERVICE; +import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__FGS_TIMEOUT; import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT; import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT_NO_FOCUSED_WINDOW; import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__JOB_SERVICE; @@ -548,6 +549,8 @@ public class AnrLatencyTracker implements AutoCloseable { return ANRLATENCY_REPORTED__ANR_TYPE__SHORT_FGS_TIMEOUT; case TimeoutKind.JOB_SERVICE: return ANRLATENCY_REPORTED__ANR_TYPE__JOB_SERVICE; + case TimeoutKind.FGS_TIMEOUT: + return ANRLATENCY_REPORTED__ANR_TYPE__FGS_TIMEOUT; default: return ANRLATENCY_REPORTED__ANR_TYPE__UNKNOWN_ANR_TYPE; } diff --git a/core/java/com/android/internal/protolog/OWNERS b/core/java/com/android/internal/protolog/OWNERS new file mode 100644 index 000000000000..18cf2be9f7df --- /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 595bf3b8eb97..ca5d44111f74 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -158,7 +158,7 @@ interface IInputMethodManager { /** 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 b5e9b8f537e4..0ceba25ede2c 100644 --- a/core/java/com/android/internal/widget/NotificationOptimizedLinearLayout.java +++ b/core/java/com/android/internal/widget/NotificationOptimizedLinearLayout.java @@ -53,6 +53,7 @@ import java.util.List; * - 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 @@ public class NotificationOptimizedLinearLayout extends LinearLayout { final View weightedChildView = getSingleWeightedChild(); mShouldUseOptimizedLayout = isUseOptimizedLinearLayoutFlagEnabled() && weightedChildView != null - && isLinearLayoutUsable(widthMeasureSpec, heightMeasureSpec); + && isOptimizationPossible(widthMeasureSpec, heightMeasureSpec); if (mShouldUseOptimizedLayout) { onMeasureOptimized(weightedChildView, widthMeasureSpec, heightMeasureSpec); @@ -118,7 +119,7 @@ public class NotificationOptimizedLinearLayout extends LinearLayout { * @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 @@ public class NotificationOptimizedLinearLayout extends LinearLayout { 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 @@ public class NotificationOptimizedLinearLayout extends LinearLayout { */ 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 boolean isContentFirstItem = !activeChildren.isEmpty() && activeChildren.get(0) - == weightedChildView; - - final boolean isContentLastItem = !activeChildren.isEmpty() && activeChildren.get( - activeChildCount - 1) == weightedChildView; - - final int horizontalPaddings = getPaddingLeft() + getPaddingRight(); + final int availableHeight = MeasureSpec.getSize(heightMeasureSpec); + final int heightMode = MeasureSpec.getMode(heightMeasureSpec); - // 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 - final MarginLayoutParams lp = (MarginLayoutParams) weightedChildView.getLayoutParams(); + // Add padding to totalLength that we are going to use for remaining space. + totalLength += mPaddingTop + mPaddingBottom; - int usedSpace = usedHeight + lp.topMargin + lp.bottomMargin; - if (isContentFirstItem) { - usedSpace += getPaddingTop(); - } - if (isContentLastItem) { - usedSpace += getPaddingBottom(); + // 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; + + // 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); } + 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); + // 3. Measure weightedChildView with the remaining space. + weightedChildView.measure(childWidthMeasureSpec, childHeightMeasureSpec); - // 2. Calculate remaining height for weightedChildView. - final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( - Math.max(0, availableHeight - usedSpace), MeasureSpec.AT_MOST); + totalLength = Math.max(totalLength, + totalLength + weightedChildView.getMeasuredHeight() + lp.topMargin + + lp.bottomMargin); - // 3. Measure weightedChildView with the remaining remaining space. - weightedChildView.measure(childWidthMeasureSpec, childHeightMeasureSpec); maxWidth = Math.max(maxWidth, weightedChildView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); - final int totalUsedHeight = usedSpace + weightedChildView.getMeasuredHeight(); - - 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; - } + // Add padding to width + maxWidth += getPaddingLeft() + getPaddingRight(); - // 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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index a425bb0e7461..75fbff330f49 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3829,6 +3829,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 +5922,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 +7034,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/res/layout/app_perms_summary.xml b/core/res/res/layout/app_perms_summary.xml index b8d93aca755b..509b98829883 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 41bc8251694f..321f9f9de457 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3641,6 +3641,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. --> @@ -3966,6 +3978,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 dbe7196d8e6a..104b7cd5450b 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" /> @@ -82,6 +88,13 @@ <bool name="config_wlan_data_service_conn_persistence_on_restart">true</bool> <java-symbol type="bool" name="config_wlan_data_service_conn_persistence_on_restart" /> + <!-- Indicating whether the retry timer from setup data call response for data throttling should + be honored for emergency network request. By default this is off, meaning for emergency + network requests, the data frameworks will ignore the previous retry timer passed in from + setup data call response. --> + <bool name="config_honor_data_retry_timer_for_emergency_network">false</bool> + <java-symbol type="bool" name="config_honor_data_retry_timer_for_emergency_network" /> + <!-- Cellular data service package name to bind to by default. If none is specified in an overlay, an empty string is passed in --> <string name="config_wwan_data_service_package" translatable="false">com.android.phone</string> diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index 5ee555543387..6029d2377cdc 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 5aace81696cf..e4cf7ac7e7cd 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 @@ public final class TunerAdapterTest { 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 0806fa0b9879..db95d7af5d52 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 class ConnectivityManagerStressTestRunner extends InstrumentationTestRunn } } - 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 e1bcd4a0727b..936f4d7d5152 100644 --- a/core/tests/coretests/src/android/view/InsetsSourceTest.java +++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java @@ -279,5 +279,204 @@ public class InsetsSourceTest { } } + @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 672875a19f9f..16bd20a42b2a 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -63,6 +63,8 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.List; + /** * Tests for {@link InsetsState}. * @@ -88,6 +90,8 @@ public class InsetsStateTest { 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 class InsetsStateTest { 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 @@ public class InsetsStateTest { } @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 @@ public class InsetsStateTest { 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 69abf5f3204f..ab4543cb8001 100644 --- a/core/tests/coretests/src/android/view/WindowInsetsTest.java +++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java @@ -41,14 +41,14 @@ public class WindowInsetsTest { 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 @@ public class WindowInsetsTest { 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/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java index f39bddd7f032..51eb41c5a271 100644 --- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java +++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java @@ -20,10 +20,12 @@ import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_HOVER_MOVE; import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_UP; +import static android.view.inputmethod.Flags.initiationWithoutInputConnection; import static android.view.stylus.HandwritingTestUtil.createView; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assume.assumeFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; @@ -91,6 +93,7 @@ public class HandwritingInitiatorTest { private EditText mTestView1; private EditText mTestView2; private Context mContext; + private boolean mInitiateWithoutConnection; @Before public void setup() throws Exception { @@ -119,6 +122,7 @@ public class HandwritingInitiatorTest { mHandwritingInitiator.updateHandwritingAreasForView(mTestView1); mHandwritingInitiator.updateHandwritingAreasForView(mTestView2); doReturn(true).when(mHandwritingInitiator).tryAcceptStylusHandwritingDelegation(any()); + mInitiateWithoutConnection = initiationWithoutInputConnection(); } @Test @@ -194,7 +198,9 @@ public class HandwritingInitiatorTest { mTestView1.setText("hello"); when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4); - mHandwritingInitiator.onInputConnectionCreated(mTestView1); + if (!mInitiateWithoutConnection) { + mHandwritingInitiator.onInputConnectionCreated(mTestView1); + } final int x1 = sHwArea1.left - HW_BOUNDS_OFFSETS_LEFT_PX / 2; final int y1 = sHwArea1.top - HW_BOUNDS_OFFSETS_TOP_PX / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); @@ -214,7 +220,7 @@ public class HandwritingInitiatorTest { } @Test - public void onTouchEvent_startHandwriting_inputConnectionBuiltAfterStylusMove() { + public void onTouchEvent_startHandwriting_servedViewUpdateAfterStylusMove() { final int x1 = (sHwArea1.left + sHwArea1.right) / 2; final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); @@ -225,14 +231,19 @@ public class HandwritingInitiatorTest { MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); mHandwritingInitiator.onTouchEvent(stylusEvent2); - // InputConnection is created after stylus movement. - mHandwritingInitiator.onInputConnectionCreated(mTestView1); + if (mInitiateWithoutConnection) { + // Focus is changed after stylus movement. + mHandwritingInitiator.updateFocusedView(mTestView1, /*fromTouchEvent*/ true); + } else { + // InputConnection is created after stylus movement. + mHandwritingInitiator.onInputConnectionCreated(mTestView1); + } verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1); } @Test - public void onTouchEvent_startHandwriting_inputConnectionBuilt_stylusMoveInExtendedHWArea() { + public void onTouchEvent_startHandwriting_servedViewUpdate_stylusMoveInExtendedHWArea() { mTestView1.setText("hello"); when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4); // The stylus down point is between mTestView1 and mTestView2, but it is within the @@ -247,21 +258,35 @@ public class HandwritingInitiatorTest { MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); mHandwritingInitiator.onTouchEvent(stylusEvent2); - // First create InputConnection for mTestView2 and verify that handwriting is not started. - mHandwritingInitiator.onInputConnectionCreated(mTestView2); - verify(mHandwritingInitiator, never()).startHandwriting(mTestView2); + if (!mInitiateWithoutConnection) { + // First create InputConnection for mTestView2 and verify that handwriting is not + // started. + mHandwritingInitiator.onInputConnectionCreated(mTestView2); + } - // Next create InputConnection for mTextView1. Handwriting is started for this view since - // the stylus down point is closest to this view. - mHandwritingInitiator.onInputConnectionCreated(mTestView1); + // Note: mTestView2 receives focus when initiationWithoutInputConnection() is enabled. + // verify that handwriting is not started. + verify(mHandwritingInitiator, never()).startHandwriting(mTestView2); + if (mInitiateWithoutConnection) { + // Focus is changed after stylus movement. + mHandwritingInitiator.updateFocusedView(mTestView1, /*fromTouchEvent*/ true); + } else { + // Next create InputConnection for mTextView1. Handwriting is started for this view + // since the stylus down point is closest to this view. + mHandwritingInitiator.onInputConnectionCreated(mTestView1); + } + // Handwriting is started for this view since the stylus down point is closest to this + // view. verify(mHandwritingInitiator).startHandwriting(mTestView1); // Since the stylus down point was outside the TextView's bounds, the handwriting initiator // sets the cursor position. verify(mTestView1).setSelection(4); } + @Test public void onTouchEvent_tryAcceptDelegation_delegatorCallbackCreatesInputConnection() { + assumeFalse(mInitiateWithoutConnection); View delegateView = new EditText(mContext); delegateView.setIsHandwritingDelegate(true); @@ -281,6 +306,7 @@ public class HandwritingInitiatorTest { verify(mHandwritingInitiator, times(1)).tryAcceptStylusHandwritingDelegation(delegateView); } + @Test public void onTouchEvent_tryAcceptDelegation_delegatorCallbackFocusesDelegate() { View delegateView = new EditText(mContext); @@ -288,8 +314,14 @@ public class HandwritingInitiatorTest { mHandwritingInitiator.onInputConnectionCreated(delegateView); reset(mHandwritingInitiator); - mTestView1.setHandwritingDelegatorCallback( - () -> mHandwritingInitiator.onDelegateViewFocused(delegateView)); + if (mInitiateWithoutConnection) { + mTestView1.setHandwritingDelegatorCallback( + () -> mHandwritingInitiator.updateFocusedView( + delegateView, /*fromTouchEvent*/ false)); + } else { + mTestView1.setHandwritingDelegatorCallback( + () -> mHandwritingInitiator.onDelegateViewFocused(delegateView)); + } final int x1 = (sHwArea1.left + sHwArea1.right) / 2; final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; @@ -339,6 +371,14 @@ public class HandwritingInitiatorTest { assertThat(onTouchEventResult4).isTrue(); } + private void callOnInputConnectionOrUpdateViewFocus(View view) { + if (mInitiateWithoutConnection) { + mHandwritingInitiator.updateFocusedView(view, /*fromTouchEvent*/ true); + } else { + mHandwritingInitiator.onInputConnectionCreated(view); + } + } + @Test public void onTouchEvent_notStartHandwriting_whenHandwritingNotAvailable() { final Rect rect = new Rect(600, 600, 900, 900); @@ -346,7 +386,7 @@ public class HandwritingInitiatorTest { false /* isStylusHandwritingAvailable */); mHandwritingInitiator.updateHandwritingAreasForView(testView); - mHandwritingInitiator.onInputConnectionCreated(testView); + callOnInputConnectionOrUpdateViewFocus(testView); final int x1 = (rect.left + rect.right) / 2; final int y1 = (rect.top + rect.bottom) / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); @@ -365,7 +405,7 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_notStartHandwriting_when_stylusTap_withinHWArea() { - mHandwritingInitiator.onInputConnectionCreated(mTestView1); + callOnInputConnectionOrUpdateViewFocus(mTestView1); final int x1 = 200; final int y1 = 200; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); @@ -381,7 +421,7 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_notStartHandwriting_when_stylusMove_outOfHWArea() { - mHandwritingInitiator.onInputConnectionCreated(mTestView1); + callOnInputConnectionOrUpdateViewFocus(mTestView1); final int x1 = 10; final int y1 = 10; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); @@ -397,7 +437,7 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_notStartHandwriting_when_stylusMove_afterTimeOut() { - mHandwritingInitiator.onInputConnectionCreated(mTestView1); + callOnInputConnectionOrUpdateViewFocus(mTestView1); final int x1 = 10; final int y1 = 10; final long time1 = 10L; @@ -433,8 +473,9 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_focusView_inputConnectionAlreadyBuilt_stylusMoveOnce_withinHWArea() { - mHandwritingInitiator.onInputConnectionCreated(mTestView1); - + if (!mInitiateWithoutConnection) { + mHandwritingInitiator.onInputConnectionCreated(mTestView1); + } final int x1 = (sHwArea1.left + sHwArea1.right) / 2; final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); @@ -487,14 +528,14 @@ public class HandwritingInitiatorTest { verify(mTestView2, times(1)).requestFocus(); - mHandwritingInitiator.onInputConnectionCreated(mTestView2); + callOnInputConnectionOrUpdateViewFocus(mTestView2); verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView2); } @Test public void onTouchEvent_handwritingAreaOverlapped_focusedViewHasPriority() { // Simulate the case where mTestView1 is focused. - mHandwritingInitiator.onInputConnectionCreated(mTestView1); + callOnInputConnectionOrUpdateViewFocus(mTestView1); // The ACTION_DOWN location is within the handwriting bounds of both mTestView1 and // mTestView2. Although it's closer to mTestView2's handwriting bounds, handwriting is // initiated for mTestView1 because it's focused. @@ -559,9 +600,14 @@ public class HandwritingInitiatorTest { // Set mTextView2 to be the delegate of mTestView1. mTestView2.setIsHandwritingDelegate(true); - mTestView1.setHandwritingDelegatorCallback( - () -> mHandwritingInitiator.onInputConnectionCreated(mTestView2)); - + if (mInitiateWithoutConnection) { + mTestView1.setHandwritingDelegatorCallback( + () -> mHandwritingInitiator.updateFocusedView( + mTestView2, /*fromTouchEvent*/ false)); + } else { + mTestView1.setHandwritingDelegatorCallback( + () -> mHandwritingInitiator.onInputConnectionCreated(mTestView2)); + } injectStylusEvent(mHandwritingInitiator, sHwArea1.centerX(), sHwArea1.centerY(), /* exceedsHWSlop */ true); // Prerequisite check, verify that handwriting started for delegateView. @@ -610,8 +656,13 @@ public class HandwritingInitiatorTest { assertThat(icon1).isNull(); // Simulate that focus is switched to mTestView2 first and then switched back. - mHandwritingInitiator.onInputConnectionCreated(mTestView2); - mHandwritingInitiator.onInputConnectionCreated(mTestView1); + if (mInitiateWithoutConnection) { + mHandwritingInitiator.updateFocusedView(mTestView2, /*fromTouchEvent*/ true); + mHandwritingInitiator.updateFocusedView(mTestView1, /*fromTouchEvent*/ true); + } else { + mHandwritingInitiator.onInputConnectionCreated(mTestView2); + mHandwritingInitiator.onInputConnectionCreated(mTestView1); + } PointerIcon icon2 = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent1); // After the change of focus, hover icon shows again. @@ -620,9 +671,15 @@ public class HandwritingInitiatorTest { @Test public void autoHandwriting_whenDisabled_wontStartHW() { - View mockView = createView(sHwArea1, false /* autoHandwritingEnabled */, - true /* isStylusHandwritingAvailable */); - mHandwritingInitiator.onInputConnectionCreated(mockView); + if (mInitiateWithoutConnection) { + mTestView1.setAutoHandwritingEnabled(false); + mTestView1.setHandwritingDelegatorCallback(null); + mHandwritingInitiator.updateFocusedView(mTestView1, /*fromTouchEvent*/ true); + } else { + View mockView = createView(sHwArea1, false /* autoHandwritingEnabled */, + true /* isStylusHandwritingAvailable */); + mHandwritingInitiator.onInputConnectionCreated(mockView); + } final int x1 = (sHwArea1.left + sHwArea1.right) / 2; final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); @@ -639,6 +696,7 @@ public class HandwritingInitiatorTest { @Test public void onInputConnectionCreated() { + assumeFalse(mInitiateWithoutConnection); mHandwritingInitiator.onInputConnectionCreated(mTestView1); assertThat(mHandwritingInitiator.mConnectedView).isNotNull(); assertThat(mHandwritingInitiator.mConnectedView.get()).isEqualTo(mTestView1); @@ -646,6 +704,7 @@ public class HandwritingInitiatorTest { @Test public void onInputConnectionCreated_whenAutoHandwritingIsDisabled() { + assumeFalse(mInitiateWithoutConnection); View view = new View(mContext); view.setAutoHandwritingEnabled(false); assertThat(view.isAutoHandwritingEnabled()).isFalse(); @@ -656,6 +715,7 @@ public class HandwritingInitiatorTest { @Test public void onInputConnectionClosed() { + assumeFalse(mInitiateWithoutConnection); mHandwritingInitiator.onInputConnectionCreated(mTestView1); mHandwritingInitiator.onInputConnectionClosed(mTestView1); @@ -664,6 +724,7 @@ public class HandwritingInitiatorTest { @Test public void onInputConnectionClosed_whenAutoHandwritingIsDisabled() { + assumeFalse(mInitiateWithoutConnection); View view = new View(mContext); view.setAutoHandwritingEnabled(false); mHandwritingInitiator.onInputConnectionCreated(view); @@ -674,6 +735,7 @@ public class HandwritingInitiatorTest { @Test public void onInputConnectionCreated_inputConnectionRestarted() { + assumeFalse(mInitiateWithoutConnection); // When IMM restarts input connection, View#onInputConnectionCreatedInternal might be // called before View#onInputConnectionClosedInternal. As a result, we need to handle the // case where "one view "2 InputConnections". 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 84dd2740e8b7..8d66cfc7a3dc 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 @@ public class ActionBarOverlayLayoutTest { 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 08333ecd99a3..bf9221a27e74 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; 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 @@ public class NotificationOptimizedLinearLayoutComparisonTest { 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 @@ public class NotificationOptimizedLinearLayoutComparisonTest { 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 @@ public class NotificationOptimizedLinearLayoutComparisonTest { } } - 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 @@ public class NotificationOptimizedLinearLayoutComparisonTest { assertLayoutsEqual("Test Case:" + testSpec, controlContainer, testContainer); } + private static class TestSpec { private final int mOrientation; private final int mWidthSpec; @@ -183,6 +207,8 @@ public class NotificationOptimizedLinearLayoutComparisonTest { 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 @@ public class NotificationOptimizedLinearLayoutComparisonTest { int firstChildGravity, int secondChildGravity, int firstChildWeight, - int secondChildWeight) { + int secondChildWeight, + int firstChildMargin, + int secondChildMargin) { mOrientation = orientation; mWidthSpec = widthSpec; mHeightSpec = heightSpec; @@ -207,6 +235,8 @@ public class NotificationOptimizedLinearLayoutComparisonTest { mSecondChildGravity = secondChildGravity; mFirstChildWeight = firstChildWeight; mSecondChildWeight = secondChildWeight; + mFirstChildMargin = firstChildMargin; + mSecondChildMargin = secondChildMargin; } @Override @@ -223,6 +253,8 @@ public class NotificationOptimizedLinearLayoutComparisonTest { + ", mSecondChildGravity=" + mSecondChildGravity + ", mFirstChildWeight=" + mFirstChildWeight + ", mSecondChildWeight=" + mSecondChildWeight + + ", mFirstChildMargin=" + mFirstChildMargin + + ", mSecondChildMargin=" + mSecondChildMargin + '}'; } @@ -246,15 +278,13 @@ public class NotificationOptimizedLinearLayoutComparisonTest { } } - 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 @@ public class NotificationOptimizedLinearLayoutComparisonTest { } 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 @@ public class NotificationOptimizedLinearLayoutComparisonTest { } } - 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 @@ public class NotificationOptimizedLinearLayoutComparisonTest { 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/devicestatetests/Android.bp b/core/tests/devicestatetests/Android.bp index 7748de57c2bc..60848b31eaec 100644 --- a/core/tests/devicestatetests/Android.bp +++ b/core/tests/devicestatetests/Android.bp @@ -29,6 +29,8 @@ android_test { "androidx.test.rules", "frameworks-base-testutils", "mockito-target-minus-junit4", + "platform-test-annotations", + "testng", ], libs: ["android.test.runner"], platform_apis: true, diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java index d54524e94139..396d403a1ada 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java +++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.devicestate; +package android.hardware.devicestate; import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE; import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE; @@ -25,18 +25,17 @@ import static org.testng.Assert.assertThrows; import android.platform.test.annotations.Presubmit; -import androidx.test.runner.AndroidJUnit4; - import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; /** - * Unit tests for {@link DeviceState}. + * Unit tests for {@link android.hardware.devicestate.DeviceState}. * <p/> * Run with <code>atest DeviceStateTest</code>. */ @Presubmit -@RunWith(AndroidJUnit4.class) +@RunWith(JUnit4.class) public final class DeviceStateTest { @Test public void testConstruct() { 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 7723d589a723..f91172df4f74 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 @@ import java.nio.charset.StandardCharsets; */ @SmallTest @RunWith(AndroidJUnit4.class) -@IgnoreUnderRavenwood(blockedBy = Xml.class) public class FastXmlSerializerTest { private static final String TAG = "FastXmlSerializerTest"; @@ -146,6 +145,7 @@ public class FastXmlSerializerTest { @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/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index d8713f7a13f8..fdb520835810 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -575,6 +575,9 @@ applications that come with the platform <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/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java index e6de5978ceb0..9159a00152dd 100644 --- a/graphics/java/android/view/PixelCopy.java +++ b/graphics/java/android/view/PixelCopy.java @@ -96,7 +96,7 @@ public final class PixelCopy { * * 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 @@ public final class PixelCopy { * * 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 @@ public final class PixelCopy { * * 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 @@ public final class PixelCopy { * * 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 @@ public final class PixelCopy { * * 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 @@ public final class PixelCopy { * * 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/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt index ea7c6edd4b56..5825bbfbfefa 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt @@ -165,6 +165,25 @@ class BubblePositionerTest { } @Test + fun testGetRestingPosition_afterBoundsChange() { + positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, + windowBounds = Rect(0, 0, 2000, 1600))) + + // Set the resting position to the right side + var allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */) + val restingPosition = PointF(allowableStackRegion.right, allowableStackRegion.centerY()) + positioner.restingPosition = restingPosition + + // Now make the device smaller + positioner.update(defaultDeviceConfig.copy(isLargeScreen = false, + windowBounds = Rect(0, 0, 1000, 1600))) + + // Check the resting position is on the correct side + allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */) + assertThat(positioner.restingPosition.x).isEqualTo(allowableStackRegion.right) + } + + @Test fun testHasUserModifiedDefaultPosition_false() { positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true)) assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index c03b6f805cf7..cda29c95281d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -120,6 +120,13 @@ public class BubblePositioner { @VisibleForTesting public void updateInternal(int rotation, Insets insets, Rect bounds) { + BubbleStackView.RelativeStackPosition prevStackPosition = null; + if (mRestingStackPosition != null && mScreenRect != null && !mScreenRect.equals(bounds)) { + // Save the resting position as a relative position with the previous bounds, at the + // end of the update we'll restore it based on the new bounds. + prevStackPosition = new BubbleStackView.RelativeStackPosition(getRestingPosition(), + getAllowableStackPositionRegion(1)); + } mRotation = rotation; mInsets = insets; @@ -182,6 +189,12 @@ public class BubblePositioner { R.dimen.bubbles_flyout_min_width_large_screen); mMaxBubbles = calculateMaxBubbles(); + + if (prevStackPosition != null) { + // Get the new resting position based on the updated values + mRestingStackPosition = prevStackPosition.getAbsolutePositionInRegion( + getAllowableStackPositionRegion(1)); + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index ead5ad23c8f3..bd9d89c9892e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -64,6 +64,7 @@ import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler; import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler; import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler; import com.android.wm.shell.draganddrop.DragAndDropController; +import com.android.wm.shell.draganddrop.UnhandledDragController; import com.android.wm.shell.freeform.FreeformComponents; import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.freeform.FreeformTaskTransitionHandler; @@ -558,6 +559,14 @@ public abstract class WMShellModule { @WMSingleton @Provides + static UnhandledDragController provideUnhandledDragController( + IWindowManager wmService, + @ShellMainThread ShellExecutor mainExecutor) { + return new UnhandledDragController(wmService, mainExecutor); + } + + @WMSingleton + @Provides static DragAndDropController provideDragAndDropController(Context context, ShellInit shellInit, ShellController shellController, 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 e8728498ad64..42c8d7417611 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.sysui.ShellSharedConstants 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 @@ class DesktopTasksController( ) } + 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 @@ class DesktopTasksController( /** 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 @@ class DesktopTasksController( * Move a task to desktop */ fun moveToDesktop( - decor: DesktopModeWindowDecoration, task: RunningTaskInfo, wct: WindowContainerTransaction = WindowContainerTransaction() ) { @@ -298,7 +302,7 @@ class DesktopTasksController( addMoveToDesktopChanges(wct, task) if (Transitions.ENABLE_SHELL_TRANSITIONS) { - enterDesktopTaskTransitionHandler.moveToDesktop(wct, decor) + enterDesktopTaskTransitionHandler.moveToDesktop(wct) } else { shellTaskOrganizer.applyTransaction(wct) } @@ -311,7 +315,6 @@ class DesktopTasksController( fun startDragToDesktop( taskInfo: RunningTaskInfo, dragToDesktopValueAnimator: MoveToDesktopAnimator, - windowDecor: DesktopModeWindowDecoration ) { KtProtoLog.v( WM_SHELL_DESKTOP_MODE, @@ -320,8 +323,7 @@ class DesktopTasksController( ) dragToDesktopTransitionHandler.startDragToDesktopTransition( taskInfo.taskId, - dragToDesktopValueAnimator, - windowDecor + dragToDesktopValueAnimator ) } @@ -522,7 +524,7 @@ class DesktopTasksController( } /** 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( * * @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 @@ class DesktopTasksController( 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 39610e3c5c80..af26e2980afe 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_END_DRAG 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 @@ class DragToDesktopTransitionHandler( 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 @@ class DragToDesktopTransitionHandler( 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 @@ class DragToDesktopTransitionHandler( fun startDragToDesktopTransition( taskId: Int, dragToDesktopAnimator: MoveToDesktopAnimator, - windowDecoration: DesktopModeWindowDecoration ) { if (inProgress) { KtProtoLog.v( @@ -128,14 +132,12 @@ class DragToDesktopTransitionHandler( 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 @@ class DragToDesktopTransitionHandler( // 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 @@ class DragToDesktopTransitionHandler( 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 @@ class DragToDesktopTransitionHandler( 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 @@ class DragToDesktopTransitionHandler( 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 @@ class DragToDesktopTransitionHandler( 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 605600f54823..07cf202ddfac 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.NonNull; 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 class EnterDesktopTaskTransitionHandler implements Transitions.Transition 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 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition 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,33 +137,33 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition @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.right) + 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); SurfaceControl.Transaction t = mTransactionSupplier.get(); animator.addUpdateListener(animation -> { final Rect animationValue = (Rect) animator.getAnimatedValue(); - t.setPosition(leash, animationValue.left, animationValue.right) + 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 0218493589b0..c469e652b117 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.RectEvaluator 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 android.window.WindowContainerTransaction 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 @@ class ToggleResizeDesktopTaskTransitionHandler( ) : 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 @@ class ToggleResizeDesktopTaskTransitionHandler( ) : 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 @@ class ToggleResizeDesktopTaskTransitionHandler( 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 @@ class ToggleResizeDesktopTaskTransitionHandler( ) .setWindowCrop(leash, startBounds.width(), startBounds.height()) .show(leash) - windowDecor.showResizeVeil(startTransaction, startBounds) + onTaskResizeAnimationListener.onAnimationStart( + taskId, + startTransaction, + startBounds + ) }, onEnd = { finishTransaction @@ -101,7 +100,7 @@ class ToggleResizeDesktopTaskTransitionHandler( ) .setWindowCrop(leash, endBounds.width(), endBounds.height()) .show(leash) - windowDecor.hideResizeVeil() + onTaskResizeAnimationListener.onAnimationEnd(taskId) finishCallback.onTransitionFinished(null) boundsAnimator = null } @@ -111,7 +110,7 @@ class ToggleResizeDesktopTaskTransitionHandler( 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/draganddrop/UnhandledDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/UnhandledDragController.kt new file mode 100644 index 000000000000..ccf48d0de9ed --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/UnhandledDragController.kt @@ -0,0 +1,100 @@ +/* + * 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.draganddrop + +import android.os.RemoteException +import android.util.Log +import android.view.DragEvent +import android.view.IWindowManager +import android.window.IUnhandledDragCallback +import android.window.IUnhandledDragListener +import androidx.annotation.VisibleForTesting +import com.android.internal.protolog.common.ProtoLog +import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.protolog.ShellProtoLogGroup +import java.util.function.Consumer + +/** + * Manages the listener and callbacks for unhandled global drags. + */ +class UnhandledDragController( + val wmService: IWindowManager, + mainExecutor: ShellExecutor +) { + private var callback: UnhandledDragAndDropCallback? = null + + private val unhandledDragListener: IUnhandledDragListener = + object : IUnhandledDragListener.Stub() { + override fun onUnhandledDrop(event: DragEvent, callback: IUnhandledDragCallback) { + mainExecutor.execute() { + this@UnhandledDragController.onUnhandledDrop(event, callback) + } + } + } + + /** + * Listener called when an unhandled drag is started. + */ + interface UnhandledDragAndDropCallback { + /** + * Called when a global drag is unhandled (ie. dropped outside of all visible windows, or + * dropped on a window that does not want to handle it). + * + * The implementer _must_ call onFinishedCallback, and if it consumes the drop, then it is + * also responsible for releasing up the drag surface provided via the drag event. + */ + fun onUnhandledDrop(dragEvent: DragEvent, onFinishedCallback: Consumer<Boolean>) {} + } + + /** + * Sets a listener for callbacks when an unhandled drag happens. + */ + fun setListener(listener: UnhandledDragAndDropCallback?) { + val updateWm = (callback == null && listener != null) + || (callback != null && listener == null) + callback = listener + if (updateWm) { + try { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, + "%s unhandled drag listener", + if (callback != null) "Registering" else "Unregistering") + wmService.setUnhandledDragListener( + if (callback != null) unhandledDragListener else null) + } catch (e: RemoteException) { + Log.e(TAG, "Failed to set unhandled drag listener") + } + } + } + + @VisibleForTesting + fun onUnhandledDrop(dragEvent: DragEvent, wmCallback: IUnhandledDragCallback) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, + "onUnhandledDrop: %s", dragEvent) + if (callback == null) { + wmCallback.notifyUnhandledDropComplete(false) + } + + callback?.onUnhandledDrop(dragEvent) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, + "Notifying onUnhandledDrop complete: %b", it) + wmCallback.notifyUnhandledDropComplete(it) + } + } + + companion object { + private val TAG = UnhandledDragController::class.java.simpleName + } +} 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 a80241e0ac5c..f2bdcae31956 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 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, @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 97d3457aaa38..dffcc6df79d8 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 @@ package com.android.wm.shell.recents; 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 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { 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 93d763608b5f..196e04edbb10 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 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { 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 891eea072c0d..7db3d382ed8e 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 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mShellCommandHandler.addDumpCallback(this::dump, this); mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(), new DesktopModeOnInsetsChangedListener()); + mDesktopTasksController.ifPresent(c -> c.setOnTaskResizeAnimationListener( + new DeskopModeOnTaskResizeAnimationListener())); } @Override @@ -356,7 +358,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { // 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 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { 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 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } mDesktopTasksController.ifPresent(c -> { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); - c.toggleDesktopTaskSize(decoration.mTaskInfo, decoration); + c.toggleDesktopTaskSize(decoration.mTaskInfo); }); return true; } @@ -761,7 +761,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { relevantDecor.mTaskInfo, relevantDecor.mTaskSurface); mDesktopTasksController.ifPresent( c -> c.startDragToDesktop(relevantDecor.mTaskInfo, - mMoveToDesktopAnimator, relevantDecor)); + mMoveToDesktopAnimator)); } } if (mMoveToDesktopAnimator != null) { @@ -1020,6 +1020,34 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { 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 000000000000..09c62bfc9da2 --- /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 afe837e834f0..35d59401ac1a 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 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> 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 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> 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 91503b1c3619..7e26577e96d4 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 @@ package com.android.wm.shell.back; 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.BackMotionEvent; 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 @@ public class BackProgressAnimatorTest { 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 9249b0a0dfda..79634e6040c4 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 @@ class DesktopTasksControllerTest : ShellTestCase() { 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 @@ class DesktopTasksControllerTest : ShellTestCase() { 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 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test fun moveToDesktop_nonExistentTask_doesNothing() { - controller.moveToDesktop(desktopModeWindowDecoration, 999) + controller.moveToDesktop(999) verifyWCTNotExecuted() } @@ -332,7 +332,7 @@ class DesktopTasksControllerTest : ShellTestCase() { 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 @@ class DesktopTasksControllerTest : ShellTestCase() { 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 @@ class DesktopTasksControllerTest : ShellTestCase() { @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 @@ class DesktopTasksControllerTest : ShellTestCase() { @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 @@ class DesktopTasksControllerTest : ShellTestCase() { 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 be639e867e0b..98e90d60b3b6 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_CANCEL_D 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.times 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 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { ) ) .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/draganddrop/UnhandledDragControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/UnhandledDragControllerTest.kt new file mode 100644 index 000000000000..522f05233f3a --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/UnhandledDragControllerTest.kt @@ -0,0 +1,115 @@ +/* + * 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.draganddrop + +import android.os.RemoteException +import android.view.DragEvent +import android.view.DragEvent.ACTION_DROP +import android.view.IWindowManager +import android.view.SurfaceControl +import android.window.IUnhandledDragCallback +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.draganddrop.UnhandledDragController.UnhandledDragAndDropCallback +import java.util.function.Consumer +import junit.framework.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.mock +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +/** + * Tests for the unhandled drag controller. + */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class UnhandledDragControllerTest : ShellTestCase() { + @Mock + private lateinit var mIWindowManager: IWindowManager + + @Mock + private lateinit var mMainExecutor: ShellExecutor + + private lateinit var mController: UnhandledDragController + + @Before + @Throws(RemoteException::class) + fun setUp() { + MockitoAnnotations.initMocks(this) + mController = UnhandledDragController(mIWindowManager, mMainExecutor) + } + + @Test + fun setListener_registersUnregistersWithWM() { + mController.setListener(object : UnhandledDragAndDropCallback {}) + mController.setListener(object : UnhandledDragAndDropCallback {}) + mController.setListener(object : UnhandledDragAndDropCallback {}) + verify(mIWindowManager, Mockito.times(1)) + .setUnhandledDragListener(ArgumentMatchers.any()) + + reset(mIWindowManager) + mController.setListener(null) + mController.setListener(null) + mController.setListener(null) + verify(mIWindowManager, Mockito.times(1)) + .setUnhandledDragListener(ArgumentMatchers.isNull()) + } + + @Test + fun onUnhandledDrop_noListener_expectNotifyUnhandled() { + // Simulate an unhandled drop + val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, null, null, null, + null, null, false) + val wmCallback = mock(IUnhandledDragCallback::class.java) + mController.onUnhandledDrop(dropEvent, wmCallback) + + verify(wmCallback).notifyUnhandledDropComplete(ArgumentMatchers.eq(false)) + } + + @Test + fun onUnhandledDrop_withListener_expectNotifyHandled() { + val lastDragEvent = arrayOfNulls<DragEvent>(1) + + // Set a listener to listen for unhandled drops + mController.setListener(object : UnhandledDragAndDropCallback { + override fun onUnhandledDrop(dragEvent: DragEvent, + onFinishedCallback: Consumer<Boolean>) { + lastDragEvent[0] = dragEvent + onFinishedCallback.accept(true) + dragEvent.dragSurface.release() + } + }) + + // Simulate an unhandled drop + val dragSurface = mock(SurfaceControl::class.java) + val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, null, null, null, + dragSurface, null, false) + val wmCallback = mock(IUnhandledDragCallback::class.java) + mController.onUnhandledDrop(dropEvent, wmCallback) + + verify(wmCallback).notifyUnhandledDropComplete(ArgumentMatchers.eq(true)) + verify(dragSurface).release() + assertEquals(lastDragEvent.get(0), dropEvent) + } +} 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 000000000000..71eea4bb59b1 --- /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 7b53f70a771c..228b25ccb1ba 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 @@ public class WindowDecorationTests extends ShellTestCase { 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 @@ public class WindowDecorationTests extends ShellTestCase { 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/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java index 2de8eefc4e78..ab5c54b5cd7e 100644 --- a/media/java/android/media/AudioDevicePort.java +++ b/media/java/android/media/AudioDevicePort.java @@ -33,7 +33,7 @@ import java.util.List; * 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 4cdfc515fe9a..2b6f72eabeec 100644 --- a/media/java/android/media/AudioHalVersionInfo.java +++ b/media/java/android/media/AudioHalVersionInfo.java @@ -79,6 +79,9 @@ public final class AudioHalVersionInfo implements Parcelable, Comparable<AudioHa /** * 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 49890c150971..8cc42e0bd9b5 100644 --- a/media/java/android/media/OWNERS +++ b/media/java/android/media/OWNERS @@ -11,3 +11,8 @@ per-file *Image* = file:/graphics/java/android/graphics/OWNERS 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 f1c5c9d0c748..9b3477f4bd69 100644 --- a/media/java/android/media/metrics/EditingEndedEvent.java +++ b/media/java/android/media/metrics/EditingEndedEvent.java @@ -18,6 +18,7 @@ package android.media.metrics; 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 @@ public final class EditingEndedEvent extends Event implements Parcelable { 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 @@ public final class EditingEndedEvent extends Event implements Parcelable { /** 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 @@ public final class EditingEndedEvent extends Event implements Parcelable { 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 @@ public final class EditingEndedEvent extends Event implements Parcelable { 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 @@ public final class EditingEndedEvent extends Event implements Parcelable { 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 @@ public final class EditingEndedEvent extends Event implements Parcelable { + "finalState = " + mFinalState + ", " + + "finalProgressPercent = " + + mFinalProgressPercent + + ", " + "errorCode = " + mErrorCode + ", " + "timeSinceCreatedMillis = " + mTimeSinceCreatedMillis + ", " + + "exporterName = " + + mExporterName + + ", " + + "muxerName = " + + mMuxerName + + ", " + "inputMediaItemInfos = " + mInputMediaItemInfos + ", " @@ -307,29 +356,38 @@ public final class EditingEndedEvent extends Event implements Parcelable { 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 @@ public final class EditingEndedEvent extends Event implements Parcelable { 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 final class EditingEndedEvent extends Event implements Parcelable { 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 final class EditingEndedEvent extends Event implements Parcelable { */ 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 @@ public final class EditingEndedEvent extends Event implements Parcelable { } /** + * 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 @@ public final class EditingEndedEvent extends Event implements Parcelable { 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 final class EditingEndedEvent extends Event implements Parcelable { 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 e3290d604794..2a0648d87c85 100644 --- a/media/java/android/media/projection/MediaProjectionManager.java +++ b/media/java/android/media/projection/MediaProjectionManager.java @@ -180,7 +180,7 @@ public final class MediaProjectionManager { @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 59b10c6bcd93..76664a66ad53 100644 --- a/media/java/android/media/tv/ad/TvAdManager.java +++ b/media/java/android/media/tv/ad/TvAdManager.java @@ -18,6 +18,7 @@ package android.media.tv.ad; 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 @@ import java.util.concurrent.Executor; */ @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 class TvAdManager { */ 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 6c8a8fd4f9e4..2bba0f395a7d 100644 --- a/media/java/android/media/tv/ad/TvAdService.java +++ b/media/java/android/media/tv/ad/TvAdService.java @@ -280,7 +280,6 @@ public abstract class TvAdService extends Service { /** * Requests the bounds of the current video. - * @hide */ @CallSuper public void requestCurrentVideoBounds() { @@ -304,7 +303,6 @@ public abstract class TvAdService extends Service { /** * Requests the URI of the current channel. - * @hide */ @CallSuper public void requestCurrentChannelUri() { @@ -328,7 +326,6 @@ public abstract class TvAdService extends Service { /** * Requests the list of {@link TvTrackInfo}. - * @hide */ @CallSuper public void requestTrackInfoList() { @@ -354,7 +351,6 @@ public abstract class TvAdService extends Service { * Requests current TV input ID. * * @see android.media.tv.TvInputInfo - * @hide */ @CallSuper public void requestCurrentTvInputId() { @@ -393,7 +389,6 @@ public abstract class TvAdService extends Service { * @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 @@ public abstract class TvAdService extends Service { * 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 @@ public abstract class TvAdService extends Service { * @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 @@ public abstract class TvAdService extends Service { * "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 @@ public abstract class TvAdService extends Service { * {@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 @@ public abstract class TvAdService extends Service { } /** + * 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 ee01468b8f87..2fac8ce4950d 100644 --- a/media/java/android/media/tv/ad/TvAdView.java +++ b/media/java/android/media/tv/ad/TvAdView.java @@ -61,10 +61,19 @@ public class TvAdView extends ViewGroup { * 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 @@ public class TvAdView extends ViewGroup { * 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 @@ public class TvAdView extends ViewGroup { * * @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 @@ public class TvAdView extends ViewGroup { /** * Sends track info list to related TV AD service. - * @hide */ public void sendTrackInfoList(@Nullable List<TvTrackInfo> tracks) { if (DEBUG) { @@ -532,7 +538,6 @@ public class TvAdView extends ViewGroup { * @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 @@ public class TvAdView extends ViewGroup { * @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 @@ public class TvAdView extends ViewGroup { * 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 @@ public class TvAdView extends ViewGroup { * {@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 @@ public class TvAdView extends ViewGroup { * @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 @@ public class TvAdView extends ViewGroup { * Clears the callback. * * @see #setCallback(Executor, TvAdCallback) - * @hide */ public void clearCallback() { synchronized (mCallbackLock) { @@ -845,7 +846,6 @@ public class TvAdView extends ViewGroup { /** * Callback used to receive various status updates on the {@link TvAdView}. - * @hide */ public abstract static class TvAdCallback { @@ -898,5 +898,20 @@ public class TvAdView extends ViewGroup { 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/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java index eba26d470737..f332f8102013 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java @@ -873,6 +873,9 @@ public abstract class TvInteractiveAppService extends Service { /** * Called when the corresponding TV input selected to a track. + * + * If the track is deselected and no track is currently selected, + * trackId is an empty string. */ public void onTrackSelected(@TvTrackInfo.Type int type, @NonNull String trackId) { } @@ -1845,6 +1848,10 @@ public abstract class TvInteractiveAppService extends Service { if (DEBUG) { Log.d(TAG, "notifyTrackSelected (type=" + type + "trackId=" + trackId + ")"); } + // TvInputService accepts a Null String, but onTrackSelected expects NonNull. + if (trackId == null) { + trackId = ""; + } onTrackSelected(type, trackId); } 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 5590219bc011..7cd6bb3a6cef 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 @@ fun CredentialsScreenChip( { 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 3252aad6262f..1b29e8367a78 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 000000000000..a696f8c9c9c9 --- /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 000000000000..60983070b1cf --- /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 000000000000..0fb8c3f8bc6d --- /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 000000000000..dfc4c0a847c0 --- /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 8ae117e4143c..4feb6e6e2023 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 @@ public class SecureSettings { 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, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 5adae37533ff..d0ed656a5059 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -397,6 +397,7 @@ public class SecureSettingsValidators { 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/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 926e181943e4..c086baa7c8db 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/accessibility/accessibilitymenu/TEST_MAPPING b/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING index 2bd52b552698..29a25ad04cbe 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING +++ b/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING @@ -4,8 +4,15 @@ "name": "AccessibilityMenuServiceTests", "options": [ { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, + "exclude-annotation": "android.support.test.filters.FlakyTest" + } + ] + } + ], + "postsubmit": [ + { + "name": "AccessibilityMenuServiceTests", + "options": [ { "exclude-annotation": "android.support.test.filters.FlakyTest" } diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java index 9d1af0e2375a..72c1092b92d6 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java +++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java @@ -36,6 +36,7 @@ import android.app.Instrumentation; import android.app.KeyguardManager; import android.app.UiAutomation; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -57,6 +58,7 @@ import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortc import org.junit.After; import org.junit.AfterClass; +import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -330,8 +332,10 @@ public class AccessibilityMenuServiceTest { AccessibilityNodeInfo assistantButton = findGridButtonInfo(getGridButtonList(), String.valueOf(ShortcutId.ID_ASSISTANT_VALUE.ordinal())); Intent expectedIntent = new Intent(Intent.ACTION_VOICE_COMMAND); - String expectedPackage = expectedIntent.resolveActivity( - sInstrumentation.getContext().getPackageManager()).getPackageName(); + ComponentName componentName = expectedIntent.resolveActivity( + sInstrumentation.getContext().getPackageManager()); + Assume.assumeNotNull(componentName); + String expectedPackage = componentName.getPackageName(); sUiAutomation.executeAndWaitForEvent( () -> assistantButton.performAction(CLICK_ID), diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 56576f1f0c03..407873e02aea 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -420,3 +420,12 @@ flag { } } +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 + } +} 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 000000000000..a7de1eede1f4 --- /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 000000000000..4c03664fc244 --- /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 000000000000..60c0b7719a25 --- /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/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt index 72e884e9e5d6..9c2791f5a257 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt @@ -120,6 +120,7 @@ internal fun Collection<SensorPropertiesInternal?>.extractAuthenticatorTypes(): internal fun promptInfo( logoRes: Int = -1, logoBitmap: Bitmap? = null, + logoDescription: String? = null, title: String = "title", subtitle: String = "sub", description: String = "desc", @@ -132,6 +133,7 @@ internal fun promptInfo( val info = PromptInfo() info.logoRes = logoRes info.logoBitmap = logoBitmap + info.logoDescription = logoDescription info.title = title info.subtitle = subtitle info.description = description diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt index 7242cb20dc77..f6c056698967 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt @@ -54,7 +54,7 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() { private lateinit var powerInteractor: PowerInteractor private lateinit var underTest: LightRevealScrimRepositoryImpl - @get:Rule val animatorTestRule = AnimatorTestRule() + @get:Rule val animatorTestRule = AnimatorTestRule(this) @Before fun setUp() { 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 000000000000..ed4b1e6a43c9 --- /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/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml index a877853eaec8..0ecdcfcbdd83 100644 --- a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml +++ b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml @@ -13,6 +13,16 @@ android:layout_height="match_parent"> android:scaleType="fitXY" android:visibility="gone" /> + <TextView + android:id="@+id/logo_description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="@integer/biometric_dialog_text_gravity" + android:singleLine="true" + android:marqueeRepeatLimit="1" + android:ellipsize="marquee" + android:visibility="gone"/> + <ImageView android:id="@+id/background" android:layout_width="0dp" diff --git a/packages/SystemUI/res/layout/biometric_prompt_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_layout.xml index 10f71134c4cc..e7590746207d 100644 --- a/packages/SystemUI/res/layout/biometric_prompt_layout.xml +++ b/packages/SystemUI/res/layout/biometric_prompt_layout.xml @@ -28,6 +28,15 @@ android:scaleType="fitXY"/> <TextView + android:id="@+id/logo_description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="@integer/biometric_dialog_text_gravity" + android:singleLine="true" + android:marqueeRepeatLimit="1" + android:ellipsize="marquee"/> + + <TextView android:id="@+id/title" android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt index c17c8dced668..6133a51c6497 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt @@ -40,6 +40,7 @@ sealed class BiometricPromptRequest( val contentView: PromptContentView? = info.contentView val logoRes: Int = info.logoRes val logoBitmap: Bitmap? = info.logoBitmap + val logoDescription: String? = info.logoDescription val negativeButtonText: String = info.negativeButtonText?.toString() ?: "" } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index efad21bda380..31aadf51c4f2 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -95,6 +95,7 @@ object BiometricViewBinder { view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme) val logoView = view.requireViewById<ImageView>(R.id.logo) + val logoDescriptionView = view.requireViewById<TextView>(R.id.logo_description) val titleView = view.requireViewById<TextView>(R.id.title) val subtitleView = view.requireViewById<TextView>(R.id.subtitle) val descriptionView = view.requireViewById<TextView>(R.id.description) @@ -104,6 +105,8 @@ object BiometricViewBinder { // set selected to enable marquee unless a screen reader is enabled logoView.isSelected = !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled + logoDescriptionView.isSelected = + !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled titleView.isSelected = !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled subtitleView.isSelected = @@ -165,6 +168,7 @@ object BiometricViewBinder { } logoView.setImageDrawable(viewModel.logo.first()) + logoDescriptionView.text = viewModel.logoDescription.first() titleView.text = viewModel.title.first() subtitleView.text = viewModel.subtitle.first() descriptionView.text = viewModel.description.first() @@ -197,6 +201,7 @@ object BiometricViewBinder { viewsToHideWhenSmall = listOf( logoView, + logoDescriptionView, titleView, subtitleView, descriptionView, @@ -205,6 +210,7 @@ object BiometricViewBinder { viewsToFadeInOnSizeChange = listOf( logoView, + logoDescriptionView, titleView, subtitleView, descriptionView, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index ef5c37eaa953..788991d2e45b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.biometrics.ui.viewmodel import android.content.Context +import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.graphics.Rect import android.graphics.drawable.BitmapDrawable @@ -280,8 +281,9 @@ constructor( it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap) else -> try { - context.packageManager.getApplicationIcon(it.opPackageName) - } catch (e: PackageManager.NameNotFoundException) { + val info = context.getApplicationInfo(it.opPackageName) + context.packageManager.getApplicationIcon(info) + } catch (e: Exception) { Log.w(TAG, "Cannot find icon for package " + it.opPackageName, e) null } @@ -289,6 +291,25 @@ constructor( } .distinctUntilChanged() + /** Logo description for the prompt. */ + val logoDescription: Flow<String> = + promptSelectorInteractor.prompt + .map { + when { + !customBiometricPrompt() || it == null -> "" + it.logoDescription != null -> it.logoDescription + else -> + try { + val info = context.getApplicationInfo(it.opPackageName) + context.packageManager.getApplicationLabel(info).toString() + } catch (e: Exception) { + Log.w(TAG, "Cannot find name for package " + it.opPackageName, e) + "" + } + } + } + .distinctUntilChanged() + /** Title for the prompt. */ val title: Flow<String> = promptSelectorInteractor.prompt.map { it?.title ?: "" }.distinctUntilChanged() @@ -682,6 +703,12 @@ constructor( } } +private fun Context.getApplicationInfo(packageName: String): ApplicationInfo = + packageManager.getApplicationInfo( + packageName, + PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER + ) + /** How the fingerprint sensor was started for the prompt. */ enum class FingerprintStartMode { /** Fingerprint sensor has not started. */ diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt index 6f7dcb173156..297ad847caa6 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt @@ -17,7 +17,7 @@ package com.android.systemui.dreams.homecontrols import android.app.Activity -import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW +import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.content.Intent import android.graphics.Rect import android.os.Binder @@ -122,15 +122,14 @@ constructor( /** Creates the task fragment */ fun createTaskFragment() { - val taskBounds = Rect(activity.resources.configuration.windowConfiguration.bounds) val fragmentOptions = TaskFragmentCreationParams.Builder( organizer.organizerToken, fragmentToken, activity.activityToken!! ) - .setInitialRelativeBounds(taskBounds) - .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW) + .setInitialRelativeBounds(Rect()) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN) .build() organizer.applyTransaction( WindowContainerTransaction().createTaskFragment(fragmentOptions), diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt index 41ce3fd11e8a..83b2ee28b664 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt @@ -22,8 +22,10 @@ import com.android.server.notification.Flags.FLAG_VIBRATE_WHILE_UNLOCKED import com.android.server.notification.Flags.crossAppPoliteNotifications import com.android.server.notification.Flags.politeNotifications import com.android.server.notification.Flags.vibrateWhileUnlocked +import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT +import com.android.systemui.Flags.communalHub import com.android.systemui.Flags.keyguardBottomAreaRefactor import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.dagger.SysUISingleton @@ -63,6 +65,9 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha ComposeLockscreen.token dependsOn KeyguardShadeMigrationNssl.token ComposeLockscreen.token dependsOn keyguardBottomAreaRefactor ComposeLockscreen.token dependsOn migrateClocksToBlueprint + + // CommunalHub dependencies + communalHub dependsOn KeyguardShadeMigrationNssl.token } private inline val politeNotifications @@ -75,4 +80,6 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha get() = FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor()) private inline val migrateClocksToBlueprint get() = FlagToken(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, migrateClocksToBlueprint()) + private inline val communalHub + get() = FlagToken(FLAG_COMMUNAL_HUB, communalHub()) } 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 000000000000..f7b45e547b7f --- /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 000000000000..a50830c1f212 --- /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 000000000000..87332ae8e084 --- /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 000000000000..b93f8378c22f --- /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 000000000000..e1b96e4db938 --- /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/notification/NotificationContentDescription.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt index e7012ea51caf..17fc5c60f74f 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 @@ package com.android.systemui.statusbar.notification 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 de3a6262eafd..c8d6abe18ac3 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 @@ object NotificationIconContainerViewBinder { 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 @@ object NotificationIconContainerViewBinder { 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 15fde0ed49ee..634de7a17ef7 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 @@ public class StackScrollAlgorithm { } } 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/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java index fc2f6e958b32..c089092c9b86 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.CachedBluetoothDevice; 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 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa @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/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java index 9f4a90658b2e..f39762766633 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java @@ -444,7 +444,8 @@ public class SecurityControllerImpl implements SecurityController { UserHandle.of(userId))) { boolean hasCACerts = !(conn.getService().getUserCaAliases().getList().isEmpty()); idWithCert = new Pair<Integer, Boolean>(userId, hasCACerts); - } catch (RemoteException | InterruptedException | AssertionError e) { + } catch (RemoteException | InterruptedException | AssertionError + | IllegalStateException e) { Log.i(TAG, "failed to get CA certs", e); idWithCert = new Pair<Integer, Boolean>(userId, null); } finally { 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 000000000000..43450c4418aa --- /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 1af5c46cd14b..67d6a7f5d735 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 @@ interface AudioModule { @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 1c17fc34a34a..bb4a2c405b31 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 @@ constructor( controller.setSuggestionCardIds(storeLocations.toSet()) } - private val binder: IWalletContextualLocationsService.Stub - = object : IWalletContextualLocationsService.Stub() { - override fun addWalletCardsUpdatedListener(listener: IWalletCardsUpdatedListener) { - addWalletCardsUpdatedListenerInternal(listener) - } - override fun onWalletContextualLocationsStateUpdated(storeLocations: List<String>) { - onWalletContextualLocationsStateUpdatedInternal(storeLocations) + private val binder: IWalletContextualLocationsService.Stub = + object : IWalletContextualLocationsService.Stub() { + override fun addWalletCardsUpdatedListener(listener: IWalletCardsUpdatedListener) { + addWalletCardsUpdatedListenerInternal(listener) + } + 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/android/animation/AnimatorTestRuleIsolationTest.kt b/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt index 0fe2283c8543..f23fbee18904 100644 --- a/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt +++ b/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt @@ -34,7 +34,7 @@ import org.junit.runner.RunWith @RunWithLooper class AnimatorTestRuleIsolationTest : SysuiTestCase() { - @get:Rule val animatorTestRule = AnimatorTestRule() + @get:Rule val animatorTestRule = AnimatorTestRule(this) @Test fun testA() { diff --git a/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt b/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt index cc7f7e4067d9..fd5f1573ba5e 100644 --- a/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt +++ b/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt @@ -31,7 +31,7 @@ import org.junit.runner.RunWith @RunWithLooper class AnimatorTestRulePrecisionTest : SysuiTestCase() { - @get:Rule val animatorTestRule = AnimatorTestRule() + @get:Rule val animatorTestRule = AnimatorTestRule(this) var value1: Float = -1f var value2: Float = -1f diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index e6637e60b146..cd19259091ab 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -22,6 +22,7 @@ import android.view.ViewTreeObserver import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.Flags as AConfigFlags import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository @@ -259,6 +260,7 @@ class ClockEventControllerTest : SysuiTestCase() { @Test fun keyguardCallback_visibilityChanged_clockDozeCalled() = runBlocking(IMMEDIATE) { + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL) val captor = argumentCaptor<KeyguardUpdateMonitorCallback>() verify(keyguardUpdateMonitor).registerCallback(capture(captor)) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index fad85526dec9..e893eb196039 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -56,7 +56,7 @@ import java.lang.reflect.Field; public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControllerBaseTest { @Rule - public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this); @Test public void dozeTimeTick_updatesSlice() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java index ba27fcd49fac..dd428f562bd1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java @@ -48,7 +48,7 @@ import org.junit.runner.RunWith; public class ExpandHelperTest extends SysuiTestCase { @Rule - public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this); private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); private ExpandableNotificationRow mRow; diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java index e006d59cad82..64936862bc20 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java @@ -82,7 +82,7 @@ import java.util.function.Supplier; public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { @Rule - public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this); @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); private static final float DEFAULT_SCALE = 4.0f; diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java index 2225ad6e49d7..f1b0c1874511 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -129,7 +129,8 @@ import java.util.concurrent.atomic.AtomicInteger; public class WindowMagnificationControllerTest extends SysuiTestCase { @Rule - public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); + // NOTE: pass 'null' to allow this test advances time on the main thread. + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(null); @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java index a35a50982a97..08b49e7d2745 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java @@ -132,7 +132,7 @@ import java.util.function.Supplier; public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiTestCase { @Rule - public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this); @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); 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 2b51ac5e3187..f07932c0de69 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 @@ package com.android.systemui.animation 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,10 +30,9 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) @SmallTest @RunWithLooper -@FlakyTest(bugId = 302149604) class AnimatorTestRuleOrderTest : SysuiTestCase() { - @get:Rule val animatorTestRule = AnimatorTestRule() + @get:Rule val animatorTestRule = AnimatorTestRule(this) var value1: Float = -1f var value2: Float = -1f diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt index a46167a423f1..8fab2332c00e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt @@ -26,6 +26,7 @@ class BiometricPromptRequestTest : SysuiTestCase() { @Test fun biometricRequestFromPromptInfo() { val logoRes = R.drawable.ic_cake + val logoDescription = "test cake" val title = "what" val subtitle = "a" val description = "request" @@ -41,6 +42,7 @@ class BiometricPromptRequestTest : SysuiTestCase() { BiometricPromptRequest.Biometric( promptInfo( logoRes = logoRes, + logoDescription = logoDescription, title = title, subtitle = subtitle, description = description, @@ -53,6 +55,7 @@ class BiometricPromptRequestTest : SysuiTestCase() { ) assertThat(request.logoRes).isEqualTo(logoRes) + assertThat(request.logoDescription).isEqualTo(logoDescription) assertThat(request.title).isEqualTo(title) assertThat(request.subtitle).isEqualTo(subtitle) assertThat(request.description).isEqualTo(description) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index 2e94d381b8dc..ff68fe3df9e9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.biometrics.ui.viewmodel +import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.res.Configuration import android.graphics.Bitmap @@ -74,6 +75,8 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.junit.MockitoJUnit @@ -95,6 +98,8 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor @Mock private lateinit var udfpsUtils: UdfpsUtils @Mock private lateinit var packageManager: PackageManager + @Mock private lateinit var applicationInfoWithIcon: ApplicationInfo + @Mock private lateinit var applicationInfoNoIcon: ApplicationInfo private val fakeExecutor = FakeExecutor(FakeSystemClock()) private val testScope = TestScope() @@ -102,6 +107,8 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa private val logoResFromApp = R.drawable.ic_cake private val logoFromApp = context.getDrawable(logoResFromApp) private val logoBitmapFromApp = Bitmap.createBitmap(400, 400, Bitmap.Config.RGB_565) + private val defaultLogoDescription = "Test Android App" + private val logoDescriptionFromApp = "Test Cake App" private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository private lateinit var promptRepository: FakePromptRepository @@ -166,7 +173,14 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa iconViewModel = viewModel.iconViewModel // Set up default logo icon and app customized icon - whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon) + whenever(packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME_NO_ICON), anyInt())) + .thenReturn(applicationInfoNoIcon) + whenever(packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME), anyInt())) + .thenReturn(applicationInfoWithIcon) + whenever(packageManager.getApplicationIcon(applicationInfoWithIcon)) + .thenReturn(defaultLogoIcon) + whenever(packageManager.getApplicationLabel(applicationInfoWithIcon)) + .thenReturn(defaultLogoDescription) context.setMockPackageManager(packageManager) val resources = context.getOrCreateTestableResources() resources.addOverride(logoResFromApp, logoFromApp) @@ -1277,6 +1291,29 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp) } + @Test + fun logoDescriptionIsEmptyIfPackageNameNotFound() = + runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) { + mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) + val logoDescription by collectLastValue(viewModel.logoDescription) + assertThat(logoDescription).isEqualTo("") + } + + @Test + fun defaultLogoDescriptionIfNoLogoDescriptionSet() = runGenericTest { + mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) + val logoDescription by collectLastValue(viewModel.logoDescription) + assertThat(logoDescription).isEqualTo(defaultLogoDescription) + } + + @Test + fun logoDescriptionSetByApp() = + runGenericTest(logoDescription = logoDescriptionFromApp) { + mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) + val logoDescription by collectLastValue(viewModel.logoDescription) + assertThat(logoDescription).isEqualTo(logoDescriptionFromApp) + } + /** Asserts that the selected buttons are visible now. */ private suspend fun TestScope.assertButtonsVisible( tryAgain: Boolean = false, @@ -1300,6 +1337,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa contentView: PromptContentView? = null, logoRes: Int = -1, logoBitmap: Bitmap? = null, + logoDescription: String? = null, packageName: String = OP_PACKAGE_NAME, block: suspend TestScope.() -> Unit, ) { @@ -1312,6 +1350,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa contentViewFromApp = contentView, logoResFromApp = logoRes, logoBitmapFromApp = logoBitmap, + logoDescriptionFromApp = logoDescription, packageName = packageName, ) @@ -1492,12 +1531,14 @@ private fun PromptSelectorInteractor.initializePrompt( contentViewFromApp: PromptContentView? = null, logoResFromApp: Int = -1, logoBitmapFromApp: Bitmap? = null, + logoDescriptionFromApp: String? = null, packageName: String = OP_PACKAGE_NAME, ) { val info = PromptInfo().apply { logoRes = logoResFromApp logoBitmap = logoBitmapFromApp + logoDescription = logoDescriptionFromApp title = "t" subtitle = "s" description = descriptionFromApp diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt index 5b29a867ceaf..7787a7fa6af1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt @@ -43,7 +43,7 @@ import org.mockito.MockitoAnnotations @RunWithLooper(setAsMainLooper = true) @kotlinx.coroutines.ExperimentalCoroutinesApi class KeyguardSurfaceBehindParamsApplierTest : SysuiTestCase() { - @get:Rule val animatorTestRule = AnimatorTestRule() + @get:Rule val animatorTestRule = AnimatorTestRule(this) private lateinit var underTest: KeyguardSurfaceBehindParamsApplier private lateinit var executor: FakeExecutor diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt index bbae0c90170a..ae2a9add4fb7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt @@ -40,7 +40,7 @@ import org.junit.runner.RunWith @SmallTest class QSIconViewImplTest_311121830 : SysuiTestCase() { - @get:Rule val animatorRule = AnimatorTestRule() + @get:Rule val animatorRule = AnimatorTestRule(this) @Test fun alwaysLastIcon() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index 0c4bf8120d25..ab5e51c1b1a1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -207,6 +207,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { @Test fun testDragDownHelperCalledWhenDraggingDown() = testScope.runTest { + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL) whenever(dragDownHelper.isDraggingDown).thenReturn(true) val now = SystemClock.elapsedRealtime() val ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0f, 0f, 0 /* meta */) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt index c2261211b339..4cc123407d83 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt @@ -26,6 +26,7 @@ import androidx.annotation.IdRes import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest +import com.android.systemui.Flags as AConfigFlags import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR import com.android.systemui.SysuiTestCase import com.android.systemui.fragments.FragmentHostManager @@ -400,6 +401,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { @Test fun testSplitShadeLayout_isAlignedToGuideline() { + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL) enableSplitShade() underTest.updateResources() assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline) @@ -409,6 +411,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { @Test fun testSinglePaneLayout_childrenHaveEqualMargins() { + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL) disableSplitShade() underTest.updateResources() val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin @@ -425,6 +428,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { @Test fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() { + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL) enableSplitShade() underTest.updateResources() assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0) @@ -443,6 +447,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { @Test fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderHeightResource() { mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR) + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL) setLargeScreen() val largeScreenHeaderResourceHeight = 100 val largeScreenHeaderHelperHeight = 200 @@ -465,6 +470,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { @Test fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHeightHelper() { mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR) + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL) setLargeScreen() val largeScreenHeaderResourceHeight = 100 val largeScreenHeaderHelperHeight = 200 @@ -486,6 +492,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { @Test fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() { + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL) setSmallScreen() underTest.updateResources() assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0) @@ -506,6 +513,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { @Test fun testSinglePaneShadeLayout_isAlignedToParent() { + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL) disableSplitShade() underTest.updateResources() assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt index 8be2ef008039..452302d4db8a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt @@ -51,7 +51,7 @@ import org.mockito.MockitoAnnotations class SystemEventChipAnimationControllerTest : SysuiTestCase() { private lateinit var controller: SystemEventChipAnimationController - @get:Rule val animatorTestRule = AnimatorTestRule() + @get:Rule val animatorTestRule = AnimatorTestRule(this) @Mock private lateinit var sbWindowController: StatusBarWindowController @Mock private lateinit var insetsProvider: StatusBarContentInsetsProvider diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt index 875fe58e5b50..cacfa8dc0be4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt @@ -76,7 +76,7 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() { private lateinit var chipAnimationController: SystemEventChipAnimationController private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler - @get:Rule val animatorTestRule = AnimatorTestRule() + @get:Rule val animatorTestRule = AnimatorTestRule(this) @Before fun setup() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt index 039fef9c1df5..82093adcb75c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt @@ -54,7 +54,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions @TestableLooper.RunWithLooper(setAsMainLooper = true) class NotificationWakeUpCoordinatorTest : SysuiTestCase() { - @get:Rule val animatorTestRule = AnimatorTestRule() + @get:Rule val animatorTestRule = AnimatorTestRule(this) private val kosmos = Kosmos() 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 f266f039958f..91a9da399ee8 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 @@ class StackScrollAlgorithmTest : SysuiTestCase() { 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 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @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/LegacyNotificationIconAreaControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java index b3708bad6917..41b959e98221 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java @@ -26,6 +26,7 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.flags.FeatureFlags; @@ -118,6 +119,7 @@ public class LegacyNotificationIconAreaControllerImplTest extends SysuiTestCase @Test public void testAppearResetsTranslation() { + mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL); mController.setupAodIcons(mAodIcons); when(mDozeParameters.shouldControlScreenOff()).thenReturn(false); mController.appearAodIcons(); 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 4293a27095c0..269b70fe6dfb 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 @@ class PhoneStatusBarViewTest : SysuiTestCase() { /* 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/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 54d3607c7a83..3da5ab9f9d3d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -131,7 +131,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Rule - public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this); private List<StatusBarWindowStateListener> mStatusBarWindowStateListeners = new ArrayList<>(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt index 2ce060c5d097..997c00cf49a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt @@ -42,7 +42,7 @@ class MultiSourceMinAlphaControllerTest : SysuiTestCase() { private val multiSourceMinAlphaController = MultiSourceMinAlphaController(view, initialAlpha = INITIAL_ALPHA) - @get:Rule val animatorTestRule = AnimatorTestRule() + @get:Rule val animatorTestRule = AnimatorTestRule(this) @Before fun setup() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index 658e6b01d7b4..13167b2d281f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -112,7 +112,7 @@ public class RemoteInputViewTest extends SysuiTestCase { private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake(); @Rule - public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this); @Before public void setUp() throws Exception { diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index 8a33778f320a..25a0bc4fba61 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -157,7 +157,7 @@ public class VolumeDialogImplTest extends SysuiTestCase { private FakeSettings mSecureSettings; @Rule - public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this); @Before public void setup() throws Exception { 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 af1d7881195e..e6b4d069ac98 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.FakeFeatureFlags 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 @@ class WalletContextualLocationsServiceTest : SysuiTestCase() { 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 @@ class WalletContextualLocationsServiceTest : SysuiTestCase() { 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 @@ class WalletContextualLocationsServiceTest : SysuiTestCase() { 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 @@ class WalletContextualLocationsServiceTest : SysuiTestCase() { 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/animation/AnimatorTestRule.java b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java index 1afe56f27be7..5860c2dd4cdc 100644 --- a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java +++ b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java @@ -16,14 +16,21 @@ package android.animation; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + import android.animation.AnimationHandler.AnimationFrameCallback; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Looper; import android.os.SystemClock; +import android.testing.TestableLooper; +import android.testing.TestableLooper.RunnableWithException; import android.util.AndroidRuntimeException; import android.util.Singleton; import android.view.Choreographer; +import android.view.animation.AnimationUtils; + +import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.util.Preconditions; @@ -74,25 +81,31 @@ public final class AnimatorTestRule implements TestRule { return new TestHandler(); } }; + private final Object mTest; private final long mStartTime; private long mTotalTimeDelta = 0; + private volatile boolean mCanLockAnimationClock; + private Looper mLooperWithLockedAnimationClock; /** - * Construct an AnimatorTestRule with a custom start time. - * @see #AnimatorTestRule() + * Construct an AnimatorTestRule with access to the test instance and a custom start time. + * @see #AnimatorTestRule(Object) */ - public AnimatorTestRule(long startTime) { + public AnimatorTestRule(Object test, long startTime) { + mTest = test; mStartTime = startTime; } /** - * Construct an AnimatorTestRule with a start time of {@link SystemClock#uptimeMillis()}. - * Initializing the start time with this clock reduces the discrepancies with various internals - * of classes like ValueAnimator which can sometimes read that clock via - * {@link android.view.animation.AnimationUtils#currentAnimationTimeMillis()}. + * Construct an AnimatorTestRule for the given test instance with a start time of + * {@link SystemClock#uptimeMillis()}. Initializing the start time with this clock reduces the + * discrepancies with various internals of classes like ValueAnimator which can sometimes read + * that clock via {@link android.view.animation.AnimationUtils#currentAnimationTimeMillis()}. + * + * @param test the test instance used to access the {@link TestableLooper} used by the class. */ - public AnimatorTestRule() { - this(SystemClock.uptimeMillis()); + public AnimatorTestRule(Object test) { + this(test, SystemClock.uptimeMillis()); } @NonNull @@ -102,10 +115,16 @@ public final class AnimatorTestRule implements TestRule { @Override public void evaluate() throws Throwable { final TestHandler testHandler = mTestHandler.get(); - AnimationHandler objAtStart = AnimationHandler.setTestHandler(testHandler); + final AnimationHandler objAtStart = AnimationHandler.setTestHandler(testHandler); + final RunnableWithException lockClock = + wrapWithRunBlocking(new LockAnimationClockRunnable()); + final RunnableWithException unlockClock = + wrapWithRunBlocking(new UnlockAnimationClockRunnable()); try { + lockClock.run(); base.evaluate(); } finally { + unlockClock.run(); AnimationHandler objAtEnd = AnimationHandler.setTestHandler(objAtStart); if (testHandler != objAtEnd) { // pass or fail, inner logic not restoring the handler needs to be reported. @@ -118,6 +137,78 @@ public final class AnimatorTestRule implements TestRule { }; } + private RunnableWithException wrapWithRunBlocking(RunnableWithException runnable) { + RunnableWithException wrapped = TestableLooper.wrapWithRunBlocking(mTest, runnable); + if (wrapped != null) { + return wrapped; + } + return () -> runOnMainThrowing(runnable); + } + + private static void runOnMainThrowing(RunnableWithException runnable) throws Exception { + if (Looper.myLooper() == Looper.getMainLooper()) { + runnable.run(); + } else { + final Throwable[] throwableBox = new Throwable[1]; + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + try { + runnable.run(); + } catch (Throwable t) { + throwableBox[0] = t; + } + }); + if (throwableBox[0] == null) { + return; + } else if (throwableBox[0] instanceof RuntimeException ex) { + throw ex; + } else if (throwableBox[0] instanceof Error err) { + throw err; + } else { + throw new RuntimeException(throwableBox[0]); + } + } + } + + private class LockAnimationClockRunnable implements RunnableWithException { + @Override + public void run() { + mLooperWithLockedAnimationClock = Looper.myLooper(); + mCanLockAnimationClock = true; + lockAnimationClockToCurrentTime(); + } + } + + private class UnlockAnimationClockRunnable implements RunnableWithException { + @Override + public void run() { + mCanLockAnimationClock = false; + mLooperWithLockedAnimationClock = null; + AnimationUtils.unlockAnimationClock(); + } + } + + private void lockAnimationClockToCurrentTime() { + if (!mCanLockAnimationClock) { + throw new AssertionError("Unable to lock the animation clock; " + + "has the test started? already finished?"); + } + if (mLooperWithLockedAnimationClock != Looper.myLooper()) { + throw new AssertionError("Animation clock being locked on " + Looper.myLooper() + + " but should only be locked on " + mLooperWithLockedAnimationClock); + } + long desiredTime = getCurrentTime(); + AnimationUtils.lockAnimationClock(desiredTime); + if (!mCanLockAnimationClock) { + AnimationUtils.unlockAnimationClock(); + throw new AssertionError("Threading error when locking the animation clock"); + } + long outputTime = AnimationUtils.currentAnimationTimeMillis(); + if (outputTime != desiredTime) { + throw new AssertionError("currentAnimationTimeMillis() is " + outputTime + + " after locking to " + desiredTime); + } + } + /** * If any new {@link Animator}s have been registered since the last time the frame time was * advanced, initialize them with the current frame time. Failing to do this will result in the @@ -178,6 +269,7 @@ public final class AnimatorTestRule implements TestRule { // advance time mTotalTimeDelta += timeDelta; } + lockAnimationClockToCurrentTime(); if (preFrameAction != null) { preFrameAction.accept(timeDelta); // After letting other code run, clear any new callbacks to avoid double-ticking them diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt index ba9c5eda1b63..e2fc44fd2d0d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt @@ -26,12 +26,16 @@ import org.junit.runners.model.Statement * A rule that wraps both [androidx.core.animation.AnimatorTestRule] and * [android.animation.AnimatorTestRule] such that the clocks of the two animation handlers can be * advanced together. + * + * @param test the instance of the test used to look up the TestableLooper. If a TestableLooper is + * found, the time can only be advanced on that thread; otherwise the time must be advanced on the + * main thread. */ -class AnimatorTestRule : TestRule { +class AnimatorTestRule(test: Any?) : TestRule { // Create the androidx rule, which initializes start time to SystemClock.uptimeMillis(), // then copy that time to the platform rule so that the two clocks are in sync. private val androidxRule = androidx.core.animation.AnimatorTestRule() - private val platformRule = android.animation.AnimatorTestRule(androidxRule.startTime) + private val platformRule = android.animation.AnimatorTestRule(test, androidxRule.startTime) private val advanceAndroidXTimeBy = Consumer<Long> { timeDelta -> androidxRule.advanceTimeBy(timeDelta) } 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 000000000000..c208aad78295 --- /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 000000000000..f5196866ae6f --- /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 000000000000..a61f7ececc69 --- /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 1ac69f6c4fc8..35ce4814f97d 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -58,11 +58,14 @@ java_library { 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 031984829e77..a5b28ad550ba 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 000000000000..acf85331601e --- /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 000000000000..474c03f4f3c9 --- /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 000000000000..ac4695bb9a50 --- /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 000000000000..71535556ec75 --- /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 000000000000..4b650b4bc5e1 --- /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 9b7f8f7d2171..a78c5c1e8227 100644 --- a/ravenwood/coretest/Android.bp +++ b/ravenwood/coretest/Android.bp @@ -12,9 +12,9 @@ android_ravenwood_test { 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 000000000000..b60bfbfcb6f4 --- /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 e58c282fb690..2cd585ff6c9c 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 @@ package com.android.platform.test.ravenwood.coretest; 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 022961164b84..2eef0cdaf24b 100644 --- a/ravenwood/framework-minus-apex-ravenwood-policies.txt +++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt @@ -89,6 +89,7 @@ class android.util.UtilConfig stubclass # 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 000000000000..8ca34bafc2c6 --- /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 8d76970f9ad4..9a4d4886d6f0 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 @@ package android.platform.test.ravenwood; 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 class RavenwoodClassRule implements TestRule { 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 764573defd7a..b90f112c1655 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 android.os.UserHandle.USER_SYSTEM; 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 @@ public class RavenwoodRule implements TestRule { 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 class RavenwoodRule implements TestRule { 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 000000000000..6b0abe968053 --- /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 03cfad5c7a70..b4771171815a 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 @@ import org.junit.runner.RunWith; @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 96bc2752fe95..5ba9b1ff2cd8 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 000000000000..4ceb795fe14a --- /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/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java index 2c5038940e98..df4e699e3926 100644 --- a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java +++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java @@ -35,6 +35,7 @@ import android.content.Context; import android.content.pm.ParceledListSlice; import android.os.Binder; import android.os.IBinder; +import android.os.IRemoteCallback; import android.os.Process; import android.os.ResultReceiver; import android.os.ShellCallback; @@ -162,6 +163,13 @@ public class AppPredictionManagerService extends (service) -> service.onDestroyPredictionSessionLocked(sessionId)); } + @Override + public void requestServiceFeatures(@NonNull AppPredictionSessionId sessionId, + IRemoteCallback callback) { + runForUserLocked("requestServiceFeatures", sessionId, + (service) -> service.requestServiceFeaturesLocked(sessionId, callback)); + } + public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback, diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java index 84707a8d9c00..a0198f2fe240 100644 --- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java +++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java @@ -31,6 +31,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ParceledListSlice; import android.content.pm.ServiceInfo; import android.os.IBinder; +import android.os.IRemoteCallback; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.provider.DeviceConfig; @@ -237,6 +238,18 @@ public class AppPredictionPerUserService extends sessionInfo.destroy(); } + /** + * Requests the service to provide AppPredictionService features info. + */ + @GuardedBy("mLock") + public void requestServiceFeaturesLocked(@NonNull AppPredictionSessionId sessionId, + @NonNull IRemoteCallback callback) { + final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); + if (sessionInfo == null) return; + resolveService(sessionId, true, sessionInfo.mUsesPeopleService, + s -> s.requestServiceFeatures(sessionId, callback)); + } + @Override public void onFailureOrTimeout(boolean timedOut) { if (isDebug()) { 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 5a548fdca12e..82ab0980a22b 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/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index cd45b03ba7ad..b8f6b3f3a988 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -480,9 +480,14 @@ public final class ActiveServices { /** * The available ANR timers. */ + // ActivityManagerConstants.SERVICE_TIMEOUT/ActivityManagerConstants.SERVICE_BACKGROUND_TIMEOUT private final ProcessAnrTimer mActiveServiceAnrTimer; + // see ServiceRecord$ShortFgsInfo#getAnrTime() private final ServiceAnrTimer mShortFGSAnrTimer; + // ActivityManagerConstants.DEFAULT_SERVICE_START_FOREGROUND_TIMEOUT_MS private final ServiceAnrTimer mServiceFGAnrTimer; + // see ServiceRecord#getEarliestStopTypeAndTime() + private final ServiceAnrTimer mFGSAnrTimer; // allowlisted packageName. ArraySet<String> mAllowListWhileInUsePermissionInFgs = new ArraySet<>(); @@ -744,10 +749,13 @@ public final class ActiveServices { "SERVICE_TIMEOUT"); this.mShortFGSAnrTimer = new ServiceAnrTimer(service, ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, - "FGS_TIMEOUT"); + "SHORT_FGS_TIMEOUT"); this.mServiceFGAnrTimer = new ServiceAnrTimer(service, ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, "SERVICE_FOREGROUND_TIMEOUT"); + this.mFGSAnrTimer = new ServiceAnrTimer(service, + ActivityManagerService.SERVICE_FGS_ANR_TIMEOUT_MSG, + "FGS_TIMEOUT"); } void systemServicesReady() { @@ -1570,6 +1578,7 @@ public final class ActiveServices { } maybeStopShortFgsTimeoutLocked(service); + maybeStopFgsTimeoutLocked(service); final int uid = service.appInfo.uid; final String packageName = service.name.getPackageName(); @@ -1758,6 +1767,7 @@ public final class ActiveServices { } maybeStopShortFgsTimeoutLocked(r); + maybeStopFgsTimeoutLocked(r); final int uid = r.appInfo.uid; final String packageName = r.name.getPackageName(); @@ -2243,6 +2253,8 @@ public final class ActiveServices { // Whether to extend the SHORT_SERVICE time out. boolean extendShortServiceTimeout = false; + // Whether to extend the timeout for a time-limited FGS type. + boolean extendFgsTimeout = false; // Whether setFgsRestrictionLocked() is called in here. Only used for logging. boolean fgsRestrictionRecalculated = false; @@ -2287,6 +2299,19 @@ public final class ActiveServices { final boolean isOldTypeShortFgsAndTimedOut = r.shouldTriggerShortFgsTimeout(nowUptime); + // Calling startForeground on a FGS type which has a time limit will only be + // allowed if the app is in a state where it can normally start another FGS. + // The timeout will behave as follows: + // A) <TIME_LIMITED_TYPE> -> another <TIME_LIMITED_TYPE> + // - If the start succeeds, the timeout is reset. + // B) <TIME_LIMITED_TYPE> -> non-time-limited type + // - If the start succeeds, the timeout will stop. + // C) non-time-limited type -> <TIME_LIMITED_TYPE> + // - If the start succeeds, the timeout will start. + final boolean isOldTypeTimeLimited = r.isFgsTimeLimited(); + final boolean isNewTypeTimeLimited = + r.canFgsTypeTimeOut(foregroundServiceType); + // If true, we skip the BFSL check. boolean bypassBfslCheck = false; @@ -2355,6 +2380,35 @@ public final class ActiveServices { // "if (r.mAllowStartForeground == REASON_DENIED...)" block below. } } + } else if (r.isForeground && isOldTypeTimeLimited) { + + // See if the app could start an FGS or not. + r.clearFgsAllowStart(); + setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(), + r.appInfo.uid, r.intent.getIntent(), r, r.userId, + BackgroundStartPrivileges.NONE, false /* isBindService */); + fgsRestrictionRecalculated = true; + + final boolean fgsStartAllowed = !isBgFgsRestrictionEnabledForService + || r.isFgsAllowedStart(); + + if (fgsStartAllowed) { + if (isNewTypeTimeLimited) { + // Note: in the future, we may want to look into metrics to see if + // apps are constantly switching between a time-limited type and a + // non-time-limited type or constantly calling startForeground() + // opportunistically on the same type to gain runtime and apply the + // stricter timeout. For now, always extend the timeout if the app + // is in a state where it's allowed to start a FGS. + extendFgsTimeout = true; + } else { + // FGS type is changing from a time-restricted type to one without + // a time limit so proceed as normal. + // The timeout will stop later, in maybeUpdateFgsTrackingLocked(). + } + } else { + // This case will be handled in the BFSL check below. + } } else if (r.mStartForegroundCount == 0) { /* If the service was started with startService(), not @@ -2596,6 +2650,7 @@ public final class ActiveServices { maybeUpdateShortFgsTrackingLocked(r, extendShortServiceTimeout); + maybeUpdateFgsTrackingLocked(r, extendFgsTimeout); } else { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG, "Suppressing startForeground() for FAS " + r); @@ -2631,6 +2686,7 @@ public final class ActiveServices { } maybeStopShortFgsTimeoutLocked(r); + maybeStopFgsTimeoutLocked(r); // Adjust notification handling before setting isForeground to false, because // that state is relevant to the notification policy side. @@ -3608,6 +3664,116 @@ public final class ActiveServices { } } + void onFgsTimeout(ServiceRecord sr) { + synchronized (mAm) { + final long nowUptime = SystemClock.uptimeMillis(); + final int fgsType = sr.getTimedOutFgsType(nowUptime); + if (fgsType == -1) { + mFGSAnrTimer.discard(sr); + return; + } + Slog.e(TAG_SERVICE, "FGS (" + ServiceInfo.foregroundServiceTypeToLabel(fgsType) + + ") timed out: " + sr); + mFGSAnrTimer.accept(sr); + traceInstant("FGS timed out: ", sr); + + logFGSStateChangeLocked(sr, + FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT, + nowUptime > sr.mFgsEnterTime ? (int) (nowUptime - sr.mFgsEnterTime) : 0, + FGS_STOP_REASON_UNKNOWN, + FGS_TYPE_POLICY_CHECK_UNKNOWN, + FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA, + false /* fgsRestrictionRecalculated */ + ); + try { + sr.app.getThread().scheduleTimeoutServiceForType(sr, sr.getLastStartId(), fgsType); + } catch (RemoteException e) { + Slog.w(TAG_SERVICE, "Exception from scheduleTimeoutServiceForType: " + e); + } + + // ANR the service after giving the service some time to clean up. + // ServiceRecord.getEarliestStopTypeAndTime() is an absolute time with a reference that + // is not "now". Compute the time from "now" when starting the anr timer. + final long anrTime = sr.getEarliestStopTypeAndTime().second + + mAm.mConstants.mFgsAnrExtraWaitDuration - SystemClock.uptimeMillis(); + mFGSAnrTimer.start(sr, anrTime); + } + } + + private void maybeUpdateFgsTrackingLocked(ServiceRecord sr, boolean extendTimeout) { + if (!sr.isFgsTimeLimited()) { + // Reset timers if they exist. + sr.setIsFgsTimeLimited(false); + mFGSAnrTimer.cancel(sr); + mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr); + return; + } + + if (extendTimeout || !sr.wasFgsPreviouslyTimeLimited()) { + traceInstant("FGS start: ", sr); + sr.setIsFgsTimeLimited(true); + + // We'll restart the timeout. + mFGSAnrTimer.cancel(sr); + mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr); + + final Message msg = mAm.mHandler.obtainMessage( + ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr); + mAm.mHandler.sendMessageAtTime(msg, sr.getEarliestStopTypeAndTime().second); + } + } + + private void maybeStopFgsTimeoutLocked(ServiceRecord sr) { + sr.setIsFgsTimeLimited(false); // reset fgs boolean holding time-limited type state. + if (!sr.isFgsTimeLimited()) { + return; // if none of the types are time-limited, return. + } + Slog.d(TAG_SERVICE, "Stop FGS timeout: " + sr); + mFGSAnrTimer.cancel(sr); + mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr); + } + + boolean hasServiceTimedOutLocked(ComponentName className, IBinder token) { + final int userId = UserHandle.getCallingUserId(); + final long ident = mAm.mInjector.clearCallingIdentity(); + try { + ServiceRecord sr = findServiceLocked(className, token, userId); + if (sr == null) { + return false; + } + final long nowUptime = SystemClock.uptimeMillis(); + return sr.getTimedOutFgsType(nowUptime) != -1; + } finally { + mAm.mInjector.restoreCallingIdentity(ident); + } + } + + void onFgsAnrTimeout(ServiceRecord sr) { + final long nowUptime = SystemClock.uptimeMillis(); + final int fgsType = sr.getTimedOutFgsType(nowUptime); + if (fgsType == -1 || !sr.wasFgsPreviouslyTimeLimited()) { + return; // no timed out FGS type was found + } + final String reason = "A foreground service of type " + + ServiceInfo.foregroundServiceTypeToLabel(fgsType) + + " did not stop within a timeout: " + sr.getComponentName(); + + final TimeoutRecord tr = TimeoutRecord.forFgsTimeout(reason); + + tr.mLatencyTracker.waitingOnAMSLockStarted(); + synchronized (mAm) { + tr.mLatencyTracker.waitingOnAMSLockEnded(); + + Slog.e(TAG_SERVICE, "FGS ANR'ed: " + sr); + traceInstant("FGS ANR: ", sr); + mAm.appNotResponding(sr.app, tr); + + // TODO: Can we close the ANR dialog here, if it's still shown? Currently, the ANR + // dialog really doesn't remember the "cause" (especially if there have been multiple + // ANRs), so it's not doable. + } + } + private void updateAllowlistManagerLocked(ProcessServiceRecord psr) { psr.mAllowlistManager = false; for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) { @@ -3621,6 +3787,7 @@ public final class ActiveServices { private void stopServiceAndUpdateAllowlistManagerLocked(ServiceRecord service) { maybeStopShortFgsTimeoutLocked(service); + maybeStopFgsTimeoutLocked(service); final ProcessServiceRecord psr = service.app.mServices; psr.stopService(service); psr.updateBoundClientUids(); @@ -5893,6 +6060,7 @@ public final class ActiveServices { Slog.w(TAG_SERVICE, "Short FGS brought down without stopping: " + r); maybeStopShortFgsTimeoutLocked(r); } + maybeStopFgsTimeoutLocked(r); // Report to all of the connections that the service is no longer // available. @@ -6015,6 +6183,7 @@ public final class ActiveServices { final boolean exitingFg = r.isForeground; if (exitingFg) { maybeStopShortFgsTimeoutLocked(r); + maybeStopFgsTimeoutLocked(r); decActiveForegroundAppLocked(smap, r); synchronized (mAm.mProcessStats.mLock) { ServiceState stracker = r.getTracker(); @@ -8643,7 +8812,7 @@ public final class ActiveServices { event = EventLogTags.AM_FOREGROUND_SERVICE_DENIED; } else if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT) { event = EventLogTags.AM_FOREGROUND_SERVICE_TIMED_OUT; - }else { + } else { // Unknown event. return; } diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 72e62c37106d..d97731c85d38 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -1052,6 +1052,27 @@ final class ActivityManagerConstants extends ContentObserver { public volatile long mShortFgsProcStateExtraWaitDuration = DEFAULT_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION; + /** Timeout for a mediaProcessing FGS, in milliseconds. */ + private static final String KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION = + "media_processing_fgs_timeout_duration"; + + /** @see #KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION */ + static final long DEFAULT_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION = 6 * 60 * 60_000; // 6 hours + + /** @see #KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION */ + public volatile long mMediaProcessingFgsTimeoutDuration = + DEFAULT_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION; + + /** Timeout for a dataSync FGS, in milliseconds. */ + private static final String KEY_DATA_SYNC_FGS_TIMEOUT_DURATION = + "data_sync_fgs_timeout_duration"; + + /** @see #KEY_DATA_SYNC_FGS_TIMEOUT_DURATION */ + static final long DEFAULT_DATA_SYNC_FGS_TIMEOUT_DURATION = 6 * 60 * 60_000; // 6 hours + + /** @see #KEY_DATA_SYNC_FGS_TIMEOUT_DURATION */ + public volatile long mDataSyncFgsTimeoutDuration = DEFAULT_DATA_SYNC_FGS_TIMEOUT_DURATION; + /** * If enabled, when starting an application, the system will wait for a * {@link ActivityManagerService#finishAttachApplication} from the app before scheduling @@ -1082,6 +1103,20 @@ final class ActivityManagerConstants extends ContentObserver { public volatile long mShortFgsAnrExtraWaitDuration = DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION; + /** + * If a service of a timeout-enforced type doesn't finish within this duration after its + * timeout, then we'll declare an ANR. + * i.e. if the time limit for a type is 1 hour, and this extra duration is 10 seconds, then + * the app will be ANR'ed 1 hour and 10 seconds after it started. + */ + private static final String KEY_FGS_ANR_EXTRA_WAIT_DURATION = "fgs_anr_extra_wait_duration"; + + /** @see #KEY_FGS_ANR_EXTRA_WAIT_DURATION */ + static final long DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION = 10_000; + + /** @see #KEY_FGS_ANR_EXTRA_WAIT_DURATION */ + public volatile long mFgsAnrExtraWaitDuration = DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION; + /** @see #KEY_USE_TIERED_CACHED_ADJ */ public boolean USE_TIERED_CACHED_ADJ = DEFAULT_USE_TIERED_CACHED_ADJ; @@ -1264,9 +1299,18 @@ final class ActivityManagerConstants extends ContentObserver { case KEY_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION: updateShortFgsProcStateExtraWaitDuration(); break; + case KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION: + updateMediaProcessingFgsTimeoutDuration(); + break; + case KEY_DATA_SYNC_FGS_TIMEOUT_DURATION: + updateDataSyncFgsTimeoutDuration(); + break; case KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION: updateShortFgsAnrExtraWaitDuration(); break; + case KEY_FGS_ANR_EXTRA_WAIT_DURATION: + updateFgsAnrExtraWaitDuration(); + break; case KEY_PROACTIVE_KILLS_ENABLED: updateProactiveKillsEnabled(); break; @@ -2064,6 +2108,27 @@ final class ActivityManagerConstants extends ContentObserver { DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION); } + private void updateMediaProcessingFgsTimeoutDuration() { + mMediaProcessingFgsTimeoutDuration = DeviceConfig.getLong( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION, + DEFAULT_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION); + } + + private void updateDataSyncFgsTimeoutDuration() { + mDataSyncFgsTimeoutDuration = DeviceConfig.getLong( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_DATA_SYNC_FGS_TIMEOUT_DURATION, + DEFAULT_DATA_SYNC_FGS_TIMEOUT_DURATION); + } + + private void updateFgsAnrExtraWaitDuration() { + mFgsAnrExtraWaitDuration = DeviceConfig.getLong( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_FGS_ANR_EXTRA_WAIT_DURATION, + DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION); + } + private void updateEnableWaitForFinishAttachApplication() { mEnableWaitForFinishAttachApplication = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, @@ -2295,6 +2360,13 @@ final class ActivityManagerConstants extends ContentObserver { pw.print(" "); pw.print(KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION); pw.print("="); pw.println(mShortFgsAnrExtraWaitDuration); + pw.print(" "); pw.print(KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION); + pw.print("="); pw.println(mMediaProcessingFgsTimeoutDuration); + pw.print(" "); pw.print(KEY_DATA_SYNC_FGS_TIMEOUT_DURATION); + pw.print("="); pw.println(mDataSyncFgsTimeoutDuration); + pw.print(" "); pw.print(KEY_FGS_ANR_EXTRA_WAIT_DURATION); + pw.print("="); pw.println(mFgsAnrExtraWaitDuration); + pw.print(" "); pw.print(KEY_USE_TIERED_CACHED_ADJ); pw.print("="); pw.println(USE_TIERED_CACHED_ADJ); pw.print(" "); pw.print(KEY_TIERED_CACHED_ADJ_DECAY_TIME); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 2750344b487e..bfdcb95b099e 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1702,6 +1702,8 @@ public class ActivityManagerService extends IActivityManager.Stub static final int REMOVE_UID_FROM_OBSERVER_MSG = 81; static final int BIND_APPLICATION_TIMEOUT_SOFT_MSG = 82; static final int BIND_APPLICATION_TIMEOUT_HARD_MSG = 83; + static final int SERVICE_FGS_TIMEOUT_MSG = 84; + static final int SERVICE_FGS_ANR_TIMEOUT_MSG = 85; static final int FIRST_BROADCAST_QUEUE_MSG = 200; @@ -2064,6 +2066,12 @@ public class ActivityManagerService extends IActivityManager.Stub case BIND_APPLICATION_TIMEOUT_HARD_MSG: { handleBindApplicationTimeoutHard((ProcessRecord) msg.obj); } break; + case SERVICE_FGS_TIMEOUT_MSG: { + mServices.onFgsTimeout((ServiceRecord) msg.obj); + } break; + case SERVICE_FGS_ANR_TIMEOUT_MSG: { + mServices.onFgsAnrTimeout((ServiceRecord) msg.obj); + } break; } } } @@ -13794,6 +13802,13 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override + public boolean hasServiceTimeLimitExceeded(ComponentName className, IBinder token) { + synchronized (this) { + return mServices.hasServiceTimedOutLocked(className, token); + } + } + + @Override public int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll, boolean requireFull, String name, String callerPackage) { return mUserController.handleIncomingUser(callingPid, callingUid, userId, allowAll, diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 2771572edb01..3c8d7fc833dc 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -56,6 +56,7 @@ import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.util.ArrayMap; +import android.util.Pair; import android.util.Slog; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; @@ -236,6 +237,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN boolean mFgsNotificationShown; // Whether FGS package has permissions to show notifications. boolean mFgsHasNotificationPermission; + // Whether the FGS contains a type that is time limited. + private boolean mFgsIsTimeLimited; // allow the service becomes foreground service? Service started from background may not be // allowed to become a foreground service. @@ -915,6 +918,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN pw.print(prefix); pw.print("isForeground="); pw.print(isForeground); pw.print(" foregroundId="); pw.print(foregroundId); pw.printf(" types=%08X", foregroundServiceType); + pw.print(" fgsHasTimeLimitedType="); pw.print(mFgsIsTimeLimited); pw.print(" foregroundNoti="); pw.println(foregroundNoti); if (isShortFgs() && mShortFgsInfo != null) { @@ -1789,6 +1793,83 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN + " " + (mShortFgsInfo == null ? "" : mShortFgsInfo.getDescription()); } + /** + * @return true if one of the types of this FGS has a time limit. + */ + public boolean isFgsTimeLimited() { + return startRequested && isForeground && canFgsTypeTimeOut(foregroundServiceType); + } + + /** + * Called when a FGS with a time-limited type starts ({@code true}) or stops ({@code false}). + */ + public void setIsFgsTimeLimited(boolean fgsIsTimeLimited) { + this.mFgsIsTimeLimited = fgsIsTimeLimited; + } + + /** + * @return whether {@link #mFgsIsTimeLimited} was previously set or not. + */ + public boolean wasFgsPreviouslyTimeLimited() { + return mFgsIsTimeLimited; + } + + /** + * @return the FGS type if the service has reached its time limit, otherwise -1. + */ + public int getTimedOutFgsType(long nowUptime) { + if (!isAppAlive() || !isFgsTimeLimited()) { + return -1; + } + + final Pair<Integer, Long> fgsTypeAndStopTime = getEarliestStopTypeAndTime(); + if (fgsTypeAndStopTime.first != -1 && fgsTypeAndStopTime.second <= nowUptime) { + return fgsTypeAndStopTime.first; + } + return -1; // no fgs type exceeded time limit + } + + /** + * @return a {@code Pair<fgs_type, stop_time>}, representing the earliest time at which the FGS + * should be stopped (fgs start time + time limit for most restrictive type) + */ + Pair<Integer, Long> getEarliestStopTypeAndTime() { + int fgsType = -1; + long timeout = 0; + if ((foregroundServiceType & ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING) + == ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING) { + fgsType = ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING; + timeout = ams.mConstants.mMediaProcessingFgsTimeoutDuration; + } + if ((foregroundServiceType & ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) + == ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) { + // update the timeout and type if this type has a more restrictive time limit + if (timeout == 0 || ams.mConstants.mDataSyncFgsTimeoutDuration < timeout) { + fgsType = ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC; + timeout = ams.mConstants.mDataSyncFgsTimeoutDuration; + } + } + // Add the logic for time limits introduced in the future for other fgs types here. + return Pair.create(fgsType, timeout == 0 ? 0 : (mFgsEnterTime + timeout)); + } + + /** + * Check if the given types contain a type which is time restricted. + */ + boolean canFgsTypeTimeOut(int fgsType) { + // The below conditionals are not simplified on purpose to help with readability. + if ((fgsType & ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING) + == ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING) { + return true; + } + if ((fgsType & ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) + == ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) { + return true; + } + // Additional types which have time limits should be added here in the future. + return false; + } + private boolean isAppAlive() { if (app == null) { return false; diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING index bd3c8e0b3fe6..feab2c05cad6 100644 --- a/services/core/java/com/android/server/am/TEST_MAPPING +++ b/services/core/java/com/android/server/am/TEST_MAPPING @@ -157,7 +157,6 @@ ] }, { - "file_patterns": ["Broadcast.*"], "name": "CtsContentTestCases", "options": [ { "include-filter": "android.content.cts.BroadcastReceiverTest" }, diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 96c6be824561..55ac4cf37283 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -1350,41 +1350,57 @@ class UserController implements Handler.Callback { } /** - * For mDelayUserDataLocking mode, storage once unlocked is kept unlocked. - * Total number of unlocked user storage is limited by mMaxRunningUsers. - * If there are more unlocked users, evict and lock the least recently stopped user and - * lock that user's data. Regardless of the mode, ephemeral user is always locked - * immediately. + * Returns which user, if any, should be locked when the given user is stopped. + * + * For typical (non-mDelayUserDataLocking) devices and users, this will be the provided user. + * + * However, for some devices or users (based on {@link #canDelayDataLockingForUser(int)}), + * storage once unlocked is kept unlocked, even after the user is stopped, so the user to be + * locked (if any) may differ. + * + * For mDelayUserDataLocking devices, the total number of unlocked user storage is limited + * (currently by mMaxRunningUsers). If there are more unlocked users, evict and lock the least + * recently stopped user and lock that user's data. + * + * Regardless of the mode, ephemeral user is always locked immediately. * * @return user id to lock. UserHandler.USER_NULL will be returned if no user should be locked. */ @GuardedBy("mLock") private int updateUserToLockLU(@UserIdInt int userId, boolean allowDelayedLocking) { - int userIdToLock = userId; - // TODO: Decouple the delayed locking flows from mMaxRunningUsers or rename the property to - // state maximum running unlocked users specifically - if (canDelayDataLockingForUser(userIdToLock) && allowDelayedLocking - && !getUserInfo(userId).isEphemeral() - && !hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) { + if (!canDelayDataLockingForUser(userId) + || !allowDelayedLocking + || getUserInfo(userId).isEphemeral() + || hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) { + return userId; + } + + // Once we reach here, we are in a delayed locking scenario. + // Now, no user will be locked, unless the device's policy dictates we should based on the + // maximum of such users allowed for the device. + if (mDelayUserDataLocking) { // arg should be object, not index mLastActiveUsersForDelayedLocking.remove((Integer) userId); mLastActiveUsersForDelayedLocking.add(0, userId); int totalUnlockedUsers = mStartedUsers.size() + mLastActiveUsersForDelayedLocking.size(); + // TODO: Decouple the delayed locking flows from mMaxRunningUsers. These users aren't + // running so this calculation shouldn't be based on this parameter. Also note that + // that if these devices ever support background running users (such as profiles), the + // implementation is incorrect since starting such users can cause the max to be + // exceeded. if (totalUnlockedUsers > mMaxRunningUsers) { // should lock a user - userIdToLock = mLastActiveUsersForDelayedLocking.get( + final int userIdToLock = mLastActiveUsersForDelayedLocking.get( mLastActiveUsersForDelayedLocking.size() - 1); mLastActiveUsersForDelayedLocking .remove(mLastActiveUsersForDelayedLocking.size() - 1); - Slogf.i(TAG, "finishUserStopped, stopping user:" + userId - + " lock user:" + userIdToLock); - } else { - Slogf.i(TAG, "finishUserStopped, user:" + userId + ", skip locking"); - // do not lock - userIdToLock = UserHandle.USER_NULL; + Slogf.i(TAG, "finishUserStopped: should stop user " + userId + + " but should lock user " + userIdToLock); + return userIdToLock; } } - return userIdToLock; + Slogf.i(TAG, "finishUserStopped: should stop user " + userId + " but without any locking"); + return UserHandle.USER_NULL; } /** diff --git a/services/core/java/com/android/server/appop/LegacyAppOpStateParser.java b/services/core/java/com/android/server/appop/LegacyAppOpStateParser.java index a6d505021090..9ed3a99013ea 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 @@ class LegacyAppOpStateParser { */ 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 @@ class LegacyAppOpStateParser { } } 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 53255a0639fe..c59f4f7888ce 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.RINGER_MODE_VIBRATE; 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 @@ public class AudioService extends IAudioService.Stub 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 8d767313d5ab..3b5fa7f00891 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 static com.android.media.audio.Flags.dsaOverBtLeAudio; 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 @@ public class SpatializerHelper { 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/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index cb15abcc65fc..cd064ae38aa5 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -19,11 +19,11 @@ package com.android.server.devicestate; import static android.Manifest.permission.CONTROL_DEVICE_STATE; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.hardware.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS; import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE; import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE; -import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS; import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE; import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_EMULATED_STATE; import static com.android.server.devicestate.OverrideRequestController.FLAG_POWER_SAVE_ENABLED; @@ -40,6 +40,7 @@ import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.IProcessObserver; import android.content.Context; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateInfo; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateManagerInternal; diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java index 8c6068d89296..02c9bb3b9deb 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java @@ -18,6 +18,7 @@ package com.android.server.devicestate; import android.annotation.NonNull; import android.annotation.Nullable; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateRequest; import android.os.Binder; diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java index d5945f4e8b52..65b393ad94b9 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java @@ -21,6 +21,7 @@ import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STA import android.annotation.IntDef; import android.annotation.IntRange; +import android.hardware.devicestate.DeviceState; import android.util.Dumpable; import java.lang.annotation.Retention; diff --git a/services/core/java/com/android/server/devicestate/OverrideRequest.java b/services/core/java/com/android/server/devicestate/OverrideRequest.java index 20485c1ac102..d92629f77a95 100644 --- a/services/core/java/com/android/server/devicestate/OverrideRequest.java +++ b/services/core/java/com/android/server/devicestate/OverrideRequest.java @@ -18,6 +18,7 @@ package com.android.server.devicestate; import android.annotation.IntDef; import android.annotation.NonNull; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateRequest; import android.os.IBinder; diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java index f5f2fa8cabdc..6c3fd83d17a0 100644 --- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java +++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java @@ -18,6 +18,7 @@ package com.android.server.devicestate; import android.annotation.IntDef; import android.annotation.NonNull; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateRequest; import android.os.IBinder; import android.util.Slog; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index c8c0482f5a9d..a100fe06c407 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 @@ final class InputMethodBindingController { @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 @@ final class InputMethodBindingController { /** * 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 @@ final class InputMethodBindingController { 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 776787c14be3..2d7fcd6ce7e6 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -1981,7 +1981,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @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); @@ -1994,14 +1995,17 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // 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()); } } @@ -3561,8 +3565,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME); mCurStatsToken = null; - if (!Flags.useHandwritingListenerForTooltype() - && lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) { + if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) { curMethod.updateEditorToolType(lastClickToolType); } mVisibilityApplier.performShowIme(windowToken, statsToken, 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 2cd8fe0ac43e..f60f55c11935 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 com.android.server.SystemService; 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 @@ public final class MediaMetricsManagerService extends SystemService { 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 @@ public final class MediaMetricsManagerService extends SystemService { .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 @@ public final class MediaMetricsManagerService extends SystemService { } } + 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 3507d2d56cbd..6936ae01d150 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -6309,6 +6309,10 @@ public class NotificationManagerService extends SystemService { 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 +6410,24 @@ public class NotificationManagerService extends SystemService { } } + 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(); diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java index 88d23ce3f9a1..82c5733655b0 100644 --- a/services/core/java/com/android/server/notification/ZenLog.java +++ b/services/core/java/com/android/server/notification/ZenLog.java @@ -28,6 +28,7 @@ import android.service.notification.IConditionProvider; import android.service.notification.NotificationListenerService; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeDiff; +import android.util.LocalLog; import android.util.Log; import android.util.Slog; @@ -37,26 +38,16 @@ import java.util.Date; import java.util.List; public class ZenLog { - private static final String TAG = "ZenLog"; - // the ZenLog is *very* verbose, so be careful about setting this to true - private static final boolean DEBUG = false; private static final int SIZE = Build.IS_DEBUGGABLE ? 200 : 100; - private static final long[] TIMES = new long[SIZE]; - private static final int[] TYPES = new int[SIZE]; - private static final String[] MSGS = new String[SIZE]; - - private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); + private static final LocalLog STATE_CHANGES = new LocalLog(SIZE); + private static final LocalLog INTERCEPTION_EVENTS = new LocalLog(SIZE); private static final int TYPE_INTERCEPTED = 1; - private static final int TYPE_ALLOW_DISABLE = 2; private static final int TYPE_SET_RINGER_MODE_EXTERNAL = 3; private static final int TYPE_SET_RINGER_MODE_INTERNAL = 4; - private static final int TYPE_DOWNTIME = 5; private static final int TYPE_SET_ZEN_MODE = 6; - private static final int TYPE_UPDATE_ZEN_MODE = 7; - private static final int TYPE_EXIT_CONDITION = 8; private static final int TYPE_SUBSCRIBE = 9; private static final int TYPE_UNSUBSCRIBE = 10; private static final int TYPE_CONFIG = 11; @@ -71,9 +62,6 @@ public class ZenLog { private static final int TYPE_CHECK_REPEAT_CALLER = 20; private static final int TYPE_ALERT_ON_UPDATED_INTERCEPT = 21; - private static int sNext; - private static int sSize; - public static void traceIntercepted(NotificationRecord record, String reason) { append(TYPE_INTERCEPTED, record.getKey() + "," + reason); } @@ -104,10 +92,6 @@ public class ZenLog { ringerModeToString(ringerModeExternalOut)); } - public static void traceDowntimeAutotrigger(String result) { - append(TYPE_DOWNTIME, result); - } - public static void traceSetZenMode(int zenMode, String reason) { append(TYPE_SET_ZEN_MODE, zenModeToString(zenMode) + "," + reason); } @@ -120,21 +104,12 @@ public class ZenLog { append(TYPE_SET_CONSOLIDATED_ZEN_POLICY, policy.toString() + "," + reason); } - public static void traceUpdateZenMode(int fromMode, int toMode) { - append(TYPE_UPDATE_ZEN_MODE, zenModeToString(fromMode) + " -> " + zenModeToString(toMode)); - } - - public static void traceExitCondition(Condition c, ComponentName component, String reason) { - append(TYPE_EXIT_CONDITION, c + "," + componentToString(component) + "," + reason); - } public static void traceSetNotificationPolicy(String pkg, int targetSdk, NotificationManager.Policy policy) { String policyLog = "pkg=" + pkg + " targetSdk=" + targetSdk + " NotificationPolicy=" + policy.toString(); append(TYPE_SET_NOTIFICATION_POLICY, policyLog); - // TODO(b/180205791): remove when we can better surface apps that are changing policy - Log.d(TAG, "Zen Policy Changed: " + policyLog); } public static void traceSubscribe(Uri uri, IConditionProvider provider, RemoteException e) { @@ -145,13 +120,14 @@ public class ZenLog { append(TYPE_UNSUBSCRIBE, uri + "," + subscribeResult(provider, e)); } - public static void traceConfig(String reason, ZenModeConfig oldConfig, - ZenModeConfig newConfig) { + public static void traceConfig(String reason, ComponentName triggeringComponent, + ZenModeConfig oldConfig, ZenModeConfig newConfig, int callingUid) { ZenModeDiff.ConfigDiff diff = new ZenModeDiff.ConfigDiff(oldConfig, newConfig); if (diff == null || !diff.hasDiff()) { append(TYPE_CONFIG, reason + " no changes"); } else { append(TYPE_CONFIG, reason + + " - " + triggeringComponent + " : " + callingUid + ",\n" + (newConfig != null ? newConfig.toString() : null) + ",\n" + diff); } @@ -204,13 +180,9 @@ public class ZenLog { private static String typeToString(int type) { switch (type) { case TYPE_INTERCEPTED: return "intercepted"; - case TYPE_ALLOW_DISABLE: return "allow_disable"; case TYPE_SET_RINGER_MODE_EXTERNAL: return "set_ringer_mode_external"; case TYPE_SET_RINGER_MODE_INTERNAL: return "set_ringer_mode_internal"; - case TYPE_DOWNTIME: return "downtime"; case TYPE_SET_ZEN_MODE: return "set_zen_mode"; - case TYPE_UPDATE_ZEN_MODE: return "update_zen_mode"; - case TYPE_EXIT_CONDITION: return "exit_condition"; case TYPE_SUBSCRIBE: return "subscribe"; case TYPE_UNSUBSCRIBE: return "unsubscribe"; case TYPE_CONFIG: return "config"; @@ -278,30 +250,27 @@ public class ZenLog { } private static void append(int type, String msg) { - synchronized(MSGS) { - TIMES[sNext] = System.currentTimeMillis(); - TYPES[sNext] = type; - MSGS[sNext] = msg; - sNext = (sNext + 1) % SIZE; - if (sSize < SIZE) { - sSize++; + if (type == TYPE_INTERCEPTED || type == TYPE_NOT_INTERCEPTED + || type == TYPE_CHECK_REPEAT_CALLER || type == TYPE_RECORD_CALLER + || type == TYPE_MATCHES_CALL_FILTER || type == TYPE_ALERT_ON_UPDATED_INTERCEPT) { + synchronized (INTERCEPTION_EVENTS) { + INTERCEPTION_EVENTS.log(typeToString(type) + ": " +msg); + } + } else { + synchronized (STATE_CHANGES) { + STATE_CHANGES.log(typeToString(type) + ": " +msg); } } - if (DEBUG) Slog.d(TAG, typeToString(type) + ": " + msg); } public static void dump(PrintWriter pw, String prefix) { - synchronized(MSGS) { - final int start = (sNext - sSize + SIZE) % SIZE; - for (int i = 0; i < sSize; i++) { - final int j = (start + i) % SIZE; - pw.print(prefix); - pw.print(FORMAT.format(new Date(TIMES[j]))); - pw.print(' '); - pw.print(typeToString(TYPES[j])); - pw.print(": "); - pw.println(MSGS[j]); - } + synchronized (INTERCEPTION_EVENTS) { + pw.printf(prefix + "Interception Events:\n"); + INTERCEPTION_EVENTS.dump(prefix, pw); + } + synchronized (STATE_CHANGES) { + pw.printf(prefix + "State Changes:\n"); + STATE_CHANGES.dump(prefix, pw); } } } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index aebd28a549ca..1c20b2df4283 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -1713,7 +1713,7 @@ public class ZenModeHelper { mConfigs.put(config.user, config); } if (DEBUG) Log.d(TAG, "setConfigLocked reason=" + reason, new Throwable()); - ZenLog.traceConfig(reason, mConfig, config); + ZenLog.traceConfig(reason, triggeringComponent, mConfig, config, callingUid); // send some broadcasts Policy newPolicy = getNotificationPolicy(config); diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java index 25a39cc8456f..86d05d92c95b 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 @@ final class IdmapManager { 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 b9464d96a019..872952299055 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 @@ public final class OverlayManagerService extends SystemService { 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 @@ public final class OverlayManagerService extends SystemService { // 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 @@ public final class OverlayManagerService extends SystemService { */ @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 @@ public final class OverlayManagerService extends SystemService { 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 @@ public final class OverlayManagerService extends SystemService { 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 972c78db9460..c1b6ccc7e25c 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 @@ final class OverlayManagerServiceImpl { 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 eae614ac9e77..b8b49f3eed2f 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.ArrayList; 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 @@ final class OverlayManagerSettings { 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/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java index 1dd790502486..18ba2cf1405e 100644 --- a/services/core/java/com/android/server/pm/AppDataHelper.java +++ b/services/core/java/com/android/server/pm/AppDataHelper.java @@ -503,21 +503,24 @@ public class AppDataHelper { private void assertPackageStorageValid(@NonNull Computer snapshot, String volumeUuid, String packageName, int userId) throws PackageManagerException { final PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName); - final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId); if (packageState == null) { throw PackageManagerException.ofInternalError("Package " + packageName + " is unknown", PackageManagerException.INTERNAL_ERROR_STORAGE_INVALID_PACKAGE_UNKNOWN); - } else if (!TextUtils.equals(volumeUuid, packageState.getVolumeUuid())) { + } + if (!TextUtils.equals(volumeUuid, packageState.getVolumeUuid())) { throw PackageManagerException.ofInternalError( "Package " + packageName + " found on unknown volume " + volumeUuid + "; expected volume " + packageState.getVolumeUuid(), PackageManagerException.INTERNAL_ERROR_STORAGE_INVALID_VOLUME_UNKNOWN); - } else if (!userState.isInstalled() && !userState.dataExists()) { + } + final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId); + if (!userState.isInstalled() && !userState.dataExists()) { throw PackageManagerException.ofInternalError( "Package " + packageName + " not installed for user " + userId + " or was deleted without DELETE_KEEP_DATA", PackageManagerException.INTERNAL_ERROR_STORAGE_INVALID_NOT_INSTALLED_FOR_USER); - } else if (packageState.getPkg() != null + } + if (packageState.getPkg() != null && !shouldHaveAppStorage(packageState.getPkg())) { throw PackageManagerException.ofInternalError( "Package " + packageName + " shouldn't have storage", diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index db5acc2eda09..a1dac0456ff9 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 @@ final class InstallPackageHelper { + "; 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 796edde7d9bd..f222fe9add0f 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 @@ public class UserManagerService extends IUserManager.Stub { 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/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java index afcf5a094bd4..76952b30c1e9 100644 --- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java +++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java @@ -29,6 +29,7 @@ import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; +import android.hardware.devicestate.DeviceState; import android.os.Environment; import android.os.PowerManager; import android.util.ArrayMap; @@ -40,7 +41,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import com.android.server.LocalServices; -import com.android.server.devicestate.DeviceState; import com.android.server.devicestate.DeviceStateProvider; import com.android.server.input.InputManagerInternal; import com.android.server.policy.devicestate.config.Conditions; 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 00036e48891f..af4da812d79e 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 @@ public class BatteryStatsImpl extends BatteryStats { 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/stats/pull/AggregatedMobileDataStatsPuller.java b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java index 0de73a5a30f6..b0dcf95b3f5d 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 android.util.StatsEvent; 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 @@ class AggregatedMobileDataStatsPuller { 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 @@ class AggregatedMobileDataStatsPuller { } 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 f4fb1a108663..1bc635bd9ca3 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 @@ package com.android.server.webkit; 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.WebViewFactory; 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 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { 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 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { 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 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { 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 640c9dc21a53..dcc68a7e3190 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { 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/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java index 6a3cf43438fd..a3e28693cb21 100644 --- a/services/core/java/com/android/server/wm/DragDropController.java +++ b/services/core/java/com/android/server/wm/DragDropController.java @@ -16,6 +16,9 @@ package com.android.server.wm; +import static android.view.View.DRAG_FLAG_GLOBAL; +import static android.view.View.DRAG_FLAG_GLOBAL_SAME_APPLICATION; + import static com.android.input.flags.Flags.enablePointerChoreographer; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; @@ -30,15 +33,20 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.RemoteException; import android.util.Slog; import android.view.Display; +import android.view.DragEvent; import android.view.IWindow; import android.view.InputDevice; import android.view.PointerIcon; import android.view.SurfaceControl; import android.view.View; import android.view.accessibility.AccessibilityManager; +import android.window.IUnhandledDragCallback; +import android.window.IUnhandledDragListener; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.wm.WindowManagerInternal.IDragDropCallback; import java.util.Objects; @@ -59,6 +67,7 @@ class DragDropController { static final int MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT = 1; static final int MSG_ANIMATION_END = 2; static final int MSG_REMOVE_DRAG_SURFACE_TIMEOUT = 3; + static final int MSG_UNHANDLED_DROP_LISTENER_TIMEOUT = 4; /** * Drag state per operation. @@ -72,6 +81,21 @@ class DragDropController { private WindowManagerService mService; private final Handler mHandler; + // The unhandled drag listener for handling cross-window drags that end with no target window + private IUnhandledDragListener mUnhandledDragListener; + private final IBinder.DeathRecipient mUnhandledDragListenerDeathRecipient = + new IBinder.DeathRecipient() { + @Override + public void binderDied() { + synchronized (mService.mGlobalLock) { + if (hasPendingUnhandledDropCallback()) { + onUnhandledDropCallback(false /* consumedByListeners */); + } + setUnhandledDragListener(null); + } + } + }; + /** * Callback which is used to sync drag state with the vendor-specific code. */ @@ -83,10 +107,16 @@ class DragDropController { mHandler = new DragHandler(service, looper); } + @VisibleForTesting + Handler getHandler() { + return mHandler; + } + boolean dragDropActiveLocked() { return mDragState != null && !mDragState.isClosing(); } + @VisibleForTesting boolean dragSurfaceRelinquishedToDropTarget() { return mDragState != null && mDragState.mRelinquishDragSurfaceToDropTarget; } @@ -96,6 +126,32 @@ class DragDropController { mCallback.set(callback); } + /** + * Sets the listener for unhandled cross-window drags. + */ + public void setUnhandledDragListener(IUnhandledDragListener listener) { + if (mUnhandledDragListener != null && mUnhandledDragListener.asBinder() != null) { + mUnhandledDragListener.asBinder().unlinkToDeath( + mUnhandledDragListenerDeathRecipient, 0); + } + mUnhandledDragListener = listener; + if (listener != null && listener.asBinder() != null) { + try { + mUnhandledDragListener.asBinder().linkToDeath( + mUnhandledDragListenerDeathRecipient, 0); + } catch (RemoteException e) { + mUnhandledDragListener = null; + } + } + } + + /** + * Returns whether there is an unhandled drag listener set. + */ + boolean hasUnhandledDragListener() { + return mUnhandledDragListener != null; + } + void sendDragStartedIfNeededLocked(WindowState window) { mDragState.sendDragStartedIfNeededLocked(window); } @@ -247,6 +303,10 @@ class DragDropController { } } + /** + * This is called from the drop target window that received ACTION_DROP + * (see DragState#reportDropWindowLock()). + */ void reportDropResult(IWindow window, boolean consumed) { IBinder token = window.asBinder(); if (DEBUG_DRAG) { @@ -273,22 +333,89 @@ class DragDropController { // so be sure to halt the timeout even if the later WindowState // lookup fails. mHandler.removeMessages(MSG_DRAG_END_TIMEOUT, window.asBinder()); + WindowState callingWin = mService.windowForClientLocked(null, window, false); if (callingWin == null) { Slog.w(TAG_WM, "Bad result-reporting window " + window); return; // !!! TODO: throw here? } - mDragState.mDragResult = consumed; - mDragState.mRelinquishDragSurfaceToDropTarget = consumed - && mDragState.targetInterceptsGlobalDrag(callingWin); - mDragState.endDragLocked(); + // If the drop was not consumed by the target window, then check if it should be + // consumed by the system unhandled drag listener + if (!consumed && notifyUnhandledDrop(mDragState.mUnhandledDropEvent, + "window-drop")) { + // If the unhandled drag listener is notified, then defer ending the drag until + // the listener calls back + return; + } + + final boolean relinquishDragSurfaceToDropTarget = + consumed && mDragState.targetInterceptsGlobalDrag(callingWin); + mDragState.endDragLocked(consumed, relinquishDragSurfaceToDropTarget); } } finally { mCallback.get().postReportDropResult(); } } + /** + * Notifies the unhandled drag listener if needed. + * @return whether the listener was notified and subsequent drag completion should be deferred + * until the listener calls back + */ + boolean notifyUnhandledDrop(DragEvent dropEvent, String reason) { + final boolean isLocalDrag = + (mDragState.mFlags & (DRAG_FLAG_GLOBAL_SAME_APPLICATION | DRAG_FLAG_GLOBAL)) == 0; + if (!com.android.window.flags.Flags.delegateUnhandledDrags() + || mUnhandledDragListener == null + || isLocalDrag) { + // Skip if the flag is disabled, there is no unhandled-drag listener, or if this is a + // purely local drag + if (DEBUG_DRAG) Slog.d(TAG_WM, "Skipping unhandled listener " + + "(listener=" + mUnhandledDragListener + ", flags=" + mDragState.mFlags + ")"); + return false; + } + if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DROP to unhandled listener (" + reason + ")"); + try { + // Schedule timeout for the unhandled drag listener to call back + sendTimeoutMessage(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT, null, DRAG_TIMEOUT_MS); + mUnhandledDragListener.onUnhandledDrop(dropEvent, new IUnhandledDragCallback.Stub() { + @Override + public void notifyUnhandledDropComplete(boolean consumedByListener) { + if (DEBUG_DRAG) Slog.d(TAG_WM, "Unhandled listener finished handling DROP"); + synchronized (mService.mGlobalLock) { + onUnhandledDropCallback(consumedByListener); + } + } + }); + return true; + } catch (RemoteException e) { + Slog.e(TAG_WM, "Failed to call unhandled drag listener", e); + return false; + } + } + + /** + * Called when the unhandled drag listener has completed handling the drop + * (if it was notififed). + */ + @VisibleForTesting + void onUnhandledDropCallback(boolean consumedByListener) { + mHandler.removeMessages(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT, null); + // If handled, then the listeners assume responsibility of cleaning up the drag surface + mDragState.mDragResult = consumedByListener; + mDragState.mRelinquishDragSurfaceToDropTarget = consumedByListener; + mDragState.closeLocked(); + } + + /** + * Returns whether we are currently waiting for the unhandled drag listener to callback after + * it was notified of an unhandled drag. + */ + boolean hasPendingUnhandledDropCallback() { + return mHandler.hasMessages(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT); + } + void cancelDragAndDrop(IBinder dragToken, boolean skipAnimation) { if (DEBUG_DRAG) { Slog.d(TAG_WM, "cancelDragAndDrop"); @@ -436,8 +563,8 @@ class DragDropController { synchronized (mService.mGlobalLock) { // !!! TODO: ANR the drag-receiving app if (mDragState != null) { - mDragState.mDragResult = false; - mDragState.endDragLocked(); + mDragState.endDragLocked(false /* consumed */, + false /* relinquishDragSurfaceToDropTarget */); } } break; @@ -473,6 +600,13 @@ class DragDropController { } break; } + + case MSG_UNHANDLED_DROP_LISTENER_TIMEOUT: { + synchronized (mService.mGlobalLock) { + onUnhandledDropCallback(false /* consumedByListener */); + } + break; + } } } } diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index d302f0641b58..76038b945288 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -147,6 +147,11 @@ class DragState { */ private boolean mIsClosing; + // Stores the last drop event which was reported to a valid drop target window, or null + // otherwise. This drop event will contain private info and should only be consumed by the + // unhandled drag listener. + DragEvent mUnhandledDropEvent; + DragState(WindowManagerService service, DragDropController controller, IBinder token, SurfaceControl surface, int flags, IBinder localWin) { mService = service; @@ -287,14 +292,54 @@ class DragState { mData = null; mThumbOffsetX = mThumbOffsetY = 0; mNotifiedWindows = null; + if (mUnhandledDropEvent != null) { + mUnhandledDropEvent.recycle(); + mUnhandledDropEvent = null; + } // Notifies the controller that the drag state is closed. mDragDropController.onDragStateClosedLocked(this); } /** + * Creates the drop event for this drag gesture. If `touchedWin` is null, then the drop event + * will be created for dispatching to the unhandled drag and the drag surface will be provided + * as a part of the dispatched event. + */ + private DragEvent createDropEvent(float x, float y, @Nullable WindowState touchedWin, + boolean includeDragSurface) { + if (touchedWin != null) { + final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid()); + final DragAndDropPermissionsHandler dragAndDropPermissions; + if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0 + && mData != null) { + dragAndDropPermissions = new DragAndDropPermissionsHandler(mService.mGlobalLock, + mData, + mUid, + touchedWin.getOwningPackage(), + mFlags & DRAG_FLAGS_URI_PERMISSIONS, + mSourceUserId, + targetUserId); + } else { + dragAndDropPermissions = null; + } + if (mSourceUserId != targetUserId) { + if (mData != null) { + mData.fixUris(mSourceUserId); + } + } + return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mData, + targetInterceptsGlobalDrag(touchedWin), dragAndDropPermissions); + } else { + return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mData, + includeDragSurface /* includeDragSurface */, null /* dragAndDropPermissions */); + } + } + + /** * Notify the drop target and tells it about the data. If the drop event is not sent to the - * target, invokes {@code endDragLocked} immediately. + * target, invokes {@code endDragLocked} after the unhandled drag listener gets a chance to + * handle the drop. */ boolean reportDropWindowLock(IBinder token, float x, float y) { if (mAnimator != null) { @@ -302,41 +347,27 @@ class DragState { } final WindowState touchedWin = mService.mInputToWindowMap.get(token); + final DragEvent unhandledDropEvent = createDropEvent(x, y, null /* touchedWin */, + true /* includePrivateInfo */); if (!isWindowNotified(touchedWin)) { - // "drop" outside a valid window -- no recipient to apply a - // timeout to, and we can send the drag-ended message immediately. - mDragResult = false; - endDragLocked(); + // Delegate to the unhandled drag listener as a first pass + if (mDragDropController.notifyUnhandledDrop(unhandledDropEvent, "unhandled-drop")) { + // The unhandled drag listener will call back to notify whether it has consumed + // the drag, so return here + return true; + } + + // "drop" outside a valid window -- no recipient to apply a timeout to, and we can send + // the drag-ended message immediately. + endDragLocked(false /* consumed */, false /* relinquishDragSurfaceToDropTarget */); if (DEBUG_DRAG) Slog.d(TAG_WM, "Drop outside a valid window " + touchedWin); return false; } if (DEBUG_DRAG) Slog.d(TAG_WM, "sending DROP to " + touchedWin); - final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid()); - - final DragAndDropPermissionsHandler dragAndDropPermissions; - if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0 - && mData != null) { - dragAndDropPermissions = new DragAndDropPermissionsHandler(mService.mGlobalLock, - mData, - mUid, - touchedWin.getOwningPackage(), - mFlags & DRAG_FLAGS_URI_PERMISSIONS, - mSourceUserId, - targetUserId); - } else { - dragAndDropPermissions = null; - } - if (mSourceUserId != targetUserId) { - if (mData != null) { - mData.fixUris(mSourceUserId); - } - } final IBinder clientToken = touchedWin.mClient.asBinder(); - final DragEvent event = obtainDragEvent(DragEvent.ACTION_DROP, x, y, - mData, targetInterceptsGlobalDrag(touchedWin), - dragAndDropPermissions); + final DragEvent event = createDropEvent(x, y, touchedWin, false /* includePrivateInfo */); try { touchedWin.mClient.dispatchDragEvent(event); @@ -345,7 +376,7 @@ class DragState { DragDropController.DRAG_TIMEOUT_MS); } catch (RemoteException e) { Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin); - endDragLocked(); + endDragLocked(false /* consumed */, false /* relinquishDragSurfaceToDropTarget */); return false; } finally { if (MY_PID != touchedWin.mSession.mPid) { @@ -353,6 +384,7 @@ class DragState { } } mToken = clientToken; + mUnhandledDropEvent = unhandledDropEvent; return true; } @@ -478,6 +510,9 @@ class DragState { boolean containsAppExtras) { final boolean interceptsGlobalDrag = targetInterceptsGlobalDrag(newWin); if (mDragInProgress && isValidDropTarget(newWin, containsAppExtras, interceptsGlobalDrag)) { + if (DEBUG_DRAG) { + Slog.d(TAG_WM, "Sending DRAG_STARTED to new window " + newWin); + } // Only allow the extras to be dispatched to a global-intercepting drag target ClipData data = interceptsGlobalDrag ? mData.copyForTransferWithActivityInfo() : null; DragEvent event = obtainDragEvent(DragEvent.ACTION_DRAG_STARTED, @@ -523,14 +558,25 @@ class DragState { return false; } if (!targetWin.isPotentialDragTarget(interceptsGlobalDrag)) { + // Window should not be a target return false; } - if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0 || !targetWindowSupportsGlobalDrag(targetWin)) { + final boolean isGlobalSameAppDrag = (mFlags & View.DRAG_FLAG_GLOBAL_SAME_APPLICATION) != 0; + final boolean isGlobalDrag = (mFlags & View.DRAG_FLAG_GLOBAL) != 0; + final boolean isAnyGlobalDrag = isGlobalDrag || isGlobalSameAppDrag; + if (!isAnyGlobalDrag || !targetWindowSupportsGlobalDrag(targetWin)) { // Drag is limited to the current window. if (!isLocalWindow) { return false; } } + if (isGlobalSameAppDrag) { + // Drag is limited to app windows from the same uid or windows that can intercept global + // drag + if (!interceptsGlobalDrag && mUid != targetWin.getUid()) { + return false; + } + } return interceptsGlobalDrag || mCrossProfileCopyAllowed @@ -547,7 +593,10 @@ class DragState { /** * @return whether the given window {@param targetWin} can intercept global drags. */ - public boolean targetInterceptsGlobalDrag(WindowState targetWin) { + public boolean targetInterceptsGlobalDrag(@Nullable WindowState targetWin) { + if (targetWin == null) { + return false; + } return (targetWin.mAttrs.privateFlags & PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP) != 0; } @@ -561,9 +610,6 @@ class DragState { if (isWindowNotified(newWin)) { return; } - if (DEBUG_DRAG) { - Slog.d(TAG_WM, "need to send DRAG_STARTED to new window " + newWin); - } sendDragStartedLocked(newWin, mCurrentX, mCurrentY, containsApplicationExtras(mDataDescription)); } @@ -578,7 +624,13 @@ class DragState { return false; } - void endDragLocked() { + /** + * Ends the current drag, animating the drag surface back to the source if the drop was not + * consumed by the receiving window. + */ + void endDragLocked(boolean dropConsumed, boolean relinquishDragSurfaceToDropTarget) { + mDragResult = dropConsumed; + mRelinquishDragSurfaceToDropTarget = relinquishDragSurfaceToDropTarget; if (mAnimator != null) { return; } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 80894b201942..61fde5e31e8f 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 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } 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/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 4ea76e128584..de8d9f96453d 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -310,6 +310,7 @@ import android.window.IScreenRecordingCallback; import android.window.ISurfaceSyncGroupCompletedListener; import android.window.ITaskFpsCallback; import android.window.ITrustedPresentationListener; +import android.window.IUnhandledDragListener; import android.window.InputTransferToken; import android.window.ScreenCapture; import android.window.SystemPerformanceHinter; @@ -10026,4 +10027,16 @@ public class WindowManagerService extends IWindowManager.Stub void onProcessActivityVisibilityChanged(int uid, boolean visible) { mScreenRecordingCallbackController.onProcessActivityVisibilityChanged(uid, visible); } + + /** + * Sets the listener to be called back when a cross-window drag and drop operation is unhandled + * (ie. not handled by any window which can handle the drag). + */ + @Override + public void setUnhandledDragListener(IUnhandledDragListener listener) throws RemoteException { + mAtmService.enforceTaskPermission("setUnhandledDragListener"); + synchronized (mGlobalLock) { + mDragDropController.setUnhandledDragListener(listener); + } + } } diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java index 8b22718786e5..bc264a46f051 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java @@ -16,11 +16,12 @@ package com.android.server.policy; -import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS; -import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP; -import static com.android.server.devicestate.DeviceState.FLAG_EMULATED_ONLY; -import static com.android.server.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE; -import static com.android.server.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL; +import static android.hardware.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS; +import static android.hardware.devicestate.DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP; +import static android.hardware.devicestate.DeviceState.FLAG_EMULATED_ONLY; +import static android.hardware.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE; +import static android.hardware.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL; + import static com.android.server.policy.BookStyleStateTransitions.DEFAULT_STATE_TRANSITIONS; import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createConfig; import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createTentModeClosedState; @@ -36,7 +37,6 @@ import com.android.server.devicestate.DeviceStatePolicy; import com.android.server.devicestate.DeviceStateProvider; import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration; import com.android.server.policy.feature.flags.FeatureFlags; -import com.android.server.policy.feature.flags.FeatureFlagsImpl; import java.io.PrintWriter; import java.util.function.Predicate; diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java index 021a667113e7..bf2619b52275 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java @@ -34,6 +34,7 @@ import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; +import android.hardware.devicestate.DeviceState; import android.hardware.display.DisplayManager; import android.os.Handler; import android.os.Looper; @@ -48,7 +49,6 @@ import android.view.Display; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; -import com.android.server.devicestate.DeviceState; import com.android.server.devicestate.DeviceStateProvider; import com.android.server.policy.feature.flags.FeatureFlags; import com.android.server.policy.feature.flags.FeatureFlagsImpl; diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java index 04cebab107ee..930f4a61a453 100644 --- a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java +++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java @@ -51,6 +51,7 @@ import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorManager; +import android.hardware.devicestate.DeviceState; import android.hardware.display.DisplayManager; import android.hardware.input.InputSensorInfo; import android.os.Handler; @@ -58,7 +59,6 @@ import android.os.PowerManager; import android.testing.AndroidTestingRunner; import android.view.Display; -import com.android.server.devicestate.DeviceState; import com.android.server.devicestate.DeviceStateProvider.Listener; import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration; import com.android.server.policy.feature.flags.FakeFeatureFlagsImpl; diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index c55d70927f8e..dbbfb039af78 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -48,7 +48,6 @@ import android.content.pm.PackageManagerInternal; 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 @@ public final class SystemServer implements Dumpable { 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 @@ public final class SystemServer implements Dumpable { 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/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java index 885ed355011b..b9f00d7979c2 100644 --- a/services/people/java/com/android/server/people/PeopleService.java +++ b/services/people/java/com/android/server/people/PeopleService.java @@ -38,6 +38,7 @@ import android.content.pm.ShortcutInfo; import android.os.Binder; import android.os.CancellationSignal; import android.os.IBinder; +import android.os.IRemoteCallback; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; @@ -474,6 +475,10 @@ public class PeopleService extends SystemService { getDataManager().restore(userId, payload); } + @Override + public void requestServiceFeatures(AppPredictionSessionId sessionId, + IRemoteCallback callback) {} + @VisibleForTesting SessionInfo getSessionInfo(AppPredictionSessionId sessionId) { return mSessions.get(sessionId); 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 2f5c1092e67b..67f66de71d39 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 @@ class PermissionService(private val service: AccessCheckingService) : /** 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 c0f0ce047da6..b363f545760e 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 656bc71eebca..7bbcd50cdf1f 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 @@ public final class UserManagerServiceTest { 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/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java index 395b3aac8fd5..d36b55345418 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 @@ public class BatteryStatsHistoryIteratorTest { 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 @@ public class BatteryStatsHistoryIteratorTest { 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 @@ public class BatteryStatsHistoryIteratorTest { } 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 @@ public class BatteryStatsHistoryIteratorTest { 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/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 26934d8e5d6e..4307ec5aa7e1 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -682,19 +682,19 @@ public class UserControllerTest { /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); setUpAndStartUserInBackground(TEST_USER_ID); - assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* delayedLocking= */ true, + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* allowDelayedLocking= */ true, /* keyEvictedCallback= */ null, /* expectLocking= */ true); setUpAndStartUserInBackground(TEST_USER_ID1); - assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true, + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true, /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true); setUpAndStartUserInBackground(TEST_USER_ID2); - assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ false, + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ false, /* keyEvictedCallback= */ null, /* expectLocking= */ true); setUpAndStartUserInBackground(TEST_USER_ID3); - assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* delayedLocking= */ false, + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* allowDelayedLocking= */ false, /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true); } @@ -739,21 +739,21 @@ public class UserControllerTest { mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true); - // delayedLocking set and no KeyEvictedCallback, so it should not lock. + // allowDelayedLocking set and no KeyEvictedCallback, so it should not lock. setUpAndStartUserInBackground(TEST_USER_ID); - assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* delayedLocking= */ true, + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* allowDelayedLocking= */ true, /* keyEvictedCallback= */ null, /* expectLocking= */ false); setUpAndStartUserInBackground(TEST_USER_ID1); - assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true, + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true, /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true); setUpAndStartUserInBackground(TEST_USER_ID2); - assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ false, + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ false, /* keyEvictedCallback= */ null, /* expectLocking= */ true); setUpAndStartUserInBackground(TEST_USER_ID3); - assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* delayedLocking= */ false, + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* allowDelayedLocking= */ false, /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true); } @@ -843,7 +843,7 @@ public class UserControllerTest { mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE); - assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true, + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true, /* keyEvictedCallback */ null, /* expectLocking= */ false); } @@ -855,19 +855,20 @@ public class UserControllerTest { mSetFlagsRule.disableFlags( android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE); - assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true, + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true, /* keyEvictedCallback */ null, /* expectLocking= */ true); mSetFlagsRule.disableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); mSetFlagsRule.enableFlags( android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_PRIVATE); - assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ true, + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ true, /* keyEvictedCallback */ null, /* expectLocking= */ true); } + /** Delayed-locking users (as opposed to devices) have no limits on how many can be unlocked. */ @Test - public void testStopPrivateProfileWithDelayedLocking_maxRunningUsersBreached() + public void testStopPrivateProfileWithDelayedLocking_imperviousToNumberOfRunningUsers() throws Exception { mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true, /* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false); @@ -875,10 +876,14 @@ public class UserControllerTest { android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE); setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_MANAGED); - assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true, - /* keyEvictedCallback */ null, /* expectLocking= */ true); + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true, + /* keyEvictedCallback */ null, /* expectLocking= */ false); } + /** + * Tests that when a device/user (managed profile) does not permit delayed locking, then + * even if allowDelayedLocking is true, the user will still be locked. + */ @Test public void testStopManagedProfileWithDelayedLocking() throws Exception { mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true, @@ -886,7 +891,7 @@ public class UserControllerTest { mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED); - assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true, + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true, /* keyEvictedCallback */ null, /* expectLocking= */ true); } @@ -1087,29 +1092,29 @@ public class UserControllerTest { mUserStates.put(userId, mUserController.getStartedUserState(userId)); } - private void assertUserLockedOrUnlockedAfterStopping(int userId, boolean delayedLocking, + private void assertUserLockedOrUnlockedAfterStopping(int userId, boolean allowDelayedLocking, KeyEvictedCallback keyEvictedCallback, boolean expectLocking) throws Exception { - int r = mUserController.stopUser(userId, /* force= */ true, /* delayedLocking= */ - delayedLocking, null, keyEvictedCallback); + int r = mUserController.stopUser(userId, /* force= */ true, /* allowDelayedLocking= */ + allowDelayedLocking, null, keyEvictedCallback); assertThat(r).isEqualTo(ActivityManager.USER_OP_SUCCESS); - assertUserLockedOrUnlockedState(userId, delayedLocking, expectLocking); + assertUserLockedOrUnlockedState(userId, allowDelayedLocking, expectLocking); } private void assertProfileLockedOrUnlockedAfterStopping(int userId, boolean expectLocking) throws Exception { boolean profileStopped = mUserController.stopProfile(userId); assertThat(profileStopped).isTrue(); - assertUserLockedOrUnlockedState(userId, /* delayedLocking= */ false, expectLocking); + assertUserLockedOrUnlockedState(userId, /* allowDelayedLocking= */ false, expectLocking); } - private void assertUserLockedOrUnlockedState(int userId, boolean delayedLocking, + private void assertUserLockedOrUnlockedState(int userId, boolean allowDelayedLocking, boolean expectLocking) throws InterruptedException, RemoteException { // fake all interim steps UserState ussUser = mUserStates.get(userId); ussUser.setState(UserState.STATE_SHUTDOWN); // Passing delayedLocking invalidates incorrect internal data passing but currently there is // no easy way to get that information passed through lambda. - mUserController.finishUserStopped(ussUser, delayedLocking); + mUserController.finishUserStopped(ussUser, allowDelayedLocking); waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS); verify(mInjector.mStorageManagerMock, times(expectLocking ? 1 : 0)) .lockCeStorage(userId); diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java index fa3936443f31..b7050771d2e4 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java @@ -31,6 +31,7 @@ import static org.testng.Assert.assertNull; import static org.testng.Assert.assertThrows; import android.annotation.NonNull; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateInfo; import android.hardware.devicestate.DeviceStateRequest; import android.hardware.devicestate.IDeviceStateManagerCallback; @@ -72,7 +73,8 @@ public final class DeviceStateManagerServiceTest { new DeviceState(0, "DEFAULT", 0 /* flags */); private static final DeviceState OTHER_DEVICE_STATE = new DeviceState(1, "OTHER", 0 /* flags */); - private static final DeviceState DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP = + private static final DeviceState + DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP = new DeviceState(2, "DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP", DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP /* flags */); // A device state that is not reported as being supported for the default test provider. diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java index b3d25f2eef25..cfdb5869e020 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java @@ -25,6 +25,7 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; import android.annotation.Nullable; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateRequest; import android.os.Binder; import android.platform.test.annotations.Presubmit; @@ -48,8 +49,10 @@ import java.util.Map; @RunWith(AndroidJUnit4.class) public final class OverrideRequestControllerTest { - private static final DeviceState TEST_DEVICE_STATE_ZERO = new DeviceState(0, "TEST_STATE", 0); - private static final DeviceState TEST_DEVICE_STATE_ONE = new DeviceState(1, "TEST_STATE", 0); + private static final DeviceState + TEST_DEVICE_STATE_ZERO = new DeviceState(0, "TEST_STATE", 0); + private static final DeviceState + TEST_DEVICE_STATE_ONE = new DeviceState(1, "TEST_STATE", 0); private TestStatusChangeListener mStatusListener; private OverrideRequestController mController; 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 df2069efb0ce..7f7cc35ced55 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 @@ public final class UserManagerTest { 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/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java index 7e40f96154d2..16909ab4511e 100644 --- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java +++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java @@ -40,12 +40,12 @@ import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorManager; +import android.hardware.devicestate.DeviceState; import android.os.PowerManager; import androidx.annotation.NonNull; import com.android.server.LocalServices; -import com.android.server.devicestate.DeviceState; import com.android.server.devicestate.DeviceStateProvider; import com.android.server.input.InputManagerInternal; 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 046e0570d439..cb9490e44977 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -303,7 +303,6 @@ import org.junit.After; 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 +4002,69 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @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 +14430,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @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 +14519,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @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); 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 bdbb6c6baa70..7db707a42ff0 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 @@ public class VibratorManagerServiceTest { 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/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java index 1fb7cd8e6e1c..9e2b1eccc3b2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java @@ -32,14 +32,17 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.server.wm.DragDropController.MSG_UNHANDLED_DROP_LISTENER_TIMEOUT; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.app.PendingIntent; @@ -49,9 +52,12 @@ import android.content.Intent; import android.content.pm.ShortcutServiceInternal; import android.graphics.PixelFormat; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.Message; import android.os.Parcelable; +import android.os.RemoteException; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.view.DragEvent; @@ -61,6 +67,7 @@ import android.view.SurfaceSession; import android.view.View; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; +import android.window.IUnhandledDragListener; import androidx.test.filters.SmallTest; @@ -533,14 +540,98 @@ public class DragDropControllerTests extends WindowTestsBase { }); } + @Test + public void testUnhandledDragListenerNotCalledForNormalDrags() throws RemoteException { + assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags()); + + final IUnhandledDragListener listener = mock(IUnhandledDragListener.class); + doReturn(mock(Binder.class)).when(listener).asBinder(); + mTarget.setUnhandledDragListener(listener); + doDragAndDrop(0, ClipData.newPlainText("label", "Test"), 0, 0); + verify(listener, times(0)).onUnhandledDrop(any(), any()); + } + + @Test + public void testUnhandledDragListenerReceivesUnhandledDropOverWindow() { + assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags()); + + final IUnhandledDragListener listener = mock(IUnhandledDragListener.class); + doReturn(mock(Binder.class)).when(listener).asBinder(); + mTarget.setUnhandledDragListener(listener); + final int invalidXY = 100_000; + startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> { + // Notify the unhandled drag listener + mTarget.reportDropWindow(mWindow.mInputChannelToken, invalidXY, invalidXY); + mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY); + mTarget.reportDropResult(mWindow.mClient, false); + mTarget.onUnhandledDropCallback(true); + mToken = null; + try { + verify(listener, times(1)).onUnhandledDrop(any(), any()); + } catch (RemoteException e) { + fail("Failed to verify unhandled drop: " + e); + } + }); + } + + @Test + public void testUnhandledDragListenerReceivesUnhandledDropOverNoValidWindow() { + assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags()); + + final IUnhandledDragListener listener = mock(IUnhandledDragListener.class); + doReturn(mock(Binder.class)).when(listener).asBinder(); + mTarget.setUnhandledDragListener(listener); + final int invalidXY = 100_000; + startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> { + // Notify the unhandled drag listener + mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY); + mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY); + mTarget.onUnhandledDropCallback(true); + mToken = null; + try { + verify(listener, times(1)).onUnhandledDrop(any(), any()); + } catch (RemoteException e) { + fail("Failed to verify unhandled drop: " + e); + } + }); + } + + @Test + public void testUnhandledDragListenerCallbackTimeout() { + assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags()); + + final IUnhandledDragListener listener = mock(IUnhandledDragListener.class); + doReturn(mock(Binder.class)).when(listener).asBinder(); + mTarget.setUnhandledDragListener(listener); + final int invalidXY = 100_000; + startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> { + // Notify the unhandled drag listener + mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY); + mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY); + + // Verify that the unhandled drop listener callback timeout has been scheduled + final Handler handler = mTarget.getHandler(); + assertTrue(handler.hasMessages(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT)); + + // Force trigger the timeout and verify that it actually cleans up the drag & timeout + handler.handleMessage(Message.obtain(handler, MSG_UNHANDLED_DROP_LISTENER_TIMEOUT)); + assertFalse(handler.hasMessages(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT)); + assertFalse(mTarget.dragDropActiveLocked()); + mToken = null; + }); + } + private void doDragAndDrop(int flags, ClipData data, float dropX, float dropY) { startDrag(flags, data, () -> { mTarget.reportDropWindow(mWindow.mInputChannelToken, dropX, dropY); - mTarget.handleMotionEvent(false, dropX, dropY); + mTarget.handleMotionEvent(false /* keepHandling */, dropX, dropY); mToken = mWindow.mClient.asBinder(); }); } + /** + * Starts a drag with the given parameters, calls Runnable `r` after drag is started. + */ private void startDrag(int flag, ClipData data, Runnable r) { final SurfaceSession appSession = new SurfaceSession(); try { 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 aa9c0c8457f5..03b695d170ad 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.android.server.wm.testing.Assert.assertThrows; 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 @@ public class WindowOrganizerTests extends WindowTestsBase { 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 @@ public class WindowOrganizerTests extends WindowTestsBase { } @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 @@ public class WindowOrganizerTests extends WindowTestsBase { 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 874c10c8ea83..a52614d5cda1 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -269,6 +269,27 @@ public final class Call { "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 f7793f373916..697c8ecd96e7 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -9778,6 +9778,13 @@ public class CarrierConfigManager { 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 @@ public class CarrierConfigManager { 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 711820796496..cd641b8be0b6 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -1156,6 +1156,28 @@ public class SubscriptionManager { */ 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 61c7a420b604..041822bf4ee9 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -10629,20 +10629,27 @@ public class TelephonyManager { } /** - * 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 @@ public class TelephonyManager { @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/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java index d07edeb971ea..cebfe014a062 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 @@ public final class ImsCallProfile implements Parcelable { "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 746246c64e8c..9789082e1460 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.IImsEcbm; 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 @@ public class MmTelFeature extends ImsFeature { 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 class MmTelFeature extends ImsFeature { */ 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 @@ public class MmTelFeature extends ImsFeature { 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/tests/UsbManagerTests/Android.bp b/tests/UsbManagerTests/Android.bp index 70c7dad2fd55..a16a7eafc8e8 100644 --- a/tests/UsbManagerTests/Android.bp +++ b/tests/UsbManagerTests/Android.bp @@ -33,6 +33,8 @@ android_test { "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 000000000000..dabfcae8e0fd --- /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/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java index 60c25b75d2e9..be5c84c0353c 100644 --- a/tests/testables/src/android/testing/TestableLooper.java +++ b/tests/testables/src/android/testing/TestableLooper.java @@ -14,6 +14,8 @@ package android.testing; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -85,6 +87,57 @@ public class TestableLooper { setupQueue(looper); } + /** + * Wrap the given runnable so that it will run blocking on the Looper that will be set up for + * the given test. + * <p> + * This method is required to support any TestRule which needs to run setup and/or teardown code + * on the TestableLooper. Whether using {@link AndroidTestingRunner} or + * {@link TestWithLooperRule}, the TestRule's Statement evaluates on the test instrumentation + * thread, rather than the TestableLooper thread, so access to the TestableLooper is required. + * However, {@link #get(Object)} will return {@code null} both before and after the inner + * statement is evaluated: + * <ul> + * <li>Before the test {@link #get} returns {@code null} because while the TestableLooperHolder + * is accessible in sLoopers, it has not been initialized with an actual TestableLooper yet. + * This method's use of the internal LooperFrameworkMethod ensures that all setup and teardown + * of the TestableLooper happen as it would for all other wrapped code blocks. + * <li>After the test {@link #get} can return {@code null} because many tests call + * {@link #remove} in the teardown method. The fact that this method returns a runnable allows + * it to be called before the test (when the TestableLooperHolder is still in sLoopers), and + * then executed as teardown after the test. + * </ul> + * + * @param test the test instance (just like passed to {@link #get(Object)}) + * @param runnable the operation that should eventually be run on the TestableLooper + * @return a runnable that will block the thread on which it is called until the given runnable + * is finished. Will be {@code null} if there is no looper for the given test. + * @hide + */ + @Nullable + public static RunnableWithException wrapWithRunBlocking( + Object test, @NonNull RunnableWithException runnable) { + TestableLooperHolder looperHolder = sLoopers.get(test); + if (looperHolder == null) { + return null; + } + try { + FrameworkMethod base = new FrameworkMethod(runnable.getClass().getMethod("run")); + LooperFrameworkMethod wrapped = new LooperFrameworkMethod(base, looperHolder); + return () -> { + try { + wrapped.invokeExplosively(runnable); + } catch (RuntimeException | Error e) { + throw e; + } catch (Throwable e) { + throw new RuntimeException(e); + } + }; + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + public Looper getLooper() { return mLooper; } diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp index 57bcc04a8aec..c6dd29ce7cc6 100644 --- a/tools/hoststubgen/hoststubgen/Android.bp +++ b/tools/hoststubgen/hoststubgen/Android.bp @@ -180,7 +180,6 @@ java_library { "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 06eeb47c94ed..1089f82b6472 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 @@ class HostStubGen(val options: HostStubGenOptions) { stubOutStream.putNextEntry(newEntry) convertClass(classInternalName, /*forImpl=*/false, bis, stubOutStream, filter, packageRedirector, enableChecker, classes, - errors, stats) + errors, null) stubOutStream.closeEntry() } } @@ -415,7 +415,7 @@ class HostStubGen(val options: HostStubGenOptions) { 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 fe4072f6b3ee..50518e1ccd9c 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 @@ package com.android.hoststubgen 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 @@ open class HostStubGenStats { 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 53eb5a8c2fdc..eb03f66b5afa 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 @@ data class FilterPolicyWithReason ( || 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 c20aa8bc70ca..45e140c8e3ff 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 @@ abstract class BaseAdapter ( */ 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 @@ abstract class BaseAdapter ( } 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 beca945a2819..416b78242899 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 @@ class ImplGeneratingAdapter( 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 000000000000..18cf2be9f7df --- /dev/null +++ b/tools/protologtool/OWNERS @@ -0,0 +1,3 @@ +# ProtoLog owners +# Bug component: 1157642 +include platform/development:/tools/winscope/OWNERS |