diff options
Diffstat (limited to 'cmds')
19 files changed, 359 insertions, 601 deletions
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 77b74e9898b8..5adcd930e341 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -707,11 +707,11 @@ void BootAnimation::resizeSurface(int newWidth, int newHeight) { eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroySurface(mDisplay, mSurface); - mFlingerSurfaceControl->updateDefaultBufferSize(newWidth, newHeight); const auto limitedSize = limitSurfaceSize(newWidth, newHeight); mWidth = limitedSize.width; mHeight = limitedSize.height; + mFlingerSurfaceControl->updateDefaultBufferSize(mWidth, mHeight); EGLConfig config = getEglConfig(mDisplay); EGLSurface surface = eglCreateWindowSurface(mDisplay, config, mFlingerSurface.get(), nullptr); if (eglMakeCurrent(mDisplay, surface, surface, mContext) == EGL_FALSE) { diff --git a/cmds/bootanimation/FORMAT.md b/cmds/bootanimation/FORMAT.md index 01e8fe13fdf6..da8331af1492 100644 --- a/cmds/bootanimation/FORMAT.md +++ b/cmds/bootanimation/FORMAT.md @@ -126,7 +126,7 @@ the system property `service.bootanim.exit` to a nonzero string.) Use `zopflipng` if you have it, otherwise `pngcrush` will do. e.g.: for fn in *.png ; do - zopflipng -m ${fn}s ${fn}s.new && mv -f ${fn}s.new ${fn} + zopflipng -m ${fn} ${fn}.new && mv -f ${fn}.new ${fn} # or: pngcrush -q .... done diff --git a/cmds/hid/jni/com_android_commands_hid_Device.cpp b/cmds/hid/jni/com_android_commands_hid_Device.cpp index 8b8d361edbd4..a142450ac0c6 100644 --- a/cmds/hid/jni/com_android_commands_hid_Device.cpp +++ b/cmds/hid/jni/com_android_commands_hid_Device.cpp @@ -134,8 +134,9 @@ JNIEnv* DeviceCallback::getJNIEnv() { return env; } -std::unique_ptr<Device> Device::open(int32_t id, const char* name, int32_t vid, int32_t pid, - uint16_t bus, const std::vector<uint8_t>& descriptor, +std::unique_ptr<Device> Device::open(int32_t id, const char* name, const char* uniq, int32_t vid, + int32_t pid, uint16_t bus, + const std::vector<uint8_t>& descriptor, std::unique_ptr<DeviceCallback> callback) { size_t size = descriptor.size(); if (size > HID_MAX_DESCRIPTOR_SIZE) { @@ -152,8 +153,7 @@ std::unique_ptr<Device> Device::open(int32_t id, const char* name, int32_t vid, struct uhid_event ev = {}; ev.type = UHID_CREATE2; strlcpy(reinterpret_cast<char*>(ev.u.create2.name), name, sizeof(ev.u.create2.name)); - std::string uniq = android::base::StringPrintf("Id: %d", id); - strlcpy(reinterpret_cast<char*>(ev.u.create2.uniq), uniq.c_str(), sizeof(ev.u.create2.uniq)); + strlcpy(reinterpret_cast<char*>(ev.u.create2.uniq), uniq, sizeof(ev.u.create2.uniq)); memcpy(&ev.u.create2.rd_data, descriptor.data(), size * sizeof(ev.u.create2.rd_data[0])); ev.u.create2.rd_size = size; ev.u.create2.bus = bus; @@ -314,19 +314,31 @@ std::vector<uint8_t> getData(JNIEnv* env, jbyteArray javaArray) { return data; } -static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint id, jint vid, - jint pid, jint bus, jbyteArray rawDescriptor, jobject callback) { +static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jstring rawUniq, jint id, + jint vid, jint pid, jint bus, jbyteArray rawDescriptor, jobject callback) { ScopedUtfChars name(env, rawName); if (name.c_str() == nullptr) { return 0; } + std::string uniq; + if (rawUniq != nullptr) { + uniq = ScopedUtfChars(env, rawUniq); + } else { + uniq = android::base::StringPrintf("Id: %d", id); + } + + if (uniq.c_str() == nullptr) { + return 0; + } + std::vector<uint8_t> desc = getData(env, rawDescriptor); std::unique_ptr<uhid::DeviceCallback> cb(new uhid::DeviceCallback(env, callback)); std::unique_ptr<uhid::Device> d = - uhid::Device::open(id, reinterpret_cast<const char*>(name.c_str()), vid, pid, bus, desc, + uhid::Device::open(id, reinterpret_cast<const char*>(name.c_str()), + reinterpret_cast<const char*>(uniq.c_str()), vid, pid, bus, desc, std::move(cb)); return reinterpret_cast<jlong>(d.release()); } @@ -370,7 +382,7 @@ static void closeDevice(JNIEnv* /* env */, jclass /* clazz */, jlong ptr) { static JNINativeMethod sMethods[] = { {"nativeOpenDevice", - "(Ljava/lang/String;IIII[B" + "(Ljava/lang/String;Ljava/lang/String;IIII[B" "Lcom/android/commands/hid/Device$DeviceCallback;)J", reinterpret_cast<void*>(openDevice)}, {"nativeSendReport", "(J[B)V", reinterpret_cast<void*>(sendReport)}, diff --git a/cmds/hid/jni/com_android_commands_hid_Device.h b/cmds/hid/jni/com_android_commands_hid_Device.h index 9c6060d837e0..bc7a9092cc4e 100644 --- a/cmds/hid/jni/com_android_commands_hid_Device.h +++ b/cmds/hid/jni/com_android_commands_hid_Device.h @@ -42,8 +42,9 @@ private: class Device { public: - static std::unique_ptr<Device> open(int32_t id, const char* name, int32_t vid, int32_t pid, - uint16_t bus, const std::vector<uint8_t>& descriptor, + static std::unique_ptr<Device> open(int32_t id, const char* name, const char* uniq, int32_t vid, + int32_t pid, uint16_t bus, + const std::vector<uint8_t>& descriptor, std::unique_ptr<DeviceCallback> callback); ~Device(); diff --git a/cmds/hid/src/com/android/commands/hid/Device.java b/cmds/hid/src/com/android/commands/hid/Device.java index 0415037cfc9f..4e8adc3af55c 100644 --- a/cmds/hid/src/com/android/commands/hid/Device.java +++ b/cmds/hid/src/com/android/commands/hid/Device.java @@ -71,6 +71,7 @@ public class Device { private static native long nativeOpenDevice( String name, + String uniq, int id, int vid, int pid, @@ -89,6 +90,7 @@ public class Device { public Device( int id, String name, + String uniq, int vid, int pid, int bus, @@ -113,8 +115,9 @@ public class Device { } else { args.arg1 = id + ":" + vid + ":" + pid; } - args.arg2 = descriptor; - args.arg3 = report; + args.arg2 = uniq; + args.arg3 = descriptor; + args.arg4 = report; mHandler.obtainMessage(MSG_OPEN_DEVICE, args).sendToTarget(); mTimeToSend = SystemClock.uptimeMillis(); } @@ -167,11 +170,12 @@ public class Device { mPtr = nativeOpenDevice( (String) args.arg1, + (String) args.arg2, args.argi1, args.argi2, args.argi3, args.argi4, - (byte[]) args.arg2, + (byte[]) args.arg3, new DeviceCallback()); pauseEvents(); break; diff --git a/cmds/hid/src/com/android/commands/hid/Event.java b/cmds/hid/src/com/android/commands/hid/Event.java index 3efb79766b94..3b022796356b 100644 --- a/cmds/hid/src/com/android/commands/hid/Event.java +++ b/cmds/hid/src/com/android/commands/hid/Event.java @@ -56,6 +56,7 @@ public class Event { private int mId; private String mCommand; private String mName; + private String mUniq; private byte[] mDescriptor; private int mVid; private int mPid; @@ -78,6 +79,10 @@ public class Event { return mName; } + public String getUniq() { + return mUniq; + } + public byte[] getDescriptor() { return mDescriptor; } @@ -116,8 +121,9 @@ public class Event { public String toString() { return "Event{id=" + mId - + ", command=" + String.valueOf(mCommand) - + ", name=" + String.valueOf(mName) + + ", command=" + mCommand + + ", name=" + mName + + ", uniq=" + mUniq + ", descriptor=" + Arrays.toString(mDescriptor) + ", vid=" + mVid + ", pid=" + mPid @@ -149,6 +155,10 @@ public class Event { mEvent.mName = name; } + public void setUniq(String uniq) { + mEvent.mUniq = uniq; + } + public void setDescriptor(byte[] descriptor) { mEvent.mDescriptor = descriptor; } @@ -247,6 +257,9 @@ public class Event { case "name": eb.setName(mReader.nextString()); break; + case "uniq": + eb.setUniq(mReader.nextString()); + break; case "vid": eb.setVid(readInt()); break; diff --git a/cmds/hid/src/com/android/commands/hid/Hid.java b/cmds/hid/src/com/android/commands/hid/Hid.java index 2db791fe90bd..5ebfd959ef33 100644 --- a/cmds/hid/src/com/android/commands/hid/Hid.java +++ b/cmds/hid/src/com/android/commands/hid/Hid.java @@ -117,8 +117,17 @@ public class Hid { "Tried to send command \"" + e.getCommand() + "\" to an unregistered device!"); } int id = e.getId(); - Device d = new Device(id, e.getName(), e.getVendorId(), e.getProductId(), e.getBus(), - e.getDescriptor(), e.getReport(), e.getFeatureReports(), e.getOutputs()); + Device d = new Device( + id, + e.getName(), + e.getUniq(), + e.getVendorId(), + e.getProductId(), + e.getBus(), + e.getDescriptor(), + e.getReport(), + e.getFeatureReports(), + e.getOutputs()); mDevices.append(id, d); } diff --git a/cmds/incident_helper/OWNERS b/cmds/incident_helper/OWNERS index cede4eae50ad..29f44aba75d1 100644 --- a/cmds/incident_helper/OWNERS +++ b/cmds/incident_helper/OWNERS @@ -1,3 +1,2 @@ joeo@google.com -kwekua@google.com yanmin@google.com diff --git a/cmds/incidentd/src/PrivacyFilter.cpp b/cmds/incidentd/src/PrivacyFilter.cpp index 0d427d1021a6..b273fd469de3 100644 --- a/cmds/incidentd/src/PrivacyFilter.cpp +++ b/cmds/incidentd/src/PrivacyFilter.cpp @@ -195,7 +195,9 @@ status_t FieldStripper::strip(const uint8_t privacyPolicy) { ProtoOutputStream proto(mEncodedBuffer); // Optimization when no strip happens. - if (mRestrictions == NULL || spec.RequireAll()) { + if (mRestrictions == NULL || spec.RequireAll() + // Do not iterate through fields if primitive data + || !mRestrictions->children /* != FieldDescriptor::TYPE_MESSAGE */) { if (spec.CheckPremission(mRestrictions)) { mSize = mData->size(); } diff --git a/cmds/locksettings/TEST_MAPPING b/cmds/locksettings/TEST_MAPPING index 7a449effdf76..af54a2decd89 100644 --- a/cmds/locksettings/TEST_MAPPING +++ b/cmds/locksettings/TEST_MAPPING @@ -11,5 +11,10 @@ } ] } + ], + "postsubmit": [ + { + "name": "CtsDevicePolicyManagerTestCases_LockSettings_NoFlakes" + } ] } diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp index 917529ec1dcf..7e4f95bc9274 100644 --- a/cmds/screencap/screencap.cpp +++ b/cmds/screencap/screencap.cpp @@ -51,11 +51,13 @@ using namespace android; void usage(const char* pname, ftl::Optional<DisplayId> displayIdOpt) { fprintf(stderr, R"( -usage: %s [-hp] [-d display-id] [FILENAME] +usage: %s [-ahp] [-d display-id] [FILENAME] -h: this message - -p: save the file as a png. + -a: captures all the active displays. This appends an integer postfix to the FILENAME. + e.g., FILENAME_0.png, FILENAME_1.png. If both -a and -d are given, it ignores -d. -d: specify the display ID to capture%s see "dumpsys SurfaceFlinger --display-id" for valid display IDs. + -p: outputs in png format. --hint-for-seamless If set will use the hintForSeamless path in SF If FILENAME ends with .png it will be saved as a png. @@ -63,11 +65,13 @@ If FILENAME is not given, the results will be printed to stdout. )", pname, displayIdOpt - .transform([](DisplayId id) { - return std::string(ftl::Concat(" (default: ", id.value, ')').str()); - }) - .value_or(std::string()) - .c_str()); + .transform([](DisplayId id) { + return std::string(ftl::Concat( + " (If the id is not given, it defaults to ", id.value,')' + ).str()); + }) + .value_or(std::string()) + .c_str()); } // For options that only exist in long-form. Anything in the @@ -123,8 +127,8 @@ static status_t notifyMediaScanner(const char* fileName) { int status; int pid = fork(); if (pid < 0){ - fprintf(stderr, "Unable to fork in order to send intent for media scanner.\n"); - return UNKNOWN_ERROR; + fprintf(stderr, "Unable to fork in order to send intent for media scanner.\n"); + return UNKNOWN_ERROR; } if (pid == 0){ int fd = open("/dev/null", O_WRONLY); @@ -146,19 +150,119 @@ static status_t notifyMediaScanner(const char* fileName) { return NO_ERROR; } +status_t capture(const DisplayId displayId, + const gui::CaptureArgs& captureArgs, + ScreenCaptureResults& outResult) { + sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener(); + ScreenshotClient::captureDisplay(displayId, captureArgs, captureListener); + + ScreenCaptureResults captureResults = captureListener->waitForResults(); + if (!captureResults.fenceResult.ok()) { + fprintf(stderr, "Failed to take screenshot. Status: %d\n", + fenceStatus(captureResults.fenceResult)); + return 1; + } + + outResult = captureResults; + + return 0; +} + +status_t saveImage(const char* fn, bool png, const ScreenCaptureResults& captureResults) { + void* base = nullptr; + ui::Dataspace dataspace = captureResults.capturedDataspace; + sp<GraphicBuffer> buffer = captureResults.buffer; + + status_t result = buffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &base); + + if (base == nullptr || result != NO_ERROR) { + String8 reason; + if (result != NO_ERROR) { + reason.appendFormat(" Error Code: %d", result); + } else { + reason = "Failed to write to buffer"; + } + fprintf(stderr, "Failed to take screenshot (%s)\n", reason.c_str()); + return 1; + } + + int fd = -1; + if (fn == nullptr) { + fd = dup(STDOUT_FILENO); + if (fd == -1) { + fprintf(stderr, "Error writing to stdout. (%s)\n", strerror(errno)); + return 1; + } + } else { + fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664); + if (fd == -1) { + fprintf(stderr, "Error opening file: %s (%s)\n", fn, strerror(errno)); + return 1; + } + } + + if (png) { + AndroidBitmapInfo info; + info.format = flinger2bitmapFormat(buffer->getPixelFormat()); + info.flags = ANDROID_BITMAP_FLAGS_ALPHA_PREMUL; + info.width = buffer->getWidth(); + info.height = buffer->getHeight(); + info.stride = buffer->getStride() * bytesPerPixel(buffer->getPixelFormat()); + + int result = AndroidBitmap_compress(&info, static_cast<int32_t>(dataspace), base, + ANDROID_BITMAP_COMPRESS_FORMAT_PNG, 100, &fd, + [](void* fdPtr, const void* data, size_t size) -> bool { + int bytesWritten = write(*static_cast<int*>(fdPtr), + data, size); + return bytesWritten == size; + }); + + if (result != ANDROID_BITMAP_RESULT_SUCCESS) { + fprintf(stderr, "Failed to compress PNG (error code: %d)\n", result); + } + + if (fn != NULL) { + notifyMediaScanner(fn); + } + } else { + uint32_t w = buffer->getWidth(); + uint32_t h = buffer->getHeight(); + uint32_t s = buffer->getStride(); + uint32_t f = buffer->getPixelFormat(); + uint32_t c = dataSpaceToInt(dataspace); + + write(fd, &w, 4); + write(fd, &h, 4); + write(fd, &f, 4); + write(fd, &c, 4); + size_t Bpp = bytesPerPixel(f); + for (size_t y=0 ; y<h ; y++) { + write(fd, base, w*Bpp); + base = (void *)((char *)base + s*Bpp); + } + } + close(fd); + + return 0; +} + int main(int argc, char** argv) { - const std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds(); - if (ids.empty()) { + const std::vector<PhysicalDisplayId> physicalDisplays = + SurfaceComposerClient::getPhysicalDisplayIds(); + + if (physicalDisplays.empty()) { fprintf(stderr, "Failed to get ID for any displays.\n"); return 1; } std::optional<DisplayId> displayIdOpt; + std::vector<DisplayId> displaysToCapture; gui::CaptureArgs captureArgs; const char* pname = argv[0]; bool png = false; + bool all = false; int c; - while ((c = getopt_long(argc, argv, "phd:", LONG_OPTIONS, nullptr)) != -1) { + while ((c = getopt_long(argc, argv, "aphd:", LONG_OPTIONS, nullptr)) != -1) { switch (c) { case 'p': png = true; @@ -177,12 +281,17 @@ int main(int argc, char** argv) fprintf(stderr, "Invalid display ID: Incorrect encoding.\n"); return 1; } + displaysToCapture.push_back(displayIdOpt.value()); + break; + } + case 'a': { + all = true; break; } case '?': case 'h': - if (ids.size() == 1) { - displayIdOpt = ids.front(); + if (physicalDisplays.size() >= 1) { + displayIdOpt = physicalDisplays.front(); } usage(pname, displayIdOpt); return 1; @@ -192,44 +301,52 @@ int main(int argc, char** argv) } } - if (!displayIdOpt) { - displayIdOpt = ids.front(); - if (ids.size() > 1) { - fprintf(stderr, - "[Warning] Multiple displays were found, but no display id was specified! " - "Defaulting to the first display found, however this default is not guaranteed " - "to be consistent across captures. A display id should be specified.\n"); - fprintf(stderr, "A display ID can be specified with the [-d display-id] option.\n"); - fprintf(stderr, "See \"dumpsys SurfaceFlinger --display-id\" for valid display IDs.\n"); - } - } - argc -= optind; argv += optind; - int fd = -1; - const char* fn = NULL; - if (argc == 0) { - fd = dup(STDOUT_FILENO); - } else if (argc == 1) { - fn = argv[0]; - fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664); - if (fd == -1) { - fprintf(stderr, "Error opening file: %s (%s)\n", fn, strerror(errno)); - return 1; + // We don't expect more than 2 arguments. + if (argc >= 2) { + if (physicalDisplays.size() >= 1) { + usage(pname, physicalDisplays.front()); + } else { + usage(pname, std::nullopt); } - const int len = strlen(fn); - if (len >= 4 && 0 == strcmp(fn+len-4, ".png")) { + return 1; + } + + std::string baseName; + std::string suffix; + + if (argc == 1) { + std::string_view filename = { argv[0] }; + if (filename.ends_with(".png")) { + baseName = filename.substr(0, filename.size()-4); + suffix = ".png"; png = true; + } else { + baseName = filename; } } - if (fd == -1) { - usage(pname, displayIdOpt); - return 1; + if (all) { + // Ignores -d if -a is given. + displaysToCapture.clear(); + for (int i = 0; i < physicalDisplays.size(); i++) { + displaysToCapture.push_back(physicalDisplays[i]); + } } - void* base = NULL; + if (displaysToCapture.empty()) { + displaysToCapture.push_back(physicalDisplays.front()); + if (physicalDisplays.size() > 1) { + fprintf(stderr, + "[Warning] Multiple displays were found, but no display id was specified! " + "Defaulting to the first display found, however this default is not guaranteed " + "to be consistent across captures. A display id should be specified.\n"); + fprintf(stderr, "A display ID can be specified with the [-d display-id] option.\n"); + fprintf(stderr, "See \"dumpsys SurfaceFlinger --display-id\" for valid display IDs.\n"); + } + } // setThreadPoolMaxThreadCount(0) actually tells the kernel it's // not allowed to spawn any additional threads, but we still spawn @@ -238,74 +355,39 @@ int main(int argc, char** argv) ProcessState::self()->setThreadPoolMaxThreadCount(0); ProcessState::self()->startThreadPool(); - sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener(); - ScreenshotClient::captureDisplay(*displayIdOpt, captureArgs, captureListener); - - ScreenCaptureResults captureResults = captureListener->waitForResults(); - if (!captureResults.fenceResult.ok()) { - close(fd); - fprintf(stderr, "Failed to take screenshot. Status: %d\n", - fenceStatus(captureResults.fenceResult)); - return 1; - } - ui::Dataspace dataspace = captureResults.capturedDataspace; - sp<GraphicBuffer> buffer = captureResults.buffer; + std::vector<ScreenCaptureResults> results; + const size_t numDisplays = displaysToCapture.size(); + for (int i=0; i<numDisplays; i++) { + ScreenCaptureResults result; - status_t result = buffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &base); + // 1. Capture the screen + if (const status_t captureStatus = + capture(displaysToCapture[i], captureArgs, result) != 0) { - if (base == nullptr || result != NO_ERROR) { - String8 reason; - if (result != NO_ERROR) { - reason.appendFormat(" Error Code: %d", result); - } else { - reason = "Failed to write to buffer"; + fprintf(stderr, "Capturing failed.\n"); + return captureStatus; } - fprintf(stderr, "Failed to take screenshot (%s)\n", reason.c_str()); - close(fd); - return 1; - } - - if (png) { - AndroidBitmapInfo info; - info.format = flinger2bitmapFormat(buffer->getPixelFormat()); - info.flags = ANDROID_BITMAP_FLAGS_ALPHA_PREMUL; - info.width = buffer->getWidth(); - info.height = buffer->getHeight(); - info.stride = buffer->getStride() * bytesPerPixel(buffer->getPixelFormat()); - - int result = AndroidBitmap_compress(&info, static_cast<int32_t>(dataspace), base, - ANDROID_BITMAP_COMPRESS_FORMAT_PNG, 100, &fd, - [](void* fdPtr, const void* data, size_t size) -> bool { - int bytesWritten = write(*static_cast<int*>(fdPtr), - data, size); - return bytesWritten == size; - }); - if (result != ANDROID_BITMAP_RESULT_SUCCESS) { - fprintf(stderr, "Failed to compress PNG (error code: %d)\n", result); + // 2. Save the capture result as an image. + // When there's more than one file to capture, add the index as postfix. + std::string filename; + if (!baseName.empty()) { + filename = baseName; + if (numDisplays > 1) { + filename += "_"; + filename += std::to_string(i); + } + filename += suffix; } - - if (fn != NULL) { - notifyMediaScanner(fn); + const char* fn = nullptr; + if (!filename.empty()) { + fn = filename.c_str(); } - } else { - uint32_t w = buffer->getWidth(); - uint32_t h = buffer->getHeight(); - uint32_t s = buffer->getStride(); - uint32_t f = buffer->getPixelFormat(); - uint32_t c = dataSpaceToInt(dataspace); - - write(fd, &w, 4); - write(fd, &h, 4); - write(fd, &f, 4); - write(fd, &c, 4); - size_t Bpp = bytesPerPixel(f); - for (size_t y=0 ; y<h ; y++) { - write(fd, base, w*Bpp); - base = (void *)((char *)base + s*Bpp); + if (const status_t saveImageStatus = saveImage(fn, png, result) != 0) { + fprintf(stderr, "Saving image failed.\n"); + return saveImageStatus; } } - close(fd); return 0; } diff --git a/cmds/telecom/Android.bp b/cmds/telecom/Android.bp index be027105ae98..494d2ae37d53 100644 --- a/cmds/telecom/Android.bp +++ b/cmds/telecom/Android.bp @@ -21,5 +21,8 @@ license { java_binary { name: "telecom", wrapper: "telecom.sh", - srcs: ["**/*.java"], + srcs: [ + ":telecom-shell-commands-src", + "**/*.java", + ], } diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java index 1488e14cfb8f..50af5a7a29b7 100644 --- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java +++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java @@ -17,30 +17,22 @@ package com.android.commands.telecom; import android.app.ActivityThread; -import android.content.ComponentName; import android.content.Context; -import android.net.Uri; -import android.os.IUserManager; import android.os.Looper; -import android.os.Process; -import android.os.RemoteException; import android.os.ServiceManager; -import android.os.UserHandle; -import android.sysprop.TelephonyProperties; -import android.telecom.Log; -import android.telecom.PhoneAccount; -import android.telecom.PhoneAccountHandle; -import android.telephony.TelephonyManager; -import android.text.TextUtils; -import com.android.internal.os.BaseCommand; import com.android.internal.telecom.ITelecomService; +import com.android.server.telecom.TelecomShellCommand; -import java.io.PrintStream; -import java.util.Arrays; -import java.util.stream.Collectors; +import java.io.FileDescriptor; -public final class Telecom extends BaseCommand { +/** + * @deprecated Use {@code com.android.server.telecom.TelecomShellCommand} instead and execute the + * shell command using {@code adb shell cmd telecom...}. This is only here for backwards + * compatibility reasons. + */ +@Deprecated +public final class Telecom { /** * Command-line entry point. @@ -52,458 +44,11 @@ public final class Telecom extends BaseCommand { // TODO: Do it in zygote and RuntimeInit. b/148897549 ActivityThread.initializeMainlineModules(); - (new Telecom()).run(args); - } - private static final String CALLING_PACKAGE = Telecom.class.getPackageName(); - private static final String COMMAND_SET_PHONE_ACCOUNT_ENABLED = "set-phone-account-enabled"; - private static final String COMMAND_SET_PHONE_ACCOUNT_DISABLED = "set-phone-account-disabled"; - private static final String COMMAND_REGISTER_PHONE_ACCOUNT = "register-phone-account"; - private static final String COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT = - "set-user-selected-outgoing-phone-account"; - private static final String COMMAND_REGISTER_SIM_PHONE_ACCOUNT = "register-sim-phone-account"; - private static final String COMMAND_SET_TEST_CALL_REDIRECTION_APP = "set-test-call-redirection-app"; - private static final String COMMAND_SET_TEST_CALL_SCREENING_APP = "set-test-call-screening-app"; - private static final String COMMAND_ADD_OR_REMOVE_CALL_COMPANION_APP = - "add-or-remove-call-companion-app"; - private static final String COMMAND_SET_PHONE_ACCOUNT_SUGGESTION_COMPONENT = - "set-phone-acct-suggestion-component"; - private static final String COMMAND_UNREGISTER_PHONE_ACCOUNT = "unregister-phone-account"; - private static final String COMMAND_SET_CALL_DIAGNOSTIC_SERVICE = "set-call-diagnostic-service"; - private static final String COMMAND_SET_DEFAULT_DIALER = "set-default-dialer"; - private static final String COMMAND_GET_DEFAULT_DIALER = "get-default-dialer"; - private static final String COMMAND_STOP_BLOCK_SUPPRESSION = "stop-block-suppression"; - private static final String COMMAND_CLEANUP_STUCK_CALLS = "cleanup-stuck-calls"; - private static final String COMMAND_CLEANUP_ORPHAN_PHONE_ACCOUNTS = - "cleanup-orphan-phone-accounts"; - private static final String COMMAND_RESET_CAR_MODE = "reset-car-mode"; - private static final String COMMAND_IS_NON_IN_CALL_SERVICE_BOUND = - "is-non-ui-in-call-service-bound"; - - /** - * Change the system dialer package name if a package name was specified, - * Example: adb shell telecom set-system-dialer <PACKAGE> - * - * Restore it to the default if if argument is "default" or no argument is passed. - * Example: adb shell telecom set-system-dialer default - */ - private static final String COMMAND_SET_SYSTEM_DIALER = "set-system-dialer"; - private static final String COMMAND_GET_SYSTEM_DIALER = "get-system-dialer"; - private static final String COMMAND_WAIT_ON_HANDLERS = "wait-on-handlers"; - private static final String COMMAND_SET_SIM_COUNT = "set-sim-count"; - private static final String COMMAND_GET_SIM_CONFIG = "get-sim-config"; - private static final String COMMAND_GET_MAX_PHONES = "get-max-phones"; - private static final String COMMAND_SET_TEST_EMERGENCY_PHONE_ACCOUNT_PACKAGE_FILTER = - "set-test-emergency-phone-account-package-filter"; - /** - * Command used to emit a distinct "mark" in the logs. - */ - private static final String COMMAND_LOG_MARK = "log-mark"; - - private ComponentName mComponent; - private String mAccountId; - private ITelecomService mTelecomService; - private TelephonyManager mTelephonyManager; - private IUserManager mUserManager; - - @Override - public void onShowUsage(PrintStream out) { - out.println("usage: telecom [subcommand] [options]\n" - + "usage: telecom set-phone-account-enabled <COMPONENT> <ID> <USER_SN>\n" - + "usage: telecom set-phone-account-disabled <COMPONENT> <ID> <USER_SN>\n" - + "usage: telecom register-phone-account <COMPONENT> <ID> <USER_SN> <LABEL>\n" - + "usage: telecom register-sim-phone-account [-e] <COMPONENT> <ID> <USER_SN>" - + " <LABEL>: registers a PhoneAccount with CAPABILITY_SIM_SUBSCRIPTION" - + " and optionally CAPABILITY_PLACE_EMERGENCY_CALLS if \"-e\" is provided\n" - + "usage: telecom set-user-selected-outgoing-phone-account [-e] <COMPONENT> <ID> " - + "<USER_SN>\n" - + "usage: telecom set-test-call-redirection-app <PACKAGE>\n" - + "usage: telecom set-test-call-screening-app <PACKAGE>\n" - + "usage: telecom set-phone-acct-suggestion-component <COMPONENT>\n" - + "usage: telecom add-or-remove-call-companion-app <PACKAGE> <1/0>\n" - + "usage: telecom register-sim-phone-account <COMPONENT> <ID> <USER_SN>" - + " <LABEL> <ADDRESS>\n" - + "usage: telecom unregister-phone-account <COMPONENT> <ID> <USER_SN>\n" - + "usage: telecom set-call-diagnostic-service <PACKAGE>\n" - + "usage: telecom set-default-dialer <PACKAGE>\n" - + "usage: telecom get-default-dialer\n" - + "usage: telecom get-system-dialer\n" - + "usage: telecom wait-on-handlers\n" - + "usage: telecom set-sim-count <COUNT>\n" - + "usage: telecom get-sim-config\n" - + "usage: telecom get-max-phones\n" - + "usage: telecom stop-block-suppression: Stop suppressing the blocked number" - + " provider after a call to emergency services.\n" - + "usage: telecom cleanup-stuck-calls: Clear any disconnected calls that have" - + " gotten wedged in Telecom.\n" - + "usage: telecom cleanup-orphan-phone-accounts: remove any phone accounts that" - + " no longer have a valid UserHandle or accounts that no longer belongs to an" - + " installed package.\n" - + "usage: telecom set-emer-phone-account-filter <PACKAGE>\n" - + "\n" - + "telecom set-phone-account-enabled: Enables the given phone account, if it has" - + " already been registered with Telecom.\n" - + "\n" - + "telecom set-phone-account-disabled: Disables the given phone account, if it" - + " has already been registered with telecom.\n" - + "\n" - + "telecom set-call-diagnostic-service: overrides call diagnostic service.\n" - + "telecom set-default-dialer: Sets the override default dialer to the given" - + " component; this will override whatever the dialer role is set to.\n" - + "\n" - + "telecom get-default-dialer: Displays the current default dialer.\n" - + "\n" - + "telecom get-system-dialer: Displays the current system dialer.\n" - + "telecom set-system-dialer: Set the override system dialer to the given" - + " component. To remove the override, send \"default\"\n" - + "\n" - + "telecom wait-on-handlers: Wait until all handlers finish their work.\n" - + "\n" - + "telecom set-sim-count: Set num SIMs (2 for DSDS, 1 for single SIM." - + " This may restart the device.\n" - + "\n" - + "telecom get-sim-config: Get the mSIM config string. \"DSDS\" for DSDS mode," - + " or \"\" for single SIM\n" - + "\n" - + "telecom get-max-phones: Get the max supported phones from the modem.\n" - + "telecom set-test-emergency-phone-account-package-filter <PACKAGE>: sets a" - + " package name that will be used for test emergency calls. To clear," - + " send an empty package name. Real emergency calls will still be placed" - + " over Telephony.\n" - + "telecom log-mark <MESSAGE>: emits a message into the telecom logs. Useful for " - + "testers to indicate where in the logs various test steps take place.\n" - + "telecom is-non-ui-in-call-service-bound <PACKAGE>: queries a particular " - + "non-ui-InCallService in InCallController to determine if it is bound \n" - ); - } - - @Override - public void onRun() throws Exception { - mTelecomService = ITelecomService.Stub.asInterface( - ServiceManager.getService(Context.TELECOM_SERVICE)); - if (mTelecomService == null) { - Log.w(this, "onRun: Can't access telecom manager."); - showError("Error: Could not access the Telecom Manager. Is the system running?"); - return; - } - Looper.prepareMainLooper(); + ITelecomService service = ITelecomService.Stub.asInterface( + ServiceManager.getService(Context.TELECOM_SERVICE)); Context context = ActivityThread.systemMain().getSystemContext(); - mTelephonyManager = context.getSystemService(TelephonyManager.class); - if (mTelephonyManager == null) { - Log.w(this, "onRun: Can't access telephony service."); - showError("Error: Could not access the Telephony Service. Is the system running?"); - return; - } - - mUserManager = IUserManager.Stub - .asInterface(ServiceManager.getService(Context.USER_SERVICE)); - if (mUserManager == null) { - Log.w(this, "onRun: Can't access user manager."); - showError("Error: Could not access the User Manager. Is the system running?"); - return; - } - Log.i(this, "onRun: parsing command."); - String command = nextArgRequired(); - switch (command) { - case COMMAND_SET_PHONE_ACCOUNT_ENABLED: - runSetPhoneAccountEnabled(true); - break; - case COMMAND_SET_PHONE_ACCOUNT_DISABLED: - runSetPhoneAccountEnabled(false); - break; - case COMMAND_REGISTER_PHONE_ACCOUNT: - runRegisterPhoneAccount(); - break; - case COMMAND_SET_TEST_CALL_REDIRECTION_APP: - runSetTestCallRedirectionApp(); - break; - case COMMAND_SET_TEST_CALL_SCREENING_APP: - runSetTestCallScreeningApp(); - break; - case COMMAND_ADD_OR_REMOVE_CALL_COMPANION_APP: - runAddOrRemoveCallCompanionApp(); - break; - case COMMAND_SET_PHONE_ACCOUNT_SUGGESTION_COMPONENT: - runSetTestPhoneAcctSuggestionComponent(); - break; - case COMMAND_SET_CALL_DIAGNOSTIC_SERVICE: - runSetCallDiagnosticService(); - break; - case COMMAND_REGISTER_SIM_PHONE_ACCOUNT: - runRegisterSimPhoneAccount(); - break; - case COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT: - runSetUserSelectedOutgoingPhoneAccount(); - break; - case COMMAND_UNREGISTER_PHONE_ACCOUNT: - runUnregisterPhoneAccount(); - break; - case COMMAND_STOP_BLOCK_SUPPRESSION: - runStopBlockSuppression(); - break; - case COMMAND_CLEANUP_STUCK_CALLS: - runCleanupStuckCalls(); - break; - case COMMAND_CLEANUP_ORPHAN_PHONE_ACCOUNTS: - runCleanupOrphanPhoneAccounts(); - break; - case COMMAND_RESET_CAR_MODE: - runResetCarMode(); - break; - case COMMAND_SET_DEFAULT_DIALER: - runSetDefaultDialer(); - break; - case COMMAND_GET_DEFAULT_DIALER: - runGetDefaultDialer(); - break; - case COMMAND_SET_SYSTEM_DIALER: - runSetSystemDialer(); - break; - case COMMAND_GET_SYSTEM_DIALER: - runGetSystemDialer(); - break; - case COMMAND_WAIT_ON_HANDLERS: - runWaitOnHandler(); - break; - case COMMAND_SET_SIM_COUNT: - runSetSimCount(); - break; - case COMMAND_GET_SIM_CONFIG: - runGetSimConfig(); - break; - case COMMAND_GET_MAX_PHONES: - runGetMaxPhones(); - break; - case COMMAND_IS_NON_IN_CALL_SERVICE_BOUND: - runIsNonUiInCallServiceBound(); - break; - case COMMAND_SET_TEST_EMERGENCY_PHONE_ACCOUNT_PACKAGE_FILTER: - runSetEmergencyPhoneAccountPackageFilter(); - break; - case COMMAND_LOG_MARK: - runLogMark(); - break; - default: - Log.w(this, "onRun: unknown command: %s", command); - throw new IllegalArgumentException ("unknown command '" + command + "'"); - } - } - - private void runSetPhoneAccountEnabled(boolean enabled) throws RemoteException { - final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs(); - final boolean success = mTelecomService.enablePhoneAccount(handle, enabled); - if (success) { - System.out.println("Success - " + handle + (enabled ? " enabled." : " disabled.")); - } else { - System.out.println("Error - is " + handle + " a valid PhoneAccount?"); - } - } - - private void runRegisterPhoneAccount() throws RemoteException { - final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs(); - final String label = nextArgRequired(); - PhoneAccount account = PhoneAccount.builder(handle, label) - .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER).build(); - mTelecomService.registerPhoneAccount(account, CALLING_PACKAGE); - System.out.println("Success - " + handle + " registered."); - } - - private void runRegisterSimPhoneAccount() throws RemoteException { - boolean isEmergencyAccount = false; - String opt; - while ((opt = nextOption()) != null) { - switch (opt) { - case "-e": { - isEmergencyAccount = true; - break; - } - } - } - final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs(); - final String label = nextArgRequired(); - final String address = nextArgRequired(); - int capabilities = PhoneAccount.CAPABILITY_CALL_PROVIDER - | PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION - | (isEmergencyAccount ? PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS : 0); - PhoneAccount account = PhoneAccount.builder( - handle, label) - .setAddress(Uri.parse(address)) - .setSubscriptionAddress(Uri.parse(address)) - .setCapabilities(capabilities) - .setShortDescription(label) - .addSupportedUriScheme(PhoneAccount.SCHEME_TEL) - .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL) - .build(); - mTelecomService.registerPhoneAccount(account, CALLING_PACKAGE); - System.out.println("Success - " + handle + " registered."); - } - - private void runSetTestCallRedirectionApp() throws RemoteException { - final String packageName = nextArg(); - mTelecomService.setTestDefaultCallRedirectionApp(packageName); - } - - private void runSetTestCallScreeningApp() throws RemoteException { - final String packageName = nextArg(); - mTelecomService.setTestDefaultCallScreeningApp(packageName); - } - - private void runAddOrRemoveCallCompanionApp() throws RemoteException { - final String packageName = nextArgRequired(); - String isAdded = nextArgRequired(); - boolean isAddedBool = "1".equals(isAdded); - mTelecomService.addOrRemoveTestCallCompanionApp(packageName, isAddedBool); - } - - private void runSetCallDiagnosticService() throws RemoteException { - String packageName = nextArg(); - if ("default".equals(packageName)) packageName = null; - mTelecomService.setTestCallDiagnosticService(packageName); - System.out.println("Success - " + packageName + " set as call diagnostic service."); - } - - private void runSetTestPhoneAcctSuggestionComponent() throws RemoteException { - final String componentName = nextArg(); - mTelecomService.setTestPhoneAcctSuggestionComponent(componentName); - } - - private void runSetUserSelectedOutgoingPhoneAccount() throws RemoteException { - Log.i(this, "runSetUserSelectedOutgoingPhoneAccount"); - final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs(); - mTelecomService.setUserSelectedOutgoingPhoneAccount(handle); - System.out.println("Success - " + handle + " set as default outgoing account."); - } - - private void runUnregisterPhoneAccount() throws RemoteException { - final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs(); - mTelecomService.unregisterPhoneAccount(handle, CALLING_PACKAGE); - System.out.println("Success - " + handle + " unregistered."); - } - - private void runStopBlockSuppression() throws RemoteException { - mTelecomService.stopBlockSuppression(); - } - - private void runCleanupStuckCalls() throws RemoteException { - mTelecomService.cleanupStuckCalls(); - } - - private void runCleanupOrphanPhoneAccounts() throws RemoteException { - System.out.println("Success - cleaned up " + mTelecomService.cleanupOrphanPhoneAccounts() - + " phone accounts."); - } - - private void runResetCarMode() throws RemoteException { - mTelecomService.resetCarMode(); - } - - private void runSetDefaultDialer() throws RemoteException { - String packageName = nextArg(); - if ("default".equals(packageName)) packageName = null; - mTelecomService.setTestDefaultDialer(packageName); - System.out.println("Success - " + packageName + " set as override default dialer."); - } - - private void runSetSystemDialer() throws RemoteException { - final String flatComponentName = nextArg(); - final ComponentName componentName = (flatComponentName.equals("default") - ? null : parseComponentName(flatComponentName)); - mTelecomService.setSystemDialer(componentName); - System.out.println("Success - " + componentName + " set as override system dialer."); - } - - private void runGetDefaultDialer() throws RemoteException { - System.out.println(mTelecomService.getDefaultDialerPackage(CALLING_PACKAGE)); - } - - private void runGetSystemDialer() throws RemoteException { - System.out.println(mTelecomService.getSystemDialerPackage(CALLING_PACKAGE)); - } - - private void runWaitOnHandler() throws RemoteException { - - } - - private void runSetSimCount() throws RemoteException { - if (!callerIsRoot()) { - System.out.println("set-sim-count requires adb root"); - return; - } - int numSims = Integer.parseInt(nextArgRequired()); - System.out.println("Setting sim count to " + numSims + ". Device may reboot"); - mTelephonyManager.switchMultiSimConfig(numSims); - } - - /** - * prints out whether a particular non-ui InCallServices is bound in a call - */ - public void runIsNonUiInCallServiceBound() throws RemoteException { - if (TextUtils.isEmpty(mArgs.peekNextArg())) { - System.out.println("No Argument passed. Please pass a <PACKAGE_NAME> to lookup."); - } else { - System.out.println( - String.valueOf(mTelecomService.isNonUiInCallServiceBound(nextArg()))); - } - } - - /** - * Prints the mSIM config to the console. - * "DSDS" for a phone in DSDS mode - * "" (empty string) for a phone in SS mode - */ - private void runGetSimConfig() throws RemoteException { - System.out.println(TelephonyProperties.multi_sim_config().orElse("")); - } - - private void runGetMaxPhones() throws RemoteException { - // how many logical modems can be potentially active simultaneously - System.out.println(mTelephonyManager.getSupportedModemCount()); - } - - private void runSetEmergencyPhoneAccountPackageFilter() throws RemoteException { - String packageName = mArgs.getNextArg(); - if (TextUtils.isEmpty(packageName)) { - mTelecomService.setTestEmergencyPhoneAccountPackageNameFilter(null); - System.out.println("Success - filter cleared"); - } else { - mTelecomService.setTestEmergencyPhoneAccountPackageNameFilter(packageName); - System.out.println("Success = filter set to " + packageName); - } - - } - - private void runLogMark() throws RemoteException { - String message = Arrays.stream(mArgs.peekRemainingArgs()).collect(Collectors.joining(" ")); - mTelecomService.requestLogMark(message); - } - - private PhoneAccountHandle getPhoneAccountHandleFromArgs() throws RemoteException { - if (TextUtils.isEmpty(mArgs.peekNextArg())) { - return null; - } - final ComponentName component = parseComponentName(nextArgRequired()); - final String accountId = nextArgRequired(); - final String userSnInStr = nextArgRequired(); - UserHandle userHandle; - try { - final int userSn = Integer.parseInt(userSnInStr); - userHandle = UserHandle.of(mUserManager.getUserHandle(userSn)); - } catch (NumberFormatException ex) { - Log.w(this, "getPhoneAccountHandleFromArgs - invalid user %s", userSnInStr); - throw new IllegalArgumentException ("Invalid user serial number " + userSnInStr); - } - return new PhoneAccountHandle(component, accountId, userHandle); - } - - private boolean callerIsRoot() { - return Process.ROOT_UID == Process.myUid(); - } - - private ComponentName parseComponentName(String component) { - ComponentName cn = ComponentName.unflattenFromString(component); - if (cn == null) { - throw new IllegalArgumentException ("Invalid component " + component); - } - return cn; + new TelecomShellCommand(service, context).exec(null, FileDescriptor.in, + FileDescriptor.out, FileDescriptor.err, args); } } diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java index 488292d68620..f726361effd6 100644 --- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java +++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java @@ -292,13 +292,17 @@ public class AccessibilityNodeInfoDumper { int childCount = node.getChildCount(); for (int x = 0; x < childCount; x++) { AccessibilityNodeInfo childNode = node.getChild(x); - + if (childNode == null) { + continue; + } if (!safeCharSeqToString(childNode.getContentDescription()).isEmpty() - || !safeCharSeqToString(childNode.getText()).isEmpty()) + || !safeCharSeqToString(childNode.getText()).isEmpty()) { return true; + } - if (childNafCheck(childNode)) + if (childNafCheck(childNode)) { return true; + } } return false; } diff --git a/cmds/uinput/README.md b/cmds/uinput/README.md index f1775864aca0..5d3f12e0d59f 100644 --- a/cmds/uinput/README.md +++ b/cmds/uinput/README.md @@ -154,8 +154,24 @@ will be unregistered. There is no explicit command for unregistering a device. #### `delay` -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. +Add a delay between the processing of commands. + +The delay will be timed relative to the time base, a reference time which is set when the device is +registered or by the `updateTimeBase` command. Take the following set of example commands: + +1. `register` device +2. `delay` 500ms +3. `inject` some events +4. `delay` 10ms +5. `inject` more events + +If the `register` command is executed at time _X_, the injection at step 3 will be scheduled for +time _X_+500ms. Since scheduling isn't precise, they might actually be injected a few milliseconds +later, but regardless of that the injection at step 5 will always be scheduled for _X_+510ms. This +prevents scheduling delays from building up over time and slowing down the playback of recordings. +However, it does mean that when you expect to wait for an indeterminate period of time, you should +send `updateTimeBase` afterwards to prevent following events being scheduled in the past — see that +command's section for an example. | Field | Type | Description | |:-------------:|:-------------:|:-------------------------- | @@ -173,6 +189,45 @@ Example: } ``` +#### `updateTimeBase` + +Update the time base from which the following events are scheduled to the current time. When +controlling `uinput` over standard input, you should send this command if you want following events +to be scheduled relative to now, rather than the last injection. See the following example set of +commands and the times they will be scheduled to run at: + +1. `register` (say this occurs at time _X_) +2. `delay` 500ms +3. `inject`: scheduled for _X_+500ms +4. `delay` 10ms +5. `inject`: scheduled for _X_+510ms +6. (wait a few seconds) +7. `updateTimeBase` (say this occurs at time _Y_) +8. `delay` 10ms +9. `inject`: scheduled for _Y_+10ms + +Without the `updateTimeBase` command, the final injection would be scheduled for _X_+520ms, which +would be in the past. + +This is useful if you are issuing commands in multiple stages with long or unknown delays in between +them. For example, say you have a test that does the following: + +1. `register` a device +2. `inject` a few events that should launch an app +3. Wait for the app to launch (an indeterminate amount of time, possibly seconds) +4. 1000 `inject` commands separated by `delay` commands of 10ms + +Without `updateTimeBase`, the `inject` commands of step 4 will be scheduled to start immediately +after the events from step 2. That time is probably in the past, so many of the 1000 injections will +be sent immediately. This will likely fill the kernel's event buffers, causing events to be dropped. +Sending `updateTimeBase` before the `inject` commands in step 4 will schedule them relative to the +current time, meaning that they will be all injected with the intended 10ms delays between them. + +| Field | Type | Description | +|:-------------:|:-------------:|:------------------------------- | +| `id` | integer | Device ID | +| `command` | string | Must be set to "updateTimeBase" | + #### `inject` Send an array of uinput event packets to the uinput device diff --git a/cmds/uinput/src/com/android/commands/uinput/Device.java b/cmds/uinput/src/com/android/commands/uinput/Device.java index b452fc7094ba..2cac6313f4f5 100644 --- a/cmds/uinput/src/com/android/commands/uinput/Device.java +++ b/cmds/uinput/src/com/android/commands/uinput/Device.java @@ -102,7 +102,7 @@ public class Device { } mHandler.obtainMessage(MSG_OPEN_UINPUT_DEVICE, args).sendToTarget(); - mTimeToSendNanos = SystemClock.uptimeNanos(); + updateTimeBase(); } private long getTimeToSendMillis() { @@ -135,6 +135,13 @@ public class Device { } /** + * Set the reference time from which future injections are scheduled to the current time. + */ + public void updateTimeBase() { + mTimeToSendNanos = SystemClock.uptimeNanos(); + } + + /** * Delay subsequent device activity by the specified amount of time. * * <p>Note that although the delay is specified in nanoseconds, due to limitations of {@link @@ -216,7 +223,7 @@ public class Device { break; } long offsetMicros = args.argl1; - if (mLastInjectTimestampMicros == -1 || offsetMicros == -1) { + if (mLastInjectTimestampMicros == -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 @@ -232,6 +239,9 @@ public class Device { // 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 if (offsetMicros == -1) { + // No timestamp offset is specified for this event, so use the current time. + mLastInjectTimestampMicros = SystemClock.uptimeNanos() / 1000; } else { mLastInjectTimestampMicros += offsetMicros; } diff --git a/cmds/uinput/src/com/android/commands/uinput/Event.java b/cmds/uinput/src/com/android/commands/uinput/Event.java index 9e7ee0937efe..a3f3d1c1bc0e 100644 --- a/cmds/uinput/src/com/android/commands/uinput/Event.java +++ b/cmds/uinput/src/com/android/commands/uinput/Event.java @@ -36,6 +36,7 @@ public class Event { DELAY, INJECT, SYNC, + UPDATE_TIME_BASE, } // Constants representing evdev event types, from include/uapi/linux/input-event-codes.h in the diff --git a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java index 6994f0cb0e4b..85a9e9b27bde 100644 --- a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java +++ b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.function.Function; import java.util.stream.IntStream; @@ -57,8 +58,7 @@ public class JsonStyleParser implements EventParser { String name = mReader.nextName(); switch (name) { case "id" -> eb.setId(readInt()); - case "command" -> eb.setCommand( - Event.Command.valueOf(mReader.nextString().toUpperCase())); + case "command" -> eb.setCommand(readCommand()); case "name" -> eb.setName(mReader.nextString()); case "vid" -> eb.setVendorId(readInt()); case "pid" -> eb.setProductId(readInt()); @@ -91,6 +91,18 @@ public class JsonStyleParser implements EventParser { return e; } + private Event.Command readCommand() throws IOException { + String commandStr = mReader.nextString(); + return switch (commandStr.toLowerCase(Locale.ROOT)) { + case "register" -> Event.Command.REGISTER; + case "delay" -> Event.Command.DELAY; + case "inject" -> Event.Command.INJECT; + case "sync" -> Event.Command.SYNC; + case "updatetimebase" -> Event.Command.UPDATE_TIME_BASE; + default -> throw new IllegalStateException("Invalid command \"" + commandStr + "\""); + }; + } + private ArrayList<Integer> readInjectedEvents() throws IOException { ArrayList<Integer> data = new ArrayList<>(); try { diff --git a/cmds/uinput/src/com/android/commands/uinput/Uinput.java b/cmds/uinput/src/com/android/commands/uinput/Uinput.java index 760e981c8465..b9967fbdc3b6 100644 --- a/cmds/uinput/src/com/android/commands/uinput/Uinput.java +++ b/cmds/uinput/src/com/android/commands/uinput/Uinput.java @@ -137,6 +137,7 @@ public class Uinput { case INJECT -> d.injectEvent(e.getInjections(), e.getTimestampOffsetMicros()); case DELAY -> d.addDelayNanos(e.getDurationNanos()); case SYNC -> d.syncEvent(e.getSyncToken()); + case UPDATE_TIME_BASE -> d.updateTimeBase(); } } |