am 85a7f99c
: Merge "Refactor how timeouts are calculated. (DO NOT MERGE)" into honeycomb-mr2
* commit '85a7f99cfe066f054d4ddf4feb737f0395c9943b': Refactor how timeouts are calculated. (DO NOT MERGE)
This commit is contained in:
@ -88,6 +88,16 @@ nsecs_t systemTime(int clock = SYSTEM_TIME_MONOTONIC);
|
|||||||
nsecs_t systemTime(int clock);
|
nsecs_t systemTime(int clock);
|
||||||
#endif // def __cplusplus
|
#endif // def __cplusplus
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of milliseconds to wait between the reference time and the timeout time.
|
||||||
|
* If the timeout is in the past relative to the reference time, returns 0.
|
||||||
|
* If the timeout is more than INT_MAX milliseconds in the future relative to the reference time,
|
||||||
|
* such as when timeoutTime == LLONG_MAX, returns -1 to indicate an infinite timeout delay.
|
||||||
|
* Otherwise, returns the difference between the reference time and timeout time
|
||||||
|
* rounded up to the next millisecond.
|
||||||
|
*/
|
||||||
|
int toMillisecondTimeoutDelay(nsecs_t referenceTime, nsecs_t timeoutTime);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
#endif
|
#endif
|
||||||
|
@ -218,14 +218,10 @@ int Looper::pollInner(int timeoutMillis) {
|
|||||||
// Adjust the timeout based on when the next message is due.
|
// Adjust the timeout based on when the next message is due.
|
||||||
if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
|
if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
|
||||||
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
|
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
|
||||||
if (mNextMessageUptime <= now) {
|
int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);
|
||||||
timeoutMillis = 0;
|
if (messageTimeoutMillis >= 0
|
||||||
} else {
|
&& (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {
|
||||||
uint64_t delay = (mNextMessageUptime - now + 999999LL) / 1000000LL;
|
timeoutMillis = messageTimeoutMillis;
|
||||||
if (delay < INT_MAX
|
|
||||||
&& (timeoutMillis < 0 || int(delay) < timeoutMillis)) {
|
|
||||||
timeoutMillis = int(delay);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#if DEBUG_POLL_AND_WAKE
|
#if DEBUG_POLL_AND_WAKE
|
||||||
LOGD("%p ~ pollOnce - next message in %lldns, adjusted timeout: timeoutMillis=%d",
|
LOGD("%p ~ pollOnce - next message in %lldns, adjusted timeout: timeoutMillis=%d",
|
||||||
@ -444,12 +440,11 @@ int Looper::pollAll(int timeoutMillis, int* outFd, int* outEvents, void** outDat
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
nsecs_t timeoutNanos = endTime - systemTime(SYSTEM_TIME_MONOTONIC);
|
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
|
||||||
if (timeoutNanos <= 0) {
|
timeoutMillis = toMillisecondTimeoutDelay(now, endTime);
|
||||||
|
if (timeoutMillis == 0) {
|
||||||
return ALOOPER_POLL_TIMEOUT;
|
return ALOOPER_POLL_TIMEOUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
timeoutMillis = int(nanoseconds_to_milliseconds(timeoutNanos + 999999LL));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
#ifdef HAVE_WIN32_THREADS
|
#ifdef HAVE_WIN32_THREADS
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
@ -53,6 +54,23 @@ nsecs_t systemTime(int clock)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int toMillisecondTimeoutDelay(nsecs_t referenceTime, nsecs_t timeoutTime)
|
||||||
|
{
|
||||||
|
int timeoutDelayMillis;
|
||||||
|
if (timeoutTime > referenceTime) {
|
||||||
|
uint64_t timeoutDelay = uint64_t(timeoutTime - referenceTime);
|
||||||
|
if (timeoutDelay > uint64_t((INT_MAX - 1) * 1000000LL)) {
|
||||||
|
timeoutDelayMillis = -1;
|
||||||
|
} else {
|
||||||
|
timeoutDelayMillis = (timeoutDelay + 999999LL) / 1000000LL;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
timeoutDelayMillis = 0;
|
||||||
|
}
|
||||||
|
return timeoutDelayMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ===========================================================================
|
* ===========================================================================
|
||||||
* DurationTimer
|
* DurationTimer
|
||||||
|
@ -445,7 +445,7 @@ EventHub::Device* EventHub::getDeviceLocked(int32_t deviceId) const {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EventHub::getEvent(RawEvent* outEvent) {
|
bool EventHub::getEvent(int timeoutMillis, RawEvent* outEvent) {
|
||||||
outEvent->deviceId = 0;
|
outEvent->deviceId = 0;
|
||||||
outEvent->type = 0;
|
outEvent->type = 0;
|
||||||
outEvent->scanCode = 0;
|
outEvent->scanCode = 0;
|
||||||
@ -598,13 +598,20 @@ bool EventHub::getEvent(RawEvent* outEvent) {
|
|||||||
// when this happens, the EventHub holds onto its own user wake lock while the client
|
// when this happens, the EventHub holds onto its own user wake lock while the client
|
||||||
// is processing events. Thus the system can only sleep if there are no events
|
// is processing events. Thus the system can only sleep if there are no events
|
||||||
// pending or currently being processed.
|
// pending or currently being processed.
|
||||||
|
//
|
||||||
|
// The timeout is advisory only. If the device is asleep, it will not wake just to
|
||||||
|
// service the timeout.
|
||||||
release_wake_lock(WAKE_LOCK_ID);
|
release_wake_lock(WAKE_LOCK_ID);
|
||||||
|
|
||||||
int pollResult = poll(mFds.editArray(), mFds.size(), -1);
|
int pollResult = poll(mFds.editArray(), mFds.size(), timeoutMillis);
|
||||||
|
|
||||||
acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
|
acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
|
||||||
|
|
||||||
if (pollResult <= 0) {
|
if (pollResult == 0) {
|
||||||
|
// Timed out.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (pollResult < 0) {
|
||||||
if (errno != EINTR) {
|
if (errno != EINTR) {
|
||||||
LOGW("poll failed (errno=%d)\n", errno);
|
LOGW("poll failed (errno=%d)\n", errno);
|
||||||
usleep(100000);
|
usleep(100000);
|
||||||
|
@ -186,8 +186,13 @@ public:
|
|||||||
* This ensures that the device will not go to sleep while the event is being processed.
|
* This ensures that the device will not go to sleep while the event is being processed.
|
||||||
* If the device needs to remain awake longer than that, then the caller is responsible
|
* If the device needs to remain awake longer than that, then the caller is responsible
|
||||||
* for taking care of it (say, by poking the power manager user activity timer).
|
* for taking care of it (say, by poking the power manager user activity timer).
|
||||||
|
*
|
||||||
|
* The timeout is advisory only. If the device is asleep, it will not wake just to
|
||||||
|
* service the timeout.
|
||||||
|
*
|
||||||
|
* Returns true if an event was obtained, false if the timeout expired.
|
||||||
*/
|
*/
|
||||||
virtual bool getEvent(RawEvent* outEvent) = 0;
|
virtual bool getEvent(int timeoutMillis, RawEvent* outEvent) = 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Query current input state.
|
* Query current input state.
|
||||||
@ -244,7 +249,7 @@ public:
|
|||||||
virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes,
|
virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes,
|
||||||
const int32_t* keyCodes, uint8_t* outFlags) const;
|
const int32_t* keyCodes, uint8_t* outFlags) const;
|
||||||
|
|
||||||
virtual bool getEvent(RawEvent* outEvent);
|
virtual bool getEvent(int timeoutMillis, RawEvent* outEvent);
|
||||||
|
|
||||||
virtual bool hasLed(int32_t deviceId, int32_t led) const;
|
virtual bool hasLed(int32_t deviceId, int32_t led) const;
|
||||||
virtual void setLedState(int32_t deviceId, int32_t led, bool on);
|
virtual void setLedState(int32_t deviceId, int32_t led, bool on);
|
||||||
|
@ -246,15 +246,7 @@ void InputDispatcher::dispatchOnce() {
|
|||||||
|
|
||||||
// Wait for callback or timeout or wake. (make sure we round up, not down)
|
// Wait for callback or timeout or wake. (make sure we round up, not down)
|
||||||
nsecs_t currentTime = now();
|
nsecs_t currentTime = now();
|
||||||
int32_t timeoutMillis;
|
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
|
||||||
if (nextWakeupTime > currentTime) {
|
|
||||||
uint64_t timeout = uint64_t(nextWakeupTime - currentTime);
|
|
||||||
timeout = (timeout + 999999LL) / 1000000LL;
|
|
||||||
timeoutMillis = timeout > INT_MAX ? -1 : int32_t(timeout);
|
|
||||||
} else {
|
|
||||||
timeoutMillis = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
mLooper->pollOnce(timeoutMillis);
|
mLooper->pollOnce(timeoutMillis);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,7 +231,7 @@ InputReader::InputReader(const sp<EventHubInterface>& eventHub,
|
|||||||
const sp<InputReaderPolicyInterface>& policy,
|
const sp<InputReaderPolicyInterface>& policy,
|
||||||
const sp<InputDispatcherInterface>& dispatcher) :
|
const sp<InputDispatcherInterface>& dispatcher) :
|
||||||
mEventHub(eventHub), mPolicy(policy), mDispatcher(dispatcher),
|
mEventHub(eventHub), mPolicy(policy), mDispatcher(dispatcher),
|
||||||
mGlobalMetaState(0), mDisableVirtualKeysTimeout(-1) {
|
mGlobalMetaState(0), mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX) {
|
||||||
configureExcludedDevices();
|
configureExcludedDevices();
|
||||||
updateGlobalMetaState();
|
updateGlobalMetaState();
|
||||||
updateInputConfiguration();
|
updateInputConfiguration();
|
||||||
@ -244,16 +244,28 @@ InputReader::~InputReader() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void InputReader::loopOnce() {
|
void InputReader::loopOnce() {
|
||||||
|
int32_t timeoutMillis = -1;
|
||||||
|
if (mNextTimeout != LLONG_MAX) {
|
||||||
|
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
|
||||||
|
timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
RawEvent rawEvent;
|
RawEvent rawEvent;
|
||||||
mEventHub->getEvent(& rawEvent);
|
if (mEventHub->getEvent(timeoutMillis, &rawEvent)) {
|
||||||
|
|
||||||
#if DEBUG_RAW_EVENTS
|
#if DEBUG_RAW_EVENTS
|
||||||
LOGD("Input event: device=%d type=0x%04x scancode=0x%04x keycode=0x%04x value=0x%04x",
|
LOGD("Input event: device=%d type=0x%04x scancode=0x%04x keycode=0x%04x value=0x%04x",
|
||||||
rawEvent.deviceId, rawEvent.type, rawEvent.scanCode, rawEvent.keyCode,
|
rawEvent.deviceId, rawEvent.type, rawEvent.scanCode, rawEvent.keyCode,
|
||||||
rawEvent.value);
|
rawEvent.value);
|
||||||
#endif
|
#endif
|
||||||
|
process(&rawEvent);
|
||||||
process(& rawEvent);
|
} else {
|
||||||
|
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
|
||||||
|
#if DEBUG_RAW_EVENTS
|
||||||
|
LOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);
|
||||||
|
#endif
|
||||||
|
mNextTimeout = LLONG_MAX;
|
||||||
|
timeoutExpired(now);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputReader::process(const RawEvent* rawEvent) {
|
void InputReader::process(const RawEvent* rawEvent) {
|
||||||
@ -415,6 +427,19 @@ void InputReader::consumeEvent(const RawEvent* rawEvent) {
|
|||||||
} // release device registry reader lock
|
} // release device registry reader lock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InputReader::timeoutExpired(nsecs_t when) {
|
||||||
|
{ // acquire device registry reader lock
|
||||||
|
RWLock::AutoRLock _rl(mDeviceRegistryLock);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < mDevices.size(); i++) {
|
||||||
|
InputDevice* device = mDevices.valueAt(i);
|
||||||
|
if (!device->isIgnored()) {
|
||||||
|
device->timeoutExpired(when);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // release device registry reader lock
|
||||||
|
}
|
||||||
|
|
||||||
void InputReader::handleConfigurationChanged(nsecs_t when) {
|
void InputReader::handleConfigurationChanged(nsecs_t when) {
|
||||||
// Reset global meta state because it depends on the list of all configured devices.
|
// Reset global meta state because it depends on the list of all configured devices.
|
||||||
updateGlobalMetaState();
|
updateGlobalMetaState();
|
||||||
@ -525,6 +550,12 @@ void InputReader::fadePointer() {
|
|||||||
} // release device registry reader lock
|
} // release device registry reader lock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InputReader::requestTimeoutAtTime(nsecs_t when) {
|
||||||
|
if (when < mNextTimeout) {
|
||||||
|
mNextTimeout = when;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void InputReader::getInputConfiguration(InputConfiguration* outConfiguration) {
|
void InputReader::getInputConfiguration(InputConfiguration* outConfiguration) {
|
||||||
{ // acquire state lock
|
{ // acquire state lock
|
||||||
AutoMutex _l(mStateLock);
|
AutoMutex _l(mStateLock);
|
||||||
@ -762,6 +793,14 @@ void InputDevice::process(const RawEvent* rawEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InputDevice::timeoutExpired(nsecs_t when) {
|
||||||
|
size_t numMappers = mMappers.size();
|
||||||
|
for (size_t i = 0; i < numMappers; i++) {
|
||||||
|
InputMapper* mapper = mMappers[i];
|
||||||
|
mapper->timeoutExpired(when);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void InputDevice::getDeviceInfo(InputDeviceInfo* outDeviceInfo) {
|
void InputDevice::getDeviceInfo(InputDeviceInfo* outDeviceInfo) {
|
||||||
outDeviceInfo->initialize(mId, mName);
|
outDeviceInfo->initialize(mId, mName);
|
||||||
|
|
||||||
@ -853,6 +892,9 @@ void InputMapper::configure() {
|
|||||||
void InputMapper::reset() {
|
void InputMapper::reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InputMapper::timeoutExpired(nsecs_t when) {
|
||||||
|
}
|
||||||
|
|
||||||
int32_t InputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
|
int32_t InputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
|
||||||
return AKEY_STATE_UNKNOWN;
|
return AKEY_STATE_UNKNOWN;
|
||||||
}
|
}
|
||||||
@ -2556,6 +2598,19 @@ void TouchInputMapper::reset() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TouchInputMapper::syncTouch(nsecs_t when, bool havePointerIds) {
|
void TouchInputMapper::syncTouch(nsecs_t when, bool havePointerIds) {
|
||||||
|
#if DEBUG_RAW_EVENTS
|
||||||
|
if (!havePointerIds) {
|
||||||
|
LOGD("syncTouch: pointerCount=%d, no pointer ids", mCurrentTouch.pointerCount);
|
||||||
|
} else {
|
||||||
|
LOGD("syncTouch: pointerCount=%d, up=0x%08x, down=0x%08x, move=0x%08x, "
|
||||||
|
"last=0x%08x, current=0x%08x", mCurrentTouch.pointerCount,
|
||||||
|
mLastTouch.idBits.value & ~mCurrentTouch.idBits.value,
|
||||||
|
mCurrentTouch.idBits.value & ~mLastTouch.idBits.value,
|
||||||
|
mLastTouch.idBits.value & mCurrentTouch.idBits.value,
|
||||||
|
mLastTouch.idBits.value, mCurrentTouch.idBits.value);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Preprocess pointer data.
|
// Preprocess pointer data.
|
||||||
if (mParameters.useBadTouchFilter) {
|
if (mParameters.useBadTouchFilter) {
|
||||||
if (applyBadTouchFilter()) {
|
if (applyBadTouchFilter()) {
|
||||||
@ -2569,7 +2624,7 @@ void TouchInputMapper::syncTouch(nsecs_t when, bool havePointerIds) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! havePointerIds) {
|
if (!havePointerIds) {
|
||||||
calculatePointerIds();
|
calculatePointerIds();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,6 +159,8 @@ public:
|
|||||||
|
|
||||||
virtual void fadePointer() = 0;
|
virtual void fadePointer() = 0;
|
||||||
|
|
||||||
|
virtual void requestTimeoutAtTime(nsecs_t when) = 0;
|
||||||
|
|
||||||
virtual InputReaderPolicyInterface* getPolicy() = 0;
|
virtual InputReaderPolicyInterface* getPolicy() = 0;
|
||||||
virtual InputDispatcherInterface* getDispatcher() = 0;
|
virtual InputDispatcherInterface* getDispatcher() = 0;
|
||||||
virtual EventHubInterface* getEventHub() = 0;
|
virtual EventHubInterface* getEventHub() = 0;
|
||||||
@ -233,6 +235,7 @@ private:
|
|||||||
void configureExcludedDevices();
|
void configureExcludedDevices();
|
||||||
|
|
||||||
void consumeEvent(const RawEvent* rawEvent);
|
void consumeEvent(const RawEvent* rawEvent);
|
||||||
|
void timeoutExpired(nsecs_t when);
|
||||||
|
|
||||||
void handleConfigurationChanged(nsecs_t when);
|
void handleConfigurationChanged(nsecs_t when);
|
||||||
|
|
||||||
@ -253,6 +256,9 @@ private:
|
|||||||
virtual bool shouldDropVirtualKey(nsecs_t now,
|
virtual bool shouldDropVirtualKey(nsecs_t now,
|
||||||
InputDevice* device, int32_t keyCode, int32_t scanCode);
|
InputDevice* device, int32_t keyCode, int32_t scanCode);
|
||||||
|
|
||||||
|
nsecs_t mNextTimeout;
|
||||||
|
virtual void requestTimeoutAtTime(nsecs_t when);
|
||||||
|
|
||||||
// state queries
|
// state queries
|
||||||
typedef int32_t (InputDevice::*GetStateFunc)(uint32_t sourceMask, int32_t code);
|
typedef int32_t (InputDevice::*GetStateFunc)(uint32_t sourceMask, int32_t code);
|
||||||
int32_t getState(int32_t deviceId, uint32_t sourceMask, int32_t code,
|
int32_t getState(int32_t deviceId, uint32_t sourceMask, int32_t code,
|
||||||
@ -296,6 +302,7 @@ public:
|
|||||||
void configure();
|
void configure();
|
||||||
void reset();
|
void reset();
|
||||||
void process(const RawEvent* rawEvent);
|
void process(const RawEvent* rawEvent);
|
||||||
|
void timeoutExpired(nsecs_t when);
|
||||||
|
|
||||||
void getDeviceInfo(InputDeviceInfo* outDeviceInfo);
|
void getDeviceInfo(InputDeviceInfo* outDeviceInfo);
|
||||||
int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
|
int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
|
||||||
@ -352,6 +359,7 @@ public:
|
|||||||
virtual void configure();
|
virtual void configure();
|
||||||
virtual void reset();
|
virtual void reset();
|
||||||
virtual void process(const RawEvent* rawEvent) = 0;
|
virtual void process(const RawEvent* rawEvent) = 0;
|
||||||
|
virtual void timeoutExpired(nsecs_t when);
|
||||||
|
|
||||||
virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
|
virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
|
||||||
virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
|
virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
|
||||||
|
@ -622,7 +622,7 @@ private:
|
|||||||
mExcludedDevices.add(String8(deviceName));
|
mExcludedDevices.add(String8(deviceName));
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual bool getEvent(RawEvent* outEvent) {
|
virtual bool getEvent(int timeoutMillis, RawEvent* outEvent) {
|
||||||
if (mEvents.empty()) {
|
if (mEvents.empty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -780,6 +780,9 @@ private:
|
|||||||
|
|
||||||
virtual void fadePointer() {
|
virtual void fadePointer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual void requestTimeoutAtTime(nsecs_t when) {
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user