diff options
Diffstat (limited to 'services')
131 files changed, 31474 insertions, 7431 deletions
diff --git a/services/audioflinger/A2dpAudioInterface.cpp b/services/audioflinger/A2dpAudioInterface.cpp index 995e31c..d926cb1 100644 --- a/services/audioflinger/A2dpAudioInterface.cpp +++ b/services/audioflinger/A2dpAudioInterface.cpp @@ -23,10 +23,13 @@ #include "A2dpAudioInterface.h" #include "audio/liba2dp.h" - +#include <hardware_legacy/power.h> namespace android { +static const char *sA2dpWakeLock = "A2dpOutputStream"; +#define MAX_WRITE_RETRIES 5 + // ---------------------------------------------------------------------------- //AudioHardwareInterface* A2dpAudioInterface::createA2dpInterface() @@ -257,52 +260,74 @@ status_t A2dpAudioInterface::A2dpAudioStreamOut::set( if (pRate) *pRate = lRate; mDevice = device; + mBufferDurationUs = ((bufferSize() * 1000 )/ frameSize() / sampleRate()) * 1000; return NO_ERROR; } A2dpAudioInterface::A2dpAudioStreamOut::~A2dpAudioStreamOut() { LOGV("A2dpAudioStreamOut destructor"); - standby(); close(); LOGV("A2dpAudioStreamOut destructor returning from close()"); } ssize_t A2dpAudioInterface::A2dpAudioStreamOut::write(const void* buffer, size_t bytes) { - Mutex::Autolock lock(mLock); - - size_t remaining = bytes; status_t status = -1; + { + Mutex::Autolock lock(mLock); - if (!mBluetoothEnabled || mClosing || mSuspended) { - LOGV("A2dpAudioStreamOut::write(), but bluetooth disabled \ - mBluetoothEnabled %d, mClosing %d, mSuspended %d", - mBluetoothEnabled, mClosing, mSuspended); - goto Error; - } - - status = init(); - if (status < 0) - goto Error; + size_t remaining = bytes; - while (remaining > 0) { - status = a2dp_write(mData, buffer, remaining); - if (status <= 0) { - LOGE("a2dp_write failed err: %d\n", status); + if (!mBluetoothEnabled || mClosing || mSuspended) { + LOGV("A2dpAudioStreamOut::write(), but bluetooth disabled \ + mBluetoothEnabled %d, mClosing %d, mSuspended %d", + mBluetoothEnabled, mClosing, mSuspended); goto Error; } - remaining -= status; - buffer = ((char *)buffer) + status; - } - mStandby = false; + if (mStandby) { + acquire_wake_lock (PARTIAL_WAKE_LOCK, sA2dpWakeLock); + mStandby = false; + mLastWriteTime = systemTime(); + } + + status = init(); + if (status < 0) + goto Error; + + int retries = MAX_WRITE_RETRIES; + while (remaining > 0 && retries) { + status = a2dp_write(mData, buffer, remaining); + if (status < 0) { + LOGE("a2dp_write failed err: %d\n", status); + goto Error; + } + if (status == 0) { + retries--; + } + remaining -= status; + buffer = (char *)buffer + status; + } - return bytes; + // if A2DP sink runs abnormally fast, sleep a little so that audioflinger mixer thread + // does no spin and starve other threads. + // NOTE: It is likely that the A2DP headset is being disconnected + nsecs_t now = systemTime(); + if ((uint32_t)ns2us(now - mLastWriteTime) < (mBufferDurationUs >> 2)) { + LOGV("A2DP sink runs too fast"); + usleep(mBufferDurationUs - (uint32_t)ns2us(now - mLastWriteTime)); + } + mLastWriteTime = now; + return bytes; + } Error: + + standby(); + // Simulate audio output timing in case of error - usleep(((bytes * 1000 )/ frameSize() / sampleRate()) * 1000); + usleep(mBufferDurationUs); return status; } @@ -324,19 +349,22 @@ status_t A2dpAudioInterface::A2dpAudioStreamOut::init() status_t A2dpAudioInterface::A2dpAudioStreamOut::standby() { - int result = 0; - - if (mClosing) { - LOGV("Ignore standby, closing"); - return result; - } - Mutex::Autolock lock(mLock); + return standby_l(); +} + +status_t A2dpAudioInterface::A2dpAudioStreamOut::standby_l() +{ + int result = NO_ERROR; if (!mStandby) { - result = a2dp_stop(mData); - if (result == 0) - mStandby = true; + LOGV_IF(mClosing || !mBluetoothEnabled, "Standby skip stop: closing %d enabled %d", + mClosing, mBluetoothEnabled); + if (!mClosing && mBluetoothEnabled) { + result = a2dp_stop(mData); + } + release_wake_lock(sA2dpWakeLock); + mStandby = true; } return result; @@ -362,6 +390,9 @@ status_t A2dpAudioInterface::A2dpAudioStreamOut::setParameters(const String8& ke key = String8("closing"); if (param.get(key, value) == NO_ERROR) { mClosing = (value == "true"); + if (mClosing) { + standby(); + } param.remove(key); } key = AudioParameter::keyRouting; @@ -444,6 +475,7 @@ status_t A2dpAudioInterface::A2dpAudioStreamOut::close() status_t A2dpAudioInterface::A2dpAudioStreamOut::close_l() { + standby_l(); if (mData) { LOGV("A2dpAudioStreamOut::close_l() calling a2dp_cleanup(mData)"); a2dp_cleanup(mData); diff --git a/services/audioflinger/A2dpAudioInterface.h b/services/audioflinger/A2dpAudioInterface.h index 48154f9..dbe2c6a 100644 --- a/services/audioflinger/A2dpAudioInterface.h +++ b/services/audioflinger/A2dpAudioInterface.h @@ -103,6 +103,7 @@ private: status_t setAddress(const char* address); status_t setBluetoothEnabled(bool enabled); status_t setSuspended(bool onOff); + status_t standby_l(); private: int mFd; @@ -116,6 +117,8 @@ private: uint32_t mDevice; bool mClosing; bool mSuspended; + nsecs_t mLastWriteTime; + uint32_t mBufferDurationUs; }; friend class A2dpAudioStreamOut; diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp index 5935bf9..4ec16c1 100644 --- a/services/audioflinger/AudioFlinger.cpp +++ b/services/audioflinger/AudioFlinger.cpp @@ -131,18 +131,21 @@ AudioFlinger::AudioFlinger() : BnAudioFlinger(), mAudioHardware(0), mMasterVolume(1.0f), mMasterMute(false), mNextUniqueId(1) { + Mutex::Autolock _l(mLock); + mHardwareStatus = AUDIO_HW_IDLE; mAudioHardware = AudioHardwareInterface::create(); mHardwareStatus = AUDIO_HW_INIT; if (mAudioHardware->initCheck() == NO_ERROR) { - // open 16-bit output stream for s/w mixer + AutoMutex lock(mHardwareLock); mMode = AudioSystem::MODE_NORMAL; - setMode(mMode); - - setMasterVolume(1.0f); - setMasterMute(false); + mHardwareStatus = AUDIO_HW_SET_MODE; + mAudioHardware->setMode(mMode); + mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME; + mAudioHardware->setMasterVolume(1.0f); + mHardwareStatus = AUDIO_HW_IDLE; } else { LOGE("Couldn't even initialize the stubbed audio hardware!"); } @@ -343,7 +346,7 @@ sp<IAudioTrack> AudioFlinger::createTrack( lSessionId = *sessionId; } else { // if no audio session id is provided, create one here - lSessionId = nextUniqueId(); + lSessionId = nextUniqueId_l(); if (sessionId != NULL) { *sessionId = lSessionId; } @@ -440,13 +443,16 @@ status_t AudioFlinger::setMasterVolume(float value) } // when hw supports master volume, don't scale in sw mixer - AutoMutex lock(mHardwareLock); - mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME; - if (mAudioHardware->setMasterVolume(value) == NO_ERROR) { - value = 1.0f; + { // scope for the lock + AutoMutex lock(mHardwareLock); + mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME; + if (mAudioHardware->setMasterVolume(value) == NO_ERROR) { + value = 1.0f; + } + mHardwareStatus = AUDIO_HW_IDLE; } - mHardwareStatus = AUDIO_HW_IDLE; + Mutex::Autolock _l(mLock); mMasterVolume = value; for (uint32_t i = 0; i < mPlaybackThreads.size(); i++) mPlaybackThreads.valueAt(i)->setMasterVolume(value); @@ -517,6 +523,7 @@ status_t AudioFlinger::setMasterMute(bool muted) return PERMISSION_DENIED; } + Mutex::Autolock _l(mLock); mMasterMute = muted; for (uint32_t i = 0; i < mPlaybackThreads.size(); i++) mPlaybackThreads.valueAt(i)->setMasterMute(muted); @@ -579,6 +586,7 @@ status_t AudioFlinger::setStreamMute(int stream, bool muted) return BAD_VALUE; } + AutoMutex lock(mLock); mStreamTypes[stream].mute = muted; for (uint32_t i = 0; i < mPlaybackThreads.size(); i++) mPlaybackThreads.valueAt(i)->setStreamMute(stream, muted); @@ -616,17 +624,6 @@ bool AudioFlinger::streamMute(int stream) const return mStreamTypes[stream].mute; } -bool AudioFlinger::isStreamActive(int stream) const -{ - Mutex::Autolock _l(mLock); - for (uint32_t i = 0; i < mPlaybackThreads.size(); i++) { - if (mPlaybackThreads.valueAt(i)->isStreamActive(stream)) { - return true; - } - } - return false; -} - status_t AudioFlinger::setParameters(int ioHandle, const String8& keyValuePairs) { status_t result; @@ -1283,20 +1280,6 @@ bool AudioFlinger::PlaybackThread::streamMute(int stream) const return mStreamTypes[stream].mute; } -bool AudioFlinger::PlaybackThread::isStreamActive(int stream) const -{ - Mutex::Autolock _l(mLock); - size_t count = mActiveTracks.size(); - for (size_t i = 0 ; i < count ; ++i) { - sp<Track> t = mActiveTracks[i].promote(); - if (t == 0) continue; - Track* const track = t.get(); - if (t->type() == stream) - return true; - } - return false; -} - // addTrack_l() must be called with ThreadBase::mLock held status_t AudioFlinger::PlaybackThread::addTrack_l(const sp<Track>& track) { @@ -3699,7 +3682,7 @@ sp<IAudioRecord> AudioFlinger::openRecord( if (sessionId != NULL && *sessionId != AudioSystem::SESSION_OUTPUT_MIX) { lSessionId = *sessionId; } else { - lSessionId = nextUniqueId(); + lSessionId = nextUniqueId_l(); if (sessionId != NULL) { *sessionId = lSessionId; } @@ -4300,7 +4283,7 @@ int AudioFlinger::openOutput(uint32_t *pDevices, mHardwareStatus = AUDIO_HW_IDLE; if (output != 0) { - int id = nextUniqueId(); + int id = nextUniqueId_l(); if ((flags & AudioSystem::OUTPUT_FLAG_DIRECT) || (format != AudioSystem::PCM_16_BIT) || (channels != AudioSystem::CHANNEL_OUT_STEREO)) { @@ -4348,7 +4331,7 @@ int AudioFlinger::openDuplicateOutput(int output1, int output2) return 0; } - int id = nextUniqueId(); + int id = nextUniqueId_l(); DuplicatingThread *thread = new DuplicatingThread(this, thread1, id); thread->addOutputTrack(thread2); mPlaybackThreads.add(id, thread); @@ -4473,7 +4456,7 @@ int AudioFlinger::openInput(uint32_t *pDevices, } if (input != 0) { - int id = nextUniqueId(); + int id = nextUniqueId_l(); // Start record thread thread = new RecordThread(this, input, reqSamplingRate, reqChannels, id); mRecordThreads.add(id, thread); @@ -4543,7 +4526,8 @@ status_t AudioFlinger::setStreamOutput(uint32_t stream, int output) int AudioFlinger::newAudioSessionId() { - return nextUniqueId(); + AutoMutex _l(mLock); + return nextUniqueId_l(); } // checkPlaybackThread_l() must be called with AudioFlinger::mLock held @@ -4578,9 +4562,10 @@ AudioFlinger::RecordThread *AudioFlinger::checkRecordThread_l(int input) const return thread; } -int AudioFlinger::nextUniqueId() +// nextUniqueId_l() must be called with AudioFlinger::mLock held +int AudioFlinger::nextUniqueId_l() { - return android_atomic_inc(&mNextUniqueId); + return mNextUniqueId++; } // ---------------------------------------------------------------------------- @@ -4967,7 +4952,7 @@ sp<AudioFlinger::EffectHandle> AudioFlinger::PlaybackThread::createEffect_l( LOGV("createEffect_l() got effect %p on chain %p", effect == 0 ? 0 : effect.get(), chain.get()); if (effect == 0) { - int id = mAudioFlinger->nextUniqueId(); + int id = mAudioFlinger->nextUniqueId_l(); // Check CPU and memory usage lStatus = AudioSystem::registerEffect(desc, mId, chain->strategy(), sessionId, id); if (lStatus != NO_ERROR) { diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h index 5917632..81f2eb4 100644 --- a/services/audioflinger/AudioFlinger.h +++ b/services/audioflinger/AudioFlinger.h @@ -107,8 +107,6 @@ public: virtual status_t setMicMute(bool state); virtual bool getMicMute() const; - virtual bool isStreamActive(int stream) const; - virtual status_t setParameters(int ioHandle, const String8& keyValuePairs); virtual String8 getParameters(int ioHandle, const String8& keys); @@ -579,8 +577,6 @@ private: virtual float streamVolume(int stream) const; virtual bool streamMute(int stream) const; - bool isStreamActive(int stream) const; - sp<Track> createTrack_l( const sp<AudioFlinger::Client>& client, int streamType, @@ -785,7 +781,7 @@ private: float streamVolumeInternal(int stream) const { return mStreamTypes[stream].volume; } void audioConfigChanged_l(int event, int ioHandle, void *param2); - int nextUniqueId(); + int nextUniqueId_l(); status_t moveEffectChain_l(int session, AudioFlinger::PlaybackThread *srcThread, AudioFlinger::PlaybackThread *dstThread, diff --git a/services/audioflinger/AudioPolicyManagerBase.cpp b/services/audioflinger/AudioPolicyManagerBase.cpp index 4612af1..afa9acc 100644 --- a/services/audioflinger/AudioPolicyManagerBase.cpp +++ b/services/audioflinger/AudioPolicyManagerBase.cpp @@ -312,8 +312,7 @@ void AudioPolicyManagerBase::setPhoneState(int state) // Flag that ringtone volume must be limited to music volume until we exit MODE_RINGTONE if (state == AudioSystem::MODE_RINGTONE && - (hwOutputDesc->mRefCount[AudioSystem::MUSIC] || - (systemTime() - mMusicStopTime) < seconds(SONIFICATION_HEADSET_MUSIC_DELAY))) { + isStreamActive(AudioSystem::MUSIC, SONIFICATION_HEADSET_MUSIC_DELAY)) { mLimitRingtoneVolume = true; } else { mLimitRingtoneVolume = false; @@ -343,7 +342,9 @@ void AudioPolicyManagerBase::setForceUse(AudioSystem::force_use usage, AudioSyst break; case AudioSystem::FOR_MEDIA: if (config != AudioSystem::FORCE_HEADPHONES && config != AudioSystem::FORCE_BT_A2DP && - config != AudioSystem::FORCE_WIRED_ACCESSORY && config != AudioSystem::FORCE_NONE) { + config != AudioSystem::FORCE_WIRED_ACCESSORY && + config != AudioSystem::FORCE_ANALOG_DOCK && + config != AudioSystem::FORCE_DIGITAL_DOCK && config != AudioSystem::FORCE_NONE) { LOGW("setForceUse() invalid config %d for FOR_MEDIA", config); return; } @@ -359,7 +360,10 @@ void AudioPolicyManagerBase::setForceUse(AudioSystem::force_use usage, AudioSyst break; case AudioSystem::FOR_DOCK: if (config != AudioSystem::FORCE_NONE && config != AudioSystem::FORCE_BT_CAR_DOCK && - config != AudioSystem::FORCE_BT_DESK_DOCK && config != AudioSystem::FORCE_WIRED_ACCESSORY) { + config != AudioSystem::FORCE_BT_DESK_DOCK && + config != AudioSystem::FORCE_WIRED_ACCESSORY && + config != AudioSystem::FORCE_ANALOG_DOCK && + config != AudioSystem::FORCE_DIGITAL_DOCK) { LOGW("setForceUse() invalid config %d for FOR_DOCK", config); } forceVolumeReeval = true; @@ -473,6 +477,7 @@ audio_io_handle_t AudioPolicyManagerBase::getOutput(AudioSystem::stream_type str outputDesc->mLatency = 0; outputDesc->mFlags = (AudioSystem::output_flags)(flags | AudioSystem::OUTPUT_FLAG_DIRECT); outputDesc->mRefCount[stream] = 0; + outputDesc->mStopTime[stream] = 0; output = mpClientInterface->openOutput(&outputDesc->mDevice, &outputDesc->mSamplingRate, &outputDesc->mFormat, @@ -601,10 +606,8 @@ status_t AudioPolicyManagerBase::stopOutput(audio_io_handle_t output, if (outputDesc->mRefCount[stream] > 0) { // decrement usage count of this stream on the output outputDesc->changeRefCount(stream, -1); - // store time at which the last music track was stopped - see computeVolume() - if (stream == AudioSystem::MUSIC) { - mMusicStopTime = systemTime(); - } + // store time at which the stream was stopped - see isStreamActive() + outputDesc->mStopTime[stream] = systemTime(); setOutputDevice(output, getNewDevice(output)); @@ -914,6 +917,19 @@ status_t AudioPolicyManagerBase::unregisterEffect(int id) return NO_ERROR; } +bool AudioPolicyManagerBase::isStreamActive(int stream, uint32_t inPastMs) const +{ + nsecs_t sysTime = systemTime(); + for (size_t i = 0; i < mOutputs.size(); i++) { + if (mOutputs.valueAt(i)->mRefCount[stream] != 0 || + ns2ms(sysTime - mOutputs.valueAt(i)->mStopTime[stream]) < inPastMs) { + return true; + } + } + return false; +} + + status_t AudioPolicyManagerBase::dump(int fd) { const size_t SIZE = 256; @@ -1004,7 +1020,7 @@ AudioPolicyManagerBase::AudioPolicyManagerBase(AudioPolicyClientInterface *clien Thread(false), #endif //AUDIO_POLICY_TEST mPhoneState(AudioSystem::MODE_NORMAL), mRingerMode(0), - mMusicStopTime(0), mLimitRingtoneVolume(false), mLastVoiceVolume(-1.0f), + mLimitRingtoneVolume(false), mLastVoiceVolume(-1.0f), mTotalEffectsCpuLoad(0), mTotalEffectsMemory(0), mA2dpSuspended(false) { @@ -1047,25 +1063,27 @@ AudioPolicyManagerBase::AudioPolicyManagerBase(AudioPolicyClientInterface *clien updateDeviceForStrategy(); #ifdef AUDIO_POLICY_TEST - AudioParameter outputCmd = AudioParameter(); - outputCmd.addInt(String8("set_id"), 0); - mpClientInterface->setParameters(mHardwareOutput, outputCmd.toString()); - - mTestDevice = AudioSystem::DEVICE_OUT_SPEAKER; - mTestSamplingRate = 44100; - mTestFormat = AudioSystem::PCM_16_BIT; - mTestChannels = AudioSystem::CHANNEL_OUT_STEREO; - mTestLatencyMs = 0; - mCurOutput = 0; - mDirectOutput = false; - for (int i = 0; i < NUM_TEST_OUTPUTS; i++) { - mTestOutputs[i] = 0; - } + if (mHardwareOutput != 0) { + AudioParameter outputCmd = AudioParameter(); + outputCmd.addInt(String8("set_id"), 0); + mpClientInterface->setParameters(mHardwareOutput, outputCmd.toString()); + + mTestDevice = AudioSystem::DEVICE_OUT_SPEAKER; + mTestSamplingRate = 44100; + mTestFormat = AudioSystem::PCM_16_BIT; + mTestChannels = AudioSystem::CHANNEL_OUT_STEREO; + mTestLatencyMs = 0; + mCurOutput = 0; + mDirectOutput = false; + for (int i = 0; i < NUM_TEST_OUTPUTS; i++) { + mTestOutputs[i] = 0; + } - const size_t SIZE = 256; - char buffer[SIZE]; - snprintf(buffer, SIZE, "AudioPolicyManagerTest"); - run(buffer, ANDROID_PRIORITY_AUDIO); + const size_t SIZE = 256; + char buffer[SIZE]; + snprintf(buffer, SIZE, "AudioPolicyManagerTest"); + run(buffer, ANDROID_PRIORITY_AUDIO); + } #endif //AUDIO_POLICY_TEST } @@ -1086,6 +1104,11 @@ AudioPolicyManagerBase::~AudioPolicyManagerBase() mInputs.clear(); } +status_t AudioPolicyManagerBase::initCheck() +{ + return (mHardwareOutput == 0) ? NO_INIT : NO_ERROR; +} + #ifdef AUDIO_POLICY_TEST bool AudioPolicyManagerBase::threadLoop() { @@ -1347,6 +1370,7 @@ status_t AudioPolicyManagerBase::handleA2dpDisconnection(AudioSystem::audio_devi void AudioPolicyManagerBase::closeA2dpOutputs() { + LOGV("setDeviceConnectionState() closing A2DP and duplicated output!"); if (mDuplicatedOutput != 0) { @@ -1581,6 +1605,12 @@ uint32_t AudioPolicyManagerBase::getDeviceForStrategy(routing_strategy strategy, if (device) break; device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADSET; if (device) break; + device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_AUX_DIGITAL; + if (device) break; + device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_DGTL_DOCK_HEADSET; + if (device) break; + device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_ANLG_DOCK_HEADSET; + if (device) break; #ifdef WITH_A2DP // when not in a phone call, phone strategy should route STREAM_VOICE_CALL to A2DP if (!isInCall()) { @@ -1597,10 +1627,12 @@ uint32_t AudioPolicyManagerBase::getDeviceForStrategy(routing_strategy strategy, break; case AudioSystem::FORCE_SPEAKER: - if (!isInCall() || strategy != STRATEGY_DTMF) { - device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_CARKIT; - if (device) break; - } + device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_AUX_DIGITAL; + if (device) break; + device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_DGTL_DOCK_HEADSET; + if (device) break; + device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_ANLG_DOCK_HEADSET; + if (device) break; #ifdef WITH_A2DP // when not in a phone call, phone strategy should route STREAM_VOICE_CALL to // A2DP speaker when forcing to speaker output @@ -1633,12 +1665,18 @@ uint32_t AudioPolicyManagerBase::getDeviceForStrategy(routing_strategy strategy, // FALL THROUGH case STRATEGY_MEDIA: { - uint32_t device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_AUX_DIGITAL; + uint32_t device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADPHONE; if (device2 == 0) { - device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADPHONE; + device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADSET; } if (device2 == 0) { - device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADSET; + device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_AUX_DIGITAL; + } + if (device2 == 0) { + device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_DGTL_DOCK_HEADSET; + } + if (device2 == 0) { + device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_ANLG_DOCK_HEADSET; } #ifdef WITH_A2DP if (mA2dpOutput != 0) { @@ -1805,7 +1843,9 @@ float AudioPolicyManagerBase::computeVolume(int stream, int index, audio_io_hand (AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP | AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES | AudioSystem::DEVICE_OUT_WIRED_HEADSET | - AudioSystem::DEVICE_OUT_WIRED_HEADPHONE)) && + AudioSystem::DEVICE_OUT_WIRED_HEADPHONE | + AudioSystem::DEVICE_OUT_ANLG_DOCK_HEADSET | + AudioSystem::DEVICE_OUT_DGTL_DOCK_HEADSET)) && ((getStrategy((AudioSystem::stream_type)stream) == STRATEGY_SONIFICATION) || (stream == AudioSystem::SYSTEM)) && streamDesc.mCanBeMuted) { @@ -2007,6 +2047,7 @@ AudioPolicyManagerBase::AudioOutputDescriptor::AudioOutputDescriptor() mRefCount[i] = 0; mCurVolume[i] = -1.0; mMuteCount[i] = 0; + mStopTime[i] = 0; } } @@ -2057,7 +2098,6 @@ uint32_t AudioPolicyManagerBase::AudioOutputDescriptor::strategyRefCount(routing return refCount; } - status_t AudioPolicyManagerBase::AudioOutputDescriptor::dump(int fd) { const size_t SIZE = 256; diff --git a/services/audioflinger/AudioPolicyService.cpp b/services/audioflinger/AudioPolicyService.cpp index f24e08e..b04672d 100644 --- a/services/audioflinger/AudioPolicyService.cpp +++ b/services/audioflinger/AudioPolicyService.cpp @@ -68,6 +68,8 @@ AudioPolicyService::AudioPolicyService() { char value[PROPERTY_VALUE_MAX]; + Mutex::Autolock _l(mLock); + // start tone playback thread mTonePlaybackThread = new AudioCommandThread(String8("")); // start audio commands thread @@ -88,9 +90,18 @@ AudioPolicyService::AudioPolicyService() } #endif - // load properties - property_get("ro.camera.sound.forced", value, "0"); - mpPolicyManager->setSystemProperty("ro.camera.sound.forced", value); + if ((mpPolicyManager != NULL) && (mpPolicyManager->initCheck() != NO_ERROR)) { + delete mpPolicyManager; + mpPolicyManager = NULL; + } + + if (mpPolicyManager == NULL) { + LOGE("Could not create AudioPolicyManager"); + } else { + // load properties + property_get("ro.camera.sound.forced", value, "0"); + mpPolicyManager->setSystemProperty("ro.camera.sound.forced", value); + } } AudioPolicyService::~AudioPolicyService() @@ -383,6 +394,15 @@ status_t AudioPolicyService::unregisterEffect(int id) return mpPolicyManager->unregisterEffect(id); } +bool AudioPolicyService::isStreamActive(int stream, uint32_t inPastMs) const +{ + if (mpPolicyManager == NULL) { + return 0; + } + Mutex::Autolock _l(mLock); + return mpPolicyManager->isStreamActive(stream, inPastMs); +} + void AudioPolicyService::binderDied(const wp<IBinder>& who) { LOGW("binderDied() %p, tid %d, calling tid %d", who.unsafe_get(), gettid(), IPCThreadState::self()->getCallingPid()); diff --git a/services/audioflinger/AudioPolicyService.h b/services/audioflinger/AudioPolicyService.h index 558f455..54af1f1 100644 --- a/services/audioflinger/AudioPolicyService.h +++ b/services/audioflinger/AudioPolicyService.h @@ -88,6 +88,7 @@ public: int session, int id); virtual status_t unregisterEffect(int id); + virtual bool isStreamActive(int stream, uint32_t inPastMs = 0) const; virtual status_t onTransact( uint32_t code, @@ -230,8 +231,8 @@ private: status_t dumpPermissionDenial(int fd); - Mutex mLock; // prevents concurrent access to AudioPolicy manager functions changing device - // connection stated our routing + mutable Mutex mLock; // prevents concurrent access to AudioPolicy manager functions changing + // device connection state or routing AudioPolicyInterface* mpPolicyManager; // the platform specific policy manager sp <AudioCommandThread> mAudioCommandThread; // audio commands thread sp <AudioCommandThread> mTonePlaybackThread; // tone playback thread diff --git a/services/camera/libcameraservice/Android.mk b/services/camera/libcameraservice/Android.mk index 87975af..b52fc69 100644 --- a/services/camera/libcameraservice/Android.mk +++ b/services/camera/libcameraservice/Android.mk @@ -49,7 +49,8 @@ LOCAL_SHARED_LIBRARIES:= \ libcutils \ libmedia \ libcamera_client \ - libsurfaceflinger_client + libsurfaceflinger_client \ + libgui LOCAL_MODULE:= libcameraservice diff --git a/services/camera/libcameraservice/CameraHardwareStub.cpp b/services/camera/libcameraservice/CameraHardwareStub.cpp index b3e0ee6..07b5a37 100644 --- a/services/camera/libcameraservice/CameraHardwareStub.cpp +++ b/services/camera/libcameraservice/CameraHardwareStub.cpp @@ -101,9 +101,9 @@ CameraHardwareStub::~CameraHardwareStub() mFakeCamera = 0; // paranoia } -sp<IMemoryHeap> CameraHardwareStub::getPreviewHeap() const +status_t CameraHardwareStub::setPreviewWindow(const sp<ANativeWindow>& buf) { - return mPreviewHeap; + return NO_ERROR; } sp<IMemoryHeap> CameraHardwareStub::getRawHeap() const diff --git a/services/camera/libcameraservice/CameraHardwareStub.h b/services/camera/libcameraservice/CameraHardwareStub.h index d3427ba..9b66a76 100644 --- a/services/camera/libcameraservice/CameraHardwareStub.h +++ b/services/camera/libcameraservice/CameraHardwareStub.h @@ -29,7 +29,7 @@ namespace android { class CameraHardwareStub : public CameraHardwareInterface { public: - virtual sp<IMemoryHeap> getPreviewHeap() const; + virtual status_t setPreviewWindow(const sp<ANativeWindow>& buf); virtual sp<IMemoryHeap> getRawHeap() const; virtual void setCallbacks(notify_callback notify_cb, diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp index a64ddcf..3d8ca7a 100644 --- a/services/camera/libcameraservice/CameraService.cpp +++ b/services/camera/libcameraservice/CameraService.cpp @@ -26,11 +26,12 @@ #include <binder/MemoryBase.h> #include <binder/MemoryHeapBase.h> #include <cutils/atomic.h> +#include <cutils/properties.h> +#include <gui/SurfaceTextureClient.h> #include <hardware/hardware.h> #include <media/AudioSystem.h> #include <media/mediaplayer.h> #include <surfaceflinger/ISurface.h> -#include <ui/Overlay.h> #include <utils/Errors.h> #include <utils/Log.h> #include <utils/String16.h> @@ -305,9 +306,9 @@ CameraService::Client::Client(const sp<CameraService>& cameraService, mCameraId = cameraId; mCameraFacing = cameraFacing; mClientPid = clientPid; - mUseOverlay = mHardware->useOverlay(); mMsgEnabled = 0; - + mSurface = 0; + mPreviewWindow = 0; mHardware->setCallbacks(notifyCallback, dataCallback, dataCallbackTimestamp, @@ -317,42 +318,21 @@ CameraService::Client::Client(const sp<CameraService>& cameraService, enableMsgType(CAMERA_MSG_ERROR | CAMERA_MSG_ZOOM | CAMERA_MSG_FOCUS); - mOverlayW = 0; - mOverlayH = 0; // Callback is disabled by default mPreviewCallbackFlag = FRAME_CALLBACK_FLAG_NOOP; mOrientation = getOrientation(0, mCameraFacing == CAMERA_FACING_FRONT); - mOrientationChanged = false; + mPlayShutterSound = true; cameraService->setCameraBusy(cameraId); cameraService->loadSound(); LOG1("Client::Client X (pid %d)", callingPid); } -static void *unregister_surface(void *arg) { - ISurface *surface = (ISurface *)arg; - surface->unregisterBuffers(); - IPCThreadState::self()->flushCommands(); - return NULL; -} - // tear down the client CameraService::Client::~Client() { int callingPid = getCallingPid(); LOG1("Client::~Client E (pid %d, this %p)", callingPid, this); - if (mSurface != 0 && !mUseOverlay) { - pthread_t thr; - // We unregister the buffers in a different thread because binder does - // not let us make sychronous transactions in a binder destructor (that - // is, upon our reaching a refcount of zero.) - pthread_create(&thr, - NULL, // attr - unregister_surface, - mSurface.get()); - pthread_join(thr, NULL); - } - // set mClientPid to let disconnet() tear down the hardware mClientPid = callingPid; disconnect(); @@ -466,9 +446,11 @@ void CameraService::Client::disconnect() { mHardware->cancelPicture(); // Release the hardware resources. mHardware->release(); - // Release the held overlay resources. - if (mUseOverlay) { - mOverlayRef = 0; + + // Release the held ANativeWindow resources. + if (mPreviewWindow != 0) { + mPreviewWindow = 0; + mHardware->setPreviewWindow(mPreviewWindow); } mHardware.clear(); @@ -480,8 +462,8 @@ void CameraService::Client::disconnect() { // ---------------------------------------------------------------------------- -// set the ISurface that the preview will use -status_t CameraService::Client::setPreviewDisplay(const sp<ISurface>& surface) { +// set the Surface that the preview will use +status_t CameraService::Client::setPreviewDisplay(const sp<Surface>& surface) { LOG1("setPreviewDisplay(%p) (pid %d)", surface.get(), getCallingPid()); Mutex::Autolock lock(mLock); status_t result = checkPidAndHardware(); @@ -491,100 +473,64 @@ status_t CameraService::Client::setPreviewDisplay(const sp<ISurface>& surface) { // return if no change in surface. // asBinder() is safe on NULL (returns NULL) - if (surface->asBinder() == mSurface->asBinder()) { + if (getISurface(surface)->asBinder() == mSurface) { return result; } if (mSurface != 0) { LOG1("clearing old preview surface %p", mSurface.get()); - if (mUseOverlay) { - // Force the destruction of any previous overlay - sp<Overlay> dummy; - mHardware->setOverlay(dummy); - mOverlayRef = 0; - } else { - mSurface->unregisterBuffers(); - } } - mSurface = surface; - mOverlayRef = 0; - // If preview has been already started, set overlay or register preview + mSurface = getISurface(surface)->asBinder(); + mPreviewWindow = surface; + + // If preview has been already started, register preview // buffers now. if (mHardware->previewEnabled()) { - if (mUseOverlay) { - result = setOverlay(); - } else if (mSurface != 0) { - result = registerPreviewBuffers(); + if (mPreviewWindow != 0) { + native_window_set_buffers_transform(mPreviewWindow.get(), + mOrientation); + result = mHardware->setPreviewWindow(mPreviewWindow); } } return result; } -status_t CameraService::Client::registerPreviewBuffers() { - int w, h; - CameraParameters params(mHardware->getParameters()); - params.getPreviewSize(&w, &h); - - // FIXME: don't use a hardcoded format here. - ISurface::BufferHeap buffers(w, h, w, h, - HAL_PIXEL_FORMAT_YCrCb_420_SP, - mOrientation, - 0, - mHardware->getPreviewHeap()); +// set the SurfaceTexture that the preview will use +status_t CameraService::Client::setPreviewTexture( + const sp<ISurfaceTexture>& surfaceTexture) { + LOG1("setPreviewTexture(%p) (pid %d)", surfaceTexture.get(), + getCallingPid()); + Mutex::Autolock lock(mLock); + status_t result = checkPidAndHardware(); + if (result != NO_ERROR) return result; - status_t result = mSurface->registerBuffers(buffers); - if (result != NO_ERROR) { - LOGE("registerBuffers failed with status %d", result); + // return if no change in surface. + // asBinder() is safe on NULL (returns NULL) + if (surfaceTexture->asBinder() == mSurface) { + return result; } - return result; -} - -status_t CameraService::Client::setOverlay() { - int w, h; - CameraParameters params(mHardware->getParameters()); - params.getPreviewSize(&w, &h); - if (w != mOverlayW || h != mOverlayH || mOrientationChanged) { - // Force the destruction of any previous overlay - sp<Overlay> dummy; - mHardware->setOverlay(dummy); - mOverlayRef = 0; - mOrientationChanged = false; + if (mSurface != 0) { + LOG1("clearing old preview surface %p", mSurface.get()); } - - status_t result = NO_ERROR; - if (mSurface == 0) { - result = mHardware->setOverlay(NULL); + mSurface = surfaceTexture->asBinder(); + if (surfaceTexture != 0) { + mPreviewWindow = new SurfaceTextureClient(surfaceTexture); } else { - if (mOverlayRef == 0) { - // FIXME: - // Surfaceflinger may hold onto the previous overlay reference for some - // time after we try to destroy it. retry a few times. In the future, we - // should make the destroy call block, or possibly specify that we can - // wait in the createOverlay call if the previous overlay is in the - // process of being destroyed. - for (int retry = 0; retry < 50; ++retry) { - mOverlayRef = mSurface->createOverlay(w, h, OVERLAY_FORMAT_DEFAULT, - mOrientation); - if (mOverlayRef != 0) break; - LOGW("Overlay create failed - retrying"); - usleep(20000); - } - if (mOverlayRef == 0) { - LOGE("Overlay Creation Failed!"); - return -EINVAL; - } - result = mHardware->setOverlay(new Overlay(mOverlayRef)); - } - } - if (result != NO_ERROR) { - LOGE("mHardware->setOverlay() failed with status %d\n", result); - return result; + mPreviewWindow = 0; } - mOverlayW = w; - mOverlayH = h; + // If preview has been already started, set overlay or register preview + // buffers now. + if (mHardware->previewEnabled()) { + // XXX: What if the new preview window is 0? + if (mPreviewWindow != 0) { + native_window_set_buffers_transform(mPreviewWindow.get(), + mOrientation); + result = mHardware->setPreviewWindow(mPreviewWindow); + } + } return result; } @@ -597,16 +543,10 @@ void CameraService::Client::setPreviewCallbackFlag(int callback_flag) { if (checkPidAndHardware() != NO_ERROR) return; mPreviewCallbackFlag = callback_flag; - - // If we don't use overlay, we always need the preview frame for display. - // If we do use overlay, we only need the preview frame if the user - // wants the data. - if (mUseOverlay) { - if(mPreviewCallbackFlag & FRAME_CALLBACK_FLAG_ENABLE_MASK) { - enableMsgType(CAMERA_MSG_PREVIEW_FRAME); - } else { - disableMsgType(CAMERA_MSG_PREVIEW_FRAME); - } + if (mPreviewCallbackFlag & FRAME_CALLBACK_FLAG_ENABLE_MASK) { + enableMsgType(CAMERA_MSG_PREVIEW_FRAME); + } else { + disableMsgType(CAMERA_MSG_PREVIEW_FRAME); } } @@ -631,14 +571,14 @@ status_t CameraService::Client::startCameraMode(camera_mode mode) { switch(mode) { case CAMERA_PREVIEW_MODE: - if (mSurface == 0) { + if (mSurface == 0 && mPreviewWindow == 0) { LOG1("mSurface is not set yet."); // still able to start preview in this case. } return startPreviewMode(); case CAMERA_RECORDING_MODE: - if (mSurface == 0) { - LOGE("mSurface must be set before startRecordingMode."); + if (mSurface == 0 && mPreviewWindow == 0) { + LOGE("mSurface or mPreviewWindow must be set before startRecordingMode."); return INVALID_OPERATION; } return startRecordingMode(); @@ -656,25 +596,13 @@ status_t CameraService::Client::startPreviewMode() { return NO_ERROR; } - if (mUseOverlay) { - // If preview display has been set, set overlay now. - if (mSurface != 0) { - result = setOverlay(); - } - if (result != NO_ERROR) return result; - result = mHardware->startPreview(); - } else { - enableMsgType(CAMERA_MSG_PREVIEW_FRAME); - result = mHardware->startPreview(); - if (result != NO_ERROR) return result; - // If preview display has been set, register preview buffers now. - if (mSurface != 0) { - // Unregister here because the surface may be previously registered - // with the raw (snapshot) heap. - mSurface->unregisterBuffers(); - result = registerPreviewBuffers(); - } + if (mPreviewWindow != 0) { + native_window_set_buffers_transform(mPreviewWindow.get(), + mOrientation); } + mHardware->setPreviewWindow(mPreviewWindow); + result = mHardware->startPreview(); + return result; } @@ -711,13 +639,10 @@ void CameraService::Client::stopPreview() { Mutex::Autolock lock(mLock); if (checkPidAndHardware() != NO_ERROR) return; + disableMsgType(CAMERA_MSG_PREVIEW_FRAME); mHardware->stopPreview(); - if (mSurface != 0 && !mUseOverlay) { - mSurface->unregisterBuffers(); - } - mPreviewBuffer.clear(); } @@ -741,6 +666,30 @@ void CameraService::Client::releaseRecordingFrame(const sp<IMemory>& mem) { mHardware->releaseRecordingFrame(mem); } +int32_t CameraService::Client::getNumberOfVideoBuffers() const { + LOG1("getNumberOfVideoBuffers"); + Mutex::Autolock lock(mLock); + if (checkPidAndHardware() != NO_ERROR) return 0; + return mHardware->getNumberOfVideoBuffers(); +} + +sp<IMemory> CameraService::Client::getVideoBuffer(int32_t index) const { + LOG1("getVideoBuffer: %d", index); + Mutex::Autolock lock(mLock); + if (checkPidAndHardware() != NO_ERROR) return 0; + return mHardware->getVideoBuffer(index); +} + +status_t CameraService::Client::storeMetaDataInBuffers(bool enabled) +{ + LOG1("storeMetaDataInBuffers: %s", enabled? "true": "false"); + Mutex::Autolock lock(mLock); + if (checkPidAndHardware() != NO_ERROR) { + return UNKNOWN_ERROR; + } + return mHardware->storeMetaDataInBuffers(enabled); +} + bool CameraService::Client::previewEnabled() { LOG1("previewEnabled (pid %d)", getCallingPid()); @@ -815,6 +764,35 @@ String8 CameraService::Client::getParameters() const { return params; } +// enable shutter sound +status_t CameraService::Client::enableShutterSound(bool enable) { + LOG1("enableShutterSound (pid %d)", getCallingPid()); + + status_t result = checkPidAndHardware(); + if (result != NO_ERROR) return result; + + if (enable) { + mPlayShutterSound = true; + return OK; + } + + // Disabling shutter sound may not be allowed. In that case only + // allow the mediaserver process to disable the sound. + char value[PROPERTY_VALUE_MAX]; + property_get("ro.camera.sound.forced", value, "0"); + if (strcmp(value, "0") != 0) { + // Disabling shutter sound is not allowed. Deny if the current + // process is not mediaserver. + if (getCallingPid() != getpid()) { + LOGE("Failed to disable shutter sound. Permission denied (pid %d)", getCallingPid()); + return PERMISSION_DENIED; + } + } + + mPlayShutterSound = false; + return OK; +} + status_t CameraService::Client::sendCommand(int32_t cmd, int32_t arg1, int32_t arg2) { LOG1("sendCommand (pid %d)", getCallingPid()); int orientation; @@ -833,9 +811,22 @@ status_t CameraService::Client::sendCommand(int32_t cmd, int32_t arg1, int32_t a if (mOrientation != orientation) { mOrientation = orientation; - if (mOverlayRef != 0) mOrientationChanged = true; } return OK; + } else if (cmd == CAMERA_CMD_ENABLE_SHUTTER_SOUND) { + switch (arg1) { + case 0: + enableShutterSound(false); + break; + case 1: + enableShutterSound(true); + break; + default: + return BAD_VALUE; + } + return OK; + } else if (cmd == CAMERA_CMD_PLAY_RECORDING_SOUND) { + mCameraService->playSound(SOUND_RECORDING); } return mHardware->sendCommand(cmd, arg1, arg2); @@ -996,11 +987,8 @@ void CameraService::Client::dataCallbackTimestamp(nsecs_t timestamp, // "size" is the width and height of yuv picture for registerBuffer. // If it is NULL, use the picture size from parameters. void CameraService::Client::handleShutter(image_rect_type *size) { - mCameraService->playSound(SOUND_SHUTTER); - - // Screen goes black after the buffer is unregistered. - if (mSurface != 0 && !mUseOverlay) { - mSurface->unregisterBuffers(); + if (mPlayShutterSound) { + mCameraService->playSound(SOUND_SHUTTER); } sp<ICameraClient> c = mCameraClient; @@ -1011,29 +999,6 @@ void CameraService::Client::handleShutter(image_rect_type *size) { } disableMsgType(CAMERA_MSG_SHUTTER); - // It takes some time before yuvPicture callback to be called. - // Register the buffer for raw image here to reduce latency. - if (mSurface != 0 && !mUseOverlay) { - int w, h; - CameraParameters params(mHardware->getParameters()); - if (size == NULL) { - params.getPictureSize(&w, &h); - } else { - w = size->width; - h = size->height; - w &= ~1; - h &= ~1; - LOG1("Snapshot image width=%d, height=%d", w, h); - } - // FIXME: don't use hardcoded format constants here - ISurface::BufferHeap buffers(w, h, w, h, - HAL_PIXEL_FORMAT_YCrCb_420_SP, mOrientation, 0, - mHardware->getRawHeap()); - - mSurface->registerBuffers(buffers); - IPCThreadState::self()->flushCommands(); - } - mLock.unlock(); } @@ -1043,12 +1008,6 @@ void CameraService::Client::handlePreviewData(const sp<IMemory>& mem) { size_t size; sp<IMemoryHeap> heap = mem->getMemory(&offset, &size); - if (!mUseOverlay) { - if (mSurface != 0) { - mSurface->postBuffer(offset); - } - } - // local copy of the callback flags int flags = mPreviewCallbackFlag; @@ -1069,9 +1028,7 @@ void CameraService::Client::handlePreviewData(const sp<IMemory>& mem) { mPreviewCallbackFlag &= ~(FRAME_CALLBACK_FLAG_ONE_SHOT_MASK | FRAME_CALLBACK_FLAG_COPY_OUT_MASK | FRAME_CALLBACK_FLAG_ENABLE_MASK); - if (mUseOverlay) { - disableMsgType(CAMERA_MSG_PREVIEW_FRAME); - } + disableMsgType(CAMERA_MSG_PREVIEW_FRAME); } if (c != 0) { @@ -1108,11 +1065,6 @@ void CameraService::Client::handleRawPicture(const sp<IMemory>& mem) { size_t size; sp<IMemoryHeap> heap = mem->getMemory(&offset, &size); - // Put the YUV version of the snapshot in the preview display. - if (mSurface != 0 && !mUseOverlay) { - mSurface->postBuffer(offset); - } - sp<ICameraClient> c = mCameraClient; mLock.unlock(); if (c != 0) { @@ -1292,4 +1244,12 @@ status_t CameraService::dump(int fd, const Vector<String16>& args) { return NO_ERROR; } +sp<ISurface> CameraService::getISurface(const sp<Surface>& surface) { + if (surface != 0) { + return surface->getISurface(); + } else { + return sp<ISurface>(0); + } +} + }; // namespace android diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h index f09773d..ccb9cf7 100644 --- a/services/camera/libcameraservice/CameraService.h +++ b/services/camera/libcameraservice/CameraService.h @@ -79,6 +79,12 @@ private: sp<MediaPlayer> mSoundPlayer[NUM_SOUNDS]; int mSoundRef; // reference count (release all MediaPlayer when 0) + // Used by Client objects to extract the ISurface from a Surface object. + // This is used because making Client a friend class of Surface would + // require including this header in Surface.h since Client is a nested + // class. + static sp<ISurface> getISurface(const sp<Surface>& surface); + class Client : public BnCamera { public: @@ -87,11 +93,15 @@ private: virtual status_t connect(const sp<ICameraClient>& client); virtual status_t lock(); virtual status_t unlock(); - virtual status_t setPreviewDisplay(const sp<ISurface>& surface); + virtual status_t setPreviewDisplay(const sp<Surface>& surface); + virtual status_t setPreviewTexture(const sp<ISurfaceTexture>& surfaceTexture); virtual void setPreviewCallbackFlag(int flag); virtual status_t startPreview(); virtual void stopPreview(); virtual bool previewEnabled(); + virtual int32_t getNumberOfVideoBuffers() const; + virtual sp<IMemory> getVideoBuffer(int32_t index) const; + virtual status_t storeMetaDataInBuffers(bool enabled); virtual status_t startRecording(); virtual void stopRecording(); virtual bool recordingEnabled(); @@ -121,7 +131,6 @@ private: // these are internal functions used to set up preview buffers status_t registerPreviewBuffers(); - status_t setOverlay(); // camera operation mode enum camera_mode { @@ -133,6 +142,9 @@ private: status_t startPreviewMode(); status_t startRecordingMode(); + // internal function used by sendCommand to enable/disable shutter sound. + status_t enableShutterSound(bool enable); + // these are static callback functions static void notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2, void* user); static void dataCallback(int32_t msgType, const sp<IMemory>& dataPtr, void* user); @@ -163,18 +175,15 @@ private: int mCameraFacing; // immutable after constructor pid_t mClientPid; sp<CameraHardwareInterface> mHardware; // cleared after disconnect() - bool mUseOverlay; // immutable after constructor - sp<OverlayRef> mOverlayRef; - int mOverlayW; - int mOverlayH; int mPreviewCallbackFlag; int mOrientation; // Current display orientation - // True if display orientation has been changed. This is only used in overlay. - int mOrientationChanged; + bool mPlayShutterSound; // Ensures atomicity among the public methods mutable Mutex mLock; - sp<ISurface> mSurface; + // This is a binder of Surface or SurfaceTexture. + sp<IBinder> mSurface; + sp<ANativeWindow> mPreviewWindow; // If the user want us to return a copy of the preview frame (instead // of the original one), we allocate mPreviewBuffer and reuse it if possible. diff --git a/services/input/Android.mk b/services/input/Android.mk new file mode 100644 index 0000000..d7b61fc --- /dev/null +++ b/services/input/Android.mk @@ -0,0 +1,57 @@ +# Copyright (C) 2010 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. + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + EventHub.cpp \ + InputDispatcher.cpp \ + InputManager.cpp \ + InputReader.cpp \ + InputWindow.cpp \ + PointerController.cpp + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libutils \ + libhardware \ + libhardware_legacy \ + libsurfaceflinger_client \ + libskia \ + libui + +LOCAL_C_INCLUDES := \ + external/skia/include/core + +LOCAL_MODULE:= libinput + +LOCAL_MODULE_TAGS := optional + +ifeq ($(TARGET_SIMULATOR),true) + LOCAL_LDLIBS += -lpthread +endif + +include $(BUILD_SHARED_LIBRARY) + + +# Include subdirectory makefiles +# ============================================================ + +# If we're building with ONE_SHOT_MAKEFILE (mm, mmm), then what the framework +# team really wants is to build the stuff defined by this makefile. +ifeq (,$(ONE_SHOT_MAKEFILE)) +include $(call first-makefiles-under,$(LOCAL_PATH)) +endif diff --git a/services/input/EventHub.cpp b/services/input/EventHub.cpp new file mode 100644 index 0000000..41dbe2f --- /dev/null +++ b/services/input/EventHub.cpp @@ -0,0 +1,1095 @@ +/* + * Copyright (C) 2005 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. + */ + +// +// Handle events, like key input and vsync. +// +// The goal is to provide an optimized solution for Linux, not an +// implementation that works well across all platforms. We expect +// events to arrive on file descriptors, so that we can use a select() +// select() call to sleep. +// +// We can't select() on anything but network sockets in Windows, so we +// provide an alternative implementation of waitEvent for that platform. +// +#define LOG_TAG "EventHub" + +//#define LOG_NDEBUG 0 + +#include "EventHub.h" + +#include <hardware_legacy/power.h> + +#include <cutils/properties.h> +#include <utils/Log.h> +#include <utils/Timers.h> +#include <utils/threads.h> +#include <utils/Errors.h> + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <memory.h> +#include <errno.h> +#include <assert.h> + +#include <ui/KeyLayoutMap.h> +#include <ui/KeyCharacterMap.h> +#include <ui/VirtualKeyMap.h> + +#include <string.h> +#include <stdint.h> +#include <dirent.h> +#ifdef HAVE_INOTIFY +# include <sys/inotify.h> +#endif +#ifdef HAVE_ANDROID_OS +# include <sys/limits.h> /* not part of Linux */ +#endif +#include <sys/poll.h> +#include <sys/ioctl.h> + +/* this macro is used to tell if "bit" is set in "array" + * it selects a byte from the array, and does a boolean AND + * operation with a byte that only has the relevant bit set. + * eg. to check for the 12th bit, we do (array[1] & 1<<4) + */ +#define test_bit(bit, array) (array[bit/8] & (1<<(bit%8))) + +/* this macro computes the number of bytes needed to represent a bit array of the specified size */ +#define sizeof_bit_array(bits) ((bits + 7) / 8) + +// Fd at index 0 is always reserved for inotify +#define FIRST_ACTUAL_DEVICE_INDEX 1 + +#define INDENT " " +#define INDENT2 " " +#define INDENT3 " " + +namespace android { + +static const char *WAKE_LOCK_ID = "KeyEvents"; +static const char *DEVICE_PATH = "/dev/input"; + +/* return the larger integer */ +static inline int max(int v1, int v2) +{ + return (v1 > v2) ? v1 : v2; +} + +static inline const char* toString(bool value) { + return value ? "true" : "false"; +} + +// --- EventHub::Device --- + +EventHub::Device::Device(int fd, int32_t id, const String8& path, + const InputDeviceIdentifier& identifier) : + next(NULL), + fd(fd), id(id), path(path), identifier(identifier), + classes(0), keyBitmask(NULL), configuration(NULL), virtualKeyMap(NULL) { +} + +EventHub::Device::~Device() { + close(); + delete[] keyBitmask; + delete configuration; + delete virtualKeyMap; +} + +void EventHub::Device::close() { + if (fd >= 0) { + ::close(fd); + fd = -1; + } +} + + +// --- EventHub --- + +EventHub::EventHub(void) : + mError(NO_INIT), mBuiltInKeyboardId(-1), mNextDeviceId(1), + mOpeningDevices(0), mClosingDevices(0), + mOpened(false), mNeedToSendFinishedDeviceScan(false), + mInputBufferIndex(0), mInputBufferCount(0), mInputFdIndex(0) { + acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID); +#ifdef EV_SW + memset(mSwitches, 0, sizeof(mSwitches)); +#endif +} + +EventHub::~EventHub(void) { + release_wake_lock(WAKE_LOCK_ID); + // we should free stuff here... +} + +status_t EventHub::errorCheck() const { + return mError; +} + +String8 EventHub::getDeviceName(int32_t deviceId) const { + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device == NULL) return String8(); + return device->identifier.name; +} + +uint32_t EventHub::getDeviceClasses(int32_t deviceId) const { + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device == NULL) return 0; + return device->classes; +} + +void EventHub::getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const { + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device && device->configuration) { + *outConfiguration = *device->configuration; + } else { + outConfiguration->clear(); + } +} + +status_t EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis, + RawAbsoluteAxisInfo* outAxisInfo) const { + outAxisInfo->clear(); + + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device == NULL) return -1; + + struct input_absinfo info; + + if(ioctl(device->fd, EVIOCGABS(axis), &info)) { + LOGW("Error reading absolute controller %d for device %s fd %d\n", + axis, device->identifier.name.string(), device->fd); + return -errno; + } + + if (info.minimum != info.maximum) { + outAxisInfo->valid = true; + outAxisInfo->minValue = info.minimum; + outAxisInfo->maxValue = info.maximum; + outAxisInfo->flat = info.flat; + outAxisInfo->fuzz = info.fuzz; + } + return OK; +} + +int32_t EventHub::getScanCodeState(int32_t deviceId, int32_t scanCode) const { + if (scanCode >= 0 && scanCode <= KEY_MAX) { + AutoMutex _l(mLock); + + Device* device = getDeviceLocked(deviceId); + if (device != NULL) { + return getScanCodeStateLocked(device, scanCode); + } + } + return AKEY_STATE_UNKNOWN; +} + +int32_t EventHub::getScanCodeStateLocked(Device* device, int32_t scanCode) const { + uint8_t key_bitmask[sizeof_bit_array(KEY_MAX + 1)]; + memset(key_bitmask, 0, sizeof(key_bitmask)); + if (ioctl(device->fd, + EVIOCGKEY(sizeof(key_bitmask)), key_bitmask) >= 0) { + return test_bit(scanCode, key_bitmask) ? AKEY_STATE_DOWN : AKEY_STATE_UP; + } + return AKEY_STATE_UNKNOWN; +} + +int32_t EventHub::getKeyCodeState(int32_t deviceId, int32_t keyCode) const { + AutoMutex _l(mLock); + + Device* device = getDeviceLocked(deviceId); + if (device != NULL) { + return getKeyCodeStateLocked(device, keyCode); + } + return AKEY_STATE_UNKNOWN; +} + +int32_t EventHub::getKeyCodeStateLocked(Device* device, int32_t keyCode) const { + if (!device->keyMap.haveKeyLayout()) { + return AKEY_STATE_UNKNOWN; + } + + Vector<int32_t> scanCodes; + device->keyMap.keyLayoutMap->findScanCodes(keyCode, &scanCodes); + + uint8_t key_bitmask[sizeof_bit_array(KEY_MAX + 1)]; + memset(key_bitmask, 0, sizeof(key_bitmask)); + if (ioctl(device->fd, EVIOCGKEY(sizeof(key_bitmask)), key_bitmask) >= 0) { + #if 0 + for (size_t i=0; i<=KEY_MAX; i++) { + LOGI("(Scan code %d: down=%d)", i, test_bit(i, key_bitmask)); + } + #endif + const size_t N = scanCodes.size(); + for (size_t i=0; i<N && i<=KEY_MAX; i++) { + int32_t sc = scanCodes.itemAt(i); + //LOGI("Code %d: down=%d", sc, test_bit(sc, key_bitmask)); + if (sc >= 0 && sc <= KEY_MAX && test_bit(sc, key_bitmask)) { + return AKEY_STATE_DOWN; + } + } + return AKEY_STATE_UP; + } + return AKEY_STATE_UNKNOWN; +} + +int32_t EventHub::getSwitchState(int32_t deviceId, int32_t sw) const { +#ifdef EV_SW + if (sw >= 0 && sw <= SW_MAX) { + AutoMutex _l(mLock); + + Device* device = getDeviceLocked(deviceId); + if (device != NULL) { + return getSwitchStateLocked(device, sw); + } + } +#endif + return AKEY_STATE_UNKNOWN; +} + +int32_t EventHub::getSwitchStateLocked(Device* device, int32_t sw) const { + uint8_t sw_bitmask[sizeof_bit_array(SW_MAX + 1)]; + memset(sw_bitmask, 0, sizeof(sw_bitmask)); + if (ioctl(device->fd, + EVIOCGSW(sizeof(sw_bitmask)), sw_bitmask) >= 0) { + return test_bit(sw, sw_bitmask) ? AKEY_STATE_DOWN : AKEY_STATE_UP; + } + return AKEY_STATE_UNKNOWN; +} + +bool EventHub::markSupportedKeyCodes(int32_t deviceId, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) const { + AutoMutex _l(mLock); + + Device* device = getDeviceLocked(deviceId); + if (device != NULL) { + return markSupportedKeyCodesLocked(device, numCodes, keyCodes, outFlags); + } + return false; +} + +bool EventHub::markSupportedKeyCodesLocked(Device* device, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) const { + if (!device->keyMap.haveKeyLayout() || !device->keyBitmask) { + return false; + } + + Vector<int32_t> scanCodes; + for (size_t codeIndex = 0; codeIndex < numCodes; codeIndex++) { + scanCodes.clear(); + + status_t err = device->keyMap.keyLayoutMap->findScanCodes(keyCodes[codeIndex], &scanCodes); + if (! err) { + // check the possible scan codes identified by the layout map against the + // map of codes actually emitted by the driver + for (size_t sc = 0; sc < scanCodes.size(); sc++) { + if (test_bit(scanCodes[sc], device->keyBitmask)) { + outFlags[codeIndex] = 1; + break; + } + } + } + } + return true; +} + +status_t EventHub::scancodeToKeycode(int32_t deviceId, int scancode, + int32_t* outKeycode, uint32_t* outFlags) const +{ + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + + if (device && device->keyMap.haveKeyLayout()) { + status_t err = device->keyMap.keyLayoutMap->map(scancode, outKeycode, outFlags); + if (err == NO_ERROR) { + return NO_ERROR; + } + } + + if (mBuiltInKeyboardId != -1) { + device = getDeviceLocked(mBuiltInKeyboardId); + + if (device && device->keyMap.haveKeyLayout()) { + status_t err = device->keyMap.keyLayoutMap->map(scancode, outKeycode, outFlags); + if (err == NO_ERROR) { + return NO_ERROR; + } + } + } + + *outKeycode = 0; + *outFlags = 0; + return NAME_NOT_FOUND; +} + +void EventHub::addExcludedDevice(const char* deviceName) +{ + AutoMutex _l(mLock); + + String8 name(deviceName); + mExcludedDevices.push_back(name); +} + +bool EventHub::hasLed(int32_t deviceId, int32_t led) const { + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device) { + uint8_t bitmask[sizeof_bit_array(LED_MAX + 1)]; + memset(bitmask, 0, sizeof(bitmask)); + if (ioctl(device->fd, EVIOCGBIT(EV_LED, sizeof(bitmask)), bitmask) >= 0) { + if (test_bit(led, bitmask)) { + return true; + } + } + } + return false; +} + +void EventHub::setLedState(int32_t deviceId, int32_t led, bool on) { + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device) { + struct input_event ev; + ev.time.tv_sec = 0; + ev.time.tv_usec = 0; + ev.type = EV_LED; + ev.code = led; + ev.value = on ? 1 : 0; + + ssize_t nWrite; + do { + nWrite = write(device->fd, &ev, sizeof(struct input_event)); + } while (nWrite == -1 && errno == EINTR); + } +} + +void EventHub::getVirtualKeyDefinitions(int32_t deviceId, + Vector<VirtualKeyDefinition>& outVirtualKeys) const { + outVirtualKeys.clear(); + + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device && device->virtualKeyMap) { + outVirtualKeys.appendVector(device->virtualKeyMap->getVirtualKeys()); + } +} + +EventHub::Device* EventHub::getDeviceLocked(int32_t deviceId) const { + if (deviceId == 0) { + deviceId = mBuiltInKeyboardId; + } + + size_t numDevices = mDevices.size(); + for (size_t i = FIRST_ACTUAL_DEVICE_INDEX; i < numDevices; i++) { + Device* device = mDevices[i]; + if (device->id == deviceId) { + return device; + } + } + return NULL; +} + +bool EventHub::getEvent(RawEvent* outEvent) { + outEvent->deviceId = 0; + outEvent->type = 0; + outEvent->scanCode = 0; + outEvent->keyCode = 0; + outEvent->flags = 0; + outEvent->value = 0; + outEvent->when = 0; + + // Note that we only allow one caller to getEvent(), so don't need + // to do locking here... only when adding/removing devices. + + if (!mOpened) { + mError = openPlatformInput() ? NO_ERROR : UNKNOWN_ERROR; + mOpened = true; + mNeedToSendFinishedDeviceScan = true; + } + + for (;;) { + // Report any devices that had last been added/removed. + if (mClosingDevices != NULL) { + Device* device = mClosingDevices; + LOGV("Reporting device closed: id=%d, name=%s\n", + device->id, device->path.string()); + mClosingDevices = device->next; + if (device->id == mBuiltInKeyboardId) { + outEvent->deviceId = 0; + } else { + outEvent->deviceId = device->id; + } + outEvent->type = DEVICE_REMOVED; + outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC); + delete device; + mNeedToSendFinishedDeviceScan = true; + return true; + } + + if (mOpeningDevices != NULL) { + Device* device = mOpeningDevices; + LOGV("Reporting device opened: id=%d, name=%s\n", + device->id, device->path.string()); + mOpeningDevices = device->next; + if (device->id == mBuiltInKeyboardId) { + outEvent->deviceId = 0; + } else { + outEvent->deviceId = device->id; + } + outEvent->type = DEVICE_ADDED; + outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC); + mNeedToSendFinishedDeviceScan = true; + return true; + } + + if (mNeedToSendFinishedDeviceScan) { + mNeedToSendFinishedDeviceScan = false; + outEvent->type = FINISHED_DEVICE_SCAN; + outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC); + return true; + } + + // Grab the next input event. + for (;;) { + // Consume buffered input events, if any. + if (mInputBufferIndex < mInputBufferCount) { + const struct input_event& iev = mInputBufferData[mInputBufferIndex++]; + const Device* device = mDevices[mInputFdIndex]; + + LOGV("%s got: t0=%d, t1=%d, type=%d, code=%d, v=%d", device->path.string(), + (int) iev.time.tv_sec, (int) iev.time.tv_usec, iev.type, iev.code, iev.value); + if (device->id == mBuiltInKeyboardId) { + outEvent->deviceId = 0; + } else { + outEvent->deviceId = device->id; + } + outEvent->type = iev.type; + outEvent->scanCode = iev.code; + outEvent->flags = 0; + if (iev.type == EV_KEY) { + outEvent->keyCode = AKEYCODE_UNKNOWN; + if (device->keyMap.haveKeyLayout()) { + status_t err = device->keyMap.keyLayoutMap->map(iev.code, + &outEvent->keyCode, &outEvent->flags); + LOGV("iev.code=%d keyCode=%d flags=0x%08x err=%d\n", + iev.code, outEvent->keyCode, outEvent->flags, err); + } + } else { + outEvent->keyCode = iev.code; + } + outEvent->value = iev.value; + + // Use an event timestamp in the same timebase as + // java.lang.System.nanoTime() and android.os.SystemClock.uptimeMillis() + // as expected by the rest of the system. + outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC); + return true; + } + + // Finish reading all events from devices identified in previous poll(). + // This code assumes that mInputDeviceIndex is initially 0 and that the + // revents member of pollfd is initialized to 0 when the device is first added. + // Since mFds[0] is used for inotify, we process regular events starting at index 1. + mInputFdIndex += 1; + if (mInputFdIndex >= mFds.size()) { + break; + } + + const struct pollfd& pfd = mFds[mInputFdIndex]; + if (pfd.revents & POLLIN) { + int32_t readSize = read(pfd.fd, mInputBufferData, + sizeof(struct input_event) * INPUT_BUFFER_SIZE); + if (readSize < 0) { + if (errno != EAGAIN && errno != EINTR) { + LOGW("could not get event (errno=%d)", errno); + } + } else if ((readSize % sizeof(struct input_event)) != 0) { + LOGE("could not get event (wrong size: %d)", readSize); + } else { + mInputBufferCount = size_t(readSize) / sizeof(struct input_event); + mInputBufferIndex = 0; + } + } + } + +#if HAVE_INOTIFY + // readNotify() will modify mFDs and mFDCount, so this must be done after + // processing all other events. + if(mFds[0].revents & POLLIN) { + readNotify(mFds[0].fd); + mFds.editItemAt(0).revents = 0; + continue; // report added or removed devices immediately + } +#endif + + mInputFdIndex = 0; + + // Poll for events. Mind the wake lock dance! + // We hold a wake lock at all times except during poll(). This works due to some + // subtle choreography. When a device driver has pending (unread) events, it acquires + // a kernel wake lock. However, once the last pending event has been read, the device + // driver will release the kernel wake lock. To prevent the system from going to sleep + // 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 + // pending or currently being processed. + release_wake_lock(WAKE_LOCK_ID); + + int pollResult = poll(mFds.editArray(), mFds.size(), -1); + + acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID); + + if (pollResult <= 0) { + if (errno != EINTR) { + LOGW("poll failed (errno=%d)\n", errno); + usleep(100000); + } + } + } +} + +/* + * Open the platform-specific input device. + */ +bool EventHub::openPlatformInput(void) { + /* + * Open platform-specific input device(s). + */ + int res, fd; + +#ifdef HAVE_INOTIFY + fd = inotify_init(); + res = inotify_add_watch(fd, DEVICE_PATH, IN_DELETE | IN_CREATE); + if(res < 0) { + LOGE("could not add watch for %s, %s\n", DEVICE_PATH, strerror(errno)); + } +#else + /* + * The code in EventHub::getEvent assumes that mFDs[0] is an inotify fd. + * We allocate space for it and set it to something invalid. + */ + fd = -1; +#endif + + // Reserve fd index 0 for inotify. + struct pollfd pollfd; + pollfd.fd = fd; + pollfd.events = POLLIN; + pollfd.revents = 0; + mFds.push(pollfd); + mDevices.push(NULL); + + res = scanDir(DEVICE_PATH); + if(res < 0) { + LOGE("scan dir failed for %s\n", DEVICE_PATH); + } + + return true; +} + +// ---------------------------------------------------------------------------- + +static bool containsNonZeroByte(const uint8_t* array, uint32_t startIndex, uint32_t endIndex) { + const uint8_t* end = array + endIndex; + array += startIndex; + while (array != end) { + if (*(array++) != 0) { + return true; + } + } + return false; +} + +static const int32_t GAMEPAD_KEYCODES[] = { + AKEYCODE_BUTTON_A, AKEYCODE_BUTTON_B, AKEYCODE_BUTTON_C, + AKEYCODE_BUTTON_X, AKEYCODE_BUTTON_Y, AKEYCODE_BUTTON_Z, + AKEYCODE_BUTTON_L1, AKEYCODE_BUTTON_R1, + AKEYCODE_BUTTON_L2, AKEYCODE_BUTTON_R2, + AKEYCODE_BUTTON_THUMBL, AKEYCODE_BUTTON_THUMBR, + AKEYCODE_BUTTON_START, AKEYCODE_BUTTON_SELECT, AKEYCODE_BUTTON_MODE +}; + +int EventHub::openDevice(const char *devicePath) { + char buffer[80]; + + LOGV("Opening device: %s", devicePath); + + AutoMutex _l(mLock); + + int fd = open(devicePath, O_RDWR); + if(fd < 0) { + LOGE("could not open %s, %s\n", devicePath, strerror(errno)); + return -1; + } + + InputDeviceIdentifier identifier; + + // Get device name. + if(ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) { + //fprintf(stderr, "could not get device name for %s, %s\n", devicePath, strerror(errno)); + } else { + buffer[sizeof(buffer) - 1] = '\0'; + identifier.name.setTo(buffer); + } + + // Check to see if the device is on our excluded list + List<String8>::iterator iter = mExcludedDevices.begin(); + List<String8>::iterator end = mExcludedDevices.end(); + for ( ; iter != end; iter++) { + const char* test = *iter; + if (identifier.name == test) { + LOGI("ignoring event id %s driver %s\n", devicePath, test); + close(fd); + return -1; + } + } + + // Get device driver version. + int driverVersion; + if(ioctl(fd, EVIOCGVERSION, &driverVersion)) { + LOGE("could not get driver version for %s, %s\n", devicePath, strerror(errno)); + close(fd); + return -1; + } + + // Get device identifier. + struct input_id inputId; + if(ioctl(fd, EVIOCGID, &inputId)) { + LOGE("could not get device input id for %s, %s\n", devicePath, strerror(errno)); + close(fd); + return -1; + } + identifier.bus = inputId.bustype; + identifier.product = inputId.product; + identifier.vendor = inputId.vendor; + identifier.version = inputId.version; + + // Get device physical location. + if(ioctl(fd, EVIOCGPHYS(sizeof(buffer) - 1), &buffer) < 1) { + //fprintf(stderr, "could not get location for %s, %s\n", devicePath, strerror(errno)); + } else { + buffer[sizeof(buffer) - 1] = '\0'; + identifier.location.setTo(buffer); + } + + // Get device unique id. + if(ioctl(fd, EVIOCGUNIQ(sizeof(buffer) - 1), &buffer) < 1) { + //fprintf(stderr, "could not get idstring for %s, %s\n", devicePath, strerror(errno)); + } else { + buffer[sizeof(buffer) - 1] = '\0'; + identifier.uniqueId.setTo(buffer); + } + + // Make file descriptor non-blocking for use with poll(). + if (fcntl(fd, F_SETFL, O_NONBLOCK)) { + LOGE("Error %d making device file descriptor non-blocking.", errno); + close(fd); + return -1; + } + + // Allocate device. (The device object takes ownership of the fd at this point.) + int32_t deviceId = mNextDeviceId++; + Device* device = new Device(fd, deviceId, String8(devicePath), identifier); + +#if 0 + LOGI("add device %d: %s\n", deviceId, devicePath); + LOGI(" bus: %04x\n" + " vendor %04x\n" + " product %04x\n" + " version %04x\n", + identifier.bus, identifier.vendor, identifier.product, identifier.version); + LOGI(" name: \"%s\"\n", identifier.name.string()); + LOGI(" location: \"%s\"\n", identifier.location.string()); + LOGI(" unique id: \"%s\"\n", identifier.uniqueId.string()); + LOGI(" driver: v%d.%d.%d\n", + driverVersion >> 16, (driverVersion >> 8) & 0xff, driverVersion & 0xff); +#endif + + // Load the configuration file for the device. + loadConfiguration(device); + + // Figure out the kinds of events the device reports. + + uint8_t key_bitmask[sizeof_bit_array(KEY_MAX + 1)]; + memset(key_bitmask, 0, sizeof(key_bitmask)); + + LOGV("Getting keys..."); + if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bitmask)), key_bitmask) >= 0) { + //LOGI("MAP\n"); + //for (int i = 0; i < sizeof(key_bitmask); i++) { + // LOGI("%d: 0x%02x\n", i, key_bitmask[i]); + //} + + // See if this is a keyboard. Ignore everything in the button range except for + // gamepads which are also considered keyboards. + if (containsNonZeroByte(key_bitmask, 0, sizeof_bit_array(BTN_MISC)) + || containsNonZeroByte(key_bitmask, sizeof_bit_array(BTN_GAMEPAD), + sizeof_bit_array(BTN_DIGI)) + || containsNonZeroByte(key_bitmask, sizeof_bit_array(KEY_OK), + sizeof_bit_array(KEY_MAX + 1))) { + device->classes |= INPUT_DEVICE_CLASS_KEYBOARD; + + device->keyBitmask = new uint8_t[sizeof(key_bitmask)]; + if (device->keyBitmask != NULL) { + memcpy(device->keyBitmask, key_bitmask, sizeof(key_bitmask)); + } else { + delete device; + LOGE("out of memory allocating key bitmask"); + return -1; + } + } + } + + // See if this is a cursor device such as a trackball or mouse. + if (test_bit(BTN_MOUSE, key_bitmask)) { + uint8_t rel_bitmask[sizeof_bit_array(REL_MAX + 1)]; + memset(rel_bitmask, 0, sizeof(rel_bitmask)); + LOGV("Getting relative controllers..."); + if (ioctl(fd, EVIOCGBIT(EV_REL, sizeof(rel_bitmask)), rel_bitmask) >= 0) { + if (test_bit(REL_X, rel_bitmask) && test_bit(REL_Y, rel_bitmask)) { + device->classes |= INPUT_DEVICE_CLASS_CURSOR; + } + } + } + + // See if this is a touch pad. + uint8_t abs_bitmask[sizeof_bit_array(ABS_MAX + 1)]; + memset(abs_bitmask, 0, sizeof(abs_bitmask)); + LOGV("Getting absolute controllers..."); + if (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(abs_bitmask)), abs_bitmask) >= 0) { + // Is this a new modern multi-touch driver? + if (test_bit(ABS_MT_POSITION_X, abs_bitmask) + && test_bit(ABS_MT_POSITION_Y, abs_bitmask)) { + device->classes |= INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_TOUCH_MT; + + // Is this an old style single-touch driver? + } else if (test_bit(BTN_TOUCH, key_bitmask) + && test_bit(ABS_X, abs_bitmask) && test_bit(ABS_Y, abs_bitmask)) { + device->classes |= INPUT_DEVICE_CLASS_TOUCH; + } + } + +#ifdef EV_SW + // figure out the switches this device reports + uint8_t sw_bitmask[sizeof_bit_array(SW_MAX + 1)]; + memset(sw_bitmask, 0, sizeof(sw_bitmask)); + bool hasSwitches = false; + if (ioctl(fd, EVIOCGBIT(EV_SW, sizeof(sw_bitmask)), sw_bitmask) >= 0) { + for (int i=0; i<EV_SW; i++) { + //LOGI("Device %d sw %d: has=%d", device->id, i, test_bit(i, sw_bitmask)); + if (test_bit(i, sw_bitmask)) { + hasSwitches = true; + if (mSwitches[i] == 0) { + mSwitches[i] = device->id; + } + } + } + } + if (hasSwitches) { + device->classes |= INPUT_DEVICE_CLASS_SWITCH; + } +#endif + + if ((device->classes & INPUT_DEVICE_CLASS_TOUCH)) { + // Load the virtual keys for the touch screen, if any. + // We do this now so that we can make sure to load the keymap if necessary. + status_t status = loadVirtualKeyMap(device); + if (!status) { + device->classes |= INPUT_DEVICE_CLASS_KEYBOARD; + } + } + + if ((device->classes & INPUT_DEVICE_CLASS_KEYBOARD) != 0) { + // Load the keymap for the device. + status_t status = loadKeyMap(device); + + // Set system properties for the keyboard. + setKeyboardProperties(device, false); + + // Register the keyboard as a built-in keyboard if it is eligible. + if (!status + && mBuiltInKeyboardId == -1 + && isEligibleBuiltInKeyboard(device->identifier, + device->configuration, &device->keyMap)) { + mBuiltInKeyboardId = device->id; + setKeyboardProperties(device, true); + } + + // 'Q' key support = cheap test of whether this is an alpha-capable kbd + if (hasKeycodeLocked(device, AKEYCODE_Q)) { + device->classes |= INPUT_DEVICE_CLASS_ALPHAKEY; + } + + // See if this device has a DPAD. + if (hasKeycodeLocked(device, AKEYCODE_DPAD_UP) && + hasKeycodeLocked(device, AKEYCODE_DPAD_DOWN) && + hasKeycodeLocked(device, AKEYCODE_DPAD_LEFT) && + hasKeycodeLocked(device, AKEYCODE_DPAD_RIGHT) && + hasKeycodeLocked(device, AKEYCODE_DPAD_CENTER)) { + device->classes |= INPUT_DEVICE_CLASS_DPAD; + } + + // See if this device has a gamepad. + for (size_t i = 0; i < sizeof(GAMEPAD_KEYCODES)/sizeof(GAMEPAD_KEYCODES[0]); i++) { + if (hasKeycodeLocked(device, GAMEPAD_KEYCODES[i])) { + device->classes |= INPUT_DEVICE_CLASS_GAMEPAD; + break; + } + } + } + + // If the device isn't recognized as something we handle, don't monitor it. + if (device->classes == 0) { + LOGV("Dropping device: id=%d, path='%s', name='%s'", + deviceId, devicePath, device->identifier.name.string()); + delete device; + return -1; + } + + LOGI("New device: id=%d, fd=%d, path='%s', name='%s', classes=0x%x, " + "configuration='%s', keyLayout='%s', keyCharacterMap='%s', builtinKeyboard=%s", + deviceId, fd, devicePath, device->identifier.name.string(), + device->classes, + device->configurationFile.string(), + device->keyMap.keyLayoutFile.string(), + device->keyMap.keyCharacterMapFile.string(), + toString(mBuiltInKeyboardId == deviceId)); + + struct pollfd pollfd; + pollfd.fd = fd; + pollfd.events = POLLIN; + pollfd.revents = 0; + mFds.push(pollfd); + mDevices.push(device); + + device->next = mOpeningDevices; + mOpeningDevices = device; + return 0; +} + +void EventHub::loadConfiguration(Device* device) { + device->configurationFile = getInputDeviceConfigurationFilePathByDeviceIdentifier( + device->identifier, INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION); + if (device->configurationFile.isEmpty()) { + LOGD("No input device configuration file found for device '%s'.", + device->identifier.name.string()); + } else { + status_t status = PropertyMap::load(device->configurationFile, + &device->configuration); + if (status) { + LOGE("Error loading input device configuration file for device '%s'. " + "Using default configuration.", + device->identifier.name.string()); + } + } +} + +status_t EventHub::loadVirtualKeyMap(Device* device) { + // The virtual key map is supplied by the kernel as a system board property file. + String8 path; + path.append("/sys/board_properties/virtualkeys."); + path.append(device->identifier.name); + if (access(path.string(), R_OK)) { + return NAME_NOT_FOUND; + } + return VirtualKeyMap::load(path, &device->virtualKeyMap); +} + +status_t EventHub::loadKeyMap(Device* device) { + return device->keyMap.load(device->identifier, device->configuration); +} + +void EventHub::setKeyboardProperties(Device* device, bool builtInKeyboard) { + int32_t id = builtInKeyboard ? 0 : device->id; + android::setKeyboardProperties(id, device->identifier, + device->keyMap.keyLayoutFile, device->keyMap.keyCharacterMapFile); +} + +void EventHub::clearKeyboardProperties(Device* device, bool builtInKeyboard) { + int32_t id = builtInKeyboard ? 0 : device->id; + android::clearKeyboardProperties(id); +} + +bool EventHub::hasKeycodeLocked(Device* device, int keycode) const { + if (!device->keyMap.haveKeyLayout() || !device->keyBitmask) { + return false; + } + + Vector<int32_t> scanCodes; + device->keyMap.keyLayoutMap->findScanCodes(keycode, &scanCodes); + const size_t N = scanCodes.size(); + for (size_t i=0; i<N && i<=KEY_MAX; i++) { + int32_t sc = scanCodes.itemAt(i); + if (sc >= 0 && sc <= KEY_MAX && test_bit(sc, device->keyBitmask)) { + return true; + } + } + + return false; +} + +int EventHub::closeDevice(const char *devicePath) { + AutoMutex _l(mLock); + + for (size_t i = FIRST_ACTUAL_DEVICE_INDEX; i < mDevices.size(); i++) { + Device* device = mDevices[i]; + if (device->path == devicePath) { + LOGI("Removed device: path=%s name=%s id=%d fd=%d classes=0x%x\n", + device->path.string(), device->identifier.name.string(), device->id, + device->fd, device->classes); + +#ifdef EV_SW + for (int j=0; j<EV_SW; j++) { + if (mSwitches[j] == device->id) { + mSwitches[j] = 0; + } + } +#endif + + if (device->id == mBuiltInKeyboardId) { + LOGW("built-in keyboard device %s (id=%d) is closing! the apps will not like this", + device->path.string(), mBuiltInKeyboardId); + mBuiltInKeyboardId = -1; + clearKeyboardProperties(device, true); + } + clearKeyboardProperties(device, false); + + mFds.removeAt(i); + mDevices.removeAt(i); + device->close(); + + device->next = mClosingDevices; + mClosingDevices = device; + return 0; + } + } + LOGE("remove device: %s not found\n", devicePath); + return -1; +} + +int EventHub::readNotify(int nfd) { +#ifdef HAVE_INOTIFY + int res; + char devname[PATH_MAX]; + char *filename; + char event_buf[512]; + int event_size; + int event_pos = 0; + struct inotify_event *event; + + LOGV("EventHub::readNotify nfd: %d\n", nfd); + res = read(nfd, event_buf, sizeof(event_buf)); + if(res < (int)sizeof(*event)) { + if(errno == EINTR) + return 0; + LOGW("could not get event, %s\n", strerror(errno)); + return 1; + } + //printf("got %d bytes of event information\n", res); + + strcpy(devname, DEVICE_PATH); + filename = devname + strlen(devname); + *filename++ = '/'; + + while(res >= (int)sizeof(*event)) { + event = (struct inotify_event *)(event_buf + event_pos); + //printf("%d: %08x \"%s\"\n", event->wd, event->mask, event->len ? event->name : ""); + if(event->len) { + strcpy(filename, event->name); + if(event->mask & IN_CREATE) { + openDevice(devname); + } + else { + closeDevice(devname); + } + } + event_size = sizeof(*event) + event->len; + res -= event_size; + event_pos += event_size; + } +#endif + return 0; +} + +int EventHub::scanDir(const char *dirname) +{ + char devname[PATH_MAX]; + char *filename; + DIR *dir; + struct dirent *de; + dir = opendir(dirname); + if(dir == NULL) + return -1; + strcpy(devname, dirname); + filename = devname + strlen(devname); + *filename++ = '/'; + while((de = readdir(dir))) { + if(de->d_name[0] == '.' && + (de->d_name[1] == '\0' || + (de->d_name[1] == '.' && de->d_name[2] == '\0'))) + continue; + strcpy(filename, de->d_name); + openDevice(devname); + } + closedir(dir); + return 0; +} + +void EventHub::dump(String8& dump) { + dump.append("Event Hub State:\n"); + + { // acquire lock + AutoMutex _l(mLock); + + dump.appendFormat(INDENT "BuiltInKeyboardId: %d\n", mBuiltInKeyboardId); + + dump.append(INDENT "Devices:\n"); + + for (size_t i = FIRST_ACTUAL_DEVICE_INDEX; i < mDevices.size(); i++) { + const Device* device = mDevices[i]; + if (device) { + if (mBuiltInKeyboardId == device->id) { + dump.appendFormat(INDENT2 "%d: %s (aka device 0 - built-in keyboard)\n", + device->id, device->identifier.name.string()); + } else { + dump.appendFormat(INDENT2 "%d: %s\n", device->id, + device->identifier.name.string()); + } + dump.appendFormat(INDENT3 "Classes: 0x%08x\n", device->classes); + dump.appendFormat(INDENT3 "Path: %s\n", device->path.string()); + dump.appendFormat(INDENT3 "Location: %s\n", device->identifier.location.string()); + dump.appendFormat(INDENT3 "UniqueId: %s\n", device->identifier.uniqueId.string()); + dump.appendFormat(INDENT3 "Identifier: bus=0x%04x, vendor=0x%04x, " + "product=0x%04x, version=0x%04x\n", + device->identifier.bus, device->identifier.vendor, + device->identifier.product, device->identifier.version); + dump.appendFormat(INDENT3 "KeyLayoutFile: %s\n", + device->keyMap.keyLayoutFile.string()); + dump.appendFormat(INDENT3 "KeyCharacterMapFile: %s\n", + device->keyMap.keyCharacterMapFile.string()); + dump.appendFormat(INDENT3 "ConfigurationFile: %s\n", + device->configurationFile.string()); + } + } + } // release lock +} + +}; // namespace android diff --git a/services/input/EventHub.h b/services/input/EventHub.h new file mode 100644 index 0000000..0ee0b9b --- /dev/null +++ b/services/input/EventHub.h @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2005 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. + */ + +// +#ifndef _RUNTIME_EVENT_HUB_H +#define _RUNTIME_EVENT_HUB_H + +#include <ui/Input.h> +#include <ui/Keyboard.h> +#include <ui/KeyLayoutMap.h> +#include <ui/KeyCharacterMap.h> +#include <ui/VirtualKeyMap.h> +#include <utils/String8.h> +#include <utils/threads.h> +#include <utils/Log.h> +#include <utils/threads.h> +#include <utils/List.h> +#include <utils/Errors.h> +#include <utils/PropertyMap.h> +#include <utils/Vector.h> + +#include <linux/input.h> + +/* These constants are not defined in linux/input.h but they are part of the multitouch + * input protocol. */ + +#define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */ +#define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */ +#define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */ +#define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */ +#define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */ +#define ABS_MT_POSITION_X 0x35 /* Center X ellipse position */ +#define ABS_MT_POSITION_Y 0x36 /* Center Y ellipse position */ +#define ABS_MT_TOOL_TYPE 0x37 /* Type of touching device (finger, pen, ...) */ +#define ABS_MT_BLOB_ID 0x38 /* Group a set of packets as a blob */ +#define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */ +#define ABS_MT_PRESSURE 0x3a /* Pressure on contact area */ + +#define MT_TOOL_FINGER 0 /* Identifies a finger */ +#define MT_TOOL_PEN 1 /* Identifies a pen */ + +#define SYN_MT_REPORT 2 + +/* Convenience constants. */ + +#define BTN_FIRST 0x100 // first button scancode +#define BTN_LAST 0x15f // last button scancode + +struct pollfd; + +namespace android { + +/* + * A raw event as retrieved from the EventHub. + */ +struct RawEvent { + nsecs_t when; + int32_t deviceId; + int32_t type; + int32_t scanCode; + int32_t keyCode; + int32_t value; + uint32_t flags; +}; + +/* Describes an absolute axis. */ +struct RawAbsoluteAxisInfo { + bool valid; // true if the information is valid, false otherwise + + int32_t minValue; // minimum value + int32_t maxValue; // maximum value + int32_t flat; // center flat position, eg. flat == 8 means center is between -8 and 8 + int32_t fuzz; // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise + + inline int32_t getRange() { return maxValue - minValue; } + + inline void clear() { + valid = false; + minValue = 0; + maxValue = 0; + flat = 0; + fuzz = 0; + } +}; + +/* + * Input device classes. + */ +enum { + /* The input device is a keyboard. */ + INPUT_DEVICE_CLASS_KEYBOARD = 0x00000001, + + /* The input device is an alpha-numeric keyboard (not just a dial pad). */ + INPUT_DEVICE_CLASS_ALPHAKEY = 0x00000002, + + /* The input device is a touchscreen or a touchpad (either single-touch or multi-touch). */ + INPUT_DEVICE_CLASS_TOUCH = 0x00000004, + + /* The input device is a cursor device such as a trackball or mouse. */ + INPUT_DEVICE_CLASS_CURSOR = 0x00000008, + + /* The input device is a multi-touch touchscreen. */ + INPUT_DEVICE_CLASS_TOUCH_MT = 0x00000010, + + /* The input device is a directional pad (implies keyboard, has DPAD keys). */ + INPUT_DEVICE_CLASS_DPAD = 0x00000020, + + /* The input device is a gamepad (implies keyboard, has BUTTON keys). */ + INPUT_DEVICE_CLASS_GAMEPAD = 0x00000040, + + /* The input device has switches. */ + INPUT_DEVICE_CLASS_SWITCH = 0x00000080, +}; + +/* + * Grand Central Station for events. + * + * The event hub aggregates input events received across all known input + * devices on the system, including devices that may be emulated by the simulator + * environment. In addition, the event hub generates fake input events to indicate + * when devices are added or removed. + * + * The event hub provides a stream of input events (via the getEvent function). + * It also supports querying the current actual state of input devices such as identifying + * which keys are currently down. Finally, the event hub keeps track of the capabilities of + * individual input devices, such as their class and the set of key codes that they support. + */ +class EventHubInterface : public virtual RefBase { +protected: + EventHubInterface() { } + virtual ~EventHubInterface() { } + +public: + // Synthetic raw event type codes produced when devices are added or removed. + enum { + // Sent when a device is added. + DEVICE_ADDED = 0x10000000, + // Sent when a device is removed. + DEVICE_REMOVED = 0x20000000, + // Sent when all added/removed devices from the most recent scan have been reported. + // This event is always sent at least once. + FINISHED_DEVICE_SCAN = 0x30000000, + }; + + virtual uint32_t getDeviceClasses(int32_t deviceId) const = 0; + + virtual String8 getDeviceName(int32_t deviceId) const = 0; + + virtual void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const = 0; + + virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis, + RawAbsoluteAxisInfo* outAxisInfo) const = 0; + + virtual status_t scancodeToKeycode(int32_t deviceId, int scancode, + int32_t* outKeycode, uint32_t* outFlags) const = 0; + + // exclude a particular device from opening + // this can be used to ignore input devices for sensors + virtual void addExcludedDevice(const char* deviceName) = 0; + + /* + * Wait for the next event to become available and return it. + * After returning, the EventHub holds onto a wake lock until the next call to getEvent. + * 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 + * for taking care of it (say, by poking the power manager user activity timer). + */ + virtual bool getEvent(RawEvent* outEvent) = 0; + + /* + * Query current input state. + */ + virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const = 0; + virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const = 0; + virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const = 0; + + /* + * Examine key input devices for specific framework keycode support + */ + virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes, + uint8_t* outFlags) const = 0; + + virtual bool hasLed(int32_t deviceId, int32_t led) const = 0; + virtual void setLedState(int32_t deviceId, int32_t led, bool on) = 0; + + virtual void getVirtualKeyDefinitions(int32_t deviceId, + Vector<VirtualKeyDefinition>& outVirtualKeys) const = 0; + + virtual void dump(String8& dump) = 0; +}; + +class EventHub : public EventHubInterface +{ +public: + EventHub(); + + status_t errorCheck() const; + + virtual uint32_t getDeviceClasses(int32_t deviceId) const; + + virtual String8 getDeviceName(int32_t deviceId) const; + + virtual void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const; + + virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis, + RawAbsoluteAxisInfo* outAxisInfo) const; + + virtual status_t scancodeToKeycode(int32_t deviceId, int scancode, + int32_t* outKeycode, uint32_t* outFlags) const; + + virtual void addExcludedDevice(const char* deviceName); + + virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const; + virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const; + virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const; + + virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) const; + + virtual bool getEvent(RawEvent* outEvent); + + virtual bool hasLed(int32_t deviceId, int32_t led) const; + virtual void setLedState(int32_t deviceId, int32_t led, bool on); + + virtual void getVirtualKeyDefinitions(int32_t deviceId, + Vector<VirtualKeyDefinition>& outVirtualKeys) const; + + virtual void dump(String8& dump); + +protected: + virtual ~EventHub(); + +private: + bool openPlatformInput(void); + + int openDevice(const char *devicePath); + int closeDevice(const char *devicePath); + int scanDir(const char *dirname); + int readNotify(int nfd); + + status_t mError; + + struct Device { + Device* next; + + int fd; + const int32_t id; + const String8 path; + const InputDeviceIdentifier identifier; + + uint32_t classes; + uint8_t* keyBitmask; + String8 configurationFile; + PropertyMap* configuration; + VirtualKeyMap* virtualKeyMap; + KeyMap keyMap; + + Device(int fd, int32_t id, const String8& path, const InputDeviceIdentifier& identifier); + ~Device(); + + void close(); + }; + + Device* getDeviceLocked(int32_t deviceId) const; + bool hasKeycodeLocked(Device* device, int keycode) const; + + int32_t getScanCodeStateLocked(Device* device, int32_t scanCode) const; + int32_t getKeyCodeStateLocked(Device* device, int32_t keyCode) const; + int32_t getSwitchStateLocked(Device* device, int32_t sw) const; + bool markSupportedKeyCodesLocked(Device* device, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) const; + + void loadConfiguration(Device* device); + status_t loadVirtualKeyMap(Device* device); + status_t loadKeyMap(Device* device); + void setKeyboardProperties(Device* device, bool builtInKeyboard); + void clearKeyboardProperties(Device* device, bool builtInKeyboard); + + // Protect all internal state. + mutable Mutex mLock; + + // The actual id of the built-in keyboard, or -1 if none. + // EventHub remaps the built-in keyboard to id 0 externally as required by the API. + int32_t mBuiltInKeyboardId; + + int32_t mNextDeviceId; + + // Parallel arrays of fds and devices. + // First index is reserved for inotify. + Vector<struct pollfd> mFds; + Vector<Device*> mDevices; + + Device *mOpeningDevices; + Device *mClosingDevices; + + bool mOpened; + bool mNeedToSendFinishedDeviceScan; + List<String8> mExcludedDevices; + + // device ids that report particular switches. +#ifdef EV_SW + int32_t mSwitches[SW_MAX + 1]; +#endif + + static const int INPUT_BUFFER_SIZE = 64; + struct input_event mInputBufferData[INPUT_BUFFER_SIZE]; + size_t mInputBufferIndex; + size_t mInputBufferCount; + size_t mInputFdIndex; +}; + +}; // namespace android + +#endif // _RUNTIME_EVENT_HUB_H diff --git a/services/input/InputApplication.h b/services/input/InputApplication.h new file mode 100644 index 0000000..cc80062 --- /dev/null +++ b/services/input/InputApplication.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef _UI_INPUT_APPLICATION_H +#define _UI_INPUT_APPLICATION_H + +#include <ui/Input.h> + +#include <utils/RefBase.h> +#include <utils/Timers.h> +#include <utils/String8.h> + +namespace android { + +/* + * A handle to an application that can receive input. + * Used by the native input dispatcher to indirectly refer to the window manager objects + * that describe an application. + */ +class InputApplicationHandle : public RefBase { +protected: + InputApplicationHandle() { } + virtual ~InputApplicationHandle() { } +}; + + +/* + * An input application describes properties of an application that can receive input. + */ +struct InputApplication { + sp<InputApplicationHandle> inputApplicationHandle; + String8 name; + nsecs_t dispatchingTimeout; +}; + +} // namespace android + +#endif // _UI_INPUT_APPLICATION_H diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp new file mode 100644 index 0000000..cbfdd75 --- /dev/null +++ b/services/input/InputDispatcher.cpp @@ -0,0 +1,3862 @@ +/* + * Copyright (C) 2010 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. + */ + +#define LOG_TAG "InputDispatcher" + +//#define LOG_NDEBUG 0 + +// Log detailed debug messages about each inbound event notification to the dispatcher. +#define DEBUG_INBOUND_EVENT_DETAILS 0 + +// Log detailed debug messages about each outbound event processed by the dispatcher. +#define DEBUG_OUTBOUND_EVENT_DETAILS 0 + +// Log debug messages about batching. +#define DEBUG_BATCHING 0 + +// Log debug messages about the dispatch cycle. +#define DEBUG_DISPATCH_CYCLE 0 + +// Log debug messages about registrations. +#define DEBUG_REGISTRATION 0 + +// Log debug messages about performance statistics. +#define DEBUG_PERFORMANCE_STATISTICS 0 + +// Log debug messages about input event injection. +#define DEBUG_INJECTION 0 + +// Log debug messages about input event throttling. +#define DEBUG_THROTTLING 0 + +// Log debug messages about input focus tracking. +#define DEBUG_FOCUS 0 + +// Log debug messages about the app switch latency optimization. +#define DEBUG_APP_SWITCH 0 + +#include "InputDispatcher.h" + +#include <cutils/log.h> +#include <ui/PowerManager.h> + +#include <stddef.h> +#include <unistd.h> +#include <errno.h> +#include <limits.h> + +#define INDENT " " +#define INDENT2 " " + +namespace android { + +// Default input dispatching timeout if there is no focused application or paused window +// from which to determine an appropriate dispatching timeout. +const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec + +// Amount of time to allow for all pending events to be processed when an app switch +// key is on the way. This is used to preempt input dispatch and drop input events +// when an application takes too long to respond and the user has pressed an app switch key. +const nsecs_t APP_SWITCH_TIMEOUT = 500 * 1000000LL; // 0.5sec + +// Amount of time to allow for an event to be dispatched (measured since its eventTime) +// before considering it stale and dropping it. +const nsecs_t STALE_EVENT_TIMEOUT = 10000 * 1000000LL; // 10sec + + +static inline nsecs_t now() { + return systemTime(SYSTEM_TIME_MONOTONIC); +} + +static inline const char* toString(bool value) { + return value ? "true" : "false"; +} + +static inline int32_t getMotionEventActionPointerIndex(int32_t action) { + return (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) + >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; +} + +static bool isValidKeyAction(int32_t action) { + switch (action) { + case AKEY_EVENT_ACTION_DOWN: + case AKEY_EVENT_ACTION_UP: + return true; + default: + return false; + } +} + +static bool validateKeyEvent(int32_t action) { + if (! isValidKeyAction(action)) { + LOGE("Key event has invalid action code 0x%x", action); + return false; + } + return true; +} + +static bool isValidMotionAction(int32_t action, size_t pointerCount) { + switch (action & AMOTION_EVENT_ACTION_MASK) { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_CANCEL: + case AMOTION_EVENT_ACTION_MOVE: + case AMOTION_EVENT_ACTION_OUTSIDE: + return true; + case AMOTION_EVENT_ACTION_POINTER_DOWN: + case AMOTION_EVENT_ACTION_POINTER_UP: { + int32_t index = getMotionEventActionPointerIndex(action); + return index >= 0 && size_t(index) < pointerCount; + } + default: + return false; + } +} + +static bool validateMotionEvent(int32_t action, size_t pointerCount, + const int32_t* pointerIds) { + if (! isValidMotionAction(action, pointerCount)) { + LOGE("Motion event has invalid action code 0x%x", action); + return false; + } + if (pointerCount < 1 || pointerCount > MAX_POINTERS) { + LOGE("Motion event has invalid pointer count %d; value must be between 1 and %d.", + pointerCount, MAX_POINTERS); + return false; + } + BitSet32 pointerIdBits; + for (size_t i = 0; i < pointerCount; i++) { + int32_t id = pointerIds[i]; + if (id < 0 || id > MAX_POINTER_ID) { + LOGE("Motion event has invalid pointer id %d; value must be between 0 and %d", + id, MAX_POINTER_ID); + return false; + } + if (pointerIdBits.hasBit(id)) { + LOGE("Motion event has duplicate pointer id %d", id); + return false; + } + pointerIdBits.markBit(id); + } + return true; +} + +static void dumpRegion(String8& dump, const SkRegion& region) { + if (region.isEmpty()) { + dump.append("<empty>"); + return; + } + + bool first = true; + for (SkRegion::Iterator it(region); !it.done(); it.next()) { + if (first) { + first = false; + } else { + dump.append("|"); + } + const SkIRect& rect = it.rect(); + dump.appendFormat("[%d,%d][%d,%d]", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom); + } +} + + +// --- InputDispatcher --- + +InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) : + mPolicy(policy), + mPendingEvent(NULL), mAppSwitchSawKeyDown(false), mAppSwitchDueTime(LONG_LONG_MAX), + mNextUnblockedEvent(NULL), + mDispatchEnabled(true), mDispatchFrozen(false), + mFocusedWindow(NULL), + mFocusedApplication(NULL), + mCurrentInputTargetsValid(false), + mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE) { + mLooper = new Looper(false); + + mInboundQueue.headSentinel.refCount = -1; + mInboundQueue.headSentinel.type = EventEntry::TYPE_SENTINEL; + mInboundQueue.headSentinel.eventTime = LONG_LONG_MIN; + + mInboundQueue.tailSentinel.refCount = -1; + mInboundQueue.tailSentinel.type = EventEntry::TYPE_SENTINEL; + mInboundQueue.tailSentinel.eventTime = LONG_LONG_MAX; + + mKeyRepeatState.lastKeyEntry = NULL; + + int32_t maxEventsPerSecond = policy->getMaxEventsPerSecond(); + mThrottleState.minTimeBetweenEvents = 1000000000LL / maxEventsPerSecond; + mThrottleState.lastDeviceId = -1; + +#if DEBUG_THROTTLING + mThrottleState.originalSampleCount = 0; + LOGD("Throttling - Max events per second = %d", maxEventsPerSecond); +#endif +} + +InputDispatcher::~InputDispatcher() { + { // acquire lock + AutoMutex _l(mLock); + + resetKeyRepeatLocked(); + releasePendingEventLocked(); + drainInboundQueueLocked(); + } + + while (mConnectionsByReceiveFd.size() != 0) { + unregisterInputChannel(mConnectionsByReceiveFd.valueAt(0)->inputChannel); + } +} + +void InputDispatcher::dispatchOnce() { + nsecs_t keyRepeatTimeout = mPolicy->getKeyRepeatTimeout(); + nsecs_t keyRepeatDelay = mPolicy->getKeyRepeatDelay(); + + nsecs_t nextWakeupTime = LONG_LONG_MAX; + { // acquire lock + AutoMutex _l(mLock); + dispatchOnceInnerLocked(keyRepeatTimeout, keyRepeatDelay, & nextWakeupTime); + + if (runCommandsLockedInterruptible()) { + nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately + } + } // release lock + + // Wait for callback or timeout or wake. (make sure we round up, not down) + nsecs_t currentTime = now(); + int32_t timeoutMillis; + 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); +} + +void InputDispatcher::dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout, + nsecs_t keyRepeatDelay, nsecs_t* nextWakeupTime) { + nsecs_t currentTime = now(); + + // Reset the key repeat timer whenever we disallow key events, even if the next event + // is not a key. This is to ensure that we abort a key repeat if the device is just coming + // out of sleep. + if (keyRepeatTimeout < 0) { + resetKeyRepeatLocked(); + } + + // If dispatching is frozen, do not process timeouts or try to deliver any new events. + if (mDispatchFrozen) { +#if DEBUG_FOCUS + LOGD("Dispatch frozen. Waiting some more."); +#endif + return; + } + + // Optimize latency of app switches. + // Essentially we start a short timeout when an app switch key (HOME / ENDCALL) has + // been pressed. When it expires, we preempt dispatch and drop all other pending events. + bool isAppSwitchDue = mAppSwitchDueTime <= currentTime; + if (mAppSwitchDueTime < *nextWakeupTime) { + *nextWakeupTime = mAppSwitchDueTime; + } + + // Ready to start a new event. + // If we don't already have a pending event, go grab one. + if (! mPendingEvent) { + if (mInboundQueue.isEmpty()) { + if (isAppSwitchDue) { + // The inbound queue is empty so the app switch key we were waiting + // for will never arrive. Stop waiting for it. + resetPendingAppSwitchLocked(false); + isAppSwitchDue = false; + } + + // Synthesize a key repeat if appropriate. + if (mKeyRepeatState.lastKeyEntry) { + if (currentTime >= mKeyRepeatState.nextRepeatTime) { + mPendingEvent = synthesizeKeyRepeatLocked(currentTime, keyRepeatDelay); + } else { + if (mKeyRepeatState.nextRepeatTime < *nextWakeupTime) { + *nextWakeupTime = mKeyRepeatState.nextRepeatTime; + } + } + } + if (! mPendingEvent) { + return; + } + } else { + // Inbound queue has at least one entry. + EventEntry* entry = mInboundQueue.headSentinel.next; + + // Throttle the entry if it is a move event and there are no + // other events behind it in the queue. Due to movement batching, additional + // samples may be appended to this event by the time the throttling timeout + // expires. + // TODO Make this smarter and consider throttling per device independently. + if (entry->type == EventEntry::TYPE_MOTION + && !isAppSwitchDue + && mDispatchEnabled + && (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) + && !entry->isInjected()) { + MotionEntry* motionEntry = static_cast<MotionEntry*>(entry); + int32_t deviceId = motionEntry->deviceId; + uint32_t source = motionEntry->source; + if (! isAppSwitchDue + && motionEntry->next == & mInboundQueue.tailSentinel // exactly one event + && motionEntry->action == AMOTION_EVENT_ACTION_MOVE + && deviceId == mThrottleState.lastDeviceId + && source == mThrottleState.lastSource) { + nsecs_t nextTime = mThrottleState.lastEventTime + + mThrottleState.minTimeBetweenEvents; + if (currentTime < nextTime) { + // Throttle it! +#if DEBUG_THROTTLING + LOGD("Throttling - Delaying motion event for " + "device %d, source 0x%08x by up to %0.3fms.", + deviceId, source, (nextTime - currentTime) * 0.000001); +#endif + if (nextTime < *nextWakeupTime) { + *nextWakeupTime = nextTime; + } + if (mThrottleState.originalSampleCount == 0) { + mThrottleState.originalSampleCount = + motionEntry->countSamples(); + } + return; + } + } + +#if DEBUG_THROTTLING + if (mThrottleState.originalSampleCount != 0) { + uint32_t count = motionEntry->countSamples(); + LOGD("Throttling - Motion event sample count grew by %d from %d to %d.", + count - mThrottleState.originalSampleCount, + mThrottleState.originalSampleCount, count); + mThrottleState.originalSampleCount = 0; + } +#endif + + mThrottleState.lastEventTime = entry->eventTime < currentTime + ? entry->eventTime : currentTime; + mThrottleState.lastDeviceId = deviceId; + mThrottleState.lastSource = source; + } + + mInboundQueue.dequeue(entry); + mPendingEvent = entry; + } + + // Poke user activity for this event. + if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) { + pokeUserActivityLocked(mPendingEvent); + } + } + + // Now we have an event to dispatch. + // All events are eventually dequeued and processed this way, even if we intend to drop them. + assert(mPendingEvent != NULL); + bool done = false; + DropReason dropReason = DROP_REASON_NOT_DROPPED; + if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) { + dropReason = DROP_REASON_POLICY; + } else if (!mDispatchEnabled) { + dropReason = DROP_REASON_DISABLED; + } + + if (mNextUnblockedEvent == mPendingEvent) { + mNextUnblockedEvent = NULL; + } + + switch (mPendingEvent->type) { + case EventEntry::TYPE_CONFIGURATION_CHANGED: { + ConfigurationChangedEntry* typedEntry = + static_cast<ConfigurationChangedEntry*>(mPendingEvent); + done = dispatchConfigurationChangedLocked(currentTime, typedEntry); + dropReason = DROP_REASON_NOT_DROPPED; // configuration changes are never dropped + break; + } + + case EventEntry::TYPE_KEY: { + KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent); + if (isAppSwitchDue) { + if (isAppSwitchKeyEventLocked(typedEntry)) { + resetPendingAppSwitchLocked(true); + isAppSwitchDue = false; + } else if (dropReason == DROP_REASON_NOT_DROPPED) { + dropReason = DROP_REASON_APP_SWITCH; + } + } + if (dropReason == DROP_REASON_NOT_DROPPED + && isStaleEventLocked(currentTime, typedEntry)) { + dropReason = DROP_REASON_STALE; + } + if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) { + dropReason = DROP_REASON_BLOCKED; + } + done = dispatchKeyLocked(currentTime, typedEntry, keyRepeatTimeout, + &dropReason, nextWakeupTime); + break; + } + + case EventEntry::TYPE_MOTION: { + MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent); + if (dropReason == DROP_REASON_NOT_DROPPED && isAppSwitchDue) { + dropReason = DROP_REASON_APP_SWITCH; + } + if (dropReason == DROP_REASON_NOT_DROPPED + && isStaleEventLocked(currentTime, typedEntry)) { + dropReason = DROP_REASON_STALE; + } + if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) { + dropReason = DROP_REASON_BLOCKED; + } + done = dispatchMotionLocked(currentTime, typedEntry, + &dropReason, nextWakeupTime); + break; + } + + default: + assert(false); + break; + } + + if (done) { + if (dropReason != DROP_REASON_NOT_DROPPED) { + dropInboundEventLocked(mPendingEvent, dropReason); + } + + releasePendingEventLocked(); + *nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately + } +} + +bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) { + bool needWake = mInboundQueue.isEmpty(); + mInboundQueue.enqueueAtTail(entry); + + switch (entry->type) { + case EventEntry::TYPE_KEY: { + // Optimize app switch latency. + // If the application takes too long to catch up then we drop all events preceding + // the app switch key. + KeyEntry* keyEntry = static_cast<KeyEntry*>(entry); + if (isAppSwitchKeyEventLocked(keyEntry)) { + if (keyEntry->action == AKEY_EVENT_ACTION_DOWN) { + mAppSwitchSawKeyDown = true; + } else if (keyEntry->action == AKEY_EVENT_ACTION_UP) { + if (mAppSwitchSawKeyDown) { +#if DEBUG_APP_SWITCH + LOGD("App switch is pending!"); +#endif + mAppSwitchDueTime = keyEntry->eventTime + APP_SWITCH_TIMEOUT; + mAppSwitchSawKeyDown = false; + needWake = true; + } + } + } + break; + } + + case EventEntry::TYPE_MOTION: { + // Optimize case where the current application is unresponsive and the user + // decides to touch a window in a different application. + // If the application takes too long to catch up then we drop all events preceding + // the touch into the other window. + MotionEntry* motionEntry = static_cast<MotionEntry*>(entry); + if (motionEntry->action == AMOTION_EVENT_ACTION_DOWN + && (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) + && mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY + && mInputTargetWaitApplication != NULL) { + int32_t x = int32_t(motionEntry->firstSample.pointerCoords[0].x); + int32_t y = int32_t(motionEntry->firstSample.pointerCoords[0].y); + const InputWindow* touchedWindow = findTouchedWindowAtLocked(x, y); + if (touchedWindow + && touchedWindow->inputWindowHandle != NULL + && touchedWindow->inputWindowHandle->getInputApplicationHandle() + != mInputTargetWaitApplication) { + // User touched a different application than the one we are waiting on. + // Flag the event, and start pruning the input queue. + mNextUnblockedEvent = motionEntry; + needWake = true; + } + } + break; + } + } + + return needWake; +} + +const InputWindow* InputDispatcher::findTouchedWindowAtLocked(int32_t x, int32_t y) { + // Traverse windows from front to back to find touched window. + size_t numWindows = mWindows.size(); + for (size_t i = 0; i < numWindows; i++) { + const InputWindow* window = & mWindows.editItemAt(i); + int32_t flags = window->layoutParamsFlags; + + if (window->visible) { + if (!(flags & InputWindow::FLAG_NOT_TOUCHABLE)) { + bool isTouchModal = (flags & (InputWindow::FLAG_NOT_FOCUSABLE + | InputWindow::FLAG_NOT_TOUCH_MODAL)) == 0; + if (isTouchModal || window->touchableRegionContainsPoint(x, y)) { + // Found window. + return window; + } + } + } + + if (flags & InputWindow::FLAG_SYSTEM_ERROR) { + // Error window is on top but not visible, so touch is dropped. + return NULL; + } + } + return NULL; +} + +void InputDispatcher::dropInboundEventLocked(EventEntry* entry, DropReason dropReason) { + const char* reason; + switch (dropReason) { + case DROP_REASON_POLICY: +#if DEBUG_INBOUND_EVENT_DETAILS + LOGD("Dropped event because policy consumed it."); +#endif + reason = "inbound event was dropped because the policy consumed it"; + break; + case DROP_REASON_DISABLED: + LOGI("Dropped event because input dispatch is disabled."); + reason = "inbound event was dropped because input dispatch is disabled"; + break; + case DROP_REASON_APP_SWITCH: + LOGI("Dropped event because of pending overdue app switch."); + reason = "inbound event was dropped because of pending overdue app switch"; + break; + case DROP_REASON_BLOCKED: + LOGI("Dropped event because the current application is not responding and the user " + "has started interating with a different application."); + reason = "inbound event was dropped because the current application is not responding " + "and the user has started interating with a different application"; + break; + case DROP_REASON_STALE: + LOGI("Dropped event because it is stale."); + reason = "inbound event was dropped because it is stale"; + break; + default: + assert(false); + return; + } + + switch (entry->type) { + case EventEntry::TYPE_KEY: + synthesizeCancelationEventsForAllConnectionsLocked( + InputState::CANCEL_NON_POINTER_EVENTS, reason); + break; + case EventEntry::TYPE_MOTION: { + MotionEntry* motionEntry = static_cast<MotionEntry*>(entry); + if (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) { + synthesizeCancelationEventsForAllConnectionsLocked( + InputState::CANCEL_POINTER_EVENTS, reason); + } else { + synthesizeCancelationEventsForAllConnectionsLocked( + InputState::CANCEL_NON_POINTER_EVENTS, reason); + } + break; + } + } +} + +bool InputDispatcher::isAppSwitchKeyCode(int32_t keyCode) { + return keyCode == AKEYCODE_HOME || keyCode == AKEYCODE_ENDCALL; +} + +bool InputDispatcher::isAppSwitchKeyEventLocked(KeyEntry* keyEntry) { + return ! (keyEntry->flags & AKEY_EVENT_FLAG_CANCELED) + && isAppSwitchKeyCode(keyEntry->keyCode) + && (keyEntry->policyFlags & POLICY_FLAG_TRUSTED) + && (keyEntry->policyFlags & POLICY_FLAG_PASS_TO_USER); +} + +bool InputDispatcher::isAppSwitchPendingLocked() { + return mAppSwitchDueTime != LONG_LONG_MAX; +} + +void InputDispatcher::resetPendingAppSwitchLocked(bool handled) { + mAppSwitchDueTime = LONG_LONG_MAX; + +#if DEBUG_APP_SWITCH + if (handled) { + LOGD("App switch has arrived."); + } else { + LOGD("App switch was abandoned."); + } +#endif +} + +bool InputDispatcher::isStaleEventLocked(nsecs_t currentTime, EventEntry* entry) { + return currentTime - entry->eventTime >= STALE_EVENT_TIMEOUT; +} + +bool InputDispatcher::runCommandsLockedInterruptible() { + if (mCommandQueue.isEmpty()) { + return false; + } + + do { + CommandEntry* commandEntry = mCommandQueue.dequeueAtHead(); + + Command command = commandEntry->command; + (this->*command)(commandEntry); // commands are implicitly 'LockedInterruptible' + + commandEntry->connection.clear(); + mAllocator.releaseCommandEntry(commandEntry); + } while (! mCommandQueue.isEmpty()); + return true; +} + +InputDispatcher::CommandEntry* InputDispatcher::postCommandLocked(Command command) { + CommandEntry* commandEntry = mAllocator.obtainCommandEntry(command); + mCommandQueue.enqueueAtTail(commandEntry); + return commandEntry; +} + +void InputDispatcher::drainInboundQueueLocked() { + while (! mInboundQueue.isEmpty()) { + EventEntry* entry = mInboundQueue.dequeueAtHead(); + releaseInboundEventLocked(entry); + } +} + +void InputDispatcher::releasePendingEventLocked() { + if (mPendingEvent) { + releaseInboundEventLocked(mPendingEvent); + mPendingEvent = NULL; + } +} + +void InputDispatcher::releaseInboundEventLocked(EventEntry* entry) { + InjectionState* injectionState = entry->injectionState; + if (injectionState && injectionState->injectionResult == INPUT_EVENT_INJECTION_PENDING) { +#if DEBUG_DISPATCH_CYCLE + LOGD("Injected inbound event was dropped."); +#endif + setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED); + } + mAllocator.releaseEventEntry(entry); +} + +void InputDispatcher::resetKeyRepeatLocked() { + if (mKeyRepeatState.lastKeyEntry) { + mAllocator.releaseKeyEntry(mKeyRepeatState.lastKeyEntry); + mKeyRepeatState.lastKeyEntry = NULL; + } +} + +InputDispatcher::KeyEntry* InputDispatcher::synthesizeKeyRepeatLocked( + nsecs_t currentTime, nsecs_t keyRepeatDelay) { + KeyEntry* entry = mKeyRepeatState.lastKeyEntry; + + // Reuse the repeated key entry if it is otherwise unreferenced. + uint32_t policyFlags = (entry->policyFlags & POLICY_FLAG_RAW_MASK) + | POLICY_FLAG_PASS_TO_USER | POLICY_FLAG_TRUSTED; + if (entry->refCount == 1) { + mAllocator.recycleKeyEntry(entry); + entry->eventTime = currentTime; + entry->policyFlags = policyFlags; + entry->repeatCount += 1; + } else { + KeyEntry* newEntry = mAllocator.obtainKeyEntry(currentTime, + entry->deviceId, entry->source, policyFlags, + entry->action, entry->flags, entry->keyCode, entry->scanCode, + entry->metaState, entry->repeatCount + 1, entry->downTime); + + mKeyRepeatState.lastKeyEntry = newEntry; + mAllocator.releaseKeyEntry(entry); + + entry = newEntry; + } + entry->syntheticRepeat = true; + + // Increment reference count since we keep a reference to the event in + // mKeyRepeatState.lastKeyEntry in addition to the one we return. + entry->refCount += 1; + + if (entry->repeatCount == 1) { + entry->flags |= AKEY_EVENT_FLAG_LONG_PRESS; + } + + mKeyRepeatState.nextRepeatTime = currentTime + keyRepeatDelay; + return entry; +} + +bool InputDispatcher::dispatchConfigurationChangedLocked( + nsecs_t currentTime, ConfigurationChangedEntry* entry) { +#if DEBUG_OUTBOUND_EVENT_DETAILS + LOGD("dispatchConfigurationChanged - eventTime=%lld", entry->eventTime); +#endif + + // Reset key repeating in case a keyboard device was added or removed or something. + resetKeyRepeatLocked(); + + // Enqueue a command to run outside the lock to tell the policy that the configuration changed. + CommandEntry* commandEntry = postCommandLocked( + & InputDispatcher::doNotifyConfigurationChangedInterruptible); + commandEntry->eventTime = entry->eventTime; + return true; +} + +bool InputDispatcher::dispatchKeyLocked( + nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout, + DropReason* dropReason, nsecs_t* nextWakeupTime) { + // Preprocessing. + if (! entry->dispatchInProgress) { + if (entry->repeatCount == 0 + && entry->action == AKEY_EVENT_ACTION_DOWN + && (entry->policyFlags & POLICY_FLAG_TRUSTED) + && !entry->isInjected()) { + if (mKeyRepeatState.lastKeyEntry + && mKeyRepeatState.lastKeyEntry->keyCode == entry->keyCode) { + // We have seen two identical key downs in a row which indicates that the device + // driver is automatically generating key repeats itself. We take note of the + // repeat here, but we disable our own next key repeat timer since it is clear that + // we will not need to synthesize key repeats ourselves. + entry->repeatCount = mKeyRepeatState.lastKeyEntry->repeatCount + 1; + resetKeyRepeatLocked(); + mKeyRepeatState.nextRepeatTime = LONG_LONG_MAX; // don't generate repeats ourselves + } else { + // Not a repeat. Save key down state in case we do see a repeat later. + resetKeyRepeatLocked(); + mKeyRepeatState.nextRepeatTime = entry->eventTime + keyRepeatTimeout; + } + mKeyRepeatState.lastKeyEntry = entry; + entry->refCount += 1; + } else if (! entry->syntheticRepeat) { + resetKeyRepeatLocked(); + } + + entry->dispatchInProgress = true; + resetTargetsLocked(); + + logOutboundKeyDetailsLocked("dispatchKey - ", entry); + } + + // Give the policy a chance to intercept the key. + if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) { + if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) { + CommandEntry* commandEntry = postCommandLocked( + & InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible); + if (mFocusedWindow) { + commandEntry->inputWindowHandle = mFocusedWindow->inputWindowHandle; + } + commandEntry->keyEntry = entry; + entry->refCount += 1; + return false; // wait for the command to run + } else { + entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE; + } + } else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) { + if (*dropReason == DROP_REASON_NOT_DROPPED) { + *dropReason = DROP_REASON_POLICY; + } + } + + // Clean up if dropping the event. + if (*dropReason != DROP_REASON_NOT_DROPPED) { + resetTargetsLocked(); + setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY + ? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED); + return true; + } + + // Identify targets. + if (! mCurrentInputTargetsValid) { + int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime, + entry, nextWakeupTime); + if (injectionResult == INPUT_EVENT_INJECTION_PENDING) { + return false; + } + + setInjectionResultLocked(entry, injectionResult); + if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) { + return true; + } + + addMonitoringTargetsLocked(); + commitTargetsLocked(); + } + + // Dispatch the key. + dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false); + return true; +} + +void InputDispatcher::logOutboundKeyDetailsLocked(const char* prefix, const KeyEntry* entry) { +#if DEBUG_OUTBOUND_EVENT_DETAILS + LOGD("%seventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, " + "action=0x%x, flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, " + "repeatCount=%d, downTime=%lld", + prefix, + entry->eventTime, entry->deviceId, entry->source, entry->policyFlags, + entry->action, entry->flags, entry->keyCode, entry->scanCode, entry->metaState, + entry->repeatCount, entry->downTime); +#endif +} + +bool InputDispatcher::dispatchMotionLocked( + nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) { + // Preprocessing. + if (! entry->dispatchInProgress) { + entry->dispatchInProgress = true; + resetTargetsLocked(); + + logOutboundMotionDetailsLocked("dispatchMotion - ", entry); + } + + // Clean up if dropping the event. + if (*dropReason != DROP_REASON_NOT_DROPPED) { + resetTargetsLocked(); + setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY + ? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED); + return true; + } + + bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER; + + // Identify targets. + if (! mCurrentInputTargetsValid) { + int32_t injectionResult; + if (isPointerEvent) { + // Pointer event. (eg. touchscreen) + injectionResult = findTouchedWindowTargetsLocked(currentTime, + entry, nextWakeupTime); + } else { + // Non touch event. (eg. trackball) + injectionResult = findFocusedWindowTargetsLocked(currentTime, + entry, nextWakeupTime); + } + if (injectionResult == INPUT_EVENT_INJECTION_PENDING) { + return false; + } + + setInjectionResultLocked(entry, injectionResult); + if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) { + return true; + } + + addMonitoringTargetsLocked(); + commitTargetsLocked(); + } + + // Dispatch the motion. + dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false); + return true; +} + + +void InputDispatcher::logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry) { +#if DEBUG_OUTBOUND_EVENT_DETAILS + LOGD("%seventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, " + "action=0x%x, flags=0x%x, " + "metaState=0x%x, edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%lld", + prefix, + entry->eventTime, entry->deviceId, entry->source, entry->policyFlags, + entry->action, entry->flags, + entry->metaState, entry->edgeFlags, entry->xPrecision, entry->yPrecision, + entry->downTime); + + // Print the most recent sample that we have available, this may change due to batching. + size_t sampleCount = 1; + const MotionSample* sample = & entry->firstSample; + for (; sample->next != NULL; sample = sample->next) { + sampleCount += 1; + } + for (uint32_t i = 0; i < entry->pointerCount; i++) { + LOGD(" Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f, " + "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, " + "orientation=%f", + i, entry->pointerIds[i], + sample->pointerCoords[i].x, sample->pointerCoords[i].y, + sample->pointerCoords[i].pressure, sample->pointerCoords[i].size, + sample->pointerCoords[i].touchMajor, sample->pointerCoords[i].touchMinor, + sample->pointerCoords[i].toolMajor, sample->pointerCoords[i].toolMinor, + sample->pointerCoords[i].orientation); + } + + // Keep in mind that due to batching, it is possible for the number of samples actually + // dispatched to change before the application finally consumed them. + if (entry->action == AMOTION_EVENT_ACTION_MOVE) { + LOGD(" ... Total movement samples currently batched %d ...", sampleCount); + } +#endif +} + +void InputDispatcher::dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTime, + EventEntry* eventEntry, bool resumeWithAppendedMotionSample) { +#if DEBUG_DISPATCH_CYCLE + LOGD("dispatchEventToCurrentInputTargets - " + "resumeWithAppendedMotionSample=%s", + toString(resumeWithAppendedMotionSample)); +#endif + + assert(eventEntry->dispatchInProgress); // should already have been set to true + + pokeUserActivityLocked(eventEntry); + + for (size_t i = 0; i < mCurrentInputTargets.size(); i++) { + const InputTarget& inputTarget = mCurrentInputTargets.itemAt(i); + + ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel); + if (connectionIndex >= 0) { + sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); + prepareDispatchCycleLocked(currentTime, connection, eventEntry, & inputTarget, + resumeWithAppendedMotionSample); + } else { +#if DEBUG_FOCUS + LOGD("Dropping event delivery to target with channel '%s' because it " + "is no longer registered with the input dispatcher.", + inputTarget.inputChannel->getName().string()); +#endif + } + } +} + +void InputDispatcher::resetTargetsLocked() { + mCurrentInputTargetsValid = false; + mCurrentInputTargets.clear(); + mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE; + mInputTargetWaitApplication.clear(); +} + +void InputDispatcher::commitTargetsLocked() { + mCurrentInputTargetsValid = true; +} + +int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime, + const EventEntry* entry, const InputApplication* application, const InputWindow* window, + nsecs_t* nextWakeupTime) { + if (application == NULL && window == NULL) { + if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) { +#if DEBUG_FOCUS + LOGD("Waiting for system to become ready for input."); +#endif + mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY; + mInputTargetWaitStartTime = currentTime; + mInputTargetWaitTimeoutTime = LONG_LONG_MAX; + mInputTargetWaitTimeoutExpired = false; + mInputTargetWaitApplication.clear(); + } + } else { + if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) { +#if DEBUG_FOCUS + LOGD("Waiting for application to become ready for input: %s", + getApplicationWindowLabelLocked(application, window).string()); +#endif + nsecs_t timeout = window ? window->dispatchingTimeout : + application ? application->dispatchingTimeout : DEFAULT_INPUT_DISPATCHING_TIMEOUT; + + mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY; + mInputTargetWaitStartTime = currentTime; + mInputTargetWaitTimeoutTime = currentTime + timeout; + mInputTargetWaitTimeoutExpired = false; + mInputTargetWaitApplication.clear(); + + if (window && window->inputWindowHandle != NULL) { + mInputTargetWaitApplication = + window->inputWindowHandle->getInputApplicationHandle(); + } + if (mInputTargetWaitApplication == NULL && application) { + mInputTargetWaitApplication = application->inputApplicationHandle; + } + } + } + + if (mInputTargetWaitTimeoutExpired) { + return INPUT_EVENT_INJECTION_TIMED_OUT; + } + + if (currentTime >= mInputTargetWaitTimeoutTime) { + onANRLocked(currentTime, application, window, entry->eventTime, mInputTargetWaitStartTime); + + // Force poll loop to wake up immediately on next iteration once we get the + // ANR response back from the policy. + *nextWakeupTime = LONG_LONG_MIN; + return INPUT_EVENT_INJECTION_PENDING; + } else { + // Force poll loop to wake up when timeout is due. + if (mInputTargetWaitTimeoutTime < *nextWakeupTime) { + *nextWakeupTime = mInputTargetWaitTimeoutTime; + } + return INPUT_EVENT_INJECTION_PENDING; + } +} + +void InputDispatcher::resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout, + const sp<InputChannel>& inputChannel) { + if (newTimeout > 0) { + // Extend the timeout. + mInputTargetWaitTimeoutTime = now() + newTimeout; + } else { + // Give up. + mInputTargetWaitTimeoutExpired = true; + + // Release the touch targets. + mTouchState.reset(); + + // Input state will not be realistic. Mark it out of sync. + if (inputChannel.get()) { + ssize_t connectionIndex = getConnectionIndexLocked(inputChannel); + if (connectionIndex >= 0) { + sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); + if (connection->status == Connection::STATUS_NORMAL) { + synthesizeCancelationEventsForConnectionLocked( + connection, InputState::CANCEL_ALL_EVENTS, + "application not responding"); + } + } + } + } +} + +nsecs_t InputDispatcher::getTimeSpentWaitingForApplicationLocked( + nsecs_t currentTime) { + if (mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) { + return currentTime - mInputTargetWaitStartTime; + } + return 0; +} + +void InputDispatcher::resetANRTimeoutsLocked() { +#if DEBUG_FOCUS + LOGD("Resetting ANR timeouts."); +#endif + + // Reset input target wait timeout. + mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE; +} + +int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime, + const EventEntry* entry, nsecs_t* nextWakeupTime) { + mCurrentInputTargets.clear(); + + int32_t injectionResult; + + // If there is no currently focused window and no focused application + // then drop the event. + if (! mFocusedWindow) { + if (mFocusedApplication) { +#if DEBUG_FOCUS + LOGD("Waiting because there is no focused window but there is a " + "focused application that may eventually add a window: %s.", + getApplicationWindowLabelLocked(mFocusedApplication, NULL).string()); +#endif + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + mFocusedApplication, NULL, nextWakeupTime); + goto Unresponsive; + } + + LOGI("Dropping event because there is no focused window or focused application."); + injectionResult = INPUT_EVENT_INJECTION_FAILED; + goto Failed; + } + + // Check permissions. + if (! checkInjectionPermission(mFocusedWindow, entry->injectionState)) { + injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED; + goto Failed; + } + + // If the currently focused window is paused then keep waiting. + if (mFocusedWindow->paused) { +#if DEBUG_FOCUS + LOGD("Waiting because focused window is paused."); +#endif + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + mFocusedApplication, mFocusedWindow, nextWakeupTime); + goto Unresponsive; + } + + // If the currently focused window is still working on previous events then keep waiting. + if (! isWindowFinishedWithPreviousInputLocked(mFocusedWindow)) { +#if DEBUG_FOCUS + LOGD("Waiting because focused window still processing previous input."); +#endif + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + mFocusedApplication, mFocusedWindow, nextWakeupTime); + goto Unresponsive; + } + + // Success! Output targets. + injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; + addWindowTargetLocked(mFocusedWindow, InputTarget::FLAG_FOREGROUND, BitSet32(0)); + + // Done. +Failed: +Unresponsive: + nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime); + updateDispatchStatisticsLocked(currentTime, entry, + injectionResult, timeSpentWaitingForApplication); +#if DEBUG_FOCUS + LOGD("findFocusedWindow finished: injectionResult=%d, " + "timeSpendWaitingForApplication=%0.1fms", + injectionResult, timeSpentWaitingForApplication / 1000000.0); +#endif + return injectionResult; +} + +int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, + const MotionEntry* entry, nsecs_t* nextWakeupTime) { + enum InjectionPermission { + INJECTION_PERMISSION_UNKNOWN, + INJECTION_PERMISSION_GRANTED, + INJECTION_PERMISSION_DENIED + }; + + mCurrentInputTargets.clear(); + + nsecs_t startTime = now(); + + // For security reasons, we defer updating the touch state until we are sure that + // event injection will be allowed. + // + // FIXME In the original code, screenWasOff could never be set to true. + // The reason is that the POLICY_FLAG_WOKE_HERE + // and POLICY_FLAG_BRIGHT_HERE flags were set only when preprocessing raw + // EV_KEY, EV_REL and EV_ABS events. As it happens, the touch event was + // actually enqueued using the policyFlags that appeared in the final EV_SYN + // events upon which no preprocessing took place. So policyFlags was always 0. + // In the new native input dispatcher we're a bit more careful about event + // preprocessing so the touches we receive can actually have non-zero policyFlags. + // Unfortunately we obtain undesirable behavior. + // + // Here's what happens: + // + // When the device dims in anticipation of going to sleep, touches + // in windows which have FLAG_TOUCHABLE_WHEN_WAKING cause + // the device to brighten and reset the user activity timer. + // Touches on other windows (such as the launcher window) + // are dropped. Then after a moment, the device goes to sleep. Oops. + // + // Also notice how screenWasOff was being initialized using POLICY_FLAG_BRIGHT_HERE + // instead of POLICY_FLAG_WOKE_HERE... + // + bool screenWasOff = false; // original policy: policyFlags & POLICY_FLAG_BRIGHT_HERE; + + int32_t action = entry->action; + int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK; + + // Update the touch state as needed based on the properties of the touch event. + int32_t injectionResult = INPUT_EVENT_INJECTION_PENDING; + InjectionPermission injectionPermission = INJECTION_PERMISSION_UNKNOWN; + bool isSplit, wrongDevice; + if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { + mTempTouchState.reset(); + mTempTouchState.down = true; + mTempTouchState.deviceId = entry->deviceId; + mTempTouchState.source = entry->source; + isSplit = false; + wrongDevice = false; + } else { + mTempTouchState.copyFrom(mTouchState); + isSplit = mTempTouchState.split; + wrongDevice = mTempTouchState.down + && (mTempTouchState.deviceId != entry->deviceId + || mTempTouchState.source != entry->source); + if (wrongDevice) { +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("Dropping event because a pointer for a different device is already down."); +#endif + injectionResult = INPUT_EVENT_INJECTION_FAILED; + goto Failed; + } + } + + if (maskedAction == AMOTION_EVENT_ACTION_DOWN + || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) { + /* Case 1: New splittable pointer going down. */ + + int32_t pointerIndex = getMotionEventActionPointerIndex(action); + int32_t x = int32_t(entry->firstSample.pointerCoords[pointerIndex].x); + int32_t y = int32_t(entry->firstSample.pointerCoords[pointerIndex].y); + const InputWindow* newTouchedWindow = NULL; + const InputWindow* topErrorWindow = NULL; + + // Traverse windows from front to back to find touched window and outside targets. + size_t numWindows = mWindows.size(); + for (size_t i = 0; i < numWindows; i++) { + const InputWindow* window = & mWindows.editItemAt(i); + int32_t flags = window->layoutParamsFlags; + + if (flags & InputWindow::FLAG_SYSTEM_ERROR) { + if (! topErrorWindow) { + topErrorWindow = window; + } + } + + if (window->visible) { + if (! (flags & InputWindow::FLAG_NOT_TOUCHABLE)) { + bool isTouchModal = (flags & (InputWindow::FLAG_NOT_FOCUSABLE + | InputWindow::FLAG_NOT_TOUCH_MODAL)) == 0; + if (isTouchModal || window->touchableRegionContainsPoint(x, y)) { + if (! screenWasOff || flags & InputWindow::FLAG_TOUCHABLE_WHEN_WAKING) { + newTouchedWindow = window; + } + break; // found touched window, exit window loop + } + } + + if (maskedAction == AMOTION_EVENT_ACTION_DOWN + && (flags & InputWindow::FLAG_WATCH_OUTSIDE_TOUCH)) { + int32_t outsideTargetFlags = InputTarget::FLAG_OUTSIDE; + if (isWindowObscuredAtPointLocked(window, x, y)) { + outsideTargetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED; + } + + mTempTouchState.addOrUpdateWindow(window, outsideTargetFlags, BitSet32(0)); + } + } + } + + // If there is an error window but it is not taking focus (typically because + // it is invisible) then wait for it. Any other focused window may in + // fact be in ANR state. + if (topErrorWindow && newTouchedWindow != topErrorWindow) { +#if DEBUG_FOCUS + LOGD("Waiting because system error window is pending."); +#endif + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + NULL, NULL, nextWakeupTime); + injectionPermission = INJECTION_PERMISSION_UNKNOWN; + goto Unresponsive; + } + + // Figure out whether splitting will be allowed for this window. + if (newTouchedWindow && newTouchedWindow->supportsSplitTouch()) { + // New window supports splitting. + isSplit = true; + } else if (isSplit) { + // New window does not support splitting but we have already split events. + // Assign the pointer to the first foreground window we find. + // (May be NULL which is why we put this code block before the next check.) + newTouchedWindow = mTempTouchState.getFirstForegroundWindow(); + } + + // If we did not find a touched window then fail. + if (! newTouchedWindow) { + if (mFocusedApplication) { +#if DEBUG_FOCUS + LOGD("Waiting because there is no touched window but there is a " + "focused application that may eventually add a new window: %s.", + getApplicationWindowLabelLocked(mFocusedApplication, NULL).string()); +#endif + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + mFocusedApplication, NULL, nextWakeupTime); + goto Unresponsive; + } + + LOGI("Dropping event because there is no touched window or focused application."); + injectionResult = INPUT_EVENT_INJECTION_FAILED; + goto Failed; + } + + // Set target flags. + int32_t targetFlags = InputTarget::FLAG_FOREGROUND; + if (isSplit) { + targetFlags |= InputTarget::FLAG_SPLIT; + } + if (isWindowObscuredAtPointLocked(newTouchedWindow, x, y)) { + targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED; + } + + // Update the temporary touch state. + BitSet32 pointerIds; + if (isSplit) { + uint32_t pointerId = entry->pointerIds[pointerIndex]; + pointerIds.markBit(pointerId); + } + mTempTouchState.addOrUpdateWindow(newTouchedWindow, targetFlags, pointerIds); + } else { + /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */ + + // If the pointer is not currently down, then ignore the event. + if (! mTempTouchState.down) { +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("Dropping event because the pointer is not down or we previously " + "dropped the pointer down event."); +#endif + injectionResult = INPUT_EVENT_INJECTION_FAILED; + goto Failed; + } + } + + // Check permission to inject into all touched foreground windows and ensure there + // is at least one touched foreground window. + { + bool haveForegroundWindow = false; + for (size_t i = 0; i < mTempTouchState.windows.size(); i++) { + const TouchedWindow& touchedWindow = mTempTouchState.windows[i]; + if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) { + haveForegroundWindow = true; + if (! checkInjectionPermission(touchedWindow.window, entry->injectionState)) { + injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED; + injectionPermission = INJECTION_PERMISSION_DENIED; + goto Failed; + } + } + } + if (! haveForegroundWindow) { +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("Dropping event because there is no touched foreground window to receive it."); +#endif + injectionResult = INPUT_EVENT_INJECTION_FAILED; + goto Failed; + } + + // Permission granted to injection into all touched foreground windows. + injectionPermission = INJECTION_PERMISSION_GRANTED; + } + + // Ensure all touched foreground windows are ready for new input. + for (size_t i = 0; i < mTempTouchState.windows.size(); i++) { + const TouchedWindow& touchedWindow = mTempTouchState.windows[i]; + if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) { + // If the touched window is paused then keep waiting. + if (touchedWindow.window->paused) { +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("Waiting because touched window is paused."); +#endif + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + NULL, touchedWindow.window, nextWakeupTime); + goto Unresponsive; + } + + // If the touched window is still working on previous events then keep waiting. + if (! isWindowFinishedWithPreviousInputLocked(touchedWindow.window)) { +#if DEBUG_FOCUS + LOGD("Waiting because touched window still processing previous input."); +#endif + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + NULL, touchedWindow.window, nextWakeupTime); + goto Unresponsive; + } + } + } + + // If this is the first pointer going down and the touched window has a wallpaper + // then also add the touched wallpaper windows so they are locked in for the duration + // of the touch gesture. + if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { + const InputWindow* foregroundWindow = mTempTouchState.getFirstForegroundWindow(); + if (foregroundWindow->hasWallpaper) { + for (size_t i = 0; i < mWindows.size(); i++) { + const InputWindow* window = & mWindows[i]; + if (window->layoutParamsType == InputWindow::TYPE_WALLPAPER) { + mTempTouchState.addOrUpdateWindow(window, + InputTarget::FLAG_WINDOW_IS_OBSCURED, BitSet32(0)); + } + } + } + } + + // Success! Output targets. + injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; + + for (size_t i = 0; i < mTempTouchState.windows.size(); i++) { + const TouchedWindow& touchedWindow = mTempTouchState.windows.itemAt(i); + addWindowTargetLocked(touchedWindow.window, touchedWindow.targetFlags, + touchedWindow.pointerIds); + } + + // Drop the outside touch window since we will not care about them in the next iteration. + mTempTouchState.removeOutsideTouchWindows(); + +Failed: + // Check injection permission once and for all. + if (injectionPermission == INJECTION_PERMISSION_UNKNOWN) { + if (checkInjectionPermission(NULL, entry->injectionState)) { + injectionPermission = INJECTION_PERMISSION_GRANTED; + } else { + injectionPermission = INJECTION_PERMISSION_DENIED; + } + } + + // Update final pieces of touch state if the injector had permission. + if (injectionPermission == INJECTION_PERMISSION_GRANTED) { + if (!wrongDevice) { + if (maskedAction == AMOTION_EVENT_ACTION_UP + || maskedAction == AMOTION_EVENT_ACTION_CANCEL) { + // All pointers up or canceled. + mTempTouchState.reset(); + } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { + // First pointer went down. + if (mTouchState.down) { +#if DEBUG_FOCUS + LOGD("Pointer down received while already down."); +#endif + } + } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) { + // One pointer went up. + if (isSplit) { + int32_t pointerIndex = getMotionEventActionPointerIndex(action); + uint32_t pointerId = entry->pointerIds[pointerIndex]; + + for (size_t i = 0; i < mTempTouchState.windows.size(); ) { + TouchedWindow& touchedWindow = mTempTouchState.windows.editItemAt(i); + if (touchedWindow.targetFlags & InputTarget::FLAG_SPLIT) { + touchedWindow.pointerIds.clearBit(pointerId); + if (touchedWindow.pointerIds.isEmpty()) { + mTempTouchState.windows.removeAt(i); + continue; + } + } + i += 1; + } + } + } + + // Save changes to touch state. + mTouchState.copyFrom(mTempTouchState); + } + } else { +#if DEBUG_FOCUS + LOGD("Not updating touch focus because injection was denied."); +#endif + } + +Unresponsive: + // Reset temporary touch state to ensure we release unnecessary references to input channels. + mTempTouchState.reset(); + + nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime); + updateDispatchStatisticsLocked(currentTime, entry, + injectionResult, timeSpentWaitingForApplication); +#if DEBUG_FOCUS + LOGD("findTouchedWindow finished: injectionResult=%d, injectionPermission=%d, " + "timeSpentWaitingForApplication=%0.1fms", + injectionResult, injectionPermission, timeSpentWaitingForApplication / 1000000.0); +#endif + return injectionResult; +} + +void InputDispatcher::addWindowTargetLocked(const InputWindow* window, int32_t targetFlags, + BitSet32 pointerIds) { + mCurrentInputTargets.push(); + + InputTarget& target = mCurrentInputTargets.editTop(); + target.inputChannel = window->inputChannel; + target.flags = targetFlags; + target.xOffset = - window->frameLeft; + target.yOffset = - window->frameTop; + target.pointerIds = pointerIds; +} + +void InputDispatcher::addMonitoringTargetsLocked() { + for (size_t i = 0; i < mMonitoringChannels.size(); i++) { + mCurrentInputTargets.push(); + + InputTarget& target = mCurrentInputTargets.editTop(); + target.inputChannel = mMonitoringChannels[i]; + target.flags = 0; + target.xOffset = 0; + target.yOffset = 0; + } +} + +bool InputDispatcher::checkInjectionPermission(const InputWindow* window, + const InjectionState* injectionState) { + if (injectionState + && (window == NULL || window->ownerUid != injectionState->injectorUid) + && !hasInjectionPermission(injectionState->injectorPid, injectionState->injectorUid)) { + if (window) { + LOGW("Permission denied: injecting event from pid %d uid %d to window " + "with input channel %s owned by uid %d", + injectionState->injectorPid, injectionState->injectorUid, + window->inputChannel->getName().string(), + window->ownerUid); + } else { + LOGW("Permission denied: injecting event from pid %d uid %d", + injectionState->injectorPid, injectionState->injectorUid); + } + return false; + } + return true; +} + +bool InputDispatcher::isWindowObscuredAtPointLocked( + const InputWindow* window, int32_t x, int32_t y) const { + size_t numWindows = mWindows.size(); + for (size_t i = 0; i < numWindows; i++) { + const InputWindow* other = & mWindows.itemAt(i); + if (other == window) { + break; + } + if (other->visible && ! other->isTrustedOverlay() && other->frameContainsPoint(x, y)) { + return true; + } + } + return false; +} + +bool InputDispatcher::isWindowFinishedWithPreviousInputLocked(const InputWindow* window) { + ssize_t connectionIndex = getConnectionIndexLocked(window->inputChannel); + if (connectionIndex >= 0) { + sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); + return connection->outboundQueue.isEmpty(); + } else { + return true; + } +} + +String8 InputDispatcher::getApplicationWindowLabelLocked(const InputApplication* application, + const InputWindow* window) { + if (application) { + if (window) { + String8 label(application->name); + label.append(" - "); + label.append(window->name); + return label; + } else { + return application->name; + } + } else if (window) { + return window->name; + } else { + return String8("<unknown application or window>"); + } +} + +void InputDispatcher::pokeUserActivityLocked(const EventEntry* eventEntry) { + int32_t eventType = POWER_MANAGER_BUTTON_EVENT; + switch (eventEntry->type) { + case EventEntry::TYPE_MOTION: { + const MotionEntry* motionEntry = static_cast<const MotionEntry*>(eventEntry); + if (motionEntry->action == AMOTION_EVENT_ACTION_CANCEL) { + return; + } + + if (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) { + eventType = POWER_MANAGER_TOUCH_EVENT; + } + break; + } + case EventEntry::TYPE_KEY: { + const KeyEntry* keyEntry = static_cast<const KeyEntry*>(eventEntry); + if (keyEntry->flags & AKEY_EVENT_FLAG_CANCELED) { + return; + } + break; + } + } + + CommandEntry* commandEntry = postCommandLocked( + & InputDispatcher::doPokeUserActivityLockedInterruptible); + commandEntry->eventTime = eventEntry->eventTime; + commandEntry->userActivityEventType = eventType; +} + +void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, + const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget, + bool resumeWithAppendedMotionSample) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ prepareDispatchCycle - flags=%d, " + "xOffset=%f, yOffset=%f, " + "pointerIds=0x%x, " + "resumeWithAppendedMotionSample=%s", + connection->getInputChannelName(), inputTarget->flags, + inputTarget->xOffset, inputTarget->yOffset, + inputTarget->pointerIds.value, + toString(resumeWithAppendedMotionSample)); +#endif + + // Make sure we are never called for streaming when splitting across multiple windows. + bool isSplit = inputTarget->flags & InputTarget::FLAG_SPLIT; + assert(! (resumeWithAppendedMotionSample && isSplit)); + + // Skip this event if the connection status is not normal. + // We don't want to enqueue additional outbound events if the connection is broken. + if (connection->status != Connection::STATUS_NORMAL) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Dropping event because the channel status is %s", + connection->getInputChannelName(), connection->getStatusLabel()); +#endif + return; + } + + // Split a motion event if needed. + if (isSplit) { + assert(eventEntry->type == EventEntry::TYPE_MOTION); + + MotionEntry* originalMotionEntry = static_cast<MotionEntry*>(eventEntry); + if (inputTarget->pointerIds.count() != originalMotionEntry->pointerCount) { + MotionEntry* splitMotionEntry = splitMotionEvent( + originalMotionEntry, inputTarget->pointerIds); + if (!splitMotionEntry) { + return; // split event was dropped + } +#if DEBUG_FOCUS + LOGD("channel '%s' ~ Split motion event.", + connection->getInputChannelName()); + logOutboundMotionDetailsLocked(" ", splitMotionEntry); +#endif + eventEntry = splitMotionEntry; + } + } + + // Resume the dispatch cycle with a freshly appended motion sample. + // First we check that the last dispatch entry in the outbound queue is for the same + // motion event to which we appended the motion sample. If we find such a dispatch + // entry, and if it is currently in progress then we try to stream the new sample. + bool wasEmpty = connection->outboundQueue.isEmpty(); + + if (! wasEmpty && resumeWithAppendedMotionSample) { + DispatchEntry* motionEventDispatchEntry = + connection->findQueuedDispatchEntryForEvent(eventEntry); + if (motionEventDispatchEntry) { + // If the dispatch entry is not in progress, then we must be busy dispatching an + // earlier event. Not a problem, the motion event is on the outbound queue and will + // be dispatched later. + if (! motionEventDispatchEntry->inProgress) { +#if DEBUG_BATCHING + LOGD("channel '%s' ~ Not streaming because the motion event has " + "not yet been dispatched. " + "(Waiting for earlier events to be consumed.)", + connection->getInputChannelName()); +#endif + return; + } + + // If the dispatch entry is in progress but it already has a tail of pending + // motion samples, then it must mean that the shared memory buffer filled up. + // Not a problem, when this dispatch cycle is finished, we will eventually start + // a new dispatch cycle to process the tail and that tail includes the newly + // appended motion sample. + if (motionEventDispatchEntry->tailMotionSample) { +#if DEBUG_BATCHING + LOGD("channel '%s' ~ Not streaming because no new samples can " + "be appended to the motion event in this dispatch cycle. " + "(Waiting for next dispatch cycle to start.)", + connection->getInputChannelName()); +#endif + return; + } + + // The dispatch entry is in progress and is still potentially open for streaming. + // Try to stream the new motion sample. This might fail if the consumer has already + // consumed the motion event (or if the channel is broken). + MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry); + MotionSample* appendedMotionSample = motionEntry->lastSample; + status_t status = connection->inputPublisher.appendMotionSample( + appendedMotionSample->eventTime, appendedMotionSample->pointerCoords); + if (status == OK) { +#if DEBUG_BATCHING + LOGD("channel '%s' ~ Successfully streamed new motion sample.", + connection->getInputChannelName()); +#endif + return; + } + +#if DEBUG_BATCHING + if (status == NO_MEMORY) { + LOGD("channel '%s' ~ Could not append motion sample to currently " + "dispatched move event because the shared memory buffer is full. " + "(Waiting for next dispatch cycle to start.)", + connection->getInputChannelName()); + } else if (status == status_t(FAILED_TRANSACTION)) { + LOGD("channel '%s' ~ Could not append motion sample to currently " + "dispatched move event because the event has already been consumed. " + "(Waiting for next dispatch cycle to start.)", + connection->getInputChannelName()); + } else { + LOGD("channel '%s' ~ Could not append motion sample to currently " + "dispatched move event due to an error, status=%d. " + "(Waiting for next dispatch cycle to start.)", + connection->getInputChannelName(), status); + } +#endif + // Failed to stream. Start a new tail of pending motion samples to dispatch + // in the next cycle. + motionEventDispatchEntry->tailMotionSample = appendedMotionSample; + return; + } + } + + // This is a new event. + // Enqueue a new dispatch entry onto the outbound queue for this connection. + DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry, // increments ref + inputTarget->flags, inputTarget->xOffset, inputTarget->yOffset); + if (dispatchEntry->hasForegroundTarget()) { + incrementPendingForegroundDispatchesLocked(eventEntry); + } + + // Handle the case where we could not stream a new motion sample because the consumer has + // already consumed the motion event (otherwise the corresponding dispatch entry would + // still be in the outbound queue for this connection). We set the head motion sample + // to the list starting with the newly appended motion sample. + if (resumeWithAppendedMotionSample) { +#if DEBUG_BATCHING + LOGD("channel '%s' ~ Preparing a new dispatch cycle for additional motion samples " + "that cannot be streamed because the motion event has already been consumed.", + connection->getInputChannelName()); +#endif + MotionSample* appendedMotionSample = static_cast<MotionEntry*>(eventEntry)->lastSample; + dispatchEntry->headMotionSample = appendedMotionSample; + } + + // Enqueue the dispatch entry. + connection->outboundQueue.enqueueAtTail(dispatchEntry); + + // If the outbound queue was previously empty, start the dispatch cycle going. + if (wasEmpty) { + activateConnectionLocked(connection.get()); + startDispatchCycleLocked(currentTime, connection); + } +} + +void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, + const sp<Connection>& connection) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ startDispatchCycle", + connection->getInputChannelName()); +#endif + + assert(connection->status == Connection::STATUS_NORMAL); + assert(! connection->outboundQueue.isEmpty()); + + DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next; + assert(! dispatchEntry->inProgress); + + // Mark the dispatch entry as in progress. + dispatchEntry->inProgress = true; + + // Update the connection's input state. + EventEntry* eventEntry = dispatchEntry->eventEntry; + InputState::Consistency consistency = connection->inputState.trackEvent(eventEntry); + +#if FILTER_INPUT_EVENTS + // Filter out inconsistent sequences of input events. + // The input system may drop or inject events in a way that could violate implicit + // invariants on input state and potentially cause an application to crash + // or think that a key or pointer is stuck down. Technically we make no guarantees + // of consistency but it would be nice to improve on this where possible. + // XXX: This code is a proof of concept only. Not ready for prime time. + if (consistency == InputState::TOLERABLE) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Sending an event that is inconsistent with the connection's " + "current input state but that is likely to be tolerated by the application.", + connection->getInputChannelName()); +#endif + } else if (consistency == InputState::BROKEN) { + LOGI("channel '%s' ~ Dropping an event that is inconsistent with the connection's " + "current input state and that is likely to cause the application to crash.", + connection->getInputChannelName()); + startNextDispatchCycleLocked(currentTime, connection); + return; + } +#endif + + // Publish the event. + status_t status; + switch (eventEntry->type) { + case EventEntry::TYPE_KEY: { + KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry); + + // Apply target flags. + int32_t action = keyEntry->action; + int32_t flags = keyEntry->flags; + + // Publish the key event. + status = connection->inputPublisher.publishKeyEvent(keyEntry->deviceId, keyEntry->source, + action, flags, keyEntry->keyCode, keyEntry->scanCode, + keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime, + keyEntry->eventTime); + + if (status) { + LOGE("channel '%s' ~ Could not publish key event, " + "status=%d", connection->getInputChannelName(), status); + abortBrokenDispatchCycleLocked(currentTime, connection); + return; + } + break; + } + + case EventEntry::TYPE_MOTION: { + MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry); + + // Apply target flags. + int32_t action = motionEntry->action; + int32_t flags = motionEntry->flags; + if (dispatchEntry->targetFlags & InputTarget::FLAG_OUTSIDE) { + action = AMOTION_EVENT_ACTION_OUTSIDE; + } + if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) { + flags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED; + } + + // If headMotionSample is non-NULL, then it points to the first new sample that we + // were unable to dispatch during the previous cycle so we resume dispatching from + // that point in the list of motion samples. + // Otherwise, we just start from the first sample of the motion event. + MotionSample* firstMotionSample = dispatchEntry->headMotionSample; + if (! firstMotionSample) { + firstMotionSample = & motionEntry->firstSample; + } + + // Set the X and Y offset depending on the input source. + float xOffset, yOffset; + if (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) { + xOffset = dispatchEntry->xOffset; + yOffset = dispatchEntry->yOffset; + } else { + xOffset = 0.0f; + yOffset = 0.0f; + } + + // Publish the motion event and the first motion sample. + status = connection->inputPublisher.publishMotionEvent(motionEntry->deviceId, + motionEntry->source, action, flags, motionEntry->edgeFlags, motionEntry->metaState, + xOffset, yOffset, + motionEntry->xPrecision, motionEntry->yPrecision, + motionEntry->downTime, firstMotionSample->eventTime, + motionEntry->pointerCount, motionEntry->pointerIds, + firstMotionSample->pointerCoords); + + if (status) { + LOGE("channel '%s' ~ Could not publish motion event, " + "status=%d", connection->getInputChannelName(), status); + abortBrokenDispatchCycleLocked(currentTime, connection); + return; + } + + // Append additional motion samples. + MotionSample* nextMotionSample = firstMotionSample->next; + for (; nextMotionSample != NULL; nextMotionSample = nextMotionSample->next) { + status = connection->inputPublisher.appendMotionSample( + nextMotionSample->eventTime, nextMotionSample->pointerCoords); + if (status == NO_MEMORY) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Shared memory buffer full. Some motion samples will " + "be sent in the next dispatch cycle.", + connection->getInputChannelName()); +#endif + break; + } + if (status != OK) { + LOGE("channel '%s' ~ Could not append motion sample " + "for a reason other than out of memory, status=%d", + connection->getInputChannelName(), status); + abortBrokenDispatchCycleLocked(currentTime, connection); + return; + } + } + + // Remember the next motion sample that we could not dispatch, in case we ran out + // of space in the shared memory buffer. + dispatchEntry->tailMotionSample = nextMotionSample; + break; + } + + default: { + assert(false); + } + } + + // Send the dispatch signal. + status = connection->inputPublisher.sendDispatchSignal(); + if (status) { + LOGE("channel '%s' ~ Could not send dispatch signal, status=%d", + connection->getInputChannelName(), status); + abortBrokenDispatchCycleLocked(currentTime, connection); + return; + } + + // Record information about the newly started dispatch cycle. + connection->lastEventTime = eventEntry->eventTime; + connection->lastDispatchTime = currentTime; + + // Notify other system components. + onDispatchCycleStartedLocked(currentTime, connection); +} + +void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime, + const sp<Connection>& connection, bool handled) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ finishDispatchCycle - %01.1fms since event, " + "%01.1fms since dispatch, handled=%s", + connection->getInputChannelName(), + connection->getEventLatencyMillis(currentTime), + connection->getDispatchLatencyMillis(currentTime), + toString(handled)); +#endif + + if (connection->status == Connection::STATUS_BROKEN + || connection->status == Connection::STATUS_ZOMBIE) { + return; + } + + // Reset the publisher since the event has been consumed. + // We do this now so that the publisher can release some of its internal resources + // while waiting for the next dispatch cycle to begin. + status_t status = connection->inputPublisher.reset(); + if (status) { + LOGE("channel '%s' ~ Could not reset publisher, status=%d", + connection->getInputChannelName(), status); + abortBrokenDispatchCycleLocked(currentTime, connection); + return; + } + + // Notify other system components and prepare to start the next dispatch cycle. + onDispatchCycleFinishedLocked(currentTime, connection, handled); +} + +void InputDispatcher::startNextDispatchCycleLocked(nsecs_t currentTime, + const sp<Connection>& connection) { + // Start the next dispatch cycle for this connection. + while (! connection->outboundQueue.isEmpty()) { + DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next; + if (dispatchEntry->inProgress) { + // Finish or resume current event in progress. + if (dispatchEntry->tailMotionSample) { + // We have a tail of undispatched motion samples. + // Reuse the same DispatchEntry and start a new cycle. + dispatchEntry->inProgress = false; + dispatchEntry->headMotionSample = dispatchEntry->tailMotionSample; + dispatchEntry->tailMotionSample = NULL; + startDispatchCycleLocked(currentTime, connection); + return; + } + // Finished. + connection->outboundQueue.dequeueAtHead(); + if (dispatchEntry->hasForegroundTarget()) { + decrementPendingForegroundDispatchesLocked(dispatchEntry->eventEntry); + } + mAllocator.releaseDispatchEntry(dispatchEntry); + } else { + // If the head is not in progress, then we must have already dequeued the in + // progress event, which means we actually aborted it. + // So just start the next event for this connection. + startDispatchCycleLocked(currentTime, connection); + return; + } + } + + // Outbound queue is empty, deactivate the connection. + deactivateConnectionLocked(connection.get()); +} + +void InputDispatcher::abortBrokenDispatchCycleLocked(nsecs_t currentTime, + const sp<Connection>& connection) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ abortBrokenDispatchCycle", + connection->getInputChannelName()); +#endif + + // Clear the outbound queue. + drainOutboundQueueLocked(connection.get()); + + // The connection appears to be unrecoverably broken. + // Ignore already broken or zombie connections. + if (connection->status == Connection::STATUS_NORMAL) { + connection->status = Connection::STATUS_BROKEN; + + // Notify other system components. + onDispatchCycleBrokenLocked(currentTime, connection); + } +} + +void InputDispatcher::drainOutboundQueueLocked(Connection* connection) { + while (! connection->outboundQueue.isEmpty()) { + DispatchEntry* dispatchEntry = connection->outboundQueue.dequeueAtHead(); + if (dispatchEntry->hasForegroundTarget()) { + decrementPendingForegroundDispatchesLocked(dispatchEntry->eventEntry); + } + mAllocator.releaseDispatchEntry(dispatchEntry); + } + + deactivateConnectionLocked(connection); +} + +int InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data) { + InputDispatcher* d = static_cast<InputDispatcher*>(data); + + { // acquire lock + AutoMutex _l(d->mLock); + + ssize_t connectionIndex = d->mConnectionsByReceiveFd.indexOfKey(receiveFd); + if (connectionIndex < 0) { + LOGE("Received spurious receive callback for unknown input channel. " + "fd=%d, events=0x%x", receiveFd, events); + return 0; // remove the callback + } + + nsecs_t currentTime = now(); + + sp<Connection> connection = d->mConnectionsByReceiveFd.valueAt(connectionIndex); + if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) { + LOGE("channel '%s' ~ Consumer closed input channel or an error occurred. " + "events=0x%x", connection->getInputChannelName(), events); + d->abortBrokenDispatchCycleLocked(currentTime, connection); + d->runCommandsLockedInterruptible(); + return 0; // remove the callback + } + + if (! (events & ALOOPER_EVENT_INPUT)) { + LOGW("channel '%s' ~ Received spurious callback for unhandled poll event. " + "events=0x%x", connection->getInputChannelName(), events); + return 1; + } + + bool handled = false; + status_t status = connection->inputPublisher.receiveFinishedSignal(&handled); + if (status) { + LOGE("channel '%s' ~ Failed to receive finished signal. status=%d", + connection->getInputChannelName(), status); + d->abortBrokenDispatchCycleLocked(currentTime, connection); + d->runCommandsLockedInterruptible(); + return 0; // remove the callback + } + + d->finishDispatchCycleLocked(currentTime, connection, handled); + d->runCommandsLockedInterruptible(); + return 1; + } // release lock +} + +void InputDispatcher::synthesizeCancelationEventsForAllConnectionsLocked( + InputState::CancelationOptions options, const char* reason) { + for (size_t i = 0; i < mConnectionsByReceiveFd.size(); i++) { + synthesizeCancelationEventsForConnectionLocked( + mConnectionsByReceiveFd.valueAt(i), options, reason); + } +} + +void InputDispatcher::synthesizeCancelationEventsForInputChannelLocked( + const sp<InputChannel>& channel, InputState::CancelationOptions options, + const char* reason) { + ssize_t index = getConnectionIndexLocked(channel); + if (index >= 0) { + synthesizeCancelationEventsForConnectionLocked( + mConnectionsByReceiveFd.valueAt(index), options, reason); + } +} + +void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( + const sp<Connection>& connection, InputState::CancelationOptions options, + const char* reason) { + nsecs_t currentTime = now(); + + mTempCancelationEvents.clear(); + connection->inputState.synthesizeCancelationEvents(currentTime, & mAllocator, + mTempCancelationEvents, options); + + if (! mTempCancelationEvents.isEmpty() + && connection->status != Connection::STATUS_BROKEN) { +#if DEBUG_OUTBOUND_EVENT_DETAILS + LOGD("channel '%s' ~ Synthesized %d cancelation events to bring channel back in sync " + "with reality: %s, options=%d.", + connection->getInputChannelName(), mTempCancelationEvents.size(), reason, options); +#endif + for (size_t i = 0; i < mTempCancelationEvents.size(); i++) { + EventEntry* cancelationEventEntry = mTempCancelationEvents.itemAt(i); + switch (cancelationEventEntry->type) { + case EventEntry::TYPE_KEY: + logOutboundKeyDetailsLocked("cancel - ", + static_cast<KeyEntry*>(cancelationEventEntry)); + break; + case EventEntry::TYPE_MOTION: + logOutboundMotionDetailsLocked("cancel - ", + static_cast<MotionEntry*>(cancelationEventEntry)); + break; + } + + int32_t xOffset, yOffset; + const InputWindow* window = getWindowLocked(connection->inputChannel); + if (window) { + xOffset = -window->frameLeft; + yOffset = -window->frameTop; + } else { + xOffset = 0; + yOffset = 0; + } + + DispatchEntry* cancelationDispatchEntry = + mAllocator.obtainDispatchEntry(cancelationEventEntry, // increments ref + 0, xOffset, yOffset); + connection->outboundQueue.enqueueAtTail(cancelationDispatchEntry); + + mAllocator.releaseEventEntry(cancelationEventEntry); + } + + if (!connection->outboundQueue.headSentinel.next->inProgress) { + startDispatchCycleLocked(currentTime, connection); + } + } +} + +InputDispatcher::MotionEntry* +InputDispatcher::splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet32 pointerIds) { + assert(pointerIds.value != 0); + + uint32_t splitPointerIndexMap[MAX_POINTERS]; + int32_t splitPointerIds[MAX_POINTERS]; + PointerCoords splitPointerCoords[MAX_POINTERS]; + + uint32_t originalPointerCount = originalMotionEntry->pointerCount; + uint32_t splitPointerCount = 0; + + for (uint32_t originalPointerIndex = 0; originalPointerIndex < originalPointerCount; + originalPointerIndex++) { + int32_t pointerId = uint32_t(originalMotionEntry->pointerIds[originalPointerIndex]); + if (pointerIds.hasBit(pointerId)) { + splitPointerIndexMap[splitPointerCount] = originalPointerIndex; + splitPointerIds[splitPointerCount] = pointerId; + splitPointerCoords[splitPointerCount] = + originalMotionEntry->firstSample.pointerCoords[originalPointerIndex]; + splitPointerCount += 1; + } + } + + if (splitPointerCount != pointerIds.count()) { + // This is bad. We are missing some of the pointers that we expected to deliver. + // Most likely this indicates that we received an ACTION_MOVE events that has + // different pointer ids than we expected based on the previous ACTION_DOWN + // or ACTION_POINTER_DOWN events that caused us to decide to split the pointers + // in this way. + LOGW("Dropping split motion event because the pointer count is %d but " + "we expected there to be %d pointers. This probably means we received " + "a broken sequence of pointer ids from the input device.", + splitPointerCount, pointerIds.count()); + return NULL; + } + + int32_t action = originalMotionEntry->action; + int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK; + if (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN + || maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) { + int32_t originalPointerIndex = getMotionEventActionPointerIndex(action); + int32_t pointerId = originalMotionEntry->pointerIds[originalPointerIndex]; + if (pointerIds.hasBit(pointerId)) { + if (pointerIds.count() == 1) { + // The first/last pointer went down/up. + action = maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN + ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP; + } else { + // A secondary pointer went down/up. + uint32_t splitPointerIndex = 0; + while (pointerId != splitPointerIds[splitPointerIndex]) { + splitPointerIndex += 1; + } + action = maskedAction | (splitPointerIndex + << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + } + } else { + // An unrelated pointer changed. + action = AMOTION_EVENT_ACTION_MOVE; + } + } + + MotionEntry* splitMotionEntry = mAllocator.obtainMotionEntry( + originalMotionEntry->eventTime, + originalMotionEntry->deviceId, + originalMotionEntry->source, + originalMotionEntry->policyFlags, + action, + originalMotionEntry->flags, + originalMotionEntry->metaState, + originalMotionEntry->edgeFlags, + originalMotionEntry->xPrecision, + originalMotionEntry->yPrecision, + originalMotionEntry->downTime, + splitPointerCount, splitPointerIds, splitPointerCoords); + + for (MotionSample* originalMotionSample = originalMotionEntry->firstSample.next; + originalMotionSample != NULL; originalMotionSample = originalMotionSample->next) { + for (uint32_t splitPointerIndex = 0; splitPointerIndex < splitPointerCount; + splitPointerIndex++) { + uint32_t originalPointerIndex = splitPointerIndexMap[splitPointerIndex]; + splitPointerCoords[splitPointerIndex] = + originalMotionSample->pointerCoords[originalPointerIndex]; + } + + mAllocator.appendMotionSample(splitMotionEntry, originalMotionSample->eventTime, + splitPointerCoords); + } + + return splitMotionEntry; +} + +void InputDispatcher::notifyConfigurationChanged(nsecs_t eventTime) { +#if DEBUG_INBOUND_EVENT_DETAILS + LOGD("notifyConfigurationChanged - eventTime=%lld", eventTime); +#endif + + bool needWake; + { // acquire lock + AutoMutex _l(mLock); + + ConfigurationChangedEntry* newEntry = mAllocator.obtainConfigurationChangedEntry(eventTime); + needWake = enqueueInboundEventLocked(newEntry); + } // release lock + + if (needWake) { + mLooper->wake(); + } +} + +void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, uint32_t source, + uint32_t policyFlags, int32_t action, int32_t flags, + int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime) { +#if DEBUG_INBOUND_EVENT_DETAILS + LOGD("notifyKey - eventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, action=0x%x, " + "flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld", + eventTime, deviceId, source, policyFlags, action, flags, + keyCode, scanCode, metaState, downTime); +#endif + if (! validateKeyEvent(action)) { + return; + } + + if ((policyFlags & POLICY_FLAG_VIRTUAL) || (flags & AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY)) { + policyFlags |= POLICY_FLAG_VIRTUAL; + flags |= AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY; + } + + policyFlags |= POLICY_FLAG_TRUSTED; + + KeyEvent event; + event.initialize(deviceId, source, action, flags, keyCode, scanCode, + metaState, 0, downTime, eventTime); + + mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags); + + if (policyFlags & POLICY_FLAG_WOKE_HERE) { + flags |= AKEY_EVENT_FLAG_WOKE_HERE; + } + + bool needWake; + { // acquire lock + AutoMutex _l(mLock); + + int32_t repeatCount = 0; + KeyEntry* newEntry = mAllocator.obtainKeyEntry(eventTime, + deviceId, source, policyFlags, action, flags, keyCode, scanCode, + metaState, repeatCount, downTime); + + needWake = enqueueInboundEventLocked(newEntry); + } // release lock + + if (needWake) { + mLooper->wake(); + } +} + +void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, uint32_t source, + uint32_t policyFlags, int32_t action, int32_t flags, int32_t metaState, int32_t edgeFlags, + uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords, + float xPrecision, float yPrecision, nsecs_t downTime) { +#if DEBUG_INBOUND_EVENT_DETAILS + LOGD("notifyMotion - eventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, " + "action=0x%x, flags=0x%x, metaState=0x%x, edgeFlags=0x%x, " + "xPrecision=%f, yPrecision=%f, downTime=%lld", + eventTime, deviceId, source, policyFlags, action, flags, metaState, edgeFlags, + xPrecision, yPrecision, downTime); + for (uint32_t i = 0; i < pointerCount; i++) { + LOGD(" Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f, " + "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, " + "orientation=%f", + i, pointerIds[i], pointerCoords[i].x, pointerCoords[i].y, + pointerCoords[i].pressure, pointerCoords[i].size, + pointerCoords[i].touchMajor, pointerCoords[i].touchMinor, + pointerCoords[i].toolMajor, pointerCoords[i].toolMinor, + pointerCoords[i].orientation); + } +#endif + if (! validateMotionEvent(action, pointerCount, pointerIds)) { + return; + } + + policyFlags |= POLICY_FLAG_TRUSTED; + mPolicy->interceptGenericBeforeQueueing(eventTime, /*byref*/ policyFlags); + + bool needWake; + { // acquire lock + AutoMutex _l(mLock); + + // Attempt batching and streaming of move events. + if (action == AMOTION_EVENT_ACTION_MOVE) { + // BATCHING CASE + // + // Try to append a move sample to the tail of the inbound queue for this device. + // Give up if we encounter a non-move motion event for this device since that + // means we cannot append any new samples until a new motion event has started. + for (EventEntry* entry = mInboundQueue.tailSentinel.prev; + entry != & mInboundQueue.headSentinel; entry = entry->prev) { + if (entry->type != EventEntry::TYPE_MOTION) { + // Keep looking for motion events. + continue; + } + + MotionEntry* motionEntry = static_cast<MotionEntry*>(entry); + if (motionEntry->deviceId != deviceId) { + // Keep looking for this device. + continue; + } + + if (motionEntry->action != AMOTION_EVENT_ACTION_MOVE + || motionEntry->source != source + || motionEntry->pointerCount != pointerCount + || motionEntry->isInjected()) { + // Last motion event in the queue for this device is not compatible for + // appending new samples. Stop here. + goto NoBatchingOrStreaming; + } + + // The last motion event is a move and is compatible for appending. + // Do the batching magic. + mAllocator.appendMotionSample(motionEntry, eventTime, pointerCoords); +#if DEBUG_BATCHING + LOGD("Appended motion sample onto batch for most recent " + "motion event for this device in the inbound queue."); +#endif + return; // done! + } + + // STREAMING CASE + // + // There is no pending motion event (of any kind) for this device in the inbound queue. + // Search the outbound queue for the current foreground targets to find a dispatched + // motion event that is still in progress. If found, then, appen the new sample to + // that event and push it out to all current targets. The logic in + // prepareDispatchCycleLocked takes care of the case where some targets may + // already have consumed the motion event by starting a new dispatch cycle if needed. + if (mCurrentInputTargetsValid) { + for (size_t i = 0; i < mCurrentInputTargets.size(); i++) { + const InputTarget& inputTarget = mCurrentInputTargets[i]; + if ((inputTarget.flags & InputTarget::FLAG_FOREGROUND) == 0) { + // Skip non-foreground targets. We only want to stream if there is at + // least one foreground target whose dispatch is still in progress. + continue; + } + + ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel); + if (connectionIndex < 0) { + // Connection must no longer be valid. + continue; + } + + sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); + if (connection->outboundQueue.isEmpty()) { + // This foreground target has an empty outbound queue. + continue; + } + + DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next; + if (! dispatchEntry->inProgress + || dispatchEntry->eventEntry->type != EventEntry::TYPE_MOTION + || dispatchEntry->isSplit()) { + // No motion event is being dispatched, or it is being split across + // windows in which case we cannot stream. + continue; + } + + MotionEntry* motionEntry = static_cast<MotionEntry*>( + dispatchEntry->eventEntry); + if (motionEntry->action != AMOTION_EVENT_ACTION_MOVE + || motionEntry->deviceId != deviceId + || motionEntry->source != source + || motionEntry->pointerCount != pointerCount + || motionEntry->isInjected()) { + // The motion event is not compatible with this move. + continue; + } + + // Hurray! This foreground target is currently dispatching a move event + // that we can stream onto. Append the motion sample and resume dispatch. + mAllocator.appendMotionSample(motionEntry, eventTime, pointerCoords); +#if DEBUG_BATCHING + LOGD("Appended motion sample onto batch for most recently dispatched " + "motion event for this device in the outbound queues. " + "Attempting to stream the motion sample."); +#endif + nsecs_t currentTime = now(); + dispatchEventToCurrentInputTargetsLocked(currentTime, motionEntry, + true /*resumeWithAppendedMotionSample*/); + + runCommandsLockedInterruptible(); + return; // done! + } + } + +NoBatchingOrStreaming:; + } + + // Just enqueue a new motion event. + MotionEntry* newEntry = mAllocator.obtainMotionEntry(eventTime, + deviceId, source, policyFlags, action, flags, metaState, edgeFlags, + xPrecision, yPrecision, downTime, + pointerCount, pointerIds, pointerCoords); + + needWake = enqueueInboundEventLocked(newEntry); + } // release lock + + if (needWake) { + mLooper->wake(); + } +} + +void InputDispatcher::notifySwitch(nsecs_t when, int32_t switchCode, int32_t switchValue, + uint32_t policyFlags) { +#if DEBUG_INBOUND_EVENT_DETAILS + LOGD("notifySwitch - switchCode=%d, switchValue=%d, policyFlags=0x%x", + switchCode, switchValue, policyFlags); +#endif + + policyFlags |= POLICY_FLAG_TRUSTED; + mPolicy->notifySwitch(when, switchCode, switchValue, policyFlags); +} + +int32_t InputDispatcher::injectInputEvent(const InputEvent* event, + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) { +#if DEBUG_INBOUND_EVENT_DETAILS + LOGD("injectInputEvent - eventType=%d, injectorPid=%d, injectorUid=%d, " + "syncMode=%d, timeoutMillis=%d", + event->getType(), injectorPid, injectorUid, syncMode, timeoutMillis); +#endif + + nsecs_t endTime = now() + milliseconds_to_nanoseconds(timeoutMillis); + + uint32_t policyFlags = POLICY_FLAG_INJECTED; + if (hasInjectionPermission(injectorPid, injectorUid)) { + policyFlags |= POLICY_FLAG_TRUSTED; + } + + EventEntry* injectedEntry; + switch (event->getType()) { + case AINPUT_EVENT_TYPE_KEY: { + const KeyEvent* keyEvent = static_cast<const KeyEvent*>(event); + int32_t action = keyEvent->getAction(); + if (! validateKeyEvent(action)) { + return INPUT_EVENT_INJECTION_FAILED; + } + + int32_t flags = keyEvent->getFlags(); + if (flags & AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY) { + policyFlags |= POLICY_FLAG_VIRTUAL; + } + + mPolicy->interceptKeyBeforeQueueing(keyEvent, /*byref*/ policyFlags); + + if (policyFlags & POLICY_FLAG_WOKE_HERE) { + flags |= AKEY_EVENT_FLAG_WOKE_HERE; + } + + mLock.lock(); + injectedEntry = mAllocator.obtainKeyEntry(keyEvent->getEventTime(), + keyEvent->getDeviceId(), keyEvent->getSource(), + policyFlags, action, flags, + keyEvent->getKeyCode(), keyEvent->getScanCode(), keyEvent->getMetaState(), + keyEvent->getRepeatCount(), keyEvent->getDownTime()); + break; + } + + case AINPUT_EVENT_TYPE_MOTION: { + const MotionEvent* motionEvent = static_cast<const MotionEvent*>(event); + int32_t action = motionEvent->getAction(); + size_t pointerCount = motionEvent->getPointerCount(); + const int32_t* pointerIds = motionEvent->getPointerIds(); + if (! validateMotionEvent(action, pointerCount, pointerIds)) { + return INPUT_EVENT_INJECTION_FAILED; + } + + nsecs_t eventTime = motionEvent->getEventTime(); + mPolicy->interceptGenericBeforeQueueing(eventTime, /*byref*/ policyFlags); + + mLock.lock(); + const nsecs_t* sampleEventTimes = motionEvent->getSampleEventTimes(); + const PointerCoords* samplePointerCoords = motionEvent->getSamplePointerCoords(); + MotionEntry* motionEntry = mAllocator.obtainMotionEntry(*sampleEventTimes, + motionEvent->getDeviceId(), motionEvent->getSource(), policyFlags, + action, motionEvent->getFlags(), + motionEvent->getMetaState(), motionEvent->getEdgeFlags(), + motionEvent->getXPrecision(), motionEvent->getYPrecision(), + motionEvent->getDownTime(), uint32_t(pointerCount), + pointerIds, samplePointerCoords); + for (size_t i = motionEvent->getHistorySize(); i > 0; i--) { + sampleEventTimes += 1; + samplePointerCoords += pointerCount; + mAllocator.appendMotionSample(motionEntry, *sampleEventTimes, samplePointerCoords); + } + injectedEntry = motionEntry; + break; + } + + default: + LOGW("Cannot inject event of type %d", event->getType()); + return INPUT_EVENT_INJECTION_FAILED; + } + + InjectionState* injectionState = mAllocator.obtainInjectionState(injectorPid, injectorUid); + if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) { + injectionState->injectionIsAsync = true; + } + + injectionState->refCount += 1; + injectedEntry->injectionState = injectionState; + + bool needWake = enqueueInboundEventLocked(injectedEntry); + mLock.unlock(); + + if (needWake) { + mLooper->wake(); + } + + int32_t injectionResult; + { // acquire lock + AutoMutex _l(mLock); + + if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) { + injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; + } else { + for (;;) { + injectionResult = injectionState->injectionResult; + if (injectionResult != INPUT_EVENT_INJECTION_PENDING) { + break; + } + + nsecs_t remainingTimeout = endTime - now(); + if (remainingTimeout <= 0) { +#if DEBUG_INJECTION + LOGD("injectInputEvent - Timed out waiting for injection result " + "to become available."); +#endif + injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT; + break; + } + + mInjectionResultAvailableCondition.waitRelative(mLock, remainingTimeout); + } + + if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED + && syncMode == INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISHED) { + while (injectionState->pendingForegroundDispatches != 0) { +#if DEBUG_INJECTION + LOGD("injectInputEvent - Waiting for %d pending foreground dispatches.", + injectionState->pendingForegroundDispatches); +#endif + nsecs_t remainingTimeout = endTime - now(); + if (remainingTimeout <= 0) { +#if DEBUG_INJECTION + LOGD("injectInputEvent - Timed out waiting for pending foreground " + "dispatches to finish."); +#endif + injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT; + break; + } + + mInjectionSyncFinishedCondition.waitRelative(mLock, remainingTimeout); + } + } + } + + mAllocator.releaseInjectionState(injectionState); + } // release lock + +#if DEBUG_INJECTION + LOGD("injectInputEvent - Finished with result %d. " + "injectorPid=%d, injectorUid=%d", + injectionResult, injectorPid, injectorUid); +#endif + + return injectionResult; +} + +bool InputDispatcher::hasInjectionPermission(int32_t injectorPid, int32_t injectorUid) { + return injectorUid == 0 + || mPolicy->checkInjectEventsPermissionNonReentrant(injectorPid, injectorUid); +} + +void InputDispatcher::setInjectionResultLocked(EventEntry* entry, int32_t injectionResult) { + InjectionState* injectionState = entry->injectionState; + if (injectionState) { +#if DEBUG_INJECTION + LOGD("Setting input event injection result to %d. " + "injectorPid=%d, injectorUid=%d", + injectionResult, injectionState->injectorPid, injectionState->injectorUid); +#endif + + if (injectionState->injectionIsAsync) { + // Log the outcome since the injector did not wait for the injection result. + switch (injectionResult) { + case INPUT_EVENT_INJECTION_SUCCEEDED: + LOGV("Asynchronous input event injection succeeded."); + break; + case INPUT_EVENT_INJECTION_FAILED: + LOGW("Asynchronous input event injection failed."); + break; + case INPUT_EVENT_INJECTION_PERMISSION_DENIED: + LOGW("Asynchronous input event injection permission denied."); + break; + case INPUT_EVENT_INJECTION_TIMED_OUT: + LOGW("Asynchronous input event injection timed out."); + break; + } + } + + injectionState->injectionResult = injectionResult; + mInjectionResultAvailableCondition.broadcast(); + } +} + +void InputDispatcher::incrementPendingForegroundDispatchesLocked(EventEntry* entry) { + InjectionState* injectionState = entry->injectionState; + if (injectionState) { + injectionState->pendingForegroundDispatches += 1; + } +} + +void InputDispatcher::decrementPendingForegroundDispatchesLocked(EventEntry* entry) { + InjectionState* injectionState = entry->injectionState; + if (injectionState) { + injectionState->pendingForegroundDispatches -= 1; + + if (injectionState->pendingForegroundDispatches == 0) { + mInjectionSyncFinishedCondition.broadcast(); + } + } +} + +const InputWindow* InputDispatcher::getWindowLocked(const sp<InputChannel>& inputChannel) { + for (size_t i = 0; i < mWindows.size(); i++) { + const InputWindow* window = & mWindows[i]; + if (window->inputChannel == inputChannel) { + return window; + } + } + return NULL; +} + +void InputDispatcher::setInputWindows(const Vector<InputWindow>& inputWindows) { +#if DEBUG_FOCUS + LOGD("setInputWindows"); +#endif + { // acquire lock + AutoMutex _l(mLock); + + // Clear old window pointers. + sp<InputChannel> oldFocusedWindowChannel; + if (mFocusedWindow) { + oldFocusedWindowChannel = mFocusedWindow->inputChannel; + mFocusedWindow = NULL; + } + + mWindows.clear(); + + // Loop over new windows and rebuild the necessary window pointers for + // tracking focus and touch. + mWindows.appendVector(inputWindows); + + size_t numWindows = mWindows.size(); + for (size_t i = 0; i < numWindows; i++) { + const InputWindow* window = & mWindows.itemAt(i); + if (window->hasFocus) { + mFocusedWindow = window; + break; + } + } + + if (oldFocusedWindowChannel != NULL) { + if (!mFocusedWindow || oldFocusedWindowChannel != mFocusedWindow->inputChannel) { +#if DEBUG_FOCUS + LOGD("Focus left window: %s", + oldFocusedWindowChannel->getName().string()); +#endif + synthesizeCancelationEventsForInputChannelLocked(oldFocusedWindowChannel, + InputState::CANCEL_NON_POINTER_EVENTS, "focus left window"); + oldFocusedWindowChannel.clear(); + } + } + if (mFocusedWindow && oldFocusedWindowChannel == NULL) { +#if DEBUG_FOCUS + LOGD("Focus entered window: %s", + mFocusedWindow->inputChannel->getName().string()); +#endif + } + + for (size_t i = 0; i < mTouchState.windows.size(); ) { + TouchedWindow& touchedWindow = mTouchState.windows.editItemAt(i); + const InputWindow* window = getWindowLocked(touchedWindow.channel); + if (window) { + touchedWindow.window = window; + i += 1; + } else { +#if DEBUG_FOCUS + LOGD("Touched window was removed: %s", touchedWindow.channel->getName().string()); +#endif + synthesizeCancelationEventsForInputChannelLocked(touchedWindow.channel, + InputState::CANCEL_POINTER_EVENTS, "touched window was removed"); + mTouchState.windows.removeAt(i); + } + } + +#if DEBUG_FOCUS + //logDispatchStateLocked(); +#endif + } // release lock + + // Wake up poll loop since it may need to make new input dispatching choices. + mLooper->wake(); +} + +void InputDispatcher::setFocusedApplication(const InputApplication* inputApplication) { +#if DEBUG_FOCUS + LOGD("setFocusedApplication"); +#endif + { // acquire lock + AutoMutex _l(mLock); + + releaseFocusedApplicationLocked(); + + if (inputApplication) { + mFocusedApplicationStorage = *inputApplication; + mFocusedApplication = & mFocusedApplicationStorage; + } + +#if DEBUG_FOCUS + //logDispatchStateLocked(); +#endif + } // release lock + + // Wake up poll loop since it may need to make new input dispatching choices. + mLooper->wake(); +} + +void InputDispatcher::releaseFocusedApplicationLocked() { + if (mFocusedApplication) { + mFocusedApplication = NULL; + mFocusedApplicationStorage.inputApplicationHandle.clear(); + } +} + +void InputDispatcher::setInputDispatchMode(bool enabled, bool frozen) { +#if DEBUG_FOCUS + LOGD("setInputDispatchMode: enabled=%d, frozen=%d", enabled, frozen); +#endif + + bool changed; + { // acquire lock + AutoMutex _l(mLock); + + if (mDispatchEnabled != enabled || mDispatchFrozen != frozen) { + if (mDispatchFrozen && !frozen) { + resetANRTimeoutsLocked(); + } + + if (mDispatchEnabled && !enabled) { + resetAndDropEverythingLocked("dispatcher is being disabled"); + } + + mDispatchEnabled = enabled; + mDispatchFrozen = frozen; + changed = true; + } else { + changed = false; + } + +#if DEBUG_FOCUS + //logDispatchStateLocked(); +#endif + } // release lock + + if (changed) { + // Wake up poll loop since it may need to make new input dispatching choices. + mLooper->wake(); + } +} + +bool InputDispatcher::transferTouchFocus(const sp<InputChannel>& fromChannel, + const sp<InputChannel>& toChannel) { +#if DEBUG_FOCUS + LOGD("transferTouchFocus: fromChannel=%s, toChannel=%s", + fromChannel->getName().string(), toChannel->getName().string()); +#endif + { // acquire lock + AutoMutex _l(mLock); + + const InputWindow* fromWindow = getWindowLocked(fromChannel); + const InputWindow* toWindow = getWindowLocked(toChannel); + if (! fromWindow || ! toWindow) { +#if DEBUG_FOCUS + LOGD("Cannot transfer focus because from or to window not found."); +#endif + return false; + } + if (fromWindow == toWindow) { +#if DEBUG_FOCUS + LOGD("Trivial transfer to same window."); +#endif + return true; + } + + bool found = false; + for (size_t i = 0; i < mTouchState.windows.size(); i++) { + const TouchedWindow& touchedWindow = mTouchState.windows[i]; + if (touchedWindow.window == fromWindow) { + int32_t oldTargetFlags = touchedWindow.targetFlags; + BitSet32 pointerIds = touchedWindow.pointerIds; + + mTouchState.windows.removeAt(i); + + int32_t newTargetFlags = oldTargetFlags + & (InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_SPLIT); + mTouchState.addOrUpdateWindow(toWindow, newTargetFlags, pointerIds); + + found = true; + break; + } + } + + if (! found) { +#if DEBUG_FOCUS + LOGD("Focus transfer failed because from window did not have focus."); +#endif + return false; + } + + ssize_t fromConnectionIndex = getConnectionIndexLocked(fromChannel); + ssize_t toConnectionIndex = getConnectionIndexLocked(toChannel); + if (fromConnectionIndex >= 0 && toConnectionIndex >= 0) { + sp<Connection> fromConnection = mConnectionsByReceiveFd.valueAt(fromConnectionIndex); + sp<Connection> toConnection = mConnectionsByReceiveFd.valueAt(toConnectionIndex); + + fromConnection->inputState.copyPointerStateTo(toConnection->inputState); + synthesizeCancelationEventsForConnectionLocked(fromConnection, + InputState::CANCEL_POINTER_EVENTS, + "transferring touch focus from this window to another window"); + } + +#if DEBUG_FOCUS + logDispatchStateLocked(); +#endif + } // release lock + + // Wake up poll loop since it may need to make new input dispatching choices. + mLooper->wake(); + return true; +} + +void InputDispatcher::resetAndDropEverythingLocked(const char* reason) { +#if DEBUG_FOCUS + LOGD("Resetting and dropping all events (%s).", reason); +#endif + + synthesizeCancelationEventsForAllConnectionsLocked(InputState::CANCEL_ALL_EVENTS, reason); + + resetKeyRepeatLocked(); + releasePendingEventLocked(); + drainInboundQueueLocked(); + resetTargetsLocked(); + + mTouchState.reset(); +} + +void InputDispatcher::logDispatchStateLocked() { + String8 dump; + dumpDispatchStateLocked(dump); + + char* text = dump.lockBuffer(dump.size()); + char* start = text; + while (*start != '\0') { + char* end = strchr(start, '\n'); + if (*end == '\n') { + *(end++) = '\0'; + } + LOGD("%s", start); + start = end; + } +} + +void InputDispatcher::dumpDispatchStateLocked(String8& dump) { + dump.appendFormat(INDENT "DispatchEnabled: %d\n", mDispatchEnabled); + dump.appendFormat(INDENT "DispatchFrozen: %d\n", mDispatchFrozen); + + if (mFocusedApplication) { + dump.appendFormat(INDENT "FocusedApplication: name='%s', dispatchingTimeout=%0.3fms\n", + mFocusedApplication->name.string(), + mFocusedApplication->dispatchingTimeout / 1000000.0); + } else { + dump.append(INDENT "FocusedApplication: <null>\n"); + } + dump.appendFormat(INDENT "FocusedWindow: name='%s'\n", + mFocusedWindow != NULL ? mFocusedWindow->name.string() : "<null>"); + + dump.appendFormat(INDENT "TouchDown: %s\n", toString(mTouchState.down)); + dump.appendFormat(INDENT "TouchSplit: %s\n", toString(mTouchState.split)); + dump.appendFormat(INDENT "TouchDeviceId: %d\n", mTouchState.deviceId); + dump.appendFormat(INDENT "TouchSource: 0x%08x\n", mTouchState.source); + if (!mTouchState.windows.isEmpty()) { + dump.append(INDENT "TouchedWindows:\n"); + for (size_t i = 0; i < mTouchState.windows.size(); i++) { + const TouchedWindow& touchedWindow = mTouchState.windows[i]; + dump.appendFormat(INDENT2 "%d: name='%s', pointerIds=0x%0x, targetFlags=0x%x\n", + i, touchedWindow.window->name.string(), touchedWindow.pointerIds.value, + touchedWindow.targetFlags); + } + } else { + dump.append(INDENT "TouchedWindows: <none>\n"); + } + + if (!mWindows.isEmpty()) { + dump.append(INDENT "Windows:\n"); + for (size_t i = 0; i < mWindows.size(); i++) { + const InputWindow& window = mWindows[i]; + dump.appendFormat(INDENT2 "%d: name='%s', paused=%s, hasFocus=%s, hasWallpaper=%s, " + "visible=%s, canReceiveKeys=%s, flags=0x%08x, type=0x%08x, layer=%d, " + "frame=[%d,%d][%d,%d], " + "touchableRegion=", + i, window.name.string(), + toString(window.paused), + toString(window.hasFocus), + toString(window.hasWallpaper), + toString(window.visible), + toString(window.canReceiveKeys), + window.layoutParamsFlags, window.layoutParamsType, + window.layer, + window.frameLeft, window.frameTop, + window.frameRight, window.frameBottom); + dumpRegion(dump, window.touchableRegion); + dump.appendFormat(", ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms\n", + window.ownerPid, window.ownerUid, + window.dispatchingTimeout / 1000000.0); + } + } else { + dump.append(INDENT "Windows: <none>\n"); + } + + if (!mMonitoringChannels.isEmpty()) { + dump.append(INDENT "MonitoringChannels:\n"); + for (size_t i = 0; i < mMonitoringChannels.size(); i++) { + const sp<InputChannel>& channel = mMonitoringChannels[i]; + dump.appendFormat(INDENT2 "%d: '%s'\n", i, channel->getName().string()); + } + } else { + dump.append(INDENT "MonitoringChannels: <none>\n"); + } + + dump.appendFormat(INDENT "InboundQueue: length=%u\n", mInboundQueue.count()); + + if (!mActiveConnections.isEmpty()) { + dump.append(INDENT "ActiveConnections:\n"); + for (size_t i = 0; i < mActiveConnections.size(); i++) { + const Connection* connection = mActiveConnections[i]; + dump.appendFormat(INDENT2 "%d: '%s', status=%s, outboundQueueLength=%u, " + "inputState.isNeutral=%s\n", + i, connection->getInputChannelName(), connection->getStatusLabel(), + connection->outboundQueue.count(), + toString(connection->inputState.isNeutral())); + } + } else { + dump.append(INDENT "ActiveConnections: <none>\n"); + } + + if (isAppSwitchPendingLocked()) { + dump.appendFormat(INDENT "AppSwitch: pending, due in %01.1fms\n", + (mAppSwitchDueTime - now()) / 1000000.0); + } else { + dump.append(INDENT "AppSwitch: not pending\n"); + } +} + +status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel, + const sp<InputWindowHandle>& inputWindowHandle, bool monitor) { +#if DEBUG_REGISTRATION + LOGD("channel '%s' ~ registerInputChannel - monitor=%s", inputChannel->getName().string(), + toString(monitor)); +#endif + + { // acquire lock + AutoMutex _l(mLock); + + if (getConnectionIndexLocked(inputChannel) >= 0) { + LOGW("Attempted to register already registered input channel '%s'", + inputChannel->getName().string()); + return BAD_VALUE; + } + + sp<Connection> connection = new Connection(inputChannel, inputWindowHandle); + status_t status = connection->initialize(); + if (status) { + LOGE("Failed to initialize input publisher for input channel '%s', status=%d", + inputChannel->getName().string(), status); + return status; + } + + int32_t receiveFd = inputChannel->getReceivePipeFd(); + mConnectionsByReceiveFd.add(receiveFd, connection); + + if (monitor) { + mMonitoringChannels.push(inputChannel); + } + + mLooper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this); + + runCommandsLockedInterruptible(); + } // release lock + return OK; +} + +status_t InputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputChannel) { +#if DEBUG_REGISTRATION + LOGD("channel '%s' ~ unregisterInputChannel", inputChannel->getName().string()); +#endif + + { // acquire lock + AutoMutex _l(mLock); + + ssize_t connectionIndex = getConnectionIndexLocked(inputChannel); + if (connectionIndex < 0) { + LOGW("Attempted to unregister already unregistered input channel '%s'", + inputChannel->getName().string()); + return BAD_VALUE; + } + + sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); + mConnectionsByReceiveFd.removeItemsAt(connectionIndex); + + connection->status = Connection::STATUS_ZOMBIE; + + for (size_t i = 0; i < mMonitoringChannels.size(); i++) { + if (mMonitoringChannels[i] == inputChannel) { + mMonitoringChannels.removeAt(i); + break; + } + } + + mLooper->removeFd(inputChannel->getReceivePipeFd()); + + nsecs_t currentTime = now(); + abortBrokenDispatchCycleLocked(currentTime, connection); + + runCommandsLockedInterruptible(); + } // release lock + + // Wake the poll loop because removing the connection may have changed the current + // synchronization state. + mLooper->wake(); + return OK; +} + +ssize_t InputDispatcher::getConnectionIndexLocked(const sp<InputChannel>& inputChannel) { + ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(inputChannel->getReceivePipeFd()); + if (connectionIndex >= 0) { + sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); + if (connection->inputChannel.get() == inputChannel.get()) { + return connectionIndex; + } + } + + return -1; +} + +void InputDispatcher::activateConnectionLocked(Connection* connection) { + for (size_t i = 0; i < mActiveConnections.size(); i++) { + if (mActiveConnections.itemAt(i) == connection) { + return; + } + } + mActiveConnections.add(connection); +} + +void InputDispatcher::deactivateConnectionLocked(Connection* connection) { + for (size_t i = 0; i < mActiveConnections.size(); i++) { + if (mActiveConnections.itemAt(i) == connection) { + mActiveConnections.removeAt(i); + return; + } + } +} + +void InputDispatcher::onDispatchCycleStartedLocked( + nsecs_t currentTime, const sp<Connection>& connection) { +} + +void InputDispatcher::onDispatchCycleFinishedLocked( + nsecs_t currentTime, const sp<Connection>& connection, bool handled) { + CommandEntry* commandEntry = postCommandLocked( + & InputDispatcher::doDispatchCycleFinishedLockedInterruptible); + commandEntry->connection = connection; + commandEntry->handled = handled; +} + +void InputDispatcher::onDispatchCycleBrokenLocked( + nsecs_t currentTime, const sp<Connection>& connection) { + LOGE("channel '%s' ~ Channel is unrecoverably broken and will be disposed!", + connection->getInputChannelName()); + + CommandEntry* commandEntry = postCommandLocked( + & InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible); + commandEntry->connection = connection; +} + +void InputDispatcher::onANRLocked( + nsecs_t currentTime, const InputApplication* application, const InputWindow* window, + nsecs_t eventTime, nsecs_t waitStartTime) { + LOGI("Application is not responding: %s. " + "%01.1fms since event, %01.1fms since wait started", + getApplicationWindowLabelLocked(application, window).string(), + (currentTime - eventTime) / 1000000.0, + (currentTime - waitStartTime) / 1000000.0); + + CommandEntry* commandEntry = postCommandLocked( + & InputDispatcher::doNotifyANRLockedInterruptible); + if (application) { + commandEntry->inputApplicationHandle = application->inputApplicationHandle; + } + if (window) { + commandEntry->inputWindowHandle = window->inputWindowHandle; + commandEntry->inputChannel = window->inputChannel; + } +} + +void InputDispatcher::doNotifyConfigurationChangedInterruptible( + CommandEntry* commandEntry) { + mLock.unlock(); + + mPolicy->notifyConfigurationChanged(commandEntry->eventTime); + + mLock.lock(); +} + +void InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible( + CommandEntry* commandEntry) { + sp<Connection> connection = commandEntry->connection; + + if (connection->status != Connection::STATUS_ZOMBIE) { + mLock.unlock(); + + mPolicy->notifyInputChannelBroken(connection->inputWindowHandle); + + mLock.lock(); + } +} + +void InputDispatcher::doNotifyANRLockedInterruptible( + CommandEntry* commandEntry) { + mLock.unlock(); + + nsecs_t newTimeout = mPolicy->notifyANR( + commandEntry->inputApplicationHandle, commandEntry->inputWindowHandle); + + mLock.lock(); + + resumeAfterTargetsNotReadyTimeoutLocked(newTimeout, commandEntry->inputChannel); +} + +void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible( + CommandEntry* commandEntry) { + KeyEntry* entry = commandEntry->keyEntry; + + KeyEvent event; + initializeKeyEvent(&event, entry); + + mLock.unlock(); + + bool consumed = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle, + &event, entry->policyFlags); + + mLock.lock(); + + entry->interceptKeyResult = consumed + ? KeyEntry::INTERCEPT_KEY_RESULT_SKIP + : KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE; + mAllocator.releaseKeyEntry(entry); +} + +void InputDispatcher::doDispatchCycleFinishedLockedInterruptible( + CommandEntry* commandEntry) { + sp<Connection> connection = commandEntry->connection; + bool handled = commandEntry->handled; + + if (!connection->outboundQueue.isEmpty()) { + DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next; + if (dispatchEntry->inProgress + && dispatchEntry->hasForegroundTarget() + && dispatchEntry->eventEntry->type == EventEntry::TYPE_KEY) { + KeyEntry* keyEntry = static_cast<KeyEntry*>(dispatchEntry->eventEntry); + if (!(keyEntry->flags & AKEY_EVENT_FLAG_FALLBACK)) { + if (handled) { + // If the application handled a non-fallback key, then immediately + // cancel all fallback keys previously dispatched to the application. + // This behavior will prevent chording with fallback keys (so they cannot + // be used as modifiers) but it will ensure that fallback keys do not + // get stuck. This takes care of the case where the application does not handle + // the original DOWN so we generate a fallback DOWN but it does handle + // the original UP in which case we would not generate the fallback UP. + synthesizeCancelationEventsForConnectionLocked(connection, + InputState::CANCEL_FALLBACK_EVENTS, + "application handled a non-fallback event, canceling all fallback events"); + } else { + // If the application did not handle a non-fallback key, then ask + // the policy what to do with it. We might generate a fallback key + // event here. + KeyEvent event; + initializeKeyEvent(&event, keyEntry); + + mLock.unlock(); + + bool fallback = mPolicy->dispatchUnhandledKey(connection->inputWindowHandle, + &event, keyEntry->policyFlags, &event); + + mLock.lock(); + + if (connection->status != Connection::STATUS_NORMAL) { + return; + } + + assert(connection->outboundQueue.headSentinel.next == dispatchEntry); + + if (fallback) { + // Restart the dispatch cycle using the fallback key. + keyEntry->eventTime = event.getEventTime(); + keyEntry->deviceId = event.getDeviceId(); + keyEntry->source = event.getSource(); + keyEntry->flags = event.getFlags() | AKEY_EVENT_FLAG_FALLBACK; + keyEntry->keyCode = event.getKeyCode(); + keyEntry->scanCode = event.getScanCode(); + keyEntry->metaState = event.getMetaState(); + keyEntry->repeatCount = event.getRepeatCount(); + keyEntry->downTime = event.getDownTime(); + keyEntry->syntheticRepeat = false; + + dispatchEntry->inProgress = false; + startDispatchCycleLocked(now(), connection); + return; + } + } + } + } + } + + startNextDispatchCycleLocked(now(), connection); +} + +void InputDispatcher::doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry) { + mLock.unlock(); + + mPolicy->pokeUserActivity(commandEntry->eventTime, commandEntry->userActivityEventType); + + mLock.lock(); +} + +void InputDispatcher::initializeKeyEvent(KeyEvent* event, const KeyEntry* entry) { + event->initialize(entry->deviceId, entry->source, entry->action, entry->flags, + entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount, + entry->downTime, entry->eventTime); +} + +void InputDispatcher::updateDispatchStatisticsLocked(nsecs_t currentTime, const EventEntry* entry, + int32_t injectionResult, nsecs_t timeSpentWaitingForApplication) { + // TODO Write some statistics about how long we spend waiting. +} + +void InputDispatcher::dump(String8& dump) { + dump.append("Input Dispatcher State:\n"); + dumpDispatchStateLocked(dump); +} + + +// --- InputDispatcher::Queue --- + +template <typename T> +uint32_t InputDispatcher::Queue<T>::count() const { + uint32_t result = 0; + for (const T* entry = headSentinel.next; entry != & tailSentinel; entry = entry->next) { + result += 1; + } + return result; +} + + +// --- InputDispatcher::Allocator --- + +InputDispatcher::Allocator::Allocator() { +} + +InputDispatcher::InjectionState* +InputDispatcher::Allocator::obtainInjectionState(int32_t injectorPid, int32_t injectorUid) { + InjectionState* injectionState = mInjectionStatePool.alloc(); + injectionState->refCount = 1; + injectionState->injectorPid = injectorPid; + injectionState->injectorUid = injectorUid; + injectionState->injectionIsAsync = false; + injectionState->injectionResult = INPUT_EVENT_INJECTION_PENDING; + injectionState->pendingForegroundDispatches = 0; + return injectionState; +} + +void InputDispatcher::Allocator::initializeEventEntry(EventEntry* entry, int32_t type, + nsecs_t eventTime, uint32_t policyFlags) { + entry->type = type; + entry->refCount = 1; + entry->dispatchInProgress = false; + entry->eventTime = eventTime; + entry->policyFlags = policyFlags; + entry->injectionState = NULL; +} + +void InputDispatcher::Allocator::releaseEventEntryInjectionState(EventEntry* entry) { + if (entry->injectionState) { + releaseInjectionState(entry->injectionState); + entry->injectionState = NULL; + } +} + +InputDispatcher::ConfigurationChangedEntry* +InputDispatcher::Allocator::obtainConfigurationChangedEntry(nsecs_t eventTime) { + ConfigurationChangedEntry* entry = mConfigurationChangeEntryPool.alloc(); + initializeEventEntry(entry, EventEntry::TYPE_CONFIGURATION_CHANGED, eventTime, 0); + return entry; +} + +InputDispatcher::KeyEntry* InputDispatcher::Allocator::obtainKeyEntry(nsecs_t eventTime, + int32_t deviceId, uint32_t source, uint32_t policyFlags, int32_t action, + int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState, + int32_t repeatCount, nsecs_t downTime) { + KeyEntry* entry = mKeyEntryPool.alloc(); + initializeEventEntry(entry, EventEntry::TYPE_KEY, eventTime, policyFlags); + + entry->deviceId = deviceId; + entry->source = source; + entry->action = action; + entry->flags = flags; + entry->keyCode = keyCode; + entry->scanCode = scanCode; + entry->metaState = metaState; + entry->repeatCount = repeatCount; + entry->downTime = downTime; + entry->syntheticRepeat = false; + entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN; + return entry; +} + +InputDispatcher::MotionEntry* InputDispatcher::Allocator::obtainMotionEntry(nsecs_t eventTime, + int32_t deviceId, uint32_t source, uint32_t policyFlags, int32_t action, int32_t flags, + int32_t metaState, int32_t edgeFlags, float xPrecision, float yPrecision, + nsecs_t downTime, uint32_t pointerCount, + const int32_t* pointerIds, const PointerCoords* pointerCoords) { + MotionEntry* entry = mMotionEntryPool.alloc(); + initializeEventEntry(entry, EventEntry::TYPE_MOTION, eventTime, policyFlags); + + entry->eventTime = eventTime; + entry->deviceId = deviceId; + entry->source = source; + entry->action = action; + entry->flags = flags; + entry->metaState = metaState; + entry->edgeFlags = edgeFlags; + entry->xPrecision = xPrecision; + entry->yPrecision = yPrecision; + entry->downTime = downTime; + entry->pointerCount = pointerCount; + entry->firstSample.eventTime = eventTime; + entry->firstSample.next = NULL; + entry->lastSample = & entry->firstSample; + for (uint32_t i = 0; i < pointerCount; i++) { + entry->pointerIds[i] = pointerIds[i]; + entry->firstSample.pointerCoords[i] = pointerCoords[i]; + } + return entry; +} + +InputDispatcher::DispatchEntry* InputDispatcher::Allocator::obtainDispatchEntry( + EventEntry* eventEntry, + int32_t targetFlags, float xOffset, float yOffset) { + DispatchEntry* entry = mDispatchEntryPool.alloc(); + entry->eventEntry = eventEntry; + eventEntry->refCount += 1; + entry->targetFlags = targetFlags; + entry->xOffset = xOffset; + entry->yOffset = yOffset; + entry->inProgress = false; + entry->headMotionSample = NULL; + entry->tailMotionSample = NULL; + return entry; +} + +InputDispatcher::CommandEntry* InputDispatcher::Allocator::obtainCommandEntry(Command command) { + CommandEntry* entry = mCommandEntryPool.alloc(); + entry->command = command; + return entry; +} + +void InputDispatcher::Allocator::releaseInjectionState(InjectionState* injectionState) { + injectionState->refCount -= 1; + if (injectionState->refCount == 0) { + mInjectionStatePool.free(injectionState); + } else { + assert(injectionState->refCount > 0); + } +} + +void InputDispatcher::Allocator::releaseEventEntry(EventEntry* entry) { + switch (entry->type) { + case EventEntry::TYPE_CONFIGURATION_CHANGED: + releaseConfigurationChangedEntry(static_cast<ConfigurationChangedEntry*>(entry)); + break; + case EventEntry::TYPE_KEY: + releaseKeyEntry(static_cast<KeyEntry*>(entry)); + break; + case EventEntry::TYPE_MOTION: + releaseMotionEntry(static_cast<MotionEntry*>(entry)); + break; + default: + assert(false); + break; + } +} + +void InputDispatcher::Allocator::releaseConfigurationChangedEntry( + ConfigurationChangedEntry* entry) { + entry->refCount -= 1; + if (entry->refCount == 0) { + releaseEventEntryInjectionState(entry); + mConfigurationChangeEntryPool.free(entry); + } else { + assert(entry->refCount > 0); + } +} + +void InputDispatcher::Allocator::releaseKeyEntry(KeyEntry* entry) { + entry->refCount -= 1; + if (entry->refCount == 0) { + releaseEventEntryInjectionState(entry); + mKeyEntryPool.free(entry); + } else { + assert(entry->refCount > 0); + } +} + +void InputDispatcher::Allocator::releaseMotionEntry(MotionEntry* entry) { + entry->refCount -= 1; + if (entry->refCount == 0) { + releaseEventEntryInjectionState(entry); + for (MotionSample* sample = entry->firstSample.next; sample != NULL; ) { + MotionSample* next = sample->next; + mMotionSamplePool.free(sample); + sample = next; + } + mMotionEntryPool.free(entry); + } else { + assert(entry->refCount > 0); + } +} + +void InputDispatcher::Allocator::releaseDispatchEntry(DispatchEntry* entry) { + releaseEventEntry(entry->eventEntry); + mDispatchEntryPool.free(entry); +} + +void InputDispatcher::Allocator::releaseCommandEntry(CommandEntry* entry) { + mCommandEntryPool.free(entry); +} + +void InputDispatcher::Allocator::appendMotionSample(MotionEntry* motionEntry, + nsecs_t eventTime, const PointerCoords* pointerCoords) { + MotionSample* sample = mMotionSamplePool.alloc(); + sample->eventTime = eventTime; + uint32_t pointerCount = motionEntry->pointerCount; + for (uint32_t i = 0; i < pointerCount; i++) { + sample->pointerCoords[i] = pointerCoords[i]; + } + + sample->next = NULL; + motionEntry->lastSample->next = sample; + motionEntry->lastSample = sample; +} + +void InputDispatcher::Allocator::recycleKeyEntry(KeyEntry* keyEntry) { + releaseEventEntryInjectionState(keyEntry); + + keyEntry->dispatchInProgress = false; + keyEntry->syntheticRepeat = false; + keyEntry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN; +} + + +// --- InputDispatcher::MotionEntry --- + +uint32_t InputDispatcher::MotionEntry::countSamples() const { + uint32_t count = 1; + for (MotionSample* sample = firstSample.next; sample != NULL; sample = sample->next) { + count += 1; + } + return count; +} + + +// --- InputDispatcher::InputState --- + +InputDispatcher::InputState::InputState() { +} + +InputDispatcher::InputState::~InputState() { +} + +bool InputDispatcher::InputState::isNeutral() const { + return mKeyMementos.isEmpty() && mMotionMementos.isEmpty(); +} + +InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackEvent( + const EventEntry* entry) { + switch (entry->type) { + case EventEntry::TYPE_KEY: + return trackKey(static_cast<const KeyEntry*>(entry)); + + case EventEntry::TYPE_MOTION: + return trackMotion(static_cast<const MotionEntry*>(entry)); + + default: + return CONSISTENT; + } +} + +InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackKey( + const KeyEntry* entry) { + int32_t action = entry->action; + for (size_t i = 0; i < mKeyMementos.size(); i++) { + KeyMemento& memento = mKeyMementos.editItemAt(i); + if (memento.deviceId == entry->deviceId + && memento.source == entry->source + && memento.keyCode == entry->keyCode + && memento.scanCode == entry->scanCode) { + switch (action) { + case AKEY_EVENT_ACTION_UP: + mKeyMementos.removeAt(i); + return CONSISTENT; + + case AKEY_EVENT_ACTION_DOWN: + return TOLERABLE; + + default: + return BROKEN; + } + } + } + + switch (action) { + case AKEY_EVENT_ACTION_DOWN: { + mKeyMementos.push(); + KeyMemento& memento = mKeyMementos.editTop(); + memento.deviceId = entry->deviceId; + memento.source = entry->source; + memento.keyCode = entry->keyCode; + memento.scanCode = entry->scanCode; + memento.flags = entry->flags; + memento.downTime = entry->downTime; + return CONSISTENT; + } + + default: + return BROKEN; + } +} + +InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackMotion( + const MotionEntry* entry) { + int32_t action = entry->action & AMOTION_EVENT_ACTION_MASK; + for (size_t i = 0; i < mMotionMementos.size(); i++) { + MotionMemento& memento = mMotionMementos.editItemAt(i); + if (memento.deviceId == entry->deviceId + && memento.source == entry->source) { + switch (action) { + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_CANCEL: + mMotionMementos.removeAt(i); + return CONSISTENT; + + case AMOTION_EVENT_ACTION_DOWN: + return TOLERABLE; + + case AMOTION_EVENT_ACTION_POINTER_DOWN: + if (entry->pointerCount == memento.pointerCount + 1) { + memento.setPointers(entry); + return CONSISTENT; + } + return BROKEN; + + case AMOTION_EVENT_ACTION_POINTER_UP: + if (entry->pointerCount == memento.pointerCount - 1) { + memento.setPointers(entry); + return CONSISTENT; + } + return BROKEN; + + case AMOTION_EVENT_ACTION_MOVE: + if (entry->pointerCount == memento.pointerCount) { + return CONSISTENT; + } + return BROKEN; + + default: + return BROKEN; + } + } + } + + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: { + mMotionMementos.push(); + MotionMemento& memento = mMotionMementos.editTop(); + memento.deviceId = entry->deviceId; + memento.source = entry->source; + memento.xPrecision = entry->xPrecision; + memento.yPrecision = entry->yPrecision; + memento.downTime = entry->downTime; + memento.setPointers(entry); + return CONSISTENT; + } + + default: + return BROKEN; + } +} + +void InputDispatcher::InputState::MotionMemento::setPointers(const MotionEntry* entry) { + pointerCount = entry->pointerCount; + for (uint32_t i = 0; i < entry->pointerCount; i++) { + pointerIds[i] = entry->pointerIds[i]; + pointerCoords[i] = entry->lastSample->pointerCoords[i]; + } +} + +void InputDispatcher::InputState::synthesizeCancelationEvents(nsecs_t currentTime, + Allocator* allocator, Vector<EventEntry*>& outEvents, + CancelationOptions options) { + for (size_t i = 0; i < mKeyMementos.size(); ) { + const KeyMemento& memento = mKeyMementos.itemAt(i); + if (shouldCancelKey(memento, options)) { + outEvents.push(allocator->obtainKeyEntry(currentTime, + memento.deviceId, memento.source, 0, + AKEY_EVENT_ACTION_UP, memento.flags | AKEY_EVENT_FLAG_CANCELED, + memento.keyCode, memento.scanCode, 0, 0, memento.downTime)); + mKeyMementos.removeAt(i); + } else { + i += 1; + } + } + + for (size_t i = 0; i < mMotionMementos.size(); ) { + const MotionMemento& memento = mMotionMementos.itemAt(i); + if (shouldCancelMotion(memento, options)) { + outEvents.push(allocator->obtainMotionEntry(currentTime, + memento.deviceId, memento.source, 0, + AMOTION_EVENT_ACTION_CANCEL, 0, 0, 0, + memento.xPrecision, memento.yPrecision, memento.downTime, + memento.pointerCount, memento.pointerIds, memento.pointerCoords)); + mMotionMementos.removeAt(i); + } else { + i += 1; + } + } +} + +void InputDispatcher::InputState::clear() { + mKeyMementos.clear(); + mMotionMementos.clear(); +} + +void InputDispatcher::InputState::copyPointerStateTo(InputState& other) const { + for (size_t i = 0; i < mMotionMementos.size(); i++) { + const MotionMemento& memento = mMotionMementos.itemAt(i); + if (memento.source & AINPUT_SOURCE_CLASS_POINTER) { + for (size_t j = 0; j < other.mMotionMementos.size(); ) { + const MotionMemento& otherMemento = other.mMotionMementos.itemAt(j); + if (memento.deviceId == otherMemento.deviceId + && memento.source == otherMemento.source) { + other.mMotionMementos.removeAt(j); + } else { + j += 1; + } + } + other.mMotionMementos.push(memento); + } + } +} + +bool InputDispatcher::InputState::shouldCancelKey(const KeyMemento& memento, + CancelationOptions options) { + switch (options) { + case CANCEL_ALL_EVENTS: + case CANCEL_NON_POINTER_EVENTS: + return true; + case CANCEL_FALLBACK_EVENTS: + return memento.flags & AKEY_EVENT_FLAG_FALLBACK; + default: + return false; + } +} + +bool InputDispatcher::InputState::shouldCancelMotion(const MotionMemento& memento, + CancelationOptions options) { + switch (options) { + case CANCEL_ALL_EVENTS: + return true; + case CANCEL_POINTER_EVENTS: + return memento.source & AINPUT_SOURCE_CLASS_POINTER; + case CANCEL_NON_POINTER_EVENTS: + return !(memento.source & AINPUT_SOURCE_CLASS_POINTER); + default: + return false; + } +} + + +// --- InputDispatcher::Connection --- + +InputDispatcher::Connection::Connection(const sp<InputChannel>& inputChannel, + const sp<InputWindowHandle>& inputWindowHandle) : + status(STATUS_NORMAL), inputChannel(inputChannel), inputWindowHandle(inputWindowHandle), + inputPublisher(inputChannel), + lastEventTime(LONG_LONG_MAX), lastDispatchTime(LONG_LONG_MAX) { +} + +InputDispatcher::Connection::~Connection() { +} + +status_t InputDispatcher::Connection::initialize() { + return inputPublisher.initialize(); +} + +const char* InputDispatcher::Connection::getStatusLabel() const { + switch (status) { + case STATUS_NORMAL: + return "NORMAL"; + + case STATUS_BROKEN: + return "BROKEN"; + + case STATUS_ZOMBIE: + return "ZOMBIE"; + + default: + return "UNKNOWN"; + } +} + +InputDispatcher::DispatchEntry* InputDispatcher::Connection::findQueuedDispatchEntryForEvent( + const EventEntry* eventEntry) const { + for (DispatchEntry* dispatchEntry = outboundQueue.tailSentinel.prev; + dispatchEntry != & outboundQueue.headSentinel; dispatchEntry = dispatchEntry->prev) { + if (dispatchEntry->eventEntry == eventEntry) { + return dispatchEntry; + } + } + return NULL; +} + + +// --- InputDispatcher::CommandEntry --- + +InputDispatcher::CommandEntry::CommandEntry() : + keyEntry(NULL) { +} + +InputDispatcher::CommandEntry::~CommandEntry() { +} + + +// --- InputDispatcher::TouchState --- + +InputDispatcher::TouchState::TouchState() : + down(false), split(false), deviceId(-1), source(0) { +} + +InputDispatcher::TouchState::~TouchState() { +} + +void InputDispatcher::TouchState::reset() { + down = false; + split = false; + deviceId = -1; + source = 0; + windows.clear(); +} + +void InputDispatcher::TouchState::copyFrom(const TouchState& other) { + down = other.down; + split = other.split; + deviceId = other.deviceId; + source = other.source; + windows.clear(); + windows.appendVector(other.windows); +} + +void InputDispatcher::TouchState::addOrUpdateWindow(const InputWindow* window, + int32_t targetFlags, BitSet32 pointerIds) { + if (targetFlags & InputTarget::FLAG_SPLIT) { + split = true; + } + + for (size_t i = 0; i < windows.size(); i++) { + TouchedWindow& touchedWindow = windows.editItemAt(i); + if (touchedWindow.window == window) { + touchedWindow.targetFlags |= targetFlags; + touchedWindow.pointerIds.value |= pointerIds.value; + return; + } + } + + windows.push(); + + TouchedWindow& touchedWindow = windows.editTop(); + touchedWindow.window = window; + touchedWindow.targetFlags = targetFlags; + touchedWindow.pointerIds = pointerIds; + touchedWindow.channel = window->inputChannel; +} + +void InputDispatcher::TouchState::removeOutsideTouchWindows() { + for (size_t i = 0 ; i < windows.size(); ) { + if (windows[i].targetFlags & InputTarget::FLAG_OUTSIDE) { + windows.removeAt(i); + } else { + i += 1; + } + } +} + +const InputWindow* InputDispatcher::TouchState::getFirstForegroundWindow() { + for (size_t i = 0; i < windows.size(); i++) { + if (windows[i].targetFlags & InputTarget::FLAG_FOREGROUND) { + return windows[i].window; + } + } + return NULL; +} + + +// --- InputDispatcherThread --- + +InputDispatcherThread::InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher) : + Thread(/*canCallJava*/ true), mDispatcher(dispatcher) { +} + +InputDispatcherThread::~InputDispatcherThread() { +} + +bool InputDispatcherThread::threadLoop() { + mDispatcher->dispatchOnce(); + return true; +} + +} // namespace android diff --git a/services/input/InputDispatcher.h b/services/input/InputDispatcher.h new file mode 100644 index 0000000..006c6b8 --- /dev/null +++ b/services/input/InputDispatcher.h @@ -0,0 +1,1001 @@ +/* + * Copyright (C) 2010 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. + */ + +#ifndef _UI_INPUT_DISPATCHER_H +#define _UI_INPUT_DISPATCHER_H + +#include <ui/Input.h> +#include <ui/InputTransport.h> +#include <utils/KeyedVector.h> +#include <utils/Vector.h> +#include <utils/threads.h> +#include <utils/Timers.h> +#include <utils/RefBase.h> +#include <utils/String8.h> +#include <utils/Looper.h> +#include <utils/Pool.h> +#include <utils/BitSet.h> + +#include <stddef.h> +#include <unistd.h> +#include <limits.h> + +#include "InputWindow.h" +#include "InputApplication.h" + + +namespace android { + +/* + * Constants used to report the outcome of input event injection. + */ +enum { + /* (INTERNAL USE ONLY) Specifies that injection is pending and its outcome is unknown. */ + INPUT_EVENT_INJECTION_PENDING = -1, + + /* Injection succeeded. */ + INPUT_EVENT_INJECTION_SUCCEEDED = 0, + + /* Injection failed because the injector did not have permission to inject + * into the application with input focus. */ + INPUT_EVENT_INJECTION_PERMISSION_DENIED = 1, + + /* Injection failed because there were no available input targets. */ + INPUT_EVENT_INJECTION_FAILED = 2, + + /* Injection failed due to a timeout. */ + INPUT_EVENT_INJECTION_TIMED_OUT = 3 +}; + +/* + * Constants used to determine the input event injection synchronization mode. + */ +enum { + /* Injection is asynchronous and is assumed always to be successful. */ + INPUT_EVENT_INJECTION_SYNC_NONE = 0, + + /* Waits for previous events to be dispatched so that the input dispatcher can determine + * whether input event injection willbe permitted based on the current input focus. + * Does not wait for the input event to finish processing. */ + INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT = 1, + + /* Waits for the input event to be completely processed. */ + INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISHED = 2, +}; + + +/* + * An input target specifies how an input event is to be dispatched to a particular window + * including the window's input channel, control flags, a timeout, and an X / Y offset to + * be added to input event coordinates to compensate for the absolute position of the + * window area. + */ +struct InputTarget { + enum { + /* This flag indicates that the event is being delivered to a foreground application. */ + FLAG_FOREGROUND = 0x01, + + /* This flag indicates that a MotionEvent with AMOTION_EVENT_ACTION_DOWN falls outside + * of the area of this target and so should instead be delivered as an + * AMOTION_EVENT_ACTION_OUTSIDE to this target. */ + FLAG_OUTSIDE = 0x02, + + /* This flag indicates that the target of a MotionEvent is partly or wholly + * obscured by another visible window above it. The motion event should be + * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED. */ + FLAG_WINDOW_IS_OBSCURED = 0x04, + + /* This flag indicates that a motion event is being split across multiple windows. */ + FLAG_SPLIT = 0x08, + }; + + // The input channel to be targeted. + sp<InputChannel> inputChannel; + + // Flags for the input target. + int32_t flags; + + // The x and y offset to add to a MotionEvent as it is delivered. + // (ignored for KeyEvents) + float xOffset, yOffset; + + // The subset of pointer ids to include in motion events dispatched to this input target + // if FLAG_SPLIT is set. + BitSet32 pointerIds; +}; + + +/* + * Input dispatcher policy interface. + * + * The input reader policy is used by the input reader to interact with the Window Manager + * and other system components. + * + * The actual implementation is partially supported by callbacks into the DVM + * via JNI. This interface is also mocked in the unit tests. + */ +class InputDispatcherPolicyInterface : public virtual RefBase { +protected: + InputDispatcherPolicyInterface() { } + virtual ~InputDispatcherPolicyInterface() { } + +public: + /* Notifies the system that a configuration change has occurred. */ + virtual void notifyConfigurationChanged(nsecs_t when) = 0; + + /* Notifies the system that an application is not responding. + * Returns a new timeout to continue waiting, or 0 to abort dispatch. */ + virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle, + const sp<InputWindowHandle>& inputWindowHandle) = 0; + + /* Notifies the system that an input channel is unrecoverably broken. */ + virtual void notifyInputChannelBroken(const sp<InputWindowHandle>& inputWindowHandle) = 0; + + /* Gets the key repeat initial timeout or -1 if automatic key repeating is disabled. */ + virtual nsecs_t getKeyRepeatTimeout() = 0; + + /* Gets the key repeat inter-key delay. */ + virtual nsecs_t getKeyRepeatDelay() = 0; + + /* Gets the maximum suggested event delivery rate per second. + * This value is used to throttle motion event movement actions on a per-device + * basis. It is not intended to be a hard limit. + */ + virtual int32_t getMaxEventsPerSecond() = 0; + + /* Intercepts a key event immediately before queueing it. + * The policy can use this method as an opportunity to perform power management functions + * and early event preprocessing such as updating policy flags. + * + * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event + * should be dispatched to applications. + */ + virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) = 0; + + /* Intercepts a generic touch, trackball or other event before queueing it. + * The policy can use this method as an opportunity to perform power management functions + * and early event preprocessing such as updating policy flags. + * + * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event + * should be dispatched to applications. + */ + virtual void interceptGenericBeforeQueueing(nsecs_t when, uint32_t& policyFlags) = 0; + + /* Allows the policy a chance to intercept a key before dispatching. */ + virtual bool interceptKeyBeforeDispatching(const sp<InputWindowHandle>& inputWindowHandle, + const KeyEvent* keyEvent, uint32_t policyFlags) = 0; + + /* Allows the policy a chance to perform default processing for an unhandled key. + * Returns an alternate keycode to redispatch as a fallback, or 0 to give up. */ + virtual bool dispatchUnhandledKey(const sp<InputWindowHandle>& inputWindowHandle, + const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) = 0; + + /* Notifies the policy about switch events. + */ + virtual void notifySwitch(nsecs_t when, + int32_t switchCode, int32_t switchValue, uint32_t policyFlags) = 0; + + /* Poke user activity for an event dispatched to a window. */ + virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType) = 0; + + /* Checks whether a given application pid/uid has permission to inject input events + * into other applications. + * + * This method is special in that its implementation promises to be non-reentrant and + * is safe to call while holding other locks. (Most other methods make no such guarantees!) + */ + virtual bool checkInjectEventsPermissionNonReentrant( + int32_t injectorPid, int32_t injectorUid) = 0; +}; + + +/* Notifies the system about input events generated by the input reader. + * The dispatcher is expected to be mostly asynchronous. */ +class InputDispatcherInterface : public virtual RefBase { +protected: + InputDispatcherInterface() { } + virtual ~InputDispatcherInterface() { } + +public: + /* Dumps the state of the input dispatcher. + * + * This method may be called on any thread (usually by the input manager). */ + virtual void dump(String8& dump) = 0; + + /* Runs a single iteration of the dispatch loop. + * Nominally processes one queued event, a timeout, or a response from an input consumer. + * + * This method should only be called on the input dispatcher thread. + */ + virtual void dispatchOnce() = 0; + + /* Notifies the dispatcher about new events. + * + * These methods should only be called on the input reader thread. + */ + virtual void notifyConfigurationChanged(nsecs_t eventTime) = 0; + virtual void notifyKey(nsecs_t eventTime, int32_t deviceId, uint32_t source, + uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode, + int32_t scanCode, int32_t metaState, nsecs_t downTime) = 0; + virtual void notifyMotion(nsecs_t eventTime, int32_t deviceId, uint32_t source, + uint32_t policyFlags, int32_t action, int32_t flags, + int32_t metaState, int32_t edgeFlags, + uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords, + float xPrecision, float yPrecision, nsecs_t downTime) = 0; + virtual void notifySwitch(nsecs_t when, + int32_t switchCode, int32_t switchValue, uint32_t policyFlags) = 0; + + /* Injects an input event and optionally waits for sync. + * The synchronization mode determines whether the method blocks while waiting for + * input injection to proceed. + * Returns one of the INPUT_EVENT_INJECTION_XXX constants. + * + * This method may be called on any thread (usually by the input manager). + */ + virtual int32_t injectInputEvent(const InputEvent* event, + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) = 0; + + /* Sets the list of input windows. + * + * This method may be called on any thread (usually by the input manager). + */ + virtual void setInputWindows(const Vector<InputWindow>& inputWindows) = 0; + + /* Sets the focused application. + * + * This method may be called on any thread (usually by the input manager). + */ + virtual void setFocusedApplication(const InputApplication* inputApplication) = 0; + + /* Sets the input dispatching mode. + * + * This method may be called on any thread (usually by the input manager). + */ + virtual void setInputDispatchMode(bool enabled, bool frozen) = 0; + + /* Transfers touch focus from the window associated with one channel to the + * window associated with the other channel. + * + * Returns true on success. False if the window did not actually have touch focus. + */ + virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel, + const sp<InputChannel>& toChannel) = 0; + + /* Registers or unregister input channels that may be used as targets for input events. + * If monitor is true, the channel will receive a copy of all input events. + * + * These methods may be called on any thread (usually by the input manager). + */ + virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel, + const sp<InputWindowHandle>& inputWindowHandle, bool monitor) = 0; + virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel) = 0; +}; + +/* Dispatches events to input targets. Some functions of the input dispatcher, such as + * identifying input targets, are controlled by a separate policy object. + * + * IMPORTANT INVARIANT: + * Because the policy can potentially block or cause re-entrance into the input dispatcher, + * the input dispatcher never calls into the policy while holding its internal locks. + * The implementation is also carefully designed to recover from scenarios such as an + * input channel becoming unregistered while identifying input targets or processing timeouts. + * + * Methods marked 'Locked' must be called with the lock acquired. + * + * Methods marked 'LockedInterruptible' must be called with the lock acquired but + * may during the course of their execution release the lock, call into the policy, and + * then reacquire the lock. The caller is responsible for recovering gracefully. + * + * A 'LockedInterruptible' method may called a 'Locked' method, but NOT vice-versa. + */ +class InputDispatcher : public InputDispatcherInterface { +protected: + virtual ~InputDispatcher(); + +public: + explicit InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy); + + virtual void dump(String8& dump); + + virtual void dispatchOnce(); + + virtual void notifyConfigurationChanged(nsecs_t eventTime); + virtual void notifyKey(nsecs_t eventTime, int32_t deviceId, uint32_t source, + uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode, + int32_t scanCode, int32_t metaState, nsecs_t downTime); + virtual void notifyMotion(nsecs_t eventTime, int32_t deviceId, uint32_t source, + uint32_t policyFlags, int32_t action, int32_t flags, + int32_t metaState, int32_t edgeFlags, + uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords, + float xPrecision, float yPrecision, nsecs_t downTime); + virtual void notifySwitch(nsecs_t when, + int32_t switchCode, int32_t switchValue, uint32_t policyFlags) ; + + virtual int32_t injectInputEvent(const InputEvent* event, + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis); + + virtual void setInputWindows(const Vector<InputWindow>& inputWindows); + virtual void setFocusedApplication(const InputApplication* inputApplication); + virtual void setInputDispatchMode(bool enabled, bool frozen); + + virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel, + const sp<InputChannel>& toChannel); + + virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel, + const sp<InputWindowHandle>& inputWindowHandle, bool monitor); + virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel); + +private: + template <typename T> + struct Link { + T* next; + T* prev; + }; + + struct InjectionState { + mutable int32_t refCount; + + int32_t injectorPid; + int32_t injectorUid; + int32_t injectionResult; // initially INPUT_EVENT_INJECTION_PENDING + bool injectionIsAsync; // set to true if injection is not waiting for the result + int32_t pendingForegroundDispatches; // the number of foreground dispatches in progress + }; + + struct EventEntry : Link<EventEntry> { + enum { + TYPE_SENTINEL, + TYPE_CONFIGURATION_CHANGED, + TYPE_KEY, + TYPE_MOTION + }; + + mutable int32_t refCount; + int32_t type; + nsecs_t eventTime; + uint32_t policyFlags; + InjectionState* injectionState; + + bool dispatchInProgress; // initially false, set to true while dispatching + + inline bool isInjected() { return injectionState != NULL; } + }; + + struct ConfigurationChangedEntry : EventEntry { + }; + + struct KeyEntry : EventEntry { + int32_t deviceId; + uint32_t source; + int32_t action; + int32_t flags; + int32_t keyCode; + int32_t scanCode; + int32_t metaState; + int32_t repeatCount; + nsecs_t downTime; + + bool syntheticRepeat; // set to true for synthetic key repeats + + enum InterceptKeyResult { + INTERCEPT_KEY_RESULT_UNKNOWN, + INTERCEPT_KEY_RESULT_SKIP, + INTERCEPT_KEY_RESULT_CONTINUE, + }; + InterceptKeyResult interceptKeyResult; // set based on the interception result + }; + + struct MotionSample { + MotionSample* next; + + nsecs_t eventTime; + PointerCoords pointerCoords[MAX_POINTERS]; + }; + + struct MotionEntry : EventEntry { + int32_t deviceId; + uint32_t source; + int32_t action; + int32_t flags; + int32_t metaState; + int32_t edgeFlags; + float xPrecision; + float yPrecision; + nsecs_t downTime; + uint32_t pointerCount; + int32_t pointerIds[MAX_POINTERS]; + + // Linked list of motion samples associated with this motion event. + MotionSample firstSample; + MotionSample* lastSample; + + uint32_t countSamples() const; + }; + + // Tracks the progress of dispatching a particular event to a particular connection. + struct DispatchEntry : Link<DispatchEntry> { + EventEntry* eventEntry; // the event to dispatch + int32_t targetFlags; + float xOffset; + float yOffset; + + // True if dispatch has started. + bool inProgress; + + // For motion events: + // Pointer to the first motion sample to dispatch in this cycle. + // Usually NULL to indicate that the list of motion samples begins at + // MotionEntry::firstSample. Otherwise, some samples were dispatched in a previous + // cycle and this pointer indicates the location of the first remainining sample + // to dispatch during the current cycle. + MotionSample* headMotionSample; + // Pointer to a motion sample to dispatch in the next cycle if the dispatcher was + // unable to send all motion samples during this cycle. On the next cycle, + // headMotionSample will be initialized to tailMotionSample and tailMotionSample + // will be set to NULL. + MotionSample* tailMotionSample; + + inline bool hasForegroundTarget() const { + return targetFlags & InputTarget::FLAG_FOREGROUND; + } + + inline bool isSplit() const { + return targetFlags & InputTarget::FLAG_SPLIT; + } + }; + + // A command entry captures state and behavior for an action to be performed in the + // dispatch loop after the initial processing has taken place. It is essentially + // a kind of continuation used to postpone sensitive policy interactions to a point + // in the dispatch loop where it is safe to release the lock (generally after finishing + // the critical parts of the dispatch cycle). + // + // The special thing about commands is that they can voluntarily release and reacquire + // the dispatcher lock at will. Initially when the command starts running, the + // dispatcher lock is held. However, if the command needs to call into the policy to + // do some work, it can release the lock, do the work, then reacquire the lock again + // before returning. + // + // This mechanism is a bit clunky but it helps to preserve the invariant that the dispatch + // never calls into the policy while holding its lock. + // + // Commands are implicitly 'LockedInterruptible'. + struct CommandEntry; + typedef void (InputDispatcher::*Command)(CommandEntry* commandEntry); + + class Connection; + struct CommandEntry : Link<CommandEntry> { + CommandEntry(); + ~CommandEntry(); + + Command command; + + // parameters for the command (usage varies by command) + sp<Connection> connection; + nsecs_t eventTime; + KeyEntry* keyEntry; + sp<InputChannel> inputChannel; + sp<InputApplicationHandle> inputApplicationHandle; + sp<InputWindowHandle> inputWindowHandle; + int32_t userActivityEventType; + bool handled; + }; + + // Generic queue implementation. + template <typename T> + struct Queue { + T headSentinel; + T tailSentinel; + + inline Queue() { + headSentinel.prev = NULL; + headSentinel.next = & tailSentinel; + tailSentinel.prev = & headSentinel; + tailSentinel.next = NULL; + } + + inline bool isEmpty() const { + return headSentinel.next == & tailSentinel; + } + + inline void enqueueAtTail(T* entry) { + T* last = tailSentinel.prev; + last->next = entry; + entry->prev = last; + entry->next = & tailSentinel; + tailSentinel.prev = entry; + } + + inline void enqueueAtHead(T* entry) { + T* first = headSentinel.next; + headSentinel.next = entry; + entry->prev = & headSentinel; + entry->next = first; + first->prev = entry; + } + + inline void dequeue(T* entry) { + entry->prev->next = entry->next; + entry->next->prev = entry->prev; + } + + inline T* dequeueAtHead() { + T* first = headSentinel.next; + dequeue(first); + return first; + } + + uint32_t count() const; + }; + + /* Allocates queue entries and performs reference counting as needed. */ + class Allocator { + public: + Allocator(); + + InjectionState* obtainInjectionState(int32_t injectorPid, int32_t injectorUid); + ConfigurationChangedEntry* obtainConfigurationChangedEntry(nsecs_t eventTime); + KeyEntry* obtainKeyEntry(nsecs_t eventTime, + int32_t deviceId, uint32_t source, uint32_t policyFlags, int32_t action, + int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState, + int32_t repeatCount, nsecs_t downTime); + MotionEntry* obtainMotionEntry(nsecs_t eventTime, + int32_t deviceId, uint32_t source, uint32_t policyFlags, int32_t action, + int32_t flags, int32_t metaState, int32_t edgeFlags, + float xPrecision, float yPrecision, + nsecs_t downTime, uint32_t pointerCount, + const int32_t* pointerIds, const PointerCoords* pointerCoords); + DispatchEntry* obtainDispatchEntry(EventEntry* eventEntry, + int32_t targetFlags, float xOffset, float yOffset); + CommandEntry* obtainCommandEntry(Command command); + + void releaseInjectionState(InjectionState* injectionState); + void releaseEventEntry(EventEntry* entry); + void releaseConfigurationChangedEntry(ConfigurationChangedEntry* entry); + void releaseKeyEntry(KeyEntry* entry); + void releaseMotionEntry(MotionEntry* entry); + void releaseDispatchEntry(DispatchEntry* entry); + void releaseCommandEntry(CommandEntry* entry); + + void recycleKeyEntry(KeyEntry* entry); + + void appendMotionSample(MotionEntry* motionEntry, + nsecs_t eventTime, const PointerCoords* pointerCoords); + + private: + Pool<InjectionState> mInjectionStatePool; + Pool<ConfigurationChangedEntry> mConfigurationChangeEntryPool; + Pool<KeyEntry> mKeyEntryPool; + Pool<MotionEntry> mMotionEntryPool; + Pool<MotionSample> mMotionSamplePool; + Pool<DispatchEntry> mDispatchEntryPool; + Pool<CommandEntry> mCommandEntryPool; + + void initializeEventEntry(EventEntry* entry, int32_t type, nsecs_t eventTime, + uint32_t policyFlags); + void releaseEventEntryInjectionState(EventEntry* entry); + }; + + /* Tracks dispatched key and motion event state so that cancelation events can be + * synthesized when events are dropped. */ + class InputState { + public: + // Specifies whether a given event will violate input state consistency. + enum Consistency { + // The event is consistent with the current input state. + CONSISTENT, + // The event is inconsistent with the current input state but applications + // will tolerate it. eg. Down followed by another down. + TOLERABLE, + // The event is inconsistent with the current input state and will probably + // cause applications to crash. eg. Up without prior down, move with + // unexpected number of pointers. + BROKEN + }; + + // Specifies the sources to cancel. + enum CancelationOptions { + CANCEL_ALL_EVENTS = 0, + CANCEL_POINTER_EVENTS = 1, + CANCEL_NON_POINTER_EVENTS = 2, + CANCEL_FALLBACK_EVENTS = 3, + }; + + InputState(); + ~InputState(); + + // Returns true if there is no state to be canceled. + bool isNeutral() const; + + // Records tracking information for an event that has just been published. + // Returns whether the event is consistent with the current input state. + Consistency trackEvent(const EventEntry* entry); + + // Records tracking information for a key event that has just been published. + // Returns whether the event is consistent with the current input state. + Consistency trackKey(const KeyEntry* entry); + + // Records tracking information for a motion event that has just been published. + // Returns whether the event is consistent with the current input state. + Consistency trackMotion(const MotionEntry* entry); + + // Synthesizes cancelation events for the current state and resets the tracked state. + void synthesizeCancelationEvents(nsecs_t currentTime, Allocator* allocator, + Vector<EventEntry*>& outEvents, CancelationOptions options); + + // Clears the current state. + void clear(); + + // Copies pointer-related parts of the input state to another instance. + void copyPointerStateTo(InputState& other) const; + + private: + struct KeyMemento { + int32_t deviceId; + uint32_t source; + int32_t keyCode; + int32_t scanCode; + int32_t flags; + nsecs_t downTime; + }; + + struct MotionMemento { + int32_t deviceId; + uint32_t source; + float xPrecision; + float yPrecision; + nsecs_t downTime; + uint32_t pointerCount; + int32_t pointerIds[MAX_POINTERS]; + PointerCoords pointerCoords[MAX_POINTERS]; + + void setPointers(const MotionEntry* entry); + }; + + Vector<KeyMemento> mKeyMementos; + Vector<MotionMemento> mMotionMementos; + + static bool shouldCancelKey(const KeyMemento& memento, + CancelationOptions options); + static bool shouldCancelMotion(const MotionMemento& memento, + CancelationOptions options); + }; + + /* Manages the dispatch state associated with a single input channel. */ + class Connection : public RefBase { + protected: + virtual ~Connection(); + + public: + enum Status { + // Everything is peachy. + STATUS_NORMAL, + // An unrecoverable communication error has occurred. + STATUS_BROKEN, + // The input channel has been unregistered. + STATUS_ZOMBIE + }; + + Status status; + sp<InputChannel> inputChannel; // never null + sp<InputWindowHandle> inputWindowHandle; // may be null + InputPublisher inputPublisher; + InputState inputState; + Queue<DispatchEntry> outboundQueue; + + nsecs_t lastEventTime; // the time when the event was originally captured + nsecs_t lastDispatchTime; // the time when the last event was dispatched + + explicit Connection(const sp<InputChannel>& inputChannel, + const sp<InputWindowHandle>& inputWindowHandle); + + inline const char* getInputChannelName() const { return inputChannel->getName().string(); } + + const char* getStatusLabel() const; + + // Finds a DispatchEntry in the outbound queue associated with the specified event. + // Returns NULL if not found. + DispatchEntry* findQueuedDispatchEntryForEvent(const EventEntry* eventEntry) const; + + // Gets the time since the current event was originally obtained from the input driver. + inline double getEventLatencyMillis(nsecs_t currentTime) const { + return (currentTime - lastEventTime) / 1000000.0; + } + + // Gets the time since the current event entered the outbound dispatch queue. + inline double getDispatchLatencyMillis(nsecs_t currentTime) const { + return (currentTime - lastDispatchTime) / 1000000.0; + } + + status_t initialize(); + }; + + enum DropReason { + DROP_REASON_NOT_DROPPED = 0, + DROP_REASON_POLICY = 1, + DROP_REASON_APP_SWITCH = 2, + DROP_REASON_DISABLED = 3, + DROP_REASON_BLOCKED = 4, + DROP_REASON_STALE = 5, + }; + + sp<InputDispatcherPolicyInterface> mPolicy; + + Mutex mLock; + + Allocator mAllocator; + sp<Looper> mLooper; + + EventEntry* mPendingEvent; + Queue<EventEntry> mInboundQueue; + Queue<CommandEntry> mCommandQueue; + + Vector<EventEntry*> mTempCancelationEvents; + + void dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout, nsecs_t keyRepeatDelay, + nsecs_t* nextWakeupTime); + + // Enqueues an inbound event. Returns true if mLooper->wake() should be called. + bool enqueueInboundEventLocked(EventEntry* entry); + + // Cleans up input state when dropping an inbound event. + void dropInboundEventLocked(EventEntry* entry, DropReason dropReason); + + // App switch latency optimization. + bool mAppSwitchSawKeyDown; + nsecs_t mAppSwitchDueTime; + + static bool isAppSwitchKeyCode(int32_t keyCode); + bool isAppSwitchKeyEventLocked(KeyEntry* keyEntry); + bool isAppSwitchPendingLocked(); + void resetPendingAppSwitchLocked(bool handled); + + // Stale event latency optimization. + static bool isStaleEventLocked(nsecs_t currentTime, EventEntry* entry); + + // Blocked event latency optimization. Drops old events when the user intends + // to transfer focus to a new application. + EventEntry* mNextUnblockedEvent; + + const InputWindow* findTouchedWindowAtLocked(int32_t x, int32_t y); + + // All registered connections mapped by receive pipe file descriptor. + KeyedVector<int, sp<Connection> > mConnectionsByReceiveFd; + + ssize_t getConnectionIndexLocked(const sp<InputChannel>& inputChannel); + + // Active connections are connections that have a non-empty outbound queue. + // We don't use a ref-counted pointer here because we explicitly abort connections + // during unregistration which causes the connection's outbound queue to be cleared + // and the connection itself to be deactivated. + Vector<Connection*> mActiveConnections; + + // Input channels that will receive a copy of all input events. + Vector<sp<InputChannel> > mMonitoringChannels; + + // Event injection and synchronization. + Condition mInjectionResultAvailableCondition; + bool hasInjectionPermission(int32_t injectorPid, int32_t injectorUid); + void setInjectionResultLocked(EventEntry* entry, int32_t injectionResult); + + Condition mInjectionSyncFinishedCondition; + void incrementPendingForegroundDispatchesLocked(EventEntry* entry); + void decrementPendingForegroundDispatchesLocked(EventEntry* entry); + + // Throttling state. + struct ThrottleState { + nsecs_t minTimeBetweenEvents; + + nsecs_t lastEventTime; + int32_t lastDeviceId; + uint32_t lastSource; + + uint32_t originalSampleCount; // only collected during debugging + } mThrottleState; + + // Key repeat tracking. + struct KeyRepeatState { + KeyEntry* lastKeyEntry; // or null if no repeat + nsecs_t nextRepeatTime; + } mKeyRepeatState; + + void resetKeyRepeatLocked(); + KeyEntry* synthesizeKeyRepeatLocked(nsecs_t currentTime, nsecs_t keyRepeatTimeout); + + // Deferred command processing. + bool runCommandsLockedInterruptible(); + CommandEntry* postCommandLocked(Command command); + + // Inbound event processing. + void drainInboundQueueLocked(); + void releasePendingEventLocked(); + void releaseInboundEventLocked(EventEntry* entry); + + // Dispatch state. + bool mDispatchEnabled; + bool mDispatchFrozen; + + Vector<InputWindow> mWindows; + + const InputWindow* getWindowLocked(const sp<InputChannel>& inputChannel); + + // Focus tracking for keys, trackball, etc. + const InputWindow* mFocusedWindow; + + // Focus tracking for touch. + struct TouchedWindow { + const InputWindow* window; + int32_t targetFlags; + BitSet32 pointerIds; // zero unless target flag FLAG_SPLIT is set + sp<InputChannel> channel; + }; + struct TouchState { + bool down; + bool split; + int32_t deviceId; // id of the device that is currently down, others are rejected + uint32_t source; // source of the device that is current down, others are rejected + Vector<TouchedWindow> windows; + + TouchState(); + ~TouchState(); + void reset(); + void copyFrom(const TouchState& other); + void addOrUpdateWindow(const InputWindow* window, int32_t targetFlags, BitSet32 pointerIds); + void removeOutsideTouchWindows(); + const InputWindow* getFirstForegroundWindow(); + }; + + TouchState mTouchState; + TouchState mTempTouchState; + + // Focused application. + InputApplication* mFocusedApplication; + InputApplication mFocusedApplicationStorage; // preallocated storage for mFocusedApplication + void releaseFocusedApplicationLocked(); + + // Dispatch inbound events. + bool dispatchConfigurationChangedLocked( + nsecs_t currentTime, ConfigurationChangedEntry* entry); + bool dispatchKeyLocked( + nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout, + DropReason* dropReason, nsecs_t* nextWakeupTime); + bool dispatchMotionLocked( + nsecs_t currentTime, MotionEntry* entry, + DropReason* dropReason, nsecs_t* nextWakeupTime); + void dispatchEventToCurrentInputTargetsLocked( + nsecs_t currentTime, EventEntry* entry, bool resumeWithAppendedMotionSample); + + void logOutboundKeyDetailsLocked(const char* prefix, const KeyEntry* entry); + void logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry); + + // The input targets that were most recently identified for dispatch. + bool mCurrentInputTargetsValid; // false while targets are being recomputed + Vector<InputTarget> mCurrentInputTargets; + + enum InputTargetWaitCause { + INPUT_TARGET_WAIT_CAUSE_NONE, + INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY, + INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY, + }; + + InputTargetWaitCause mInputTargetWaitCause; + nsecs_t mInputTargetWaitStartTime; + nsecs_t mInputTargetWaitTimeoutTime; + bool mInputTargetWaitTimeoutExpired; + sp<InputApplicationHandle> mInputTargetWaitApplication; + + // Finding targets for input events. + void resetTargetsLocked(); + void commitTargetsLocked(); + int32_t handleTargetsNotReadyLocked(nsecs_t currentTime, const EventEntry* entry, + const InputApplication* application, const InputWindow* window, + nsecs_t* nextWakeupTime); + void resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout, + const sp<InputChannel>& inputChannel); + nsecs_t getTimeSpentWaitingForApplicationLocked(nsecs_t currentTime); + void resetANRTimeoutsLocked(); + + int32_t findFocusedWindowTargetsLocked(nsecs_t currentTime, const EventEntry* entry, + nsecs_t* nextWakeupTime); + int32_t findTouchedWindowTargetsLocked(nsecs_t currentTime, const MotionEntry* entry, + nsecs_t* nextWakeupTime); + + void addWindowTargetLocked(const InputWindow* window, int32_t targetFlags, + BitSet32 pointerIds); + void addMonitoringTargetsLocked(); + void pokeUserActivityLocked(const EventEntry* eventEntry); + bool checkInjectionPermission(const InputWindow* window, const InjectionState* injectionState); + bool isWindowObscuredAtPointLocked(const InputWindow* window, int32_t x, int32_t y) const; + bool isWindowFinishedWithPreviousInputLocked(const InputWindow* window); + String8 getApplicationWindowLabelLocked(const InputApplication* application, + const InputWindow* window); + + // Manage the dispatch cycle for a single connection. + // These methods are deliberately not Interruptible because doing all of the work + // with the mutex held makes it easier to ensure that connection invariants are maintained. + // If needed, the methods post commands to run later once the critical bits are done. + void prepareDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection, + EventEntry* eventEntry, const InputTarget* inputTarget, + bool resumeWithAppendedMotionSample); + void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection); + void finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection, + bool handled); + void startNextDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection); + void abortBrokenDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection); + void drainOutboundQueueLocked(Connection* connection); + static int handleReceiveCallback(int receiveFd, int events, void* data); + + void synthesizeCancelationEventsForAllConnectionsLocked( + InputState::CancelationOptions options, const char* reason); + void synthesizeCancelationEventsForInputChannelLocked(const sp<InputChannel>& channel, + InputState::CancelationOptions options, const char* reason); + void synthesizeCancelationEventsForConnectionLocked(const sp<Connection>& connection, + InputState::CancelationOptions options, const char* reason); + + // Splitting motion events across windows. + MotionEntry* splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet32 pointerIds); + + // Reset and drop everything the dispatcher is doing. + void resetAndDropEverythingLocked(const char* reason); + + // Dump state. + void dumpDispatchStateLocked(String8& dump); + void logDispatchStateLocked(); + + // Add or remove a connection to the mActiveConnections vector. + void activateConnectionLocked(Connection* connection); + void deactivateConnectionLocked(Connection* connection); + + // Interesting events that we might like to log or tell the framework about. + void onDispatchCycleStartedLocked( + nsecs_t currentTime, const sp<Connection>& connection); + void onDispatchCycleFinishedLocked( + nsecs_t currentTime, const sp<Connection>& connection, bool handled); + void onDispatchCycleBrokenLocked( + nsecs_t currentTime, const sp<Connection>& connection); + void onANRLocked( + nsecs_t currentTime, const InputApplication* application, const InputWindow* window, + nsecs_t eventTime, nsecs_t waitStartTime); + + // Outbound policy interactions. + void doNotifyConfigurationChangedInterruptible(CommandEntry* commandEntry); + void doNotifyInputChannelBrokenLockedInterruptible(CommandEntry* commandEntry); + void doNotifyANRLockedInterruptible(CommandEntry* commandEntry); + void doInterceptKeyBeforeDispatchingLockedInterruptible(CommandEntry* commandEntry); + void doDispatchCycleFinishedLockedInterruptible(CommandEntry* commandEntry); + void doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry); + void initializeKeyEvent(KeyEvent* event, const KeyEntry* entry); + + // Statistics gathering. + void updateDispatchStatisticsLocked(nsecs_t currentTime, const EventEntry* entry, + int32_t injectionResult, nsecs_t timeSpentWaitingForApplication); +}; + +/* Enqueues and dispatches input events, endlessly. */ +class InputDispatcherThread : public Thread { +public: + explicit InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher); + ~InputDispatcherThread(); + +private: + virtual bool threadLoop(); + + sp<InputDispatcherInterface> mDispatcher; +}; + +} // namespace android + +#endif // _UI_INPUT_DISPATCHER_H diff --git a/services/input/InputManager.cpp b/services/input/InputManager.cpp new file mode 100644 index 0000000..5dfa5d5 --- /dev/null +++ b/services/input/InputManager.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2010 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. + */ + +#define LOG_TAG "InputManager" + +//#define LOG_NDEBUG 0 + +#include "InputManager.h" + +#include <cutils/log.h> + +namespace android { + +InputManager::InputManager( + const sp<EventHubInterface>& eventHub, + const sp<InputReaderPolicyInterface>& readerPolicy, + const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) { + mDispatcher = new InputDispatcher(dispatcherPolicy); + mReader = new InputReader(eventHub, readerPolicy, mDispatcher); + initialize(); +} + +InputManager::InputManager( + const sp<InputReaderInterface>& reader, + const sp<InputDispatcherInterface>& dispatcher) : + mReader(reader), + mDispatcher(dispatcher) { + initialize(); +} + +InputManager::~InputManager() { + stop(); +} + +void InputManager::initialize() { + mReaderThread = new InputReaderThread(mReader); + mDispatcherThread = new InputDispatcherThread(mDispatcher); +} + +status_t InputManager::start() { + status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY); + if (result) { + LOGE("Could not start InputDispatcher thread due to error %d.", result); + return result; + } + + result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY); + if (result) { + LOGE("Could not start InputReader thread due to error %d.", result); + + mDispatcherThread->requestExit(); + return result; + } + + return OK; +} + +status_t InputManager::stop() { + status_t result = mReaderThread->requestExitAndWait(); + if (result) { + LOGW("Could not stop InputReader thread due to error %d.", result); + } + + result = mDispatcherThread->requestExitAndWait(); + if (result) { + LOGW("Could not stop InputDispatcher thread due to error %d.", result); + } + + return OK; +} + +sp<InputReaderInterface> InputManager::getReader() { + return mReader; +} + +sp<InputDispatcherInterface> InputManager::getDispatcher() { + return mDispatcher; +} + +} // namespace android diff --git a/services/input/InputManager.h b/services/input/InputManager.h new file mode 100644 index 0000000..df4d299 --- /dev/null +++ b/services/input/InputManager.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2010 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. + */ + +#ifndef _UI_INPUT_MANAGER_H +#define _UI_INPUT_MANAGER_H + +/** + * Native input manager. + */ + +#include "EventHub.h" +#include "InputReader.h" +#include "InputDispatcher.h" + +#include <ui/Input.h> +#include <ui/InputTransport.h> +#include <utils/Errors.h> +#include <utils/Vector.h> +#include <utils/Timers.h> +#include <utils/RefBase.h> +#include <utils/String8.h> + +namespace android { + +/* + * The input manager is the core of the system event processing. + * + * The input manager uses two threads. + * + * 1. The InputReaderThread (called "InputReader") reads and preprocesses raw input events, + * applies policy, and posts messages to a queue managed by the DispatcherThread. + * 2. The InputDispatcherThread (called "InputDispatcher") thread waits for new events on the + * queue and asynchronously dispatches them to applications. + * + * By design, the InputReaderThread class and InputDispatcherThread class do not share any + * internal state. Moreover, all communication is done one way from the InputReaderThread + * into the InputDispatcherThread and never the reverse. Both classes may interact with the + * InputDispatchPolicy, however. + * + * The InputManager class never makes any calls into Java itself. Instead, the + * InputDispatchPolicy is responsible for performing all external interactions with the + * system, including calling DVM services. + */ +class InputManagerInterface : public virtual RefBase { +protected: + InputManagerInterface() { } + virtual ~InputManagerInterface() { } + +public: + /* Starts the input manager threads. */ + virtual status_t start() = 0; + + /* Stops the input manager threads and waits for them to exit. */ + virtual status_t stop() = 0; + + /* Gets the input reader. */ + virtual sp<InputReaderInterface> getReader() = 0; + + /* Gets the input dispatcher. */ + virtual sp<InputDispatcherInterface> getDispatcher() = 0; +}; + +class InputManager : public InputManagerInterface { +protected: + virtual ~InputManager(); + +public: + InputManager( + const sp<EventHubInterface>& eventHub, + const sp<InputReaderPolicyInterface>& readerPolicy, + const sp<InputDispatcherPolicyInterface>& dispatcherPolicy); + + // (used for testing purposes) + InputManager( + const sp<InputReaderInterface>& reader, + const sp<InputDispatcherInterface>& dispatcher); + + virtual status_t start(); + virtual status_t stop(); + + virtual sp<InputReaderInterface> getReader(); + virtual sp<InputDispatcherInterface> getDispatcher(); + +private: + sp<InputReaderInterface> mReader; + sp<InputReaderThread> mReaderThread; + + sp<InputDispatcherInterface> mDispatcher; + sp<InputDispatcherThread> mDispatcherThread; + + void initialize(); +}; + +} // namespace android + +#endif // _UI_INPUT_MANAGER_H diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp new file mode 100644 index 0000000..46d374d --- /dev/null +++ b/services/input/InputReader.cpp @@ -0,0 +1,3708 @@ +/* + * Copyright (C) 2010 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. + */ + +#define LOG_TAG "InputReader" + +//#define LOG_NDEBUG 0 + +// Log debug messages for each raw event received from the EventHub. +#define DEBUG_RAW_EVENTS 0 + +// Log debug messages about touch screen filtering hacks. +#define DEBUG_HACKS 0 + +// Log debug messages about virtual key processing. +#define DEBUG_VIRTUAL_KEYS 0 + +// Log debug messages about pointers. +#define DEBUG_POINTERS 0 + +// Log debug messages about pointer assignment calculations. +#define DEBUG_POINTER_ASSIGNMENT 0 + +#include "InputReader.h" + +#include <cutils/log.h> +#include <ui/Keyboard.h> +#include <ui/VirtualKeyMap.h> + +#include <stddef.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <limits.h> +#include <math.h> + +#define INDENT " " +#define INDENT2 " " +#define INDENT3 " " +#define INDENT4 " " + +namespace android { + +// --- Static Functions --- + +template<typename T> +inline static T abs(const T& value) { + return value < 0 ? - value : value; +} + +template<typename T> +inline static T min(const T& a, const T& b) { + return a < b ? a : b; +} + +template<typename T> +inline static void swap(T& a, T& b) { + T temp = a; + a = b; + b = temp; +} + +inline static float avg(float x, float y) { + return (x + y) / 2; +} + +inline static float pythag(float x, float y) { + return sqrtf(x * x + y * y); +} + +inline static int32_t signExtendNybble(int32_t value) { + return value >= 8 ? value - 16 : value; +} + +static inline const char* toString(bool value) { + return value ? "true" : "false"; +} + +static const int32_t keyCodeRotationMap[][4] = { + // key codes enumerated counter-clockwise with the original (unrotated) key first + // no rotation, 90 degree rotation, 180 degree rotation, 270 degree rotation + { AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT }, + { AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN }, + { AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT }, + { AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP }, +}; +static const int keyCodeRotationMapSize = + sizeof(keyCodeRotationMap) / sizeof(keyCodeRotationMap[0]); + +int32_t rotateKeyCode(int32_t keyCode, int32_t orientation) { + if (orientation != DISPLAY_ORIENTATION_0) { + for (int i = 0; i < keyCodeRotationMapSize; i++) { + if (keyCode == keyCodeRotationMap[i][0]) { + return keyCodeRotationMap[i][orientation]; + } + } + } + return keyCode; +} + +static inline bool sourcesMatchMask(uint32_t sources, uint32_t sourceMask) { + return (sources & sourceMask & ~ AINPUT_SOURCE_CLASS_MASK) != 0; +} + + +// --- InputReader --- + +InputReader::InputReader(const sp<EventHubInterface>& eventHub, + const sp<InputReaderPolicyInterface>& policy, + const sp<InputDispatcherInterface>& dispatcher) : + mEventHub(eventHub), mPolicy(policy), mDispatcher(dispatcher), + mGlobalMetaState(0), mDisableVirtualKeysTimeout(-1) { + configureExcludedDevices(); + updateGlobalMetaState(); + updateInputConfiguration(); +} + +InputReader::~InputReader() { + for (size_t i = 0; i < mDevices.size(); i++) { + delete mDevices.valueAt(i); + } +} + +void InputReader::loopOnce() { + RawEvent rawEvent; + mEventHub->getEvent(& rawEvent); + +#if DEBUG_RAW_EVENTS + LOGD("Input event: device=%d type=0x%x scancode=%d keycode=%d value=%d", + rawEvent.deviceId, rawEvent.type, rawEvent.scanCode, rawEvent.keyCode, + rawEvent.value); +#endif + + process(& rawEvent); +} + +void InputReader::process(const RawEvent* rawEvent) { + switch (rawEvent->type) { + case EventHubInterface::DEVICE_ADDED: + addDevice(rawEvent->deviceId); + break; + + case EventHubInterface::DEVICE_REMOVED: + removeDevice(rawEvent->deviceId); + break; + + case EventHubInterface::FINISHED_DEVICE_SCAN: + handleConfigurationChanged(rawEvent->when); + break; + + default: + consumeEvent(rawEvent); + break; + } +} + +void InputReader::addDevice(int32_t deviceId) { + String8 name = mEventHub->getDeviceName(deviceId); + uint32_t classes = mEventHub->getDeviceClasses(deviceId); + + InputDevice* device = createDevice(deviceId, name, classes); + device->configure(); + + if (device->isIgnored()) { + LOGI("Device added: id=%d, name='%s' (ignored non-input device)", deviceId, name.string()); + } else { + LOGI("Device added: id=%d, name='%s', sources=0x%08x", deviceId, name.string(), + device->getSources()); + } + + bool added = false; + { // acquire device registry writer lock + RWLock::AutoWLock _wl(mDeviceRegistryLock); + + ssize_t deviceIndex = mDevices.indexOfKey(deviceId); + if (deviceIndex < 0) { + mDevices.add(deviceId, device); + added = true; + } + } // release device registry writer lock + + if (! added) { + LOGW("Ignoring spurious device added event for deviceId %d.", deviceId); + delete device; + return; + } +} + +void InputReader::removeDevice(int32_t deviceId) { + bool removed = false; + InputDevice* device = NULL; + { // acquire device registry writer lock + RWLock::AutoWLock _wl(mDeviceRegistryLock); + + ssize_t deviceIndex = mDevices.indexOfKey(deviceId); + if (deviceIndex >= 0) { + device = mDevices.valueAt(deviceIndex); + mDevices.removeItemsAt(deviceIndex, 1); + removed = true; + } + } // release device registry writer lock + + if (! removed) { + LOGW("Ignoring spurious device removed event for deviceId %d.", deviceId); + return; + } + + if (device->isIgnored()) { + LOGI("Device removed: id=%d, name='%s' (ignored non-input device)", + device->getId(), device->getName().string()); + } else { + LOGI("Device removed: id=%d, name='%s', sources=0x%08x", + device->getId(), device->getName().string(), device->getSources()); + } + + device->reset(); + + delete device; +} + +InputDevice* InputReader::createDevice(int32_t deviceId, const String8& name, uint32_t classes) { + InputDevice* device = new InputDevice(this, deviceId, name); + + // Switch-like devices. + if (classes & INPUT_DEVICE_CLASS_SWITCH) { + device->addMapper(new SwitchInputMapper(device)); + } + + // Keyboard-like devices. + uint32_t keyboardSources = 0; + int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC; + if (classes & INPUT_DEVICE_CLASS_KEYBOARD) { + keyboardSources |= AINPUT_SOURCE_KEYBOARD; + } + if (classes & INPUT_DEVICE_CLASS_ALPHAKEY) { + keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC; + } + if (classes & INPUT_DEVICE_CLASS_DPAD) { + keyboardSources |= AINPUT_SOURCE_DPAD; + } + + if (keyboardSources != 0) { + device->addMapper(new KeyboardInputMapper(device, keyboardSources, keyboardType)); + } + + // Cursor-like devices. + if (classes & INPUT_DEVICE_CLASS_CURSOR) { + device->addMapper(new CursorInputMapper(device)); + } + + // Touchscreens and touchpad devices. + if (classes & INPUT_DEVICE_CLASS_TOUCH_MT) { + device->addMapper(new MultiTouchInputMapper(device)); + } else if (classes & INPUT_DEVICE_CLASS_TOUCH) { + device->addMapper(new SingleTouchInputMapper(device)); + } + + return device; +} + +void InputReader::consumeEvent(const RawEvent* rawEvent) { + int32_t deviceId = rawEvent->deviceId; + + { // acquire device registry reader lock + RWLock::AutoRLock _rl(mDeviceRegistryLock); + + ssize_t deviceIndex = mDevices.indexOfKey(deviceId); + if (deviceIndex < 0) { + LOGW("Discarding event for unknown deviceId %d.", deviceId); + return; + } + + InputDevice* device = mDevices.valueAt(deviceIndex); + if (device->isIgnored()) { + //LOGD("Discarding event for ignored deviceId %d.", deviceId); + return; + } + + device->process(rawEvent); + } // release device registry reader lock +} + +void InputReader::handleConfigurationChanged(nsecs_t when) { + // Reset global meta state because it depends on the list of all configured devices. + updateGlobalMetaState(); + + // Update input configuration. + updateInputConfiguration(); + + // Enqueue configuration changed. + mDispatcher->notifyConfigurationChanged(when); +} + +void InputReader::configureExcludedDevices() { + Vector<String8> excludedDeviceNames; + mPolicy->getExcludedDeviceNames(excludedDeviceNames); + + for (size_t i = 0; i < excludedDeviceNames.size(); i++) { + mEventHub->addExcludedDevice(excludedDeviceNames[i]); + } +} + +void InputReader::updateGlobalMetaState() { + { // acquire state lock + AutoMutex _l(mStateLock); + + mGlobalMetaState = 0; + + { // acquire device registry reader lock + RWLock::AutoRLock _rl(mDeviceRegistryLock); + + for (size_t i = 0; i < mDevices.size(); i++) { + InputDevice* device = mDevices.valueAt(i); + mGlobalMetaState |= device->getMetaState(); + } + } // release device registry reader lock + } // release state lock +} + +int32_t InputReader::getGlobalMetaState() { + { // acquire state lock + AutoMutex _l(mStateLock); + + return mGlobalMetaState; + } // release state lock +} + +void InputReader::updateInputConfiguration() { + { // acquire state lock + AutoMutex _l(mStateLock); + + int32_t touchScreenConfig = InputConfiguration::TOUCHSCREEN_NOTOUCH; + int32_t keyboardConfig = InputConfiguration::KEYBOARD_NOKEYS; + int32_t navigationConfig = InputConfiguration::NAVIGATION_NONAV; + { // acquire device registry reader lock + RWLock::AutoRLock _rl(mDeviceRegistryLock); + + InputDeviceInfo deviceInfo; + for (size_t i = 0; i < mDevices.size(); i++) { + InputDevice* device = mDevices.valueAt(i); + device->getDeviceInfo(& deviceInfo); + uint32_t sources = deviceInfo.getSources(); + + if ((sources & AINPUT_SOURCE_TOUCHSCREEN) == AINPUT_SOURCE_TOUCHSCREEN) { + touchScreenConfig = InputConfiguration::TOUCHSCREEN_FINGER; + } + if ((sources & AINPUT_SOURCE_TRACKBALL) == AINPUT_SOURCE_TRACKBALL) { + navigationConfig = InputConfiguration::NAVIGATION_TRACKBALL; + } else if ((sources & AINPUT_SOURCE_DPAD) == AINPUT_SOURCE_DPAD) { + navigationConfig = InputConfiguration::NAVIGATION_DPAD; + } + if (deviceInfo.getKeyboardType() == AINPUT_KEYBOARD_TYPE_ALPHABETIC) { + keyboardConfig = InputConfiguration::KEYBOARD_QWERTY; + } + } + } // release device registry reader lock + + mInputConfiguration.touchScreen = touchScreenConfig; + mInputConfiguration.keyboard = keyboardConfig; + mInputConfiguration.navigation = navigationConfig; + } // release state lock +} + +void InputReader::disableVirtualKeysUntil(nsecs_t time) { + mDisableVirtualKeysTimeout = time; +} + +bool InputReader::shouldDropVirtualKey(nsecs_t now, + InputDevice* device, int32_t keyCode, int32_t scanCode) { + if (now < mDisableVirtualKeysTimeout) { + LOGI("Dropping virtual key from device %s because virtual keys are " + "temporarily disabled for the next %0.3fms. keyCode=%d, scanCode=%d", + device->getName().string(), + (mDisableVirtualKeysTimeout - now) * 0.000001, + keyCode, scanCode); + return true; + } else { + return false; + } +} + +void InputReader::getInputConfiguration(InputConfiguration* outConfiguration) { + { // acquire state lock + AutoMutex _l(mStateLock); + + *outConfiguration = mInputConfiguration; + } // release state lock +} + +status_t InputReader::getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) { + { // acquire device registry reader lock + RWLock::AutoRLock _rl(mDeviceRegistryLock); + + ssize_t deviceIndex = mDevices.indexOfKey(deviceId); + if (deviceIndex < 0) { + return NAME_NOT_FOUND; + } + + InputDevice* device = mDevices.valueAt(deviceIndex); + if (device->isIgnored()) { + return NAME_NOT_FOUND; + } + + device->getDeviceInfo(outDeviceInfo); + return OK; + } // release device registy reader lock +} + +void InputReader::getInputDeviceIds(Vector<int32_t>& outDeviceIds) { + outDeviceIds.clear(); + + { // acquire device registry reader lock + RWLock::AutoRLock _rl(mDeviceRegistryLock); + + size_t numDevices = mDevices.size(); + for (size_t i = 0; i < numDevices; i++) { + InputDevice* device = mDevices.valueAt(i); + if (! device->isIgnored()) { + outDeviceIds.add(device->getId()); + } + } + } // release device registy reader lock +} + +int32_t InputReader::getKeyCodeState(int32_t deviceId, uint32_t sourceMask, + int32_t keyCode) { + return getState(deviceId, sourceMask, keyCode, & InputDevice::getKeyCodeState); +} + +int32_t InputReader::getScanCodeState(int32_t deviceId, uint32_t sourceMask, + int32_t scanCode) { + return getState(deviceId, sourceMask, scanCode, & InputDevice::getScanCodeState); +} + +int32_t InputReader::getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t switchCode) { + return getState(deviceId, sourceMask, switchCode, & InputDevice::getSwitchState); +} + +int32_t InputReader::getState(int32_t deviceId, uint32_t sourceMask, int32_t code, + GetStateFunc getStateFunc) { + { // acquire device registry reader lock + RWLock::AutoRLock _rl(mDeviceRegistryLock); + + int32_t result = AKEY_STATE_UNKNOWN; + if (deviceId >= 0) { + ssize_t deviceIndex = mDevices.indexOfKey(deviceId); + if (deviceIndex >= 0) { + InputDevice* device = mDevices.valueAt(deviceIndex); + if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) { + result = (device->*getStateFunc)(sourceMask, code); + } + } + } else { + size_t numDevices = mDevices.size(); + for (size_t i = 0; i < numDevices; i++) { + InputDevice* device = mDevices.valueAt(i); + if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) { + result = (device->*getStateFunc)(sourceMask, code); + if (result >= AKEY_STATE_DOWN) { + return result; + } + } + } + } + return result; + } // release device registy reader lock +} + +bool InputReader::hasKeys(int32_t deviceId, uint32_t sourceMask, + size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) { + memset(outFlags, 0, numCodes); + return markSupportedKeyCodes(deviceId, sourceMask, numCodes, keyCodes, outFlags); +} + +bool InputReader::markSupportedKeyCodes(int32_t deviceId, uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) { + { // acquire device registry reader lock + RWLock::AutoRLock _rl(mDeviceRegistryLock); + bool result = false; + if (deviceId >= 0) { + ssize_t deviceIndex = mDevices.indexOfKey(deviceId); + if (deviceIndex >= 0) { + InputDevice* device = mDevices.valueAt(deviceIndex); + if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) { + result = device->markSupportedKeyCodes(sourceMask, + numCodes, keyCodes, outFlags); + } + } + } else { + size_t numDevices = mDevices.size(); + for (size_t i = 0; i < numDevices; i++) { + InputDevice* device = mDevices.valueAt(i); + if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) { + result |= device->markSupportedKeyCodes(sourceMask, + numCodes, keyCodes, outFlags); + } + } + } + return result; + } // release device registy reader lock +} + +void InputReader::dump(String8& dump) { + mEventHub->dump(dump); + dump.append("\n"); + + dump.append("Input Reader State:\n"); + + { // acquire device registry reader lock + RWLock::AutoRLock _rl(mDeviceRegistryLock); + + for (size_t i = 0; i < mDevices.size(); i++) { + mDevices.valueAt(i)->dump(dump); + } + } // release device registy reader lock +} + + +// --- InputReaderThread --- + +InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) : + Thread(/*canCallJava*/ true), mReader(reader) { +} + +InputReaderThread::~InputReaderThread() { +} + +bool InputReaderThread::threadLoop() { + mReader->loopOnce(); + return true; +} + + +// --- InputDevice --- + +InputDevice::InputDevice(InputReaderContext* context, int32_t id, const String8& name) : + mContext(context), mId(id), mName(name), mSources(0) { +} + +InputDevice::~InputDevice() { + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + delete mMappers[i]; + } + mMappers.clear(); +} + +static void dumpMotionRange(String8& dump, const InputDeviceInfo& deviceInfo, + int32_t rangeType, const char* name) { + const InputDeviceInfo::MotionRange* range = deviceInfo.getMotionRange(rangeType); + if (range) { + dump.appendFormat(INDENT3 "%s: min=%0.3f, max=%0.3f, flat=%0.3f, fuzz=%0.3f\n", + name, range->min, range->max, range->flat, range->fuzz); + } +} + +void InputDevice::dump(String8& dump) { + InputDeviceInfo deviceInfo; + getDeviceInfo(& deviceInfo); + + dump.appendFormat(INDENT "Device %d: %s\n", deviceInfo.getId(), + deviceInfo.getName().string()); + dump.appendFormat(INDENT2 "Sources: 0x%08x\n", deviceInfo.getSources()); + dump.appendFormat(INDENT2 "KeyboardType: %d\n", deviceInfo.getKeyboardType()); + if (!deviceInfo.getMotionRanges().isEmpty()) { + dump.append(INDENT2 "Motion Ranges:\n"); + dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_X, "X"); + dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_Y, "Y"); + dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_PRESSURE, "Pressure"); + dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_SIZE, "Size"); + dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOUCH_MAJOR, "TouchMajor"); + dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOUCH_MINOR, "TouchMinor"); + dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOOL_MAJOR, "ToolMajor"); + dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOOL_MINOR, "ToolMinor"); + dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_ORIENTATION, "Orientation"); + } + + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + mapper->dump(dump); + } +} + +void InputDevice::addMapper(InputMapper* mapper) { + mMappers.add(mapper); +} + +void InputDevice::configure() { + if (! isIgnored()) { + mContext->getEventHub()->getConfiguration(mId, &mConfiguration); + } + + mSources = 0; + + for (size_t i = 0; i < mMappers.size(); i++) { + InputMapper* mapper = mMappers[i]; + mapper->configure(); + + uint32_t sources = mapper->getSources(); + if (sources) { + mSources |= sources; + } else { + // The input mapper does not provide any sources. Remove it from the list. + mMappers.removeAt(i); + delete mapper; + i -= 1; + } + } +} + +void InputDevice::reset() { + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + mapper->reset(); + } +} + +void InputDevice::process(const RawEvent* rawEvent) { + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + mapper->process(rawEvent); + } +} + +void InputDevice::getDeviceInfo(InputDeviceInfo* outDeviceInfo) { + outDeviceInfo->initialize(mId, mName); + + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + mapper->populateDeviceInfo(outDeviceInfo); + } +} + +int32_t InputDevice::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) { + return getState(sourceMask, keyCode, & InputMapper::getKeyCodeState); +} + +int32_t InputDevice::getScanCodeState(uint32_t sourceMask, int32_t scanCode) { + return getState(sourceMask, scanCode, & InputMapper::getScanCodeState); +} + +int32_t InputDevice::getSwitchState(uint32_t sourceMask, int32_t switchCode) { + return getState(sourceMask, switchCode, & InputMapper::getSwitchState); +} + +int32_t InputDevice::getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc) { + int32_t result = AKEY_STATE_UNKNOWN; + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + if (sourcesMatchMask(mapper->getSources(), sourceMask)) { + result = (mapper->*getStateFunc)(sourceMask, code); + if (result >= AKEY_STATE_DOWN) { + return result; + } + } + } + return result; +} + +bool InputDevice::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) { + bool result = false; + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + if (sourcesMatchMask(mapper->getSources(), sourceMask)) { + result |= mapper->markSupportedKeyCodes(sourceMask, numCodes, keyCodes, outFlags); + } + } + return result; +} + +int32_t InputDevice::getMetaState() { + int32_t result = 0; + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + result |= mapper->getMetaState(); + } + return result; +} + + +// --- InputMapper --- + +InputMapper::InputMapper(InputDevice* device) : + mDevice(device), mContext(device->getContext()) { +} + +InputMapper::~InputMapper() { +} + +void InputMapper::populateDeviceInfo(InputDeviceInfo* info) { + info->addSource(getSources()); +} + +void InputMapper::dump(String8& dump) { +} + +void InputMapper::configure() { +} + +void InputMapper::reset() { +} + +int32_t InputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) { + return AKEY_STATE_UNKNOWN; +} + +int32_t InputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) { + return AKEY_STATE_UNKNOWN; +} + +int32_t InputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCode) { + return AKEY_STATE_UNKNOWN; +} + +bool InputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) { + return false; +} + +int32_t InputMapper::getMetaState() { + return 0; +} + + +// --- SwitchInputMapper --- + +SwitchInputMapper::SwitchInputMapper(InputDevice* device) : + InputMapper(device) { +} + +SwitchInputMapper::~SwitchInputMapper() { +} + +uint32_t SwitchInputMapper::getSources() { + return AINPUT_SOURCE_SWITCH; +} + +void SwitchInputMapper::process(const RawEvent* rawEvent) { + switch (rawEvent->type) { + case EV_SW: + processSwitch(rawEvent->when, rawEvent->scanCode, rawEvent->value); + break; + } +} + +void SwitchInputMapper::processSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue) { + getDispatcher()->notifySwitch(when, switchCode, switchValue, 0); +} + +int32_t SwitchInputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCode) { + return getEventHub()->getSwitchState(getDeviceId(), switchCode); +} + + +// --- KeyboardInputMapper --- + +KeyboardInputMapper::KeyboardInputMapper(InputDevice* device, + uint32_t sources, int32_t keyboardType) : + InputMapper(device), mSources(sources), + mKeyboardType(keyboardType) { + initializeLocked(); +} + +KeyboardInputMapper::~KeyboardInputMapper() { +} + +void KeyboardInputMapper::initializeLocked() { + mLocked.metaState = AMETA_NONE; + mLocked.downTime = 0; +} + +uint32_t KeyboardInputMapper::getSources() { + return mSources; +} + +void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo* info) { + InputMapper::populateDeviceInfo(info); + + info->setKeyboardType(mKeyboardType); +} + +void KeyboardInputMapper::dump(String8& dump) { + { // acquire lock + AutoMutex _l(mLock); + dump.append(INDENT2 "Keyboard Input Mapper:\n"); + dumpParameters(dump); + dump.appendFormat(INDENT3 "KeyboardType: %d\n", mKeyboardType); + dump.appendFormat(INDENT3 "KeyDowns: %d keys currently down\n", mLocked.keyDowns.size()); + dump.appendFormat(INDENT3 "MetaState: 0x%0x\n", mLocked.metaState); + dump.appendFormat(INDENT3 "DownTime: %lld\n", mLocked.downTime); + } // release lock +} + + +void KeyboardInputMapper::configure() { + InputMapper::configure(); + + // Configure basic parameters. + configureParameters(); + + // Reset LEDs. + { + AutoMutex _l(mLock); + resetLedStateLocked(); + } +} + +void KeyboardInputMapper::configureParameters() { + mParameters.orientationAware = false; + getDevice()->getConfiguration().tryGetProperty(String8("keyboard.orientationAware"), + mParameters.orientationAware); + + mParameters.associatedDisplayId = mParameters.orientationAware ? 0 : -1; +} + +void KeyboardInputMapper::dumpParameters(String8& dump) { + dump.append(INDENT3 "Parameters:\n"); + dump.appendFormat(INDENT4 "AssociatedDisplayId: %d\n", + mParameters.associatedDisplayId); + dump.appendFormat(INDENT4 "OrientationAware: %s\n", + toString(mParameters.orientationAware)); +} + +void KeyboardInputMapper::reset() { + for (;;) { + int32_t keyCode, scanCode; + { // acquire lock + AutoMutex _l(mLock); + + // Synthesize key up event on reset if keys are currently down. + if (mLocked.keyDowns.isEmpty()) { + initializeLocked(); + resetLedStateLocked(); + break; // done + } + + const KeyDown& keyDown = mLocked.keyDowns.top(); + keyCode = keyDown.keyCode; + scanCode = keyDown.scanCode; + } // release lock + + nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC); + processKey(when, false, keyCode, scanCode, 0); + } + + InputMapper::reset(); + getContext()->updateGlobalMetaState(); +} + +void KeyboardInputMapper::process(const RawEvent* rawEvent) { + switch (rawEvent->type) { + case EV_KEY: { + int32_t scanCode = rawEvent->scanCode; + if (isKeyboardOrGamepadKey(scanCode)) { + processKey(rawEvent->when, rawEvent->value != 0, rawEvent->keyCode, scanCode, + rawEvent->flags); + } + break; + } + } +} + +bool KeyboardInputMapper::isKeyboardOrGamepadKey(int32_t scanCode) { + return scanCode < BTN_MOUSE + || scanCode >= KEY_OK + || (scanCode >= BTN_GAMEPAD && scanCode < BTN_DIGI); +} + +void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode, + int32_t scanCode, uint32_t policyFlags) { + int32_t newMetaState; + nsecs_t downTime; + bool metaStateChanged = false; + + { // acquire lock + AutoMutex _l(mLock); + + if (down) { + // Rotate key codes according to orientation if needed. + // Note: getDisplayInfo is non-reentrant so we can continue holding the lock. + if (mParameters.orientationAware && mParameters.associatedDisplayId >= 0) { + int32_t orientation; + if (!getPolicy()->getDisplayInfo(mParameters.associatedDisplayId, + NULL, NULL, & orientation)) { + orientation = DISPLAY_ORIENTATION_0; + } + + keyCode = rotateKeyCode(keyCode, orientation); + } + + // Add key down. + ssize_t keyDownIndex = findKeyDownLocked(scanCode); + if (keyDownIndex >= 0) { + // key repeat, be sure to use same keycode as before in case of rotation + keyCode = mLocked.keyDowns.itemAt(keyDownIndex).keyCode; + } else { + // key down + if ((policyFlags & POLICY_FLAG_VIRTUAL) + && mContext->shouldDropVirtualKey(when, + getDevice(), keyCode, scanCode)) { + return; + } + + mLocked.keyDowns.push(); + KeyDown& keyDown = mLocked.keyDowns.editTop(); + keyDown.keyCode = keyCode; + keyDown.scanCode = scanCode; + } + + mLocked.downTime = when; + } else { + // Remove key down. + ssize_t keyDownIndex = findKeyDownLocked(scanCode); + if (keyDownIndex >= 0) { + // key up, be sure to use same keycode as before in case of rotation + keyCode = mLocked.keyDowns.itemAt(keyDownIndex).keyCode; + mLocked.keyDowns.removeAt(size_t(keyDownIndex)); + } else { + // key was not actually down + LOGI("Dropping key up from device %s because the key was not down. " + "keyCode=%d, scanCode=%d", + getDeviceName().string(), keyCode, scanCode); + return; + } + } + + int32_t oldMetaState = mLocked.metaState; + newMetaState = updateMetaState(keyCode, down, oldMetaState); + if (oldMetaState != newMetaState) { + mLocked.metaState = newMetaState; + metaStateChanged = true; + updateLedStateLocked(false); + } + + downTime = mLocked.downTime; + } // release lock + + if (metaStateChanged) { + getContext()->updateGlobalMetaState(); + } + + if (policyFlags & POLICY_FLAG_FUNCTION) { + newMetaState |= AMETA_FUNCTION_ON; + } + getDispatcher()->notifyKey(when, getDeviceId(), mSources, policyFlags, + down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, + AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime); +} + +ssize_t KeyboardInputMapper::findKeyDownLocked(int32_t scanCode) { + size_t n = mLocked.keyDowns.size(); + for (size_t i = 0; i < n; i++) { + if (mLocked.keyDowns[i].scanCode == scanCode) { + return i; + } + } + return -1; +} + +int32_t KeyboardInputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) { + return getEventHub()->getKeyCodeState(getDeviceId(), keyCode); +} + +int32_t KeyboardInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) { + return getEventHub()->getScanCodeState(getDeviceId(), scanCode); +} + +bool KeyboardInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) { + return getEventHub()->markSupportedKeyCodes(getDeviceId(), numCodes, keyCodes, outFlags); +} + +int32_t KeyboardInputMapper::getMetaState() { + { // acquire lock + AutoMutex _l(mLock); + return mLocked.metaState; + } // release lock +} + +void KeyboardInputMapper::resetLedStateLocked() { + initializeLedStateLocked(mLocked.capsLockLedState, LED_CAPSL); + initializeLedStateLocked(mLocked.numLockLedState, LED_NUML); + initializeLedStateLocked(mLocked.scrollLockLedState, LED_SCROLLL); + + updateLedStateLocked(true); +} + +void KeyboardInputMapper::initializeLedStateLocked(LockedState::LedState& ledState, int32_t led) { + ledState.avail = getEventHub()->hasLed(getDeviceId(), led); + ledState.on = false; +} + +void KeyboardInputMapper::updateLedStateLocked(bool reset) { + updateLedStateForModifierLocked(mLocked.capsLockLedState, LED_CAPSL, + AMETA_CAPS_LOCK_ON, reset); + updateLedStateForModifierLocked(mLocked.numLockLedState, LED_NUML, + AMETA_NUM_LOCK_ON, reset); + updateLedStateForModifierLocked(mLocked.scrollLockLedState, LED_SCROLLL, + AMETA_SCROLL_LOCK_ON, reset); +} + +void KeyboardInputMapper::updateLedStateForModifierLocked(LockedState::LedState& ledState, + int32_t led, int32_t modifier, bool reset) { + if (ledState.avail) { + bool desiredState = (mLocked.metaState & modifier) != 0; + if (reset || ledState.on != desiredState) { + getEventHub()->setLedState(getDeviceId(), led, desiredState); + ledState.on = desiredState; + } + } +} + + +// --- CursorInputMapper --- + +CursorInputMapper::CursorInputMapper(InputDevice* device) : + InputMapper(device) { + initializeLocked(); +} + +CursorInputMapper::~CursorInputMapper() { +} + +uint32_t CursorInputMapper::getSources() { + return mSources; +} + +void CursorInputMapper::populateDeviceInfo(InputDeviceInfo* info) { + InputMapper::populateDeviceInfo(info); + + if (mParameters.mode == Parameters::MODE_POINTER) { + float minX, minY, maxX, maxY; + if (mPointerController->getBounds(&minX, &minY, &maxX, &maxY)) { + info->addMotionRange(AINPUT_MOTION_RANGE_X, minX, maxX, 0.0f, 0.0f); + info->addMotionRange(AINPUT_MOTION_RANGE_Y, minY, maxY, 0.0f, 0.0f); + } + } else { + info->addMotionRange(AINPUT_MOTION_RANGE_X, -1.0f, 1.0f, 0.0f, mXScale); + info->addMotionRange(AINPUT_MOTION_RANGE_Y, -1.0f, 1.0f, 0.0f, mYScale); + } + info->addMotionRange(AINPUT_MOTION_RANGE_PRESSURE, 0.0f, 1.0f, 0.0f, 0.0f); +} + +void CursorInputMapper::dump(String8& dump) { + { // acquire lock + AutoMutex _l(mLock); + dump.append(INDENT2 "Cursor Input Mapper:\n"); + dumpParameters(dump); + dump.appendFormat(INDENT3 "XPrecision: %0.3f\n", mXPrecision); + dump.appendFormat(INDENT3 "YPrecision: %0.3f\n", mYPrecision); + dump.appendFormat(INDENT3 "Down: %s\n", toString(mLocked.down)); + dump.appendFormat(INDENT3 "DownTime: %lld\n", mLocked.downTime); + } // release lock +} + +void CursorInputMapper::configure() { + InputMapper::configure(); + + // Configure basic parameters. + configureParameters(); + + // Configure device mode. + switch (mParameters.mode) { + case Parameters::MODE_POINTER: + mSources = 0; // AINPUT_SOURCE_MOUSE; disable mouse support + mXPrecision = 1.0f; + mYPrecision = 1.0f; + mXScale = 1.0f; + mYScale = 1.0f; + mPointerController = getPolicy()->obtainPointerController(getDeviceId()); + break; + case Parameters::MODE_NAVIGATION: + mSources = AINPUT_SOURCE_TRACKBALL; + mXPrecision = TRACKBALL_MOVEMENT_THRESHOLD; + mYPrecision = TRACKBALL_MOVEMENT_THRESHOLD; + mXScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD; + mYScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD; + break; + } +} + +void CursorInputMapper::configureParameters() { + mParameters.mode = Parameters::MODE_POINTER; + String8 cursorModeString; + if (getDevice()->getConfiguration().tryGetProperty(String8("cursor.mode"), cursorModeString)) { + if (cursorModeString == "navigation") { + mParameters.mode = Parameters::MODE_NAVIGATION; + } else if (cursorModeString != "pointer" && cursorModeString != "default") { + LOGW("Invalid value for cursor.mode: '%s'", cursorModeString.string()); + } + } + + mParameters.orientationAware = false; + getDevice()->getConfiguration().tryGetProperty(String8("cursor.orientationAware"), + mParameters.orientationAware); + + mParameters.associatedDisplayId = mParameters.mode == Parameters::MODE_POINTER + || mParameters.orientationAware ? 0 : -1; +} + +void CursorInputMapper::dumpParameters(String8& dump) { + dump.append(INDENT3 "Parameters:\n"); + dump.appendFormat(INDENT4 "AssociatedDisplayId: %d\n", + mParameters.associatedDisplayId); + + switch (mParameters.mode) { + case Parameters::MODE_POINTER: + dump.append(INDENT4 "Mode: pointer\n"); + break; + case Parameters::MODE_NAVIGATION: + dump.append(INDENT4 "Mode: navigation\n"); + break; + default: + assert(false); + } + + dump.appendFormat(INDENT4 "OrientationAware: %s\n", + toString(mParameters.orientationAware)); +} + +void CursorInputMapper::initializeLocked() { + mAccumulator.clear(); + + mLocked.down = false; + mLocked.downTime = 0; +} + +void CursorInputMapper::reset() { + for (;;) { + { // acquire lock + AutoMutex _l(mLock); + + if (! mLocked.down) { + initializeLocked(); + break; // done + } + } // release lock + + // Synthesize button up event on reset. + nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC); + mAccumulator.fields = Accumulator::FIELD_BTN_MOUSE; + mAccumulator.btnMouse = false; + sync(when); + } + + InputMapper::reset(); +} + +void CursorInputMapper::process(const RawEvent* rawEvent) { + switch (rawEvent->type) { + case EV_KEY: + switch (rawEvent->scanCode) { + case BTN_MOUSE: + mAccumulator.fields |= Accumulator::FIELD_BTN_MOUSE; + mAccumulator.btnMouse = rawEvent->value != 0; + // Sync now since BTN_MOUSE is not necessarily followed by SYN_REPORT and + // we need to ensure that we report the up/down promptly. + sync(rawEvent->when); + break; + } + break; + + case EV_REL: + switch (rawEvent->scanCode) { + case REL_X: + mAccumulator.fields |= Accumulator::FIELD_REL_X; + mAccumulator.relX = rawEvent->value; + break; + case REL_Y: + mAccumulator.fields |= Accumulator::FIELD_REL_Y; + mAccumulator.relY = rawEvent->value; + break; + } + break; + + case EV_SYN: + switch (rawEvent->scanCode) { + case SYN_REPORT: + sync(rawEvent->when); + break; + } + break; + } +} + +void CursorInputMapper::sync(nsecs_t when) { + uint32_t fields = mAccumulator.fields; + if (fields == 0) { + return; // no new state changes, so nothing to do + } + + int motionEventAction; + PointerCoords pointerCoords; + nsecs_t downTime; + { // acquire lock + AutoMutex _l(mLock); + + bool downChanged = fields & Accumulator::FIELD_BTN_MOUSE; + + if (downChanged) { + if (mAccumulator.btnMouse) { + if (!mLocked.down) { + mLocked.down = true; + mLocked.downTime = when; + } else { + downChanged = false; + } + } else { + if (mLocked.down) { + mLocked.down = false; + } else { + downChanged = false; + } + } + } + + downTime = mLocked.downTime; + float deltaX = fields & Accumulator::FIELD_REL_X ? mAccumulator.relX * mXScale : 0.0f; + float deltaY = fields & Accumulator::FIELD_REL_Y ? mAccumulator.relY * mYScale : 0.0f; + + if (downChanged) { + motionEventAction = mLocked.down ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP; + } else { + motionEventAction = AMOTION_EVENT_ACTION_MOVE; + } + + if (mParameters.orientationAware && mParameters.associatedDisplayId >= 0 + && (deltaX != 0.0f || deltaY != 0.0f)) { + // Rotate motion based on display orientation if needed. + // Note: getDisplayInfo is non-reentrant so we can continue holding the lock. + int32_t orientation; + if (! getPolicy()->getDisplayInfo(mParameters.associatedDisplayId, + NULL, NULL, & orientation)) { + orientation = DISPLAY_ORIENTATION_0; + } + + float temp; + switch (orientation) { + case DISPLAY_ORIENTATION_90: + temp = deltaX; + deltaX = deltaY; + deltaY = -temp; + break; + + case DISPLAY_ORIENTATION_180: + deltaX = -deltaX; + deltaY = -deltaY; + break; + + case DISPLAY_ORIENTATION_270: + temp = deltaX; + deltaX = -deltaY; + deltaY = temp; + break; + } + } + + if (mPointerController != NULL) { + mPointerController->move(deltaX, deltaY); + if (downChanged) { + mPointerController->setButtonState(mLocked.down ? POINTER_BUTTON_1 : 0); + } + mPointerController->getPosition(&pointerCoords.x, &pointerCoords.y); + } else { + pointerCoords.x = deltaX; + pointerCoords.y = deltaY; + } + + pointerCoords.pressure = mLocked.down ? 1.0f : 0.0f; + pointerCoords.size = 0; + pointerCoords.touchMajor = 0; + pointerCoords.touchMinor = 0; + pointerCoords.toolMajor = 0; + pointerCoords.toolMinor = 0; + pointerCoords.orientation = 0; + } // release lock + + int32_t metaState = mContext->getGlobalMetaState(); + int32_t pointerId = 0; + getDispatcher()->notifyMotion(when, getDeviceId(), mSources, 0, + motionEventAction, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE, + 1, &pointerId, &pointerCoords, mXPrecision, mYPrecision, downTime); + + mAccumulator.clear(); +} + +int32_t CursorInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) { + if (scanCode >= BTN_MOUSE && scanCode < BTN_JOYSTICK) { + return getEventHub()->getScanCodeState(getDeviceId(), scanCode); + } else { + return AKEY_STATE_UNKNOWN; + } +} + + +// --- TouchInputMapper --- + +TouchInputMapper::TouchInputMapper(InputDevice* device) : + InputMapper(device) { + mLocked.surfaceOrientation = -1; + mLocked.surfaceWidth = -1; + mLocked.surfaceHeight = -1; + + initializeLocked(); +} + +TouchInputMapper::~TouchInputMapper() { +} + +uint32_t TouchInputMapper::getSources() { + return mSources; +} + +void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) { + InputMapper::populateDeviceInfo(info); + + { // acquire lock + AutoMutex _l(mLock); + + // Ensure surface information is up to date so that orientation changes are + // noticed immediately. + configureSurfaceLocked(); + + info->addMotionRange(AINPUT_MOTION_RANGE_X, mLocked.orientedRanges.x); + info->addMotionRange(AINPUT_MOTION_RANGE_Y, mLocked.orientedRanges.y); + + if (mLocked.orientedRanges.havePressure) { + info->addMotionRange(AINPUT_MOTION_RANGE_PRESSURE, + mLocked.orientedRanges.pressure); + } + + if (mLocked.orientedRanges.haveSize) { + info->addMotionRange(AINPUT_MOTION_RANGE_SIZE, + mLocked.orientedRanges.size); + } + + if (mLocked.orientedRanges.haveTouchSize) { + info->addMotionRange(AINPUT_MOTION_RANGE_TOUCH_MAJOR, + mLocked.orientedRanges.touchMajor); + info->addMotionRange(AINPUT_MOTION_RANGE_TOUCH_MINOR, + mLocked.orientedRanges.touchMinor); + } + + if (mLocked.orientedRanges.haveToolSize) { + info->addMotionRange(AINPUT_MOTION_RANGE_TOOL_MAJOR, + mLocked.orientedRanges.toolMajor); + info->addMotionRange(AINPUT_MOTION_RANGE_TOOL_MINOR, + mLocked.orientedRanges.toolMinor); + } + + if (mLocked.orientedRanges.haveOrientation) { + info->addMotionRange(AINPUT_MOTION_RANGE_ORIENTATION, + mLocked.orientedRanges.orientation); + } + } // release lock +} + +void TouchInputMapper::dump(String8& dump) { + { // acquire lock + AutoMutex _l(mLock); + dump.append(INDENT2 "Touch Input Mapper:\n"); + dumpParameters(dump); + dumpVirtualKeysLocked(dump); + dumpRawAxes(dump); + dumpCalibration(dump); + dumpSurfaceLocked(dump); + dump.appendFormat(INDENT3 "Translation and Scaling Factors:\n"); + dump.appendFormat(INDENT4 "XOrigin: %d\n", mLocked.xOrigin); + dump.appendFormat(INDENT4 "YOrigin: %d\n", mLocked.yOrigin); + dump.appendFormat(INDENT4 "XScale: %0.3f\n", mLocked.xScale); + dump.appendFormat(INDENT4 "YScale: %0.3f\n", mLocked.yScale); + dump.appendFormat(INDENT4 "XPrecision: %0.3f\n", mLocked.xPrecision); + dump.appendFormat(INDENT4 "YPrecision: %0.3f\n", mLocked.yPrecision); + dump.appendFormat(INDENT4 "GeometricScale: %0.3f\n", mLocked.geometricScale); + dump.appendFormat(INDENT4 "ToolSizeLinearScale: %0.3f\n", mLocked.toolSizeLinearScale); + dump.appendFormat(INDENT4 "ToolSizeLinearBias: %0.3f\n", mLocked.toolSizeLinearBias); + dump.appendFormat(INDENT4 "ToolSizeAreaScale: %0.3f\n", mLocked.toolSizeAreaScale); + dump.appendFormat(INDENT4 "ToolSizeAreaBias: %0.3f\n", mLocked.toolSizeAreaBias); + dump.appendFormat(INDENT4 "PressureScale: %0.3f\n", mLocked.pressureScale); + dump.appendFormat(INDENT4 "SizeScale: %0.3f\n", mLocked.sizeScale); + dump.appendFormat(INDENT4 "OrientationSCale: %0.3f\n", mLocked.orientationScale); + } // release lock +} + +void TouchInputMapper::initializeLocked() { + mCurrentTouch.clear(); + mLastTouch.clear(); + mDownTime = 0; + + for (uint32_t i = 0; i < MAX_POINTERS; i++) { + mAveragingTouchFilter.historyStart[i] = 0; + mAveragingTouchFilter.historyEnd[i] = 0; + } + + mJumpyTouchFilter.jumpyPointsDropped = 0; + + mLocked.currentVirtualKey.down = false; + + mLocked.orientedRanges.havePressure = false; + mLocked.orientedRanges.haveSize = false; + mLocked.orientedRanges.haveTouchSize = false; + mLocked.orientedRanges.haveToolSize = false; + mLocked.orientedRanges.haveOrientation = false; +} + +void TouchInputMapper::configure() { + InputMapper::configure(); + + // Configure basic parameters. + configureParameters(); + + // Configure sources. + switch (mParameters.deviceType) { + case Parameters::DEVICE_TYPE_TOUCH_SCREEN: + mSources = AINPUT_SOURCE_TOUCHSCREEN; + break; + case Parameters::DEVICE_TYPE_TOUCH_PAD: + mSources = AINPUT_SOURCE_TOUCHPAD; + break; + default: + assert(false); + } + + // Configure absolute axis information. + configureRawAxes(); + + // Prepare input device calibration. + parseCalibration(); + resolveCalibration(); + + { // acquire lock + AutoMutex _l(mLock); + + // Configure surface dimensions and orientation. + configureSurfaceLocked(); + } // release lock +} + +void TouchInputMapper::configureParameters() { + mParameters.useBadTouchFilter = getPolicy()->filterTouchEvents(); + mParameters.useAveragingTouchFilter = getPolicy()->filterTouchEvents(); + mParameters.useJumpyTouchFilter = getPolicy()->filterJumpyTouchEvents(); + mParameters.virtualKeyQuietTime = getPolicy()->getVirtualKeyQuietTime(); + + String8 deviceTypeString; + mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD; + if (getDevice()->getConfiguration().tryGetProperty(String8("touch.deviceType"), + deviceTypeString)) { + if (deviceTypeString == "touchScreen") { + mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_SCREEN; + } else if (deviceTypeString != "touchPad") { + LOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.string()); + } + } + bool isTouchScreen = mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN; + + mParameters.orientationAware = isTouchScreen; + getDevice()->getConfiguration().tryGetProperty(String8("touch.orientationAware"), + mParameters.orientationAware); + + mParameters.associatedDisplayId = mParameters.orientationAware || isTouchScreen ? 0 : -1; +} + +void TouchInputMapper::dumpParameters(String8& dump) { + dump.append(INDENT3 "Parameters:\n"); + + switch (mParameters.deviceType) { + case Parameters::DEVICE_TYPE_TOUCH_SCREEN: + dump.append(INDENT4 "DeviceType: touchScreen\n"); + break; + case Parameters::DEVICE_TYPE_TOUCH_PAD: + dump.append(INDENT4 "DeviceType: touchPad\n"); + break; + default: + assert(false); + } + + dump.appendFormat(INDENT4 "AssociatedDisplayId: %d\n", + mParameters.associatedDisplayId); + dump.appendFormat(INDENT4 "OrientationAware: %s\n", + toString(mParameters.orientationAware)); + + dump.appendFormat(INDENT4 "UseBadTouchFilter: %s\n", + toString(mParameters.useBadTouchFilter)); + dump.appendFormat(INDENT4 "UseAveragingTouchFilter: %s\n", + toString(mParameters.useAveragingTouchFilter)); + dump.appendFormat(INDENT4 "UseJumpyTouchFilter: %s\n", + toString(mParameters.useJumpyTouchFilter)); +} + +void TouchInputMapper::configureRawAxes() { + mRawAxes.x.clear(); + mRawAxes.y.clear(); + mRawAxes.pressure.clear(); + mRawAxes.touchMajor.clear(); + mRawAxes.touchMinor.clear(); + mRawAxes.toolMajor.clear(); + mRawAxes.toolMinor.clear(); + mRawAxes.orientation.clear(); +} + +static void dumpAxisInfo(String8& dump, RawAbsoluteAxisInfo axis, const char* name) { + if (axis.valid) { + dump.appendFormat(INDENT4 "%s: min=%d, max=%d, flat=%d, fuzz=%d\n", + name, axis.minValue, axis.maxValue, axis.flat, axis.fuzz); + } else { + dump.appendFormat(INDENT4 "%s: unknown range\n", name); + } +} + +void TouchInputMapper::dumpRawAxes(String8& dump) { + dump.append(INDENT3 "Raw Axes:\n"); + dumpAxisInfo(dump, mRawAxes.x, "X"); + dumpAxisInfo(dump, mRawAxes.y, "Y"); + dumpAxisInfo(dump, mRawAxes.pressure, "Pressure"); + dumpAxisInfo(dump, mRawAxes.touchMajor, "TouchMajor"); + dumpAxisInfo(dump, mRawAxes.touchMinor, "TouchMinor"); + dumpAxisInfo(dump, mRawAxes.toolMajor, "ToolMajor"); + dumpAxisInfo(dump, mRawAxes.toolMinor, "ToolMinor"); + dumpAxisInfo(dump, mRawAxes.orientation, "Orientation"); +} + +bool TouchInputMapper::configureSurfaceLocked() { + // Update orientation and dimensions if needed. + int32_t orientation = DISPLAY_ORIENTATION_0; + int32_t width = mRawAxes.x.getRange(); + int32_t height = mRawAxes.y.getRange(); + + if (mParameters.associatedDisplayId >= 0) { + bool wantSize = mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN; + bool wantOrientation = mParameters.orientationAware; + + // Note: getDisplayInfo is non-reentrant so we can continue holding the lock. + if (! getPolicy()->getDisplayInfo(mParameters.associatedDisplayId, + wantSize ? &width : NULL, wantSize ? &height : NULL, + wantOrientation ? &orientation : NULL)) { + return false; + } + } + + bool orientationChanged = mLocked.surfaceOrientation != orientation; + if (orientationChanged) { + mLocked.surfaceOrientation = orientation; + } + + bool sizeChanged = mLocked.surfaceWidth != width || mLocked.surfaceHeight != height; + if (sizeChanged) { + LOGI("Device reconfigured: id=%d, name='%s', display size is now %dx%d", + getDeviceId(), getDeviceName().string(), width, height); + + mLocked.surfaceWidth = width; + mLocked.surfaceHeight = height; + + // Configure X and Y factors. + if (mRawAxes.x.valid && mRawAxes.y.valid) { + mLocked.xOrigin = mCalibration.haveXOrigin + ? mCalibration.xOrigin + : mRawAxes.x.minValue; + mLocked.yOrigin = mCalibration.haveYOrigin + ? mCalibration.yOrigin + : mRawAxes.y.minValue; + mLocked.xScale = mCalibration.haveXScale + ? mCalibration.xScale + : float(width) / mRawAxes.x.getRange(); + mLocked.yScale = mCalibration.haveYScale + ? mCalibration.yScale + : float(height) / mRawAxes.y.getRange(); + mLocked.xPrecision = 1.0f / mLocked.xScale; + mLocked.yPrecision = 1.0f / mLocked.yScale; + + configureVirtualKeysLocked(); + } else { + LOGW(INDENT "Touch device did not report support for X or Y axis!"); + mLocked.xOrigin = 0; + mLocked.yOrigin = 0; + mLocked.xScale = 1.0f; + mLocked.yScale = 1.0f; + mLocked.xPrecision = 1.0f; + mLocked.yPrecision = 1.0f; + } + + // Scale factor for terms that are not oriented in a particular axis. + // If the pixels are square then xScale == yScale otherwise we fake it + // by choosing an average. + mLocked.geometricScale = avg(mLocked.xScale, mLocked.yScale); + + // Size of diagonal axis. + float diagonalSize = pythag(width, height); + + // TouchMajor and TouchMinor factors. + if (mCalibration.touchSizeCalibration != Calibration::TOUCH_SIZE_CALIBRATION_NONE) { + mLocked.orientedRanges.haveTouchSize = true; + mLocked.orientedRanges.touchMajor.min = 0; + mLocked.orientedRanges.touchMajor.max = diagonalSize; + mLocked.orientedRanges.touchMajor.flat = 0; + mLocked.orientedRanges.touchMajor.fuzz = 0; + mLocked.orientedRanges.touchMinor = mLocked.orientedRanges.touchMajor; + } + + // ToolMajor and ToolMinor factors. + mLocked.toolSizeLinearScale = 0; + mLocked.toolSizeLinearBias = 0; + mLocked.toolSizeAreaScale = 0; + mLocked.toolSizeAreaBias = 0; + if (mCalibration.toolSizeCalibration != Calibration::TOOL_SIZE_CALIBRATION_NONE) { + if (mCalibration.toolSizeCalibration == Calibration::TOOL_SIZE_CALIBRATION_LINEAR) { + if (mCalibration.haveToolSizeLinearScale) { + mLocked.toolSizeLinearScale = mCalibration.toolSizeLinearScale; + } else if (mRawAxes.toolMajor.valid && mRawAxes.toolMajor.maxValue != 0) { + mLocked.toolSizeLinearScale = float(min(width, height)) + / mRawAxes.toolMajor.maxValue; + } + + if (mCalibration.haveToolSizeLinearBias) { + mLocked.toolSizeLinearBias = mCalibration.toolSizeLinearBias; + } + } else if (mCalibration.toolSizeCalibration == + Calibration::TOOL_SIZE_CALIBRATION_AREA) { + if (mCalibration.haveToolSizeLinearScale) { + mLocked.toolSizeLinearScale = mCalibration.toolSizeLinearScale; + } else { + mLocked.toolSizeLinearScale = min(width, height); + } + + if (mCalibration.haveToolSizeLinearBias) { + mLocked.toolSizeLinearBias = mCalibration.toolSizeLinearBias; + } + + if (mCalibration.haveToolSizeAreaScale) { + mLocked.toolSizeAreaScale = mCalibration.toolSizeAreaScale; + } else if (mRawAxes.toolMajor.valid && mRawAxes.toolMajor.maxValue != 0) { + mLocked.toolSizeAreaScale = 1.0f / mRawAxes.toolMajor.maxValue; + } + + if (mCalibration.haveToolSizeAreaBias) { + mLocked.toolSizeAreaBias = mCalibration.toolSizeAreaBias; + } + } + + mLocked.orientedRanges.haveToolSize = true; + mLocked.orientedRanges.toolMajor.min = 0; + mLocked.orientedRanges.toolMajor.max = diagonalSize; + mLocked.orientedRanges.toolMajor.flat = 0; + mLocked.orientedRanges.toolMajor.fuzz = 0; + mLocked.orientedRanges.toolMinor = mLocked.orientedRanges.toolMajor; + } + + // Pressure factors. + mLocked.pressureScale = 0; + if (mCalibration.pressureCalibration != Calibration::PRESSURE_CALIBRATION_NONE) { + RawAbsoluteAxisInfo rawPressureAxis; + switch (mCalibration.pressureSource) { + case Calibration::PRESSURE_SOURCE_PRESSURE: + rawPressureAxis = mRawAxes.pressure; + break; + case Calibration::PRESSURE_SOURCE_TOUCH: + rawPressureAxis = mRawAxes.touchMajor; + break; + default: + rawPressureAxis.clear(); + } + + if (mCalibration.pressureCalibration == Calibration::PRESSURE_CALIBRATION_PHYSICAL + || mCalibration.pressureCalibration + == Calibration::PRESSURE_CALIBRATION_AMPLITUDE) { + if (mCalibration.havePressureScale) { + mLocked.pressureScale = mCalibration.pressureScale; + } else if (rawPressureAxis.valid && rawPressureAxis.maxValue != 0) { + mLocked.pressureScale = 1.0f / rawPressureAxis.maxValue; + } + } + + mLocked.orientedRanges.havePressure = true; + mLocked.orientedRanges.pressure.min = 0; + mLocked.orientedRanges.pressure.max = 1.0; + mLocked.orientedRanges.pressure.flat = 0; + mLocked.orientedRanges.pressure.fuzz = 0; + } + + // Size factors. + mLocked.sizeScale = 0; + if (mCalibration.sizeCalibration != Calibration::SIZE_CALIBRATION_NONE) { + if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_NORMALIZED) { + if (mRawAxes.toolMajor.valid && mRawAxes.toolMajor.maxValue != 0) { + mLocked.sizeScale = 1.0f / mRawAxes.toolMajor.maxValue; + } + } + + mLocked.orientedRanges.haveSize = true; + mLocked.orientedRanges.size.min = 0; + mLocked.orientedRanges.size.max = 1.0; + mLocked.orientedRanges.size.flat = 0; + mLocked.orientedRanges.size.fuzz = 0; + } + + // Orientation + mLocked.orientationScale = 0; + if (mCalibration.orientationCalibration != Calibration::ORIENTATION_CALIBRATION_NONE) { + if (mCalibration.orientationCalibration + == Calibration::ORIENTATION_CALIBRATION_INTERPOLATED) { + if (mRawAxes.orientation.valid && mRawAxes.orientation.maxValue != 0) { + mLocked.orientationScale = float(M_PI_2) / mRawAxes.orientation.maxValue; + } + } + + mLocked.orientedRanges.orientation.min = - M_PI_2; + mLocked.orientedRanges.orientation.max = M_PI_2; + mLocked.orientedRanges.orientation.flat = 0; + mLocked.orientedRanges.orientation.fuzz = 0; + } + } + + if (orientationChanged || sizeChanged) { + // Compute oriented surface dimensions, precision, and scales. + float orientedXScale, orientedYScale; + switch (mLocked.surfaceOrientation) { + case DISPLAY_ORIENTATION_90: + case DISPLAY_ORIENTATION_270: + mLocked.orientedSurfaceWidth = mLocked.surfaceHeight; + mLocked.orientedSurfaceHeight = mLocked.surfaceWidth; + mLocked.orientedXPrecision = mLocked.yPrecision; + mLocked.orientedYPrecision = mLocked.xPrecision; + orientedXScale = mLocked.yScale; + orientedYScale = mLocked.xScale; + break; + default: + mLocked.orientedSurfaceWidth = mLocked.surfaceWidth; + mLocked.orientedSurfaceHeight = mLocked.surfaceHeight; + mLocked.orientedXPrecision = mLocked.xPrecision; + mLocked.orientedYPrecision = mLocked.yPrecision; + orientedXScale = mLocked.xScale; + orientedYScale = mLocked.yScale; + break; + } + + // Configure position ranges. + mLocked.orientedRanges.x.min = 0; + mLocked.orientedRanges.x.max = mLocked.orientedSurfaceWidth; + mLocked.orientedRanges.x.flat = 0; + mLocked.orientedRanges.x.fuzz = orientedXScale; + + mLocked.orientedRanges.y.min = 0; + mLocked.orientedRanges.y.max = mLocked.orientedSurfaceHeight; + mLocked.orientedRanges.y.flat = 0; + mLocked.orientedRanges.y.fuzz = orientedYScale; + } + + return true; +} + +void TouchInputMapper::dumpSurfaceLocked(String8& dump) { + dump.appendFormat(INDENT3 "SurfaceWidth: %dpx\n", mLocked.surfaceWidth); + dump.appendFormat(INDENT3 "SurfaceHeight: %dpx\n", mLocked.surfaceHeight); + dump.appendFormat(INDENT3 "SurfaceOrientation: %d\n", mLocked.surfaceOrientation); +} + +void TouchInputMapper::configureVirtualKeysLocked() { + assert(mRawAxes.x.valid && mRawAxes.y.valid); + + Vector<VirtualKeyDefinition> virtualKeyDefinitions; + getEventHub()->getVirtualKeyDefinitions(getDeviceId(), virtualKeyDefinitions); + + mLocked.virtualKeys.clear(); + + if (virtualKeyDefinitions.size() == 0) { + return; + } + + mLocked.virtualKeys.setCapacity(virtualKeyDefinitions.size()); + + int32_t touchScreenLeft = mRawAxes.x.minValue; + int32_t touchScreenTop = mRawAxes.y.minValue; + int32_t touchScreenWidth = mRawAxes.x.getRange(); + int32_t touchScreenHeight = mRawAxes.y.getRange(); + + for (size_t i = 0; i < virtualKeyDefinitions.size(); i++) { + const VirtualKeyDefinition& virtualKeyDefinition = + virtualKeyDefinitions[i]; + + mLocked.virtualKeys.add(); + VirtualKey& virtualKey = mLocked.virtualKeys.editTop(); + + virtualKey.scanCode = virtualKeyDefinition.scanCode; + int32_t keyCode; + uint32_t flags; + if (getEventHub()->scancodeToKeycode(getDeviceId(), virtualKey.scanCode, + & keyCode, & flags)) { + LOGW(INDENT "VirtualKey %d: could not obtain key code, ignoring", + virtualKey.scanCode); + mLocked.virtualKeys.pop(); // drop the key + continue; + } + + virtualKey.keyCode = keyCode; + virtualKey.flags = flags; + + // convert the key definition's display coordinates into touch coordinates for a hit box + int32_t halfWidth = virtualKeyDefinition.width / 2; + int32_t halfHeight = virtualKeyDefinition.height / 2; + + virtualKey.hitLeft = (virtualKeyDefinition.centerX - halfWidth) + * touchScreenWidth / mLocked.surfaceWidth + touchScreenLeft; + virtualKey.hitRight= (virtualKeyDefinition.centerX + halfWidth) + * touchScreenWidth / mLocked.surfaceWidth + touchScreenLeft; + virtualKey.hitTop = (virtualKeyDefinition.centerY - halfHeight) + * touchScreenHeight / mLocked.surfaceHeight + touchScreenTop; + virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight) + * touchScreenHeight / mLocked.surfaceHeight + touchScreenTop; + + } +} + +void TouchInputMapper::dumpVirtualKeysLocked(String8& dump) { + if (!mLocked.virtualKeys.isEmpty()) { + dump.append(INDENT3 "Virtual Keys:\n"); + + for (size_t i = 0; i < mLocked.virtualKeys.size(); i++) { + const VirtualKey& virtualKey = mLocked.virtualKeys.itemAt(i); + dump.appendFormat(INDENT4 "%d: scanCode=%d, keyCode=%d, " + "hitLeft=%d, hitRight=%d, hitTop=%d, hitBottom=%d\n", + i, virtualKey.scanCode, virtualKey.keyCode, + virtualKey.hitLeft, virtualKey.hitRight, + virtualKey.hitTop, virtualKey.hitBottom); + } + } +} + +void TouchInputMapper::parseCalibration() { + const PropertyMap& in = getDevice()->getConfiguration(); + Calibration& out = mCalibration; + + // Position + out.haveXOrigin = in.tryGetProperty(String8("touch.position.xOrigin"), out.xOrigin); + out.haveYOrigin = in.tryGetProperty(String8("touch.position.yOrigin"), out.yOrigin); + out.haveXScale = in.tryGetProperty(String8("touch.position.xScale"), out.xScale); + out.haveYScale = in.tryGetProperty(String8("touch.position.yScale"), out.yScale); + + // Touch Size + out.touchSizeCalibration = Calibration::TOUCH_SIZE_CALIBRATION_DEFAULT; + String8 touchSizeCalibrationString; + if (in.tryGetProperty(String8("touch.touchSize.calibration"), touchSizeCalibrationString)) { + if (touchSizeCalibrationString == "none") { + out.touchSizeCalibration = Calibration::TOUCH_SIZE_CALIBRATION_NONE; + } else if (touchSizeCalibrationString == "geometric") { + out.touchSizeCalibration = Calibration::TOUCH_SIZE_CALIBRATION_GEOMETRIC; + } else if (touchSizeCalibrationString == "pressure") { + out.touchSizeCalibration = Calibration::TOUCH_SIZE_CALIBRATION_PRESSURE; + } else if (touchSizeCalibrationString != "default") { + LOGW("Invalid value for touch.touchSize.calibration: '%s'", + touchSizeCalibrationString.string()); + } + } + + // Tool Size + out.toolSizeCalibration = Calibration::TOOL_SIZE_CALIBRATION_DEFAULT; + String8 toolSizeCalibrationString; + if (in.tryGetProperty(String8("touch.toolSize.calibration"), toolSizeCalibrationString)) { + if (toolSizeCalibrationString == "none") { + out.toolSizeCalibration = Calibration::TOOL_SIZE_CALIBRATION_NONE; + } else if (toolSizeCalibrationString == "geometric") { + out.toolSizeCalibration = Calibration::TOOL_SIZE_CALIBRATION_GEOMETRIC; + } else if (toolSizeCalibrationString == "linear") { + out.toolSizeCalibration = Calibration::TOOL_SIZE_CALIBRATION_LINEAR; + } else if (toolSizeCalibrationString == "area") { + out.toolSizeCalibration = Calibration::TOOL_SIZE_CALIBRATION_AREA; + } else if (toolSizeCalibrationString != "default") { + LOGW("Invalid value for touch.toolSize.calibration: '%s'", + toolSizeCalibrationString.string()); + } + } + + out.haveToolSizeLinearScale = in.tryGetProperty(String8("touch.toolSize.linearScale"), + out.toolSizeLinearScale); + out.haveToolSizeLinearBias = in.tryGetProperty(String8("touch.toolSize.linearBias"), + out.toolSizeLinearBias); + out.haveToolSizeAreaScale = in.tryGetProperty(String8("touch.toolSize.areaScale"), + out.toolSizeAreaScale); + out.haveToolSizeAreaBias = in.tryGetProperty(String8("touch.toolSize.areaBias"), + out.toolSizeAreaBias); + out.haveToolSizeIsSummed = in.tryGetProperty(String8("touch.toolSize.isSummed"), + out.toolSizeIsSummed); + + // Pressure + out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_DEFAULT; + String8 pressureCalibrationString; + if (in.tryGetProperty(String8("touch.pressure.calibration"), pressureCalibrationString)) { + if (pressureCalibrationString == "none") { + out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_NONE; + } else if (pressureCalibrationString == "physical") { + out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_PHYSICAL; + } else if (pressureCalibrationString == "amplitude") { + out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_AMPLITUDE; + } else if (pressureCalibrationString != "default") { + LOGW("Invalid value for touch.pressure.calibration: '%s'", + pressureCalibrationString.string()); + } + } + + out.pressureSource = Calibration::PRESSURE_SOURCE_DEFAULT; + String8 pressureSourceString; + if (in.tryGetProperty(String8("touch.pressure.source"), pressureSourceString)) { + if (pressureSourceString == "pressure") { + out.pressureSource = Calibration::PRESSURE_SOURCE_PRESSURE; + } else if (pressureSourceString == "touch") { + out.pressureSource = Calibration::PRESSURE_SOURCE_TOUCH; + } else if (pressureSourceString != "default") { + LOGW("Invalid value for touch.pressure.source: '%s'", + pressureSourceString.string()); + } + } + + out.havePressureScale = in.tryGetProperty(String8("touch.pressure.scale"), + out.pressureScale); + + // Size + out.sizeCalibration = Calibration::SIZE_CALIBRATION_DEFAULT; + String8 sizeCalibrationString; + if (in.tryGetProperty(String8("touch.size.calibration"), sizeCalibrationString)) { + if (sizeCalibrationString == "none") { + out.sizeCalibration = Calibration::SIZE_CALIBRATION_NONE; + } else if (sizeCalibrationString == "normalized") { + out.sizeCalibration = Calibration::SIZE_CALIBRATION_NORMALIZED; + } else if (sizeCalibrationString != "default") { + LOGW("Invalid value for touch.size.calibration: '%s'", + sizeCalibrationString.string()); + } + } + + // Orientation + out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_DEFAULT; + String8 orientationCalibrationString; + if (in.tryGetProperty(String8("touch.orientation.calibration"), orientationCalibrationString)) { + if (orientationCalibrationString == "none") { + out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_NONE; + } else if (orientationCalibrationString == "interpolated") { + out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_INTERPOLATED; + } else if (orientationCalibrationString == "vector") { + out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_VECTOR; + } else if (orientationCalibrationString != "default") { + LOGW("Invalid value for touch.orientation.calibration: '%s'", + orientationCalibrationString.string()); + } + } +} + +void TouchInputMapper::resolveCalibration() { + // Pressure + switch (mCalibration.pressureSource) { + case Calibration::PRESSURE_SOURCE_DEFAULT: + if (mRawAxes.pressure.valid) { + mCalibration.pressureSource = Calibration::PRESSURE_SOURCE_PRESSURE; + } else if (mRawAxes.touchMajor.valid) { + mCalibration.pressureSource = Calibration::PRESSURE_SOURCE_TOUCH; + } + break; + + case Calibration::PRESSURE_SOURCE_PRESSURE: + if (! mRawAxes.pressure.valid) { + LOGW("Calibration property touch.pressure.source is 'pressure' but " + "the pressure axis is not available."); + } + break; + + case Calibration::PRESSURE_SOURCE_TOUCH: + if (! mRawAxes.touchMajor.valid) { + LOGW("Calibration property touch.pressure.source is 'touch' but " + "the touchMajor axis is not available."); + } + break; + + default: + break; + } + + switch (mCalibration.pressureCalibration) { + case Calibration::PRESSURE_CALIBRATION_DEFAULT: + if (mCalibration.pressureSource != Calibration::PRESSURE_SOURCE_DEFAULT) { + mCalibration.pressureCalibration = Calibration::PRESSURE_CALIBRATION_AMPLITUDE; + } else { + mCalibration.pressureCalibration = Calibration::PRESSURE_CALIBRATION_NONE; + } + break; + + default: + break; + } + + // Tool Size + switch (mCalibration.toolSizeCalibration) { + case Calibration::TOOL_SIZE_CALIBRATION_DEFAULT: + if (mRawAxes.toolMajor.valid) { + mCalibration.toolSizeCalibration = Calibration::TOOL_SIZE_CALIBRATION_LINEAR; + } else { + mCalibration.toolSizeCalibration = Calibration::TOOL_SIZE_CALIBRATION_NONE; + } + break; + + default: + break; + } + + // Touch Size + switch (mCalibration.touchSizeCalibration) { + case Calibration::TOUCH_SIZE_CALIBRATION_DEFAULT: + if (mCalibration.pressureCalibration != Calibration::PRESSURE_CALIBRATION_NONE + && mCalibration.toolSizeCalibration != Calibration::TOOL_SIZE_CALIBRATION_NONE) { + mCalibration.touchSizeCalibration = Calibration::TOUCH_SIZE_CALIBRATION_PRESSURE; + } else { + mCalibration.touchSizeCalibration = Calibration::TOUCH_SIZE_CALIBRATION_NONE; + } + break; + + default: + break; + } + + // Size + switch (mCalibration.sizeCalibration) { + case Calibration::SIZE_CALIBRATION_DEFAULT: + if (mRawAxes.toolMajor.valid) { + mCalibration.sizeCalibration = Calibration::SIZE_CALIBRATION_NORMALIZED; + } else { + mCalibration.sizeCalibration = Calibration::SIZE_CALIBRATION_NONE; + } + break; + + default: + break; + } + + // Orientation + switch (mCalibration.orientationCalibration) { + case Calibration::ORIENTATION_CALIBRATION_DEFAULT: + if (mRawAxes.orientation.valid) { + mCalibration.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_INTERPOLATED; + } else { + mCalibration.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_NONE; + } + break; + + default: + break; + } +} + +void TouchInputMapper::dumpCalibration(String8& dump) { + dump.append(INDENT3 "Calibration:\n"); + + // Position + if (mCalibration.haveXOrigin) { + dump.appendFormat(INDENT4 "touch.position.xOrigin: %d\n", mCalibration.xOrigin); + } + if (mCalibration.haveYOrigin) { + dump.appendFormat(INDENT4 "touch.position.yOrigin: %d\n", mCalibration.yOrigin); + } + if (mCalibration.haveXScale) { + dump.appendFormat(INDENT4 "touch.position.xScale: %0.3f\n", mCalibration.xScale); + } + if (mCalibration.haveYScale) { + dump.appendFormat(INDENT4 "touch.position.yScale: %0.3f\n", mCalibration.yScale); + } + + // Touch Size + switch (mCalibration.touchSizeCalibration) { + case Calibration::TOUCH_SIZE_CALIBRATION_NONE: + dump.append(INDENT4 "touch.touchSize.calibration: none\n"); + break; + case Calibration::TOUCH_SIZE_CALIBRATION_GEOMETRIC: + dump.append(INDENT4 "touch.touchSize.calibration: geometric\n"); + break; + case Calibration::TOUCH_SIZE_CALIBRATION_PRESSURE: + dump.append(INDENT4 "touch.touchSize.calibration: pressure\n"); + break; + default: + assert(false); + } + + // Tool Size + switch (mCalibration.toolSizeCalibration) { + case Calibration::TOOL_SIZE_CALIBRATION_NONE: + dump.append(INDENT4 "touch.toolSize.calibration: none\n"); + break; + case Calibration::TOOL_SIZE_CALIBRATION_GEOMETRIC: + dump.append(INDENT4 "touch.toolSize.calibration: geometric\n"); + break; + case Calibration::TOOL_SIZE_CALIBRATION_LINEAR: + dump.append(INDENT4 "touch.toolSize.calibration: linear\n"); + break; + case Calibration::TOOL_SIZE_CALIBRATION_AREA: + dump.append(INDENT4 "touch.toolSize.calibration: area\n"); + break; + default: + assert(false); + } + + if (mCalibration.haveToolSizeLinearScale) { + dump.appendFormat(INDENT4 "touch.toolSize.linearScale: %0.3f\n", + mCalibration.toolSizeLinearScale); + } + + if (mCalibration.haveToolSizeLinearBias) { + dump.appendFormat(INDENT4 "touch.toolSize.linearBias: %0.3f\n", + mCalibration.toolSizeLinearBias); + } + + if (mCalibration.haveToolSizeAreaScale) { + dump.appendFormat(INDENT4 "touch.toolSize.areaScale: %0.3f\n", + mCalibration.toolSizeAreaScale); + } + + if (mCalibration.haveToolSizeAreaBias) { + dump.appendFormat(INDENT4 "touch.toolSize.areaBias: %0.3f\n", + mCalibration.toolSizeAreaBias); + } + + if (mCalibration.haveToolSizeIsSummed) { + dump.appendFormat(INDENT4 "touch.toolSize.isSummed: %s\n", + toString(mCalibration.toolSizeIsSummed)); + } + + // Pressure + switch (mCalibration.pressureCalibration) { + case Calibration::PRESSURE_CALIBRATION_NONE: + dump.append(INDENT4 "touch.pressure.calibration: none\n"); + break; + case Calibration::PRESSURE_CALIBRATION_PHYSICAL: + dump.append(INDENT4 "touch.pressure.calibration: physical\n"); + break; + case Calibration::PRESSURE_CALIBRATION_AMPLITUDE: + dump.append(INDENT4 "touch.pressure.calibration: amplitude\n"); + break; + default: + assert(false); + } + + switch (mCalibration.pressureSource) { + case Calibration::PRESSURE_SOURCE_PRESSURE: + dump.append(INDENT4 "touch.pressure.source: pressure\n"); + break; + case Calibration::PRESSURE_SOURCE_TOUCH: + dump.append(INDENT4 "touch.pressure.source: touch\n"); + break; + case Calibration::PRESSURE_SOURCE_DEFAULT: + break; + default: + assert(false); + } + + if (mCalibration.havePressureScale) { + dump.appendFormat(INDENT4 "touch.pressure.scale: %0.3f\n", + mCalibration.pressureScale); + } + + // Size + switch (mCalibration.sizeCalibration) { + case Calibration::SIZE_CALIBRATION_NONE: + dump.append(INDENT4 "touch.size.calibration: none\n"); + break; + case Calibration::SIZE_CALIBRATION_NORMALIZED: + dump.append(INDENT4 "touch.size.calibration: normalized\n"); + break; + default: + assert(false); + } + + // Orientation + switch (mCalibration.orientationCalibration) { + case Calibration::ORIENTATION_CALIBRATION_NONE: + dump.append(INDENT4 "touch.orientation.calibration: none\n"); + break; + case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED: + dump.append(INDENT4 "touch.orientation.calibration: interpolated\n"); + break; + case Calibration::ORIENTATION_CALIBRATION_VECTOR: + dump.append(INDENT4 "touch.orientation.calibration: vector\n"); + break; + default: + assert(false); + } +} + +void TouchInputMapper::reset() { + // Synthesize touch up event if touch is currently down. + // This will also take care of finishing virtual key processing if needed. + if (mLastTouch.pointerCount != 0) { + nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC); + mCurrentTouch.clear(); + syncTouch(when, true); + } + + { // acquire lock + AutoMutex _l(mLock); + initializeLocked(); + } // release lock + + InputMapper::reset(); +} + +void TouchInputMapper::syncTouch(nsecs_t when, bool havePointerIds) { + uint32_t policyFlags = 0; + + // Preprocess pointer data. + + if (mParameters.useBadTouchFilter) { + if (applyBadTouchFilter()) { + havePointerIds = false; + } + } + + if (mParameters.useJumpyTouchFilter) { + if (applyJumpyTouchFilter()) { + havePointerIds = false; + } + } + + if (! havePointerIds) { + calculatePointerIds(); + } + + TouchData temp; + TouchData* savedTouch; + if (mParameters.useAveragingTouchFilter) { + temp.copyFrom(mCurrentTouch); + savedTouch = & temp; + + applyAveragingTouchFilter(); + } else { + savedTouch = & mCurrentTouch; + } + + // Process touches and virtual keys. + + TouchResult touchResult = consumeOffScreenTouches(when, policyFlags); + if (touchResult == DISPATCH_TOUCH) { + detectGestures(when); + dispatchTouches(when, policyFlags); + } + + // Copy current touch to last touch in preparation for the next cycle. + + if (touchResult == DROP_STROKE) { + mLastTouch.clear(); + } else { + mLastTouch.copyFrom(*savedTouch); + } +} + +TouchInputMapper::TouchResult TouchInputMapper::consumeOffScreenTouches( + nsecs_t when, uint32_t policyFlags) { + int32_t keyEventAction, keyEventFlags; + int32_t keyCode, scanCode, downTime; + TouchResult touchResult; + + { // acquire lock + AutoMutex _l(mLock); + + // Update surface size and orientation, including virtual key positions. + if (! configureSurfaceLocked()) { + return DROP_STROKE; + } + + // Check for virtual key press. + if (mLocked.currentVirtualKey.down) { + if (mCurrentTouch.pointerCount == 0) { + // Pointer went up while virtual key was down. + mLocked.currentVirtualKey.down = false; +#if DEBUG_VIRTUAL_KEYS + LOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d", + mLocked.currentVirtualKey.keyCode, mLocked.currentVirtualKey.scanCode); +#endif + keyEventAction = AKEY_EVENT_ACTION_UP; + keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY; + touchResult = SKIP_TOUCH; + goto DispatchVirtualKey; + } + + if (mCurrentTouch.pointerCount == 1) { + int32_t x = mCurrentTouch.pointers[0].x; + int32_t y = mCurrentTouch.pointers[0].y; + const VirtualKey* virtualKey = findVirtualKeyHitLocked(x, y); + if (virtualKey && virtualKey->keyCode == mLocked.currentVirtualKey.keyCode) { + // Pointer is still within the space of the virtual key. + return SKIP_TOUCH; + } + } + + // Pointer left virtual key area or another pointer also went down. + // Send key cancellation and drop the stroke so subsequent motions will be + // considered fresh downs. This is useful when the user swipes away from the + // virtual key area into the main display surface. + mLocked.currentVirtualKey.down = false; +#if DEBUG_VIRTUAL_KEYS + LOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d", + mLocked.currentVirtualKey.keyCode, mLocked.currentVirtualKey.scanCode); +#endif + keyEventAction = AKEY_EVENT_ACTION_UP; + keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY + | AKEY_EVENT_FLAG_CANCELED; + + // Check whether the pointer moved inside the display area where we should + // start a new stroke. + int32_t x = mCurrentTouch.pointers[0].x; + int32_t y = mCurrentTouch.pointers[0].y; + if (isPointInsideSurfaceLocked(x, y)) { + mLastTouch.clear(); + touchResult = DISPATCH_TOUCH; + } else { + touchResult = DROP_STROKE; + } + } else { + if (mCurrentTouch.pointerCount >= 1 && mLastTouch.pointerCount == 0) { + // Pointer just went down. Handle off-screen touches, if needed. + int32_t x = mCurrentTouch.pointers[0].x; + int32_t y = mCurrentTouch.pointers[0].y; + if (! isPointInsideSurfaceLocked(x, y)) { + // If exactly one pointer went down, check for virtual key hit. + // Otherwise we will drop the entire stroke. + if (mCurrentTouch.pointerCount == 1) { + const VirtualKey* virtualKey = findVirtualKeyHitLocked(x, y); + if (virtualKey) { + if (mContext->shouldDropVirtualKey(when, getDevice(), + virtualKey->keyCode, virtualKey->scanCode)) { + return DROP_STROKE; + } + + mLocked.currentVirtualKey.down = true; + mLocked.currentVirtualKey.downTime = when; + mLocked.currentVirtualKey.keyCode = virtualKey->keyCode; + mLocked.currentVirtualKey.scanCode = virtualKey->scanCode; +#if DEBUG_VIRTUAL_KEYS + LOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d", + mLocked.currentVirtualKey.keyCode, + mLocked.currentVirtualKey.scanCode); +#endif + keyEventAction = AKEY_EVENT_ACTION_DOWN; + keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM + | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY; + touchResult = SKIP_TOUCH; + goto DispatchVirtualKey; + } + } + return DROP_STROKE; + } + } + return DISPATCH_TOUCH; + } + + DispatchVirtualKey: + // Collect remaining state needed to dispatch virtual key. + keyCode = mLocked.currentVirtualKey.keyCode; + scanCode = mLocked.currentVirtualKey.scanCode; + downTime = mLocked.currentVirtualKey.downTime; + } // release lock + + // Dispatch virtual key. + int32_t metaState = mContext->getGlobalMetaState(); + policyFlags |= POLICY_FLAG_VIRTUAL; + getDispatcher()->notifyKey(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, policyFlags, + keyEventAction, keyEventFlags, keyCode, scanCode, metaState, downTime); + return touchResult; +} + +void TouchInputMapper::detectGestures(nsecs_t when) { + // Disable all virtual key touches that happen within a short time interval of the + // most recent touch. The idea is to filter out stray virtual key presses when + // interacting with the touch screen. + // + // Problems we're trying to solve: + // + // 1. While scrolling a list or dragging the window shade, the user swipes down into a + // virtual key area that is implemented by a separate touch panel and accidentally + // triggers a virtual key. + // + // 2. While typing in the on screen keyboard, the user taps slightly outside the screen + // area and accidentally triggers a virtual key. This often happens when virtual keys + // are layed out below the screen near to where the on screen keyboard's space bar + // is displayed. + if (mParameters.virtualKeyQuietTime > 0 && mCurrentTouch.pointerCount != 0) { + mContext->disableVirtualKeysUntil(when + mParameters.virtualKeyQuietTime); + } +} + +void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) { + uint32_t currentPointerCount = mCurrentTouch.pointerCount; + uint32_t lastPointerCount = mLastTouch.pointerCount; + if (currentPointerCount == 0 && lastPointerCount == 0) { + return; // nothing to do! + } + + BitSet32 currentIdBits = mCurrentTouch.idBits; + BitSet32 lastIdBits = mLastTouch.idBits; + + if (currentIdBits == lastIdBits) { + // No pointer id changes so this is a move event. + // The dispatcher takes care of batching moves so we don't have to deal with that here. + int32_t motionEventAction = AMOTION_EVENT_ACTION_MOVE; + dispatchTouch(when, policyFlags, & mCurrentTouch, + currentIdBits, -1, currentPointerCount, motionEventAction); + } else { + // There may be pointers going up and pointers going down and pointers moving + // all at the same time. + BitSet32 upIdBits(lastIdBits.value & ~ currentIdBits.value); + BitSet32 downIdBits(currentIdBits.value & ~ lastIdBits.value); + BitSet32 activeIdBits(lastIdBits.value); + uint32_t pointerCount = lastPointerCount; + + // Produce an intermediate representation of the touch data that consists of the + // old location of pointers that have just gone up and the new location of pointers that + // have just moved but omits the location of pointers that have just gone down. + TouchData interimTouch; + interimTouch.copyFrom(mLastTouch); + + BitSet32 moveIdBits(lastIdBits.value & currentIdBits.value); + bool moveNeeded = false; + while (!moveIdBits.isEmpty()) { + uint32_t moveId = moveIdBits.firstMarkedBit(); + moveIdBits.clearBit(moveId); + + int32_t oldIndex = mLastTouch.idToIndex[moveId]; + int32_t newIndex = mCurrentTouch.idToIndex[moveId]; + if (mLastTouch.pointers[oldIndex] != mCurrentTouch.pointers[newIndex]) { + interimTouch.pointers[oldIndex] = mCurrentTouch.pointers[newIndex]; + moveNeeded = true; + } + } + + // Dispatch pointer up events using the interim pointer locations. + while (!upIdBits.isEmpty()) { + uint32_t upId = upIdBits.firstMarkedBit(); + upIdBits.clearBit(upId); + BitSet32 oldActiveIdBits = activeIdBits; + activeIdBits.clearBit(upId); + + int32_t motionEventAction; + if (activeIdBits.isEmpty()) { + motionEventAction = AMOTION_EVENT_ACTION_UP; + } else { + motionEventAction = AMOTION_EVENT_ACTION_POINTER_UP; + } + + dispatchTouch(when, policyFlags, &interimTouch, + oldActiveIdBits, upId, pointerCount, motionEventAction); + pointerCount -= 1; + } + + // Dispatch move events if any of the remaining pointers moved from their old locations. + // Although applications receive new locations as part of individual pointer up + // events, they do not generally handle them except when presented in a move event. + if (moveNeeded) { + dispatchTouch(when, policyFlags, &mCurrentTouch, + activeIdBits, -1, pointerCount, AMOTION_EVENT_ACTION_MOVE); + } + + // Dispatch pointer down events using the new pointer locations. + while (!downIdBits.isEmpty()) { + uint32_t downId = downIdBits.firstMarkedBit(); + downIdBits.clearBit(downId); + BitSet32 oldActiveIdBits = activeIdBits; + activeIdBits.markBit(downId); + + int32_t motionEventAction; + if (oldActiveIdBits.isEmpty()) { + motionEventAction = AMOTION_EVENT_ACTION_DOWN; + mDownTime = when; + } else { + motionEventAction = AMOTION_EVENT_ACTION_POINTER_DOWN; + } + + pointerCount += 1; + dispatchTouch(when, policyFlags, &mCurrentTouch, + activeIdBits, downId, pointerCount, motionEventAction); + } + } +} + +void TouchInputMapper::dispatchTouch(nsecs_t when, uint32_t policyFlags, + TouchData* touch, BitSet32 idBits, uint32_t changedId, uint32_t pointerCount, + int32_t motionEventAction) { + int32_t pointerIds[MAX_POINTERS]; + PointerCoords pointerCoords[MAX_POINTERS]; + int32_t motionEventEdgeFlags = 0; + float xPrecision, yPrecision; + + { // acquire lock + AutoMutex _l(mLock); + + // Walk through the the active pointers and map touch screen coordinates (TouchData) into + // display coordinates (PointerCoords) and adjust for display orientation. + for (uint32_t outIndex = 0; ! idBits.isEmpty(); outIndex++) { + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); + uint32_t inIndex = touch->idToIndex[id]; + + const PointerData& in = touch->pointers[inIndex]; + + // X and Y + float x = float(in.x - mLocked.xOrigin) * mLocked.xScale; + float y = float(in.y - mLocked.yOrigin) * mLocked.yScale; + + // ToolMajor and ToolMinor + float toolMajor, toolMinor; + switch (mCalibration.toolSizeCalibration) { + case Calibration::TOOL_SIZE_CALIBRATION_GEOMETRIC: + toolMajor = in.toolMajor * mLocked.geometricScale; + if (mRawAxes.toolMinor.valid) { + toolMinor = in.toolMinor * mLocked.geometricScale; + } else { + toolMinor = toolMajor; + } + break; + case Calibration::TOOL_SIZE_CALIBRATION_LINEAR: + toolMajor = in.toolMajor != 0 + ? in.toolMajor * mLocked.toolSizeLinearScale + mLocked.toolSizeLinearBias + : 0; + if (mRawAxes.toolMinor.valid) { + toolMinor = in.toolMinor != 0 + ? in.toolMinor * mLocked.toolSizeLinearScale + + mLocked.toolSizeLinearBias + : 0; + } else { + toolMinor = toolMajor; + } + break; + case Calibration::TOOL_SIZE_CALIBRATION_AREA: + if (in.toolMajor != 0) { + float diameter = sqrtf(in.toolMajor + * mLocked.toolSizeAreaScale + mLocked.toolSizeAreaBias); + toolMajor = diameter * mLocked.toolSizeLinearScale + mLocked.toolSizeLinearBias; + } else { + toolMajor = 0; + } + toolMinor = toolMajor; + break; + default: + toolMajor = 0; + toolMinor = 0; + break; + } + + if (mCalibration.haveToolSizeIsSummed && mCalibration.toolSizeIsSummed) { + toolMajor /= pointerCount; + toolMinor /= pointerCount; + } + + // Pressure + float rawPressure; + switch (mCalibration.pressureSource) { + case Calibration::PRESSURE_SOURCE_PRESSURE: + rawPressure = in.pressure; + break; + case Calibration::PRESSURE_SOURCE_TOUCH: + rawPressure = in.touchMajor; + break; + default: + rawPressure = 0; + } + + float pressure; + switch (mCalibration.pressureCalibration) { + case Calibration::PRESSURE_CALIBRATION_PHYSICAL: + case Calibration::PRESSURE_CALIBRATION_AMPLITUDE: + pressure = rawPressure * mLocked.pressureScale; + break; + default: + pressure = 1; + break; + } + + // TouchMajor and TouchMinor + float touchMajor, touchMinor; + switch (mCalibration.touchSizeCalibration) { + case Calibration::TOUCH_SIZE_CALIBRATION_GEOMETRIC: + touchMajor = in.touchMajor * mLocked.geometricScale; + if (mRawAxes.touchMinor.valid) { + touchMinor = in.touchMinor * mLocked.geometricScale; + } else { + touchMinor = touchMajor; + } + break; + case Calibration::TOUCH_SIZE_CALIBRATION_PRESSURE: + touchMajor = toolMajor * pressure; + touchMinor = toolMinor * pressure; + break; + default: + touchMajor = 0; + touchMinor = 0; + break; + } + + if (touchMajor > toolMajor) { + touchMajor = toolMajor; + } + if (touchMinor > toolMinor) { + touchMinor = toolMinor; + } + + // Size + float size; + switch (mCalibration.sizeCalibration) { + case Calibration::SIZE_CALIBRATION_NORMALIZED: { + float rawSize = mRawAxes.toolMinor.valid + ? avg(in.toolMajor, in.toolMinor) + : in.toolMajor; + size = rawSize * mLocked.sizeScale; + break; + } + default: + size = 0; + break; + } + + // Orientation + float orientation; + switch (mCalibration.orientationCalibration) { + case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED: + orientation = in.orientation * mLocked.orientationScale; + break; + case Calibration::ORIENTATION_CALIBRATION_VECTOR: { + int32_t c1 = signExtendNybble((in.orientation & 0xf0) >> 4); + int32_t c2 = signExtendNybble(in.orientation & 0x0f); + if (c1 != 0 || c2 != 0) { + orientation = atan2f(c1, c2) * 0.5f; + float minorAxisScale = (16.0f - pythag(c1, c2)) / 16.0f; + toolMinor *= minorAxisScale; + touchMinor *= minorAxisScale; + } else { + orientation = 0; + } + break; + } + default: + orientation = 0; + } + + // Adjust coords for orientation. + switch (mLocked.surfaceOrientation) { + case DISPLAY_ORIENTATION_90: { + float xTemp = x; + x = y; + y = mLocked.surfaceWidth - xTemp; + orientation -= M_PI_2; + if (orientation < - M_PI_2) { + orientation += M_PI; + } + break; + } + case DISPLAY_ORIENTATION_180: { + x = mLocked.surfaceWidth - x; + y = mLocked.surfaceHeight - y; + break; + } + case DISPLAY_ORIENTATION_270: { + float xTemp = x; + x = mLocked.surfaceHeight - y; + y = xTemp; + orientation += M_PI_2; + if (orientation > M_PI_2) { + orientation -= M_PI; + } + break; + } + } + + // Write output coords. + PointerCoords& out = pointerCoords[outIndex]; + out.x = x; + out.y = y; + out.pressure = pressure; + out.size = size; + out.touchMajor = touchMajor; + out.touchMinor = touchMinor; + out.toolMajor = toolMajor; + out.toolMinor = toolMinor; + out.orientation = orientation; + + pointerIds[outIndex] = int32_t(id); + + if (id == changedId) { + motionEventAction |= outIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + } + } + + // Check edge flags by looking only at the first pointer since the flags are + // global to the event. + if (motionEventAction == AMOTION_EVENT_ACTION_DOWN) { + if (pointerCoords[0].x <= 0) { + motionEventEdgeFlags |= AMOTION_EVENT_EDGE_FLAG_LEFT; + } else if (pointerCoords[0].x >= mLocked.orientedSurfaceWidth) { + motionEventEdgeFlags |= AMOTION_EVENT_EDGE_FLAG_RIGHT; + } + if (pointerCoords[0].y <= 0) { + motionEventEdgeFlags |= AMOTION_EVENT_EDGE_FLAG_TOP; + } else if (pointerCoords[0].y >= mLocked.orientedSurfaceHeight) { + motionEventEdgeFlags |= AMOTION_EVENT_EDGE_FLAG_BOTTOM; + } + } + + xPrecision = mLocked.orientedXPrecision; + yPrecision = mLocked.orientedYPrecision; + } // release lock + + getDispatcher()->notifyMotion(when, getDeviceId(), mSources, policyFlags, + motionEventAction, 0, getContext()->getGlobalMetaState(), motionEventEdgeFlags, + pointerCount, pointerIds, pointerCoords, + xPrecision, yPrecision, mDownTime); +} + +bool TouchInputMapper::isPointInsideSurfaceLocked(int32_t x, int32_t y) { + if (mRawAxes.x.valid && mRawAxes.y.valid) { + return x >= mRawAxes.x.minValue && x <= mRawAxes.x.maxValue + && y >= mRawAxes.y.minValue && y <= mRawAxes.y.maxValue; + } + return true; +} + +const TouchInputMapper::VirtualKey* TouchInputMapper::findVirtualKeyHitLocked( + int32_t x, int32_t y) { + size_t numVirtualKeys = mLocked.virtualKeys.size(); + for (size_t i = 0; i < numVirtualKeys; i++) { + const VirtualKey& virtualKey = mLocked.virtualKeys[i]; + +#if DEBUG_VIRTUAL_KEYS + LOGD("VirtualKeys: Hit test (%d, %d): keyCode=%d, scanCode=%d, " + "left=%d, top=%d, right=%d, bottom=%d", + x, y, + virtualKey.keyCode, virtualKey.scanCode, + virtualKey.hitLeft, virtualKey.hitTop, + virtualKey.hitRight, virtualKey.hitBottom); +#endif + + if (virtualKey.isHit(x, y)) { + return & virtualKey; + } + } + + return NULL; +} + +void TouchInputMapper::calculatePointerIds() { + uint32_t currentPointerCount = mCurrentTouch.pointerCount; + uint32_t lastPointerCount = mLastTouch.pointerCount; + + if (currentPointerCount == 0) { + // No pointers to assign. + mCurrentTouch.idBits.clear(); + } else if (lastPointerCount == 0) { + // All pointers are new. + mCurrentTouch.idBits.clear(); + for (uint32_t i = 0; i < currentPointerCount; i++) { + mCurrentTouch.pointers[i].id = i; + mCurrentTouch.idToIndex[i] = i; + mCurrentTouch.idBits.markBit(i); + } + } else if (currentPointerCount == 1 && lastPointerCount == 1) { + // Only one pointer and no change in count so it must have the same id as before. + uint32_t id = mLastTouch.pointers[0].id; + mCurrentTouch.pointers[0].id = id; + mCurrentTouch.idToIndex[id] = 0; + mCurrentTouch.idBits.value = BitSet32::valueForBit(id); + } else { + // General case. + // We build a heap of squared euclidean distances between current and last pointers + // associated with the current and last pointer indices. Then, we find the best + // match (by distance) for each current pointer. + PointerDistanceHeapElement heap[MAX_POINTERS * MAX_POINTERS]; + + uint32_t heapSize = 0; + for (uint32_t currentPointerIndex = 0; currentPointerIndex < currentPointerCount; + currentPointerIndex++) { + for (uint32_t lastPointerIndex = 0; lastPointerIndex < lastPointerCount; + lastPointerIndex++) { + int64_t deltaX = mCurrentTouch.pointers[currentPointerIndex].x + - mLastTouch.pointers[lastPointerIndex].x; + int64_t deltaY = mCurrentTouch.pointers[currentPointerIndex].y + - mLastTouch.pointers[lastPointerIndex].y; + + uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY); + + // Insert new element into the heap (sift up). + heap[heapSize].currentPointerIndex = currentPointerIndex; + heap[heapSize].lastPointerIndex = lastPointerIndex; + heap[heapSize].distance = distance; + heapSize += 1; + } + } + + // Heapify + for (uint32_t startIndex = heapSize / 2; startIndex != 0; ) { + startIndex -= 1; + for (uint32_t parentIndex = startIndex; ;) { + uint32_t childIndex = parentIndex * 2 + 1; + if (childIndex >= heapSize) { + break; + } + + if (childIndex + 1 < heapSize + && heap[childIndex + 1].distance < heap[childIndex].distance) { + childIndex += 1; + } + + if (heap[parentIndex].distance <= heap[childIndex].distance) { + break; + } + + swap(heap[parentIndex], heap[childIndex]); + parentIndex = childIndex; + } + } + +#if DEBUG_POINTER_ASSIGNMENT + LOGD("calculatePointerIds - initial distance min-heap: size=%d", heapSize); + for (size_t i = 0; i < heapSize; i++) { + LOGD(" heap[%d]: cur=%d, last=%d, distance=%lld", + i, heap[i].currentPointerIndex, heap[i].lastPointerIndex, + heap[i].distance); + } +#endif + + // Pull matches out by increasing order of distance. + // To avoid reassigning pointers that have already been matched, the loop keeps track + // of which last and current pointers have been matched using the matchedXXXBits variables. + // It also tracks the used pointer id bits. + BitSet32 matchedLastBits(0); + BitSet32 matchedCurrentBits(0); + BitSet32 usedIdBits(0); + bool first = true; + for (uint32_t i = min(currentPointerCount, lastPointerCount); i > 0; i--) { + for (;;) { + if (first) { + // The first time through the loop, we just consume the root element of + // the heap (the one with smallest distance). + first = false; + } else { + // Previous iterations consumed the root element of the heap. + // Pop root element off of the heap (sift down). + heapSize -= 1; + assert(heapSize > 0); + + // Sift down. + heap[0] = heap[heapSize]; + for (uint32_t parentIndex = 0; ;) { + uint32_t childIndex = parentIndex * 2 + 1; + if (childIndex >= heapSize) { + break; + } + + if (childIndex + 1 < heapSize + && heap[childIndex + 1].distance < heap[childIndex].distance) { + childIndex += 1; + } + + if (heap[parentIndex].distance <= heap[childIndex].distance) { + break; + } + + swap(heap[parentIndex], heap[childIndex]); + parentIndex = childIndex; + } + +#if DEBUG_POINTER_ASSIGNMENT + LOGD("calculatePointerIds - reduced distance min-heap: size=%d", heapSize); + for (size_t i = 0; i < heapSize; i++) { + LOGD(" heap[%d]: cur=%d, last=%d, distance=%lld", + i, heap[i].currentPointerIndex, heap[i].lastPointerIndex, + heap[i].distance); + } +#endif + } + + uint32_t currentPointerIndex = heap[0].currentPointerIndex; + if (matchedCurrentBits.hasBit(currentPointerIndex)) continue; // already matched + + uint32_t lastPointerIndex = heap[0].lastPointerIndex; + if (matchedLastBits.hasBit(lastPointerIndex)) continue; // already matched + + matchedCurrentBits.markBit(currentPointerIndex); + matchedLastBits.markBit(lastPointerIndex); + + uint32_t id = mLastTouch.pointers[lastPointerIndex].id; + mCurrentTouch.pointers[currentPointerIndex].id = id; + mCurrentTouch.idToIndex[id] = currentPointerIndex; + usedIdBits.markBit(id); + +#if DEBUG_POINTER_ASSIGNMENT + LOGD("calculatePointerIds - matched: cur=%d, last=%d, id=%d, distance=%lld", + lastPointerIndex, currentPointerIndex, id, heap[0].distance); +#endif + break; + } + } + + // Assign fresh ids to new pointers. + if (currentPointerCount > lastPointerCount) { + for (uint32_t i = currentPointerCount - lastPointerCount; ;) { + uint32_t currentPointerIndex = matchedCurrentBits.firstUnmarkedBit(); + uint32_t id = usedIdBits.firstUnmarkedBit(); + + mCurrentTouch.pointers[currentPointerIndex].id = id; + mCurrentTouch.idToIndex[id] = currentPointerIndex; + usedIdBits.markBit(id); + +#if DEBUG_POINTER_ASSIGNMENT + LOGD("calculatePointerIds - assigned: cur=%d, id=%d", + currentPointerIndex, id); +#endif + + if (--i == 0) break; // done + matchedCurrentBits.markBit(currentPointerIndex); + } + } + + // Fix id bits. + mCurrentTouch.idBits = usedIdBits; + } +} + +/* Special hack for devices that have bad screen data: if one of the + * points has moved more than a screen height from the last position, + * then drop it. */ +bool TouchInputMapper::applyBadTouchFilter() { + // This hack requires valid axis parameters. + if (! mRawAxes.y.valid) { + return false; + } + + uint32_t pointerCount = mCurrentTouch.pointerCount; + + // Nothing to do if there are no points. + if (pointerCount == 0) { + return false; + } + + // Don't do anything if a finger is going down or up. We run + // here before assigning pointer IDs, so there isn't a good + // way to do per-finger matching. + if (pointerCount != mLastTouch.pointerCount) { + return false; + } + + // We consider a single movement across more than a 7/16 of + // the long size of the screen to be bad. This was a magic value + // determined by looking at the maximum distance it is feasible + // to actually move in one sample. + int32_t maxDeltaY = mRawAxes.y.getRange() * 7 / 16; + + // XXX The original code in InputDevice.java included commented out + // code for testing the X axis. Note that when we drop a point + // we don't actually restore the old X either. Strange. + // The old code also tries to track when bad points were previously + // detected but it turns out that due to the placement of a "break" + // at the end of the loop, we never set mDroppedBadPoint to true + // so it is effectively dead code. + // Need to figure out if the old code is busted or just overcomplicated + // but working as intended. + + // Look through all new points and see if any are farther than + // acceptable from all previous points. + for (uint32_t i = pointerCount; i-- > 0; ) { + int32_t y = mCurrentTouch.pointers[i].y; + int32_t closestY = INT_MAX; + int32_t closestDeltaY = 0; + +#if DEBUG_HACKS + LOGD("BadTouchFilter: Looking at next point #%d: y=%d", i, y); +#endif + + for (uint32_t j = pointerCount; j-- > 0; ) { + int32_t lastY = mLastTouch.pointers[j].y; + int32_t deltaY = abs(y - lastY); + +#if DEBUG_HACKS + LOGD("BadTouchFilter: Comparing with last point #%d: y=%d deltaY=%d", + j, lastY, deltaY); +#endif + + if (deltaY < maxDeltaY) { + goto SkipSufficientlyClosePoint; + } + if (deltaY < closestDeltaY) { + closestDeltaY = deltaY; + closestY = lastY; + } + } + + // Must not have found a close enough match. +#if DEBUG_HACKS + LOGD("BadTouchFilter: Dropping bad point #%d: newY=%d oldY=%d deltaY=%d maxDeltaY=%d", + i, y, closestY, closestDeltaY, maxDeltaY); +#endif + + mCurrentTouch.pointers[i].y = closestY; + return true; // XXX original code only corrects one point + + SkipSufficientlyClosePoint: ; + } + + // No change. + return false; +} + +/* Special hack for devices that have bad screen data: drop points where + * the coordinate value for one axis has jumped to the other pointer's location. + */ +bool TouchInputMapper::applyJumpyTouchFilter() { + // This hack requires valid axis parameters. + if (! mRawAxes.y.valid) { + return false; + } + + uint32_t pointerCount = mCurrentTouch.pointerCount; + if (mLastTouch.pointerCount != pointerCount) { +#if DEBUG_HACKS + LOGD("JumpyTouchFilter: Different pointer count %d -> %d", + mLastTouch.pointerCount, pointerCount); + for (uint32_t i = 0; i < pointerCount; i++) { + LOGD(" Pointer %d (%d, %d)", i, + mCurrentTouch.pointers[i].x, mCurrentTouch.pointers[i].y); + } +#endif + + if (mJumpyTouchFilter.jumpyPointsDropped < JUMPY_TRANSITION_DROPS) { + if (mLastTouch.pointerCount == 1 && pointerCount == 2) { + // Just drop the first few events going from 1 to 2 pointers. + // They're bad often enough that they're not worth considering. + mCurrentTouch.pointerCount = 1; + mJumpyTouchFilter.jumpyPointsDropped += 1; + +#if DEBUG_HACKS + LOGD("JumpyTouchFilter: Pointer 2 dropped"); +#endif + return true; + } else if (mLastTouch.pointerCount == 2 && pointerCount == 1) { + // The event when we go from 2 -> 1 tends to be messed up too + mCurrentTouch.pointerCount = 2; + mCurrentTouch.pointers[0] = mLastTouch.pointers[0]; + mCurrentTouch.pointers[1] = mLastTouch.pointers[1]; + mJumpyTouchFilter.jumpyPointsDropped += 1; + +#if DEBUG_HACKS + for (int32_t i = 0; i < 2; i++) { + LOGD("JumpyTouchFilter: Pointer %d replaced (%d, %d)", i, + mCurrentTouch.pointers[i].x, mCurrentTouch.pointers[i].y); + } +#endif + return true; + } + } + // Reset jumpy points dropped on other transitions or if limit exceeded. + mJumpyTouchFilter.jumpyPointsDropped = 0; + +#if DEBUG_HACKS + LOGD("JumpyTouchFilter: Transition - drop limit reset"); +#endif + return false; + } + + // We have the same number of pointers as last time. + // A 'jumpy' point is one where the coordinate value for one axis + // has jumped to the other pointer's location. No need to do anything + // else if we only have one pointer. + if (pointerCount < 2) { + return false; + } + + if (mJumpyTouchFilter.jumpyPointsDropped < JUMPY_DROP_LIMIT) { + int jumpyEpsilon = mRawAxes.y.getRange() / JUMPY_EPSILON_DIVISOR; + + // We only replace the single worst jumpy point as characterized by pointer distance + // in a single axis. + int32_t badPointerIndex = -1; + int32_t badPointerReplacementIndex = -1; + int32_t badPointerDistance = INT_MIN; // distance to be corrected + + for (uint32_t i = pointerCount; i-- > 0; ) { + int32_t x = mCurrentTouch.pointers[i].x; + int32_t y = mCurrentTouch.pointers[i].y; + +#if DEBUG_HACKS + LOGD("JumpyTouchFilter: Point %d (%d, %d)", i, x, y); +#endif + + // Check if a touch point is too close to another's coordinates + bool dropX = false, dropY = false; + for (uint32_t j = 0; j < pointerCount; j++) { + if (i == j) { + continue; + } + + if (abs(x - mCurrentTouch.pointers[j].x) <= jumpyEpsilon) { + dropX = true; + break; + } + + if (abs(y - mCurrentTouch.pointers[j].y) <= jumpyEpsilon) { + dropY = true; + break; + } + } + if (! dropX && ! dropY) { + continue; // not jumpy + } + + // Find a replacement candidate by comparing with older points on the + // complementary (non-jumpy) axis. + int32_t distance = INT_MIN; // distance to be corrected + int32_t replacementIndex = -1; + + if (dropX) { + // X looks too close. Find an older replacement point with a close Y. + int32_t smallestDeltaY = INT_MAX; + for (uint32_t j = 0; j < pointerCount; j++) { + int32_t deltaY = abs(y - mLastTouch.pointers[j].y); + if (deltaY < smallestDeltaY) { + smallestDeltaY = deltaY; + replacementIndex = j; + } + } + distance = abs(x - mLastTouch.pointers[replacementIndex].x); + } else { + // Y looks too close. Find an older replacement point with a close X. + int32_t smallestDeltaX = INT_MAX; + for (uint32_t j = 0; j < pointerCount; j++) { + int32_t deltaX = abs(x - mLastTouch.pointers[j].x); + if (deltaX < smallestDeltaX) { + smallestDeltaX = deltaX; + replacementIndex = j; + } + } + distance = abs(y - mLastTouch.pointers[replacementIndex].y); + } + + // If replacing this pointer would correct a worse error than the previous ones + // considered, then use this replacement instead. + if (distance > badPointerDistance) { + badPointerIndex = i; + badPointerReplacementIndex = replacementIndex; + badPointerDistance = distance; + } + } + + // Correct the jumpy pointer if one was found. + if (badPointerIndex >= 0) { +#if DEBUG_HACKS + LOGD("JumpyTouchFilter: Replacing bad pointer %d with (%d, %d)", + badPointerIndex, + mLastTouch.pointers[badPointerReplacementIndex].x, + mLastTouch.pointers[badPointerReplacementIndex].y); +#endif + + mCurrentTouch.pointers[badPointerIndex].x = + mLastTouch.pointers[badPointerReplacementIndex].x; + mCurrentTouch.pointers[badPointerIndex].y = + mLastTouch.pointers[badPointerReplacementIndex].y; + mJumpyTouchFilter.jumpyPointsDropped += 1; + return true; + } + } + + mJumpyTouchFilter.jumpyPointsDropped = 0; + return false; +} + +/* Special hack for devices that have bad screen data: aggregate and + * compute averages of the coordinate data, to reduce the amount of + * jitter seen by applications. */ +void TouchInputMapper::applyAveragingTouchFilter() { + for (uint32_t currentIndex = 0; currentIndex < mCurrentTouch.pointerCount; currentIndex++) { + uint32_t id = mCurrentTouch.pointers[currentIndex].id; + int32_t x = mCurrentTouch.pointers[currentIndex].x; + int32_t y = mCurrentTouch.pointers[currentIndex].y; + int32_t pressure; + switch (mCalibration.pressureSource) { + case Calibration::PRESSURE_SOURCE_PRESSURE: + pressure = mCurrentTouch.pointers[currentIndex].pressure; + break; + case Calibration::PRESSURE_SOURCE_TOUCH: + pressure = mCurrentTouch.pointers[currentIndex].touchMajor; + break; + default: + pressure = 1; + break; + } + + if (mLastTouch.idBits.hasBit(id)) { + // Pointer was down before and is still down now. + // Compute average over history trace. + uint32_t start = mAveragingTouchFilter.historyStart[id]; + uint32_t end = mAveragingTouchFilter.historyEnd[id]; + + int64_t deltaX = x - mAveragingTouchFilter.historyData[end].pointers[id].x; + int64_t deltaY = y - mAveragingTouchFilter.historyData[end].pointers[id].y; + uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY); + +#if DEBUG_HACKS + LOGD("AveragingTouchFilter: Pointer id %d - Distance from last sample: %lld", + id, distance); +#endif + + if (distance < AVERAGING_DISTANCE_LIMIT) { + // Increment end index in preparation for recording new historical data. + end += 1; + if (end > AVERAGING_HISTORY_SIZE) { + end = 0; + } + + // If the end index has looped back to the start index then we have filled + // the historical trace up to the desired size so we drop the historical + // data at the start of the trace. + if (end == start) { + start += 1; + if (start > AVERAGING_HISTORY_SIZE) { + start = 0; + } + } + + // Add the raw data to the historical trace. + mAveragingTouchFilter.historyStart[id] = start; + mAveragingTouchFilter.historyEnd[id] = end; + mAveragingTouchFilter.historyData[end].pointers[id].x = x; + mAveragingTouchFilter.historyData[end].pointers[id].y = y; + mAveragingTouchFilter.historyData[end].pointers[id].pressure = pressure; + + // Average over all historical positions in the trace by total pressure. + int32_t averagedX = 0; + int32_t averagedY = 0; + int32_t totalPressure = 0; + for (;;) { + int32_t historicalX = mAveragingTouchFilter.historyData[start].pointers[id].x; + int32_t historicalY = mAveragingTouchFilter.historyData[start].pointers[id].y; + int32_t historicalPressure = mAveragingTouchFilter.historyData[start] + .pointers[id].pressure; + + averagedX += historicalX * historicalPressure; + averagedY += historicalY * historicalPressure; + totalPressure += historicalPressure; + + if (start == end) { + break; + } + + start += 1; + if (start > AVERAGING_HISTORY_SIZE) { + start = 0; + } + } + + if (totalPressure != 0) { + averagedX /= totalPressure; + averagedY /= totalPressure; + +#if DEBUG_HACKS + LOGD("AveragingTouchFilter: Pointer id %d - " + "totalPressure=%d, averagedX=%d, averagedY=%d", id, totalPressure, + averagedX, averagedY); +#endif + + mCurrentTouch.pointers[currentIndex].x = averagedX; + mCurrentTouch.pointers[currentIndex].y = averagedY; + } + } else { +#if DEBUG_HACKS + LOGD("AveragingTouchFilter: Pointer id %d - Exceeded max distance", id); +#endif + } + } else { +#if DEBUG_HACKS + LOGD("AveragingTouchFilter: Pointer id %d - Pointer went up", id); +#endif + } + + // Reset pointer history. + mAveragingTouchFilter.historyStart[id] = 0; + mAveragingTouchFilter.historyEnd[id] = 0; + mAveragingTouchFilter.historyData[0].pointers[id].x = x; + mAveragingTouchFilter.historyData[0].pointers[id].y = y; + mAveragingTouchFilter.historyData[0].pointers[id].pressure = pressure; + } +} + +int32_t TouchInputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) { + { // acquire lock + AutoMutex _l(mLock); + + if (mLocked.currentVirtualKey.down && mLocked.currentVirtualKey.keyCode == keyCode) { + return AKEY_STATE_VIRTUAL; + } + + size_t numVirtualKeys = mLocked.virtualKeys.size(); + for (size_t i = 0; i < numVirtualKeys; i++) { + const VirtualKey& virtualKey = mLocked.virtualKeys[i]; + if (virtualKey.keyCode == keyCode) { + return AKEY_STATE_UP; + } + } + } // release lock + + return AKEY_STATE_UNKNOWN; +} + +int32_t TouchInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) { + { // acquire lock + AutoMutex _l(mLock); + + if (mLocked.currentVirtualKey.down && mLocked.currentVirtualKey.scanCode == scanCode) { + return AKEY_STATE_VIRTUAL; + } + + size_t numVirtualKeys = mLocked.virtualKeys.size(); + for (size_t i = 0; i < numVirtualKeys; i++) { + const VirtualKey& virtualKey = mLocked.virtualKeys[i]; + if (virtualKey.scanCode == scanCode) { + return AKEY_STATE_UP; + } + } + } // release lock + + return AKEY_STATE_UNKNOWN; +} + +bool TouchInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) { + { // acquire lock + AutoMutex _l(mLock); + + size_t numVirtualKeys = mLocked.virtualKeys.size(); + for (size_t i = 0; i < numVirtualKeys; i++) { + const VirtualKey& virtualKey = mLocked.virtualKeys[i]; + + for (size_t i = 0; i < numCodes; i++) { + if (virtualKey.keyCode == keyCodes[i]) { + outFlags[i] = 1; + } + } + } + } // release lock + + return true; +} + + +// --- SingleTouchInputMapper --- + +SingleTouchInputMapper::SingleTouchInputMapper(InputDevice* device) : + TouchInputMapper(device) { + initialize(); +} + +SingleTouchInputMapper::~SingleTouchInputMapper() { +} + +void SingleTouchInputMapper::initialize() { + mAccumulator.clear(); + + mDown = false; + mX = 0; + mY = 0; + mPressure = 0; // default to 0 for devices that don't report pressure + mToolWidth = 0; // default to 0 for devices that don't report tool width +} + +void SingleTouchInputMapper::reset() { + TouchInputMapper::reset(); + + initialize(); + } + +void SingleTouchInputMapper::process(const RawEvent* rawEvent) { + switch (rawEvent->type) { + case EV_KEY: + switch (rawEvent->scanCode) { + case BTN_TOUCH: + mAccumulator.fields |= Accumulator::FIELD_BTN_TOUCH; + mAccumulator.btnTouch = rawEvent->value != 0; + // Don't sync immediately. Wait until the next SYN_REPORT since we might + // not have received valid position information yet. This logic assumes that + // BTN_TOUCH is always followed by SYN_REPORT as part of a complete packet. + break; + } + break; + + case EV_ABS: + switch (rawEvent->scanCode) { + case ABS_X: + mAccumulator.fields |= Accumulator::FIELD_ABS_X; + mAccumulator.absX = rawEvent->value; + break; + case ABS_Y: + mAccumulator.fields |= Accumulator::FIELD_ABS_Y; + mAccumulator.absY = rawEvent->value; + break; + case ABS_PRESSURE: + mAccumulator.fields |= Accumulator::FIELD_ABS_PRESSURE; + mAccumulator.absPressure = rawEvent->value; + break; + case ABS_TOOL_WIDTH: + mAccumulator.fields |= Accumulator::FIELD_ABS_TOOL_WIDTH; + mAccumulator.absToolWidth = rawEvent->value; + break; + } + break; + + case EV_SYN: + switch (rawEvent->scanCode) { + case SYN_REPORT: + sync(rawEvent->when); + break; + } + break; + } +} + +void SingleTouchInputMapper::sync(nsecs_t when) { + uint32_t fields = mAccumulator.fields; + if (fields == 0) { + return; // no new state changes, so nothing to do + } + + if (fields & Accumulator::FIELD_BTN_TOUCH) { + mDown = mAccumulator.btnTouch; + } + + if (fields & Accumulator::FIELD_ABS_X) { + mX = mAccumulator.absX; + } + + if (fields & Accumulator::FIELD_ABS_Y) { + mY = mAccumulator.absY; + } + + if (fields & Accumulator::FIELD_ABS_PRESSURE) { + mPressure = mAccumulator.absPressure; + } + + if (fields & Accumulator::FIELD_ABS_TOOL_WIDTH) { + mToolWidth = mAccumulator.absToolWidth; + } + + mCurrentTouch.clear(); + + if (mDown) { + mCurrentTouch.pointerCount = 1; + mCurrentTouch.pointers[0].id = 0; + mCurrentTouch.pointers[0].x = mX; + mCurrentTouch.pointers[0].y = mY; + mCurrentTouch.pointers[0].pressure = mPressure; + mCurrentTouch.pointers[0].touchMajor = 0; + mCurrentTouch.pointers[0].touchMinor = 0; + mCurrentTouch.pointers[0].toolMajor = mToolWidth; + mCurrentTouch.pointers[0].toolMinor = mToolWidth; + mCurrentTouch.pointers[0].orientation = 0; + mCurrentTouch.idToIndex[0] = 0; + mCurrentTouch.idBits.markBit(0); + } + + syncTouch(when, true); + + mAccumulator.clear(); +} + +void SingleTouchInputMapper::configureRawAxes() { + TouchInputMapper::configureRawAxes(); + + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_X, & mRawAxes.x); + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_Y, & mRawAxes.y); + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_PRESSURE, & mRawAxes.pressure); + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_TOOL_WIDTH, & mRawAxes.toolMajor); +} + + +// --- MultiTouchInputMapper --- + +MultiTouchInputMapper::MultiTouchInputMapper(InputDevice* device) : + TouchInputMapper(device) { + initialize(); +} + +MultiTouchInputMapper::~MultiTouchInputMapper() { +} + +void MultiTouchInputMapper::initialize() { + mAccumulator.clear(); +} + +void MultiTouchInputMapper::reset() { + TouchInputMapper::reset(); + + initialize(); +} + +void MultiTouchInputMapper::process(const RawEvent* rawEvent) { + switch (rawEvent->type) { + case EV_ABS: { + uint32_t pointerIndex = mAccumulator.pointerCount; + Accumulator::Pointer* pointer = & mAccumulator.pointers[pointerIndex]; + + switch (rawEvent->scanCode) { + case ABS_MT_POSITION_X: + pointer->fields |= Accumulator::FIELD_ABS_MT_POSITION_X; + pointer->absMTPositionX = rawEvent->value; + break; + case ABS_MT_POSITION_Y: + pointer->fields |= Accumulator::FIELD_ABS_MT_POSITION_Y; + pointer->absMTPositionY = rawEvent->value; + break; + case ABS_MT_TOUCH_MAJOR: + pointer->fields |= Accumulator::FIELD_ABS_MT_TOUCH_MAJOR; + pointer->absMTTouchMajor = rawEvent->value; + break; + case ABS_MT_TOUCH_MINOR: + pointer->fields |= Accumulator::FIELD_ABS_MT_TOUCH_MINOR; + pointer->absMTTouchMinor = rawEvent->value; + break; + case ABS_MT_WIDTH_MAJOR: + pointer->fields |= Accumulator::FIELD_ABS_MT_WIDTH_MAJOR; + pointer->absMTWidthMajor = rawEvent->value; + break; + case ABS_MT_WIDTH_MINOR: + pointer->fields |= Accumulator::FIELD_ABS_MT_WIDTH_MINOR; + pointer->absMTWidthMinor = rawEvent->value; + break; + case ABS_MT_ORIENTATION: + pointer->fields |= Accumulator::FIELD_ABS_MT_ORIENTATION; + pointer->absMTOrientation = rawEvent->value; + break; + case ABS_MT_TRACKING_ID: + pointer->fields |= Accumulator::FIELD_ABS_MT_TRACKING_ID; + pointer->absMTTrackingId = rawEvent->value; + break; + case ABS_MT_PRESSURE: + pointer->fields |= Accumulator::FIELD_ABS_MT_PRESSURE; + pointer->absMTPressure = rawEvent->value; + break; + } + break; + } + + case EV_SYN: + switch (rawEvent->scanCode) { + case SYN_MT_REPORT: { + // MultiTouch Sync: The driver has returned all data for *one* of the pointers. + uint32_t pointerIndex = mAccumulator.pointerCount; + + if (mAccumulator.pointers[pointerIndex].fields) { + if (pointerIndex == MAX_POINTERS) { + LOGW("MultiTouch device driver returned more than maximum of %d pointers.", + MAX_POINTERS); + } else { + pointerIndex += 1; + mAccumulator.pointerCount = pointerIndex; + } + } + + mAccumulator.pointers[pointerIndex].clear(); + break; + } + + case SYN_REPORT: + sync(rawEvent->when); + break; + } + break; + } +} + +void MultiTouchInputMapper::sync(nsecs_t when) { + static const uint32_t REQUIRED_FIELDS = + Accumulator::FIELD_ABS_MT_POSITION_X | Accumulator::FIELD_ABS_MT_POSITION_Y; + + uint32_t inCount = mAccumulator.pointerCount; + uint32_t outCount = 0; + bool havePointerIds = true; + + mCurrentTouch.clear(); + + for (uint32_t inIndex = 0; inIndex < inCount; inIndex++) { + const Accumulator::Pointer& inPointer = mAccumulator.pointers[inIndex]; + uint32_t fields = inPointer.fields; + + if ((fields & REQUIRED_FIELDS) != REQUIRED_FIELDS) { + // Some drivers send empty MT sync packets without X / Y to indicate a pointer up. + // Drop this finger. + continue; + } + + PointerData& outPointer = mCurrentTouch.pointers[outCount]; + outPointer.x = inPointer.absMTPositionX; + outPointer.y = inPointer.absMTPositionY; + + if (fields & Accumulator::FIELD_ABS_MT_PRESSURE) { + if (inPointer.absMTPressure <= 0) { + // Some devices send sync packets with X / Y but with a 0 pressure to indicate + // a pointer going up. Drop this finger. + continue; + } + outPointer.pressure = inPointer.absMTPressure; + } else { + // Default pressure to 0 if absent. + outPointer.pressure = 0; + } + + if (fields & Accumulator::FIELD_ABS_MT_TOUCH_MAJOR) { + if (inPointer.absMTTouchMajor <= 0) { + // Some devices send sync packets with X / Y but with a 0 touch major to indicate + // a pointer going up. Drop this finger. + continue; + } + outPointer.touchMajor = inPointer.absMTTouchMajor; + } else { + // Default touch area to 0 if absent. + outPointer.touchMajor = 0; + } + + if (fields & Accumulator::FIELD_ABS_MT_TOUCH_MINOR) { + outPointer.touchMinor = inPointer.absMTTouchMinor; + } else { + // Assume touch area is circular. + outPointer.touchMinor = outPointer.touchMajor; + } + + if (fields & Accumulator::FIELD_ABS_MT_WIDTH_MAJOR) { + outPointer.toolMajor = inPointer.absMTWidthMajor; + } else { + // Default tool area to 0 if absent. + outPointer.toolMajor = 0; + } + + if (fields & Accumulator::FIELD_ABS_MT_WIDTH_MINOR) { + outPointer.toolMinor = inPointer.absMTWidthMinor; + } else { + // Assume tool area is circular. + outPointer.toolMinor = outPointer.toolMajor; + } + + if (fields & Accumulator::FIELD_ABS_MT_ORIENTATION) { + outPointer.orientation = inPointer.absMTOrientation; + } else { + // Default orientation to vertical if absent. + outPointer.orientation = 0; + } + + // Assign pointer id using tracking id if available. + if (havePointerIds) { + if (fields & Accumulator::FIELD_ABS_MT_TRACKING_ID) { + uint32_t id = uint32_t(inPointer.absMTTrackingId); + + if (id > MAX_POINTER_ID) { +#if DEBUG_POINTERS + LOGD("Pointers: Ignoring driver provided pointer id %d because " + "it is larger than max supported id %d", + id, MAX_POINTER_ID); +#endif + havePointerIds = false; + } + else { + outPointer.id = id; + mCurrentTouch.idToIndex[id] = outCount; + mCurrentTouch.idBits.markBit(id); + } + } else { + havePointerIds = false; + } + } + + outCount += 1; + } + + mCurrentTouch.pointerCount = outCount; + + syncTouch(when, havePointerIds); + + mAccumulator.clear(); +} + +void MultiTouchInputMapper::configureRawAxes() { + TouchInputMapper::configureRawAxes(); + + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_POSITION_X, & mRawAxes.x); + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_POSITION_Y, & mRawAxes.y); + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_TOUCH_MAJOR, & mRawAxes.touchMajor); + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_TOUCH_MINOR, & mRawAxes.touchMinor); + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_WIDTH_MAJOR, & mRawAxes.toolMajor); + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_WIDTH_MINOR, & mRawAxes.toolMinor); + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_ORIENTATION, & mRawAxes.orientation); + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_PRESSURE, & mRawAxes.pressure); +} + + +} // namespace android diff --git a/services/input/InputReader.h b/services/input/InputReader.h new file mode 100644 index 0000000..7619682 --- /dev/null +++ b/services/input/InputReader.h @@ -0,0 +1,973 @@ +/* + * Copyright (C) 2010 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. + */ + +#ifndef _UI_INPUT_READER_H +#define _UI_INPUT_READER_H + +#include "EventHub.h" +#include "InputDispatcher.h" +#include "PointerController.h" + +#include <ui/Input.h> +#include <ui/DisplayInfo.h> +#include <utils/KeyedVector.h> +#include <utils/threads.h> +#include <utils/Timers.h> +#include <utils/RefBase.h> +#include <utils/String8.h> +#include <utils/BitSet.h> + +#include <stddef.h> +#include <unistd.h> + +namespace android { + +class InputDevice; +class InputMapper; + + +/* + * Input reader policy interface. + * + * The input reader policy is used by the input reader to interact with the Window Manager + * and other system components. + * + * The actual implementation is partially supported by callbacks into the DVM + * via JNI. This interface is also mocked in the unit tests. + */ +class InputReaderPolicyInterface : public virtual RefBase { +protected: + InputReaderPolicyInterface() { } + virtual ~InputReaderPolicyInterface() { } + +public: + /* Display orientations. */ + enum { + ROTATION_0 = 0, + ROTATION_90 = 1, + ROTATION_180 = 2, + ROTATION_270 = 3 + }; + + /* Gets information about the display with the specified id. + * Returns true if the display info is available, false otherwise. + */ + virtual bool getDisplayInfo(int32_t displayId, + int32_t* width, int32_t* height, int32_t* orientation) = 0; + + /* Determines whether to turn on some hacks we have to improve the touch interaction with a + * certain device whose screen currently is not all that good. + */ + virtual bool filterTouchEvents() = 0; + + /* Determines whether to turn on some hacks to improve touch interaction with another device + * where touch coordinate data can get corrupted. + */ + virtual bool filterJumpyTouchEvents() = 0; + + /* Gets the amount of time to disable virtual keys after the screen is touched + * in order to filter out accidental virtual key presses due to swiping gestures + * or taps near the edge of the display. May be 0 to disable the feature. + */ + virtual nsecs_t getVirtualKeyQuietTime() = 0; + + /* Gets the excluded device names for the platform. */ + virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames) = 0; + + /* Gets a pointer controller associated with the specified cursor device (ie. a mouse). */ + virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) = 0; +}; + + +/* Processes raw input events and sends cooked event data to an input dispatcher. */ +class InputReaderInterface : public virtual RefBase { +protected: + InputReaderInterface() { } + virtual ~InputReaderInterface() { } + +public: + /* Dumps the state of the input reader. + * + * This method may be called on any thread (usually by the input manager). */ + virtual void dump(String8& dump) = 0; + + /* Runs a single iteration of the processing loop. + * Nominally reads and processes one incoming message from the EventHub. + * + * This method should be called on the input reader thread. + */ + virtual void loopOnce() = 0; + + /* Gets the current input device configuration. + * + * This method may be called on any thread (usually by the input manager). + */ + virtual void getInputConfiguration(InputConfiguration* outConfiguration) = 0; + + /* Gets information about the specified input device. + * Returns OK if the device information was obtained or NAME_NOT_FOUND if there + * was no such device. + * + * This method may be called on any thread (usually by the input manager). + */ + virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) = 0; + + /* Gets the list of all registered device ids. */ + virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds) = 0; + + /* Query current input state. */ + virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask, + int32_t scanCode) = 0; + virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask, + int32_t keyCode) = 0; + virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, + int32_t sw) = 0; + + /* Determine whether physical keys exist for the given framework-domain key codes. */ + virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask, + size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) = 0; +}; + + +/* Internal interface used by individual input devices to access global input device state + * and parameters maintained by the input reader. + */ +class InputReaderContext { +public: + InputReaderContext() { } + virtual ~InputReaderContext() { } + + virtual void updateGlobalMetaState() = 0; + virtual int32_t getGlobalMetaState() = 0; + + virtual void disableVirtualKeysUntil(nsecs_t time) = 0; + virtual bool shouldDropVirtualKey(nsecs_t now, + InputDevice* device, int32_t keyCode, int32_t scanCode) = 0; + + virtual InputReaderPolicyInterface* getPolicy() = 0; + virtual InputDispatcherInterface* getDispatcher() = 0; + virtual EventHubInterface* getEventHub() = 0; +}; + + +/* The input reader reads raw event data from the event hub and processes it into input events + * that it sends to the input dispatcher. Some functions of the input reader, such as early + * event filtering in low power states, are controlled by a separate policy object. + * + * IMPORTANT INVARIANT: + * Because the policy and dispatcher can potentially block or cause re-entrance into + * the input reader, the input reader never calls into other components while holding + * an exclusive internal lock whenever re-entrance can happen. + */ +class InputReader : public InputReaderInterface, protected InputReaderContext { +public: + InputReader(const sp<EventHubInterface>& eventHub, + const sp<InputReaderPolicyInterface>& policy, + const sp<InputDispatcherInterface>& dispatcher); + virtual ~InputReader(); + + virtual void dump(String8& dump); + + virtual void loopOnce(); + + virtual void getInputConfiguration(InputConfiguration* outConfiguration); + + virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo); + virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds); + + virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask, + int32_t scanCode); + virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask, + int32_t keyCode); + virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, + int32_t sw); + + virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask, + size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags); + +protected: + // These methods are protected virtual so they can be overridden and instrumented + // by test cases. + virtual InputDevice* createDevice(int32_t deviceId, const String8& name, uint32_t classes); + +private: + sp<EventHubInterface> mEventHub; + sp<InputReaderPolicyInterface> mPolicy; + sp<InputDispatcherInterface> mDispatcher; + + virtual InputReaderPolicyInterface* getPolicy() { return mPolicy.get(); } + virtual InputDispatcherInterface* getDispatcher() { return mDispatcher.get(); } + virtual EventHubInterface* getEventHub() { return mEventHub.get(); } + + // This reader/writer lock guards the list of input devices. + // The writer lock must be held whenever the list of input devices is modified + // and then promptly released. + // The reader lock must be held whenever the list of input devices is traversed or an + // input device in the list is accessed. + // This lock only protects the registry and prevents inadvertent deletion of device objects + // that are in use. Individual devices are responsible for guarding their own internal state + // as needed for concurrent operation. + RWLock mDeviceRegistryLock; + KeyedVector<int32_t, InputDevice*> mDevices; + + // low-level input event decoding and device management + void process(const RawEvent* rawEvent); + + void addDevice(int32_t deviceId); + void removeDevice(int32_t deviceId); + void configureExcludedDevices(); + + void consumeEvent(const RawEvent* rawEvent); + + void handleConfigurationChanged(nsecs_t when); + + // state management for all devices + Mutex mStateLock; + + int32_t mGlobalMetaState; + virtual void updateGlobalMetaState(); + virtual int32_t getGlobalMetaState(); + + InputConfiguration mInputConfiguration; + void updateInputConfiguration(); + + nsecs_t mDisableVirtualKeysTimeout; + virtual void disableVirtualKeysUntil(nsecs_t time); + virtual bool shouldDropVirtualKey(nsecs_t now, + InputDevice* device, int32_t keyCode, int32_t scanCode); + + // state queries + typedef int32_t (InputDevice::*GetStateFunc)(uint32_t sourceMask, int32_t code); + int32_t getState(int32_t deviceId, uint32_t sourceMask, int32_t code, + GetStateFunc getStateFunc); + bool markSupportedKeyCodes(int32_t deviceId, uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags); +}; + + +/* Reads raw events from the event hub and processes them, endlessly. */ +class InputReaderThread : public Thread { +public: + InputReaderThread(const sp<InputReaderInterface>& reader); + virtual ~InputReaderThread(); + +private: + sp<InputReaderInterface> mReader; + + virtual bool threadLoop(); +}; + + +/* Represents the state of a single input device. */ +class InputDevice { +public: + InputDevice(InputReaderContext* context, int32_t id, const String8& name); + ~InputDevice(); + + inline InputReaderContext* getContext() { return mContext; } + inline int32_t getId() { return mId; } + inline const String8& getName() { return mName; } + inline uint32_t getSources() { return mSources; } + + inline bool isIgnored() { return mMappers.isEmpty(); } + + void dump(String8& dump); + void addMapper(InputMapper* mapper); + void configure(); + void reset(); + void process(const RawEvent* rawEvent); + + void getDeviceInfo(InputDeviceInfo* outDeviceInfo); + int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode); + int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode); + int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode); + bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags); + + int32_t getMetaState(); + + inline const PropertyMap& getConfiguration() { + return mConfiguration; + } + +private: + InputReaderContext* mContext; + int32_t mId; + + Vector<InputMapper*> mMappers; + + String8 mName; + uint32_t mSources; + + typedef int32_t (InputMapper::*GetStateFunc)(uint32_t sourceMask, int32_t code); + int32_t getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc); + + PropertyMap mConfiguration; +}; + + +/* An input mapper transforms raw input events into cooked event data. + * A single input device can have multiple associated input mappers in order to interpret + * different classes of events. + */ +class InputMapper { +public: + InputMapper(InputDevice* device); + virtual ~InputMapper(); + + inline InputDevice* getDevice() { return mDevice; } + inline int32_t getDeviceId() { return mDevice->getId(); } + inline const String8 getDeviceName() { return mDevice->getName(); } + inline InputReaderContext* getContext() { return mContext; } + inline InputReaderPolicyInterface* getPolicy() { return mContext->getPolicy(); } + inline InputDispatcherInterface* getDispatcher() { return mContext->getDispatcher(); } + inline EventHubInterface* getEventHub() { return mContext->getEventHub(); } + + virtual uint32_t getSources() = 0; + virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo); + virtual void dump(String8& dump); + virtual void configure(); + virtual void reset(); + virtual void process(const RawEvent* rawEvent) = 0; + + virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode); + virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode); + virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode); + virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags); + + virtual int32_t getMetaState(); + +protected: + InputDevice* mDevice; + InputReaderContext* mContext; +}; + + +class SwitchInputMapper : public InputMapper { +public: + SwitchInputMapper(InputDevice* device); + virtual ~SwitchInputMapper(); + + virtual uint32_t getSources(); + virtual void process(const RawEvent* rawEvent); + + virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode); + +private: + void processSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue); +}; + + +class KeyboardInputMapper : public InputMapper { +public: + KeyboardInputMapper(InputDevice* device, uint32_t sources, int32_t keyboardType); + virtual ~KeyboardInputMapper(); + + virtual uint32_t getSources(); + virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo); + virtual void dump(String8& dump); + virtual void configure(); + virtual void reset(); + virtual void process(const RawEvent* rawEvent); + + virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode); + virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode); + virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags); + + virtual int32_t getMetaState(); + +private: + Mutex mLock; + + struct KeyDown { + int32_t keyCode; + int32_t scanCode; + }; + + uint32_t mSources; + int32_t mKeyboardType; + + // Immutable configuration parameters. + struct Parameters { + int32_t associatedDisplayId; + bool orientationAware; + } mParameters; + + struct LockedState { + Vector<KeyDown> keyDowns; // keys that are down + int32_t metaState; + nsecs_t downTime; // time of most recent key down + + struct LedState { + bool avail; // led is available + bool on; // we think the led is currently on + }; + LedState capsLockLedState; + LedState numLockLedState; + LedState scrollLockLedState; + } mLocked; + + void initializeLocked(); + + void configureParameters(); + void dumpParameters(String8& dump); + + bool isKeyboardOrGamepadKey(int32_t scanCode); + + void processKey(nsecs_t when, bool down, int32_t keyCode, int32_t scanCode, + uint32_t policyFlags); + + ssize_t findKeyDownLocked(int32_t scanCode); + + void resetLedStateLocked(); + void initializeLedStateLocked(LockedState::LedState& ledState, int32_t led); + void updateLedStateLocked(bool reset); + void updateLedStateForModifierLocked(LockedState::LedState& ledState, int32_t led, + int32_t modifier, bool reset); +}; + + +class CursorInputMapper : public InputMapper { +public: + CursorInputMapper(InputDevice* device); + virtual ~CursorInputMapper(); + + virtual uint32_t getSources(); + virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo); + virtual void dump(String8& dump); + virtual void configure(); + virtual void reset(); + virtual void process(const RawEvent* rawEvent); + + virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode); + +private: + // Amount that trackball needs to move in order to generate a key event. + static const int32_t TRACKBALL_MOVEMENT_THRESHOLD = 6; + + Mutex mLock; + + // Immutable configuration parameters. + struct Parameters { + enum Mode { + MODE_POINTER, + MODE_NAVIGATION, + }; + + Mode mode; + int32_t associatedDisplayId; + bool orientationAware; + } mParameters; + + struct Accumulator { + enum { + FIELD_BTN_MOUSE = 1, + FIELD_REL_X = 2, + FIELD_REL_Y = 4 + }; + + uint32_t fields; + + bool btnMouse; + int32_t relX; + int32_t relY; + + inline void clear() { + fields = 0; + } + } mAccumulator; + + int32_t mSources; + float mXScale; + float mYScale; + float mXPrecision; + float mYPrecision; + sp<PointerControllerInterface> mPointerController; + + struct LockedState { + bool down; + nsecs_t downTime; + } mLocked; + + void initializeLocked(); + + void configureParameters(); + void dumpParameters(String8& dump); + + void sync(nsecs_t when); +}; + + +class TouchInputMapper : public InputMapper { +public: + TouchInputMapper(InputDevice* device); + virtual ~TouchInputMapper(); + + virtual uint32_t getSources(); + virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo); + virtual void dump(String8& dump); + virtual void configure(); + virtual void reset(); + + virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode); + virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode); + virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags); + +protected: + Mutex mLock; + + struct VirtualKey { + int32_t keyCode; + int32_t scanCode; + uint32_t flags; + + // computed hit box, specified in touch screen coords based on known display size + int32_t hitLeft; + int32_t hitTop; + int32_t hitRight; + int32_t hitBottom; + + inline bool isHit(int32_t x, int32_t y) const { + return x >= hitLeft && x <= hitRight && y >= hitTop && y <= hitBottom; + } + }; + + // Raw data for a single pointer. + struct PointerData { + uint32_t id; + int32_t x; + int32_t y; + int32_t pressure; + int32_t touchMajor; + int32_t touchMinor; + int32_t toolMajor; + int32_t toolMinor; + int32_t orientation; + + inline bool operator== (const PointerData& other) const { + return id == other.id + && x == other.x + && y == other.y + && pressure == other.pressure + && touchMajor == other.touchMajor + && touchMinor == other.touchMinor + && toolMajor == other.toolMajor + && toolMinor == other.toolMinor + && orientation == other.orientation; + } + inline bool operator!= (const PointerData& other) const { + return !(*this == other); + } + }; + + // Raw data for a collection of pointers including a pointer id mapping table. + struct TouchData { + uint32_t pointerCount; + PointerData pointers[MAX_POINTERS]; + BitSet32 idBits; + uint32_t idToIndex[MAX_POINTER_ID + 1]; + + void copyFrom(const TouchData& other) { + pointerCount = other.pointerCount; + idBits = other.idBits; + + for (uint32_t i = 0; i < pointerCount; i++) { + pointers[i] = other.pointers[i]; + + int id = pointers[i].id; + idToIndex[id] = other.idToIndex[id]; + } + } + + inline void clear() { + pointerCount = 0; + idBits.clear(); + } + }; + + // Input sources supported by the device. + int32_t mSources; + + // Immutable configuration parameters. + struct Parameters { + enum DeviceType { + DEVICE_TYPE_TOUCH_SCREEN, + DEVICE_TYPE_TOUCH_PAD, + }; + + DeviceType deviceType; + int32_t associatedDisplayId; + bool orientationAware; + + bool useBadTouchFilter; + bool useJumpyTouchFilter; + bool useAveragingTouchFilter; + nsecs_t virtualKeyQuietTime; + } mParameters; + + // Immutable calibration parameters in parsed form. + struct Calibration { + // Position + bool haveXOrigin; + int32_t xOrigin; + bool haveYOrigin; + int32_t yOrigin; + bool haveXScale; + float xScale; + bool haveYScale; + float yScale; + + // Touch Size + enum TouchSizeCalibration { + TOUCH_SIZE_CALIBRATION_DEFAULT, + TOUCH_SIZE_CALIBRATION_NONE, + TOUCH_SIZE_CALIBRATION_GEOMETRIC, + TOUCH_SIZE_CALIBRATION_PRESSURE, + }; + + TouchSizeCalibration touchSizeCalibration; + + // Tool Size + enum ToolSizeCalibration { + TOOL_SIZE_CALIBRATION_DEFAULT, + TOOL_SIZE_CALIBRATION_NONE, + TOOL_SIZE_CALIBRATION_GEOMETRIC, + TOOL_SIZE_CALIBRATION_LINEAR, + TOOL_SIZE_CALIBRATION_AREA, + }; + + ToolSizeCalibration toolSizeCalibration; + bool haveToolSizeLinearScale; + float toolSizeLinearScale; + bool haveToolSizeLinearBias; + float toolSizeLinearBias; + bool haveToolSizeAreaScale; + float toolSizeAreaScale; + bool haveToolSizeAreaBias; + float toolSizeAreaBias; + bool haveToolSizeIsSummed; + bool toolSizeIsSummed; + + // Pressure + enum PressureCalibration { + PRESSURE_CALIBRATION_DEFAULT, + PRESSURE_CALIBRATION_NONE, + PRESSURE_CALIBRATION_PHYSICAL, + PRESSURE_CALIBRATION_AMPLITUDE, + }; + enum PressureSource { + PRESSURE_SOURCE_DEFAULT, + PRESSURE_SOURCE_PRESSURE, + PRESSURE_SOURCE_TOUCH, + }; + + PressureCalibration pressureCalibration; + PressureSource pressureSource; + bool havePressureScale; + float pressureScale; + + // Size + enum SizeCalibration { + SIZE_CALIBRATION_DEFAULT, + SIZE_CALIBRATION_NONE, + SIZE_CALIBRATION_NORMALIZED, + }; + + SizeCalibration sizeCalibration; + + // Orientation + enum OrientationCalibration { + ORIENTATION_CALIBRATION_DEFAULT, + ORIENTATION_CALIBRATION_NONE, + ORIENTATION_CALIBRATION_INTERPOLATED, + ORIENTATION_CALIBRATION_VECTOR, + }; + + OrientationCalibration orientationCalibration; + } mCalibration; + + // Raw axis information from the driver. + struct RawAxes { + RawAbsoluteAxisInfo x; + RawAbsoluteAxisInfo y; + RawAbsoluteAxisInfo pressure; + RawAbsoluteAxisInfo touchMajor; + RawAbsoluteAxisInfo touchMinor; + RawAbsoluteAxisInfo toolMajor; + RawAbsoluteAxisInfo toolMinor; + RawAbsoluteAxisInfo orientation; + } mRawAxes; + + // Current and previous touch sample data. + TouchData mCurrentTouch; + TouchData mLastTouch; + + // The time the primary pointer last went down. + nsecs_t mDownTime; + + struct LockedState { + Vector<VirtualKey> virtualKeys; + + // The surface orientation and width and height set by configureSurfaceLocked(). + int32_t surfaceOrientation; + int32_t surfaceWidth, surfaceHeight; + + // Translation and scaling factors, orientation-independent. + int32_t xOrigin; + float xScale; + float xPrecision; + + int32_t yOrigin; + float yScale; + float yPrecision; + + float geometricScale; + + float toolSizeLinearScale; + float toolSizeLinearBias; + float toolSizeAreaScale; + float toolSizeAreaBias; + + float pressureScale; + + float sizeScale; + + float orientationScale; + + // Oriented motion ranges for input device info. + struct OrientedRanges { + InputDeviceInfo::MotionRange x; + InputDeviceInfo::MotionRange y; + + bool havePressure; + InputDeviceInfo::MotionRange pressure; + + bool haveSize; + InputDeviceInfo::MotionRange size; + + bool haveTouchSize; + InputDeviceInfo::MotionRange touchMajor; + InputDeviceInfo::MotionRange touchMinor; + + bool haveToolSize; + InputDeviceInfo::MotionRange toolMajor; + InputDeviceInfo::MotionRange toolMinor; + + bool haveOrientation; + InputDeviceInfo::MotionRange orientation; + } orientedRanges; + + // Oriented dimensions and precision. + float orientedSurfaceWidth, orientedSurfaceHeight; + float orientedXPrecision, orientedYPrecision; + + struct CurrentVirtualKeyState { + bool down; + nsecs_t downTime; + int32_t keyCode; + int32_t scanCode; + } currentVirtualKey; + } mLocked; + + virtual void configureParameters(); + virtual void dumpParameters(String8& dump); + virtual void configureRawAxes(); + virtual void dumpRawAxes(String8& dump); + virtual bool configureSurfaceLocked(); + virtual void dumpSurfaceLocked(String8& dump); + virtual void configureVirtualKeysLocked(); + virtual void dumpVirtualKeysLocked(String8& dump); + virtual void parseCalibration(); + virtual void resolveCalibration(); + virtual void dumpCalibration(String8& dump); + + enum TouchResult { + // Dispatch the touch normally. + DISPATCH_TOUCH, + // Do not dispatch the touch, but keep tracking the current stroke. + SKIP_TOUCH, + // Do not dispatch the touch, and drop all information associated with the current stoke + // so the next movement will appear as a new down. + DROP_STROKE + }; + + void syncTouch(nsecs_t when, bool havePointerIds); + +private: + /* Maximum number of historical samples to average. */ + static const uint32_t AVERAGING_HISTORY_SIZE = 5; + + /* Slop distance for jumpy pointer detection. + * The vertical range of the screen divided by this is our epsilon value. */ + static const uint32_t JUMPY_EPSILON_DIVISOR = 212; + + /* Number of jumpy points to drop for touchscreens that need it. */ + static const uint32_t JUMPY_TRANSITION_DROPS = 3; + static const uint32_t JUMPY_DROP_LIMIT = 3; + + /* Maximum squared distance for averaging. + * If moving farther than this, turn of averaging to avoid lag in response. */ + static const uint64_t AVERAGING_DISTANCE_LIMIT = 75 * 75; + + struct AveragingTouchFilterState { + // Individual history tracks are stored by pointer id + uint32_t historyStart[MAX_POINTERS]; + uint32_t historyEnd[MAX_POINTERS]; + struct { + struct { + int32_t x; + int32_t y; + int32_t pressure; + } pointers[MAX_POINTERS]; + } historyData[AVERAGING_HISTORY_SIZE]; + } mAveragingTouchFilter; + + struct JumpyTouchFilterState { + uint32_t jumpyPointsDropped; + } mJumpyTouchFilter; + + struct PointerDistanceHeapElement { + uint32_t currentPointerIndex : 8; + uint32_t lastPointerIndex : 8; + uint64_t distance : 48; // squared distance + }; + + void initializeLocked(); + + TouchResult consumeOffScreenTouches(nsecs_t when, uint32_t policyFlags); + void dispatchTouches(nsecs_t when, uint32_t policyFlags); + void dispatchTouch(nsecs_t when, uint32_t policyFlags, TouchData* touch, + BitSet32 idBits, uint32_t changedId, uint32_t pointerCount, + int32_t motionEventAction); + void detectGestures(nsecs_t when); + + bool isPointInsideSurfaceLocked(int32_t x, int32_t y); + const VirtualKey* findVirtualKeyHitLocked(int32_t x, int32_t y); + + bool applyBadTouchFilter(); + bool applyJumpyTouchFilter(); + void applyAveragingTouchFilter(); + void calculatePointerIds(); +}; + + +class SingleTouchInputMapper : public TouchInputMapper { +public: + SingleTouchInputMapper(InputDevice* device); + virtual ~SingleTouchInputMapper(); + + virtual void reset(); + virtual void process(const RawEvent* rawEvent); + +protected: + virtual void configureRawAxes(); + +private: + struct Accumulator { + enum { + FIELD_BTN_TOUCH = 1, + FIELD_ABS_X = 2, + FIELD_ABS_Y = 4, + FIELD_ABS_PRESSURE = 8, + FIELD_ABS_TOOL_WIDTH = 16 + }; + + uint32_t fields; + + bool btnTouch; + int32_t absX; + int32_t absY; + int32_t absPressure; + int32_t absToolWidth; + + inline void clear() { + fields = 0; + } + } mAccumulator; + + bool mDown; + int32_t mX; + int32_t mY; + int32_t mPressure; + int32_t mToolWidth; + + void initialize(); + + void sync(nsecs_t when); +}; + + +class MultiTouchInputMapper : public TouchInputMapper { +public: + MultiTouchInputMapper(InputDevice* device); + virtual ~MultiTouchInputMapper(); + + virtual void reset(); + virtual void process(const RawEvent* rawEvent); + +protected: + virtual void configureRawAxes(); + +private: + struct Accumulator { + enum { + FIELD_ABS_MT_POSITION_X = 1, + FIELD_ABS_MT_POSITION_Y = 2, + FIELD_ABS_MT_TOUCH_MAJOR = 4, + FIELD_ABS_MT_TOUCH_MINOR = 8, + FIELD_ABS_MT_WIDTH_MAJOR = 16, + FIELD_ABS_MT_WIDTH_MINOR = 32, + FIELD_ABS_MT_ORIENTATION = 64, + FIELD_ABS_MT_TRACKING_ID = 128, + FIELD_ABS_MT_PRESSURE = 256, + }; + + uint32_t pointerCount; + struct Pointer { + uint32_t fields; + + int32_t absMTPositionX; + int32_t absMTPositionY; + int32_t absMTTouchMajor; + int32_t absMTTouchMinor; + int32_t absMTWidthMajor; + int32_t absMTWidthMinor; + int32_t absMTOrientation; + int32_t absMTTrackingId; + int32_t absMTPressure; + + inline void clear() { + fields = 0; + } + } pointers[MAX_POINTERS + 1]; // + 1 to remove the need for extra range checks + + inline void clear() { + pointerCount = 0; + pointers[0].clear(); + } + } mAccumulator; + + void initialize(); + + void sync(nsecs_t when); +}; + +} // namespace android + +#endif // _UI_INPUT_READER_H diff --git a/services/input/InputWindow.cpp b/services/input/InputWindow.cpp new file mode 100644 index 0000000..b552f6d --- /dev/null +++ b/services/input/InputWindow.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011 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. + */ + +#define LOG_TAG "InputWindow" + +#include "InputWindow.h" + +#include <cutils/log.h> + +namespace android { + +// --- InputWindow --- + +bool InputWindow::touchableRegionContainsPoint(int32_t x, int32_t y) const { + return touchableRegion.contains(x, y); +} + +bool InputWindow::frameContainsPoint(int32_t x, int32_t y) const { + return x >= frameLeft && x <= frameRight + && y >= frameTop && y <= frameBottom; +} + +bool InputWindow::isTrustedOverlay() const { + return layoutParamsType == TYPE_INPUT_METHOD + || layoutParamsType == TYPE_INPUT_METHOD_DIALOG + || layoutParamsType == TYPE_SECURE_SYSTEM_OVERLAY; +} + +bool InputWindow::supportsSplitTouch() const { + return layoutParamsFlags & InputWindow::FLAG_SPLIT_TOUCH; +} + +} // namespace android diff --git a/services/input/InputWindow.h b/services/input/InputWindow.h new file mode 100644 index 0000000..9c43067 --- /dev/null +++ b/services/input/InputWindow.h @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef _UI_INPUT_WINDOW_H +#define _UI_INPUT_WINDOW_H + +#include <ui/Input.h> +#include <ui/InputTransport.h> +#include <utils/RefBase.h> +#include <utils/Timers.h> +#include <utils/String8.h> + +#include <SkRegion.h> + +#include "InputApplication.h" + +namespace android { + +/* + * A handle to a window that can receive input. + * Used by the native input dispatcher to indirectly refer to the window manager objects + * that describe a window. + */ +class InputWindowHandle : public RefBase { +protected: + InputWindowHandle(const sp<InputApplicationHandle>& inputApplicationHandle) : + mInputApplicationHandle(inputApplicationHandle) { } + virtual ~InputWindowHandle() { } + +public: + inline sp<InputApplicationHandle> getInputApplicationHandle() { + return mInputApplicationHandle; + } + +private: + sp<InputApplicationHandle> mInputApplicationHandle; +}; + + +/* + * An input window describes the bounds of a window that can receive input. + */ +struct InputWindow { + // Window flags from WindowManager.LayoutParams + enum { + FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001, + FLAG_DIM_BEHIND = 0x00000002, + FLAG_BLUR_BEHIND = 0x00000004, + FLAG_NOT_FOCUSABLE = 0x00000008, + FLAG_NOT_TOUCHABLE = 0x00000010, + FLAG_NOT_TOUCH_MODAL = 0x00000020, + FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040, + FLAG_KEEP_SCREEN_ON = 0x00000080, + FLAG_LAYOUT_IN_SCREEN = 0x00000100, + FLAG_LAYOUT_NO_LIMITS = 0x00000200, + FLAG_FULLSCREEN = 0x00000400, + FLAG_FORCE_NOT_FULLSCREEN = 0x00000800, + FLAG_DITHER = 0x00001000, + FLAG_SECURE = 0x00002000, + FLAG_SCALED = 0x00004000, + FLAG_IGNORE_CHEEK_PRESSES = 0x00008000, + FLAG_LAYOUT_INSET_DECOR = 0x00010000, + FLAG_ALT_FOCUSABLE_IM = 0x00020000, + FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000, + FLAG_SHOW_WHEN_LOCKED = 0x00080000, + FLAG_SHOW_WALLPAPER = 0x00100000, + FLAG_TURN_SCREEN_ON = 0x00200000, + FLAG_DISMISS_KEYGUARD = 0x00400000, + FLAG_SPLIT_TOUCH = 0x00800000, + FLAG_KEEP_SURFACE_WHILE_ANIMATING = 0x10000000, + FLAG_COMPATIBLE_WINDOW = 0x20000000, + FLAG_SYSTEM_ERROR = 0x40000000, + }; + + // Window types from WindowManager.LayoutParams + enum { + FIRST_APPLICATION_WINDOW = 1, + TYPE_BASE_APPLICATION = 1, + TYPE_APPLICATION = 2, + TYPE_APPLICATION_STARTING = 3, + LAST_APPLICATION_WINDOW = 99, + FIRST_SUB_WINDOW = 1000, + TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW, + TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1, + TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2, + TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3, + TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW+4, + LAST_SUB_WINDOW = 1999, + FIRST_SYSTEM_WINDOW = 2000, + TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW, + TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1, + TYPE_PHONE = FIRST_SYSTEM_WINDOW+2, + TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3, + TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4, + TYPE_TOAST = FIRST_SYSTEM_WINDOW+5, + TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6, + TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7, + TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8, + TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9, + TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10, + TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11, + TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12, + TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13, + TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+14, + TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15, + TYPE_DRAG = FIRST_SYSTEM_WINDOW+16, + TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+17, + LAST_SYSTEM_WINDOW = 2999, + }; + + sp<InputWindowHandle> inputWindowHandle; + sp<InputChannel> inputChannel; + String8 name; + int32_t layoutParamsFlags; + int32_t layoutParamsType; + nsecs_t dispatchingTimeout; + int32_t frameLeft; + int32_t frameTop; + int32_t frameRight; + int32_t frameBottom; + SkRegion touchableRegion; + bool visible; + bool canReceiveKeys; + bool hasFocus; + bool hasWallpaper; + bool paused; + int32_t layer; + int32_t ownerPid; + int32_t ownerUid; + + bool touchableRegionContainsPoint(int32_t x, int32_t y) const; + bool frameContainsPoint(int32_t x, int32_t y) const; + + /* Returns true if the window is of a trusted type that is allowed to silently + * overlay other windows for the purpose of implementing the secure views feature. + * Trusted overlays, such as IME windows, can partly obscure other windows without causing + * motion events to be delivered to them with AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED. + */ + bool isTrustedOverlay() const; + + bool supportsSplitTouch() const; +}; + +} // namespace android + +#endif // _UI_INPUT_WINDOW_H diff --git a/services/input/PointerController.cpp b/services/input/PointerController.cpp new file mode 100644 index 0000000..92af51e --- /dev/null +++ b/services/input/PointerController.cpp @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2010 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. + */ + +#define LOG_TAG "PointerController" + +//#define LOG_NDEBUG 0 + +// Log debug messages about pointer updates +#define DEBUG_POINTER_UPDATES 0 + +#include "PointerController.h" + +#include <cutils/log.h> + +#include <SkBitmap.h> +#include <SkCanvas.h> +#include <SkColor.h> +#include <SkPaint.h> +#include <SkXfermode.h> + +namespace android { + +// --- PointerController --- + +PointerController::PointerController(int32_t pointerLayer) : + mPointerLayer(pointerLayer) { + AutoMutex _l(mLock); + + mLocked.displayWidth = -1; + mLocked.displayHeight = -1; + mLocked.displayOrientation = DISPLAY_ORIENTATION_0; + + mLocked.pointerX = 0; + mLocked.pointerY = 0; + mLocked.buttonState = 0; + + mLocked.iconBitmap = NULL; + mLocked.iconHotSpotX = 0; + mLocked.iconHotSpotY = 0; + + mLocked.wantVisible = false; + mLocked.visible = false; + mLocked.drawn = false; +} + +PointerController::~PointerController() { + if (mSurfaceControl != NULL) { + mSurfaceControl->clear(); + mSurfaceControl.clear(); + } + + if (mSurfaceComposerClient != NULL) { + mSurfaceComposerClient->dispose(); + mSurfaceComposerClient.clear(); + } + + delete mLocked.iconBitmap; +} + +bool PointerController::getBounds(float* outMinX, float* outMinY, + float* outMaxX, float* outMaxY) const { + AutoMutex _l(mLock); + + return getBoundsLocked(outMinX, outMinY, outMaxX, outMaxY); +} + +bool PointerController::getBoundsLocked(float* outMinX, float* outMinY, + float* outMaxX, float* outMaxY) const { + if (mLocked.displayWidth <= 0 || mLocked.displayHeight <= 0) { + return false; + } + + *outMinX = 0; + *outMinY = 0; + switch (mLocked.displayOrientation) { + case DISPLAY_ORIENTATION_90: + case DISPLAY_ORIENTATION_270: + *outMaxX = mLocked.displayHeight; + *outMaxY = mLocked.displayWidth; + break; + default: + *outMaxX = mLocked.displayWidth; + *outMaxY = mLocked.displayHeight; + break; + } + return true; +} + +void PointerController::move(float deltaX, float deltaY) { +#if DEBUG_POINTER_UPDATES + LOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY); +#endif + if (deltaX == 0.0f && deltaY == 0.0f) { + return; + } + + AutoMutex _l(mLock); + + setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY); +} + +void PointerController::setButtonState(uint32_t buttonState) { +#if DEBUG_POINTER_UPDATES + LOGD("Set button state 0x%08x", buttonState); +#endif + AutoMutex _l(mLock); + + if (mLocked.buttonState != buttonState) { + mLocked.buttonState = buttonState; + mLocked.wantVisible = true; + updateLocked(); + } +} + +uint32_t PointerController::getButtonState() const { + AutoMutex _l(mLock); + + return mLocked.buttonState; +} + +void PointerController::setPosition(float x, float y) { +#if DEBUG_POINTER_UPDATES + LOGD("Set pointer position to x=%0.3f, y=%0.3f", x, y); +#endif + AutoMutex _l(mLock); + + setPositionLocked(x, y); +} + +void PointerController::setPositionLocked(float x, float y) { + float minX, minY, maxX, maxY; + if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) { + if (x <= minX) { + mLocked.pointerX = minX; + } else if (x >= maxX) { + mLocked.pointerX = maxX; + } else { + mLocked.pointerX = x; + } + if (y <= minY) { + mLocked.pointerY = minY; + } else if (y >= maxY) { + mLocked.pointerY = maxY; + } else { + mLocked.pointerY = y; + } + mLocked.wantVisible = true; + updateLocked(); + } +} + +void PointerController::getPosition(float* outX, float* outY) const { + AutoMutex _l(mLock); + + *outX = mLocked.pointerX; + *outY = mLocked.pointerY; +} + +void PointerController::updateLocked() { + bool wantVisibleAndHavePointerIcon = mLocked.wantVisible && mLocked.iconBitmap; + + if (wantVisibleAndHavePointerIcon) { + // Want the pointer to be visible. + // Ensure the surface is created and drawn. + if (!createSurfaceIfNeededLocked() || !drawPointerIfNeededLocked()) { + return; + } + } else { + // Don't want the pointer to be visible. + // If it is not visible then we are done. + if (mSurfaceControl == NULL || !mLocked.visible) { + return; + } + } + + status_t status = mSurfaceComposerClient->openTransaction(); + if (status) { + LOGE("Error opening surface transaction to update pointer surface."); + return; + } + + if (wantVisibleAndHavePointerIcon) { + status = mSurfaceControl->setPosition( + mLocked.pointerX - mLocked.iconHotSpotX, + mLocked.pointerY - mLocked.iconHotSpotY); + if (status) { + LOGE("Error %d moving pointer surface.", status); + goto CloseTransaction; + } + + if (!mLocked.visible) { + status = mSurfaceControl->setLayer(mPointerLayer); + if (status) { + LOGE("Error %d setting pointer surface layer.", status); + goto CloseTransaction; + } + + status = mSurfaceControl->show(mPointerLayer); + if (status) { + LOGE("Error %d showing pointer surface.", status); + goto CloseTransaction; + } + + mLocked.visible = true; + } + } else { + if (mLocked.visible) { + status = mSurfaceControl->hide(); + if (status) { + LOGE("Error %d hiding pointer surface.", status); + goto CloseTransaction; + } + + mLocked.visible = false; + } + } + +CloseTransaction: + status = mSurfaceComposerClient->closeTransaction(); + if (status) { + LOGE("Error closing surface transaction to update pointer surface."); + } +} + +void PointerController::setDisplaySize(int32_t width, int32_t height) { + AutoMutex _l(mLock); + + if (mLocked.displayWidth != width || mLocked.displayHeight != height) { + mLocked.displayWidth = width; + mLocked.displayHeight = height; + + float minX, minY, maxX, maxY; + if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) { + mLocked.pointerX = (minX + maxX) * 0.5f; + mLocked.pointerY = (minY + maxY) * 0.5f; + } else { + mLocked.pointerX = 0; + mLocked.pointerY = 0; + } + + updateLocked(); + } +} + +void PointerController::setDisplayOrientation(int32_t orientation) { + AutoMutex _l(mLock); + + if (mLocked.displayOrientation != orientation) { + float absoluteX, absoluteY; + + // Map from oriented display coordinates to absolute display coordinates. + switch (mLocked.displayOrientation) { + case DISPLAY_ORIENTATION_90: + absoluteX = mLocked.displayWidth - mLocked.pointerY; + absoluteY = mLocked.pointerX; + break; + case DISPLAY_ORIENTATION_180: + absoluteX = mLocked.displayWidth - mLocked.pointerX; + absoluteY = mLocked.displayHeight - mLocked.pointerY; + break; + case DISPLAY_ORIENTATION_270: + absoluteX = mLocked.pointerY; + absoluteY = mLocked.displayHeight - mLocked.pointerX; + break; + default: + absoluteX = mLocked.pointerX; + absoluteY = mLocked.pointerY; + break; + } + + // Map from absolute display coordinates to oriented display coordinates. + switch (orientation) { + case DISPLAY_ORIENTATION_90: + mLocked.pointerX = absoluteY; + mLocked.pointerY = mLocked.displayWidth - absoluteX; + break; + case DISPLAY_ORIENTATION_180: + mLocked.pointerX = mLocked.displayWidth - absoluteX; + mLocked.pointerY = mLocked.displayHeight - absoluteY; + break; + case DISPLAY_ORIENTATION_270: + mLocked.pointerX = mLocked.displayHeight - absoluteY; + mLocked.pointerY = absoluteX; + break; + default: + mLocked.pointerX = absoluteX; + mLocked.pointerY = absoluteY; + break; + } + + mLocked.displayOrientation = orientation; + + updateLocked(); + } +} + +void PointerController::setPointerIcon(const SkBitmap* bitmap, float hotSpotX, float hotSpotY) { + AutoMutex _l(mLock); + + if (mLocked.iconBitmap) { + delete mLocked.iconBitmap; + mLocked.iconBitmap = NULL; + } + + if (bitmap) { + mLocked.iconBitmap = new SkBitmap(); + bitmap->copyTo(mLocked.iconBitmap, SkBitmap::kARGB_8888_Config); + } + + mLocked.iconHotSpotX = hotSpotX; + mLocked.iconHotSpotY = hotSpotY; + mLocked.drawn = false; +} + +bool PointerController::createSurfaceIfNeededLocked() { + if (!mLocked.iconBitmap) { + // If we don't have a pointer icon, then no point allocating a surface now. + return false; + } + + if (mSurfaceComposerClient == NULL) { + mSurfaceComposerClient = new SurfaceComposerClient(); + } + + if (mSurfaceControl == NULL) { + mSurfaceControl = mSurfaceComposerClient->createSurface(getpid(), + String8("Pointer Icon"), 0, + mLocked.iconBitmap->width(), mLocked.iconBitmap->height(), + PIXEL_FORMAT_RGBA_8888); + if (mSurfaceControl == NULL) { + LOGE("Error creating pointer surface."); + return false; + } + } + return true; +} + +bool PointerController::drawPointerIfNeededLocked() { + if (!mLocked.drawn) { + if (!mLocked.iconBitmap) { + return false; + } + + if (!resizeSurfaceLocked(mLocked.iconBitmap->width(), mLocked.iconBitmap->height())) { + return false; + } + + sp<Surface> surface = mSurfaceControl->getSurface(); + + Surface::SurfaceInfo surfaceInfo; + status_t status = surface->lock(&surfaceInfo); + if (status) { + LOGE("Error %d locking pointer surface before drawing.", status); + return false; + } + + SkBitmap surfaceBitmap; + ssize_t bpr = surfaceInfo.s * bytesPerPixel(surfaceInfo.format); + surfaceBitmap.setConfig(SkBitmap::kARGB_8888_Config, surfaceInfo.w, surfaceInfo.h, bpr); + surfaceBitmap.setPixels(surfaceInfo.bits); + + SkCanvas surfaceCanvas; + surfaceCanvas.setBitmapDevice(surfaceBitmap); + + SkPaint paint; + paint.setXfermodeMode(SkXfermode::kSrc_Mode); + surfaceCanvas.drawBitmap(*mLocked.iconBitmap, 0, 0, &paint); + + status = surface->unlockAndPost(); + if (status) { + LOGE("Error %d unlocking pointer surface after drawing.", status); + return false; + } + } + + mLocked.drawn = true; + return true; +} + +bool PointerController::resizeSurfaceLocked(int32_t width, int32_t height) { + status_t status = mSurfaceComposerClient->openTransaction(); + if (status) { + LOGE("Error opening surface transaction to resize pointer surface."); + return false; + } + + status = mSurfaceControl->setSize(width, height); + if (status) { + LOGE("Error %d setting pointer surface size.", status); + return false; + } + + status = mSurfaceComposerClient->closeTransaction(); + if (status) { + LOGE("Error closing surface transaction to resize pointer surface."); + return false; + } + + return true; +} + +} // namespace android diff --git a/services/input/PointerController.h b/services/input/PointerController.h new file mode 100644 index 0000000..a2a9955 --- /dev/null +++ b/services/input/PointerController.h @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2010 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. + */ + +#ifndef _UI_POINTER_CONTROLLER_H +#define _UI_POINTER_CONTROLLER_H + +#include <ui/DisplayInfo.h> +#include <utils/RefBase.h> +#include <utils/String8.h> + +#include <surfaceflinger/Surface.h> +#include <surfaceflinger/SurfaceComposerClient.h> +#include <surfaceflinger/ISurfaceComposer.h> + +#include <SkBitmap.h> + +namespace android { + +enum { + POINTER_BUTTON_1 = 1 << 0, +}; + +/** + * Interface for tracking a single (mouse) pointer. + * + * The pointer controller is responsible for providing synchronization and for tracking + * display orientation changes if needed. + */ +class PointerControllerInterface : public virtual RefBase { +protected: + PointerControllerInterface() { } + virtual ~PointerControllerInterface() { } + +public: + /* Gets the bounds of the region that the pointer can traverse. + * Returns true if the bounds are available. */ + virtual bool getBounds(float* outMinX, float* outMinY, + float* outMaxX, float* outMaxY) const = 0; + + /* Move the pointer. */ + virtual void move(float deltaX, float deltaY) = 0; + + /* Sets a mask that indicates which buttons are pressed. */ + virtual void setButtonState(uint32_t buttonState) = 0; + + /* Gets a mask that indicates which buttons are pressed. */ + virtual uint32_t getButtonState() const = 0; + + /* Sets the absolute location of the pointer. */ + virtual void setPosition(float x, float y) = 0; + + /* Gets the absolute location of the pointer. */ + virtual void getPosition(float* outX, float* outY) const = 0; +}; + + +/* + * Tracks pointer movements and draws the pointer sprite to a surface. + * + * Handles pointer acceleration and animation. + */ +class PointerController : public PointerControllerInterface { +protected: + virtual ~PointerController(); + +public: + PointerController(int32_t pointerLayer); + + virtual bool getBounds(float* outMinX, float* outMinY, + float* outMaxX, float* outMaxY) const; + virtual void move(float deltaX, float deltaY); + virtual void setButtonState(uint32_t buttonState); + virtual uint32_t getButtonState() const; + virtual void setPosition(float x, float y); + virtual void getPosition(float* outX, float* outY) const; + + void setDisplaySize(int32_t width, int32_t height); + void setDisplayOrientation(int32_t orientation); + void setPointerIcon(const SkBitmap* bitmap, float hotSpotX, float hotSpotY); + +private: + mutable Mutex mLock; + + int32_t mPointerLayer; + sp<SurfaceComposerClient> mSurfaceComposerClient; + sp<SurfaceControl> mSurfaceControl; + + struct Locked { + int32_t displayWidth; + int32_t displayHeight; + int32_t displayOrientation; + + float pointerX; + float pointerY; + uint32_t buttonState; + + SkBitmap* iconBitmap; + float iconHotSpotX; + float iconHotSpotY; + + bool wantVisible; + bool visible; + bool drawn; + } mLocked; + + bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const; + void setPositionLocked(float x, float y); + void updateLocked(); + bool createSurfaceIfNeededLocked(); + bool drawPointerIfNeededLocked(); + bool resizeSurfaceLocked(int32_t width, int32_t height); +}; + +} // namespace android + +#endif // _UI_POINTER_CONTROLLER_H diff --git a/services/input/tests/Android.mk b/services/input/tests/Android.mk new file mode 100644 index 0000000..799eb76 --- /dev/null +++ b/services/input/tests/Android.mk @@ -0,0 +1,50 @@ +# Build the unit tests. +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +ifneq ($(TARGET_SIMULATOR),true) + +# Build the unit tests. +test_src_files := \ + InputReader_test.cpp \ + InputDispatcher_test.cpp + +shared_libraries := \ + libcutils \ + libutils \ + libhardware \ + libhardware_legacy \ + libui \ + libsurfaceflinger_client \ + libskia \ + libstlport \ + libinput + +static_libraries := \ + libgtest \ + libgtest_main + +c_includes := \ + bionic \ + bionic/libstdc++/include \ + external/gtest/include \ + external/stlport/stlport \ + external/skia/include/core + +module_tags := eng tests + +$(foreach file,$(test_src_files), \ + $(eval include $(CLEAR_VARS)) \ + $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \ + $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \ + $(eval LOCAL_C_INCLUDES := $(c_includes)) \ + $(eval LOCAL_SRC_FILES := $(file)) \ + $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \ + $(eval LOCAL_MODULE_TAGS := $(module_tags)) \ + $(eval include $(BUILD_EXECUTABLE)) \ +) + +# Build the manual test programs. +include $(call all-subdir-makefiles) + +endif
\ No newline at end of file diff --git a/services/input/tests/InputDispatcher_test.cpp b/services/input/tests/InputDispatcher_test.cpp new file mode 100644 index 0000000..5ba1867 --- /dev/null +++ b/services/input/tests/InputDispatcher_test.cpp @@ -0,0 +1,230 @@ +// +// Copyright 2010 The Android Open Source Project +// + +#include "../InputDispatcher.h" + +#include <gtest/gtest.h> +#include <linux/input.h> + +namespace android { + +// An arbitrary time value. +static const nsecs_t ARBITRARY_TIME = 1234; + +// An arbitrary device id. +static const int32_t DEVICE_ID = 1; + +// An arbitrary injector pid / uid pair that has permission to inject events. +static const int32_t INJECTOR_PID = 999; +static const int32_t INJECTOR_UID = 1001; + + +// --- FakeInputDispatcherPolicy --- + +class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface { +protected: + virtual ~FakeInputDispatcherPolicy() { + } + +public: + FakeInputDispatcherPolicy() { + } + +private: + virtual void notifyConfigurationChanged(nsecs_t when) { + } + + virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle, + const sp<InputWindowHandle>& inputWindowHandle) { + return 0; + } + + virtual void notifyInputChannelBroken(const sp<InputWindowHandle>& inputWindowHandle) { + } + + virtual nsecs_t getKeyRepeatTimeout() { + return 500 * 1000000LL; + } + + virtual nsecs_t getKeyRepeatDelay() { + return 50 * 1000000LL; + } + + virtual int32_t getMaxEventsPerSecond() { + return 60; + } + + virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) { + } + + virtual void interceptGenericBeforeQueueing(nsecs_t when, uint32_t& policyFlags) { + } + + virtual bool interceptKeyBeforeDispatching(const sp<InputWindowHandle>& inputWindowHandle, + const KeyEvent* keyEvent, uint32_t policyFlags) { + return false; + } + + virtual bool dispatchUnhandledKey(const sp<InputWindowHandle>& inputWindowHandle, + const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) { + return false; + } + + virtual void notifySwitch(nsecs_t when, + int32_t switchCode, int32_t switchValue, uint32_t policyFlags) { + } + + virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType) { + } + + virtual bool checkInjectEventsPermissionNonReentrant( + int32_t injectorPid, int32_t injectorUid) { + return false; + } +}; + + +// --- InputDispatcherTest --- + +class InputDispatcherTest : public testing::Test { +protected: + sp<FakeInputDispatcherPolicy> mFakePolicy; + sp<InputDispatcher> mDispatcher; + + virtual void SetUp() { + mFakePolicy = new FakeInputDispatcherPolicy(); + mDispatcher = new InputDispatcher(mFakePolicy); + } + + virtual void TearDown() { + mFakePolicy.clear(); + mDispatcher.clear(); + } +}; + + +TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) { + KeyEvent event; + + // Rejects undefined key actions. + event.initialize(DEVICE_ID, AINPUT_SOURCE_KEYBOARD, + /*action*/ -1, 0, + AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME); + ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + << "Should reject key events with undefined action."; + + // Rejects ACTION_MULTIPLE since it is not supported despite being defined in the API. + event.initialize(DEVICE_ID, AINPUT_SOURCE_KEYBOARD, + AKEY_EVENT_ACTION_MULTIPLE, 0, + AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME); + ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + << "Should reject key events with ACTION_MULTIPLE."; +} + +TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { + MotionEvent event; + int32_t pointerIds[MAX_POINTERS + 1]; + PointerCoords pointerCoords[MAX_POINTERS + 1]; + for (int i = 0; i <= MAX_POINTERS; i++) { + pointerIds[i] = i; + } + + // Rejects undefined motion actions. + event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, + /*action*/ -1, 0, 0, AMETA_NONE, 0, 0, 0, 0, + ARBITRARY_TIME, ARBITRARY_TIME, + /*pointerCount*/ 1, pointerIds, pointerCoords); + ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + << "Should reject motion events with undefined action."; + + // Rejects pointer down with invalid index. + event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, + AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + 0, 0, AMETA_NONE, 0, 0, 0, 0, + ARBITRARY_TIME, ARBITRARY_TIME, + /*pointerCount*/ 1, pointerIds, pointerCoords); + ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + << "Should reject motion events with pointer down index too large."; + + event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, + AMOTION_EVENT_ACTION_POINTER_DOWN | (-1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + 0, 0, AMETA_NONE, 0, 0, 0, 0, + ARBITRARY_TIME, ARBITRARY_TIME, + /*pointerCount*/ 1, pointerIds, pointerCoords); + ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + << "Should reject motion events with pointer down index too small."; + + // Rejects pointer up with invalid index. + event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, + AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + 0, 0, AMETA_NONE, 0, 0, 0, 0, + ARBITRARY_TIME, ARBITRARY_TIME, + /*pointerCount*/ 1, pointerIds, pointerCoords); + ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + << "Should reject motion events with pointer up index too large."; + + event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, + AMOTION_EVENT_ACTION_POINTER_UP | (-1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + 0, 0, AMETA_NONE, 0, 0, 0, 0, + ARBITRARY_TIME, ARBITRARY_TIME, + /*pointerCount*/ 1, pointerIds, pointerCoords); + ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + << "Should reject motion events with pointer up index too small."; + + // Rejects motion events with invalid number of pointers. + event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, + AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0, + ARBITRARY_TIME, ARBITRARY_TIME, + /*pointerCount*/ 0, pointerIds, pointerCoords); + ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + << "Should reject motion events with 0 pointers."; + + event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, + AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0, + ARBITRARY_TIME, ARBITRARY_TIME, + /*pointerCount*/ MAX_POINTERS + 1, pointerIds, pointerCoords); + ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + << "Should reject motion events with more than MAX_POINTERS pointers."; + + // Rejects motion events with invalid pointer ids. + pointerIds[0] = -1; + event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, + AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0, + ARBITRARY_TIME, ARBITRARY_TIME, + /*pointerCount*/ 1, pointerIds, pointerCoords); + ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + << "Should reject motion events with pointer ids less than 0."; + + pointerIds[0] = MAX_POINTER_ID + 1; + event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, + AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0, + ARBITRARY_TIME, ARBITRARY_TIME, + /*pointerCount*/ 1, pointerIds, pointerCoords); + ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + << "Should reject motion events with pointer ids greater than MAX_POINTER_ID."; + + // Rejects motion events with duplicate pointer ids. + pointerIds[0] = 1; + pointerIds[1] = 1; + event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, + AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0, + ARBITRARY_TIME, ARBITRARY_TIME, + /*pointerCount*/ 2, pointerIds, pointerCoords); + ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + << "Should reject motion events with duplicate pointer ids."; +} + +} // namespace android diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp new file mode 100644 index 0000000..25030d8 --- /dev/null +++ b/services/input/tests/InputReader_test.cpp @@ -0,0 +1,3696 @@ +// +// Copyright 2010 The Android Open Source Project +// + +#include "../InputReader.h" + +#include <utils/List.h> +#include <gtest/gtest.h> +#include <math.h> + +namespace android { + +// An arbitrary time value. +static const nsecs_t ARBITRARY_TIME = 1234; + +// Arbitrary display properties. +static const int32_t DISPLAY_ID = 0; +static const int32_t DISPLAY_WIDTH = 480; +static const int32_t DISPLAY_HEIGHT = 800; + +// Error tolerance for floating point assertions. +static const float EPSILON = 0.001f; + +template<typename T> +static inline T min(T a, T b) { + return a < b ? a : b; +} + +static inline float avg(float x, float y) { + return (x + y) / 2; +} + + +// --- FakePointerController --- + +class FakePointerController : public PointerControllerInterface { + bool mHaveBounds; + float mMinX, mMinY, mMaxX, mMaxY; + +protected: + virtual ~FakePointerController() { } + +public: + FakePointerController() : + mHaveBounds(false), mMinX(0), mMinY(0), mMaxX(0), mMaxY(0) { + } + + void setBounds(float minX, float minY, float maxX, float maxY) { + mHaveBounds = true; + mMinX = minX; + mMinY = minY; + mMaxX = maxX; + mMaxY = maxY; + } + +private: + virtual bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const { + *outMinX = mMinX; + *outMinY = mMinY; + *outMaxX = mMaxX; + *outMaxY = mMaxY; + return mHaveBounds; + } + + virtual void move(float deltaX, float deltaY) { + } + + virtual void setButtonState(uint32_t buttonState) { + } + + virtual uint32_t getButtonState() const { + return 0; + } + + virtual void setPosition(float x, float y) { + } + + virtual void getPosition(float* outX, float* outY) const { + *outX = 0; + *outY = 0; + } +}; + + +// --- FakeInputReaderPolicy --- + +class FakeInputReaderPolicy : public InputReaderPolicyInterface { + struct DisplayInfo { + int32_t width; + int32_t height; + int32_t orientation; + }; + + KeyedVector<int32_t, DisplayInfo> mDisplayInfos; + bool mFilterTouchEvents; + bool mFilterJumpyTouchEvents; + Vector<String8> mExcludedDeviceNames; + KeyedVector<int32_t, sp<FakePointerController> > mPointerControllers; + +protected: + virtual ~FakeInputReaderPolicy() { } + +public: + FakeInputReaderPolicy() : + mFilterTouchEvents(false), mFilterJumpyTouchEvents(false) { + } + + void removeDisplayInfo(int32_t displayId) { + mDisplayInfos.removeItem(displayId); + } + + void setDisplayInfo(int32_t displayId, int32_t width, int32_t height, int32_t orientation) { + removeDisplayInfo(displayId); + + DisplayInfo info; + info.width = width; + info.height = height; + info.orientation = orientation; + mDisplayInfos.add(displayId, info); + } + + void setFilterTouchEvents(bool enabled) { + mFilterTouchEvents = enabled; + } + + void setFilterJumpyTouchEvents(bool enabled) { + mFilterJumpyTouchEvents = enabled; + } + + virtual nsecs_t getVirtualKeyQuietTime() { + return 0; + } + + void addExcludedDeviceName(const String8& deviceName) { + mExcludedDeviceNames.push(deviceName); + } + + void setPointerController(int32_t deviceId, const sp<FakePointerController>& controller) { + mPointerControllers.add(deviceId, controller); + } + +private: + virtual bool getDisplayInfo(int32_t displayId, + int32_t* width, int32_t* height, int32_t* orientation) { + ssize_t index = mDisplayInfos.indexOfKey(displayId); + if (index >= 0) { + const DisplayInfo& info = mDisplayInfos.valueAt(index); + if (width) { + *width = info.width; + } + if (height) { + *height = info.height; + } + if (orientation) { + *orientation = info.orientation; + } + return true; + } + return false; + } + + virtual bool filterTouchEvents() { + return mFilterTouchEvents; + } + + virtual bool filterJumpyTouchEvents() { + return mFilterJumpyTouchEvents; + } + + virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames) { + outExcludedDeviceNames.appendVector(mExcludedDeviceNames); + } + + virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) { + return mPointerControllers.valueFor(deviceId); + } +}; + + +// --- FakeInputDispatcher --- + +class FakeInputDispatcher : public InputDispatcherInterface { +public: + struct NotifyConfigurationChangedArgs { + nsecs_t eventTime; + }; + + struct NotifyKeyArgs { + nsecs_t eventTime; + int32_t deviceId; + uint32_t source; + uint32_t policyFlags; + int32_t action; + int32_t flags; + int32_t keyCode; + int32_t scanCode; + int32_t metaState; + nsecs_t downTime; + }; + + struct NotifyMotionArgs { + nsecs_t eventTime; + int32_t deviceId; + uint32_t source; + uint32_t policyFlags; + int32_t action; + int32_t flags; + int32_t metaState; + int32_t edgeFlags; + uint32_t pointerCount; + Vector<int32_t> pointerIds; + Vector<PointerCoords> pointerCoords; + float xPrecision; + float yPrecision; + nsecs_t downTime; + }; + + struct NotifySwitchArgs { + nsecs_t when; + int32_t switchCode; + int32_t switchValue; + uint32_t policyFlags; + }; + +private: + List<NotifyConfigurationChangedArgs> mNotifyConfigurationChangedArgs; + List<NotifyKeyArgs> mNotifyKeyArgs; + List<NotifyMotionArgs> mNotifyMotionArgs; + List<NotifySwitchArgs> mNotifySwitchArgs; + +protected: + virtual ~FakeInputDispatcher() { } + +public: + FakeInputDispatcher() { + } + + void assertNotifyConfigurationChangedWasCalled(NotifyConfigurationChangedArgs* outArgs = NULL) { + ASSERT_FALSE(mNotifyConfigurationChangedArgs.empty()) + << "Expected notifyConfigurationChanged() to have been called."; + if (outArgs) { + *outArgs = *mNotifyConfigurationChangedArgs.begin(); + } + mNotifyConfigurationChangedArgs.erase(mNotifyConfigurationChangedArgs.begin()); + } + + void assertNotifyKeyWasCalled(NotifyKeyArgs* outArgs = NULL) { + ASSERT_FALSE(mNotifyKeyArgs.empty()) + << "Expected notifyKey() to have been called."; + if (outArgs) { + *outArgs = *mNotifyKeyArgs.begin(); + } + mNotifyKeyArgs.erase(mNotifyKeyArgs.begin()); + } + + void assertNotifyKeyWasNotCalled() { + ASSERT_TRUE(mNotifyKeyArgs.empty()) + << "Expected notifyKey() to not have been called."; + } + + void assertNotifyMotionWasCalled(NotifyMotionArgs* outArgs = NULL) { + ASSERT_FALSE(mNotifyMotionArgs.empty()) + << "Expected notifyMotion() to have been called."; + if (outArgs) { + *outArgs = *mNotifyMotionArgs.begin(); + } + mNotifyMotionArgs.erase(mNotifyMotionArgs.begin()); + } + + void assertNotifyMotionWasNotCalled() { + ASSERT_TRUE(mNotifyMotionArgs.empty()) + << "Expected notifyMotion() to not have been called."; + } + + void assertNotifySwitchWasCalled(NotifySwitchArgs* outArgs = NULL) { + ASSERT_FALSE(mNotifySwitchArgs.empty()) + << "Expected notifySwitch() to have been called."; + if (outArgs) { + *outArgs = *mNotifySwitchArgs.begin(); + } + mNotifySwitchArgs.erase(mNotifySwitchArgs.begin()); + } + +private: + virtual void notifyConfigurationChanged(nsecs_t eventTime) { + NotifyConfigurationChangedArgs args; + args.eventTime = eventTime; + mNotifyConfigurationChangedArgs.push_back(args); + } + + virtual void notifyKey(nsecs_t eventTime, int32_t deviceId, uint32_t source, + uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode, + int32_t scanCode, int32_t metaState, nsecs_t downTime) { + NotifyKeyArgs args; + args.eventTime = eventTime; + args.deviceId = deviceId; + args.source = source; + args.policyFlags = policyFlags; + args.action = action; + args.flags = flags; + args.keyCode = keyCode; + args.scanCode = scanCode; + args.metaState = metaState; + args.downTime = downTime; + mNotifyKeyArgs.push_back(args); + } + + virtual void notifyMotion(nsecs_t eventTime, int32_t deviceId, uint32_t source, + uint32_t policyFlags, int32_t action, int32_t flags, + int32_t metaState, int32_t edgeFlags, + uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords, + float xPrecision, float yPrecision, nsecs_t downTime) { + NotifyMotionArgs args; + args.eventTime = eventTime; + args.deviceId = deviceId; + args.source = source; + args.policyFlags = policyFlags; + args.action = action; + args.flags = flags; + args.metaState = metaState; + args.edgeFlags = edgeFlags; + args.pointerCount = pointerCount; + args.pointerIds.clear(); + args.pointerIds.appendArray(pointerIds, pointerCount); + args.pointerCoords.clear(); + args.pointerCoords.appendArray(pointerCoords, pointerCount); + args.xPrecision = xPrecision; + args.yPrecision = yPrecision; + args.downTime = downTime; + mNotifyMotionArgs.push_back(args); + } + + virtual void notifySwitch(nsecs_t when, + int32_t switchCode, int32_t switchValue, uint32_t policyFlags) { + NotifySwitchArgs args; + args.when = when; + args.switchCode = switchCode; + args.switchValue = switchValue; + args.policyFlags = policyFlags; + mNotifySwitchArgs.push_back(args); + } + + virtual void dump(String8& dump) { + ADD_FAILURE() << "Should never be called by input reader."; + } + + virtual void dispatchOnce() { + ADD_FAILURE() << "Should never be called by input reader."; + } + + virtual int32_t injectInputEvent(const InputEvent* event, + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) { + ADD_FAILURE() << "Should never be called by input reader."; + return INPUT_EVENT_INJECTION_FAILED; + } + + virtual void setInputWindows(const Vector<InputWindow>& inputWindows) { + ADD_FAILURE() << "Should never be called by input reader."; + } + + virtual void setFocusedApplication(const InputApplication* inputApplication) { + ADD_FAILURE() << "Should never be called by input reader."; + } + + virtual void setInputDispatchMode(bool enabled, bool frozen) { + ADD_FAILURE() << "Should never be called by input reader."; + } + + virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel, + const sp<InputChannel>& toChannel) { + ADD_FAILURE() << "Should never be called by input reader."; + return 0; + } + + virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel, + const sp<InputWindowHandle>& inputWindowHandle, bool monitor) { + ADD_FAILURE() << "Should never be called by input reader."; + return 0; + } + + virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel) { + ADD_FAILURE() << "Should never be called by input reader."; + return 0; + } +}; + + +// --- FakeEventHub --- + +class FakeEventHub : public EventHubInterface { + struct KeyInfo { + int32_t keyCode; + uint32_t flags; + }; + + struct Device { + String8 name; + uint32_t classes; + PropertyMap configuration; + KeyedVector<int, RawAbsoluteAxisInfo> axes; + KeyedVector<int32_t, int32_t> keyCodeStates; + KeyedVector<int32_t, int32_t> scanCodeStates; + KeyedVector<int32_t, int32_t> switchStates; + KeyedVector<int32_t, KeyInfo> keys; + KeyedVector<int32_t, bool> leds; + Vector<VirtualKeyDefinition> virtualKeys; + + Device(const String8& name, uint32_t classes) : + name(name), classes(classes) { + } + }; + + KeyedVector<int32_t, Device*> mDevices; + Vector<String8> mExcludedDevices; + List<RawEvent> mEvents; + +protected: + virtual ~FakeEventHub() { + for (size_t i = 0; i < mDevices.size(); i++) { + delete mDevices.valueAt(i); + } + } + +public: + FakeEventHub() { } + + void addDevice(int32_t deviceId, const String8& name, uint32_t classes) { + Device* device = new Device(name, classes); + mDevices.add(deviceId, device); + + enqueueEvent(ARBITRARY_TIME, deviceId, EventHubInterface::DEVICE_ADDED, 0, 0, 0, 0); + } + + void removeDevice(int32_t deviceId) { + delete mDevices.valueFor(deviceId); + mDevices.removeItem(deviceId); + + enqueueEvent(ARBITRARY_TIME, deviceId, EventHubInterface::DEVICE_REMOVED, 0, 0, 0, 0); + } + + void finishDeviceScan() { + enqueueEvent(ARBITRARY_TIME, 0, EventHubInterface::FINISHED_DEVICE_SCAN, 0, 0, 0, 0); + } + + void addConfigurationProperty(int32_t deviceId, const String8& key, const String8& value) { + Device* device = getDevice(deviceId); + device->configuration.addProperty(key, value); + } + + void addConfigurationMap(int32_t deviceId, const PropertyMap* configuration) { + Device* device = getDevice(deviceId); + device->configuration.addAll(configuration); + } + + void addAxis(int32_t deviceId, int axis, + int32_t minValue, int32_t maxValue, int flat, int fuzz) { + Device* device = getDevice(deviceId); + + RawAbsoluteAxisInfo info; + info.valid = true; + info.minValue = minValue; + info.maxValue = maxValue; + info.flat = flat; + info.fuzz = fuzz; + device->axes.add(axis, info); + } + + void setKeyCodeState(int32_t deviceId, int32_t keyCode, int32_t state) { + Device* device = getDevice(deviceId); + device->keyCodeStates.replaceValueFor(keyCode, state); + } + + void setScanCodeState(int32_t deviceId, int32_t scanCode, int32_t state) { + Device* device = getDevice(deviceId); + device->scanCodeStates.replaceValueFor(scanCode, state); + } + + void setSwitchState(int32_t deviceId, int32_t switchCode, int32_t state) { + Device* device = getDevice(deviceId); + device->switchStates.replaceValueFor(switchCode, state); + } + + void addKey(int32_t deviceId, int32_t scanCode, int32_t keyCode, uint32_t flags) { + Device* device = getDevice(deviceId); + KeyInfo info; + info.keyCode = keyCode; + info.flags = flags; + device->keys.add(scanCode, info); + } + + void addLed(int32_t deviceId, int32_t led, bool initialState) { + Device* device = getDevice(deviceId); + device->leds.add(led, initialState); + } + + bool getLedState(int32_t deviceId, int32_t led) { + Device* device = getDevice(deviceId); + return device->leds.valueFor(led); + } + + Vector<String8>& getExcludedDevices() { + return mExcludedDevices; + } + + void addVirtualKeyDefinition(int32_t deviceId, const VirtualKeyDefinition& definition) { + Device* device = getDevice(deviceId); + device->virtualKeys.push(definition); + } + + void enqueueEvent(nsecs_t when, int32_t deviceId, int32_t type, + int32_t scanCode, int32_t keyCode, int32_t value, uint32_t flags) { + RawEvent event; + event.when = when; + event.deviceId = deviceId; + event.type = type; + event.scanCode = scanCode; + event.keyCode = keyCode; + event.value = value; + event.flags = flags; + mEvents.push_back(event); + } + + void assertQueueIsEmpty() { + ASSERT_EQ(size_t(0), mEvents.size()) + << "Expected the event queue to be empty (fully consumed)."; + } + +private: + Device* getDevice(int32_t deviceId) const { + ssize_t index = mDevices.indexOfKey(deviceId); + return index >= 0 ? mDevices.valueAt(index) : NULL; + } + + virtual uint32_t getDeviceClasses(int32_t deviceId) const { + Device* device = getDevice(deviceId); + return device ? device->classes : 0; + } + + virtual String8 getDeviceName(int32_t deviceId) const { + Device* device = getDevice(deviceId); + return device ? device->name : String8("unknown"); + } + + virtual void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const { + Device* device = getDevice(deviceId); + if (device) { + *outConfiguration = device->configuration; + } + } + + virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis, + RawAbsoluteAxisInfo* outAxisInfo) const { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->axes.indexOfKey(axis); + if (index >= 0) { + *outAxisInfo = device->axes.valueAt(index); + return OK; + } + } + return -1; + } + + virtual status_t scancodeToKeycode(int32_t deviceId, int scancode, + int32_t* outKeycode, uint32_t* outFlags) const { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->keys.indexOfKey(scancode); + if (index >= 0) { + if (outKeycode) { + *outKeycode = device->keys.valueAt(index).keyCode; + } + if (outFlags) { + *outFlags = device->keys.valueAt(index).flags; + } + return OK; + } + } + return NAME_NOT_FOUND; + } + + virtual void addExcludedDevice(const char* deviceName) { + mExcludedDevices.add(String8(deviceName)); + } + + virtual bool getEvent(RawEvent* outEvent) { + if (mEvents.empty()) { + return false; + } + + *outEvent = *mEvents.begin(); + mEvents.erase(mEvents.begin()); + return true; + } + + virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->scanCodeStates.indexOfKey(scanCode); + if (index >= 0) { + return device->scanCodeStates.valueAt(index); + } + } + return AKEY_STATE_UNKNOWN; + } + + virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->keyCodeStates.indexOfKey(keyCode); + if (index >= 0) { + return device->keyCodeStates.valueAt(index); + } + } + return AKEY_STATE_UNKNOWN; + } + + virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->switchStates.indexOfKey(sw); + if (index >= 0) { + return device->switchStates.valueAt(index); + } + } + return AKEY_STATE_UNKNOWN; + } + + virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes, + uint8_t* outFlags) const { + bool result = false; + Device* device = getDevice(deviceId); + if (device) { + for (size_t i = 0; i < numCodes; i++) { + for (size_t j = 0; j < device->keys.size(); j++) { + if (keyCodes[i] == device->keys.valueAt(j).keyCode) { + outFlags[i] = 1; + result = true; + } + } + } + } + return result; + } + + virtual bool hasLed(int32_t deviceId, int32_t led) const { + Device* device = getDevice(deviceId); + return device && device->leds.indexOfKey(led) >= 0; + } + + virtual void setLedState(int32_t deviceId, int32_t led, bool on) { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->leds.indexOfKey(led); + if (index >= 0) { + device->leds.replaceValueAt(led, on); + } else { + ADD_FAILURE() + << "Attempted to set the state of an LED that the EventHub declared " + "was not present. led=" << led; + } + } + } + + virtual void getVirtualKeyDefinitions(int32_t deviceId, + Vector<VirtualKeyDefinition>& outVirtualKeys) const { + outVirtualKeys.clear(); + + Device* device = getDevice(deviceId); + if (device) { + outVirtualKeys.appendVector(device->virtualKeys); + } + } + + virtual void dump(String8& dump) { + } +}; + + +// --- FakeInputReaderContext --- + +class FakeInputReaderContext : public InputReaderContext { + sp<EventHubInterface> mEventHub; + sp<InputReaderPolicyInterface> mPolicy; + sp<InputDispatcherInterface> mDispatcher; + int32_t mGlobalMetaState; + bool mUpdateGlobalMetaStateWasCalled; + +public: + FakeInputReaderContext(const sp<EventHubInterface>& eventHub, + const sp<InputReaderPolicyInterface>& policy, + const sp<InputDispatcherInterface>& dispatcher) : + mEventHub(eventHub), mPolicy(policy), mDispatcher(dispatcher), + mGlobalMetaState(0) { + } + + virtual ~FakeInputReaderContext() { } + + void assertUpdateGlobalMetaStateWasCalled() { + ASSERT_TRUE(mUpdateGlobalMetaStateWasCalled) + << "Expected updateGlobalMetaState() to have been called."; + mUpdateGlobalMetaStateWasCalled = false; + } + + void setGlobalMetaState(int32_t state) { + mGlobalMetaState = state; + } + +private: + virtual void updateGlobalMetaState() { + mUpdateGlobalMetaStateWasCalled = true; + } + + virtual int32_t getGlobalMetaState() { + return mGlobalMetaState; + } + + virtual EventHubInterface* getEventHub() { + return mEventHub.get(); + } + + virtual InputReaderPolicyInterface* getPolicy() { + return mPolicy.get(); + } + + virtual InputDispatcherInterface* getDispatcher() { + return mDispatcher.get(); + } + + virtual void disableVirtualKeysUntil(nsecs_t time) { + } + + virtual bool shouldDropVirtualKey(nsecs_t now, + InputDevice* device, int32_t keyCode, int32_t scanCode) { + return false; + } +}; + + +// --- FakeInputMapper --- + +class FakeInputMapper : public InputMapper { + uint32_t mSources; + int32_t mKeyboardType; + int32_t mMetaState; + KeyedVector<int32_t, int32_t> mKeyCodeStates; + KeyedVector<int32_t, int32_t> mScanCodeStates; + KeyedVector<int32_t, int32_t> mSwitchStates; + Vector<int32_t> mSupportedKeyCodes; + RawEvent mLastEvent; + + bool mConfigureWasCalled; + bool mResetWasCalled; + bool mProcessWasCalled; + +public: + FakeInputMapper(InputDevice* device, uint32_t sources) : + InputMapper(device), + mSources(sources), mKeyboardType(AINPUT_KEYBOARD_TYPE_NONE), + mMetaState(0), + mConfigureWasCalled(false), mResetWasCalled(false), mProcessWasCalled(false) { + } + + virtual ~FakeInputMapper() { } + + void setKeyboardType(int32_t keyboardType) { + mKeyboardType = keyboardType; + } + + void setMetaState(int32_t metaState) { + mMetaState = metaState; + } + + void assertConfigureWasCalled() { + ASSERT_TRUE(mConfigureWasCalled) + << "Expected configure() to have been called."; + mConfigureWasCalled = false; + } + + void assertResetWasCalled() { + ASSERT_TRUE(mResetWasCalled) + << "Expected reset() to have been called."; + mResetWasCalled = false; + } + + void assertProcessWasCalled(RawEvent* outLastEvent = NULL) { + ASSERT_TRUE(mProcessWasCalled) + << "Expected process() to have been called."; + if (outLastEvent) { + *outLastEvent = mLastEvent; + } + mProcessWasCalled = false; + } + + void setKeyCodeState(int32_t keyCode, int32_t state) { + mKeyCodeStates.replaceValueFor(keyCode, state); + } + + void setScanCodeState(int32_t scanCode, int32_t state) { + mScanCodeStates.replaceValueFor(scanCode, state); + } + + void setSwitchState(int32_t switchCode, int32_t state) { + mSwitchStates.replaceValueFor(switchCode, state); + } + + void addSupportedKeyCode(int32_t keyCode) { + mSupportedKeyCodes.add(keyCode); + } + +private: + virtual uint32_t getSources() { + return mSources; + } + + virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) { + InputMapper::populateDeviceInfo(deviceInfo); + + if (mKeyboardType != AINPUT_KEYBOARD_TYPE_NONE) { + deviceInfo->setKeyboardType(mKeyboardType); + } + } + + virtual void configure() { + mConfigureWasCalled = true; + } + + virtual void reset() { + mResetWasCalled = true; + } + + virtual void process(const RawEvent* rawEvent) { + mLastEvent = *rawEvent; + mProcessWasCalled = true; + } + + virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode) { + ssize_t index = mKeyCodeStates.indexOfKey(keyCode); + return index >= 0 ? mKeyCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN; + } + + virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) { + ssize_t index = mScanCodeStates.indexOfKey(scanCode); + return index >= 0 ? mScanCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN; + } + + virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode) { + ssize_t index = mSwitchStates.indexOfKey(switchCode); + return index >= 0 ? mSwitchStates.valueAt(index) : AKEY_STATE_UNKNOWN; + } + + virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) { + bool result = false; + for (size_t i = 0; i < numCodes; i++) { + for (size_t j = 0; j < mSupportedKeyCodes.size(); j++) { + if (keyCodes[i] == mSupportedKeyCodes[j]) { + outFlags[i] = 1; + result = true; + } + } + } + return result; + } + + virtual int32_t getMetaState() { + return mMetaState; + } +}; + + +// --- InstrumentedInputReader --- + +class InstrumentedInputReader : public InputReader { + InputDevice* mNextDevice; + +public: + InstrumentedInputReader(const sp<EventHubInterface>& eventHub, + const sp<InputReaderPolicyInterface>& policy, + const sp<InputDispatcherInterface>& dispatcher) : + InputReader(eventHub, policy, dispatcher) { + } + + virtual ~InstrumentedInputReader() { + if (mNextDevice) { + delete mNextDevice; + } + } + + void setNextDevice(InputDevice* device) { + mNextDevice = device; + } + +protected: + virtual InputDevice* createDevice(int32_t deviceId, const String8& name, uint32_t classes) { + if (mNextDevice) { + InputDevice* device = mNextDevice; + mNextDevice = NULL; + return device; + } + return InputReader::createDevice(deviceId, name, classes); + } + + friend class InputReaderTest; +}; + + +// --- InputReaderTest --- + +class InputReaderTest : public testing::Test { +protected: + sp<FakeInputDispatcher> mFakeDispatcher; + sp<FakeInputReaderPolicy> mFakePolicy; + sp<FakeEventHub> mFakeEventHub; + sp<InstrumentedInputReader> mReader; + + virtual void SetUp() { + mFakeEventHub = new FakeEventHub(); + mFakePolicy = new FakeInputReaderPolicy(); + mFakeDispatcher = new FakeInputDispatcher(); + + mReader = new InstrumentedInputReader(mFakeEventHub, mFakePolicy, mFakeDispatcher); + } + + virtual void TearDown() { + mReader.clear(); + + mFakeDispatcher.clear(); + mFakePolicy.clear(); + mFakeEventHub.clear(); + } + + void addDevice(int32_t deviceId, const String8& name, uint32_t classes, + const PropertyMap* configuration) { + mFakeEventHub->addDevice(deviceId, name, classes); + if (configuration) { + mFakeEventHub->addConfigurationMap(deviceId, configuration); + } + mFakeEventHub->finishDeviceScan(); + mReader->loopOnce(); + mReader->loopOnce(); + mFakeEventHub->assertQueueIsEmpty(); + } + + FakeInputMapper* addDeviceWithFakeInputMapper(int32_t deviceId, + const String8& name, uint32_t classes, uint32_t sources, + const PropertyMap* configuration) { + InputDevice* device = new InputDevice(mReader.get(), deviceId, name); + FakeInputMapper* mapper = new FakeInputMapper(device, sources); + device->addMapper(mapper); + mReader->setNextDevice(device); + addDevice(deviceId, name, classes, configuration); + return mapper; + } +}; + +TEST_F(InputReaderTest, GetInputConfiguration_WhenNoDevices_ReturnsDefaults) { + InputConfiguration config; + mReader->getInputConfiguration(&config); + + ASSERT_EQ(InputConfiguration::KEYBOARD_NOKEYS, config.keyboard); + ASSERT_EQ(InputConfiguration::NAVIGATION_NONAV, config.navigation); + ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen); +} + +TEST_F(InputReaderTest, GetInputConfiguration_WhenAlphabeticKeyboardPresent_ReturnsQwertyKeyboard) { + ASSERT_NO_FATAL_FAILURE(addDevice(0, String8("keyboard"), + INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_ALPHAKEY, NULL)); + + InputConfiguration config; + mReader->getInputConfiguration(&config); + + ASSERT_EQ(InputConfiguration::KEYBOARD_QWERTY, config.keyboard); + ASSERT_EQ(InputConfiguration::NAVIGATION_NONAV, config.navigation); + ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen); +} + +TEST_F(InputReaderTest, GetInputConfiguration_WhenTouchScreenPresent_ReturnsFingerTouchScreen) { + PropertyMap configuration; + configuration.addProperty(String8("touch.deviceType"), String8("touchScreen")); + ASSERT_NO_FATAL_FAILURE(addDevice(0, String8("touchscreen"), + INPUT_DEVICE_CLASS_TOUCH, &configuration)); + + InputConfiguration config; + mReader->getInputConfiguration(&config); + + ASSERT_EQ(InputConfiguration::KEYBOARD_NOKEYS, config.keyboard); + ASSERT_EQ(InputConfiguration::NAVIGATION_NONAV, config.navigation); + ASSERT_EQ(InputConfiguration::TOUCHSCREEN_FINGER, config.touchScreen); +} + +TEST_F(InputReaderTest, GetInputConfiguration_WhenTouchPadPresent_ReturnsFingerNoTouch) { + ASSERT_NO_FATAL_FAILURE(addDevice(0, String8("touchpad"), + INPUT_DEVICE_CLASS_TOUCH, NULL)); + + InputConfiguration config; + mReader->getInputConfiguration(&config); + + ASSERT_EQ(InputConfiguration::KEYBOARD_NOKEYS, config.keyboard); + ASSERT_EQ(InputConfiguration::NAVIGATION_NONAV, config.navigation); + ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen); +} + +TEST_F(InputReaderTest, GetInputConfiguration_WhenMousePresent_ReturnsNoNavigation) { + sp<FakePointerController> controller = new FakePointerController(); + mFakePolicy->setPointerController(0, controller); + + PropertyMap configuration; + configuration.addProperty(String8("cursor.mode"), String8("pointer")); + ASSERT_NO_FATAL_FAILURE(addDevice(0, String8("mouse"), + INPUT_DEVICE_CLASS_CURSOR, &configuration)); + + InputConfiguration config; + mReader->getInputConfiguration(&config); + + ASSERT_EQ(InputConfiguration::KEYBOARD_NOKEYS, config.keyboard); + ASSERT_EQ(InputConfiguration::NAVIGATION_NONAV, config.navigation); + ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen); +} + +TEST_F(InputReaderTest, GetInputConfiguration_WhenTrackballPresent_ReturnsTrackballNavigation) { + PropertyMap configuration; + configuration.addProperty(String8("cursor.mode"), String8("navigation")); + ASSERT_NO_FATAL_FAILURE(addDevice(0, String8("trackball"), + INPUT_DEVICE_CLASS_CURSOR, &configuration)); + + InputConfiguration config; + mReader->getInputConfiguration(&config); + + ASSERT_EQ(InputConfiguration::KEYBOARD_NOKEYS, config.keyboard); + ASSERT_EQ(InputConfiguration::NAVIGATION_TRACKBALL, config.navigation); + ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen); +} + +TEST_F(InputReaderTest, GetInputConfiguration_WhenDPadPresent_ReturnsDPadNavigation) { + ASSERT_NO_FATAL_FAILURE(addDevice(0, String8("dpad"), + INPUT_DEVICE_CLASS_DPAD, NULL)); + + InputConfiguration config; + mReader->getInputConfiguration(&config); + + ASSERT_EQ(InputConfiguration::KEYBOARD_NOKEYS, config.keyboard); + ASSERT_EQ(InputConfiguration::NAVIGATION_DPAD, config.navigation); + ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen); +} + +TEST_F(InputReaderTest, GetInputDeviceInfo_WhenDeviceIdIsValid) { + ASSERT_NO_FATAL_FAILURE(addDevice(1, String8("keyboard"), + INPUT_DEVICE_CLASS_KEYBOARD, NULL)); + + InputDeviceInfo info; + status_t result = mReader->getInputDeviceInfo(1, &info); + + ASSERT_EQ(OK, result); + ASSERT_EQ(1, info.getId()); + ASSERT_STREQ("keyboard", info.getName().string()); + ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, info.getKeyboardType()); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, info.getSources()); + ASSERT_EQ(size_t(0), info.getMotionRanges().size()); +} + +TEST_F(InputReaderTest, GetInputDeviceInfo_WhenDeviceIdIsInvalid) { + InputDeviceInfo info; + status_t result = mReader->getInputDeviceInfo(-1, &info); + + ASSERT_EQ(NAME_NOT_FOUND, result); +} + +TEST_F(InputReaderTest, GetInputDeviceInfo_WhenDeviceIdIsIgnored) { + addDevice(1, String8("ignored"), 0, NULL); // no classes so device will be ignored + + InputDeviceInfo info; + status_t result = mReader->getInputDeviceInfo(1, &info); + + ASSERT_EQ(NAME_NOT_FOUND, result); +} + +TEST_F(InputReaderTest, GetInputDeviceIds) { + sp<FakePointerController> controller = new FakePointerController(); + mFakePolicy->setPointerController(2, controller); + + ASSERT_NO_FATAL_FAILURE(addDevice(1, String8("keyboard"), + INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_ALPHAKEY, NULL)); + ASSERT_NO_FATAL_FAILURE(addDevice(2, String8("mouse"), + INPUT_DEVICE_CLASS_CURSOR, NULL)); + + Vector<int32_t> ids; + mReader->getInputDeviceIds(ids); + + ASSERT_EQ(size_t(2), ids.size()); + ASSERT_EQ(1, ids[0]); + ASSERT_EQ(2, ids[1]); +} + +TEST_F(InputReaderTest, GetKeyCodeState_ForwardsRequestsToMappers) { + FakeInputMapper* mapper = NULL; + ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, String8("fake"), + INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD, NULL)); + mapper->setKeyCodeState(AKEYCODE_A, AKEY_STATE_DOWN); + + ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getKeyCodeState(0, + AINPUT_SOURCE_ANY, AKEYCODE_A)) + << "Should return unknown when the device id is >= 0 but unknown."; + + ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getKeyCodeState(1, + AINPUT_SOURCE_TRACKBALL, AKEYCODE_A)) + << "Should return unknown when the device id is valid but the sources are not supported by the device."; + + ASSERT_EQ(AKEY_STATE_DOWN, mReader->getKeyCodeState(1, + AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, AKEYCODE_A)) + << "Should return value provided by mapper when device id is valid and the device supports some of the sources."; + + ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getKeyCodeState(-1, + AINPUT_SOURCE_TRACKBALL, AKEYCODE_A)) + << "Should return unknown when the device id is < 0 but the sources are not supported by any device."; + + ASSERT_EQ(AKEY_STATE_DOWN, mReader->getKeyCodeState(-1, + AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, AKEYCODE_A)) + << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources."; +} + +TEST_F(InputReaderTest, GetScanCodeState_ForwardsRequestsToMappers) { + FakeInputMapper* mapper = NULL; + ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, String8("fake"), + INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD, NULL)); + mapper->setScanCodeState(KEY_A, AKEY_STATE_DOWN); + + ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getScanCodeState(0, + AINPUT_SOURCE_ANY, KEY_A)) + << "Should return unknown when the device id is >= 0 but unknown."; + + ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getScanCodeState(1, + AINPUT_SOURCE_TRACKBALL, KEY_A)) + << "Should return unknown when the device id is valid but the sources are not supported by the device."; + + ASSERT_EQ(AKEY_STATE_DOWN, mReader->getScanCodeState(1, + AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, KEY_A)) + << "Should return value provided by mapper when device id is valid and the device supports some of the sources."; + + ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getScanCodeState(-1, + AINPUT_SOURCE_TRACKBALL, KEY_A)) + << "Should return unknown when the device id is < 0 but the sources are not supported by any device."; + + ASSERT_EQ(AKEY_STATE_DOWN, mReader->getScanCodeState(-1, + AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, KEY_A)) + << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources."; +} + +TEST_F(InputReaderTest, GetSwitchState_ForwardsRequestsToMappers) { + FakeInputMapper* mapper = NULL; + ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, String8("fake"), + INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD, NULL)); + mapper->setSwitchState(SW_LID, AKEY_STATE_DOWN); + + ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getSwitchState(0, + AINPUT_SOURCE_ANY, SW_LID)) + << "Should return unknown when the device id is >= 0 but unknown."; + + ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getSwitchState(1, + AINPUT_SOURCE_TRACKBALL, SW_LID)) + << "Should return unknown when the device id is valid but the sources are not supported by the device."; + + ASSERT_EQ(AKEY_STATE_DOWN, mReader->getSwitchState(1, + AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, SW_LID)) + << "Should return value provided by mapper when device id is valid and the device supports some of the sources."; + + ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getSwitchState(-1, + AINPUT_SOURCE_TRACKBALL, SW_LID)) + << "Should return unknown when the device id is < 0 but the sources are not supported by any device."; + + ASSERT_EQ(AKEY_STATE_DOWN, mReader->getSwitchState(-1, + AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, SW_LID)) + << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources."; +} + +TEST_F(InputReaderTest, MarkSupportedKeyCodes_ForwardsRequestsToMappers) { + FakeInputMapper* mapper = NULL; + ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, String8("fake"), + INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD, NULL)); + mapper->addSupportedKeyCode(AKEYCODE_A); + mapper->addSupportedKeyCode(AKEYCODE_B); + + const int32_t keyCodes[4] = { AKEYCODE_A, AKEYCODE_B, AKEYCODE_1, AKEYCODE_2 }; + uint8_t flags[4] = { 0, 0, 0, 1 }; + + ASSERT_FALSE(mReader->hasKeys(0, AINPUT_SOURCE_ANY, 4, keyCodes, flags)) + << "Should return false when device id is >= 0 but unknown."; + ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]); + + flags[3] = 1; + ASSERT_FALSE(mReader->hasKeys(1, AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags)) + << "Should return false when device id is valid but the sources are not supported by the device."; + ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]); + + flags[3] = 1; + ASSERT_TRUE(mReader->hasKeys(1, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags)) + << "Should return value provided by mapper when device id is valid and the device supports some of the sources."; + ASSERT_TRUE(flags[0] && flags[1] && !flags[2] && !flags[3]); + + flags[3] = 1; + ASSERT_FALSE(mReader->hasKeys(-1, AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags)) + << "Should return false when the device id is < 0 but the sources are not supported by any device."; + ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]); + + flags[3] = 1; + ASSERT_TRUE(mReader->hasKeys(-1, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags)) + << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources."; + ASSERT_TRUE(flags[0] && flags[1] && !flags[2] && !flags[3]); +} + +TEST_F(InputReaderTest, LoopOnce_WhenDeviceScanFinished_SendsConfigurationChanged) { + addDevice(1, String8("ignored"), INPUT_DEVICE_CLASS_KEYBOARD, NULL); + + FakeInputDispatcher::NotifyConfigurationChangedArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyConfigurationChangedWasCalled(&args)); + ASSERT_EQ(ARBITRARY_TIME, args.eventTime); +} + +TEST_F(InputReaderTest, LoopOnce_ForwardsRawEventsToMappers) { + FakeInputMapper* mapper = NULL; + ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, String8("fake"), + INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD, NULL)); + + mFakeEventHub->enqueueEvent(0, 1, EV_KEY, KEY_A, AKEYCODE_A, 1, POLICY_FLAG_WAKE); + mReader->loopOnce(); + ASSERT_NO_FATAL_FAILURE(mFakeEventHub->assertQueueIsEmpty()); + + RawEvent event; + ASSERT_NO_FATAL_FAILURE(mapper->assertProcessWasCalled(&event)); + ASSERT_EQ(0, event.when); + ASSERT_EQ(1, event.deviceId); + ASSERT_EQ(EV_KEY, event.type); + ASSERT_EQ(KEY_A, event.scanCode); + ASSERT_EQ(AKEYCODE_A, event.keyCode); + ASSERT_EQ(1, event.value); + ASSERT_EQ(POLICY_FLAG_WAKE, event.flags); +} + + +// --- InputDeviceTest --- + +class InputDeviceTest : public testing::Test { +protected: + static const char* DEVICE_NAME; + static const int32_t DEVICE_ID; + + sp<FakeEventHub> mFakeEventHub; + sp<FakeInputReaderPolicy> mFakePolicy; + sp<FakeInputDispatcher> mFakeDispatcher; + FakeInputReaderContext* mFakeContext; + + InputDevice* mDevice; + + virtual void SetUp() { + mFakeEventHub = new FakeEventHub(); + mFakePolicy = new FakeInputReaderPolicy(); + mFakeDispatcher = new FakeInputDispatcher(); + mFakeContext = new FakeInputReaderContext(mFakeEventHub, mFakePolicy, mFakeDispatcher); + + mFakeEventHub->addDevice(DEVICE_ID, String8(DEVICE_NAME), 0); + mDevice = new InputDevice(mFakeContext, DEVICE_ID, String8(DEVICE_NAME)); + } + + virtual void TearDown() { + delete mDevice; + + delete mFakeContext; + mFakeDispatcher.clear(); + mFakePolicy.clear(); + mFakeEventHub.clear(); + } +}; + +const char* InputDeviceTest::DEVICE_NAME = "device"; +const int32_t InputDeviceTest::DEVICE_ID = 1; + +TEST_F(InputDeviceTest, ImmutableProperties) { + ASSERT_EQ(DEVICE_ID, mDevice->getId()); + ASSERT_STREQ(DEVICE_NAME, mDevice->getName()); +} + +TEST_F(InputDeviceTest, WhenNoMappersAreRegistered_DeviceIsIgnored) { + // Configuration. + mDevice->configure(); + + // Metadata. + ASSERT_TRUE(mDevice->isIgnored()); + ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, mDevice->getSources()); + + InputDeviceInfo info; + mDevice->getDeviceInfo(&info); + ASSERT_EQ(DEVICE_ID, info.getId()); + ASSERT_STREQ(DEVICE_NAME, info.getName().string()); + ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NONE, info.getKeyboardType()); + ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, info.getSources()); + + // State queries. + ASSERT_EQ(0, mDevice->getMetaState()); + + ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getKeyCodeState(AINPUT_SOURCE_KEYBOARD, 0)) + << "Ignored device should return unknown key code state."; + ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getScanCodeState(AINPUT_SOURCE_KEYBOARD, 0)) + << "Ignored device should return unknown scan code state."; + ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getSwitchState(AINPUT_SOURCE_KEYBOARD, 0)) + << "Ignored device should return unknown switch state."; + + const int32_t keyCodes[2] = { AKEYCODE_A, AKEYCODE_B }; + uint8_t flags[2] = { 0, 1 }; + ASSERT_FALSE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_KEYBOARD, 2, keyCodes, flags)) + << "Ignored device should never mark any key codes."; + ASSERT_EQ(0, flags[0]) << "Flag for unsupported key should be unchanged."; + ASSERT_EQ(1, flags[1]) << "Flag for unsupported key should be unchanged."; + + // Reset. + mDevice->reset(); +} + +TEST_F(InputDeviceTest, WhenMappersAreRegistered_DeviceIsNotIgnoredAndForwardsRequestsToMappers) { + // Configuration. + mFakeEventHub->addConfigurationProperty(DEVICE_ID, String8("key"), String8("value")); + + FakeInputMapper* mapper1 = new FakeInputMapper(mDevice, AINPUT_SOURCE_KEYBOARD); + mapper1->setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC); + mapper1->setMetaState(AMETA_ALT_ON); + mapper1->addSupportedKeyCode(AKEYCODE_A); + mapper1->addSupportedKeyCode(AKEYCODE_B); + mapper1->setKeyCodeState(AKEYCODE_A, AKEY_STATE_DOWN); + mapper1->setKeyCodeState(AKEYCODE_B, AKEY_STATE_UP); + mapper1->setScanCodeState(2, AKEY_STATE_DOWN); + mapper1->setScanCodeState(3, AKEY_STATE_UP); + mapper1->setSwitchState(4, AKEY_STATE_DOWN); + mDevice->addMapper(mapper1); + + FakeInputMapper* mapper2 = new FakeInputMapper(mDevice, AINPUT_SOURCE_TOUCHSCREEN); + mapper2->setMetaState(AMETA_SHIFT_ON); + mDevice->addMapper(mapper2); + + mDevice->configure(); + + String8 propertyValue; + ASSERT_TRUE(mDevice->getConfiguration().tryGetProperty(String8("key"), propertyValue)) + << "Device should have read configuration during configuration phase."; + ASSERT_STREQ("value", propertyValue.string()); + + ASSERT_NO_FATAL_FAILURE(mapper1->assertConfigureWasCalled()); + ASSERT_NO_FATAL_FAILURE(mapper2->assertConfigureWasCalled()); + + // Metadata. + ASSERT_FALSE(mDevice->isIgnored()); + ASSERT_EQ(uint32_t(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TOUCHSCREEN), mDevice->getSources()); + + InputDeviceInfo info; + mDevice->getDeviceInfo(&info); + ASSERT_EQ(DEVICE_ID, info.getId()); + ASSERT_STREQ(DEVICE_NAME, info.getName().string()); + ASSERT_EQ(AINPUT_KEYBOARD_TYPE_ALPHABETIC, info.getKeyboardType()); + ASSERT_EQ(uint32_t(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TOUCHSCREEN), info.getSources()); + + // State queries. + ASSERT_EQ(AMETA_ALT_ON | AMETA_SHIFT_ON, mDevice->getMetaState()) + << "Should query mappers and combine meta states."; + + ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getKeyCodeState(AINPUT_SOURCE_TRACKBALL, AKEYCODE_A)) + << "Should return unknown key code state when source not supported."; + ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getScanCodeState(AINPUT_SOURCE_TRACKBALL, AKEYCODE_A)) + << "Should return unknown scan code state when source not supported."; + ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getSwitchState(AINPUT_SOURCE_TRACKBALL, AKEYCODE_A)) + << "Should return unknown switch state when source not supported."; + + ASSERT_EQ(AKEY_STATE_DOWN, mDevice->getKeyCodeState(AINPUT_SOURCE_KEYBOARD, AKEYCODE_A)) + << "Should query mapper when source is supported."; + ASSERT_EQ(AKEY_STATE_UP, mDevice->getScanCodeState(AINPUT_SOURCE_KEYBOARD, 3)) + << "Should query mapper when source is supported."; + ASSERT_EQ(AKEY_STATE_DOWN, mDevice->getSwitchState(AINPUT_SOURCE_KEYBOARD, 4)) + << "Should query mapper when source is supported."; + + const int32_t keyCodes[4] = { AKEYCODE_A, AKEYCODE_B, AKEYCODE_1, AKEYCODE_2 }; + uint8_t flags[4] = { 0, 0, 0, 1 }; + ASSERT_FALSE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags)) + << "Should do nothing when source is unsupported."; + ASSERT_EQ(0, flags[0]) << "Flag should be unchanged when source is unsupported."; + ASSERT_EQ(0, flags[1]) << "Flag should be unchanged when source is unsupported."; + ASSERT_EQ(0, flags[2]) << "Flag should be unchanged when source is unsupported."; + ASSERT_EQ(1, flags[3]) << "Flag should be unchanged when source is unsupported."; + + ASSERT_TRUE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_KEYBOARD, 4, keyCodes, flags)) + << "Should query mapper when source is supported."; + ASSERT_EQ(1, flags[0]) << "Flag for supported key should be set."; + ASSERT_EQ(1, flags[1]) << "Flag for supported key should be set."; + ASSERT_EQ(0, flags[2]) << "Flag for unsupported key should be unchanged."; + ASSERT_EQ(1, flags[3]) << "Flag for unsupported key should be unchanged."; + + // Event handling. + RawEvent event; + mDevice->process(&event); + + ASSERT_NO_FATAL_FAILURE(mapper1->assertProcessWasCalled()); + ASSERT_NO_FATAL_FAILURE(mapper2->assertProcessWasCalled()); + + // Reset. + mDevice->reset(); + + ASSERT_NO_FATAL_FAILURE(mapper1->assertResetWasCalled()); + ASSERT_NO_FATAL_FAILURE(mapper2->assertResetWasCalled()); +} + + +// --- InputMapperTest --- + +class InputMapperTest : public testing::Test { +protected: + static const char* DEVICE_NAME; + static const int32_t DEVICE_ID; + + sp<FakeEventHub> mFakeEventHub; + sp<FakeInputReaderPolicy> mFakePolicy; + sp<FakeInputDispatcher> mFakeDispatcher; + FakeInputReaderContext* mFakeContext; + InputDevice* mDevice; + + virtual void SetUp() { + mFakeEventHub = new FakeEventHub(); + mFakePolicy = new FakeInputReaderPolicy(); + mFakeDispatcher = new FakeInputDispatcher(); + mFakeContext = new FakeInputReaderContext(mFakeEventHub, mFakePolicy, mFakeDispatcher); + mDevice = new InputDevice(mFakeContext, DEVICE_ID, String8(DEVICE_NAME)); + + mFakeEventHub->addDevice(DEVICE_ID, String8(DEVICE_NAME), 0); + } + + virtual void TearDown() { + delete mDevice; + delete mFakeContext; + mFakeDispatcher.clear(); + mFakePolicy.clear(); + mFakeEventHub.clear(); + } + + void addConfigurationProperty(const char* key, const char* value) { + mFakeEventHub->addConfigurationProperty(DEVICE_ID, String8(key), String8(value)); + } + + void addMapperAndConfigure(InputMapper* mapper) { + mDevice->addMapper(mapper); + mDevice->configure(); + } + + static void process(InputMapper* mapper, nsecs_t when, int32_t deviceId, int32_t type, + int32_t scanCode, int32_t keyCode, int32_t value, uint32_t flags) { + RawEvent event; + event.when = when; + event.deviceId = deviceId; + event.type = type; + event.scanCode = scanCode; + event.keyCode = keyCode; + event.value = value; + event.flags = flags; + mapper->process(&event); + } + + static void assertMotionRange(const InputDeviceInfo& info, + int32_t rangeType, float min, float max, float flat, float fuzz) { + const InputDeviceInfo::MotionRange* range = info.getMotionRange(rangeType); + ASSERT_TRUE(range != NULL) << "Range: " << rangeType; + ASSERT_NEAR(min, range->min, EPSILON) << "Range: " << rangeType; + ASSERT_NEAR(max, range->max, EPSILON) << "Range: " << rangeType; + ASSERT_NEAR(flat, range->flat, EPSILON) << "Range: " << rangeType; + ASSERT_NEAR(fuzz, range->fuzz, EPSILON) << "Range: " << rangeType; + } + + static void assertPointerCoords(const PointerCoords& coords, + float x, float y, float pressure, float size, + float touchMajor, float touchMinor, float toolMajor, float toolMinor, + float orientation) { + ASSERT_NEAR(x, coords.x, 1); + ASSERT_NEAR(y, coords.y, 1); + ASSERT_NEAR(pressure, coords.pressure, EPSILON); + ASSERT_NEAR(size, coords.size, EPSILON); + ASSERT_NEAR(touchMajor, coords.touchMajor, 1); + ASSERT_NEAR(touchMinor, coords.touchMinor, 1); + ASSERT_NEAR(toolMajor, coords.toolMajor, 1); + ASSERT_NEAR(toolMinor, coords.toolMinor, 1); + ASSERT_NEAR(orientation, coords.orientation, EPSILON); + } +}; + +const char* InputMapperTest::DEVICE_NAME = "device"; +const int32_t InputMapperTest::DEVICE_ID = 1; + + +// --- SwitchInputMapperTest --- + +class SwitchInputMapperTest : public InputMapperTest { +protected: +}; + +TEST_F(SwitchInputMapperTest, GetSources) { + SwitchInputMapper* mapper = new SwitchInputMapper(mDevice); + addMapperAndConfigure(mapper); + + ASSERT_EQ(uint32_t(AINPUT_SOURCE_SWITCH), mapper->getSources()); +} + +TEST_F(SwitchInputMapperTest, GetSwitchState) { + SwitchInputMapper* mapper = new SwitchInputMapper(mDevice); + addMapperAndConfigure(mapper); + + mFakeEventHub->setSwitchState(DEVICE_ID, SW_LID, 1); + ASSERT_EQ(1, mapper->getSwitchState(AINPUT_SOURCE_ANY, SW_LID)); + + mFakeEventHub->setSwitchState(DEVICE_ID, SW_LID, 0); + ASSERT_EQ(0, mapper->getSwitchState(AINPUT_SOURCE_ANY, SW_LID)); +} + +TEST_F(SwitchInputMapperTest, Process) { + SwitchInputMapper* mapper = new SwitchInputMapper(mDevice); + addMapperAndConfigure(mapper); + + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SW, SW_LID, 0, 1, 0); + + FakeInputDispatcher::NotifySwitchArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifySwitchWasCalled(&args)); + ASSERT_EQ(ARBITRARY_TIME, args.when); + ASSERT_EQ(SW_LID, args.switchCode); + ASSERT_EQ(1, args.switchValue); + ASSERT_EQ(uint32_t(0), args.policyFlags); +} + + +// --- KeyboardInputMapperTest --- + +class KeyboardInputMapperTest : public InputMapperTest { +protected: + void testDPadKeyRotation(KeyboardInputMapper* mapper, + int32_t originalScanCode, int32_t originalKeyCode, int32_t rotatedKeyCode); +}; + +void KeyboardInputMapperTest::testDPadKeyRotation(KeyboardInputMapper* mapper, + int32_t originalScanCode, int32_t originalKeyCode, int32_t rotatedKeyCode) { + FakeInputDispatcher::NotifyKeyArgs args; + + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, originalScanCode, originalKeyCode, 1, 0); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action); + ASSERT_EQ(originalScanCode, args.scanCode); + ASSERT_EQ(rotatedKeyCode, args.keyCode); + + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, originalScanCode, originalKeyCode, 0, 0); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action); + ASSERT_EQ(originalScanCode, args.scanCode); + ASSERT_EQ(rotatedKeyCode, args.keyCode); +} + + +TEST_F(KeyboardInputMapperTest, GetSources) { + KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, + AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); + addMapperAndConfigure(mapper); + + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, mapper->getSources()); +} + +TEST_F(KeyboardInputMapperTest, Process_SimpleKeyPress) { + KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, + AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); + addMapperAndConfigure(mapper); + + // Key down. + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_HOME, AKEYCODE_HOME, 1, POLICY_FLAG_WAKE); + FakeInputDispatcher::NotifyKeyArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(DEVICE_ID, args.deviceId); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source); + ASSERT_EQ(ARBITRARY_TIME, args.eventTime); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action); + ASSERT_EQ(AKEYCODE_HOME, args.keyCode); + ASSERT_EQ(KEY_HOME, args.scanCode); + ASSERT_EQ(AMETA_NONE, args.metaState); + ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags); + ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); + ASSERT_EQ(ARBITRARY_TIME, args.downTime); + + // Key up. + process(mapper, ARBITRARY_TIME + 1, DEVICE_ID, + EV_KEY, KEY_HOME, AKEYCODE_HOME, 0, POLICY_FLAG_WAKE); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(DEVICE_ID, args.deviceId); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source); + ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action); + ASSERT_EQ(AKEYCODE_HOME, args.keyCode); + ASSERT_EQ(KEY_HOME, args.scanCode); + ASSERT_EQ(AMETA_NONE, args.metaState); + ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags); + ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); + ASSERT_EQ(ARBITRARY_TIME, args.downTime); +} + +TEST_F(KeyboardInputMapperTest, Reset_WhenKeysAreNotDown_DoesNotSynthesizeKeyUp) { + KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, + AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); + addMapperAndConfigure(mapper); + + // Key down. + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_HOME, AKEYCODE_HOME, 1, POLICY_FLAG_WAKE); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled()); + + // Key up. + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_HOME, AKEYCODE_HOME, 0, POLICY_FLAG_WAKE); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled()); + + // Reset. Since no keys still down, should not synthesize any key ups. + mapper->reset(); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled()); +} + +TEST_F(KeyboardInputMapperTest, Reset_WhenKeysAreDown_SynthesizesKeyUps) { + KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, + AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); + addMapperAndConfigure(mapper); + + // Metakey down. + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_LEFTSHIFT, AKEYCODE_SHIFT_LEFT, 1, 0); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled()); + + // Key down. + process(mapper, ARBITRARY_TIME + 1, DEVICE_ID, + EV_KEY, KEY_A, AKEYCODE_A, 1, 0); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled()); + + // Reset. Since two keys are still down, should synthesize two key ups in reverse order. + mapper->reset(); + + FakeInputDispatcher::NotifyKeyArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(DEVICE_ID, args.deviceId); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action); + ASSERT_EQ(AKEYCODE_A, args.keyCode); + ASSERT_EQ(KEY_A, args.scanCode); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); + ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags); + ASSERT_EQ(uint32_t(0), args.policyFlags); + ASSERT_EQ(ARBITRARY_TIME + 1, args.downTime); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(DEVICE_ID, args.deviceId); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action); + ASSERT_EQ(AKEYCODE_SHIFT_LEFT, args.keyCode); + ASSERT_EQ(KEY_LEFTSHIFT, args.scanCode); + ASSERT_EQ(AMETA_NONE, args.metaState); + ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags); + ASSERT_EQ(uint32_t(0), args.policyFlags); + ASSERT_EQ(ARBITRARY_TIME + 1, args.downTime); + + // And that's it. + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled()); +} + +TEST_F(KeyboardInputMapperTest, Process_ShouldUpdateMetaState) { + KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, + AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); + addMapperAndConfigure(mapper); + + // Initial metastate. + ASSERT_EQ(AMETA_NONE, mapper->getMetaState()); + + // Metakey down. + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_LEFTSHIFT, AKEYCODE_SHIFT_LEFT, 1, 0); + FakeInputDispatcher::NotifyKeyArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper->getMetaState()); + ASSERT_NO_FATAL_FAILURE(mFakeContext->assertUpdateGlobalMetaStateWasCalled()); + + // Key down. + process(mapper, ARBITRARY_TIME + 1, DEVICE_ID, + EV_KEY, KEY_A, AKEYCODE_A, 1, 0); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper->getMetaState()); + + // Key up. + process(mapper, ARBITRARY_TIME + 2, DEVICE_ID, + EV_KEY, KEY_A, AKEYCODE_A, 0, 0); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper->getMetaState()); + + // Metakey up. + process(mapper, ARBITRARY_TIME + 3, DEVICE_ID, + EV_KEY, KEY_LEFTSHIFT, AKEYCODE_SHIFT_LEFT, 0, 0); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(AMETA_NONE, args.metaState); + ASSERT_EQ(AMETA_NONE, mapper->getMetaState()); + ASSERT_NO_FATAL_FAILURE(mFakeContext->assertUpdateGlobalMetaStateWasCalled()); +} + +TEST_F(KeyboardInputMapperTest, Process_WhenNotOrientationAware_ShouldNotRotateDPad) { + KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, + AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); + addMapperAndConfigure(mapper); + + mFakePolicy->setDisplayInfo(DISPLAY_ID, + DISPLAY_WIDTH, DISPLAY_HEIGHT, + DISPLAY_ORIENTATION_90); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_RIGHT)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_DOWN)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_LEFT)); +} + +TEST_F(KeyboardInputMapperTest, Process_WhenOrientationAware_ShouldRotateDPad) { + KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, + AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); + addConfigurationProperty("keyboard.orientationAware", "1"); + addMapperAndConfigure(mapper); + + mFakePolicy->setDisplayInfo(DISPLAY_ID, + DISPLAY_WIDTH, DISPLAY_HEIGHT, + DISPLAY_ORIENTATION_0); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_RIGHT)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_DOWN)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_LEFT)); + + mFakePolicy->setDisplayInfo(DISPLAY_ID, + DISPLAY_WIDTH, DISPLAY_HEIGHT, + DISPLAY_ORIENTATION_90); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN)); + + mFakePolicy->setDisplayInfo(DISPLAY_ID, + DISPLAY_WIDTH, DISPLAY_HEIGHT, + DISPLAY_ORIENTATION_180); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_DOWN)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_LEFT)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_UP)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_RIGHT)); + + mFakePolicy->setDisplayInfo(DISPLAY_ID, + DISPLAY_WIDTH, DISPLAY_HEIGHT, + DISPLAY_ORIENTATION_270); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_RIGHT)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_DOWN)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_LEFT)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_UP)); + + // Special case: if orientation changes while key is down, we still emit the same keycode + // in the key up as we did in the key down. + FakeInputDispatcher::NotifyKeyArgs args; + + mFakePolicy->setDisplayInfo(DISPLAY_ID, + DISPLAY_WIDTH, DISPLAY_HEIGHT, + DISPLAY_ORIENTATION_270); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, KEY_UP, AKEYCODE_DPAD_UP, 1, 0); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action); + ASSERT_EQ(KEY_UP, args.scanCode); + ASSERT_EQ(AKEYCODE_DPAD_RIGHT, args.keyCode); + + mFakePolicy->setDisplayInfo(DISPLAY_ID, + DISPLAY_WIDTH, DISPLAY_HEIGHT, + DISPLAY_ORIENTATION_180); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, KEY_UP, AKEYCODE_DPAD_UP, 0, 0); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action); + ASSERT_EQ(KEY_UP, args.scanCode); + ASSERT_EQ(AKEYCODE_DPAD_RIGHT, args.keyCode); +} + +TEST_F(KeyboardInputMapperTest, GetKeyCodeState) { + KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, + AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); + addMapperAndConfigure(mapper); + + mFakeEventHub->setKeyCodeState(DEVICE_ID, AKEYCODE_A, 1); + ASSERT_EQ(1, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A)); + + mFakeEventHub->setKeyCodeState(DEVICE_ID, AKEYCODE_A, 0); + ASSERT_EQ(0, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A)); +} + +TEST_F(KeyboardInputMapperTest, GetScanCodeState) { + KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, + AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); + addMapperAndConfigure(mapper); + + mFakeEventHub->setScanCodeState(DEVICE_ID, KEY_A, 1); + ASSERT_EQ(1, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_A)); + + mFakeEventHub->setScanCodeState(DEVICE_ID, KEY_A, 0); + ASSERT_EQ(0, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_A)); +} + +TEST_F(KeyboardInputMapperTest, MarkSupportedKeyCodes) { + KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, + AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); + addMapperAndConfigure(mapper); + + mFakeEventHub->addKey(DEVICE_ID, KEY_A, AKEYCODE_A, 0); + + const int32_t keyCodes[2] = { AKEYCODE_A, AKEYCODE_B }; + uint8_t flags[2] = { 0, 0 }; + ASSERT_TRUE(mapper->markSupportedKeyCodes(AINPUT_SOURCE_ANY, 1, keyCodes, flags)); + ASSERT_TRUE(flags[0]); + ASSERT_FALSE(flags[1]); +} + +TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleMetaStateAndLeds) { + mFakeEventHub->addLed(DEVICE_ID, LED_CAPSL, true /*initially on*/); + mFakeEventHub->addLed(DEVICE_ID, LED_NUML, false /*initially off*/); + mFakeEventHub->addLed(DEVICE_ID, LED_SCROLLL, false /*initially off*/); + + KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, + AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); + addMapperAndConfigure(mapper); + + // Initialization should have turned all of the lights off. + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL)); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML)); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL)); + + // Toggle caps lock on. + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 1, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 0, 0); + ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL)); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML)); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL)); + ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper->getMetaState()); + + // Toggle num lock on. + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 1, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 0, 0); + ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL)); + ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML)); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL)); + ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON, mapper->getMetaState()); + + // Toggle caps lock off. + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 1, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 0, 0); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL)); + ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML)); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL)); + ASSERT_EQ(AMETA_NUM_LOCK_ON, mapper->getMetaState()); + + // Toggle scroll lock on. + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 1, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 0, 0); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL)); + ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML)); + ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL)); + ASSERT_EQ(AMETA_NUM_LOCK_ON | AMETA_SCROLL_LOCK_ON, mapper->getMetaState()); + + // Toggle num lock off. + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 1, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 0, 0); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL)); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML)); + ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL)); + ASSERT_EQ(AMETA_SCROLL_LOCK_ON, mapper->getMetaState()); + + // Toggle scroll lock off. + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 1, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 0, 0); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL)); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML)); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL)); + ASSERT_EQ(AMETA_NONE, mapper->getMetaState()); +} + + +// --- CursorInputMapperTest --- + +class CursorInputMapperTest : public InputMapperTest { +protected: + static const int32_t TRACKBALL_MOVEMENT_THRESHOLD; + + sp<FakePointerController> mFakePointerController; + + virtual void SetUp() { + InputMapperTest::SetUp(); + + mFakePointerController = new FakePointerController(); + mFakePolicy->setPointerController(DEVICE_ID, mFakePointerController); + } + + void testMotionRotation(CursorInputMapper* mapper, + int32_t originalX, int32_t originalY, int32_t rotatedX, int32_t rotatedY); +}; + +const int32_t CursorInputMapperTest::TRACKBALL_MOVEMENT_THRESHOLD = 6; + +void CursorInputMapperTest::testMotionRotation(CursorInputMapper* mapper, + int32_t originalX, int32_t originalY, int32_t rotatedX, int32_t rotatedY) { + FakeInputDispatcher::NotifyMotionArgs args; + + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, originalX, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, originalY, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + float(rotatedX) / TRACKBALL_MOVEMENT_THRESHOLD, + float(rotatedY) / TRACKBALL_MOVEMENT_THRESHOLD, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); +} + +TEST_F(CursorInputMapperTest, WhenModeIsPointer_GetSources_ReturnsMouse) { + CursorInputMapper* mapper = new CursorInputMapper(mDevice); + addConfigurationProperty("cursor.mode", "pointer"); + addMapperAndConfigure(mapper); + + ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper->getSources()); +} + +TEST_F(CursorInputMapperTest, WhenModeIsNavigation_GetSources_ReturnsTrackball) { + CursorInputMapper* mapper = new CursorInputMapper(mDevice); + addConfigurationProperty("cursor.mode", "navigation"); + addMapperAndConfigure(mapper); + + ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, mapper->getSources()); +} + +TEST_F(CursorInputMapperTest, WhenModeIsPointer_PopulateDeviceInfo_ReturnsRangeFromPointerController) { + CursorInputMapper* mapper = new CursorInputMapper(mDevice); + addConfigurationProperty("cursor.mode", "pointer"); + addMapperAndConfigure(mapper); + + InputDeviceInfo info; + mapper->populateDeviceInfo(&info); + + // Initially there may not be a valid motion range. + ASSERT_EQ(NULL, info.getMotionRange(AINPUT_MOTION_RANGE_X)); + ASSERT_EQ(NULL, info.getMotionRange(AINPUT_MOTION_RANGE_Y)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE, + 0.0f, 1.0f, 0.0f, 0.0f)); + + // When the bounds are set, then there should be a valid motion range. + mFakePointerController->setBounds(1, 2, 800, 480); + + InputDeviceInfo info2; + mapper->populateDeviceInfo(&info2); + + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_X, + 1, 800, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_Y, + 2, 480, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_PRESSURE, + 0.0f, 1.0f, 0.0f, 0.0f)); +} + +TEST_F(CursorInputMapperTest, WhenModeIsNavigation_PopulateDeviceInfo_ReturnsScaledRange) { + CursorInputMapper* mapper = new CursorInputMapper(mDevice); + addConfigurationProperty("cursor.mode", "navigation"); + addMapperAndConfigure(mapper); + + InputDeviceInfo info; + mapper->populateDeviceInfo(&info); + + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_X, + -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_Y, + -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE, + 0.0f, 1.0f, 0.0f, 0.0f)); +} + +TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaState) { + CursorInputMapper* mapper = new CursorInputMapper(mDevice); + addConfigurationProperty("cursor.mode", "navigation"); + addMapperAndConfigure(mapper); + + mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); + + FakeInputDispatcher::NotifyMotionArgs args; + + // Button press. + // Mostly testing non x/y behavior here so we don't need to check again elsewhere. + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(ARBITRARY_TIME, args.eventTime); + ASSERT_EQ(DEVICE_ID, args.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source); + ASSERT_EQ(uint32_t(0), args.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); + ASSERT_EQ(0, args.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); + ASSERT_EQ(0, args.edgeFlags); + ASSERT_EQ(uint32_t(1), args.pointerCount); + ASSERT_EQ(0, args.pointerIds[0]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision); + ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision); + ASSERT_EQ(ARBITRARY_TIME, args.downTime); + + // Button release. Should have same down time. + process(mapper, ARBITRARY_TIME + 1, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime); + ASSERT_EQ(DEVICE_ID, args.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source); + ASSERT_EQ(uint32_t(0), args.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); + ASSERT_EQ(0, args.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); + ASSERT_EQ(0, args.edgeFlags); + ASSERT_EQ(uint32_t(1), args.pointerCount); + ASSERT_EQ(0, args.pointerIds[0]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision); + ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision); + ASSERT_EQ(ARBITRARY_TIME, args.downTime); +} + +TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentXYUpdates) { + CursorInputMapper* mapper = new CursorInputMapper(mDevice); + addConfigurationProperty("cursor.mode", "navigation"); + addMapperAndConfigure(mapper); + + FakeInputDispatcher::NotifyMotionArgs args; + + // Motion in X but not Y. + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, 1, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + 1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + + // Motion in Y but not X. + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, -2, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + ASSERT_NEAR(0.0f, args.pointerCoords[0].x, EPSILON); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + 0.0f, -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); +} + +TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentButtonUpdates) { + CursorInputMapper* mapper = new CursorInputMapper(mDevice); + addConfigurationProperty("cursor.mode", "navigation"); + addMapperAndConfigure(mapper); + + FakeInputDispatcher::NotifyMotionArgs args; + + // Button press without following sync. + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + + // Button release without following sync. + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); +} + +TEST_F(CursorInputMapperTest, Process_ShouldHandleCombinedXYAndButtonUpdates) { + CursorInputMapper* mapper = new CursorInputMapper(mDevice); + addConfigurationProperty("cursor.mode", "navigation"); + addMapperAndConfigure(mapper); + + FakeInputDispatcher::NotifyMotionArgs args; + + // Combined X, Y and Button. + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, 1, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, -2, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + 1.0f / TRACKBALL_MOVEMENT_THRESHOLD, -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, + 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + + // Move X, Y a bit while pressed. + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, 2, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, 1, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + 2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD, + 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + + // Release Button. + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); +} + +TEST_F(CursorInputMapperTest, Reset_WhenButtonIsNotDown_ShouldNotSynthesizeButtonUp) { + CursorInputMapper* mapper = new CursorInputMapper(mDevice); + addConfigurationProperty("cursor.mode", "navigation"); + addMapperAndConfigure(mapper); + + FakeInputDispatcher::NotifyMotionArgs args; + + // Button press. + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + + // Button release. + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + + // Reset. Should not synthesize button up since button is not pressed. + mapper->reset(); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled()); +} + +TEST_F(CursorInputMapperTest, Reset_WhenButtonIsDown_ShouldSynthesizeButtonUp) { + CursorInputMapper* mapper = new CursorInputMapper(mDevice); + addConfigurationProperty("cursor.mode", "navigation"); + addMapperAndConfigure(mapper); + + FakeInputDispatcher::NotifyMotionArgs args; + + // Button press. + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + + // Reset. Should synthesize button up. + mapper->reset(); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); +} + +TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldNotRotateMotions) { + CursorInputMapper* mapper = new CursorInputMapper(mDevice); + addConfigurationProperty("cursor.mode", "navigation"); + addMapperAndConfigure(mapper); + + mFakePolicy->setDisplayInfo(DISPLAY_ID, + DISPLAY_WIDTH, DISPLAY_HEIGHT, + DISPLAY_ORIENTATION_90); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, 1, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, 1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 0, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, -1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, 1)); +} + +TEST_F(CursorInputMapperTest, Process_WhenOrientationAware_ShouldRotateMotions) { + CursorInputMapper* mapper = new CursorInputMapper(mDevice); + addConfigurationProperty("cursor.mode", "navigation"); + addConfigurationProperty("cursor.orientationAware", "1"); + addMapperAndConfigure(mapper); + + mFakePolicy->setDisplayInfo(DISPLAY_ID, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, 1, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, 1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 0, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, -1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, 1)); + + mFakePolicy->setDisplayInfo(DISPLAY_ID, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_90); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, 1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 0, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, -1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, -1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, 0, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, 1, 1)); + + mFakePolicy->setDisplayInfo(DISPLAY_ID, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_180); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, -1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, -1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, -1, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 0, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, 1, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, 1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, 1, -1)); + + mFakePolicy->setDisplayInfo(DISPLAY_ID, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_270); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, -1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, -1, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 0, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, 1, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, 1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, 0, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, -1)); +} + + +// --- TouchInputMapperTest --- + +class TouchInputMapperTest : public InputMapperTest { +protected: + static const int32_t RAW_X_MIN; + static const int32_t RAW_X_MAX; + static const int32_t RAW_Y_MIN; + static const int32_t RAW_Y_MAX; + static const int32_t RAW_TOUCH_MIN; + static const int32_t RAW_TOUCH_MAX; + static const int32_t RAW_TOOL_MIN; + static const int32_t RAW_TOOL_MAX; + static const int32_t RAW_PRESSURE_MIN; + static const int32_t RAW_PRESSURE_MAX; + static const int32_t RAW_ORIENTATION_MIN; + static const int32_t RAW_ORIENTATION_MAX; + static const int32_t RAW_ID_MIN; + static const int32_t RAW_ID_MAX; + static const float X_PRECISION; + static const float Y_PRECISION; + + static const VirtualKeyDefinition VIRTUAL_KEYS[2]; + + enum Axes { + POSITION = 1 << 0, + TOUCH = 1 << 1, + TOOL = 1 << 2, + PRESSURE = 1 << 3, + ORIENTATION = 1 << 4, + MINOR = 1 << 5, + ID = 1 << 6, + }; + + void prepareDisplay(int32_t orientation); + void prepareVirtualKeys(); + int32_t toRawX(float displayX); + int32_t toRawY(float displayY); + float toDisplayX(int32_t rawX); + float toDisplayY(int32_t rawY); +}; + +const int32_t TouchInputMapperTest::RAW_X_MIN = 25; +const int32_t TouchInputMapperTest::RAW_X_MAX = 1020; +const int32_t TouchInputMapperTest::RAW_Y_MIN = 30; +const int32_t TouchInputMapperTest::RAW_Y_MAX = 1010; +const int32_t TouchInputMapperTest::RAW_TOUCH_MIN = 0; +const int32_t TouchInputMapperTest::RAW_TOUCH_MAX = 31; +const int32_t TouchInputMapperTest::RAW_TOOL_MIN = 0; +const int32_t TouchInputMapperTest::RAW_TOOL_MAX = 15; +const int32_t TouchInputMapperTest::RAW_PRESSURE_MIN = RAW_TOUCH_MIN; +const int32_t TouchInputMapperTest::RAW_PRESSURE_MAX = RAW_TOUCH_MAX; +const int32_t TouchInputMapperTest::RAW_ORIENTATION_MIN = -7; +const int32_t TouchInputMapperTest::RAW_ORIENTATION_MAX = 7; +const int32_t TouchInputMapperTest::RAW_ID_MIN = 0; +const int32_t TouchInputMapperTest::RAW_ID_MAX = 9; +const float TouchInputMapperTest::X_PRECISION = float(RAW_X_MAX - RAW_X_MIN) / DISPLAY_WIDTH; +const float TouchInputMapperTest::Y_PRECISION = float(RAW_Y_MAX - RAW_Y_MIN) / DISPLAY_HEIGHT; + +const VirtualKeyDefinition TouchInputMapperTest::VIRTUAL_KEYS[2] = { + { KEY_HOME, 60, DISPLAY_HEIGHT + 15, 20, 20 }, + { KEY_MENU, DISPLAY_HEIGHT - 60, DISPLAY_WIDTH + 15, 20, 20 }, +}; + +void TouchInputMapperTest::prepareDisplay(int32_t orientation) { + mFakePolicy->setDisplayInfo(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation); +} + +void TouchInputMapperTest::prepareVirtualKeys() { + mFakeEventHub->addVirtualKeyDefinition(DEVICE_ID, VIRTUAL_KEYS[0]); + mFakeEventHub->addVirtualKeyDefinition(DEVICE_ID, VIRTUAL_KEYS[1]); + mFakeEventHub->addKey(DEVICE_ID, KEY_HOME, AKEYCODE_HOME, POLICY_FLAG_WAKE); + mFakeEventHub->addKey(DEVICE_ID, KEY_MENU, AKEYCODE_MENU, POLICY_FLAG_WAKE); +} + +int32_t TouchInputMapperTest::toRawX(float displayX) { + return int32_t(displayX * (RAW_X_MAX - RAW_X_MIN) / DISPLAY_WIDTH + RAW_X_MIN); +} + +int32_t TouchInputMapperTest::toRawY(float displayY) { + return int32_t(displayY * (RAW_Y_MAX - RAW_Y_MIN) / DISPLAY_HEIGHT + RAW_Y_MIN); +} + +float TouchInputMapperTest::toDisplayX(int32_t rawX) { + return float(rawX - RAW_X_MIN) * DISPLAY_WIDTH / (RAW_X_MAX - RAW_X_MIN); +} + +float TouchInputMapperTest::toDisplayY(int32_t rawY) { + return float(rawY - RAW_Y_MIN) * DISPLAY_HEIGHT / (RAW_Y_MAX - RAW_Y_MIN); +} + + +// --- SingleTouchInputMapperTest --- + +class SingleTouchInputMapperTest : public TouchInputMapperTest { +protected: + void prepareAxes(int axes); + + void processDown(SingleTouchInputMapper* mapper, int32_t x, int32_t y); + void processMove(SingleTouchInputMapper* mapper, int32_t x, int32_t y); + void processUp(SingleTouchInputMapper* mappery); + void processPressure(SingleTouchInputMapper* mapper, int32_t pressure); + void processToolMajor(SingleTouchInputMapper* mapper, int32_t toolMajor); + void processSync(SingleTouchInputMapper* mapper); +}; + +void SingleTouchInputMapperTest::prepareAxes(int axes) { + if (axes & POSITION) { + mFakeEventHub->addAxis(DEVICE_ID, ABS_X, RAW_X_MIN, RAW_X_MAX, 0, 0); + mFakeEventHub->addAxis(DEVICE_ID, ABS_Y, RAW_Y_MIN, RAW_Y_MAX, 0, 0); + } + if (axes & PRESSURE) { + mFakeEventHub->addAxis(DEVICE_ID, ABS_PRESSURE, RAW_PRESSURE_MIN, RAW_PRESSURE_MAX, 0, 0); + } + if (axes & TOOL) { + mFakeEventHub->addAxis(DEVICE_ID, ABS_TOOL_WIDTH, RAW_TOOL_MIN, RAW_TOOL_MAX, 0, 0); + } +} + +void SingleTouchInputMapperTest::processDown(SingleTouchInputMapper* mapper, int32_t x, int32_t y) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_TOUCH, 0, 1, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_X, 0, x, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_Y, 0, y, 0); +} + +void SingleTouchInputMapperTest::processMove(SingleTouchInputMapper* mapper, int32_t x, int32_t y) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_X, 0, x, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_Y, 0, y, 0); +} + +void SingleTouchInputMapperTest::processUp(SingleTouchInputMapper* mapper) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_TOUCH, 0, 0, 0); +} + +void SingleTouchInputMapperTest::processPressure( + SingleTouchInputMapper* mapper, int32_t pressure) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_PRESSURE, 0, pressure, 0); +} + +void SingleTouchInputMapperTest::processToolMajor( + SingleTouchInputMapper* mapper, int32_t toolMajor) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_TOOL_WIDTH, 0, toolMajor, 0); +} + +void SingleTouchInputMapperTest::processSync(SingleTouchInputMapper* mapper) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0); +} + + +TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecified_ReturnsTouchPad) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + prepareAxes(POSITION); + addMapperAndConfigure(mapper); + + ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper->getSources()); +} + +TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchPad_ReturnsTouchPad) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + prepareAxes(POSITION); + addConfigurationProperty("touch.deviceType", "touchPad"); + addMapperAndConfigure(mapper); + + ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper->getSources()); +} + +TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchScreen_ReturnsTouchScreen) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + prepareAxes(POSITION); + addConfigurationProperty("touch.deviceType", "touchScreen"); + addMapperAndConfigure(mapper); + + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper->getSources()); +} + +TEST_F(SingleTouchInputMapperTest, GetKeyCodeState) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION); + prepareVirtualKeys(); + addMapperAndConfigure(mapper); + + // Unknown key. + ASSERT_EQ(AKEY_STATE_UNKNOWN, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A)); + + // Virtual key is down. + int32_t x = toRawX(VIRTUAL_KEYS[0].centerX); + int32_t y = toRawY(VIRTUAL_KEYS[0].centerY); + processDown(mapper, x, y); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled()); + + ASSERT_EQ(AKEY_STATE_VIRTUAL, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_HOME)); + + // Virtual key is up. + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled()); + + ASSERT_EQ(AKEY_STATE_UP, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_HOME)); +} + +TEST_F(SingleTouchInputMapperTest, GetScanCodeState) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION); + prepareVirtualKeys(); + addMapperAndConfigure(mapper); + + // Unknown key. + ASSERT_EQ(AKEY_STATE_UNKNOWN, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_A)); + + // Virtual key is down. + int32_t x = toRawX(VIRTUAL_KEYS[0].centerX); + int32_t y = toRawY(VIRTUAL_KEYS[0].centerY); + processDown(mapper, x, y); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled()); + + ASSERT_EQ(AKEY_STATE_VIRTUAL, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_HOME)); + + // Virtual key is up. + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled()); + + ASSERT_EQ(AKEY_STATE_UP, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_HOME)); +} + +TEST_F(SingleTouchInputMapperTest, MarkSupportedKeyCodes) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION); + prepareVirtualKeys(); + addMapperAndConfigure(mapper); + + const int32_t keys[2] = { AKEYCODE_HOME, AKEYCODE_A }; + uint8_t flags[2] = { 0, 0 }; + ASSERT_TRUE(mapper->markSupportedKeyCodes(AINPUT_SOURCE_ANY, 2, keys, flags)); + ASSERT_TRUE(flags[0]); + ASSERT_FALSE(flags[1]); +} + +TEST_F(SingleTouchInputMapperTest, Reset_WhenVirtualKeysAreDown_SendsUp) { + // Note: Ideally we should send cancels but the implementation is more straightforward + // with up and this will only happen if a device is forcibly removed. + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION); + prepareVirtualKeys(); + addMapperAndConfigure(mapper); + + mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); + + // Press virtual key. + int32_t x = toRawX(VIRTUAL_KEYS[0].centerX); + int32_t y = toRawY(VIRTUAL_KEYS[0].centerY); + processDown(mapper, x, y); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled()); + + // Reset. Since key is down, synthesize key up. + mapper->reset(); + + FakeInputDispatcher::NotifyKeyArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args)); + //ASSERT_EQ(ARBITRARY_TIME, args.eventTime); + ASSERT_EQ(DEVICE_ID, args.deviceId); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source); + ASSERT_EQ(POLICY_FLAG_VIRTUAL, args.policyFlags); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action); + ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, args.flags); + ASSERT_EQ(AKEYCODE_HOME, args.keyCode); + ASSERT_EQ(KEY_HOME, args.scanCode); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); + ASSERT_EQ(ARBITRARY_TIME, args.downTime); +} + +TEST_F(SingleTouchInputMapperTest, Reset_WhenNothingIsPressed_NothingMuchHappens) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION); + prepareVirtualKeys(); + addMapperAndConfigure(mapper); + + // Press virtual key. + int32_t x = toRawX(VIRTUAL_KEYS[0].centerX); + int32_t y = toRawY(VIRTUAL_KEYS[0].centerY); + processDown(mapper, x, y); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled()); + + // Release virtual key. + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled()); + + // Reset. Since no key is down, nothing happens. + mapper->reset(); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled()); +} + +TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndReleasedNormally_SendsKeyDownAndKeyUp) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION); + prepareVirtualKeys(); + addMapperAndConfigure(mapper); + + mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); + + FakeInputDispatcher::NotifyKeyArgs args; + + // Press virtual key. + int32_t x = toRawX(VIRTUAL_KEYS[0].centerX); + int32_t y = toRawY(VIRTUAL_KEYS[0].centerY); + processDown(mapper, x, y); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(ARBITRARY_TIME, args.eventTime); + ASSERT_EQ(DEVICE_ID, args.deviceId); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source); + ASSERT_EQ(POLICY_FLAG_VIRTUAL, args.policyFlags); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action); + ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, args.flags); + ASSERT_EQ(AKEYCODE_HOME, args.keyCode); + ASSERT_EQ(KEY_HOME, args.scanCode); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); + ASSERT_EQ(ARBITRARY_TIME, args.downTime); + + // Release virtual key. + processUp(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(ARBITRARY_TIME, args.eventTime); + ASSERT_EQ(DEVICE_ID, args.deviceId); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source); + ASSERT_EQ(POLICY_FLAG_VIRTUAL, args.policyFlags); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action); + ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, args.flags); + ASSERT_EQ(AKEYCODE_HOME, args.keyCode); + ASSERT_EQ(KEY_HOME, args.scanCode); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); + ASSERT_EQ(ARBITRARY_TIME, args.downTime); + + // Should not have sent any motions. + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled()); +} + +TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndMovedOutOfBounds_SendsKeyDownAndKeyCancel) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION); + prepareVirtualKeys(); + addMapperAndConfigure(mapper); + + mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); + + FakeInputDispatcher::NotifyKeyArgs keyArgs; + + // Press virtual key. + int32_t x = toRawX(VIRTUAL_KEYS[0].centerX); + int32_t y = toRawY(VIRTUAL_KEYS[0].centerY); + processDown(mapper, x, y); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(ARBITRARY_TIME, keyArgs.eventTime); + ASSERT_EQ(DEVICE_ID, keyArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, keyArgs.source); + ASSERT_EQ(POLICY_FLAG_VIRTUAL, keyArgs.policyFlags); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, keyArgs.flags); + ASSERT_EQ(AKEYCODE_HOME, keyArgs.keyCode); + ASSERT_EQ(KEY_HOME, keyArgs.scanCode); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, keyArgs.metaState); + ASSERT_EQ(ARBITRARY_TIME, keyArgs.downTime); + + // Move out of bounds. This should generate a cancel and a pointer down since we moved + // into the display area. + y -= 100; + processMove(mapper, x, y); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(ARBITRARY_TIME, keyArgs.eventTime); + ASSERT_EQ(DEVICE_ID, keyArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, keyArgs.source); + ASSERT_EQ(POLICY_FLAG_VIRTUAL, keyArgs.policyFlags); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY + | AKEY_EVENT_FLAG_CANCELED, keyArgs.flags); + ASSERT_EQ(AKEYCODE_HOME, keyArgs.keyCode); + ASSERT_EQ(KEY_HOME, keyArgs.scanCode); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, keyArgs.metaState); + ASSERT_EQ(ARBITRARY_TIME, keyArgs.downTime); + + FakeInputDispatcher::NotifyMotionArgs motionArgs; + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerIds[0]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); + + // Keep moving out of bounds. Should generate a pointer move. + y -= 50; + processMove(mapper, x, y); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerIds[0]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); + + // Release out of bounds. Should generate a pointer up. + processUp(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerIds[0]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); + + // Should not have sent any more keys or motions. + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled()); +} + +TEST_F(SingleTouchInputMapperTest, Process_WhenTouchStartsOutsideDisplayAndMovesIn_SendsDownAsTouchEntersDisplay) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION); + prepareVirtualKeys(); + addMapperAndConfigure(mapper); + + mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); + + FakeInputDispatcher::NotifyMotionArgs motionArgs; + + // Initially go down out of bounds. + int32_t x = -10; + int32_t y = -10; + processDown(mapper, x, y); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled()); + + // Move into the display area. Should generate a pointer down. + x = 50; + y = 75; + processMove(mapper, x, y); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerIds[0]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); + + // Release. Should generate a pointer up. + processUp(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerIds[0]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); + + // Should not have sent any more keys or motions. + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled()); +} + +TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION); + prepareVirtualKeys(); + addMapperAndConfigure(mapper); + + mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); + + FakeInputDispatcher::NotifyMotionArgs motionArgs; + + // Down. + int32_t x = 100; + int32_t y = 125; + processDown(mapper, x, y); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerIds[0]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); + + // Move. + x += 50; + y += 75; + processMove(mapper, x, y); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerIds[0]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); + + // Up. + processUp(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerIds[0]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); + + // Should not have sent any more keys or motions. + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled()); +} + +TEST_F(SingleTouchInputMapperTest, Process_WhenNotOrientationAware_DoesNotRotateMotions) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareAxes(POSITION); + addConfigurationProperty("touch.orientationAware", "0"); + addMapperAndConfigure(mapper); + + FakeInputDispatcher::NotifyMotionArgs args; + + // Rotation 90. + prepareDisplay(DISPLAY_ORIENTATION_90); + processDown(mapper, toRawX(50), toRawY(75)); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_NEAR(50, args.pointerCoords[0].x, 1); + ASSERT_NEAR(75, args.pointerCoords[0].y, 1); + + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled()); +} + +TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationAware_RotatesMotions) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareAxes(POSITION); + addMapperAndConfigure(mapper); + + FakeInputDispatcher::NotifyMotionArgs args; + + // Rotation 0. + prepareDisplay(DISPLAY_ORIENTATION_0); + processDown(mapper, toRawX(50), toRawY(75)); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_NEAR(50, args.pointerCoords[0].x, 1); + ASSERT_NEAR(75, args.pointerCoords[0].y, 1); + + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled()); + + // Rotation 90. + prepareDisplay(DISPLAY_ORIENTATION_90); + processDown(mapper, toRawX(50), toRawY(75)); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_NEAR(75, args.pointerCoords[0].x, 1); + ASSERT_NEAR(DISPLAY_WIDTH - 50, args.pointerCoords[0].y, 1); + + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled()); + + // Rotation 180. + prepareDisplay(DISPLAY_ORIENTATION_180); + processDown(mapper, toRawX(50), toRawY(75)); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_NEAR(DISPLAY_WIDTH - 50, args.pointerCoords[0].x, 1); + ASSERT_NEAR(DISPLAY_HEIGHT - 75, args.pointerCoords[0].y, 1); + + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled()); + + // Rotation 270. + prepareDisplay(DISPLAY_ORIENTATION_270); + processDown(mapper, toRawX(50), toRawY(75)); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_NEAR(DISPLAY_HEIGHT - 75, args.pointerCoords[0].x, 1); + ASSERT_NEAR(50, args.pointerCoords[0].y, 1); + + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled()); +} + +TEST_F(SingleTouchInputMapperTest, Process_AllAxes_DefaultCalibration) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | PRESSURE | TOOL); + addMapperAndConfigure(mapper); + + // These calculations are based on the input device calibration documentation. + int32_t rawX = 100; + int32_t rawY = 200; + int32_t rawPressure = 10; + int32_t rawToolMajor = 12; + + float x = toDisplayX(rawX); + float y = toDisplayY(rawY); + float pressure = float(rawPressure) / RAW_PRESSURE_MAX; + float size = float(rawToolMajor) / RAW_TOOL_MAX; + float tool = min(DISPLAY_WIDTH, DISPLAY_HEIGHT) * size; + float touch = min(tool * pressure, tool); + + processDown(mapper, rawX, rawY); + processPressure(mapper, rawPressure); + processToolMajor(mapper, rawToolMajor); + processSync(mapper); + + FakeInputDispatcher::NotifyMotionArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + x, y, pressure, size, touch, touch, tool, tool, 0)); +} + + +// --- MultiTouchInputMapperTest --- + +class MultiTouchInputMapperTest : public TouchInputMapperTest { +protected: + void prepareAxes(int axes); + + void processPosition(MultiTouchInputMapper* mapper, int32_t x, int32_t y); + void processTouchMajor(MultiTouchInputMapper* mapper, int32_t touchMajor); + void processTouchMinor(MultiTouchInputMapper* mapper, int32_t touchMinor); + void processToolMajor(MultiTouchInputMapper* mapper, int32_t toolMajor); + void processToolMinor(MultiTouchInputMapper* mapper, int32_t toolMinor); + void processOrientation(MultiTouchInputMapper* mapper, int32_t orientation); + void processPressure(MultiTouchInputMapper* mapper, int32_t pressure); + void processId(MultiTouchInputMapper* mapper, int32_t id); + void processMTSync(MultiTouchInputMapper* mapper); + void processSync(MultiTouchInputMapper* mapper); +}; + +void MultiTouchInputMapperTest::prepareAxes(int axes) { + if (axes & POSITION) { + mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX, 0, 0); + mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_POSITION_Y, RAW_Y_MIN, RAW_Y_MAX, 0, 0); + } + if (axes & TOUCH) { + mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_TOUCH_MAJOR, RAW_TOUCH_MIN, RAW_TOUCH_MAX, 0, 0); + if (axes & MINOR) { + mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_TOUCH_MINOR, + RAW_TOUCH_MIN, RAW_TOUCH_MAX, 0, 0); + } + } + if (axes & TOOL) { + mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_WIDTH_MAJOR, RAW_TOOL_MIN, RAW_TOOL_MAX, 0, 0); + if (axes & MINOR) { + mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_WIDTH_MINOR, + RAW_TOOL_MAX, RAW_TOOL_MAX, 0, 0); + } + } + if (axes & ORIENTATION) { + mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_ORIENTATION, + RAW_ORIENTATION_MIN, RAW_ORIENTATION_MAX, 0, 0); + } + if (axes & PRESSURE) { + mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_PRESSURE, + RAW_PRESSURE_MIN, RAW_PRESSURE_MAX, 0, 0); + } + if (axes & ID) { + mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_TRACKING_ID, + RAW_ID_MIN, RAW_ID_MAX, 0, 0); + } +} + +void MultiTouchInputMapperTest::processPosition( + MultiTouchInputMapper* mapper, int32_t x, int32_t y) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_POSITION_X, 0, x, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_POSITION_Y, 0, y, 0); +} + +void MultiTouchInputMapperTest::processTouchMajor( + MultiTouchInputMapper* mapper, int32_t touchMajor) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOUCH_MAJOR, 0, touchMajor, 0); +} + +void MultiTouchInputMapperTest::processTouchMinor( + MultiTouchInputMapper* mapper, int32_t touchMinor) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOUCH_MINOR, 0, touchMinor, 0); +} + +void MultiTouchInputMapperTest::processToolMajor( + MultiTouchInputMapper* mapper, int32_t toolMajor) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_WIDTH_MAJOR, 0, toolMajor, 0); +} + +void MultiTouchInputMapperTest::processToolMinor( + MultiTouchInputMapper* mapper, int32_t toolMinor) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_WIDTH_MINOR, 0, toolMinor, 0); +} + +void MultiTouchInputMapperTest::processOrientation( + MultiTouchInputMapper* mapper, int32_t orientation) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_ORIENTATION, 0, orientation, 0); +} + +void MultiTouchInputMapperTest::processPressure( + MultiTouchInputMapper* mapper, int32_t pressure) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_PRESSURE, 0, pressure, 0); +} + +void MultiTouchInputMapperTest::processId( + MultiTouchInputMapper* mapper, int32_t id) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TRACKING_ID, 0, id, 0); +} + +void MultiTouchInputMapperTest::processMTSync(MultiTouchInputMapper* mapper) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_MT_REPORT, 0, 0, 0); +} + +void MultiTouchInputMapperTest::processSync(MultiTouchInputMapper* mapper) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0); +} + + +TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackingIds) { + MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION); + prepareVirtualKeys(); + addMapperAndConfigure(mapper); + + mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); + + FakeInputDispatcher::NotifyMotionArgs motionArgs; + + // Two fingers down at once. + int32_t x1 = 100, y1 = 125, x2 = 300, y2 = 500; + processPosition(mapper, x1, y1); + processMTSync(mapper); + processPosition(mapper, x2, y2); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerIds[0]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(2), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerIds[0]); + ASSERT_EQ(1, motionArgs.pointerIds[1]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], + toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); + + // Move. + x1 += 10; y1 += 15; x2 += 5; y2 -= 10; + processPosition(mapper, x1, y1); + processMTSync(mapper); + processPosition(mapper, x2, y2); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(2), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerIds[0]); + ASSERT_EQ(1, motionArgs.pointerIds[1]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], + toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); + + // First finger up. + x2 += 15; y2 -= 20; + processPosition(mapper, x2, y2); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(2), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerIds[0]); + ASSERT_EQ(1, motionArgs.pointerIds[1]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], + toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(1, motionArgs.pointerIds[0]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); + + // Move. + x2 += 20; y2 -= 25; + processPosition(mapper, x2, y2); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(1, motionArgs.pointerIds[0]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); + + // New finger down. + int32_t x3 = 700, y3 = 300; + processPosition(mapper, x2, y2); + processMTSync(mapper); + processPosition(mapper, x3, y3); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(2), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerIds[0]); + ASSERT_EQ(1, motionArgs.pointerIds[1]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], + toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); + + // Second finger up. + x3 += 30; y3 -= 20; + processPosition(mapper, x3, y3); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(2), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerIds[0]); + ASSERT_EQ(1, motionArgs.pointerIds[1]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], + toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerIds[0]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); + + // Last finger up. + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerIds[0]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); + + // Should not have sent any more keys or motions. + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled()); +} + +TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingIds) { + MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | ID); + prepareVirtualKeys(); + addMapperAndConfigure(mapper); + + mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); + + FakeInputDispatcher::NotifyMotionArgs motionArgs; + + // Two fingers down at once. + int32_t x1 = 100, y1 = 125, x2 = 300, y2 = 500; + processPosition(mapper, x1, y1); + processId(mapper, 1); + processMTSync(mapper); + processPosition(mapper, x2, y2); + processId(mapper, 2); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(1, motionArgs.pointerIds[0]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + motionArgs.action); + ASSERT_EQ(size_t(2), motionArgs.pointerCount); + ASSERT_EQ(1, motionArgs.pointerIds[0]); + ASSERT_EQ(2, motionArgs.pointerIds[1]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], + toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0)); + + // Move. + x1 += 10; y1 += 15; x2 += 5; y2 -= 10; + processPosition(mapper, x1, y1); + processId(mapper, 1); + processMTSync(mapper); + processPosition(mapper, x2, y2); + processId(mapper, 2); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(size_t(2), motionArgs.pointerCount); + ASSERT_EQ(1, motionArgs.pointerIds[0]); + ASSERT_EQ(2, motionArgs.pointerIds[1]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], + toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0)); + + // First finger up. + x2 += 15; y2 -= 20; + processPosition(mapper, x2, y2); + processId(mapper, 2); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + motionArgs.action); + ASSERT_EQ(size_t(2), motionArgs.pointerCount); + ASSERT_EQ(1, motionArgs.pointerIds[0]); + ASSERT_EQ(2, motionArgs.pointerIds[1]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], + toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(2, motionArgs.pointerIds[0]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0)); + + // Move. + x2 += 20; y2 -= 25; + processPosition(mapper, x2, y2); + processId(mapper, 2); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(2, motionArgs.pointerIds[0]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0)); + + // New finger down. + int32_t x3 = 700, y3 = 300; + processPosition(mapper, x2, y2); + processId(mapper, 2); + processMTSync(mapper); + processPosition(mapper, x3, y3); + processId(mapper, 3); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + motionArgs.action); + ASSERT_EQ(size_t(2), motionArgs.pointerCount); + ASSERT_EQ(2, motionArgs.pointerIds[0]); + ASSERT_EQ(3, motionArgs.pointerIds[1]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], + toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0)); + + // Second finger up. + x3 += 30; y3 -= 20; + processPosition(mapper, x3, y3); + processId(mapper, 3); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + motionArgs.action); + ASSERT_EQ(size_t(2), motionArgs.pointerCount); + ASSERT_EQ(2, motionArgs.pointerIds[0]); + ASSERT_EQ(3, motionArgs.pointerIds[1]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], + toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(3, motionArgs.pointerIds[0]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0)); + + // Last finger up. + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(3, motionArgs.pointerIds[0]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0)); + + // Should not have sent any more keys or motions. + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled()); +} + +TEST_F(MultiTouchInputMapperTest, Process_AllAxes_WithDefaultCalibration) { + MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | TOUCH | TOOL | PRESSURE | ORIENTATION | ID | MINOR); + addMapperAndConfigure(mapper); + + // These calculations are based on the input device calibration documentation. + int32_t rawX = 100; + int32_t rawY = 200; + int32_t rawTouchMajor = 7; + int32_t rawTouchMinor = 6; + int32_t rawToolMajor = 9; + int32_t rawToolMinor = 8; + int32_t rawPressure = 11; + int32_t rawOrientation = 3; + int32_t id = 5; + + float x = toDisplayX(rawX); + float y = toDisplayY(rawY); + float pressure = float(rawPressure) / RAW_PRESSURE_MAX; + float size = avg(rawToolMajor, rawToolMinor) / RAW_TOOL_MAX; + float toolMajor = float(min(DISPLAY_WIDTH, DISPLAY_HEIGHT)) * rawToolMajor / RAW_TOOL_MAX; + float toolMinor = float(min(DISPLAY_WIDTH, DISPLAY_HEIGHT)) * rawToolMinor / RAW_TOOL_MAX; + float touchMajor = min(toolMajor * pressure, toolMajor); + float touchMinor = min(toolMinor * pressure, toolMinor); + float orientation = float(rawOrientation) / RAW_ORIENTATION_MAX * M_PI_2; + + processPosition(mapper, rawX, rawY); + processTouchMajor(mapper, rawTouchMajor); + processTouchMinor(mapper, rawTouchMinor); + processToolMajor(mapper, rawToolMajor); + processToolMinor(mapper, rawToolMinor); + processPressure(mapper, rawPressure); + processOrientation(mapper, rawOrientation); + processId(mapper, id); + processMTSync(mapper); + processSync(mapper); + + FakeInputDispatcher::NotifyMotionArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(id, args.pointerIds[0]); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + x, y, pressure, size, touchMajor, touchMinor, toolMajor, toolMinor, orientation)); +} + +TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_GeometricCalibration) { + MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | TOUCH | TOOL | MINOR); + addConfigurationProperty("touch.touchSize.calibration", "geometric"); + addConfigurationProperty("touch.toolSize.calibration", "geometric"); + addMapperAndConfigure(mapper); + + // These calculations are based on the input device calibration documentation. + int32_t rawX = 100; + int32_t rawY = 200; + int32_t rawTouchMajor = 140; + int32_t rawTouchMinor = 120; + int32_t rawToolMajor = 180; + int32_t rawToolMinor = 160; + + float x = toDisplayX(rawX); + float y = toDisplayY(rawY); + float pressure = float(rawTouchMajor) / RAW_TOUCH_MAX; + float size = avg(rawToolMajor, rawToolMinor) / RAW_TOOL_MAX; + float scale = avg(float(DISPLAY_WIDTH) / (RAW_X_MAX - RAW_X_MIN), + float(DISPLAY_HEIGHT) / (RAW_Y_MAX - RAW_Y_MIN)); + float toolMajor = float(rawToolMajor) * scale; + float toolMinor = float(rawToolMinor) * scale; + float touchMajor = min(float(rawTouchMajor) * scale, toolMajor); + float touchMinor = min(float(rawTouchMinor) * scale, toolMinor); + + processPosition(mapper, rawX, rawY); + processTouchMajor(mapper, rawTouchMajor); + processTouchMinor(mapper, rawTouchMinor); + processToolMajor(mapper, rawToolMajor); + processToolMinor(mapper, rawToolMinor); + processMTSync(mapper); + processSync(mapper); + + FakeInputDispatcher::NotifyMotionArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + x, y, pressure, size, touchMajor, touchMinor, toolMajor, toolMinor, 0)); +} + +TEST_F(MultiTouchInputMapperTest, Process_TouchToolPressureSizeAxes_SummedLinearCalibration) { + MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | TOUCH | TOOL); + addConfigurationProperty("touch.touchSize.calibration", "pressure"); + addConfigurationProperty("touch.toolSize.calibration", "linear"); + addConfigurationProperty("touch.toolSize.linearScale", "10"); + addConfigurationProperty("touch.toolSize.linearBias", "160"); + addConfigurationProperty("touch.toolSize.isSummed", "1"); + addConfigurationProperty("touch.pressure.calibration", "amplitude"); + addConfigurationProperty("touch.pressure.source", "touch"); + addConfigurationProperty("touch.pressure.scale", "0.01"); + addMapperAndConfigure(mapper); + + // These calculations are based on the input device calibration documentation. + // Note: We only provide a single common touch/tool value because the device is assumed + // not to emit separate values for each pointer (isSummed = 1). + int32_t rawX = 100; + int32_t rawY = 200; + int32_t rawX2 = 150; + int32_t rawY2 = 250; + int32_t rawTouchMajor = 60; + int32_t rawToolMajor = 5; + + float x = toDisplayX(rawX); + float y = toDisplayY(rawY); + float x2 = toDisplayX(rawX2); + float y2 = toDisplayY(rawY2); + float pressure = float(rawTouchMajor) * 0.01f; + float size = float(rawToolMajor) / RAW_TOOL_MAX; + float tool = (float(rawToolMajor) * 10.0f + 160.0f) / 2; + float touch = min(tool * pressure, tool); + + processPosition(mapper, rawX, rawY); + processTouchMajor(mapper, rawTouchMajor); + processToolMajor(mapper, rawToolMajor); + processMTSync(mapper); + processPosition(mapper, rawX2, rawY2); + processTouchMajor(mapper, rawTouchMajor); + processToolMajor(mapper, rawToolMajor); + processMTSync(mapper); + processSync(mapper); + + FakeInputDispatcher::NotifyMotionArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + args.action); + ASSERT_EQ(size_t(2), args.pointerCount); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + x, y, pressure, size, touch, touch, tool, tool, 0)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[1], + x2, y2, pressure, size, touch, touch, tool, tool, 0)); +} + +TEST_F(MultiTouchInputMapperTest, Process_TouchToolPressureSizeAxes_AreaCalibration) { + MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | TOUCH | TOOL); + addConfigurationProperty("touch.touchSize.calibration", "pressure"); + addConfigurationProperty("touch.toolSize.calibration", "area"); + addConfigurationProperty("touch.toolSize.areaScale", "22"); + addConfigurationProperty("touch.toolSize.areaBias", "1"); + addConfigurationProperty("touch.toolSize.linearScale", "9.2"); + addConfigurationProperty("touch.toolSize.linearBias", "3"); + addConfigurationProperty("touch.pressure.calibration", "amplitude"); + addConfigurationProperty("touch.pressure.source", "touch"); + addConfigurationProperty("touch.pressure.scale", "0.01"); + addMapperAndConfigure(mapper); + + // These calculations are based on the input device calibration documentation. + int32_t rawX = 100; + int32_t rawY = 200; + int32_t rawTouchMajor = 60; + int32_t rawToolMajor = 5; + + float x = toDisplayX(rawX); + float y = toDisplayY(rawY); + float pressure = float(rawTouchMajor) * 0.01f; + float size = float(rawToolMajor) / RAW_TOOL_MAX; + float tool = sqrtf(float(rawToolMajor) * 22.0f + 1.0f) * 9.2f + 3.0f; + float touch = min(tool * pressure, tool); + + processPosition(mapper, rawX, rawY); + processTouchMajor(mapper, rawTouchMajor); + processToolMajor(mapper, rawToolMajor); + processMTSync(mapper); + processSync(mapper); + + FakeInputDispatcher::NotifyMotionArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + x, y, pressure, size, touch, touch, tool, tool, 0)); +} + +} // namespace android diff --git a/services/java/com/android/server/AccessibilityManagerService.java b/services/java/com/android/server/AccessibilityManagerService.java index 87de79a..04ae490 100644 --- a/services/java/com/android/server/AccessibilityManagerService.java +++ b/services/java/com/android/server/AccessibilityManagerService.java @@ -240,10 +240,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public void onChange(boolean selfChange) { super.onChange(selfChange); - mIsEnabled = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1; - synchronized (mLock) { + mIsEnabled = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1; if (mIsEnabled) { manageServicesLocked(); } else { @@ -269,14 +268,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub }); } - public void addClient(IAccessibilityManagerClient client) { + public boolean addClient(IAccessibilityManagerClient client) { synchronized (mLock) { - try { - client.setEnabled(mIsEnabled); - mClients.add(client); - } catch (RemoteException re) { - Slog.w(LOG_TAG, "Dead AccessibilityManagerClient: " + client, re); - } + mClients.add(client); + return mIsEnabled; } } @@ -456,9 +451,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } catch (RemoteException re) { if (re instanceof DeadObjectException) { Slog.w(LOG_TAG, "Dead " + service.mService + ". Cleaning up."); - synchronized (mLock) { - removeDeadServiceLocked(service); - } + removeDeadServiceLocked(service); } else { Slog.e(LOG_TAG, "Error during sending " + event + " to " + service.mService, re); } @@ -472,19 +465,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * @return True if the service was removed, false otherwise. */ private boolean removeDeadServiceLocked(Service service) { - mServices.remove(service); - mHandler.removeMessages(service.mId); - if (Config.DEBUG) { Slog.i(LOG_TAG, "Dead service " + service.mService + " removed"); } - - if (mServices.isEmpty()) { - mIsEnabled = false; - updateClientsLocked(); - } - - return true; + mHandler.removeMessages(service.mId); + return mServices.remove(service); } /** @@ -547,11 +532,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub for (int i = 0, count = services.size(); i < count; i++) { Service service = services.get(i); - - service.unbind(); - mComponentNameToServiceMap.remove(service.mComponentName); + if (service.unbind()) { + i--; + count--; + } } - services.clear(); } /** @@ -593,7 +578,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub Set<ComponentName> enabledServices) { Map<ComponentName, Service> componentNameToServiceMap = mComponentNameToServiceMap; - List<Service> services = mServices; boolean isEnabled = mIsEnabled; for (int i = 0, count = installedServices.size(); i < count; i++) { @@ -602,15 +586,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub intalledService.name); Service service = componentNameToServiceMap.get(componentName); - if (isEnabled && enabledServices.contains(componentName)) { - if (service == null) { - new Service(componentName).bind(); + if (isEnabled) { + if (enabledServices.contains(componentName)) { + if (service == null) { + service = new Service(componentName); + } + service.bind(); + } else if (!enabledServices.contains(componentName)) { + if (service != null) { + service.unbind(); + } } } else { if (service != null) { service.unbind(); - componentNameToServiceMap.remove(componentName); - services.remove(service); } } } @@ -678,21 +667,31 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub /** * Binds to the accessibility service. + * + * @return True if binding is successful. */ - public void bind() { + public boolean bind() { if (mService == null) { - mContext.bindService(mIntent, this, Context.BIND_AUTO_CREATE); + return mContext.bindService(mIntent, this, Context.BIND_AUTO_CREATE); } + return false; } /** * Unbinds form the accessibility service and removes it from the data * structures for service management. + * + * @return True if unbinding is successful. */ - public void unbind() { + public boolean unbind() { if (mService != null) { + mService = null; mContext.unbindService(this); + mComponentNameToServiceMap.remove(mComponentName); + mServices.remove(this); + return true; } + return false; } /** diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java index 4931cc7..5a36417 100644 --- a/services/java/com/android/server/AlarmManagerService.java +++ b/services/java/com/android/server/AlarmManagerService.java @@ -661,7 +661,8 @@ class AlarmManagerService extends IAlarmManager.Stub { remove(mTimeTickSender); mClockReceiver.scheduleTimeTickEvent(); Intent intent = new Intent(Intent.ACTION_TIME_CHANGED); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING + | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); mContext.sendBroadcast(intent); } diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java index 731fb22..ad25657 100644 --- a/services/java/com/android/server/AppWidgetService.java +++ b/services/java/com/android/server/AppWidgetService.java @@ -16,6 +16,24 @@ package com.android.server; +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + import android.app.AlarmManager; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; @@ -24,46 +42,38 @@ import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.Intent.FilterComparison; import android.content.IntentFilter; +import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.net.Uri; import android.os.Binder; import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.util.AttributeSet; +import android.util.Log; +import android.util.Pair; import android.util.Slog; import android.util.TypedValue; import android.util.Xml; import android.widget.RemoteViews; -import java.io.IOException; -import java.io.File; -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.HashMap; -import java.util.HashSet; - -import com.android.internal.appwidget.IAppWidgetService; import com.android.internal.appwidget.IAppWidgetHost; +import com.android.internal.appwidget.IAppWidgetService; import com.android.internal.util.FastXmlSerializer; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; +import com.android.internal.widget.IRemoteViewsAdapterConnection; class AppWidgetService extends IAppWidgetService.Stub { @@ -107,6 +117,46 @@ class AppWidgetService extends IAppWidgetService.Stub Host host; } + /** + * Acts as a proxy between the ServiceConnection and the RemoteViewsAdapterConnection. + * This needs to be a static inner class since a reference to the ServiceConnection is held + * globally and may lead us to leak AppWidgetService instances (if there were more than one). + */ + static class ServiceConnectionProxy implements ServiceConnection { + private final Pair<Integer, Intent.FilterComparison> mKey; + private final IBinder mConnectionCb; + + ServiceConnectionProxy(Pair<Integer, Intent.FilterComparison> key, IBinder connectionCb) { + mKey = key; + mConnectionCb = connectionCb; + } + public void onServiceConnected(ComponentName name, IBinder service) { + final IRemoteViewsAdapterConnection cb = + IRemoteViewsAdapterConnection.Stub.asInterface(mConnectionCb); + try { + cb.onServiceConnected(service); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + public void onServiceDisconnected(ComponentName name) { + disconnect(); + } + public void disconnect() { + final IRemoteViewsAdapterConnection cb = + IRemoteViewsAdapterConnection.Stub.asInterface(mConnectionCb); + try { + cb.onServiceDisconnected(); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } + + // Manages connections to RemoteViewsServices + private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection> + mBoundRemoteViewsServices = new HashMap<Pair<Integer,FilterComparison>,ServiceConnection>(); + Context mContext; Locale mLocale; PackageManager mPackageManager; @@ -144,6 +194,7 @@ class AppWidgetService extends IAppWidgetService.Stub // update the provider list. IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addDataScheme("package"); mContext.registerReceiver(mBroadcastReceiver, filter); @@ -293,6 +344,9 @@ class AppWidgetService extends IAppWidgetService.Stub } void deleteAppWidgetLocked(AppWidgetId id) { + // We first unbind all services that are bound to this id + unbindAppWidgetRemoteViewsServicesLocked(id); + Host host = id.host; host.instances.remove(id); pruneHostLocked(host); @@ -375,6 +429,94 @@ class AppWidgetService extends IAppWidgetService.Stub } } + public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) { + synchronized (mAppWidgetIds) { + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + if (id == null) { + throw new IllegalArgumentException("bad appWidgetId"); + } + final ComponentName componentName = intent.getComponent(); + try { + final ServiceInfo si = mContext.getPackageManager().getServiceInfo(componentName, + PackageManager.GET_PERMISSIONS); + if (!android.Manifest.permission.BIND_REMOTEVIEWS.equals(si.permission)) { + throw new SecurityException("Selected service does not require " + + android.Manifest.permission.BIND_REMOTEVIEWS + + ": " + componentName); + } + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalArgumentException("Unknown component " + componentName); + } + + // If there is already a connection made for this service intent, then disconnect from + // that first. (This does not allow multiple connections to the same service under + // the same key) + ServiceConnectionProxy conn = null; + Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, + new FilterComparison(intent)); + if (mBoundRemoteViewsServices.containsKey(key)) { + conn = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key); + conn.disconnect(); + mContext.unbindService(conn); + mBoundRemoteViewsServices.remove(key); + } + + // Bind to the RemoteViewsService (which will trigger a callback to the + // RemoteViewsAdapter.onServiceConnected()) + final long token = Binder.clearCallingIdentity(); + try { + conn = new ServiceConnectionProxy(key, connection); + mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); + mBoundRemoteViewsServices.put(key, conn); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + + public void unbindRemoteViewsService(int appWidgetId, Intent intent) { + synchronized (mAppWidgetIds) { + // Unbind from the RemoteViewsService (which will trigger a callback to the bound + // RemoteViewsAdapter) + Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, + new FilterComparison(intent)); + if (mBoundRemoteViewsServices.containsKey(key)) { + // We don't need to use the appWidgetId until after we are sure there is something + // to unbind. Note that this may mask certain issues with apps calling unbind() + // more than necessary. + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + if (id == null) { + throw new IllegalArgumentException("bad appWidgetId"); + } + + ServiceConnectionProxy conn = + (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key); + conn.disconnect(); + mContext.unbindService(conn); + mBoundRemoteViewsServices.remove(key); + } else { + Log.e("AppWidgetService", "Error (unbindRemoteViewsService): Connection not bound"); + } + } + } + + private void unbindAppWidgetRemoteViewsServicesLocked(AppWidgetId id) { + int appWidgetId = id.appWidgetId; + // Unbind all connections to Services bound to this AppWidgetId + Iterator<Pair<Integer, Intent.FilterComparison>> it = + mBoundRemoteViewsServices.keySet().iterator(); + while (it.hasNext()) { + final Pair<Integer, Intent.FilterComparison> key = it.next(); + if (key.first.intValue() == appWidgetId) { + final ServiceConnectionProxy conn = (ServiceConnectionProxy) + mBoundRemoteViewsServices.get(key); + conn.disconnect(); + mContext.unbindService(conn); + it.remove(); + } + } + } + public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) { synchronized (mAppWidgetIds) { AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); @@ -426,6 +568,40 @@ class AppWidgetService extends IAppWidgetService.Stub } } + public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views) { + if (appWidgetIds == null) { + return; + } + if (appWidgetIds.length == 0) { + return; + } + final int N = appWidgetIds.length; + + synchronized (mAppWidgetIds) { + for (int i=0; i<N; i++) { + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); + updateAppWidgetInstanceLocked(id, views, true); + } + } + } + + public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) { + if (appWidgetIds == null) { + return; + } + if (appWidgetIds.length == 0) { + return; + } + final int N = appWidgetIds.length; + + synchronized (mAppWidgetIds) { + for (int i=0; i<N; i++) { + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); + notifyAppWidgetViewDataChangedInstanceLocked(id, viewId); + } + } + } + public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) { synchronized (mAppWidgetIds) { Provider p = lookupProviderLocked(provider); @@ -443,11 +619,17 @@ class AppWidgetService extends IAppWidgetService.Stub } void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views) { + updateAppWidgetInstanceLocked(id, views, false); + } + + void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views, boolean isPartialUpdate) { // allow for stale appWidgetIds and other badness // lookup also checks that the calling process can access the appWidgetId // drop unbound appWidgetIds (shouldn't be possible under normal circumstances) if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) { - id.views = views; + + // We do not want to save this RemoteViews + if (!isPartialUpdate) id.views = views; // is anyone listening? if (id.host.callbacks != null) { @@ -463,6 +645,25 @@ class AppWidgetService extends IAppWidgetService.Stub } } + void notifyAppWidgetViewDataChangedInstanceLocked(AppWidgetId id, int viewId) { + // allow for stale appWidgetIds and other badness + // lookup also checks that the calling process can access the appWidgetId + // drop unbound appWidgetIds (shouldn't be possible under normal circumstances) + if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) { + // is anyone listening? + if (id.host.callbacks != null) { + try { + // the lock is held, but this is a oneway call + id.host.callbacks.viewDataChanged(id.appWidgetId, viewId); + } catch (RemoteException e) { + // It failed; remove the callback. No need to prune because + // we know that this host is still referenced by this instance. + id.host.callbacks = null; + } + } + } + } + public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId, List<RemoteViews> updatedViews) { int callingUid = enforceCallingUid(packageName); @@ -584,6 +785,12 @@ class AppWidgetService extends IAppWidgetService.Stub } boolean addProviderLocked(ResolveInfo ri) { + if ((ri.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { + return false; + } + if (!ri.activityInfo.isEnabled()) { + return false; + } Provider p = parseProviderInfoXml(new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name), ri); if (p != null) { @@ -742,6 +949,11 @@ class AppWidgetService extends IAppWidgetService.Stub } info.label = activityInfo.loadLabel(mPackageManager).toString(); info.icon = ri.getIconResource(); + info.previewImage = sa.getResourceId( + com.android.internal.R.styleable.AppWidgetProviderInfo_previewImage, 0); + info.autoAdvanceViewId = sa.getResourceId( + com.android.internal.R.styleable.AppWidgetProviderInfo_autoAdvanceViewId, -1); + sa.recycle(); } catch (Exception e) { // Ok to catch Exception here, because anything going wrong because @@ -1096,6 +1308,7 @@ class AppWidgetService extends IAppWidgetService.Stub } } else { boolean added = false; + boolean changed = false; String pkgList[] = null; if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); @@ -1114,14 +1327,16 @@ class AppWidgetService extends IAppWidgetService.Stub } pkgList = new String[] { pkgName }; added = Intent.ACTION_PACKAGE_ADDED.equals(action); + changed = Intent.ACTION_PACKAGE_CHANGED.equals(action); } if (pkgList == null || pkgList.length == 0) { return; } - if (added) { + if (added || changed) { synchronized (mAppWidgetIds) { Bundle extras = intent.getExtras(); - if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) { + if (changed || (extras != null && + extras.getBoolean(Intent.EXTRA_REPLACING, false))) { for (String pkgName : pkgList) { // The package was just upgraded updateProvidersForPackageLocked(pkgName); diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index f1e226e..c48f360 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -107,6 +107,7 @@ class BackupManagerService extends IBackupManager.Stub { private static final int MSG_RUN_INITIALIZE = 5; private static final int MSG_RUN_GET_RESTORE_SETS = 6; private static final int MSG_TIMEOUT = 7; + private static final int MSG_RESTORE_TIMEOUT = 8; // Timeout interval for deciding that a bind or clear-data has taken too long static final long TIMEOUT_INTERVAL = 10 * 1000; @@ -148,9 +149,9 @@ class BackupManagerService extends IBackupManager.Stub { return "BackupRequest{app=" + appInfo + " full=" + fullBackup + "}"; } } - // Backups that we haven't started yet. - HashMap<ApplicationInfo,BackupRequest> mPendingBackups - = new HashMap<ApplicationInfo,BackupRequest>(); + // Backups that we haven't started yet. Keys are package names. + HashMap<String,BackupRequest> mPendingBackups + = new HashMap<String,BackupRequest>(); // Pseudoname that we use for the Package Manager metadata "package" static final String PACKAGE_MANAGER_SENTINEL = "@pm@"; @@ -396,6 +397,21 @@ class BackupManagerService extends IBackupManager.Stub { } break; } + + case MSG_RESTORE_TIMEOUT: + { + synchronized (BackupManagerService.this) { + if (mActiveRestoreSession != null) { + // Client app left the restore session dangling. We know that it + // can't be in the middle of an actual restore operation because + // those are executed serially on this same handler thread. Clean + // up now. + Slog.w(TAG, "Restore session timed out; aborting"); + post(mActiveRestoreSession.new EndRestoreRunnable( + BackupManagerService.this, mActiveRestoreSession)); + } + } + } } } } @@ -590,6 +606,7 @@ class BackupManagerService extends IBackupManager.Stub { } } } + tf.close(); } catch (FileNotFoundException fnf) { // Probably innocuous Slog.v(TAG, "No ancestral data"); @@ -912,42 +929,48 @@ class BackupManagerService extends IBackupManager.Stub { // 'packageName' is null, *all* participating apps will be removed. void removePackageParticipantsLocked(String packageName) { if (DEBUG) Slog.v(TAG, "removePackageParticipantsLocked: " + packageName); - List<PackageInfo> allApps = null; + List<String> allApps = new ArrayList<String>(); if (packageName != null) { - allApps = new ArrayList<PackageInfo>(); - try { - int flags = PackageManager.GET_SIGNATURES; - allApps.add(mPackageManager.getPackageInfo(packageName, flags)); - } catch (Exception e) { - // just skip it (???) - } + allApps.add(packageName); } else { // all apps with agents - allApps = allAgentPackages(); + List<PackageInfo> knownPackages = allAgentPackages(); + for (PackageInfo pkg : knownPackages) { + allApps.add(pkg.packageName); + } } removePackageParticipantsLockedInner(packageName, allApps); } private void removePackageParticipantsLockedInner(String packageName, - List<PackageInfo> agents) { + List<String> allPackageNames) { if (DEBUG) { Slog.v(TAG, "removePackageParticipantsLockedInner (" + packageName - + ") removing " + agents.size() + " entries"); - for (PackageInfo p : agents) { + + ") removing " + allPackageNames.size() + " entries"); + for (String p : allPackageNames) { Slog.v(TAG, " - " + p); } } - for (PackageInfo pkg : agents) { - if (packageName == null || pkg.packageName.equals(packageName)) { - int uid = pkg.applicationInfo.uid; + for (String pkg : allPackageNames) { + if (packageName == null || pkg.equals(packageName)) { + int uid = -1; + try { + PackageInfo info = mPackageManager.getPackageInfo(packageName, 0); + uid = info.applicationInfo.uid; + } catch (NameNotFoundException e) { + // we don't know this package name, so just skip it for now + continue; + } + HashSet<ApplicationInfo> set = mBackupParticipants.get(uid); if (set != null) { // Find the existing entry with the same package name, and remove it. // We can't just remove(app) because the instances are different. for (ApplicationInfo entry: set) { - if (entry.packageName.equals(pkg.packageName)) { + if (entry.packageName.equals(pkg)) { + if (DEBUG) Slog.v(TAG, " removing participant " + pkg); set.remove(entry); - removeEverBackedUp(pkg.packageName); + removeEverBackedUp(pkg); break; } } @@ -997,7 +1020,11 @@ class BackupManagerService extends IBackupManager.Stub { // brute force but small code size List<PackageInfo> allApps = allAgentPackages(); - removePackageParticipantsLockedInner(packageName, allApps); + List<String> allAppNames = new ArrayList<String>(); + for (PackageInfo pkg : allApps) { + allAppNames.add(pkg.packageName); + } + removePackageParticipantsLockedInner(packageName, allAppNames); addPackageParticipantsLockedInner(packageName, allApps); } @@ -1364,6 +1391,17 @@ class BackupManagerService extends IBackupManager.Stub { for (BackupRequest request : mQueue) { Slog.d(TAG, "starting agent for backup of " + request); + // Verify that the requested app exists; it might be something that + // requested a backup but was then uninstalled. The request was + // journalled and rather than tamper with the journal it's safer + // to sanity-check here. + try { + mPackageManager.getPackageInfo(request.appInfo.packageName, 0); + } catch (NameNotFoundException e) { + Slog.d(TAG, "Package does not exist; skipping"); + continue; + } + IBackupAgent agent = null; int mode = (request.fullBackup) ? IApplicationThread.BACKUP_MODE_FULL @@ -1825,6 +1863,11 @@ class BackupManagerService extends IBackupManager.Stub { } catch (RemoteException e) { /* can't happen */ } } + // Furthermore we need to reset the session timeout clock + mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT); + mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_TIMEOUT, + TIMEOUT_RESTORE_INTERVAL); + // done; we can finally release the wakelock mWakelock.release(); } @@ -2046,7 +2089,7 @@ class BackupManagerService extends IBackupManager.Stub { // Add the caller to the set of pending backups. If there is // one already there, then overwrite it, but no harm done. BackupRequest req = new BackupRequest(app, false); - if (mPendingBackups.put(app, req) == null) { + if (mPendingBackups.put(app.packageName, req) == null) { // Journal this request in case of crash. The put() // operation returned null when this package was not already // in the set; we want to avoid touching the disk redundantly. @@ -2343,6 +2386,55 @@ class BackupManagerService extends IBackupManager.Stub { } } + // Supply the configuration Intent for the given transport. If the name is not one + // of the available transports, or if the transport does not supply any configuration + // UI, the method returns null. + public Intent getConfigurationIntent(String transportName) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, + "getConfigurationIntent"); + + synchronized (mTransports) { + final IBackupTransport transport = mTransports.get(transportName); + if (transport != null) { + try { + final Intent intent = transport.configurationIntent(); + if (DEBUG) Slog.d(TAG, "getConfigurationIntent() returning config intent " + + intent); + return intent; + } catch (RemoteException e) { + /* fall through to return null */ + } + } + } + + return null; + } + + // Supply the configuration summary string for the given transport. If the name is + // not one of the available transports, or if the transport does not supply any + // summary / destination string, the method can return null. + // + // This string is used VERBATIM as the summary text of the relevant Settings item! + public String getDestinationString(String transportName) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, + "getConfigurationIntent"); + + synchronized (mTransports) { + final IBackupTransport transport = mTransports.get(transportName); + if (transport != null) { + try { + final String text = transport.currentDestinationString(); + if (DEBUG) Slog.d(TAG, "getDestinationString() returning " + text); + return text; + } catch (RemoteException e) { + /* fall through to return null */ + } + } + } + + return null; + } + // Callback: a requested backup agent has been instantiated. This should only // be called from the Activity Manager. public void agentConnected(String packageName, IBinder agentBinder) { @@ -2456,10 +2548,23 @@ class BackupManagerService extends IBackupManager.Stub { return null; } mActiveRestoreSession = new ActiveRestoreSession(packageName, transport); + mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_TIMEOUT, TIMEOUT_RESTORE_INTERVAL); } return mActiveRestoreSession; } + void clearRestoreSession(ActiveRestoreSession currentSession) { + synchronized(this) { + if (currentSession != mActiveRestoreSession) { + Slog.e(TAG, "ending non-current restore session"); + } else { + if (DEBUG) Slog.v(TAG, "Clearing restore session and halting timeout"); + mActiveRestoreSession = null; + mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT); + } + } + } + // Note that a currently-active backup agent has notified us that it has // completed the given outstanding asynchronous backup/restore operation. public void opComplete(int token) { @@ -2478,6 +2583,7 @@ class BackupManagerService extends IBackupManager.Stub { private String mPackageName; private IBackupTransport mRestoreTransport = null; RestoreSet[] mRestoreSets = null; + boolean mEnded = false; ActiveRestoreSession(String packageName, String transport) { mPackageName = packageName; @@ -2492,6 +2598,10 @@ class BackupManagerService extends IBackupManager.Stub { throw new IllegalArgumentException("Observer must not be null"); } + if (mEnded) { + throw new IllegalStateException("Restore session already ended"); + } + long oldId = Binder.clearCallingIdentity(); try { if (mRestoreTransport == null) { @@ -2519,6 +2629,10 @@ class BackupManagerService extends IBackupManager.Stub { if (DEBUG) Slog.d(TAG, "restoreAll token=" + Long.toHexString(token) + " observer=" + observer); + if (mEnded) { + throw new IllegalStateException("Restore session already ended"); + } + if (mRestoreTransport == null || mRestoreSets == null) { Slog.e(TAG, "Ignoring restoreAll() with no restore set"); return -1; @@ -2550,6 +2664,10 @@ class BackupManagerService extends IBackupManager.Stub { public synchronized int restorePackage(String packageName, IRestoreObserver observer) { if (DEBUG) Slog.v(TAG, "restorePackage pkg=" + packageName + " obs=" + observer); + if (mEnded) { + throw new IllegalStateException("Restore session already ended"); + } + if (mPackageName != null) { if (! mPackageName.equals(packageName)) { Slog.e(TAG, "Ignoring attempt to restore pkg=" + packageName @@ -2606,34 +2724,59 @@ class BackupManagerService extends IBackupManager.Stub { return 0; } - public synchronized void endRestoreSession() { - if (DEBUG) Slog.d(TAG, "endRestoreSession"); + // Posted to the handler to tear down a restore session in a cleanly synchronized way + class EndRestoreRunnable implements Runnable { + BackupManagerService mBackupManager; + ActiveRestoreSession mSession; - synchronized (this) { - long oldId = Binder.clearCallingIdentity(); - try { - if (mRestoreTransport != null) mRestoreTransport.finishRestore(); - } catch (Exception e) { - Slog.e(TAG, "Error in finishRestore", e); - } finally { - mRestoreTransport = null; - Binder.restoreCallingIdentity(oldId); - } + EndRestoreRunnable(BackupManagerService manager, ActiveRestoreSession session) { + mBackupManager = manager; + mSession = session; } - synchronized (BackupManagerService.this) { - if (BackupManagerService.this.mActiveRestoreSession == this) { - BackupManagerService.this.mActiveRestoreSession = null; - } else { - Slog.e(TAG, "ending non-current restore session"); + public void run() { + // clean up the session's bookkeeping + synchronized (mSession) { + try { + if (mSession.mRestoreTransport != null) { + mSession.mRestoreTransport.finishRestore(); + } + } catch (Exception e) { + Slog.e(TAG, "Error in finishRestore", e); + } finally { + mSession.mRestoreTransport = null; + mSession.mEnded = true; + } } + + // clean up the BackupManagerService side of the bookkeeping + // and cancel any pending timeout message + mBackupManager.clearRestoreSession(mSession); } } - } + public synchronized void endRestoreSession() { + if (DEBUG) Slog.d(TAG, "endRestoreSession"); + + if (mEnded) { + throw new IllegalStateException("Restore session already ended"); + } + + mBackupHandler.post(new EndRestoreRunnable(BackupManagerService.this, this)); + } + } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + long identityToken = Binder.clearCallingIdentity(); + try { + dumpInternal(pw); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + + private void dumpInternal(PrintWriter pw) { synchronized (mQueueLock) { pw.println("Backup Manager is " + (mEnabled ? "enabled" : "disabled") + " / " + (!mProvisioned ? "not " : "") + "provisioned / " @@ -2647,12 +2790,15 @@ class BackupManagerService extends IBackupManager.Stub { for (String t : listAllTransports()) { pw.println((t.equals(mCurrentTransport) ? " * " : " ") + t); try { - File dir = new File(mBaseStateDir, getTransport(t).transportDirName()); + IBackupTransport transport = getTransport(t); + File dir = new File(mBaseStateDir, transport.transportDirName()); + pw.println(" destination: " + transport.currentDestinationString()); + pw.println(" intent: " + transport.configurationIntent()); for (File f : dir.listFiles()) { pw.println(" " + f.getName() + " - " + f.length() + " state bytes"); } - } catch (RemoteException e) { - Slog.e(TAG, "Error in transportDirName()", e); + } catch (Exception e) { + Slog.e(TAG, "Error in transport", e); pw.println(" Error: " + e); } } diff --git a/services/java/com/android/server/BatteryService.java b/services/java/com/android/server/BatteryService.java index fc4e06f..47599c8 100644 --- a/services/java/com/android/server/BatteryService.java +++ b/services/java/com/android/server/BatteryService.java @@ -43,6 +43,7 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; +import java.util.Arrays; /** @@ -76,7 +77,7 @@ class BatteryService extends Binder { // Used locally for determining when to make a last ditch effort to log // discharge stats before the device dies. - private static final int CRITICAL_BATTERY_LEVEL = 4; + private int mCriticalBatteryLevel; private static final int DUMP_MAX_LENGTH = 24 * 1024; private static final String[] DUMPSYS_ARGS = new String[] { "--checkin", "-u" }; @@ -100,6 +101,7 @@ class BatteryService extends Binder { private int mBatteryTemperature; private String mBatteryTechnology; private boolean mBatteryLevelCritical; + private int mInvalidCharger; private int mLastBatteryStatus; private int mLastBatteryHealth; @@ -108,6 +110,7 @@ class BatteryService extends Binder { private int mLastBatteryVoltage; private int mLastBatteryTemperature; private boolean mLastBatteryLevelCritical; + private int mLastInvalidCharger; private int mLowBatteryWarningLevel; private int mLowBatteryCloseWarningLevel; @@ -118,18 +121,28 @@ class BatteryService extends Binder { private long mDischargeStartTime; private int mDischargeStartLevel; + private Led mLed; + private boolean mSentLowBatteryBroadcast = false; - public BatteryService(Context context) { + public BatteryService(Context context, LightsService lights) { mContext = context; + mLed = new Led(context, lights); mBatteryStats = BatteryStatsService.getService(); + mCriticalBatteryLevel = mContext.getResources().getInteger( + com.android.internal.R.integer.config_criticalBatteryWarningLevel); mLowBatteryWarningLevel = mContext.getResources().getInteger( com.android.internal.R.integer.config_lowBatteryWarningLevel); mLowBatteryCloseWarningLevel = mContext.getResources().getInteger( com.android.internal.R.integer.config_lowBatteryCloseWarningLevel); - mUEventObserver.startObserving("SUBSYSTEM=power_supply"); + mPowerSupplyObserver.startObserving("SUBSYSTEM=power_supply"); + + // watch for invalid charger messages if the invalid_charger switch exists + if (new File("/sys/devices/virtual/switch/invalid_charger/state").exists()) { + mInvalidChargerObserver.startObserving("DEVPATH=/devices/virtual/switch/invalid_charger"); + } // set initial status update(); @@ -163,13 +176,24 @@ class BatteryService extends Binder { return mPlugType; } - private UEventObserver mUEventObserver = new UEventObserver() { + private UEventObserver mPowerSupplyObserver = new UEventObserver() { @Override public void onUEvent(UEventObserver.UEvent event) { update(); } }; + private UEventObserver mInvalidChargerObserver = new UEventObserver() { + @Override + public void onUEvent(UEventObserver.UEvent event) { + int invalidCharger = "1".equals(event.get("SWITCH_STATE")) ? 1 : 0; + if (mInvalidCharger != invalidCharger) { + mInvalidCharger = invalidCharger; + update(); + } + } + }; + // returns battery level as a percentage final int getBatteryLevel() { return mBatteryLevel; @@ -207,11 +231,14 @@ class BatteryService extends Binder { private synchronized final void update() { native_update(); + processValues(); + } + private void processValues() { boolean logOutlier = false; long dischargeDuration = 0; - mBatteryLevelCritical = mBatteryLevel <= CRITICAL_BATTERY_LEVEL; + mBatteryLevelCritical = mBatteryLevel <= mCriticalBatteryLevel; if (mAcOnline) { mPlugType = BatteryManager.BATTERY_PLUGGED_AC; } else if (mUsbOnline) { @@ -238,7 +265,8 @@ class BatteryService extends Binder { mBatteryLevel != mLastBatteryLevel || mPlugType != mLastPlugType || mBatteryVoltage != mLastBatteryVoltage || - mBatteryTemperature != mLastBatteryTemperature) { + mBatteryTemperature != mLastBatteryTemperature || + mInvalidCharger != mLastInvalidCharger) { if (mPlugType != mLastPlugType) { if (mLastPlugType == BATTERY_PLUGGED_NONE) { @@ -292,9 +320,9 @@ class BatteryService extends Binder { * (becomes <= mLowBatteryWarningLevel). */ final boolean sendBatteryLow = !plugged - && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN - && mBatteryLevel <= mLowBatteryWarningLevel - && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel); + && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN + && mBatteryLevel <= mLowBatteryWarningLevel + && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel); sendIntent(); @@ -322,6 +350,9 @@ class BatteryService extends Binder { mContext.sendBroadcast(statusIntent); } + // Update the battery LED + mLed.updateLightsLocked(); + // This needs to be done after sendIntent() so that we get the lastest battery stats. if (logOutlier && dischargeDuration != 0) { logOutlier(dischargeDuration); @@ -335,6 +366,7 @@ class BatteryService extends Binder { mLastBatteryVoltage = mBatteryVoltage; mLastBatteryTemperature = mBatteryTemperature; mLastBatteryLevelCritical = mBatteryLevelCritical; + mLastInvalidCharger = mInvalidCharger; } } @@ -356,16 +388,17 @@ class BatteryService extends Binder { intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mBatteryVoltage); intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mBatteryTemperature); intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryTechnology); + intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger); - if (false) { - Slog.d(TAG, "updateBattery level:" + mBatteryLevel + + if (true) { + Slog.d(TAG, "level:" + mBatteryLevel + " scale:" + BATTERY_SCALE + " status:" + mBatteryStatus + " health:" + mBatteryHealth + " present:" + mBatteryPresent + " voltage: " + mBatteryVoltage + " temperature: " + mBatteryTemperature + " technology: " + mBatteryTechnology + " AC powered:" + mAcOnline + " USB powered:" + mUsbOnline + - " icon:" + icon ); + " icon:" + icon + " invalid charger:" + mInvalidCharger); } ActivityManagerNative.broadcastStickyIntent(intent, null); @@ -440,10 +473,15 @@ class BatteryService extends Binder { private final int getIcon(int level) { if (mBatteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) { return com.android.internal.R.drawable.stat_sys_battery_charge; - } else if (mBatteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING || - mBatteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING || - mBatteryStatus == BatteryManager.BATTERY_STATUS_FULL) { + } else if (mBatteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) { return com.android.internal.R.drawable.stat_sys_battery; + } else if (mBatteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING + || mBatteryStatus == BatteryManager.BATTERY_STATUS_FULL) { + if (isPowered() && mBatteryLevel >= 100) { + return com.android.internal.R.drawable.stat_sys_battery_charge; + } else { + return com.android.internal.R.drawable.stat_sys_battery; + } } else { return com.android.internal.R.drawable.stat_sys_battery_unknown; } @@ -460,18 +498,109 @@ class BatteryService extends Binder { return; } - synchronized (this) { - pw.println("Current Battery Service state:"); - pw.println(" AC powered: " + mAcOnline); - pw.println(" USB powered: " + mUsbOnline); - pw.println(" status: " + mBatteryStatus); - pw.println(" health: " + mBatteryHealth); - pw.println(" present: " + mBatteryPresent); - pw.println(" level: " + mBatteryLevel); - pw.println(" scale: " + BATTERY_SCALE); - pw.println(" voltage:" + mBatteryVoltage); - pw.println(" temperature: " + mBatteryTemperature); - pw.println(" technology: " + mBatteryTechnology); + if (args == null || args.length == 0) { + synchronized (this) { + pw.println("Current Battery Service state:"); + pw.println(" AC powered: " + mAcOnline); + pw.println(" USB powered: " + mUsbOnline); + pw.println(" status: " + mBatteryStatus); + pw.println(" health: " + mBatteryHealth); + pw.println(" present: " + mBatteryPresent); + pw.println(" level: " + mBatteryLevel); + pw.println(" scale: " + BATTERY_SCALE); + pw.println(" voltage:" + mBatteryVoltage); + pw.println(" temperature: " + mBatteryTemperature); + pw.println(" technology: " + mBatteryTechnology); + } + } else if (false) { + // DO NOT SUBMIT WITH THIS TURNED ON + if (args.length == 3 && "set".equals(args[0])) { + String key = args[1]; + String value = args[2]; + try { + boolean update = true; + if ("ac".equals(key)) { + mAcOnline = Integer.parseInt(value) != 0; + } else if ("usb".equals(key)) { + mUsbOnline = Integer.parseInt(value) != 0; + } else if ("status".equals(key)) { + mBatteryStatus = Integer.parseInt(value); + } else if ("level".equals(key)) { + mBatteryLevel = Integer.parseInt(value); + } else if ("invalid".equals(key)) { + mInvalidCharger = Integer.parseInt(value); + } else { + update = false; + } + if (update) { + processValues(); + } + } catch (NumberFormatException ex) { + pw.println("Bad value: " + value); + } + } + } + } + + class Led { + private LightsService mLightsService; + private LightsService.Light mBatteryLight; + + private int mBatteryLowARGB; + private int mBatteryMediumARGB; + private int mBatteryFullARGB; + private int mBatteryLedOn; + private int mBatteryLedOff; + + private boolean mBatteryCharging; + private boolean mBatteryLow; + private boolean mBatteryFull; + + Led(Context context, LightsService lights) { + mLightsService = lights; + mBatteryLight = lights.getLight(LightsService.LIGHT_ID_BATTERY); + + mBatteryLowARGB = mContext.getResources().getInteger( + com.android.internal.R.integer.config_notificationsBatteryLowARGB); + mBatteryMediumARGB = mContext.getResources().getInteger( + com.android.internal.R.integer.config_notificationsBatteryMediumARGB); + mBatteryFullARGB = mContext.getResources().getInteger( + com.android.internal.R.integer.config_notificationsBatteryFullARGB); + mBatteryLedOn = mContext.getResources().getInteger( + com.android.internal.R.integer.config_notificationsBatteryLedOn); + mBatteryLedOff = mContext.getResources().getInteger( + com.android.internal.R.integer.config_notificationsBatteryLedOff); + } + + /** + * Synchronize on BatteryService. + */ + void updateLightsLocked() { + final int level = mBatteryLevel; + final int status = mBatteryStatus; + if (level < mLowBatteryWarningLevel) { + if (status == BatteryManager.BATTERY_STATUS_CHARGING) { + // Solid red when battery is charging + mBatteryLight.setColor(mBatteryLowARGB); + } else { + // Flash red when battery is low and not charging + mBatteryLight.setFlashing(mBatteryLowARGB, LightsService.LIGHT_FLASH_TIMED, + mBatteryLedOn, mBatteryLedOff); + } + } else if (status == BatteryManager.BATTERY_STATUS_CHARGING + || status == BatteryManager.BATTERY_STATUS_FULL) { + if (status == BatteryManager.BATTERY_STATUS_FULL || level >= 90) { + // Solid green when full or charging and nearly full + mBatteryLight.setColor(mBatteryFullARGB); + } else { + // Solid orange when charging and halfway full + mBatteryLight.setColor(mBatteryMediumARGB); + } + } else { + // No lights if not charging and not low + mBatteryLight.turnOff(); + } } } } + diff --git a/services/java/com/android/server/ClipboardService.java b/services/java/com/android/server/ClipboardService.java index aa8cded..062ab74 100644 --- a/services/java/com/android/server/ClipboardService.java +++ b/services/java/com/android/server/ClipboardService.java @@ -16,42 +16,240 @@ package com.android.server; -import android.text.IClipboard; +import android.app.ActivityManagerNative; +import android.app.IActivityManager; +import android.content.ClipData; +import android.content.ClipDescription; +import android.content.IClipboard; +import android.content.IOnPrimaryClipChangedListener; import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.net.Uri; +import android.os.Binder; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Process; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.util.Pair; +import android.util.Slog; + +import java.util.HashSet; /** * Implementation of the clipboard for copy and paste. */ public class ClipboardService extends IClipboard.Stub { - private CharSequence mClipboard = ""; + private final Context mContext; + private final IActivityManager mAm; + private final PackageManager mPm; + private final IBinder mPermissionOwner; + + private final RemoteCallbackList<IOnPrimaryClipChangedListener> mPrimaryClipListeners + = new RemoteCallbackList<IOnPrimaryClipChangedListener>(); + + private ClipData mPrimaryClip; + + private final HashSet<String> mActivePermissionOwners + = new HashSet<String>(); /** * Instantiates the clipboard. */ - public ClipboardService(Context context) { } + public ClipboardService(Context context) { + mContext = context; + mAm = ActivityManagerNative.getDefault(); + mPm = context.getPackageManager(); + IBinder permOwner = null; + try { + permOwner = mAm.newUriPermissionOwner("clipboard"); + } catch (RemoteException e) { + Slog.w("clipboard", "AM dead", e); + } + mPermissionOwner = permOwner; + } + + @Override + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + try { + return super.onTransact(code, data, reply, flags); + } catch (RuntimeException e) { + Slog.w("clipboard", "Exception: ", e); + throw e; + } + + } - // javadoc from interface - public void setClipboardText(CharSequence text) { + public void setPrimaryClip(ClipData clip) { synchronized (this) { - if (text == null) { - text = ""; + if (clip != null && clip.getItemCount() <= 0) { + throw new IllegalArgumentException("No items"); + } + checkDataOwnerLocked(clip, Binder.getCallingUid()); + clearActiveOwnersLocked(); + mPrimaryClip = clip; + final int n = mPrimaryClipListeners.beginBroadcast(); + for (int i = 0; i < n; i++) { + try { + mPrimaryClipListeners.getBroadcastItem(i).dispatchPrimaryClipChanged(); + } catch (RemoteException e) { + + // The RemoteCallbackList will take care of removing + // the dead object for us. + } } + mPrimaryClipListeners.finishBroadcast(); + } + } - mClipboard = text; + public ClipData getPrimaryClip(String pkg) { + synchronized (this) { + addActiveOwnerLocked(Binder.getCallingUid(), pkg); + return mPrimaryClip; } } - // javadoc from interface - public CharSequence getClipboardText() { + public ClipDescription getPrimaryClipDescription() { synchronized (this) { - return mClipboard; + return mPrimaryClip != null ? mPrimaryClip.getDescription() : null; + } + } + + public boolean hasPrimaryClip() { + synchronized (this) { + return mPrimaryClip != null; + } + } + + public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) { + synchronized (this) { + mPrimaryClipListeners.register(listener); + } + } + + public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) { + synchronized (this) { + mPrimaryClipListeners.unregister(listener); } } - // javadoc from interface public boolean hasClipboardText() { synchronized (this) { - return mClipboard.length() > 0; + if (mPrimaryClip != null) { + CharSequence text = mPrimaryClip.getItemAt(0).getText(); + return text != null && text.length() > 0; + } + return false; + } + } + + private final void checkUriOwnerLocked(Uri uri, int uid) { + if (!"content".equals(uri.getScheme())) { + return; + } + long ident = Binder.clearCallingIdentity(); + boolean allowed = false; + try { + // This will throw SecurityException for us. + mAm.checkGrantUriPermission(uid, null, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private final void checkItemOwnerLocked(ClipData.Item item, int uid) { + if (item.getUri() != null) { + checkUriOwnerLocked(item.getUri(), uid); + } + Intent intent = item.getIntent(); + if (intent != null && intent.getData() != null) { + checkUriOwnerLocked(intent.getData(), uid); + } + } + + private final void checkDataOwnerLocked(ClipData data, int uid) { + final int N = data.getItemCount(); + for (int i=0; i<N; i++) { + checkItemOwnerLocked(data.getItemAt(i), uid); + } + } + + private final void grantUriLocked(Uri uri, String pkg) { + long ident = Binder.clearCallingIdentity(); + try { + mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg, uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION); + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private final void grantItemLocked(ClipData.Item item, String pkg) { + if (item.getUri() != null) { + grantUriLocked(item.getUri(), pkg); + } + Intent intent = item.getIntent(); + if (intent != null && intent.getData() != null) { + grantUriLocked(intent.getData(), pkg); + } + } + + private final void addActiveOwnerLocked(int uid, String pkg) { + PackageInfo pi; + try { + pi = mPm.getPackageInfo(pkg, 0); + if (pi.applicationInfo.uid != uid) { + throw new SecurityException("Calling uid " + uid + + " does not own package " + pkg); + } + } catch (NameNotFoundException e) { + throw new IllegalArgumentException("Unknown package " + pkg, e); + } + if (mPrimaryClip != null && !mActivePermissionOwners.contains(pkg)) { + final int N = mPrimaryClip.getItemCount(); + for (int i=0; i<N; i++) { + grantItemLocked(mPrimaryClip.getItemAt(i), pkg); + } + mActivePermissionOwners.add(pkg); + } + } + + private final void revokeUriLocked(Uri uri) { + long ident = Binder.clearCallingIdentity(); + try { + mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private final void revokeItemLocked(ClipData.Item item) { + if (item.getUri() != null) { + revokeUriLocked(item.getUri()); + } + Intent intent = item.getIntent(); + if (intent != null && intent.getData() != null) { + revokeUriLocked(intent.getData()); + } + } + + private final void clearActiveOwnersLocked() { + mActivePermissionOwners.clear(); + if (mPrimaryClip == null) { + return; + } + final int N = mPrimaryClip.getItemCount(); + for (int i=0; i<N; i++) { + revokeItemLocked(mPrimaryClip.getItemAt(i)); } } } diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 40cfda9..e689654 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -16,24 +16,31 @@ package com.android.server; -import android.app.Notification; -import android.app.NotificationManager; +import android.bluetooth.BluetoothTetheringDataTracker; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.database.ContentObserver; import android.net.ConnectivityManager; +import android.net.DummyDataStateTracker; import android.net.IConnectivityManager; +import android.net.LinkProperties; import android.net.MobileDataStateTracker; import android.net.NetworkInfo; import android.net.NetworkStateTracker; import android.net.NetworkUtils; +import android.net.Proxy; +import android.net.ProxyProperties; +import android.net.vpn.VpnManager; import android.net.wifi.WifiStateTracker; import android.os.Binder; import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; @@ -43,23 +50,26 @@ import android.util.EventLog; import android.util.Slog; import com.android.internal.telephony.Phone; - import com.android.server.connectivity.Tethering; import java.io.FileDescriptor; +import java.io.FileWriter; +import java.io.IOException; import java.io.PrintWriter; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.GregorianCalendar; import java.util.List; -import java.net.InetAddress; -import java.net.UnknownHostException; /** * @hide */ public class ConnectivityService extends IConnectivityManager.Stub { - private static final boolean DBG = false; + private static final boolean DBG = true; private static final String TAG = "ConnectivityService"; // how long to wait before switching back to a radio's default network @@ -68,7 +78,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { private static final String NETWORK_RESTORE_DELAY_PROP_NAME = "android.telephony.apn-restore"; - private Tethering mTethering; private boolean mTetheringConfigValid = false; @@ -85,6 +94,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private List mNetRequestersPids[]; + private WifiWatchdogService mWifiWatchdogService; + // priority order of the nettrackers // (excluding dynamically set mNetworkPreference) // TODO - move mNetworkTypePreference into this @@ -104,6 +115,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { private boolean mTestMode; private static ConnectivityService sServiceInstance; + private AtomicBoolean mBackgroundDataEnabled = new AtomicBoolean(true); + private static final int ENABLED = 1; private static final int DISABLED = 0; @@ -162,6 +175,19 @@ public class ConnectivityService extends IConnectivityManager.Stub { private static final int EVENT_SET_MOBILE_DATA = MAX_NETWORK_STATE_TRACKER_EVENT + 7; + /** + * used internally to clear a wakelock when transitioning + * from one net to another + */ + private static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK = + MAX_NETWORK_STATE_TRACKER_EVENT + 8; + + /** + * used internally to reload global proxy settings + */ + private static final int EVENT_APPLY_GLOBAL_HTTP_PROXY = + MAX_NETWORK_STATE_TRACKER_EVENT + 9; + private Handler mHandler; // list of DeathRecipients used to make sure features are turned off when @@ -171,10 +197,25 @@ public class ConnectivityService extends IConnectivityManager.Stub { private boolean mSystemReady; private Intent mInitialBroadcast; + private PowerManager.WakeLock mNetTransitionWakeLock; + private String mNetTransitionWakeLockCausedBy = ""; + private int mNetTransitionWakeLockSerialNumber; + private int mNetTransitionWakeLockTimeout; + + private InetAddress mDefaultDns; + // used in DBG mode to track inet condition reports private static final int INET_CONDITION_LOG_MAX_SIZE = 15; private ArrayList mInetLog; + // track the current default http proxy - tell the world if we get a new one (real change) + private ProxyProperties mDefaultProxy = null; + // track the global proxy. + private ProxyProperties mGlobalProxy = null; + private final Object mGlobalProxyLock = new Object(); + + private SettingsObserver mSettingsObserver; + private static class NetworkAttributes { /** * Class for holding settings read from resources. @@ -210,64 +251,55 @@ public class ConnectivityService extends IConnectivityManager.Stub { } RadioAttributes[] mRadioAttributes; - private static class ConnectivityThread extends Thread { - private Context mContext; - - private ConnectivityThread(Context context) { - super("ConnectivityThread"); - mContext = context; + public static synchronized ConnectivityService getInstance(Context context) { + if (sServiceInstance == null) { + sServiceInstance = new ConnectivityService(context); } - - @Override - public void run() { - Looper.prepare(); - synchronized (this) { - sServiceInstance = new ConnectivityService(mContext); - notifyAll(); - } - Looper.loop(); - } - - public static ConnectivityService getServiceInstance(Context context) { - ConnectivityThread thread = new ConnectivityThread(context); - thread.start(); - - synchronized (thread) { - while (sServiceInstance == null) { - try { - // Wait until sServiceInstance has been initialized. - thread.wait(); - } catch (InterruptedException ignore) { - Slog.e(TAG, - "Unexpected InterruptedException while waiting"+ - " for ConnectivityService thread"); - } - } - } - - return sServiceInstance; - } - } - - public static ConnectivityService getInstance(Context context) { - return ConnectivityThread.getServiceInstance(context); + return sServiceInstance; } private ConnectivityService(Context context) { - if (DBG) Slog.v(TAG, "ConnectivityService starting up"); + if (DBG) log("ConnectivityService starting up"); + + HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread"); + handlerThread.start(); + mHandler = new MyHandler(handlerThread.getLooper()); + + mBackgroundDataEnabled.set(Settings.Secure.getInt(context.getContentResolver(), + Settings.Secure.BACKGROUND_DATA, 1) == 1); // setup our unique device name - String id = Settings.Secure.getString(context.getContentResolver(), - Settings.Secure.ANDROID_ID); - if (id != null && id.length() > 0) { - String name = new String("android_").concat(id); - SystemProperties.set("net.hostname", name); + if (TextUtils.isEmpty(SystemProperties.get("net.hostname"))) { + String id = Settings.Secure.getString(context.getContentResolver(), + Settings.Secure.ANDROID_ID); + if (id != null && id.length() > 0) { + String name = new String("android_").concat(id); + SystemProperties.set("net.hostname", name); + } + } + + // read our default dns server ip + String dns = Settings.Secure.getString(context.getContentResolver(), + Settings.Secure.DEFAULT_DNS_SERVER); + if (dns == null || dns.length() == 0) { + dns = context.getResources().getString( + com.android.internal.R.string.config_default_dns_server); + } + try { + mDefaultDns = InetAddress.getByName(dns); + } catch (UnknownHostException e) { + loge("Error setting defaultDns using " + dns); } mContext = context; + + PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); + mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + mNetTransitionWakeLockTimeout = mContext.getResources().getInteger( + com.android.internal.R.integer.config_networkTransitionTimeout); + mNetTrackers = new NetworkStateTracker[ ConnectivityManager.MAX_NETWORK_TYPE+1]; - mHandler = new MyHandler(); mNetworkPreference = getPersistedNetworkPreference(); @@ -280,11 +312,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { for (String raString : raStrings) { RadioAttributes r = new RadioAttributes(raString); if (r.mType > ConnectivityManager.MAX_RADIO_TYPE) { - Slog.e(TAG, "Error in radioAttributes - ignoring attempt to define type " + r.mType); + loge("Error in radioAttributes - ignoring attempt to define type " + r.mType); continue; } if (mRadioAttributes[r.mType] != null) { - Slog.e(TAG, "Error in radioAttributes - ignoring attempt to redefine type " + + loge("Error in radioAttributes - ignoring attempt to redefine type " + r.mType); continue; } @@ -297,17 +329,17 @@ public class ConnectivityService extends IConnectivityManager.Stub { try { NetworkAttributes n = new NetworkAttributes(naString); if (n.mType > ConnectivityManager.MAX_NETWORK_TYPE) { - Slog.e(TAG, "Error in networkAttributes - ignoring attempt to define type " + + loge("Error in networkAttributes - ignoring attempt to define type " + n.mType); continue; } if (mNetAttributes[n.mType] != null) { - Slog.e(TAG, "Error in networkAttributes - ignoring attempt to redefine type " + + loge("Error in networkAttributes - ignoring attempt to redefine type " + n.mType); continue; } if (mRadioAttributes[n.mRadio] == null) { - Slog.e(TAG, "Error in networkAttributes - ignoring attempt to use undefined " + + loge("Error in networkAttributes - ignoring attempt to use undefined " + "radio " + n.mRadio + " in network type " + n.mType); continue; } @@ -359,30 +391,37 @@ public class ConnectivityService extends IConnectivityManager.Stub { * the number of different network types is not going * to change very often. */ - boolean noMobileData = !getMobileDataEnabled(); for (int netType : mPriorityList) { switch (mNetAttributes[netType].mRadio) { case ConnectivityManager.TYPE_WIFI: - if (DBG) Slog.v(TAG, "Starting Wifi Service."); - WifiStateTracker wst = new WifiStateTracker(context, mHandler); - WifiService wifiService = new WifiService(context, wst); + if (DBG) log("Starting Wifi Service."); + WifiStateTracker wst = new WifiStateTracker(); + WifiService wifiService = new WifiService(context); ServiceManager.addService(Context.WIFI_SERVICE, wifiService); - wifiService.startWifi(); + wifiService.checkAndStartWifi(); mNetTrackers[ConnectivityManager.TYPE_WIFI] = wst; - wst.startMonitoring(); + wst.startMonitoring(context, mHandler); + + //TODO: as part of WWS refactor, create only when needed + mWifiWatchdogService = new WifiWatchdogService(context); break; case ConnectivityManager.TYPE_MOBILE: - mNetTrackers[netType] = new MobileDataStateTracker(context, mHandler, - netType, mNetAttributes[netType].mName); - mNetTrackers[netType].startMonitoring(); - if (noMobileData) { - if (DBG) Slog.d(TAG, "tearing down Mobile networks due to setting"); - mNetTrackers[netType].teardown(); - } + mNetTrackers[netType] = new MobileDataStateTracker(netType, + mNetAttributes[netType].mName); + mNetTrackers[netType].startMonitoring(context, mHandler); + break; + case ConnectivityManager.TYPE_DUMMY: + mNetTrackers[netType] = new DummyDataStateTracker(netType, + mNetAttributes[netType].mName); + mNetTrackers[netType].startMonitoring(context, mHandler); + break; + case ConnectivityManager.TYPE_BLUETOOTH: + mNetTrackers[netType] = BluetoothTetheringDataTracker.getInstance(); + mNetTrackers[netType].startMonitoring(context, mHandler); break; default: - Slog.e(TAG, "Trying to create a DataStateTracker for an unknown radio type " + + loge("Trying to create a DataStateTracker for an unknown radio type " + mNetAttributes[netType].mRadio); continue; } @@ -392,12 +431,20 @@ public class ConnectivityService extends IConnectivityManager.Stub { mTetheringConfigValid = (((mNetTrackers[ConnectivityManager.TYPE_MOBILE_DUN] != null) || !mTethering.isDunRequired()) && (mTethering.getTetherableUsbRegexs().length != 0 || - mTethering.getTetherableWifiRegexs().length != 0) && + mTethering.getTetherableWifiRegexs().length != 0 || + mTethering.getTetherableBluetoothRegexs().length != 0) && mTethering.getUpstreamIfaceRegexs().length != 0); if (DBG) { mInetLog = new ArrayList(); } + + mSettingsObserver = new SettingsObserver(mHandler, EVENT_APPLY_GLOBAL_HTTP_PROXY); + mSettingsObserver.observe(mContext); + + loadGlobalProxy(); + + VpnManager.startVpnService(context); } @@ -465,8 +512,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (t != mNetworkPreference && mNetTrackers[t] != null && mNetTrackers[t].getNetworkInfo().isConnected()) { if (DBG) { - Slog.d(TAG, "tearing down " + - mNetTrackers[t].getNetworkInfo() + + log("tearing down " + mNetTrackers[t].getNetworkInfo() + " in enforcePreference"); } teardown(mNetTrackers[t]); @@ -499,9 +545,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { NetworkStateTracker t = mNetTrackers[type]; NetworkInfo info = t.getNetworkInfo(); if (info.isConnected()) { - if (DBG && type != mActiveDefaultNetwork) Slog.e(TAG, - "connected default network is not " + - "mActiveDefaultNetwork!"); + if (DBG && type != mActiveDefaultNetwork) { + loge("connected default network is not mActiveDefaultNetwork!"); + } return info; } } @@ -528,6 +574,38 @@ public class ConnectivityService extends IConnectivityManager.Stub { return result; } + /** + * Return LinkProperties for the active (i.e., connected) default + * network interface. It is assumed that at most one default network + * is active at a time. If more than one is active, it is indeterminate + * which will be returned. + * @return the ip properties for the active network, or {@code null} if + * none is active + */ + public LinkProperties getActiveLinkProperties() { + enforceAccessPermission(); + for (int type=0; type <= ConnectivityManager.MAX_NETWORK_TYPE; type++) { + if (mNetAttributes[type] == null || !mNetAttributes[type].isDefault()) { + continue; + } + NetworkStateTracker t = mNetTrackers[type]; + NetworkInfo info = t.getNetworkInfo(); + if (info.isConnected()) { + return t.getLinkProperties(); + } + } + return null; + } + + public LinkProperties getLinkProperties(int networkType) { + enforceAccessPermission(); + if (ConnectivityManager.isNetworkTypeValid(networkType)) { + NetworkStateTracker t = mNetTrackers[networkType]; + if (t != null) return t.getLinkProperties(); + } + return null; + } + public boolean setRadios(boolean turnOn) { boolean result = true; enforceChangePermission(); @@ -582,14 +660,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { } public void binderDied() { - Slog.d(TAG, "ConnectivityService FeatureUser binderDied(" + + log("ConnectivityService FeatureUser binderDied(" + mNetworkType + ", " + mFeature + ", " + mBinder + "), created " + (System.currentTimeMillis() - mCreateTime) + " mSec ago"); stopUsingNetworkFeature(this, false); } public void expire() { - Slog.d(TAG, "ConnectivityService FeatureUser expire(" + + log("ConnectivityService FeatureUser expire(" + mNetworkType + ", " + mFeature + ", " + mBinder +"), created " + (System.currentTimeMillis() - mCreateTime) + " mSec ago"); stopUsingNetworkFeature(this, false); @@ -605,8 +683,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { public int startUsingNetworkFeature(int networkType, String feature, IBinder binder) { if (DBG) { - Slog.d(TAG, "startUsingNetworkFeature for net " + networkType + - ": " + feature); + log("startUsingNetworkFeature for net " + networkType + ": " + feature); } enforceChangePermission(); if (!ConnectivityManager.isNetworkTypeValid(networkType) || @@ -619,15 +696,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { // TODO - move this into the MobileDataStateTracker int usedNetworkType = networkType; if(networkType == ConnectivityManager.TYPE_MOBILE) { - if (!getMobileDataEnabled()) { - if (DBG) Slog.d(TAG, "requested special network with data disabled - rejected"); - return Phone.APN_TYPE_NOT_AVAILABLE; - } if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) { usedNetworkType = ConnectivityManager.TYPE_MOBILE_MMS; } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_SUPL)) { usedNetworkType = ConnectivityManager.TYPE_MOBILE_SUPL; - } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN)) { + } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN) || + TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN_ALWAYS)) { usedNetworkType = ConnectivityManager.TYPE_MOBILE_DUN; } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) { usedNetworkType = ConnectivityManager.TYPE_MOBILE_HIPRI; @@ -635,15 +709,18 @@ public class ConnectivityService extends IConnectivityManager.Stub { } NetworkStateTracker network = mNetTrackers[usedNetworkType]; if (network != null) { + Integer currentPid = new Integer(getCallingPid()); if (usedNetworkType != networkType) { - Integer currentPid = new Integer(getCallingPid()); - NetworkStateTracker radio = mNetTrackers[networkType]; NetworkInfo ni = network.getNetworkInfo(); if (ni.isAvailable() == false) { - if (DBG) Slog.d(TAG, "special network not available"); - return Phone.APN_TYPE_NOT_AVAILABLE; + if (DBG) log("special network not available"); + if (!TextUtils.equals(feature,Phone.FEATURE_ENABLE_DUN_ALWAYS)) { + return Phone.APN_TYPE_NOT_AVAILABLE; + } else { + // else make the attempt anyway - probably giving REQUEST_STARTED below + } } synchronized(this) { @@ -662,28 +739,29 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (ni.isConnected() == true) { // add the pid-specific dns handleDnsConfigurationChange(networkType); - if (DBG) Slog.d(TAG, "special network already active"); + if (DBG) log("special network already active"); return Phone.APN_ALREADY_ACTIVE; } - if (DBG) Slog.d(TAG, "special network already connecting"); + if (DBG) log("special network already connecting"); return Phone.APN_REQUEST_STARTED; } // check if the radio in play can make another contact // assume if cannot for now - if (DBG) Slog.d(TAG, "reconnecting to special network"); + if (DBG) log("reconnecting to special network"); network.reconnect(); return Phone.APN_REQUEST_STARTED; } else { + // need to remember this unsupported request so we respond appropriately on stop synchronized(this) { mFeatureUsers.add(f); + if (!mNetRequestersPids[usedNetworkType].contains(currentPid)) { + // this gets used for per-pid dns when connected + mNetRequestersPids[usedNetworkType].add(currentPid); + } } - mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_RESTORE_DEFAULT_NETWORK, - f), getRestoreDefaultNetworkDelay()); - - return network.startUsingNetworkFeature(feature, - getCallingPid(), getCallingUid()); + return -1; } } return Phone.APN_TYPE_NOT_AVAILABLE; @@ -715,7 +793,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { return stopUsingNetworkFeature(u, true); } else { // none found! - if (DBG) Slog.d(TAG, "ignoring stopUsingNetworkFeature - not a live request"); + if (DBG) log("ignoring stopUsingNetworkFeature - not a live request"); return 1; } } @@ -730,7 +808,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { boolean callTeardown = false; // used to carry our decision outside of sync block if (DBG) { - Slog.d(TAG, "stopUsingNetworkFeature for net " + networkType + + log("stopUsingNetworkFeature for net " + networkType + ": " + feature); } @@ -743,7 +821,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { synchronized(this) { // check if this process still has an outstanding start request if (!mFeatureUsers.contains(u)) { - if (DBG) Slog.d(TAG, "ignoring - this process has no outstanding requests"); + if (DBG) log("ignoring - this process has no outstanding requests"); return 1; } u.unlinkDeathRecipient(); @@ -761,7 +839,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (x.mUid == u.mUid && x.mPid == u.mPid && x.mNetworkType == u.mNetworkType && TextUtils.equals(x.mFeature, u.mFeature)) { - if (DBG) Slog.d(TAG, "ignoring stopUsingNetworkFeature as dup is found"); + if (DBG) log("ignoring stopUsingNetworkFeature as dup is found"); return 1; } } @@ -774,7 +852,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { usedNetworkType = ConnectivityManager.TYPE_MOBILE_MMS; } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_SUPL)) { usedNetworkType = ConnectivityManager.TYPE_MOBILE_SUPL; - } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN)) { + } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN) || + TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN_ALWAYS)) { usedNetworkType = ConnectivityManager.TYPE_MOBILE_DUN; } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) { usedNetworkType = ConnectivityManager.TYPE_MOBILE_HIPRI; @@ -782,7 +861,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } tracker = mNetTrackers[usedNetworkType]; if (tracker == null) { - if (DBG) Slog.d(TAG, "ignoring - no known tracker for net type " + usedNetworkType); + if (DBG) log("ignoring - no known tracker for net type " + usedNetworkType); return -1; } if (usedNetworkType != networkType) { @@ -790,20 +869,21 @@ public class ConnectivityService extends IConnectivityManager.Stub { mNetRequestersPids[usedNetworkType].remove(currentPid); reassessPidDns(pid, true); if (mNetRequestersPids[usedNetworkType].size() != 0) { - if (DBG) Slog.d(TAG, "not tearing down special network - " + + if (DBG) log("not tearing down special network - " + "others still using it"); return 1; } callTeardown = true; + } else { + if (DBG) log("not a known feature - dropping"); } } - if (DBG) Slog.d(TAG, "Doing network teardown"); + if (DBG) log("Doing network teardown"); if (callTeardown) { tracker.teardown(); return 1; } else { - // do it the old fashioned way - return tracker.stopUsingNetworkFeature(feature, pid, uid); + return -1; } } @@ -847,16 +927,42 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (tracker == null || !tracker.getNetworkInfo().isConnected() || tracker.isTeardownRequested()) { if (DBG) { - Slog.d(TAG, "requestRouteToHostAddress on down network " + + log("requestRouteToHostAddress on down network " + "(" + networkType + ") - dropped"); } return false; } - try { - InetAddress inetAddress = InetAddress.getByAddress(hostAddress); - return tracker.requestRouteToHost(inetAddress); - } catch (UnknownHostException e) { + InetAddress addr = InetAddress.getByAddress(hostAddress); + return addHostRoute(tracker, addr); + } catch (UnknownHostException e) {} + return false; + } + + /** + * Ensure that a network route exists to deliver traffic to the specified + * host via the mobile data network. + * @param hostAddress the IP address of the host to which the route is desired, + * in network byte order. + * TODO - deprecate + * @return {@code true} on success, {@code false} on failure + */ + private boolean addHostRoute(NetworkStateTracker nt, InetAddress hostAddress) { + if (nt.getNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI) { + return false; + } + + LinkProperties p = nt.getLinkProperties(); + if (p == null) return false; + String interfaceName = p.getInterfaceName(); + + if (DBG) { + log("Requested host route to " + hostAddress + "(" + interfaceName + ")"); + } + if (interfaceName != null) { + return NetworkUtils.addHostRoute(interfaceName, hostAddress, null); + } else { + if (DBG) loge("addHostRoute failed due to null interface name"); return false; } } @@ -865,8 +971,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { * @see ConnectivityManager#getBackgroundDataSetting() */ public boolean getBackgroundDataSetting() { - return Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.BACKGROUND_DATA, 1) == 1; + return mBackgroundDataEnabled.get(); } /** @@ -877,6 +982,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { android.Manifest.permission.CHANGE_BACKGROUND_DATA_SETTING, "ConnectivityService"); + mBackgroundDataEnabled.set(allowBackgroundDataUsage); + mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_BACKGROUND_DATA, (allowBackgroundDataUsage ? ENABLED : DISABLED), 0)); } @@ -895,10 +1002,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { * @see ConnectivityManager#getMobileDataEnabled() */ public boolean getMobileDataEnabled() { + // TODO: This detail should probably be in DataConnectionTracker's + // which is where we store the value and maybe make this + // asynchronous. enforceAccessPermission(); boolean retVal = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.MOBILE_DATA, 1) == 1; - if (DBG) Slog.d(TAG, "getMobileDataEnabled returning " + retVal); + if (DBG) log("getMobileDataEnabled returning " + retVal); return retVal; } @@ -907,47 +1017,19 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ public void setMobileDataEnabled(boolean enabled) { enforceChangePermission(); - if (DBG) Slog.d(TAG, "setMobileDataEnabled(" + enabled + ")"); + if (DBG) log("setMobileDataEnabled(" + enabled + ")"); mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_MOBILE_DATA, (enabled ? ENABLED : DISABLED), 0)); } private void handleSetMobileData(boolean enabled) { - if (getMobileDataEnabled() == enabled) return; - - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.MOBILE_DATA, enabled ? 1 : 0); - - if (enabled) { - if (mNetTrackers[ConnectivityManager.TYPE_MOBILE] != null) { - if (DBG) { - Slog.d(TAG, "starting up " + mNetTrackers[ConnectivityManager.TYPE_MOBILE]); - } - mNetTrackers[ConnectivityManager.TYPE_MOBILE].reconnect(); - } - } else { - for (NetworkStateTracker nt : mNetTrackers) { - if (nt == null) continue; - int netType = nt.getNetworkInfo().getType(); - if (mNetAttributes[netType].mRadio == ConnectivityManager.TYPE_MOBILE) { - if (DBG) Slog.d(TAG, "tearing down " + nt); - nt.teardown(); - } - } - } - } - - private int getNumConnectedNetworks() { - int numConnectedNets = 0; - - for (NetworkStateTracker nt : mNetTrackers) { - if (nt != null && nt.getNetworkInfo().isConnected() && - !nt.isTeardownRequested()) { - ++numConnectedNets; + if (mNetTrackers[ConnectivityManager.TYPE_MOBILE] != null) { + if (DBG) { + Slog.d(TAG, mNetTrackers[ConnectivityManager.TYPE_MOBILE].toString() + enabled); } + mNetTrackers[ConnectivityManager.TYPE_MOBILE].setDataEnable(enabled); } - return numConnectedNets; } private void enforceAccessPermission() { @@ -975,6 +1057,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { "ConnectivityService"); } + private void enforceConnectivityInternalPermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CONNECTIVITY_INTERNAL, + "ConnectivityService"); + } + /** * Handle a {@code DISCONNECTED} event. If this pertains to the non-active * network, we ignore it. If it is for the active network, we send out a @@ -1018,18 +1106,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { info.getExtraInfo()); } - NetworkStateTracker newNet = null; if (mNetAttributes[prevNetType].isDefault()) { - newNet = tryFailover(prevNetType); - if (newNet != null) { - NetworkInfo switchTo = newNet.getNetworkInfo(); - if (!switchTo.isConnected()) { - // if the other net is connected they've already reset this and perhaps even gotten - // a positive report we don't want to overwrite, but if not we need to clear this now - // to turn our cellular sig strength white - mDefaultInetConditionPublished = 0; - intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true); - } + tryFailover(prevNetType); + if (mActiveDefaultNetwork != -1) { + NetworkInfo switchTo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo(); intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo); } else { mDefaultInetConditionPublished = 0; // we're not connected anymore @@ -1045,59 +1125,55 @@ public class ConnectivityService extends IConnectivityManager.Stub { * If the failover network is already connected, then immediately send * out a followup broadcast indicating successful failover */ - if (newNet != null && newNet.getNetworkInfo().isConnected()) { - sendConnectedBroadcast(newNet.getNetworkInfo()); + if (mActiveDefaultNetwork != -1) { + sendConnectedBroadcast(mNetTrackers[mActiveDefaultNetwork].getNetworkInfo()); } } - // returns null if no failover available - private NetworkStateTracker tryFailover(int prevNetType) { + private void tryFailover(int prevNetType) { /* - * If this is a default network, check if other defaults are available - * or active + * If this is a default network, check if other defaults are available. + * Try to reconnect on all available and let them hash it out when + * more than one connects. */ - NetworkStateTracker newNet = null; if (mNetAttributes[prevNetType].isDefault()) { if (mActiveDefaultNetwork == prevNetType) { mActiveDefaultNetwork = -1; } - int newType = -1; - int newPriority = -1; - boolean noMobileData = !getMobileDataEnabled(); + // don't signal a reconnect for anything lower or equal priority than our + // current connected default + // TODO - don't filter by priority now - nice optimization but risky +// int currentPriority = -1; +// if (mActiveDefaultNetwork != -1) { +// currentPriority = mNetAttributes[mActiveDefaultNetwork].mPriority; +// } for (int checkType=0; checkType <= ConnectivityManager.MAX_NETWORK_TYPE; checkType++) { if (checkType == prevNetType) continue; if (mNetAttributes[checkType] == null) continue; - if (mNetAttributes[checkType].isDefault() == false) continue; - if (mNetAttributes[checkType].mRadio == ConnectivityManager.TYPE_MOBILE && - noMobileData) { - Slog.e(TAG, "not failing over to mobile type " + checkType + - " because Mobile Data Disabled"); - continue; - } - NetworkStateTracker tracker = mNetTrackers[checkType]; - NetworkInfo info = tracker.getNetworkInfo(); - if (!info.isConnectedOrConnecting() || - tracker.isTeardownRequested()) { - info.setFailover(true); - tracker.reconnect(); - } - if (DBG) Slog.d(TAG, "Attempting to switch to " + info.getTypeName()); - - // figure out if this is the highest priority network - // so we send an appropriate return value - if (checkType == mNetworkPreference) { - newType = checkType; - } - if (mNetAttributes[checkType].mPriority > newPriority && - newType != mNetworkPreference) { - newType = checkType; - newPriority = mNetAttributes[checkType].mPriority; + if (!mNetAttributes[checkType].isDefault()) continue; + +// Enabling the isAvailable() optimization caused mobile to not get +// selected if it was in the middle of error handling. Specifically +// a moble connection that took 30 seconds to complete the DEACTIVATE_DATA_CALL +// would not be available and we wouldn't get connected to anything. +// So removing the isAvailable() optimization below for now. TODO: This +// optimization should work and we need to investigate why it doesn't work. +// This could be related to how DEACTIVATE_DATA_CALL is reporting its +// complete before it is really complete. +// if (!mNetTrackers[checkType].isAvailable()) continue; + +// if (currentPriority >= mNetAttributes[checkType].mPriority) continue; + + NetworkStateTracker checkTracker = mNetTrackers[checkType]; + NetworkInfo checkInfo = checkTracker.getNetworkInfo(); + if (!checkInfo.isConnectedOrConnecting() || checkTracker.isTeardownRequested()) { + checkInfo.setFailover(true); + checkTracker.reconnect(); } + if (DBG) log("Attempting to switch to " + checkInfo.getTypeName()); } } - - return newNet; } private void sendConnectedBroadcast(NetworkInfo info) { @@ -1142,7 +1218,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } else { reasonText = " (" + reason + ")."; } - Slog.e(TAG, "Attempt to connect to " + info.getTypeName() + " failed" + reasonText); + loge("Attempt to connect to " + info.getTypeName() + " failed" + reasonText); Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); @@ -1160,17 +1236,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { info.setFailover(false); } - NetworkStateTracker newNet = null; if (mNetAttributes[info.getType()].isDefault()) { - newNet = tryFailover(info.getType()); - if (newNet != null) { - NetworkInfo switchTo = newNet.getNetworkInfo(); - if (!switchTo.isConnected()) { - // if the other net is connected they've already reset this and perhaps - // even gotten a positive report we don't want to overwrite, but if not - // we need to clear this now to turn our cellular sig strength white - mDefaultInetConditionPublished = 0; - } + tryFailover(info.getType()); + if (mActiveDefaultNetwork != -1) { + NetworkInfo switchTo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo(); intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo); } else { mDefaultInetConditionPublished = 0; @@ -1184,8 +1253,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { * If the failover network is already connected, then immediately send * out a followup broadcast indicating successful failover */ - if (newNet != null && newNet.getNetworkInfo().isConnected()) { - sendConnectedBroadcast(newNet.getNetworkInfo()); + if (mActiveDefaultNetwork != -1) { + sendConnectedBroadcast(mNetTrackers[mActiveDefaultNetwork].getNetworkInfo()); } } @@ -1207,6 +1276,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { mInitialBroadcast = null; } } + // load the global proxy at startup + mHandler.sendMessage(mHandler.obtainMessage(EVENT_APPLY_GLOBAL_HTTP_PROXY)); } private void handleConnect(NetworkInfo info) { @@ -1225,24 +1296,35 @@ public class ConnectivityService extends IConnectivityManager.Stub { mNetAttributes[type].mPriority) || mNetworkPreference == mActiveDefaultNetwork) { // don't accept this one - if (DBG) Slog.v(TAG, "Not broadcasting CONNECT_ACTION " + + if (DBG) { + log("Not broadcasting CONNECT_ACTION " + "to torn down network " + info.getTypeName()); + } teardown(thisNet); return; } else { // tear down the other NetworkStateTracker otherNet = mNetTrackers[mActiveDefaultNetwork]; - if (DBG) Slog.v(TAG, "Policy requires " + - otherNet.getNetworkInfo().getTypeName() + + if (DBG) { + log("Policy requires " + otherNet.getNetworkInfo().getTypeName() + " teardown"); + } if (!teardown(otherNet)) { - Slog.e(TAG, "Network declined teardown request"); + loge("Network declined teardown request"); return; } - if (isFailover) { - otherNet.releaseWakeLock(); - } + } + } + synchronized (ConnectivityService.this) { + // have a new default network, release the transition wakelock in a second + // if it's held. The second pause is to allow apps to reconnect over the + // new network + if (mNetTransitionWakeLock.isHeld()) { + mHandler.sendMessageDelayed(mHandler.obtainMessage( + EVENT_CLEAR_NET_TRANSITION_WAKELOCK, + mNetTransitionWakeLockSerialNumber, 0), + 1000); } } mActiveDefaultNetwork = type; @@ -1256,36 +1338,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { //reportNetworkCondition(mActiveDefaultNetwork, 100); } thisNet.setTeardownRequested(false); - thisNet.updateNetworkSettings(); + updateNetworkSettings(thisNet); handleConnectivityChange(type); sendConnectedBroadcast(info); } - private void handleScanResultsAvailable(NetworkInfo info) { - int networkType = info.getType(); - if (networkType != ConnectivityManager.TYPE_WIFI) { - if (DBG) Slog.v(TAG, "Got ScanResultsAvailable for " + - info.getTypeName() + " network. Don't know how to handle."); - } - - mNetTrackers[networkType].interpretScanResultsAvailable(); - } - - private void handleNotificationChange(boolean visible, int id, - Notification notification) { - NotificationManager notificationManager = (NotificationManager) mContext - .getSystemService(Context.NOTIFICATION_SERVICE); - - if (visible) { - notificationManager.notify(id, notification); - } else { - notificationManager.cancel(id); - } - } - /** - * After a change in the connectivity state of any network, We're mainly - * concerned with making sure that the list of DNS servers is setupup + * After a change in the connectivity state of a network. We're mainly + * concerned with making sure that the list of DNS servers is set up * according to which networks are connected, and ensuring that the * right routing table entries exist. */ @@ -1298,19 +1358,159 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (mNetTrackers[netType].getNetworkInfo().isConnected()) { if (mNetAttributes[netType].isDefault()) { - mNetTrackers[netType].addDefaultRoute(); + handleApplyDefaultProxy(netType); + addDefaultRoute(mNetTrackers[netType]); } else { - mNetTrackers[netType].addPrivateDnsRoutes(); + addPrivateDnsRoutes(mNetTrackers[netType]); } } else { if (mNetAttributes[netType].isDefault()) { - mNetTrackers[netType].removeDefaultRoute(); + removeDefaultRoute(mNetTrackers[netType]); } else { - mNetTrackers[netType].removePrivateDnsRoutes(); + removePrivateDnsRoutes(mNetTrackers[netType]); + } + } + } + + private void addPrivateDnsRoutes(NetworkStateTracker nt) { + boolean privateDnsRouteSet = nt.isPrivateDnsRouteSet(); + LinkProperties p = nt.getLinkProperties(); + if (p == null) return; + String interfaceName = p.getInterfaceName(); + + if (DBG) { + log("addPrivateDnsRoutes for " + nt + + "(" + interfaceName + ") - mPrivateDnsRouteSet = " + privateDnsRouteSet); + } + if (interfaceName != null && !privateDnsRouteSet) { + Collection<InetAddress> dnsList = p.getDnses(); + for (InetAddress dns : dnsList) { + if (DBG) log(" adding " + dns); + NetworkUtils.addHostRoute(interfaceName, dns, null); + } + nt.privateDnsRouteSet(true); + } + } + + private void removePrivateDnsRoutes(NetworkStateTracker nt) { + // TODO - we should do this explicitly but the NetUtils api doesnt + // support this yet - must remove all. No worse than before + LinkProperties p = nt.getLinkProperties(); + if (p == null) return; + String interfaceName = p.getInterfaceName(); + boolean privateDnsRouteSet = nt.isPrivateDnsRouteSet(); + if (interfaceName != null && privateDnsRouteSet) { + if (DBG) { + log("removePrivateDnsRoutes for " + nt.getNetworkInfo().getTypeName() + + " (" + interfaceName + ")"); } + NetworkUtils.removeHostRoutes(interfaceName); + nt.privateDnsRouteSet(false); } } + + private void addDefaultRoute(NetworkStateTracker nt) { + LinkProperties p = nt.getLinkProperties(); + if (p == null) return; + String interfaceName = p.getInterfaceName(); + InetAddress defaultGatewayAddr = p.getGateway(); + + if ((interfaceName != null) && (defaultGatewayAddr != null )) { + if (!NetworkUtils.addDefaultRoute(interfaceName, defaultGatewayAddr) && DBG) { + NetworkInfo networkInfo = nt.getNetworkInfo(); + log("addDefaultRoute for " + networkInfo.getTypeName() + + " (" + interfaceName + "), GatewayAddr=" + defaultGatewayAddr); + } + } + } + + + public void removeDefaultRoute(NetworkStateTracker nt) { + LinkProperties p = nt.getLinkProperties(); + if (p == null) return; + String interfaceName = p.getInterfaceName(); + + if (interfaceName != null) { + if ((NetworkUtils.removeDefaultRoute(interfaceName) >= 0) && DBG) { + NetworkInfo networkInfo = nt.getNetworkInfo(); + log("removeDefaultRoute for " + networkInfo.getTypeName() + " (" + + interfaceName + ")"); + } + } + } + + /** + * Reads the network specific TCP buffer sizes from SystemProperties + * net.tcp.buffersize.[default|wifi|umts|edge|gprs] and set them for system + * wide use + */ + public void updateNetworkSettings(NetworkStateTracker nt) { + String key = nt.getTcpBufferSizesPropName(); + String bufferSizes = SystemProperties.get(key); + + if (bufferSizes.length() == 0) { + loge(key + " not found in system properties. Using defaults"); + + // Setting to default values so we won't be stuck to previous values + key = "net.tcp.buffersize.default"; + bufferSizes = SystemProperties.get(key); + } + + // Set values in kernel + if (bufferSizes.length() != 0) { + if (DBG) { + log("Setting TCP values: [" + bufferSizes + + "] which comes from [" + key + "]"); + } + setBufferSize(bufferSizes); + } + } + + /** + * Writes TCP buffer sizes to /sys/kernel/ipv4/tcp_[r/w]mem_[min/def/max] + * which maps to /proc/sys/net/ipv4/tcp_rmem and tcpwmem + * + * @param bufferSizes in the format of "readMin, readInitial, readMax, + * writeMin, writeInitial, writeMax" + */ + private void setBufferSize(String bufferSizes) { + try { + String[] values = bufferSizes.split(","); + + if (values.length == 6) { + final String prefix = "/sys/kernel/ipv4/tcp_"; + stringToFile(prefix + "rmem_min", values[0]); + stringToFile(prefix + "rmem_def", values[1]); + stringToFile(prefix + "rmem_max", values[2]); + stringToFile(prefix + "wmem_min", values[3]); + stringToFile(prefix + "wmem_def", values[4]); + stringToFile(prefix + "wmem_max", values[5]); + } else { + loge("Invalid buffersize string: " + bufferSizes); + } + } catch (IOException e) { + loge("Can't set tcp buffer sizes:" + e); + } + } + + /** + * Writes string to file. Basically same as "echo -n $string > $filename" + * + * @param filename + * @param string + * @throws IOException + */ + private void stringToFile(String filename, String string) throws IOException { + FileWriter out = new FileWriter(filename); + try { + out.write(string); + } finally { + out.close(); + } + } + + /** * Adjust the per-process dns entries (net.dns<x>.<pid>) based * on the highest priority active net which this process requested. @@ -1318,7 +1518,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private void reassessPidDns(int myPid, boolean doBump) { - if (DBG) Slog.d(TAG, "reassessPidDns for pid " + myPid); + if (DBG) log("reassessPidDns for pid " + myPid); for(int i : mPriorityList) { if (mNetAttributes[i].isDefault()) { continue; @@ -1326,12 +1526,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { NetworkStateTracker nt = mNetTrackers[i]; if (nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) { + LinkProperties p = nt.getLinkProperties(); + if (p == null) continue; List pids = mNetRequestersPids[i]; for (int j=0; j<pids.size(); j++) { Integer pid = (Integer)pids.get(j); if (pid.intValue() == myPid) { - String[] dnsList = nt.getNameServers(); - writePidDns(dnsList, myPid); + Collection<InetAddress> dnses = p.getDnses(); + writePidDns(dnses, myPid); if (doBump) { bumpDns(); } @@ -1353,13 +1555,18 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - private void writePidDns(String[] dnsList, int pid) { + // return true if results in a change + private boolean writePidDns(Collection <InetAddress> dnses, int pid) { int j = 1; - for (String dns : dnsList) { - if (dns != null && !TextUtils.equals(dns, "0.0.0.0")) { - SystemProperties.set("net.dns" + j++ + "." + pid, dns); + boolean changed = false; + for (InetAddress dns : dnses) { + String dnsString = dns.getHostAddress(); + if (changed || !dnsString.equals(SystemProperties.get("net.dns" + j + "." + pid))) { + changed = true; + SystemProperties.set("net.dns" + j++ + "." + pid, dns.getHostAddress()); } } + return changed; } private void bumpDns() { @@ -1375,27 +1582,59 @@ public class ConnectivityService extends IConnectivityManager.Stub { } catch (NumberFormatException e) {} } SystemProperties.set("net.dnschange", "" + (n+1)); + /* + * Tell the VMs to toss their DNS caches + */ + Intent intent = new Intent(Intent.ACTION_CLEAR_DNS_CACHE); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + /* + * Connectivity events can happen before boot has completed ... + */ + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mContext.sendBroadcast(intent); } private void handleDnsConfigurationChange(int netType) { // add default net's dns entries NetworkStateTracker nt = mNetTrackers[netType]; if (nt != null && nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) { - String[] dnsList = nt.getNameServers(); + LinkProperties p = nt.getLinkProperties(); + if (p == null) return; + Collection<InetAddress> dnses = p.getDnses(); + boolean changed = false; if (mNetAttributes[netType].isDefault()) { int j = 1; - for (String dns : dnsList) { - if (dns != null && !TextUtils.equals(dns, "0.0.0.0")) { + if (dnses.size() == 0 && mDefaultDns != null) { + String dnsString = mDefaultDns.getHostAddress(); + if (!dnsString.equals(SystemProperties.get("net.dns1"))) { + if (DBG) { + log("no dns provided - using " + dnsString); + } + changed = true; + SystemProperties.set("net.dns1", dnsString); + } + j++; + } else { + for (InetAddress dns : dnses) { + String dnsString = dns.getHostAddress(); + if (!changed && dnsString.equals(SystemProperties.get("net.dns" + j))) { + j++; + continue; + } if (DBG) { - Slog.d(TAG, "adding dns " + dns + " for " + + log("adding dns " + dns + " for " + nt.getNetworkInfo().getTypeName()); } - SystemProperties.set("net.dns" + j++, dns); + changed = true; + SystemProperties.set("net.dns" + j++, dnsString); } } for (int k=j ; k<mNumDnsEntries; k++) { - if (DBG) Slog.d(TAG, "erasing net.dns" + k); - SystemProperties.set("net.dns" + k, ""); + if (changed || !TextUtils.isEmpty(SystemProperties.get("net.dns" + k))) { + if (DBG) log("erasing net.dns" + k); + changed = true; + SystemProperties.set("net.dns" + k, ""); + } } mNumDnsEntries = j; } else { @@ -1403,11 +1642,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { List pids = mNetRequestersPids[netType]; for (int y=0; y< pids.size(); y++) { Integer pid = (Integer)pids.get(y); - writePidDns(dnsList, pid.intValue()); + changed = writePidDns(dnses, pid.intValue()); } } + if (changed) bumpDns(); } - bumpDns(); } private int getRestoreDefaultNetworkDelay() { @@ -1462,6 +1701,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { } pw.println(); + synchronized (this) { + pw.println("NetworkTranstionWakeLock is currently " + + (mNetTransitionWakeLock.isHeld() ? "" : "not ") + "held."); + pw.println("It was last requested for "+mNetTransitionWakeLockCausedBy); + } + pw.println(); + mTethering.dump(fd, pw, args); if (mInetLog != null) { @@ -1475,6 +1721,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { // must be stateless - things change under us. private class MyHandler extends Handler { + public MyHandler(Looper looper) { + super(looper); + } + @Override public void handleMessage(Message msg) { NetworkInfo info; @@ -1493,7 +1743,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (DBG) { // TODO - remove this after we validate the dropping doesn't break // anything - Slog.d(TAG, "Dropping ConnectivityChange for " + + log("Dropping ConnectivityChange for " + info.getTypeName() + ": " + state + "/" + info.getDetailedState()); } @@ -1501,7 +1751,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } mNetAttributes[type].mLastState = state; - if (DBG) Slog.d(TAG, "ConnectivityChange for " + + if (DBG) log("ConnectivityChange for " + info.getTypeName() + ": " + state + "/" + info.getDetailedState()); @@ -1536,29 +1786,23 @@ public class ConnectivityService extends IConnectivityManager.Stub { handleConnect(info); } break; - - case NetworkStateTracker.EVENT_SCAN_RESULTS_AVAILABLE: - info = (NetworkInfo) msg.obj; - handleScanResultsAvailable(info); - break; - - case NetworkStateTracker.EVENT_NOTIFICATION_CHANGED: - handleNotificationChange(msg.arg1 == 1, msg.arg2, - (Notification) msg.obj); - break; - case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED: info = (NetworkInfo) msg.obj; type = info.getType(); - handleDnsConfigurationChange(type); + handleConnectivityChange(type); break; - - case NetworkStateTracker.EVENT_ROAMING_CHANGED: - // fill me in - break; - - case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED: - // fill me in + case EVENT_CLEAR_NET_TRANSITION_WAKELOCK: + String causedBy = null; + synchronized (ConnectivityService.this) { + if (msg.arg1 == mNetTransitionWakeLockSerialNumber && + mNetTransitionWakeLock.isHeld()) { + mNetTransitionWakeLock.release(); + causedBy = mNetTransitionWakeLockCausedBy; + } + } + if (causedBy != null) { + log("NetTransition Wakelock for " + causedBy + " released by timeout"); + } break; case EVENT_RESTORE_DEFAULT_NETWORK: FeatureUser u = (FeatureUser)msg.obj; @@ -1596,6 +1840,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { handleSetMobileData(enabled); break; } + case EVENT_APPLY_GLOBAL_HTTP_PROXY: + { + handleDeprecatedGlobalHttpProxy(); + } } } } @@ -1652,6 +1900,15 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } + public String[] getTetherableBluetoothRegexs() { + enforceTetherAccessPermission(); + if (isTetheringSupported()) { + return mTethering.getTetherableBluetoothRegexs(); + } else { + return new String[0]; + } + } + // TODO - move iface listing, queries, etc to new module // javadoc from interface public String[] getTetherableIfaces() { @@ -1680,9 +1937,28 @@ public class ConnectivityService extends IConnectivityManager.Stub { return tetherEnabledInSettings && mTetheringConfigValid; } + // An API NetworkStateTrackers can call when they lose their network. + // This will automatically be cleared after X seconds or a network becomes CONNECTED, + // whichever happens first. The timer is started by the first caller and not + // restarted by subsequent callers. + public void requestNetworkTransitionWakelock(String forWhom) { + enforceConnectivityInternalPermission(); + synchronized (this) { + if (mNetTransitionWakeLock.isHeld()) return; + mNetTransitionWakeLockSerialNumber++; + mNetTransitionWakeLock.acquire(); + mNetTransitionWakeLockCausedBy = forWhom; + } + mHandler.sendMessageDelayed(mHandler.obtainMessage( + EVENT_CLEAR_NET_TRANSITION_WAKELOCK, + mNetTransitionWakeLockSerialNumber, 0), + mNetTransitionWakeLockTimeout); + return; + } + // 100 percent is full good, 0 is full bad. public void reportInetCondition(int networkType, int percentage) { - if (DBG) Slog.d(TAG, "reportNetworkCondition(" + networkType + ", " + percentage + ")"); + if (DBG) log("reportNetworkCondition(" + networkType + ", " + percentage + ")"); mContext.enforceCallingOrSelfPermission( android.Manifest.permission.STATUS_BAR, "ConnectivityService"); @@ -1704,22 +1980,22 @@ public class ConnectivityService extends IConnectivityManager.Stub { private void handleInetConditionChange(int netType, int condition) { if (DBG) { - Slog.d(TAG, "Inet connectivity change, net=" + + log("Inet connectivity change, net=" + netType + ", condition=" + condition + ",mActiveDefaultNetwork=" + mActiveDefaultNetwork); } if (mActiveDefaultNetwork == -1) { - if (DBG) Slog.d(TAG, "no active default network - aborting"); + if (DBG) log("no active default network - aborting"); return; } if (mActiveDefaultNetwork != netType) { - if (DBG) Slog.d(TAG, "given net not default - aborting"); + if (DBG) log("given net not default - aborting"); return; } mDefaultInetCondition = condition; int delay; if (mInetConditionChangeInFlight == false) { - if (DBG) Slog.d(TAG, "starting a change hold"); + if (DBG) log("starting a change hold"); // setup a new hold to debounce this if (mDefaultInetCondition > 50) { delay = Settings.Secure.getInt(mContext.getContentResolver(), @@ -1734,37 +2010,169 @@ public class ConnectivityService extends IConnectivityManager.Stub { } else { // we've set the new condition, when this hold ends that will get // picked up - if (DBG) Slog.d(TAG, "currently in hold - not setting new end evt"); + if (DBG) log("currently in hold - not setting new end evt"); } } private void handleInetConditionHoldEnd(int netType, int sequence) { if (DBG) { - Slog.d(TAG, "Inet hold end, net=" + netType + + log("Inet hold end, net=" + netType + ", condition =" + mDefaultInetCondition + ", published condition =" + mDefaultInetConditionPublished); } mInetConditionChangeInFlight = false; if (mActiveDefaultNetwork == -1) { - if (DBG) Slog.d(TAG, "no active default network - aborting"); + if (DBG) log("no active default network - aborting"); return; } if (mDefaultConnectionSequence != sequence) { - if (DBG) Slog.d(TAG, "event hold for obsolete network - aborting"); + if (DBG) log("event hold for obsolete network - aborting"); return; } if (mDefaultInetConditionPublished == mDefaultInetCondition) { - if (DBG) Slog.d(TAG, "no change in condition - aborting"); + if (DBG) log("no change in condition - aborting"); return; } NetworkInfo networkInfo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo(); if (networkInfo.isConnected() == false) { - if (DBG) Slog.d(TAG, "default network not connected - aborting"); + if (DBG) log("default network not connected - aborting"); return; } mDefaultInetConditionPublished = mDefaultInetCondition; sendInetConditionBroadcast(networkInfo); return; } + + public synchronized ProxyProperties getProxy() { + if (mGlobalProxy != null) return mGlobalProxy; + if (mDefaultProxy != null) return mDefaultProxy; + return null; + } + + public void setGlobalProxy(ProxyProperties proxyProperties) { + enforceChangePermission(); + synchronized (mGlobalProxyLock) { + if (proxyProperties == mGlobalProxy) return; + if (proxyProperties != null && proxyProperties.equals(mGlobalProxy)) return; + if (mGlobalProxy != null && mGlobalProxy.equals(proxyProperties)) return; + + String host = ""; + int port = 0; + String exclList = ""; + if (proxyProperties != null && !TextUtils.isEmpty(proxyProperties.getHost())) { + mGlobalProxy = new ProxyProperties(proxyProperties); + host = mGlobalProxy.getHost(); + port = mGlobalProxy.getPort(); + exclList = mGlobalProxy.getExclusionList(); + } else { + mGlobalProxy = null; + } + ContentResolver res = mContext.getContentResolver(); + Settings.Secure.putString(res, Settings.Secure.GLOBAL_HTTP_PROXY_HOST, host); + Settings.Secure.putInt(res, Settings.Secure.GLOBAL_HTTP_PROXY_PORT, port); + Settings.Secure.putString(res, Settings.Secure.GLOBAL_HTTP_PROXY_EXCLUSION_LIST, + exclList); + } + + if (mGlobalProxy == null) { + proxyProperties = mDefaultProxy; + } + sendProxyBroadcast(proxyProperties); + } + + private void loadGlobalProxy() { + ContentResolver res = mContext.getContentResolver(); + String host = Settings.Secure.getString(res, Settings.Secure.GLOBAL_HTTP_PROXY_HOST); + int port = Settings.Secure.getInt(res, Settings.Secure.GLOBAL_HTTP_PROXY_PORT, 0); + String exclList = Settings.Secure.getString(res, + Settings.Secure.GLOBAL_HTTP_PROXY_EXCLUSION_LIST); + if (!TextUtils.isEmpty(host)) { + ProxyProperties proxyProperties = new ProxyProperties(host, port, exclList); + synchronized (mGlobalProxyLock) { + mGlobalProxy = proxyProperties; + } + } + } + + public ProxyProperties getGlobalProxy() { + synchronized (mGlobalProxyLock) { + return mGlobalProxy; + } + } + + private void handleApplyDefaultProxy(int type) { + // check if new default - push it out to all VM if so + ProxyProperties proxy = mNetTrackers[type].getLinkProperties().getHttpProxy(); + synchronized (this) { + if (mDefaultProxy != null && mDefaultProxy.equals(proxy)) return; + if (mDefaultProxy == proxy) return; + if (!TextUtils.isEmpty(proxy.getHost())) { + mDefaultProxy = proxy; + } else { + mDefaultProxy = null; + } + } + if (DBG) log("changing default proxy to " + proxy); + if ((proxy == null && mGlobalProxy == null) || proxy.equals(mGlobalProxy)) return; + if (mGlobalProxy != null) return; + sendProxyBroadcast(proxy); + } + + private void handleDeprecatedGlobalHttpProxy() { + String proxy = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.HTTP_PROXY); + if (!TextUtils.isEmpty(proxy)) { + String data[] = proxy.split(":"); + String proxyHost = data[0]; + int proxyPort = 8080; + if (data.length > 1) { + try { + proxyPort = Integer.parseInt(data[1]); + } catch (NumberFormatException e) { + return; + } + } + ProxyProperties p = new ProxyProperties(data[0], proxyPort, ""); + setGlobalProxy(p); + } + } + + private void sendProxyBroadcast(ProxyProperties proxy) { + if (proxy == null) proxy = new ProxyProperties("", 0, ""); + log("sending Proxy Broadcast for " + proxy); + Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra(Proxy.EXTRA_PROXY_INFO, proxy); + mContext.sendStickyBroadcast(intent); + } + + private static class SettingsObserver extends ContentObserver { + private int mWhat; + private Handler mHandler; + SettingsObserver(Handler handler, int what) { + super(handler); + mHandler = handler; + mWhat = what; + } + + void observe(Context context) { + ContentResolver resolver = context.getContentResolver(); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.HTTP_PROXY), false, this); + } + + @Override + public void onChange(boolean selfChange) { + mHandler.obtainMessage(mWhat).sendToTarget(); + } + } + + private void log(String s) { + Slog.d(TAG, s); + } + + private void loge(String s) { + Slog.e(TAG, s); + } } diff --git a/services/java/com/android/server/CountryDetectorService.java b/services/java/com/android/server/CountryDetectorService.java new file mode 100644 index 0000000..3081ebe --- /dev/null +++ b/services/java/com/android/server/CountryDetectorService.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2010 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.server; + +import java.util.HashMap; + +import com.android.server.location.ComprehensiveCountryDetector; + +import android.content.Context; +import android.location.Country; +import android.location.CountryListener; +import android.location.ICountryDetector; +import android.location.ICountryListener; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Process; +import android.os.RemoteException; +import android.util.Slog; + +/** + * This class detects the country that the user is in through + * {@link ComprehensiveCountryDetector}. + * + * @hide + */ +public class CountryDetectorService extends ICountryDetector.Stub implements Runnable { + + /** + * The class represents the remote listener, it will also removes itself + * from listener list when the remote process was died. + */ + private final class Receiver implements IBinder.DeathRecipient { + private final ICountryListener mListener; + private final IBinder mKey; + + public Receiver(ICountryListener listener) { + mListener = listener; + mKey = listener.asBinder(); + } + + public void binderDied() { + removeListener(mKey); + } + + @Override + public boolean equals(Object otherObj) { + if (otherObj instanceof Receiver) { + return mKey.equals(((Receiver) otherObj).mKey); + } + return false; + } + + @Override + public int hashCode() { + return mKey.hashCode(); + } + + public ICountryListener getListener() { + return mListener; + } + } + + private final static String TAG = "CountryDetectorService"; + + private final HashMap<IBinder, Receiver> mReceivers; + private final Context mContext; + private ComprehensiveCountryDetector mCountryDetector; + private boolean mSystemReady; + private Handler mHandler; + private CountryListener mLocationBasedDetectorListener; + + public CountryDetectorService(Context context) { + super(); + mReceivers = new HashMap<IBinder, Receiver>(); + mContext = context; + } + + @Override + public Country detectCountry() throws RemoteException { + if (!mSystemReady) { + throw new RemoteException(); + } + return mCountryDetector.detectCountry(); + } + + /** + * Add the ICountryListener into the listener list. + */ + @Override + public void addCountryListener(ICountryListener listener) throws RemoteException { + if (!mSystemReady) { + throw new RemoteException(); + } + addListener(listener); + } + + /** + * Remove the ICountryListener from the listener list. + */ + @Override + public void removeCountryListener(ICountryListener listener) throws RemoteException { + if (!mSystemReady) { + throw new RemoteException(); + } + removeListener(listener.asBinder()); + } + + private void addListener(ICountryListener listener) { + synchronized (mReceivers) { + Receiver r = new Receiver(listener); + try { + listener.asBinder().linkToDeath(r, 0); + mReceivers.put(listener.asBinder(), r); + if (mReceivers.size() == 1) { + Slog.d(TAG, "The first listener is added"); + setCountryListener(mLocationBasedDetectorListener); + } + } catch (RemoteException e) { + Slog.e(TAG, "linkToDeath failed:", e); + } + } + } + + private void removeListener(IBinder key) { + synchronized (mReceivers) { + mReceivers.remove(key); + if (mReceivers.isEmpty()) { + setCountryListener(null); + Slog.d(TAG, "No listener is left"); + } + } + } + + + protected void notifyReceivers(Country country) { + synchronized(mReceivers) { + for (Receiver receiver : mReceivers.values()) { + try { + receiver.getListener().onCountryDetected(country); + } catch (RemoteException e) { + // TODO: Shall we remove the receiver? + Slog.e(TAG, "notifyReceivers failed:", e); + } + } + } + } + + void systemReady() { + // Shall we wait for the initialization finish. + Thread thread = new Thread(this, "CountryDetectorService"); + thread.start(); + } + + private void initialize() { + mCountryDetector = new ComprehensiveCountryDetector(mContext); + mLocationBasedDetectorListener = new CountryListener() { + public void onCountryDetected(final Country country) { + mHandler.post(new Runnable() { + public void run() { + notifyReceivers(country); + } + }); + } + }; + } + + public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + Looper.prepare(); + mHandler = new Handler(); + initialize(); + mSystemReady = true; + Looper.loop(); + } + + protected void setCountryListener(final CountryListener listener) { + mHandler.post(new Runnable() { + @Override + public void run() { + mCountryDetector.setCountryListener(listener); + } + }); + } + + // For testing + boolean isSystemReady() { + return mSystemReady; + } +} diff --git a/services/java/com/android/server/DemoDataSet.java b/services/java/com/android/server/DemoDataSet.java deleted file mode 100644 index 277985f..0000000 --- a/services/java/com/android/server/DemoDataSet.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2007 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.server; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.res.AssetManager; -import android.net.Uri; -import android.os.Environment; -import android.provider.Contacts; -import android.provider.Settings; -import android.provider.MediaStore.Images; -import android.util.Config; -import android.util.Slog; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.io.OutputStream; - -public class DemoDataSet -{ - private final static String LOG_TAG = "DemoDataSet"; - - private ContentResolver mContentResolver; - - public final void add(Context context) - { - mContentResolver = context.getContentResolver(); - - // Remove all the old data - mContentResolver.delete(Contacts.People.CONTENT_URI, null, null); - - // Add the new data - addDefaultData(); - - // Add images from /android/images - addDefaultImages(); - } - - private final void addDefaultImages() - { - File rootDirectory = Environment.getRootDirectory(); - String [] files - = new File(rootDirectory, "images").list(); - int count = files.length; - - if (count == 0) { - Slog.i(LOG_TAG, "addDefaultImages: no images found!"); - return; - } - - for (int i = 0; i < count; i++) - { - String name = files[i]; - String path = rootDirectory + "/" + name; - - try { - Images.Media.insertImage(mContentResolver, path, name, null); - } catch (FileNotFoundException e) { - Slog.e(LOG_TAG, "Failed to import image " + path, e); - } - } - } - - private final void addDefaultData() - { - Slog.i(LOG_TAG, "Adding default data..."); - -// addImage("Violet", "images/violet.png"); -// addImage("Corky", "images/corky.png"); - - // PENDING: should this be done here?!?! - Intent intent = new Intent( - Intent.ACTION_CALL, Uri.fromParts("voicemail", "", null)); - addShortcut("1", intent); - } - - private final Uri addImage(String name, Uri file) - { - ContentValues imagev = new ContentValues(); - imagev.put("name", name); - - Uri url = null; - - AssetManager ass = AssetManager.getSystem(); - InputStream in = null; - OutputStream out = null; - - try - { - in = ass.open(file.toString()); - - url = mContentResolver.insert(Images.Media.INTERNAL_CONTENT_URI, imagev); - out = mContentResolver.openOutputStream(url); - - final int size = 8 * 1024; - byte[] buf = new byte[size]; - - int count = 0; - do - { - count = in.read(buf, 0, size); - if (count > 0) { - out.write(buf, 0, count); - } - } while (count > 0); - } - catch (Exception e) - { - Slog.e(LOG_TAG, "Failed to insert image '" + file + "'", e); - url = null; - } - - return url; - } - - private final Uri addShortcut(String shortcut, Intent intent) - { - if (Config.LOGV) Slog.v(LOG_TAG, "addShortcut: shortcut=" + shortcut + ", intent=" + intent); - return Settings.Bookmarks.add(mContentResolver, intent, null, null, - shortcut != null ? shortcut.charAt(0) : 0, 0); - } -} diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java index 1538003..b2d534b 100644 --- a/services/java/com/android/server/DevicePolicyManagerService.java +++ b/services/java/com/android/server/DevicePolicyManagerService.java @@ -28,18 +28,23 @@ import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import android.app.Activity; +import android.app.AlarmManager; +import android.app.PendingIntent; import android.app.admin.DeviceAdminInfo; import android.app.admin.DeviceAdminReceiver; import android.app.admin.DevicePolicyManager; import android.app.admin.IDevicePolicyManager; import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; import android.os.IPowerManager; import android.os.PowerManager; @@ -48,9 +53,11 @@ import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; -import android.util.Slog; +import android.os.SystemProperties; +import android.provider.Settings; import android.util.PrintWriterPrinter; import android.util.Printer; +import android.util.Slog; import android.util.Xml; import android.view.WindowManagerPolicy; @@ -61,47 +68,97 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; +import java.text.DateFormat; import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.Set; /** * Implementation of the device policy APIs. */ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + private static final int REQUEST_EXPIRE_PASSWORD = 5571; + static final String TAG = "DevicePolicyManagerService"; - + + private static final long EXPIRATION_GRACE_PERIOD_MS = 5 * 86400 * 1000; // 5 days, in ms + + protected static final String ACTION_EXPIRED_PASSWORD_NOTIFICATION + = "com.android.server.ACTION_EXPIRED_PASSWORD_NOTIFICATION"; + + private static final long MS_PER_DAY = 86400 * 1000; + final Context mContext; final MyPackageMonitor mMonitor; final PowerManager.WakeLock mWakeLock; IPowerManager mIPowerManager; - + int mActivePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; int mActivePasswordLength = 0; + int mActivePasswordUpperCase = 0; + int mActivePasswordLowerCase = 0; + int mActivePasswordLetters = 0; + int mActivePasswordNumeric = 0; + int mActivePasswordSymbols = 0; + int mActivePasswordNonLetter = 0; int mFailedPasswordAttempts = 0; - + int mPasswordOwner = -1; - + Handler mHandler = new Handler(); + final HashMap<ComponentName, ActiveAdmin> mAdminMap = new HashMap<ComponentName, ActiveAdmin>(); final ArrayList<ActiveAdmin> mAdminList = new ArrayList<ActiveAdmin>(); - + + BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_BOOT_COMPLETED.equals(action) + || ACTION_EXPIRED_PASSWORD_NOTIFICATION.equals(action)) { + Slog.v(TAG, "Sending password expiration notifications for action " + action); + mHandler.post(new Runnable() { + public void run() { + handlePasswordExpirationNotification(); + } + }); + } + } + }; + static class ActiveAdmin { final DeviceAdminInfo info; - + int passwordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; int minimumPasswordLength = 0; + int passwordHistoryLength = 0; + int minimumPasswordUpperCase = 0; + int minimumPasswordLowerCase = 0; + int minimumPasswordLetters = 1; + int minimumPasswordNumeric = 1; + int minimumPasswordSymbols = 1; + int minimumPasswordNonLetter = 0; long maximumTimeToUnlock = 0; int maximumFailedPasswordsForWipe = 0; - + long passwordExpirationTimeout = 0L; + long passwordExpirationDate = 0L; + boolean encryptionRequested = false; + + // TODO: review implementation decisions with frameworks team + boolean specifiesGlobalProxy = false; + String globalProxySpec = null; + String globalProxyExclusionList = null; + ActiveAdmin(DeviceAdminInfo _info) { info = _info; } - + int getUid() { return info.getActivityInfo().applicationInfo.uid; } - + void writeToXml(XmlSerializer out) throws IllegalArgumentException, IllegalStateException, IOException { out.startTag(null, "policies"); @@ -114,10 +171,45 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (minimumPasswordLength > 0) { out.startTag(null, "min-password-length"); out.attribute(null, "value", Integer.toString(minimumPasswordLength)); - out.endTag(null, "mn-password-length"); + out.endTag(null, "min-password-length"); + } + if(passwordHistoryLength > 0) { + out.startTag(null, "password-history-length"); + out.attribute(null, "value", Integer.toString(passwordHistoryLength)); + out.endTag(null, "password-history-length"); + } + if (minimumPasswordUpperCase > 0) { + out.startTag(null, "min-password-uppercase"); + out.attribute(null, "value", Integer.toString(minimumPasswordUpperCase)); + out.endTag(null, "min-password-uppercase"); + } + if (minimumPasswordLowerCase > 0) { + out.startTag(null, "min-password-lowercase"); + out.attribute(null, "value", Integer.toString(minimumPasswordLowerCase)); + out.endTag(null, "min-password-lowercase"); + } + if (minimumPasswordLetters > 0) { + out.startTag(null, "min-password-letters"); + out.attribute(null, "value", Integer.toString(minimumPasswordLetters)); + out.endTag(null, "min-password-letters"); + } + if (minimumPasswordNumeric > 0) { + out.startTag(null, "min-password-numeric"); + out.attribute(null, "value", Integer.toString(minimumPasswordNumeric)); + out.endTag(null, "min-password-numeric"); + } + if (minimumPasswordSymbols > 0) { + out.startTag(null, "min-password-symbols"); + out.attribute(null, "value", Integer.toString(minimumPasswordSymbols)); + out.endTag(null, "min-password-symbols"); + } + if (minimumPasswordNonLetter > 0) { + out.startTag(null, "min-password-nonletter"); + out.attribute(null, "value", Integer.toString(minimumPasswordNonLetter)); + out.endTag(null, "min-password-nonletter"); } } - if (maximumTimeToUnlock != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { + if (maximumTimeToUnlock != 0) { out.startTag(null, "max-time-to-unlock"); out.attribute(null, "value", Long.toString(maximumTimeToUnlock)); out.endTag(null, "max-time-to-unlock"); @@ -127,8 +219,38 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.attribute(null, "value", Integer.toString(maximumFailedPasswordsForWipe)); out.endTag(null, "max-failed-password-wipe"); } + if (specifiesGlobalProxy) { + out.startTag(null, "specifies-global-proxy"); + out.attribute(null, "value", Boolean.toString(specifiesGlobalProxy)); + out.endTag(null, "specifies_global_proxy"); + if (globalProxySpec != null) { + out.startTag(null, "global-proxy-spec"); + out.attribute(null, "value", globalProxySpec); + out.endTag(null, "global-proxy-spec"); + } + if (globalProxyExclusionList != null) { + out.startTag(null, "global-proxy-exclusion-list"); + out.attribute(null, "value", globalProxyExclusionList); + out.endTag(null, "global-proxy-exclusion-list"); + } + } + if (passwordExpirationTimeout != 0L) { + out.startTag(null, "password-expiration-timeout"); + out.attribute(null, "value", Long.toString(passwordExpirationTimeout)); + out.endTag(null, "password-expiration-timeout"); + } + if (passwordExpirationDate != 0L) { + out.startTag(null, "password-expiration-date"); + out.attribute(null, "value", Long.toString(passwordExpirationDate)); + out.endTag(null, "password-expiration-date"); + } + if (encryptionRequested) { + out.startTag(null, "encryption-requested"); + out.attribute(null, "value", Boolean.toString(encryptionRequested)); + out.endTag(null, "encryption-requested"); + } } - + void readFromXml(XmlPullParser parser) throws XmlPullParserException, IOException { int outerDepth = parser.getDepth(); @@ -147,19 +269,58 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else if ("min-password-length".equals(tag)) { minimumPasswordLength = Integer.parseInt( parser.getAttributeValue(null, "value")); + } else if ("password-history-length".equals(tag)) { + passwordHistoryLength = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } else if ("min-password-uppercase".equals(tag)) { + minimumPasswordUpperCase = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } else if ("min-password-lowercase".equals(tag)) { + minimumPasswordLowerCase = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } else if ("min-password-letters".equals(tag)) { + minimumPasswordLetters = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } else if ("min-password-numeric".equals(tag)) { + minimumPasswordNumeric = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } else if ("min-password-symbols".equals(tag)) { + minimumPasswordSymbols = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } else if ("min-password-nonletter".equals(tag)) { + minimumPasswordNonLetter = Integer.parseInt( + parser.getAttributeValue(null, "value")); } else if ("max-time-to-unlock".equals(tag)) { maximumTimeToUnlock = Long.parseLong( parser.getAttributeValue(null, "value")); } else if ("max-failed-password-wipe".equals(tag)) { maximumFailedPasswordsForWipe = Integer.parseInt( parser.getAttributeValue(null, "value")); + } else if ("specifies-global-proxy".equals(tag)) { + specifiesGlobalProxy = Boolean.parseBoolean( + parser.getAttributeValue(null, "value")); + } else if ("global-proxy-spec".equals(tag)) { + globalProxySpec = + parser.getAttributeValue(null, "value"); + } else if ("global-proxy-exclusion-list".equals(tag)) { + globalProxyExclusionList = + parser.getAttributeValue(null, "value"); + } else if ("password-expiration-timeout".equals(tag)) { + passwordExpirationTimeout = Long.parseLong( + parser.getAttributeValue(null, "value")); + } else if ("password-expiration-date".equals(tag)) { + passwordExpirationDate = Long.parseLong( + parser.getAttributeValue(null, "value")); + } else if ("encryption-requested".equals(tag)) { + encryptionRequested = Boolean.parseBoolean( + parser.getAttributeValue(null, "value")); } else { Slog.w(TAG, "Unknown admin tag: " + tag); } XmlUtils.skipCurrentTag(parser); } } - + void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("uid="); pw.println(getUid()); pw.print(prefix); pw.println("policies:"); @@ -170,23 +331,54 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } pw.print(prefix); pw.print("passwordQuality=0x"); - pw.print(Integer.toHexString(passwordQuality)); - pw.print(" minimumPasswordLength="); + pw.println(Integer.toHexString(passwordQuality)); + pw.print(prefix); pw.print("minimumPasswordLength="); pw.println(minimumPasswordLength); + pw.print(prefix); pw.print("passwordHistoryLength="); + pw.println(passwordHistoryLength); + pw.print(prefix); pw.print("minimumPasswordUpperCase="); + pw.println(minimumPasswordUpperCase); + pw.print(prefix); pw.print("minimumPasswordLowerCase="); + pw.println(minimumPasswordLowerCase); + pw.print(prefix); pw.print("minimumPasswordLetters="); + pw.println(minimumPasswordLetters); + pw.print(prefix); pw.print("minimumPasswordNumeric="); + pw.println(minimumPasswordNumeric); + pw.print(prefix); pw.print("minimumPasswordSymbols="); + pw.println(minimumPasswordSymbols); + pw.print(prefix); pw.print("minimumPasswordNonLetter="); + pw.println(minimumPasswordNonLetter); pw.print(prefix); pw.print("maximumTimeToUnlock="); pw.println(maximumTimeToUnlock); pw.print(prefix); pw.print("maximumFailedPasswordsForWipe="); pw.println(maximumFailedPasswordsForWipe); + pw.print(prefix); pw.print("specifiesGlobalProxy="); + pw.println(specifiesGlobalProxy); + pw.print(prefix); pw.print("passwordExpirationTimeout="); + pw.println(passwordExpirationTimeout); + pw.print(prefix); pw.print("passwordExpirationDate="); + pw.println(passwordExpirationDate); + if (globalProxySpec != null) { + pw.print(prefix); pw.print("globalProxySpec="); + pw.println(globalProxySpec); + } + if (globalProxyExclusionList != null) { + pw.print(prefix); pw.print("globalProxyEclusionList="); + pw.println(globalProxyExclusionList); + } + pw.print(prefix); pw.print("encryptionRequested="); + pw.println(encryptionRequested); } } - + class MyPackageMonitor extends PackageMonitor { + @Override public void onSomePackagesChanged() { synchronized (DevicePolicyManagerService.this) { boolean removed = false; for (int i=mAdminList.size()-1; i>=0; i--) { ActiveAdmin aa = mAdminList.get(i); - int change = isPackageDisappearing(aa.info.getPackageName()); + int change = isPackageDisappearing(aa.info.getPackageName()); if (change == PACKAGE_PERMANENT_CHANGE || change == PACKAGE_TEMPORARY_CHANGE) { Slog.w(TAG, "Admin unexpectedly uninstalled: " @@ -211,7 +403,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + /** * Instantiates the service. */ @@ -221,6 +413,50 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mMonitor.register(context, true); mWakeLock = ((PowerManager)context.getSystemService(Context.POWER_SERVICE)) .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "DPM"); + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_BOOT_COMPLETED); + filter.addAction(ACTION_EXPIRED_PASSWORD_NOTIFICATION); + context.registerReceiver(mReceiver, filter); + } + + /** + * Set an alarm for an upcoming event - expiration warning, expiration, or post-expiration + * reminders. Clears alarm if no expirations are configured. + */ + protected void setExpirationAlarmCheckLocked(Context context) { + final long expiration = getPasswordExpirationLocked(null); + final long now = System.currentTimeMillis(); + final long timeToExpire = expiration - now; + final long alarmTime; + if (expiration == 0) { + // No expirations are currently configured: Cancel alarm. + alarmTime = 0; + } else if (timeToExpire <= 0) { + // The password has already expired: Repeat every 24 hours. + alarmTime = now + MS_PER_DAY; + } else { + // Selecting the next alarm time: Roll forward to the next 24 hour multiple before + // the expiration time. + long alarmInterval = timeToExpire % MS_PER_DAY; + if (alarmInterval == 0) { + alarmInterval = MS_PER_DAY; + } + alarmTime = now + alarmInterval; + } + + long token = Binder.clearCallingIdentity(); + try { + AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + PendingIntent pi = PendingIntent.getBroadcast(context, REQUEST_EXPIRE_PASSWORD, + new Intent(ACTION_EXPIRED_PASSWORD_NOTIFICATION), + PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT); + am.cancel(pi); + if (alarmTime != 0) { + am.set(AlarmManager.RTC, alarmTime, pi); + } + } finally { + Binder.restoreCallingIdentity(token); + } } private IPowerManager getIPowerManager() { @@ -230,7 +466,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } return mIPowerManager; } - + ActiveAdmin getActiveAdminUncheckedLocked(ComponentName who) { ActiveAdmin admin = mAdminMap.get(who); if (admin != null @@ -240,7 +476,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } return null; } - + ActiveAdmin getActiveAdminForCallerLocked(ComponentName who, int reqPolicy) throws SecurityException { final int callingUid = Binder.getCallingUid(); @@ -271,13 +507,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + Binder.getCallingUid() + " for policy #" + reqPolicy); } } - + void sendAdminCommandLocked(ActiveAdmin admin, String action) { Intent intent = new Intent(action); intent.setComponent(admin.info.getComponent()); + if (action.equals(DeviceAdminReceiver.ACTION_PASSWORD_EXPIRING)) { + intent.putExtra("expiration", admin.passwordExpirationDate); + } mContext.sendBroadcast(intent); } - + void sendAdminCommandLocked(String action, int reqPolicy) { final int N = mAdminList.size(); if (N > 0) { @@ -289,19 +528,24 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + void removeActiveAdminLocked(ComponentName adminReceiver) { ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver); if (admin != null) { + boolean doProxyCleanup = + admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_SETS_GLOBAL_PROXY); sendAdminCommandLocked(admin, DeviceAdminReceiver.ACTION_DEVICE_ADMIN_DISABLED); // XXX need to wait for it to complete. mAdminList.remove(admin); mAdminMap.remove(adminReceiver); validatePasswordOwnerLocked(); + if (doProxyCleanup) { + resetGlobalProxy(); + } } } - + public DeviceAdminInfo findAdmin(ComponentName adminName) { Intent resolveIntent = new Intent(); resolveIntent.setComponent(adminName); @@ -310,7 +554,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (infos == null || infos.size() <= 0) { throw new IllegalArgumentException("Unknown admin: " + adminName); } - + try { return new DeviceAdminInfo(mContext, infos.get(0)); } catch (XmlPullParserException e) { @@ -321,7 +565,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return null; } } - + private static JournaledFile makeJournaledFile() { final String base = "/data/system/device_policies.xml"; return new JournaledFile(new File(base), new File(base + ".tmp")); @@ -337,7 +581,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.startDocument(null, true); out.startTag(null, "policies"); - + final int N = mAdminList.size(); for (int i=0; i<N; i++) { ActiveAdmin ap = mAdminList.get(i); @@ -348,26 +592,36 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.endTag(null, "admin"); } } - + if (mPasswordOwner >= 0) { out.startTag(null, "password-owner"); out.attribute(null, "value", Integer.toString(mPasswordOwner)); out.endTag(null, "password-owner"); } - + if (mFailedPasswordAttempts != 0) { out.startTag(null, "failed-password-attempts"); out.attribute(null, "value", Integer.toString(mFailedPasswordAttempts)); out.endTag(null, "failed-password-attempts"); } - - if (mActivePasswordQuality != 0 || mActivePasswordLength != 0) { + + if (mActivePasswordQuality != 0 || mActivePasswordLength != 0 + || mActivePasswordUpperCase != 0 || mActivePasswordLowerCase != 0 + || mActivePasswordLetters != 0 || mActivePasswordNumeric != 0 + || mActivePasswordSymbols != 0 || mActivePasswordNonLetter != 0) { out.startTag(null, "active-password"); out.attribute(null, "quality", Integer.toString(mActivePasswordQuality)); out.attribute(null, "length", Integer.toString(mActivePasswordLength)); + out.attribute(null, "uppercase", Integer.toString(mActivePasswordUpperCase)); + out.attribute(null, "lowercase", Integer.toString(mActivePasswordLowerCase)); + out.attribute(null, "letters", Integer.toString(mActivePasswordLetters)); + out.attribute(null, "numeric", Integer + .toString(mActivePasswordNumeric)); + out.attribute(null, "symbols", Integer.toString(mActivePasswordSymbols)); + out.attribute(null, "nonletter", Integer.toString(mActivePasswordNonLetter)); out.endTag(null, "active-password"); } - + out.endTag(null, "policies"); out.endDocument(); @@ -445,6 +699,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { parser.getAttributeValue(null, "quality")); mActivePasswordLength = Integer.parseInt( parser.getAttributeValue(null, "length")); + mActivePasswordUpperCase = Integer.parseInt( + parser.getAttributeValue(null, "uppercase")); + mActivePasswordLowerCase = Integer.parseInt( + parser.getAttributeValue(null, "lowercase")); + mActivePasswordLetters = Integer.parseInt( + parser.getAttributeValue(null, "letters")); + mActivePasswordNumeric = Integer.parseInt( + parser.getAttributeValue(null, "numeric")); + mActivePasswordSymbols = Integer.parseInt( + parser.getAttributeValue(null, "symbols")); + mActivePasswordNonLetter = Integer.parseInt( + parser.getAttributeValue(null, "nonletter")); XmlUtils.skipCurrentTag(parser); } else { Slog.w(TAG, "Unknown tag: " + tag); @@ -484,10 +750,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + Integer.toHexString(utils.getActivePasswordQuality())); mActivePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; mActivePasswordLength = 0; + mActivePasswordUpperCase = 0; + mActivePasswordLowerCase = 0; + mActivePasswordLetters = 0; + mActivePasswordNumeric = 0; + mActivePasswordSymbols = 0; + mActivePasswordNonLetter = 0; } - + validatePasswordOwnerLocked(); - + long timeMs = getMaximumTimeToLock(null); if (timeMs <= 0) { timeMs = Integer.MAX_VALUE; @@ -506,12 +778,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: + case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX: return; } throw new IllegalArgumentException("Invalid quality constant: 0x" + Integer.toHexString(quality)); } - + void validatePasswordOwnerLocked() { if (mPasswordOwner >= 0) { boolean haveOwner = false; @@ -528,17 +801,41 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + public void systemReady() { synchronized (this) { loadSettingsLocked(); } } - - public void setActiveAdmin(ComponentName adminReceiver) { + + private void handlePasswordExpirationNotification() { + synchronized (this) { + final long now = System.currentTimeMillis(); + final int N = mAdminList.size(); + if (N <= 0) { + return; + } + for (int i=0; i < N; i++) { + ActiveAdmin admin = mAdminList.get(i); + if (admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD) + && admin.passwordExpirationTimeout > 0L + && admin.passwordExpirationDate > 0L + && now >= admin.passwordExpirationDate - EXPIRATION_GRACE_PERIOD_MS) { + sendAdminCommandLocked(admin, DeviceAdminReceiver.ACTION_PASSWORD_EXPIRING); + } + } + setExpirationAlarmCheckLocked(mContext); + } + } + + /** + * @param adminReceiver The admin to add + * @param refreshing true = update an active admin, no error + */ + public void setActiveAdmin(ComponentName adminReceiver, boolean refreshing) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); - + DeviceAdminInfo info = findAdmin(adminReceiver); if (info == null) { throw new IllegalArgumentException("Bad admin: " + adminReceiver); @@ -546,27 +843,51 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (this) { long ident = Binder.clearCallingIdentity(); try { - if (getActiveAdminUncheckedLocked(adminReceiver) != null) { + if (!refreshing && getActiveAdminUncheckedLocked(adminReceiver) != null) { throw new IllegalArgumentException("Admin is already added"); } - ActiveAdmin admin = new ActiveAdmin(info); - mAdminMap.put(adminReceiver, admin); - mAdminList.add(admin); + ActiveAdmin newAdmin = new ActiveAdmin(info); + mAdminMap.put(adminReceiver, newAdmin); + int replaceIndex = -1; + if (refreshing) { + final int N = mAdminList.size(); + for (int i=0; i < N; i++) { + ActiveAdmin oldAdmin = mAdminList.get(i); + if (oldAdmin.info.getComponent().equals(adminReceiver)) { + replaceIndex = i; + break; + } + } + } + if (replaceIndex == -1) { + mAdminList.add(newAdmin); + } else { + mAdminList.set(replaceIndex, newAdmin); + } saveSettingsLocked(); - sendAdminCommandLocked(admin, - DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED); + sendAdminCommandLocked(newAdmin, DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED); } finally { Binder.restoreCallingIdentity(ident); } } } - + public boolean isAdminActive(ComponentName adminReceiver) { synchronized (this) { return getActiveAdminUncheckedLocked(adminReceiver) != null; } } - + + public boolean hasGrantedPolicy(ComponentName adminReceiver, int policyId) { + synchronized (this) { + ActiveAdmin administrator = getActiveAdminUncheckedLocked(adminReceiver); + if (administrator == null) { + throw new SecurityException("No active admin " + adminReceiver); + } + return administrator.info.usesPolicy(policyId); + } + } + public List<ComponentName> getActiveAdmins() { synchronized (this) { final int N = mAdminList.size(); @@ -580,7 +901,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return res; } } - + public boolean packageHasActiveAdmins(String packageName) { synchronized (this) { final int N = mAdminList.size(); @@ -592,7 +913,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } } - + public void removeActiveAdmin(ComponentName adminReceiver) { synchronized (this) { ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver); @@ -611,10 +932,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + public void setPasswordQuality(ComponentName who, int quality) { validateQualityConstant(quality); - + synchronized (this) { if (who == null) { throw new NullPointerException("ComponentName is null"); @@ -627,16 +948,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + public int getPasswordQuality(ComponentName who) { synchronized (this) { int mode = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; - + if (who != null) { ActiveAdmin admin = getActiveAdminUncheckedLocked(who); return admin != null ? admin.passwordQuality : mode; } - + final int N = mAdminList.size(); for (int i=0; i<N; i++) { ActiveAdmin admin = mAdminList.get(i); @@ -647,7 +968,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return mode; } } - + public void setPasswordMinimumLength(ComponentName who, int length) { synchronized (this) { if (who == null) { @@ -661,16 +982,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + public int getPasswordMinimumLength(ComponentName who) { synchronized (this) { int length = 0; - + if (who != null) { ActiveAdmin admin = getActiveAdminUncheckedLocked(who); return admin != null ? admin.minimumPasswordLength : length; } - + final int N = mAdminList.size(); for (int i=0; i<N; i++) { ActiveAdmin admin = mAdminList.get(i); @@ -681,18 +1002,343 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return length; } } - + + public void setPasswordHistoryLength(ComponentName who, int length) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); + if (ap.passwordHistoryLength != length) { + ap.passwordHistoryLength = length; + saveSettingsLocked(); + } + } + } + + public int getPasswordHistoryLength(ComponentName who) { + synchronized (this) { + int length = 0; + + if (who != null) { + ActiveAdmin admin = getActiveAdminUncheckedLocked(who); + return admin != null ? admin.passwordHistoryLength : length; + } + + final int N = mAdminList.size(); + for (int i = 0; i < N; i++) { + ActiveAdmin admin = mAdminList.get(i); + if (length < admin.passwordHistoryLength) { + length = admin.passwordHistoryLength; + } + } + return length; + } + } + + public void setPasswordExpirationTimeout(ComponentName who, long timeout) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + if (timeout < 0) { + throw new IllegalArgumentException("Timeout must be >= 0 ms"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD); + // Calling this API automatically bumps the expiration date + final long expiration = timeout > 0L ? (timeout + System.currentTimeMillis()) : 0L; + ap.passwordExpirationDate = expiration; + ap.passwordExpirationTimeout = timeout; + if (timeout > 0L) { + Slog.w(TAG, "setPasswordExpiration(): password will expire on " + + DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT) + .format(new Date(expiration))); + } + saveSettingsLocked(); + setExpirationAlarmCheckLocked(mContext); // in case this is the first one + } + } + + /** + * Return a single admin's expiration cycle time, or the min of all cycle times. + * Returns 0 if not configured. + */ + public long getPasswordExpirationTimeout(ComponentName who) { + synchronized (this) { + if (who != null) { + ActiveAdmin admin = getActiveAdminUncheckedLocked(who); + return admin != null ? admin.passwordExpirationTimeout : 0L; + } + + long timeout = 0L; + final int N = mAdminList.size(); + for (int i = 0; i < N; i++) { + ActiveAdmin admin = mAdminList.get(i); + if (timeout == 0L || (admin.passwordExpirationTimeout != 0L + && timeout > admin.passwordExpirationTimeout)) { + timeout = admin.passwordExpirationTimeout; + } + } + return timeout; + } + } + + /** + * Return a single admin's expiration date/time, or the min (soonest) for all admins. + * Returns 0 if not configured. + */ + private long getPasswordExpirationLocked(ComponentName who) { + if (who != null) { + ActiveAdmin admin = getActiveAdminUncheckedLocked(who); + return admin != null ? admin.passwordExpirationDate : 0L; + } + + long timeout = 0L; + final int N = mAdminList.size(); + for (int i = 0; i < N; i++) { + ActiveAdmin admin = mAdminList.get(i); + if (timeout == 0L || (admin.passwordExpirationDate != 0 + && timeout > admin.passwordExpirationDate)) { + timeout = admin.passwordExpirationDate; + } + } + return timeout; + } + + public long getPasswordExpiration(ComponentName who) { + synchronized (this) { + return getPasswordExpirationLocked(who); + } + } + + public void setPasswordMinimumUpperCase(ComponentName who, int length) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); + if (ap.minimumPasswordUpperCase != length) { + ap.minimumPasswordUpperCase = length; + saveSettingsLocked(); + } + } + } + + public int getPasswordMinimumUpperCase(ComponentName who) { + synchronized (this) { + int length = 0; + + if (who != null) { + ActiveAdmin admin = getActiveAdminUncheckedLocked(who); + return admin != null ? admin.minimumPasswordUpperCase : length; + } + + final int N = mAdminList.size(); + for (int i=0; i<N; i++) { + ActiveAdmin admin = mAdminList.get(i); + if (length < admin.minimumPasswordUpperCase) { + length = admin.minimumPasswordUpperCase; + } + } + return length; + } + } + + public void setPasswordMinimumLowerCase(ComponentName who, int length) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); + if (ap.minimumPasswordLowerCase != length) { + ap.minimumPasswordLowerCase = length; + saveSettingsLocked(); + } + } + } + + public int getPasswordMinimumLowerCase(ComponentName who) { + synchronized (this) { + int length = 0; + + if (who != null) { + ActiveAdmin admin = getActiveAdminUncheckedLocked(who); + return admin != null ? admin.minimumPasswordLowerCase : length; + } + + final int N = mAdminList.size(); + for (int i=0; i<N; i++) { + ActiveAdmin admin = mAdminList.get(i); + if (length < admin.minimumPasswordLowerCase) { + length = admin.minimumPasswordLowerCase; + } + } + return length; + } + } + + public void setPasswordMinimumLetters(ComponentName who, int length) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); + if (ap.minimumPasswordLetters != length) { + ap.minimumPasswordLetters = length; + saveSettingsLocked(); + } + } + } + + public int getPasswordMinimumLetters(ComponentName who) { + synchronized (this) { + int length = 0; + + if (who != null) { + ActiveAdmin admin = getActiveAdminUncheckedLocked(who); + return admin != null ? admin.minimumPasswordLetters : length; + } + + final int N = mAdminList.size(); + for (int i=0; i<N; i++) { + ActiveAdmin admin = mAdminList.get(i); + if (length < admin.minimumPasswordLetters) { + length = admin.minimumPasswordLetters; + } + } + return length; + } + } + + public void setPasswordMinimumNumeric(ComponentName who, int length) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); + if (ap.minimumPasswordNumeric != length) { + ap.minimumPasswordNumeric = length; + saveSettingsLocked(); + } + } + } + + public int getPasswordMinimumNumeric(ComponentName who) { + synchronized (this) { + int length = 0; + + if (who != null) { + ActiveAdmin admin = getActiveAdminUncheckedLocked(who); + return admin != null ? admin.minimumPasswordNumeric : length; + } + + final int N = mAdminList.size(); + for (int i = 0; i < N; i++) { + ActiveAdmin admin = mAdminList.get(i); + if (length < admin.minimumPasswordNumeric) { + length = admin.minimumPasswordNumeric; + } + } + return length; + } + } + + public void setPasswordMinimumSymbols(ComponentName who, int length) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); + if (ap.minimumPasswordSymbols != length) { + ap.minimumPasswordSymbols = length; + saveSettingsLocked(); + } + } + } + + public int getPasswordMinimumSymbols(ComponentName who) { + synchronized (this) { + int length = 0; + + if (who != null) { + ActiveAdmin admin = getActiveAdminUncheckedLocked(who); + return admin != null ? admin.minimumPasswordSymbols : length; + } + + final int N = mAdminList.size(); + for (int i=0; i<N; i++) { + ActiveAdmin admin = mAdminList.get(i); + if (length < admin.minimumPasswordSymbols) { + length = admin.minimumPasswordSymbols; + } + } + return length; + } + } + + public void setPasswordMinimumNonLetter(ComponentName who, int length) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); + if (ap.minimumPasswordNonLetter != length) { + ap.minimumPasswordNonLetter = length; + saveSettingsLocked(); + } + } + } + + public int getPasswordMinimumNonLetter(ComponentName who) { + synchronized (this) { + int length = 0; + + if (who != null) { + ActiveAdmin admin = getActiveAdminUncheckedLocked(who); + return admin != null ? admin.minimumPasswordNonLetter : length; + } + + final int N = mAdminList.size(); + for (int i=0; i<N; i++) { + ActiveAdmin admin = mAdminList.get(i); + if (length < admin.minimumPasswordNonLetter) { + length = admin.minimumPasswordNonLetter; + } + } + return length; + } + } + public boolean isActivePasswordSufficient() { synchronized (this) { // This API can only be called by an active device admin, // so try to retrieve it to check that the caller is one. getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); - return mActivePasswordQuality >= getPasswordQuality(null) - && mActivePasswordLength >= getPasswordMinimumLength(null); + if (mActivePasswordQuality < getPasswordQuality(null) + || mActivePasswordLength < getPasswordMinimumLength(null)) { + return false; + } + if(mActivePasswordQuality != DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) { + return true; + } + return mActivePasswordUpperCase >= getPasswordMinimumUpperCase(null) + && mActivePasswordLowerCase >= getPasswordMinimumLowerCase(null) + && mActivePasswordLetters >= getPasswordMinimumLetters(null) + && mActivePasswordNumeric >= getPasswordMinimumNumeric(null) + && mActivePasswordSymbols >= getPasswordMinimumSymbols(null) + && mActivePasswordNonLetter >= getPasswordMinimumNonLetter(null); } } - + public int getCurrentFailedPasswordAttempts() { synchronized (this) { // This API can only be called by an active device admin, @@ -702,7 +1348,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return mFailedPasswordAttempts; } } - + public void setMaximumFailedPasswordsForWipe(ComponentName who, int num) { synchronized (this) { // This API can only be called by an active device admin, @@ -717,16 +1363,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + public int getMaximumFailedPasswordsForWipe(ComponentName who) { synchronized (this) { int count = 0; - + if (who != null) { ActiveAdmin admin = getActiveAdminUncheckedLocked(who); return admin != null ? admin.maximumFailedPasswordsForWipe : count; } - + final int N = mAdminList.size(); for (int i=0; i<N; i++) { ActiveAdmin admin = mAdminList.get(i); @@ -740,7 +1386,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return count; } } - + public boolean resetPassword(String password, int flags) { int quality; synchronized (this) { @@ -751,14 +1397,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { quality = getPasswordQuality(null); if (quality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { int realQuality = LockPatternUtils.computePasswordQuality(password); - if (realQuality < quality) { + if (realQuality < quality + && quality != DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) { Slog.w(TAG, "resetPassword: password quality 0x" + Integer.toHexString(quality) + " does not meet required quality 0x" + Integer.toHexString(quality)); return false; } - quality = realQuality; + quality = Math.max(realQuality, quality); } int length = getPasswordMinimumLength(null); if (password.length() < length) { @@ -766,14 +1413,86 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + " does not meet required length " + length); return false; } + if (quality == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) { + int letters = 0; + int uppercase = 0; + int lowercase = 0; + int numbers = 0; + int symbols = 0; + int nonletter = 0; + for (int i = 0; i < password.length(); i++) { + char c = password.charAt(i); + if (c >= 'A' && c <= 'Z') { + letters++; + uppercase++; + } else if (c >= 'a' && c <= 'z') { + letters++; + lowercase++; + } else if (c >= '0' && c <= '9') { + numbers++; + nonletter++; + } else { + symbols++; + nonletter++; + } + } + int neededLetters = getPasswordMinimumLetters(null); + if(letters < neededLetters) { + Slog.w(TAG, "resetPassword: number of letters " + letters + + " does not meet required number of letters " + neededLetters); + return false; + } + int neededNumbers = getPasswordMinimumNumeric(null); + if (numbers < neededNumbers) { + Slog + .w(TAG, "resetPassword: number of numerical digits " + numbers + + " does not meet required number of numerical digits " + + neededNumbers); + return false; + } + int neededLowerCase = getPasswordMinimumLowerCase(null); + if (lowercase < neededLowerCase) { + Slog.w(TAG, "resetPassword: number of lowercase letters " + lowercase + + " does not meet required number of lowercase letters " + + neededLowerCase); + return false; + } + int neededUpperCase = getPasswordMinimumUpperCase(null); + if (uppercase < neededUpperCase) { + Slog.w(TAG, "resetPassword: number of uppercase letters " + uppercase + + " does not meet required number of uppercase letters " + + neededUpperCase); + return false; + } + int neededSymbols = getPasswordMinimumSymbols(null); + if (symbols < neededSymbols) { + Slog.w(TAG, "resetPassword: number of special symbols " + symbols + + " does not meet required number of special symbols " + neededSymbols); + return false; + } + int neededNonLetter = getPasswordMinimumNonLetter(null); + if (nonletter < neededNonLetter) { + Slog.w(TAG, "resetPassword: number of non-letter characters " + nonletter + + " does not meet required number of non-letter characters " + + neededNonLetter); + return false; + } + } + + LockPatternUtils utils = new LockPatternUtils(mContext); + if(utils.checkPasswordHistory(password)) { + Slog.w(TAG, "resetPassword: password is the same as one of the last " + + getPasswordHistoryLength(null) + " passwords"); + return false; + } } - + int callingUid = Binder.getCallingUid(); if (mPasswordOwner >= 0 && mPasswordOwner != callingUid) { Slog.w(TAG, "resetPassword: already set by another uid and not entered by user"); return false; } - + // Don't do this with the lock held, because it is going to call // back in to the service. long ident = Binder.clearCallingIdentity(); @@ -791,10 +1510,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } finally { Binder.restoreCallingIdentity(ident); } - + return true; } - + public void setMaximumTimeToLock(ComponentName who, long timeMs) { synchronized (this) { if (who == null) { @@ -804,16 +1523,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { DeviceAdminInfo.USES_POLICY_FORCE_LOCK); if (ap.maximumTimeToUnlock != timeMs) { ap.maximumTimeToUnlock = timeMs; - + long ident = Binder.clearCallingIdentity(); try { saveSettingsLocked(); - + timeMs = getMaximumTimeToLock(null); if (timeMs <= 0) { timeMs = Integer.MAX_VALUE; } - + try { getIPowerManager().setMaximumScreenOffTimeount((int)timeMs); } catch (RemoteException e) { @@ -825,16 +1544,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + public long getMaximumTimeToLock(ComponentName who) { synchronized (this) { long time = 0; - + if (who != null) { ActiveAdmin admin = getActiveAdminUncheckedLocked(who); return admin != null ? admin.maximumTimeToUnlock : time; } - + final int N = mAdminList.size(); for (int i=0; i<N; i++) { ActiveAdmin admin = mAdminList.get(i); @@ -848,7 +1567,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return time; } } - + public void lockNow() { synchronized (this) { // This API can only be called by an active device admin, @@ -865,7 +1584,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + void wipeDataLocked(int flags) { if ((flags&DevicePolicyManager.WIPE_EXTERNAL_STORAGE) != 0) { Intent intent = new Intent(ExternalStorageFormatter.FORMAT_AND_FACTORY_RESET); @@ -880,7 +1599,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + public void wipeData(int flags) { synchronized (this) { // This API can only be called by an active device admin, @@ -895,11 +1614,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + public void getRemoveWarning(ComponentName comp, final RemoteCallback result) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); - + synchronized (this) { ActiveAdmin admin = getActiveAdminUncheckedLocked(comp); if (admin == null) { @@ -922,22 +1641,34 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { }, null, Activity.RESULT_OK, null, null); } } - - public void setActivePasswordState(int quality, int length) { + + public void setActivePasswordState(int quality, int length, int letters, int uppercase, + int lowercase, int numbers, int symbols, int nonletter) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); - + validateQualityConstant(quality); - + synchronized (this) { if (mActivePasswordQuality != quality || mActivePasswordLength != length - || mFailedPasswordAttempts != 0) { + || mFailedPasswordAttempts != 0 || mActivePasswordLetters != letters + || mActivePasswordUpperCase != uppercase + || mActivePasswordLowerCase != lowercase || mActivePasswordNumeric != numbers + || mActivePasswordSymbols != symbols || mActivePasswordNonLetter != nonletter) { long ident = Binder.clearCallingIdentity(); try { mActivePasswordQuality = quality; mActivePasswordLength = length; + mActivePasswordLetters = letters; + mActivePasswordLowerCase = lowercase; + mActivePasswordUpperCase = uppercase; + mActivePasswordNumeric = numbers; + mActivePasswordSymbols = symbols; + mActivePasswordNonLetter = nonletter; mFailedPasswordAttempts = 0; saveSettingsLocked(); + updatePasswordExpirationsLocked(); + setExpirationAlarmCheckLocked(mContext); sendAdminCommandLocked(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); } finally { @@ -946,11 +1677,29 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + + /** + * Called any time the device password is updated. Resets all password expiration clocks. + */ + private void updatePasswordExpirationsLocked() { + final int N = mAdminList.size(); + if (N > 0) { + for (int i=0; i<N; i++) { + ActiveAdmin admin = mAdminList.get(i); + if (admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)) { + long timeout = admin.passwordExpirationTimeout; + long expiration = timeout > 0L ? (timeout + System.currentTimeMillis()) : 0L; + admin.passwordExpirationDate = expiration; + } + } + saveSettingsLocked(); + } + } + public void reportFailedPasswordAttempt() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); - + synchronized (this) { long ident = Binder.clearCallingIdentity(); try { @@ -967,11 +1716,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + public void reportSuccessfulPasswordAttempt() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); - + synchronized (this) { if (mFailedPasswordAttempts != 0 || mPasswordOwner >= 0) { long ident = Binder.clearCallingIdentity(); @@ -987,7 +1736,212 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + + public ComponentName setGlobalProxy(ComponentName who, String proxySpec, + String exclusionList) { + synchronized(this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + + ActiveAdmin admin = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_SETS_GLOBAL_PROXY); + + // Scan through active admins and find if anyone has already + // set the global proxy. + final int N = mAdminList.size(); + Set<ComponentName> compSet = mAdminMap.keySet(); + for (ComponentName component : compSet) { + ActiveAdmin ap = mAdminMap.get(component); + if ((ap.specifiesGlobalProxy) && (!component.equals(who))) { + // Another admin already sets the global proxy + // Return it to the caller. + return component; + } + } + if (proxySpec == null) { + admin.specifiesGlobalProxy = false; + admin.globalProxySpec = null; + admin.globalProxyExclusionList = null; + } else { + + admin.specifiesGlobalProxy = true; + admin.globalProxySpec = proxySpec; + admin.globalProxyExclusionList = exclusionList; + } + + // Reset the global proxy accordingly + // Do this using system permissions, as apps cannot write to secure settings + long origId = Binder.clearCallingIdentity(); + resetGlobalProxy(); + Binder.restoreCallingIdentity(origId); + return null; + } + } + + public ComponentName getGlobalProxyAdmin() { + synchronized(this) { + // Scan through active admins and find if anyone has already + // set the global proxy. + final int N = mAdminList.size(); + for (int i = 0; i < N; i++) { + ActiveAdmin ap = mAdminList.get(i); + if (ap.specifiesGlobalProxy) { + // Device admin sets the global proxy + // Return it to the caller. + return ap.info.getComponent(); + } + } + } + // No device admin sets the global proxy. + return null; + } + + private void resetGlobalProxy() { + final int N = mAdminList.size(); + for (int i = 0; i < N; i++) { + ActiveAdmin ap = mAdminList.get(i); + if (ap.specifiesGlobalProxy) { + saveGlobalProxy(ap.globalProxySpec, ap.globalProxyExclusionList); + return; + } + } + // No device admins defining global proxies - reset global proxy settings to none + saveGlobalProxy(null, null); + } + + private void saveGlobalProxy(String proxySpec, String exclusionList) { + if (exclusionList == null) { + exclusionList = ""; + } + if (proxySpec == null) { + proxySpec = ""; + } + // Remove white spaces + proxySpec = proxySpec.trim(); + String data[] = proxySpec.split(":"); + int proxyPort = 8080; + if (data.length > 1) { + try { + proxyPort = Integer.parseInt(data[1]); + } catch (NumberFormatException e) {} + } + exclusionList = exclusionList.trim(); + ContentResolver res = mContext.getContentResolver(); + Settings.Secure.putString(res, Settings.Secure.GLOBAL_HTTP_PROXY_HOST, data[0]); + Settings.Secure.putInt(res, Settings.Secure.GLOBAL_HTTP_PROXY_PORT, proxyPort); + Settings.Secure.putString(res, Settings.Secure.GLOBAL_HTTP_PROXY_EXCLUSION_LIST, + exclusionList); + } + + /** + * Set the storage encryption request for a single admin. Returns the new total request + * status (for all admins). + */ + public int setStorageEncryption(ComponentName who, boolean encrypt) { + synchronized (this) { + // Check for permissions + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_ENCRYPTED_STORAGE); + + // Quick exit: If the filesystem does not support encryption, we can exit early. + if (!isEncryptionSupported()) { + return DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED; + } + + // (1) Record the value for the admin so it's sticky + if (ap.encryptionRequested != encrypt) { + ap.encryptionRequested = encrypt; + saveSettingsLocked(); + } + + // (2) Compute "max" for all admins + boolean newRequested = false; + final int N = mAdminList.size(); + for (int i = 0; i < N; i++) { + newRequested |= mAdminList.get(i).encryptionRequested; + } + + // Notify OS of new request + setEncryptionRequested(newRequested); + + // Return the new global request status + return newRequested + ? DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE + : DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE; + } + } + + /** + * Get the current storage encryption request status for a given admin, or aggregate of all + * active admins. + */ + public boolean getStorageEncryption(ComponentName who) { + synchronized (this) { + // Check for permissions if a particular caller is specified + if (who != null) { + // When checking for a single caller, status is based on caller's request + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_ENCRYPTED_STORAGE); + return ap.encryptionRequested; + } + + // If no particular caller is specified, return the aggregate set of requests. + // This is short circuited by returning true on the first hit. + final int N = mAdminList.size(); + for (int i = 0; i < N; i++) { + if (mAdminList.get(i).encryptionRequested) { + return true; + } + } + return false; + } + } + + /** + * Get the current encryption status of the device. + */ + public int getStorageEncryptionStatus() { + return getEncryptionStatus(); + } + + /** + * Hook to low-levels: This should report if the filesystem supports encrypted storage. + */ + private boolean isEncryptionSupported() { + // Note, this can be implemented as + // return getEncryptionStatus() != DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED; + // But is provided as a separate internal method if there's a faster way to do a + // simple check for supported-or-not. + return getEncryptionStatus() != DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED; + } + + /** + * Hook to low-levels: Reporting the current status of encryption. + * @return A value such as {@link DevicePolicyManager#ENCRYPTION_STATUS_UNSUPPORTED} or + * {@link DevicePolicyManager#ENCRYPTION_STATUS_INACTIVE} or + * {@link DevicePolicyManager#ENCRYPTION_STATUS_ACTIVE}. + */ + private int getEncryptionStatus() { + String status = SystemProperties.get("ro.crypto.state", "unsupported"); + if ("encrypted".equalsIgnoreCase(status)) { + return DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE; + } else if ("unencrypted".equalsIgnoreCase(status)) { + return DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE; + } else { + return DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED; + } + } + + /** + * Hook to low-levels: If needed, record the new admin setting for encryption. + */ + private void setEncryptionRequested(boolean encrypt) { + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) @@ -998,12 +1952,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + ", uid=" + Binder.getCallingUid()); return; } - + final Printer p = new PrintWriterPrinter(pw); - + synchronized (this) { p.println("Current Device Policy Manager state:"); - + p.println(" Enabled Device Admins:"); final int N = mAdminList.size(); for (int i=0; i<N; i++) { @@ -1014,11 +1968,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { ap.dump(" ", pw); } } - + pw.println(" "); pw.print(" mActivePasswordQuality=0x"); pw.println(Integer.toHexString(mActivePasswordQuality)); pw.print(" mActivePasswordLength="); pw.println(mActivePasswordLength); + pw.print(" mActivePasswordUpperCase="); pw.println(mActivePasswordUpperCase); + pw.print(" mActivePasswordLowerCase="); pw.println(mActivePasswordLowerCase); + pw.print(" mActivePasswordLetters="); pw.println(mActivePasswordLetters); + pw.print(" mActivePasswordNumeric="); pw.println(mActivePasswordNumeric); + pw.print(" mActivePasswordSymbols="); pw.println(mActivePasswordSymbols); + pw.print(" mActivePasswordNonLetter="); pw.println(mActivePasswordNonLetter); pw.print(" mFailedPasswordAttempts="); pw.println(mFailedPasswordAttempts); pw.print(" mPasswordOwner="); pw.println(mPasswordOwner); } diff --git a/services/java/com/android/server/DeviceStorageMonitorService.java b/services/java/com/android/server/DeviceStorageMonitorService.java index 0b1a4a3..0fba7c3 100644 --- a/services/java/com/android/server/DeviceStorageMonitorService.java +++ b/services/java/com/android/server/DeviceStorageMonitorService.java @@ -16,7 +16,6 @@ package com.android.server; -import com.android.server.am.ActivityManagerService; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -38,7 +37,6 @@ import android.provider.Settings; import android.util.Config; import android.util.EventLog; import android.util.Slog; -import android.provider.Settings; /** * This class implements a service to monitor the amount of disk @@ -66,6 +64,7 @@ class DeviceStorageMonitorService extends Binder { private static final int MONITOR_INTERVAL = 1; //in minutes private static final int LOW_MEMORY_NOTIFICATION_ID = 1; private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10; + private static final int DEFAULT_THRESHOLD_MAX_BYTES = 500*1024*1024; // 500MB private static final int DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES = 12*60; //in minutes private static final long DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD = 2 * 1024 * 1024; // 2MB private static final long DEFAULT_CHECK_INTERVAL = MONITOR_INTERVAL*60*1000; @@ -271,13 +270,18 @@ class DeviceStorageMonitorService extends Binder { * any way */ private long getMemThreshold() { - int value = Settings.Secure.getInt( + long value = Settings.Secure.getInt( mContentResolver, Settings.Secure.SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE); if(localLOGV) Slog.v(TAG, "Threshold Percentage="+value); + value *= mTotalMemory; + long maxValue = Settings.Secure.getInt( + mContentResolver, + Settings.Secure.SYS_STORAGE_THRESHOLD_MAX_BYTES, + DEFAULT_THRESHOLD_MAX_BYTES); //evaluate threshold value - return mTotalMemory*value; + return value < maxValue ? value : maxValue; } /* diff --git a/services/java/com/android/server/DockObserver.java b/services/java/com/android/server/DockObserver.java index bee8872..dea9007 100644 --- a/services/java/com/android/server/DockObserver.java +++ b/services/java/com/android/server/DockObserver.java @@ -102,8 +102,8 @@ class DockObserver extends UEventObserver { try { FileReader file = new FileReader(DOCK_STATE_PATH); int len = file.read(buffer, 0, 1024); + file.close(); mPreviousDockState = mDockState = Integer.valueOf((new String(buffer, 0, len)).trim()); - } catch (FileNotFoundException e) { Slog.w(TAG, "This kernel does not have dock station support"); } catch (Exception e) { @@ -158,13 +158,17 @@ class DockObserver extends UEventObserver { { String whichSound = null; if (mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) { - if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) { + if ((mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) || + (mPreviousDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || + (mPreviousDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) { whichSound = Settings.System.DESK_UNDOCK_SOUND; } else if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_CAR) { whichSound = Settings.System.CAR_UNDOCK_SOUND; } } else { - if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) { + if ((mDockState == Intent.EXTRA_DOCK_STATE_DESK) || + (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || + (mDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) { whichSound = Settings.System.DESK_DOCK_SOUND; } else if (mDockState == Intent.EXTRA_DOCK_STATE_CAR) { whichSound = Settings.System.CAR_DOCK_SOUND; diff --git a/services/java/com/android/server/DropBoxManagerService.java b/services/java/com/android/server/DropBoxManagerService.java index 3981525..5c878c9 100644 --- a/services/java/com/android/server/DropBoxManagerService.java +++ b/services/java/com/android/server/DropBoxManagerService.java @@ -97,10 +97,18 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { // Ensure that all log entries have a unique timestamp private long mLastTimestamp = 0; + private volatile boolean mBooted = false; + /** Receives events that might indicate a need to clean up files. */ private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + if (intent != null && Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { + mBooted = true; + return; + } + + // Else, for ACTION_DEVICE_STORAGE_LOW: mCachedQuotaUptimeMillis = 0; // Force a re-check of quota size // Run the initialization in the background (not this main thread). @@ -132,7 +140,11 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { // Set up intent receivers mContext = context; mContentResolver = context.getContentResolver(); - context.registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW)); + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW); + filter.addAction(Intent.ACTION_BOOT_COMPLETED); + context.registerReceiver(mReceiver, filter); mContentResolver.registerContentObserver( Settings.Secure.CONTENT_URI, true, @@ -224,6 +236,9 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { Intent dropboxIntent = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED); dropboxIntent.putExtra(DropBoxManager.EXTRA_TAG, tag); dropboxIntent.putExtra(DropBoxManager.EXTRA_TIME, time); + if (!mBooted) { + dropboxIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + } mContext.sendBroadcast(dropboxIntent, android.Manifest.permission.READ_LOGS); } catch (IOException e) { @@ -349,16 +364,17 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { if ((entry.flags & DropBoxManager.IS_TEXT) != 0 && (doPrint || !doFile)) { DropBoxManager.Entry dbe = null; + InputStreamReader isr = null; try { dbe = new DropBoxManager.Entry( entry.tag, entry.timestampMillis, entry.file, entry.flags); if (doPrint) { - InputStreamReader r = new InputStreamReader(dbe.getInputStream()); + isr = new InputStreamReader(dbe.getInputStream()); char[] buf = new char[4096]; boolean newline = false; for (;;) { - int n = r.read(buf); + int n = isr.read(buf); if (n <= 0) break; out.append(buf, 0, n); newline = (buf[n - 1] == '\n'); @@ -382,6 +398,12 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { Slog.e(TAG, "Can't read: " + entry.file, e); } finally { if (dbe != null) dbe.close(); + if (isr != null) { + try { + isr.close(); + } catch (IOException unused) { + } + } } } diff --git a/services/java/com/android/server/HeadsetObserver.java b/services/java/com/android/server/HeadsetObserver.java deleted file mode 100644 index 6f0a91d..0000000 --- a/services/java/com/android/server/HeadsetObserver.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 2008 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.server; - -import android.app.ActivityManagerNative; -import android.content.Context; -import android.content.Intent; -import android.os.Handler; -import android.os.Message; -import android.os.PowerManager; -import android.os.PowerManager.WakeLock; -import android.os.UEventObserver; -import android.util.Slog; -import android.media.AudioManager; - -import java.io.FileReader; -import java.io.FileNotFoundException; - -/** - * <p>HeadsetObserver monitors for a wired headset. - */ -class HeadsetObserver extends UEventObserver { - private static final String TAG = HeadsetObserver.class.getSimpleName(); - private static final boolean LOG = true; - - private static final String HEADSET_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/h2w"; - private static final String HEADSET_STATE_PATH = "/sys/class/switch/h2w/state"; - private static final String HEADSET_NAME_PATH = "/sys/class/switch/h2w/name"; - - private static final int BIT_HEADSET = (1 << 0); - private static final int BIT_HEADSET_NO_MIC = (1 << 1); - private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC); - private static final int HEADSETS_WITH_MIC = BIT_HEADSET; - - private int mHeadsetState; - private int mPrevHeadsetState; - private String mHeadsetName; - - private final Context mContext; - private final WakeLock mWakeLock; // held while there is a pending route change - - public HeadsetObserver(Context context) { - mContext = context; - PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); - mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetObserver"); - mWakeLock.setReferenceCounted(false); - - startObserving(HEADSET_UEVENT_MATCH); - - init(); // set initial status - } - - @Override - public void onUEvent(UEventObserver.UEvent event) { - if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString()); - - try { - update(event.get("SWITCH_NAME"), Integer.parseInt(event.get("SWITCH_STATE"))); - } catch (NumberFormatException e) { - Slog.e(TAG, "Could not parse switch state from event " + event); - } - } - - private synchronized final void init() { - char[] buffer = new char[1024]; - - String newName = mHeadsetName; - int newState = mHeadsetState; - mPrevHeadsetState = mHeadsetState; - try { - FileReader file = new FileReader(HEADSET_STATE_PATH); - int len = file.read(buffer, 0, 1024); - newState = Integer.valueOf((new String(buffer, 0, len)).trim()); - - file = new FileReader(HEADSET_NAME_PATH); - len = file.read(buffer, 0, 1024); - newName = new String(buffer, 0, len).trim(); - - } catch (FileNotFoundException e) { - Slog.w(TAG, "This kernel does not have wired headset support"); - } catch (Exception e) { - Slog.e(TAG, "" , e); - } - - update(newName, newState); - } - - private synchronized final void update(String newName, int newState) { - // Retain only relevant bits - int headsetState = newState & SUPPORTED_HEADSETS; - int newOrOld = headsetState | mHeadsetState; - int delay = 0; - // reject all suspect transitions: only accept state changes from: - // - a: 0 heaset to 1 headset - // - b: 1 headset to 0 headset - if (mHeadsetState == headsetState || ((newOrOld & (newOrOld - 1)) != 0)) { - return; - } - - mHeadsetName = newName; - mPrevHeadsetState = mHeadsetState; - mHeadsetState = headsetState; - - if (headsetState == 0) { - Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY); - mContext.sendBroadcast(intent); - // It can take hundreds of ms flush the audio pipeline after - // apps pause audio playback, but audio route changes are - // immediate, so delay the route change by 1000ms. - // This could be improved once the audio sub-system provides an - // interface to clear the audio pipeline. - delay = 1000; - } else { - // Insert the same delay for headset connection so that the connection event is not - // broadcast before the disconnection event in case of fast removal/insertion - if (mHandler.hasMessages(0)) { - delay = 1000; - } - } - mWakeLock.acquire(); - mHandler.sendMessageDelayed(mHandler.obtainMessage(0, - mHeadsetState, - mPrevHeadsetState, - mHeadsetName), - delay); - } - - private synchronized final void sendIntents(int headsetState, int prevHeadsetState, String headsetName) { - int allHeadsets = SUPPORTED_HEADSETS; - for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) { - if ((curHeadset & allHeadsets) != 0) { - sendIntent(curHeadset, headsetState, prevHeadsetState, headsetName); - allHeadsets &= ~curHeadset; - } - } - } - - private final void sendIntent(int headset, int headsetState, int prevHeadsetState, String headsetName) { - if ((headsetState & headset) != (prevHeadsetState & headset)) { - // Pack up the values and broadcast them to everyone - Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - int state = 0; - int microphone = 0; - - if ((headset & HEADSETS_WITH_MIC) != 0) { - microphone = 1; - } - if ((headsetState & headset) != 0) { - state = 1; - } - intent.putExtra("state", state); - intent.putExtra("name", headsetName); - intent.putExtra("microphone", microphone); - - if (LOG) Slog.v(TAG, "Intent.ACTION_HEADSET_PLUG: state: "+state+" name: "+headsetName+" mic: "+microphone); - // TODO: Should we require a permission? - ActivityManagerNative.broadcastStickyIntent(intent, null); - } - } - - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - sendIntents(msg.arg1, msg.arg2, (String)msg.obj); - mWakeLock.release(); - } - }; -} diff --git a/services/java/com/android/server/InputApplication.java b/services/java/com/android/server/InputApplication.java index 38420d4..ae09484 100644 --- a/services/java/com/android/server/InputApplication.java +++ b/services/java/com/android/server/InputApplication.java @@ -18,16 +18,19 @@ package com.android.server; /** * Describes input-related application properties for use by the input dispatcher. - * * @hide */ public final class InputApplication { + // Application handle. + public InputApplicationHandle inputApplicationHandle; + // Application name. public String name; - + // Dispatching timeout. public long dispatchingTimeoutNanos; - - // The application window token. - public Object token; + + public void recycle() { + inputApplicationHandle = null; + } } diff --git a/services/java/com/android/server/InputApplicationHandle.java b/services/java/com/android/server/InputApplicationHandle.java new file mode 100644 index 0000000..d396d11 --- /dev/null +++ b/services/java/com/android/server/InputApplicationHandle.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2011 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.server; + +/** + * Functions as a handle for an application that can receive input. + * Enables the native input dispatcher to refer indirectly to the window manager's + * application window token. + * @hide + */ +public final class InputApplicationHandle { + // Pointer to the native input application handle. + // This field is lazily initialized via JNI. + @SuppressWarnings("unused") + private int ptr; + + // The window manager's application window token. + public final WindowManagerService.AppWindowToken appWindowToken; + + private native void nativeDispose(); + + public InputApplicationHandle(WindowManagerService.AppWindowToken appWindowToken) { + this.appWindowToken = appWindowToken; + } + + @Override + protected void finalize() throws Throwable { + nativeDispose(); + super.finalize(); + } +} diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java index ba39c57..8d249ff 100644 --- a/services/java/com/android/server/InputManager.java +++ b/services/java/com/android/server/InputManager.java @@ -19,10 +19,17 @@ package com.android.server; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.os.Environment; import android.os.SystemProperties; import android.util.Slog; @@ -30,7 +37,9 @@ import android.util.Xml; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; +import android.view.KeyEvent; import android.view.Surface; +import android.view.WindowManager; import java.io.BufferedReader; import java.io.File; @@ -41,7 +50,6 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Properties; /* * Wraps the C++ InputManager and provides its callbacks. @@ -69,7 +77,7 @@ public class InputManager { private static native boolean nativeHasKeys(int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists); private static native void nativeRegisterInputChannel(InputChannel inputChannel, - boolean monitor); + InputWindowHandle inputWindowHandle, boolean monitor); private static native void nativeUnregisterInputChannel(InputChannel inputChannel); private static native int nativeInjectInputEvent(InputEvent event, int injectorPid, int injectorUid, int syncMode, int timeoutMillis); @@ -79,6 +87,8 @@ public class InputManager { private static native InputDevice nativeGetInputDevice(int deviceId); private static native void nativeGetInputConfiguration(Configuration configuration); private static native int[] nativeGetInputDeviceIds(); + private static native boolean nativeTransferTouchFocus(InputChannel fromChannel, + InputChannel toChannel); private static native String nativeDump(); // Input event injection constants defined in InputDispatcher.h. @@ -230,7 +240,7 @@ public class InputManager { } InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName); - nativeRegisterInputChannel(inputChannels[0], true); + nativeRegisterInputChannel(inputChannels[0], null, true); inputChannels[0].dispose(); // don't need to retain the Java object reference return inputChannels[1]; } @@ -238,13 +248,16 @@ public class InputManager { /** * Registers an input channel so that it can be used as an input event target. * @param inputChannel The input channel to register. + * @param inputWindowHandle The handle of the input window associated with the + * input channel, or null if none. */ - public void registerInputChannel(InputChannel inputChannel) { + public void registerInputChannel(InputChannel inputChannel, + InputWindowHandle inputWindowHandle) { if (inputChannel == null) { throw new IllegalArgumentException("inputChannel must not be null."); } - nativeRegisterInputChannel(inputChannel, false); + nativeRegisterInputChannel(inputChannel, inputWindowHandle, false); } /** @@ -326,28 +339,78 @@ public class InputManager { nativeSetInputDispatchMode(enabled, frozen); } + /** + * Atomically transfers touch focus from one window to another as identified by + * their input channels. It is possible for multiple windows to have + * touch focus if they support split touch dispatch + * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this + * method only transfers touch focus of the specified window without affecting + * other windows that may also have touch focus at the same time. + * @param fromChannel The channel of a window that currently has touch focus. + * @param toChannel The channel of the window that should receive touch focus in + * place of the first. + * @return True if the transfer was successful. False if the window with the + * specified channel did not actually have touch focus at the time of the request. + */ + public boolean transferTouchFocus(InputChannel fromChannel, InputChannel toChannel) { + if (fromChannel == null) { + throw new IllegalArgumentException("fromChannel must not be null."); + } + if (toChannel == null) { + throw new IllegalArgumentException("toChannel must not be null."); + } + return nativeTransferTouchFocus(fromChannel, toChannel); + } + public void dump(PrintWriter pw) { String dumpStr = nativeDump(); if (dumpStr != null) { pw.println(dumpStr); } } - - private static final class VirtualKeyDefinition { - public int scanCode; - - // configured position data, specified in display coords - public int centerX; - public int centerY; - public int width; - public int height; - } - - private static final class InputDeviceCalibration { - public String[] keys; - public String[] values; + + private static final class PointerIcon { + public Bitmap bitmap; + public float hotSpotX; + public float hotSpotY; + + public static PointerIcon load(Resources resources, int resourceId) { + PointerIcon icon = new PointerIcon(); + + XmlResourceParser parser = resources.getXml(resourceId); + final int bitmapRes; + try { + XmlUtils.beginDocument(parser, "pointer-icon"); + + TypedArray a = resources.obtainAttributes( + parser, com.android.internal.R.styleable.PointerIcon); + bitmapRes = a.getResourceId(com.android.internal.R.styleable.PointerIcon_bitmap, 0); + icon.hotSpotX = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0); + icon.hotSpotY = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0); + a.recycle(); + } catch (Exception ex) { + Slog.e(TAG, "Exception parsing pointer icon resource.", ex); + return null; + } finally { + parser.close(); + } + + if (bitmapRes == 0) { + Slog.e(TAG, "<pointer-icon> is missing bitmap attribute"); + return null; + } + + Drawable drawable = resources.getDrawable(bitmapRes); + if (!(drawable instanceof BitmapDrawable)) { + Slog.e(TAG, "<pointer-icon> bitmap attribute must refer to a bitmap drawable"); + return null; + } + + icon.bitmap = ((BitmapDrawable)drawable).getBitmap(); + return icon; + } } - + /* * Callbacks from native. */ @@ -360,7 +423,7 @@ public class InputManager { @SuppressWarnings("unused") public void notifyConfigurationChanged(long whenNanos) { - mWindowManagerService.sendNewConfiguration(); + mWindowManagerService.mInputMonitor.notifyConfigurationChanged(); } @SuppressWarnings("unused") @@ -369,28 +432,35 @@ public class InputManager { } @SuppressWarnings("unused") - public void notifyInputChannelBroken(InputChannel inputChannel) { - mWindowManagerService.mInputMonitor.notifyInputChannelBroken(inputChannel); + public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle) { + mWindowManagerService.mInputMonitor.notifyInputChannelBroken(inputWindowHandle); } @SuppressWarnings("unused") - public long notifyANR(Object token, InputChannel inputChannel) { - return mWindowManagerService.mInputMonitor.notifyANR(token, inputChannel); + public long notifyANR(InputApplicationHandle inputApplicationHandle, + InputWindowHandle inputWindowHandle) { + return mWindowManagerService.mInputMonitor.notifyANR( + inputApplicationHandle, inputWindowHandle); } @SuppressWarnings("unused") - public int interceptKeyBeforeQueueing(long whenNanos, int action, int flags, - int keyCode, int scanCode, int policyFlags, boolean isScreenOn) { + public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) { return mWindowManagerService.mInputMonitor.interceptKeyBeforeQueueing( - whenNanos, action, flags, keyCode, scanCode, policyFlags, isScreenOn); + event, policyFlags, isScreenOn); + } + + @SuppressWarnings("unused") + public boolean interceptKeyBeforeDispatching(InputWindowHandle focus, + KeyEvent event, int policyFlags) { + return mWindowManagerService.mInputMonitor.interceptKeyBeforeDispatching( + focus, event, policyFlags); } @SuppressWarnings("unused") - public boolean interceptKeyBeforeDispatching(InputChannel focus, int action, - int flags, int keyCode, int scanCode, int metaState, int repeatCount, - int policyFlags) { - return mWindowManagerService.mInputMonitor.interceptKeyBeforeDispatching(focus, - action, flags, keyCode, scanCode, metaState, repeatCount, policyFlags); + public KeyEvent dispatchUnhandledKey(InputWindowHandle focus, + KeyEvent event, int policyFlags) { + return mWindowManagerService.mInputMonitor.dispatchUnhandledKey( + focus, event, policyFlags); } @SuppressWarnings("unused") @@ -419,79 +489,6 @@ public class InputManager { } @SuppressWarnings("unused") - public VirtualKeyDefinition[] getVirtualKeyDefinitions(String deviceName) { - ArrayList<VirtualKeyDefinition> keys = new ArrayList<VirtualKeyDefinition>(); - - try { - FileInputStream fis = new FileInputStream( - "/sys/board_properties/virtualkeys." + deviceName); - InputStreamReader isr = new InputStreamReader(fis); - BufferedReader br = new BufferedReader(isr, 2048); - String str = br.readLine(); - if (str != null) { - String[] it = str.split(":"); - if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "***** VIRTUAL KEYS: " + it); - final int N = it.length-6; - for (int i=0; i<=N; i+=6) { - if (!"0x01".equals(it[i])) { - Slog.w(TAG, "Unknown virtual key type at elem #" - + i + ": " + it[i] + " for device " + deviceName); - continue; - } - try { - VirtualKeyDefinition key = new VirtualKeyDefinition(); - key.scanCode = Integer.parseInt(it[i+1]); - key.centerX = Integer.parseInt(it[i+2]); - key.centerY = Integer.parseInt(it[i+3]); - key.width = Integer.parseInt(it[i+4]); - key.height = Integer.parseInt(it[i+5]); - if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Virtual key " - + key.scanCode + ": center=" + key.centerX + "," - + key.centerY + " size=" + key.width + "x" - + key.height); - keys.add(key); - } catch (NumberFormatException e) { - Slog.w(TAG, "Bad number in virtual key definition at region " - + i + " in: " + str + " for device " + deviceName, e); - } - } - } - br.close(); - } catch (FileNotFoundException e) { - Slog.i(TAG, "No virtual keys found for device " + deviceName + "."); - } catch (IOException e) { - Slog.w(TAG, "Error reading virtual keys for device " + deviceName + ".", e); - } - - return keys.toArray(new VirtualKeyDefinition[keys.size()]); - } - - @SuppressWarnings("unused") - public InputDeviceCalibration getInputDeviceCalibration(String deviceName) { - // Calibration is specified as a sequence of colon-delimited key value pairs. - Properties properties = new Properties(); - File calibrationFile = new File(Environment.getRootDirectory(), - CALIBRATION_DIR_PATH + deviceName + ".idc"); - if (calibrationFile.exists()) { - try { - properties.load(new FileInputStream(calibrationFile)); - } catch (IOException ex) { - Slog.w(TAG, "Error reading input device calibration properties for device " - + deviceName + " from " + calibrationFile + ".", ex); - } - } else { - Slog.i(TAG, "No input device calibration properties found for device " - + deviceName + "."); - return null; - } - - InputDeviceCalibration calibration = new InputDeviceCalibration(); - calibration.keys = properties.keySet().toArray(new String[properties.size()]); - calibration.values = properties.values().toArray(new String[properties.size()]); - return calibration; - } - - @SuppressWarnings("unused") public String[] getExcludedDeviceNames() { ArrayList<String> names = new ArrayList<String>(); @@ -539,5 +536,19 @@ public class InputManager { } return result; } + + @SuppressWarnings("unused") + public int getPointerLayer() { + return mWindowManagerService.mPolicy.windowTypeToLayerLw( + WindowManager.LayoutParams.TYPE_POINTER) + * WindowManagerService.TYPE_LAYER_MULTIPLIER + + WindowManagerService.TYPE_LAYER_OFFSET; + } + + @SuppressWarnings("unused") + public PointerIcon getPointerIcon() { + return PointerIcon.load(mContext.getResources(), + com.android.internal.R.drawable.pointer_arrow_icon); + } } } diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index ecad3cc..6636fb7 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -49,6 +49,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.database.ContentObserver; +import android.inputmethodservice.InputMethodService; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -59,20 +60,24 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.SystemProperties; import android.provider.Settings; import android.provider.Settings.Secure; +import android.provider.Settings.SettingNotFoundException; import android.text.TextUtils; import android.util.EventLog; +import android.util.Pair; import android.util.Slog; import android.util.PrintWriterPrinter; import android.util.Printer; import android.view.IWindowManager; import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputBinding; import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodSubtype; import java.io.FileDescriptor; import java.io.IOException; @@ -80,6 +85,7 @@ import java.io.PrintWriter; import java.text.Collator; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -93,6 +99,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static final String TAG = "InputManagerService"; static final int MSG_SHOW_IM_PICKER = 1; + static final int MSG_SHOW_IM_SUBTYPE_PICKER = 2; + static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 3; + static final int MSG_SHOW_IM_CONFIG = 4; static final int MSG_UNBIND_INPUT = 1000; static final int MSG_BIND_INPUT = 1010; @@ -109,8 +118,19 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static final long TIME_TO_RECONNECT = 10*1000; + private static final int NOT_A_SUBTYPE_ID = -1; + private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID); + private static final String SUBTYPE_MODE_KEYBOARD = "keyboard"; + private static final String SUBTYPE_MODE_VOICE = "voice"; + + // TODO: Will formalize this value as API + private static final String SUBTYPE_EXTRAVALUE_EXCLUDE_FROM_LAST_IME = + "excludeFromLastInputMethod"; + final Context mContext; + final Resources mRes; final Handler mHandler; + final InputMethodSettings mSettings; final SettingsObserver mSettingsObserver; final StatusBarManagerService mStatusBar; final IWindowManager mIWindowManager; @@ -120,13 +140,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // All known input methods. mMethodMap also serves as the global // lock for this class. - final ArrayList<InputMethodInfo> mMethodList - = new ArrayList<InputMethodInfo>(); - final HashMap<String, InputMethodInfo> mMethodMap - = new HashMap<String, InputMethodInfo>(); - - final TextUtils.SimpleStringSplitter mStringColonSplitter - = new TextUtils.SimpleStringSplitter(':'); + final ArrayList<InputMethodInfo> mMethodList = new ArrayList<InputMethodInfo>(); + final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<String, InputMethodInfo>(); class SessionState { final ClientState client; @@ -224,6 +239,16 @@ public class InputMethodManagerService extends IInputMethodManager.Stub String mCurId; /** + * The current subtype of the current input method. + */ + private InputMethodSubtype mCurrentSubtype; + + // This list contains the pairs of InputMethodInfo and InputMethodSubtype. + private final HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>> + mShortcutInputMethodsAndSubtypes = + new HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>(); + + /** * Set to true if our ServiceConnection is currently actively bound to * a service (whether or not we have gotten its IBinder back yet). */ @@ -288,10 +313,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub */ boolean mScreenOn = true; + int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT; + int mImeWindowVis; + long mOldSystemSettingsVersion; + AlertDialog.Builder mDialogBuilder; AlertDialog mSwitchingDialog; InputMethodInfo[] mIms; CharSequence[] mItems; + int[] mSubtypeIds; class SettingsObserver extends ContentObserver { SettingsObserver(Handler handler) { @@ -299,6 +329,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub ContentResolver resolver = mContext.getContentResolver(); resolver.registerContentObserver(Settings.Secure.getUriFor( Settings.Secure.DEFAULT_INPUT_METHOD), false, this); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this); } @Override public void onChange(boolean selfChange) { @@ -351,9 +383,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (!doit) { return true; } - - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD, ""); + resetSelectedInputMethodAndSubtypeLocked(""); chooseNewDefaultIMELocked(); return true; } @@ -406,19 +436,19 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // Uh oh, current input method is no longer around! // Pick another one... Slog.i(TAG, "Current input method removed: " + curInputMethodId); + mImeWindowVis = 0; + mStatusBar.setImeWindowStatus(mCurToken, mImeWindowVis, + mBackDisposition); if (!chooseNewDefaultIMELocked()) { changed = true; curIm = null; - curInputMethodId = ""; Slog.i(TAG, "Unsetting current input method"); - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD, - curInputMethodId); + resetSelectedInputMethodAndSubtypeLocked(""); } } } } - + if (curIm == null) { // We currently don't have a default input method... is // one now available? @@ -449,6 +479,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public InputMethodManagerService(Context context, StatusBarManagerService statusBar) { mContext = context; + mRes = context.getResources(); mHandler = new Handler(this); mIWindowManager = IWindowManager.Stub.asInterface( ServiceManager.getService(Context.WINDOW_SERVICE)); @@ -457,6 +488,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub handleMessage(msg); } }); + // Initialize the system settings version to undefined. + mOldSystemSettingsVersion = -1; (new MyPackageMonitor()).register(mContext, true); @@ -469,27 +502,19 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mStatusBar = statusBar; statusBar.setIconVisibility("ime", false); + // mSettings should be created before buildInputMethodListLocked + mSettings = new InputMethodSettings( + mRes, context.getContentResolver(), mMethodMap, mMethodList); buildInputMethodListLocked(mMethodList, mMethodMap); + mSettings.enableAllIMEsIfThereIsNoEnabledIME(); - final String enabledStr = Settings.Secure.getString( - mContext.getContentResolver(), - Settings.Secure.ENABLED_INPUT_METHODS); - Slog.i(TAG, "Enabled input methods: " + enabledStr); - final String defaultIme = Settings.Secure.getString(mContext - .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); - if (enabledStr == null || TextUtils.isEmpty(defaultIme)) { - Slog.i(TAG, "Enabled input methods or default IME has not been set, enabling all"); + if (TextUtils.isEmpty(Settings.Secure.getString( + mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD))) { InputMethodInfo defIm = null; - StringBuilder sb = new StringBuilder(256); - final int N = mMethodList.size(); - for (int i=0; i<N; i++) { - InputMethodInfo imi = mMethodList.get(i); - Slog.i(TAG, "Adding: " + imi.getId()); - if (i > 0) sb.append(':'); - sb.append(imi.getId()); + for (InputMethodInfo imi: mMethodList) { if (defIm == null && imi.getIsDefaultResourceId() != 0) { try { - Resources res = mContext.createPackageContext( + Resources res = context.createPackageContext( imi.getPackageName(), 0).getResources(); if (res.getBoolean(imi.getIsDefaultResourceId())) { defIm = imi; @@ -500,15 +525,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } } - if (defIm == null && N > 0) { + if (defIm == null && mMethodList.size() > 0) { defIm = mMethodList.get(0); Slog.i(TAG, "No default found, using " + defIm.getId()); } - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.ENABLED_INPUT_METHODS, sb.toString()); if (defIm != null) { - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD, defIm.getId()); + setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false); } } @@ -552,29 +574,40 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public List<InputMethodInfo> getEnabledInputMethodList() { synchronized (mMethodMap) { - return getEnabledInputMethodListLocked(); + return mSettings.getEnabledInputMethodListLocked(); } } - List<InputMethodInfo> getEnabledInputMethodListLocked() { - final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>(); - - final String enabledStr = Settings.Secure.getString( - mContext.getContentResolver(), - Settings.Secure.ENABLED_INPUT_METHODS); - if (enabledStr != null) { - final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter; - splitter.setString(enabledStr); + private HashMap<InputMethodInfo, List<InputMethodSubtype>> + getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked() { + HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes = + new HashMap<InputMethodInfo, List<InputMethodSubtype>>(); + for (InputMethodInfo imi: getEnabledInputMethodList()) { + enabledInputMethodAndSubtypes.put( + imi, getEnabledInputMethodSubtypeListLocked(imi, true)); + } + return enabledInputMethodAndSubtypes; + } - while (splitter.hasNext()) { - InputMethodInfo info = mMethodMap.get(splitter.next()); - if (info != null) { - res.add(info); - } - } + public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi, + boolean allowsImplicitlySelectedSubtypes) { + if (imi == null && mCurMethodId != null) { + imi = mMethodMap.get(mCurMethodId); + } + final List<InputMethodSubtype> enabledSubtypes = + mSettings.getEnabledInputMethodSubtypeListLocked(imi); + if (!allowsImplicitlySelectedSubtypes || enabledSubtypes.size() > 0) { + return enabledSubtypes; + } else { + return getApplicableSubtypesLocked(mRes, getSubtypes(imi)); } + } - return res; + public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi, + boolean allowsImplicitlySelectedSubtypes) { + synchronized (mMethodMap) { + return getEnabledInputMethodSubtypeListLocked(imi, allowsImplicitlySelectedSubtypes); + } } public void addClient(IInputMethodClient client, @@ -959,21 +992,54 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + public void setImeWindowStatus(IBinder token, int vis, int backDisposition) { + int uid = Binder.getCallingUid(); + long ident = Binder.clearCallingIdentity(); + try { + if (token == null || mCurToken != token) { + Slog.w(TAG, "Ignoring setImeWindowStatus of uid " + uid + " token: " + token); + return; + } + + synchronized (mMethodMap) { + mImeWindowVis = vis; + mBackDisposition = backDisposition; + mStatusBar.setImeWindowStatus(token, vis, backDisposition); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + // TODO: Investigate and fix why are settings changes getting processed before the settings seq + // number is updated? + // TODO: Change this stuff to not rely on modifying settings for normal user interactions. void updateFromSettingsLocked() { + long newSystemSettingsVersion = getSystemSettingsVersion(); + // This is a workaround to avoid a situation that old cached value in Settings.Secure + // will be handled. + if (newSystemSettingsVersion == mOldSystemSettingsVersion) return; + // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and // ENABLED_INPUT_METHODS is taking care of keeping them correctly in // sync, so we will never have a DEFAULT_INPUT_METHOD that is not // enabled. String id = Settings.Secure.getString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD); - if (id != null && id.length() > 0) { + Settings.Secure.DEFAULT_INPUT_METHOD); + // There is no input method selected, try to choose new applicable input method. + if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) { + id = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.DEFAULT_INPUT_METHOD); + } + if (!TextUtils.isEmpty(id)) { try { - setInputMethodLocked(id); + setInputMethodLocked(id, getSelectedInputMethodSubtypeId(id)); } catch (IllegalArgumentException e) { Slog.w(TAG, "Unknown input method from prefs: " + id, e); mCurMethodId = null; unbindCurrentMethodLocked(true); } + mShortcutInputMethodsAndSubtypes.clear(); } else { // There is no longer an input method set, so stop any current one. mCurMethodId = null; @@ -981,21 +1047,59 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - void setInputMethodLocked(String id) { + /* package */ void setInputMethodLocked(String id, int subtypeId) { InputMethodInfo info = mMethodMap.get(id); if (info == null) { throw new IllegalArgumentException("Unknown id: " + id); } if (id.equals(mCurMethodId)) { + InputMethodSubtype subtype = null; + if (subtypeId >= 0 && subtypeId < info.getSubtypeCount()) { + subtype = info.getSubtypeAt(subtypeId); + } + if (subtype != mCurrentSubtype) { + synchronized (mMethodMap) { + if (subtype != null) { + setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true); + } + if (mCurMethod != null) { + try { + final Configuration conf = mRes.getConfiguration(); + final boolean haveHardKeyboard = conf.keyboard + != Configuration.KEYBOARD_NOKEYS; + final boolean hardKeyShown = haveHardKeyboard + && conf.hardKeyboardHidden + != Configuration.HARDKEYBOARDHIDDEN_YES; + mImeWindowVis = (mInputShown || hardKeyShown) ? ( + InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE) + : 0; + mStatusBar.setImeWindowStatus(mCurToken, mImeWindowVis, + mBackDisposition); + // If subtype is null, try to find the most applicable one from + // getCurrentInputMethodSubtype. + if (subtype == null) { + subtype = getCurrentInputMethodSubtype(); + } + mCurMethod.changeInputMethodSubtype(subtype); + } catch (RemoteException e) { + return; + } + } + } + } return; } final long ident = Binder.clearCallingIdentity(); try { + // Set a subtype to this input method. + // subtypeId the name of a subtype which will be set. + setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false); + // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked() + // because mCurMethodId is stored as a history in + // setSelectedInputMethodAndSubtypeLocked(). mCurMethodId = id; - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD, id); if (ActivityManagerNative.isSystemReady()) { Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED); @@ -1089,9 +1193,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (!mIWindowManager.inputMethodClientHasFocus(client)) { if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid " + uid + ": " + client); + mImeWindowVis = 0; + mStatusBar.setImeWindowStatus(mCurToken, mImeWindowVis, + mBackDisposition); return false; } } catch (RemoteException e) { + mImeWindowVis = 0; + mStatusBar.setImeWindowStatus(mCurToken, mImeWindowVis, mBackDisposition); return false; } } @@ -1164,11 +1273,22 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } mCurFocusedWindow = windowToken; + // Should we auto-show the IME even if the caller has not + // specified what should be done with it? + // We only do this automatically if the window can resize + // to accommodate the IME (so what the user sees will give + // them good context without input information being obscured + // by the IME) or if running on a large screen where there + // is more room for the target window + IME. + final boolean doAutoShow = + (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) + == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE + || mRes.getConfiguration().isLayoutSizeAtLeast( + Configuration.SCREENLAYOUT_SIZE_LARGE); + switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) { case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: - if (!isTextEditor || (softInputMode & - WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) - != WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) { + if (!isTextEditor || !doAutoShow) { if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) { // There is no focus view, and this window will // be behind any soft input window, so hide the @@ -1176,13 +1296,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (DEBUG) Slog.v(TAG, "Unspecified window will hide input"); hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null); } - } else if (isTextEditor && (softInputMode & - WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) - == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE - && (softInputMode & - WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { + } else if (isTextEditor && doAutoShow && (softInputMode & + WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { // There is a focus view, and we are navigating forward // into the window, so show the input window for the user. + // We only do this automatically if the window an resize + // to accomodate the IME (so what the user sees will give + // them good context without input information being obscured + // by the IME) or if running on a large screen where there + // is more room for the target window + IME. if (DEBUG) Slog.v(TAG, "Unspecified window will show input"); showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null); } @@ -1223,15 +1345,72 @@ public class InputMethodManagerService extends IInputMethodManager.Stub synchronized (mMethodMap) { if (mCurClient == null || client == null || mCurClient.client.asBinder() != client.asBinder()) { - Slog.w(TAG, "Ignoring showInputMethodDialogFromClient of uid " + Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid " + Binder.getCallingUid() + ": " + client); } - mHandler.sendEmptyMessage(MSG_SHOW_IM_PICKER); + // Always call subtype picker, because subtype picker is a superset of input method + // picker. + mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER); } } public void setInputMethod(IBinder token, String id) { + setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID); + } + + public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) { + synchronized (mMethodMap) { + if (subtype != null) { + setInputMethodWithSubtypeId(token, id, getSubtypeIdFromHashCode( + mMethodMap.get(id), subtype.hashCode())); + } else { + setInputMethod(token, id); + } + } + } + + public void showInputMethodAndSubtypeEnablerFromClient( + IInputMethodClient client, String inputMethodId) { + synchronized (mMethodMap) { + if (mCurClient == null || client == null + || mCurClient.client.asBinder() != client.asBinder()) { + Slog.w(TAG, "Ignoring showInputMethodAndSubtypeEnablerFromClient of: " + client); + } + executeOrSendMessage(mCurMethod, mCaller.obtainMessageO( + MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId)); + } + } + + public boolean switchToLastInputMethod(IBinder token) { + synchronized (mMethodMap) { + final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked(); + if (lastIme == null) return false; + final InputMethodInfo lastImi = mMethodMap.get(lastIme.first); + if (lastImi == null) return false; + + final boolean imiIdIsSame = lastImi.getId().equals(mCurMethodId); + final int lastSubtypeHash = Integer.valueOf(lastIme.second); + // If the last IME is the same as the current IME and the last subtype is not defined, + // there is no need to switch to the last IME. + if (imiIdIsSame && lastSubtypeHash == NOT_A_SUBTYPE_ID) return false; + + int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID + : mCurrentSubtype.hashCode(); + if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) { + if (DEBUG) { + Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second + ", from: " + + mCurMethodId + ", " + currentSubtypeHash); + } + setInputMethodWithSubtypeId(token, lastIme.first, getSubtypeIdFromHashCode( + lastImi, lastSubtypeHash)); + return true; + } + return false; + } + } + + private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) { synchronized (mMethodMap) { if (token == null) { if (mContext.checkCallingOrSelfPermission( @@ -1249,7 +1428,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub long ident = Binder.clearCallingIdentity(); try { - setInputMethodLocked(id); + setInputMethodLocked(id, subtypeId); } finally { Binder.restoreCallingIdentity(ident); } @@ -1315,6 +1494,19 @@ public class InputMethodManagerService extends IInputMethodManager.Stub showInputMethodMenu(); return true; + case MSG_SHOW_IM_SUBTYPE_PICKER: + showInputMethodSubtypeMenu(); + return true; + + case MSG_SHOW_IM_SUBTYPE_ENABLER: + args = (HandlerCaller.SomeArgs)msg.obj; + showInputMethodAndSubtypeEnabler((String)args.arg1); + return true; + + case MSG_SHOW_IM_CONFIG: + showConfigureInputMethods(); + return true; + // --------------------------------------------------------- case MSG_UNBIND_INPUT: @@ -1413,8 +1605,17 @@ public class InputMethodManagerService extends IInputMethodManager.Stub & ApplicationInfo.FLAG_SYSTEM) != 0; } + private static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) { + ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); + final int subtypeCount = imi.getSubtypeCount(); + for (int i = 0; i < subtypeCount; ++i) { + subtypes.add(imi.getSubtypeAt(i)); + } + return subtypes; + } + private boolean chooseNewDefaultIMELocked() { - List<InputMethodInfo> enabled = getEnabledInputMethodListLocked(); + List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked(); if (enabled != null && enabled.size() > 0) { // We'd prefer to fall back on a system IME, since that is safer. int i=enabled.size(); @@ -1425,9 +1626,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub break; } } - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD, - enabled.get(i).getId()); + InputMethodInfo imi = enabled.get(i); + if (DEBUG) { + Slog.d(TAG, "New default IME was selected: " + imi.getId()); + } + resetSelectedInputMethodAndSubtypeLocked(imi.getId()); return true; } @@ -1440,7 +1643,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub map.clear(); PackageManager pm = mContext.getPackageManager(); - final Configuration config = mContext.getResources().getConfiguration(); + final Configuration config = mRes.getConfiguration(); final boolean haveHardKeyboard = config.keyboard == Configuration.KEYBOARD_QWERTY; String disabledSysImes = Settings.Secure.getString(mContext.getContentResolver(), Secure.DISABLED_SYSTEM_INPUT_METHODS); @@ -1498,7 +1701,34 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // ---------------------------------------------------------------------- - void showInputMethodMenu() { + private void showInputMethodMenu() { + showInputMethodMenuInternal(false); + } + + private void showInputMethodSubtypeMenu() { + showInputMethodMenuInternal(true); + } + + private void showInputMethodAndSubtypeEnabler(String inputMethodId) { + Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + | Intent.FLAG_ACTIVITY_CLEAR_TOP); + if (!TextUtils.isEmpty(inputMethodId)) { + intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId); + } + mContext.startActivity(intent); + } + + private void showConfigureInputMethods() { + Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mContext.startActivity(intent); + } + + private void showInputMethodMenuInternal(boolean showSubtypes) { if (DEBUG) Slog.v(TAG, "Show switching menu"); final Context context = mContext; @@ -1507,48 +1737,81 @@ public class InputMethodManagerService extends IInputMethodManager.Stub String lastInputMethodId = Settings.Secure.getString(context .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); + int lastInputMethodSubtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId); if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId); - final List<InputMethodInfo> immis = getEnabledInputMethodList(); - - if (immis == null) { - return; - } - synchronized (mMethodMap) { - hideInputMethodMenuLocked(); + final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis = + getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(); + if (immis == null || immis.size() == 0) { + return; + } - int N = immis.size(); + hideInputMethodMenuLocked(); - final Map<CharSequence, InputMethodInfo> imMap = - new TreeMap<CharSequence, InputMethodInfo>(Collator.getInstance()); + final Map<CharSequence, Pair<InputMethodInfo, Integer>> imMap = + new TreeMap<CharSequence, Pair<InputMethodInfo, Integer>>(Collator.getInstance()); - for (int i = 0; i < N; ++i) { - InputMethodInfo property = immis.get(i); - if (property == null) { - continue; + for (InputMethodInfo imi: immis.keySet()) { + if (imi == null) continue; + List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = immis.get(imi); + HashSet<String> enabledSubtypeSet = new HashSet<String>(); + for (InputMethodSubtype subtype: explicitlyOrImplicitlyEnabledSubtypeList) { + enabledSubtypeSet.add(String.valueOf(subtype.hashCode())); + } + ArrayList<InputMethodSubtype> subtypes = getSubtypes(imi); + CharSequence label = imi.loadLabel(pm); + if (showSubtypes && enabledSubtypeSet.size() > 0) { + final int subtypeCount = imi.getSubtypeCount(); + for (int j = 0; j < subtypeCount; ++j) { + InputMethodSubtype subtype = imi.getSubtypeAt(j); + if (enabledSubtypeSet.contains(String.valueOf(subtype.hashCode()))) { + CharSequence title; + int nameResId = subtype.getNameResId(); + String mode = subtype.getMode(); + if (nameResId != 0) { + title = pm.getText(imi.getPackageName(), nameResId, + imi.getServiceInfo().applicationInfo); + } else { + CharSequence language = subtype.getLocale(); + // TODO: Use more friendly Title and UI + title = label + "," + (mode == null ? "" : mode) + "," + + (language == null ? "" : language); + } + imMap.put(title, new Pair<InputMethodInfo, Integer>(imi, j)); + } + } + } else { + imMap.put(label, + new Pair<InputMethodInfo, Integer>(imi, NOT_A_SUBTYPE_ID)); } - imMap.put(property.loadLabel(pm), property); } - N = imMap.size(); + final int N = imMap.size(); mItems = imMap.keySet().toArray(new CharSequence[N]); - mIms = imMap.values().toArray(new InputMethodInfo[N]); - + mIms = new InputMethodInfo[N]; + mSubtypeIds = new int[N]; int checkedItem = 0; for (int i = 0; i < N; ++i) { + Pair<InputMethodInfo, Integer> value = imMap.get(mItems[i]); + mIms[i] = value.first; + mSubtypeIds[i] = value.second; if (mIms[i].getId().equals(lastInputMethodId)) { - checkedItem = i; - break; + int subtypeId = mSubtypeIds[i]; + if ((subtypeId == NOT_A_SUBTYPE_ID) + || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0) + || (subtypeId == lastInputMethodSubtypeId)) { + checkedItem = i; + } } } - + AlertDialog.OnClickListener adocl = new AlertDialog.OnClickListener() { public void onClick(DialogInterface dialog, int which) { hideInputMethodMenu(); } }; - + TypedArray a = context.obtainStyledAttributes(null, com.android.internal.R.styleable.DialogPreference, com.android.internal.R.attr.alertDialogStyle, 0); @@ -1562,23 +1825,38 @@ public class InputMethodManagerService extends IInputMethodManager.Stub .setIcon(a.getDrawable( com.android.internal.R.styleable.DialogPreference_dialogTitle)); a.recycle(); - + mDialogBuilder.setSingleChoiceItems(mItems, checkedItem, new AlertDialog.OnClickListener() { public void onClick(DialogInterface dialog, int which) { synchronized (mMethodMap) { - if (mIms == null || mIms.length <= which) { + if (mIms == null || mIms.length <= which + || mSubtypeIds == null || mSubtypeIds.length <= which) { return; } InputMethodInfo im = mIms[which]; + int subtypeId = mSubtypeIds[which]; hideInputMethodMenu(); if (im != null) { - setInputMethodLocked(im.getId()); + if ((subtypeId < 0) + || (subtypeId >= im.getSubtypeCount())) { + subtypeId = NOT_A_SUBTYPE_ID; + } + setInputMethodLocked(im.getId(), subtypeId); } } } }); + if (showSubtypes) { + mDialogBuilder.setPositiveButton( + com.android.internal.R.string.configure_input_methods, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + showConfigureInputMethods(); + } + }); + } mSwitchingDialog = mDialogBuilder.create(); mSwitchingDialog.getWindow().setType( WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG); @@ -1630,80 +1908,800 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // Make sure this is a valid input method. InputMethodInfo imm = mMethodMap.get(id); if (imm == null) { - if (imm == null) { - throw new IllegalArgumentException("Unknown id: " + mCurMethodId); + throw new IllegalArgumentException("Unknown id: " + mCurMethodId); + } + + List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings + .getEnabledInputMethodsAndSubtypeListLocked(); + + if (enabled) { + for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) { + if (pair.first.equals(id)) { + // We are enabling this input method, but it is already enabled. + // Nothing to do. The previous state was enabled. + return true; + } + } + mSettings.appendAndPutEnabledInputMethodLocked(id, false); + // Previous state was disabled. + return false; + } else { + StringBuilder builder = new StringBuilder(); + if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked( + builder, enabledInputMethodsList, id)) { + // Disabled input method is currently selected, switch to another one. + String selId = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.DEFAULT_INPUT_METHOD); + if (id.equals(selId) && !chooseNewDefaultIMELocked()) { + Slog.i(TAG, "Can't find new IME, unsetting the current input method."); + resetSelectedInputMethodAndSubtypeLocked(""); + } + // Previous state was enabled. + return true; + } else { + // We are disabling the input method but it is already disabled. + // Nothing to do. The previous state was disabled. + return false; + } + } + } + + private boolean canAddToLastInputMethod(InputMethodSubtype subtype) { + if (subtype == null) return true; + String[] extraValues = subtype.getExtraValue().split(","); + final int N = extraValues.length; + for (int i = 0; i < N; ++i) { + if (SUBTYPE_EXTRAVALUE_EXCLUDE_FROM_LAST_IME.equals(extraValues[i])) { + return false; } } + return true; + } - StringBuilder builder = new StringBuilder(256); + private void saveCurrentInputMethodAndSubtypeToHistory() { + String subtypeId = NOT_A_SUBTYPE_ID_STR; + if (mCurrentSubtype != null) { + subtypeId = String.valueOf(mCurrentSubtype.hashCode()); + } + if (canAddToLastInputMethod(mCurrentSubtype)) { + mSettings.addSubtypeToHistory(mCurMethodId, subtypeId); + } + } - boolean removed = false; - String firstId = null; + private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId, + boolean setSubtypeOnly) { + mOldSystemSettingsVersion = getSystemSettingsVersion(); + // Update the history of InputMethod and Subtype + saveCurrentInputMethodAndSubtypeToHistory(); - // Look through the currently enabled input methods. - String enabledStr = Settings.Secure.getString(mContext.getContentResolver(), - Settings.Secure.ENABLED_INPUT_METHODS); - if (enabledStr != null) { - final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter; - splitter.setString(enabledStr); - while (splitter.hasNext()) { - String curId = splitter.next(); - if (curId.equals(id)) { - if (enabled) { - // We are enabling this input method, but it is - // already enabled. Nothing to do. The previous - // state was enabled. - return true; + // Set Subtype here + if (imi == null || subtypeId < 0) { + mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID); + mCurrentSubtype = null; + } else { + if (subtypeId < imi.getSubtypeCount()) { + InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId); + mSettings.putSelectedSubtype(subtype.hashCode()); + mCurrentSubtype = subtype; + } else { + mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID); + mCurrentSubtype = null; + } + } + + if (!setSubtypeOnly) { + // Set InputMethod here + mSettings.putSelectedInputMethod(imi != null ? imi.getId() : ""); + } + } + + private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) { + InputMethodInfo imi = mMethodMap.get(newDefaultIme); + int lastSubtypeId = NOT_A_SUBTYPE_ID; + // newDefaultIme is empty when there is no candidate for the selected IME. + if (imi != null && !TextUtils.isEmpty(newDefaultIme)) { + String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme); + if (subtypeHashCode != null) { + try { + lastSubtypeId = getSubtypeIdFromHashCode( + imi, Integer.valueOf(subtypeHashCode)); + } catch (NumberFormatException e) { + Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e); + } + } + } + setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false); + } + + private int getSelectedInputMethodSubtypeId(String id) { + InputMethodInfo imi = mMethodMap.get(id); + if (imi == null) { + return NOT_A_SUBTYPE_ID; + } + int subtypeId; + try { + subtypeId = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE); + } catch (SettingNotFoundException e) { + return NOT_A_SUBTYPE_ID; + } + return getSubtypeIdFromHashCode(imi, subtypeId); + } + + private int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) { + if (imi != null) { + final int subtypeCount = imi.getSubtypeCount(); + for (int i = 0; i < subtypeCount; ++i) { + InputMethodSubtype ims = imi.getSubtypeAt(i); + if (subtypeHashCode == ims.hashCode()) { + return i; + } + } + } + return NOT_A_SUBTYPE_ID; + } + + private static ArrayList<InputMethodSubtype> getApplicableSubtypesLocked( + Resources res, List<InputMethodSubtype> subtypes) { + final String systemLocale = res.getConfiguration().locale.toString(); + if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>(); + HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap = + new HashMap<String, InputMethodSubtype>(); + final int N = subtypes.size(); + boolean containsKeyboardSubtype = false; + for (int i = 0; i < N; ++i) { + InputMethodSubtype subtype = subtypes.get(i); + final String locale = subtype.getLocale(); + final String mode = subtype.getMode(); + // When system locale starts with subtype's locale, that subtype will be applicable + // for system locale + // For instance, it's clearly applicable for cases like system locale = en_US and + // subtype = en, but it is not necessarily considered applicable for cases like system + // locale = en and subtype = en_US. + // We just call systemLocale.startsWith(locale) in this function because there is no + // need to find applicable subtypes aggressively unlike + // findLastResortApplicableSubtypeLocked. + if (systemLocale.startsWith(locale)) { + InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode); + // If more applicable subtypes are contained, skip. + if (applicableSubtype != null + && systemLocale.equals(applicableSubtype.getLocale())) continue; + applicableModeAndSubtypesMap.put(mode, subtype); + if (!containsKeyboardSubtype + && SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())) { + containsKeyboardSubtype = true; + } + } + } + ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>( + applicableModeAndSubtypesMap.values()); + if (!containsKeyboardSubtype) { + InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked( + res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true); + if (lastResortKeyboardSubtype != null) { + applicableSubtypes.add(lastResortKeyboardSubtype); + } + } + return applicableSubtypes; + } + + /** + * If there are no selected subtypes, tries finding the most applicable one according to the + * given locale. + * @param subtypes this function will search the most applicable subtype in subtypes + * @param mode subtypes will be filtered by mode + * @param locale subtypes will be filtered by locale + * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype, + * it will return the first subtype matched with mode + * @return the most applicable subtypeId + */ + private static InputMethodSubtype findLastResortApplicableSubtypeLocked( + Resources res, List<InputMethodSubtype> subtypes, String mode, String locale, + boolean canIgnoreLocaleAsLastResort) { + if (subtypes == null || subtypes.size() == 0) { + return null; + } + if (TextUtils.isEmpty(locale)) { + locale = res.getConfiguration().locale.toString(); + } + final String language = locale.substring(0, 2); + boolean partialMatchFound = false; + InputMethodSubtype applicableSubtype = null; + InputMethodSubtype firstMatchedModeSubtype = null; + final int N = subtypes.size(); + for (int i = 0; i < N; ++i) { + InputMethodSubtype subtype = subtypes.get(i); + final String subtypeLocale = subtype.getLocale(); + // An applicable subtype should match "mode". If mode is null, mode will be ignored, + // and all subtypes with all modes can be candidates. + if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) { + if (firstMatchedModeSubtype == null) { + firstMatchedModeSubtype = subtype; + } + if (locale.equals(subtypeLocale)) { + // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US") + applicableSubtype = subtype; + break; + } else if (!partialMatchFound && subtypeLocale.startsWith(language)) { + // Partial match (e.g. system locale is "en_US" and subtype locale is "en") + applicableSubtype = subtype; + partialMatchFound = true; + } + } + } + + if (applicableSubtype == null && canIgnoreLocaleAsLastResort) { + return firstMatchedModeSubtype; + } + + // The first subtype applicable to the system locale will be defined as the most applicable + // subtype. + if (DEBUG) { + if (applicableSubtype != null) { + Slog.d(TAG, "Applicable InputMethodSubtype was found: " + + applicableSubtype.getMode() + "," + applicableSubtype.getLocale()); + } + } + return applicableSubtype; + } + + // If there are no selected shortcuts, tries finding the most applicable ones. + private Pair<InputMethodInfo, InputMethodSubtype> + findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) { + List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked(); + InputMethodInfo mostApplicableIMI = null; + InputMethodSubtype mostApplicableSubtype = null; + boolean foundInSystemIME = false; + + // Search applicable subtype for each InputMethodInfo + for (InputMethodInfo imi: imis) { + final String imiId = imi.getId(); + if (foundInSystemIME && !imiId.equals(mCurMethodId)) { + continue; + } + InputMethodSubtype subtype = null; + final List<InputMethodSubtype> enabledSubtypes = + getEnabledInputMethodSubtypeList(imi, true); + // 1. Search by the current subtype's locale from enabledSubtypes. + if (mCurrentSubtype != null) { + subtype = findLastResortApplicableSubtypeLocked( + mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false); + } + // 2. Search by the system locale from enabledSubtypes. + // 3. Search the first enabled subtype matched with mode from enabledSubtypes. + if (subtype == null) { + subtype = findLastResortApplicableSubtypeLocked( + mRes, enabledSubtypes, mode, null, true); + } + // 4. Search by the current subtype's locale from all subtypes. + if (subtype == null && mCurrentSubtype != null) { + subtype = findLastResortApplicableSubtypeLocked( + mRes, getSubtypes(imi), mode, mCurrentSubtype.getLocale(), false); + } + // 5. Search by the system locale from all subtypes. + // 6. Search the first enabled subtype matched with mode from all subtypes. + if (subtype == null) { + subtype = findLastResortApplicableSubtypeLocked( + mRes, getSubtypes(imi), mode, null, true); + } + if (subtype != null) { + if (imiId.equals(mCurMethodId)) { + // The current input method is the most applicable IME. + mostApplicableIMI = imi; + mostApplicableSubtype = subtype; + break; + } else if (!foundInSystemIME) { + // The system input method is 2nd applicable IME. + mostApplicableIMI = imi; + mostApplicableSubtype = subtype; + if ((imi.getServiceInfo().applicationInfo.flags + & ApplicationInfo.FLAG_SYSTEM) != 0) { + foundInSystemIME = true; } + } + } + } + if (DEBUG) { + if (mostApplicableIMI != null) { + Slog.w(TAG, "Most applicable shortcut input method was:" + + mostApplicableIMI.getId()); + if (mostApplicableSubtype != null) { + Slog.w(TAG, "Most applicable shortcut input method subtype was:" + + "," + mostApplicableSubtype.getMode() + "," + + mostApplicableSubtype.getLocale()); + } + } + } + if (mostApplicableIMI != null) { + return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI, + mostApplicableSubtype); + } else { + return null; + } + } + + private static long getSystemSettingsVersion() { + return SystemProperties.getLong(Settings.Secure.SYS_PROP_SETTING_VERSION, 0); + } + + /** + * @return Return the current subtype of this input method. + */ + public InputMethodSubtype getCurrentInputMethodSubtype() { + boolean subtypeIsSelected = false; + try { + subtypeIsSelected = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE) != NOT_A_SUBTYPE_ID; + } catch (SettingNotFoundException e) { + } + synchronized (mMethodMap) { + if (!subtypeIsSelected || mCurrentSubtype == null) { + String lastInputMethodId = Settings.Secure.getString( + mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); + int subtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId); + if (subtypeId == NOT_A_SUBTYPE_ID) { + InputMethodInfo imi = mMethodMap.get(lastInputMethodId); + if (imi != null) { + // If there are no selected subtypes, the framework will try to find + // the most applicable subtype from explicitly or implicitly enabled + // subtypes. + List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes = + getEnabledInputMethodSubtypeList(imi, true); + // If there is only one explicitly or implicitly enabled subtype, + // just returns it. + if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) { + mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0); + } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) { + mCurrentSubtype = findLastResortApplicableSubtypeLocked( + mRes, explicitlyOrImplicitlyEnabledSubtypes, + SUBTYPE_MODE_KEYBOARD, null, true); + if (mCurrentSubtype == null) { + mCurrentSubtype = findLastResortApplicableSubtypeLocked( + mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null, + true); + } + } + } + } else { + mCurrentSubtype = + getSubtypes(mMethodMap.get(lastInputMethodId)).get(subtypeId); + } + } + return mCurrentSubtype; + } + } + + private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi, + InputMethodSubtype subtype) { + if (mShortcutInputMethodsAndSubtypes.containsKey(imi)) { + mShortcutInputMethodsAndSubtypes.get(imi).add(subtype); + } else { + ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); + subtypes.add(subtype); + mShortcutInputMethodsAndSubtypes.put(imi, subtypes); + } + } + + // TODO: We should change the return type from List to List<Parcelable> + public List getShortcutInputMethodsAndSubtypes() { + synchronized (mMethodMap) { + ArrayList<Object> ret = new ArrayList<Object>(); + if (mShortcutInputMethodsAndSubtypes.size() == 0) { + // If there are no selected shortcut subtypes, the framework will try to find + // the most applicable subtype from all subtypes whose mode is + // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode. + Pair<InputMethodInfo, InputMethodSubtype> info = + findLastResortApplicableShortcutInputMethodAndSubtypeLocked( + SUBTYPE_MODE_VOICE); + if (info != null) { + ret.add(info.first); + ret.add(info.second); + } + return ret; + } + for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) { + ret.add(imi); + for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) { + ret.add(subtype); + } + } + return ret; + } + } + + public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) { + synchronized (mMethodMap) { + if (subtype != null && mCurMethodId != null) { + InputMethodInfo imi = mMethodMap.get(mCurMethodId); + int subtypeId = getSubtypeIdFromHashCode(imi, subtype.hashCode()); + if (subtypeId != NOT_A_SUBTYPE_ID) { + setInputMethodLocked(mCurMethodId, subtypeId); + return true; + } + } + return false; + } + } + + /** + * Utility class for putting and getting settings for InputMethod + * TODO: Move all putters and getters of settings to this class. + */ + private static class InputMethodSettings { + // The string for enabled input method is saved as follows: + // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0") + private static final char INPUT_METHOD_SEPARATER = ':'; + private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';'; + private final TextUtils.SimpleStringSplitter mInputMethodSplitter = + new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER); + + private final TextUtils.SimpleStringSplitter mSubtypeSplitter = + new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER); + + private final Resources mRes; + private final ContentResolver mResolver; + private final HashMap<String, InputMethodInfo> mMethodMap; + private final ArrayList<InputMethodInfo> mMethodList; + + private String mEnabledInputMethodsStrCache; + + private static void buildEnabledInputMethodsSettingString( + StringBuilder builder, Pair<String, ArrayList<String>> pair) { + String id = pair.first; + ArrayList<String> subtypes = pair.second; + builder.append(id); + // Inputmethod and subtypes are saved in the settings as follows: + // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1 + for (String subtypeId: subtypes) { + builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId); + } + } + + public InputMethodSettings( + Resources res, ContentResolver resolver, + HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList) { + mRes = res; + mResolver = resolver; + mMethodMap = methodMap; + mMethodList = methodList; + } + + public List<InputMethodInfo> getEnabledInputMethodListLocked() { + return createEnabledInputMethodListLocked( + getEnabledInputMethodsAndSubtypeListLocked()); + } + + public List<Pair<InputMethodInfo, ArrayList<String>>> + getEnabledInputMethodAndSubtypeHashCodeListLocked() { + return createEnabledInputMethodAndSubtypeHashCodeListLocked( + getEnabledInputMethodsAndSubtypeListLocked()); + } + + public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked( + InputMethodInfo imi) { + List<Pair<String, ArrayList<String>>> imsList = + getEnabledInputMethodsAndSubtypeListLocked(); + ArrayList<InputMethodSubtype> enabledSubtypes = + new ArrayList<InputMethodSubtype>(); + if (imi != null) { + for (Pair<String, ArrayList<String>> imsPair : imsList) { + InputMethodInfo info = mMethodMap.get(imsPair.first); + if (info != null && info.getId().equals(imi.getId())) { + final int subtypeCount = info.getSubtypeCount(); + for (int i = 0; i < subtypeCount; ++i) { + InputMethodSubtype ims = info.getSubtypeAt(i); + for (String s: imsPair.second) { + if (String.valueOf(ims.hashCode()).equals(s)) { + enabledSubtypes.add(ims); + } + } + } + break; + } + } + } + return enabledSubtypes; + } + + // At the initial boot, the settings for input methods are not set, + // so we need to enable IME in that case. + public void enableAllIMEsIfThereIsNoEnabledIME() { + if (TextUtils.isEmpty(getEnabledInputMethodsStr())) { + StringBuilder sb = new StringBuilder(); + final int N = mMethodList.size(); + for (int i = 0; i < N; i++) { + InputMethodInfo imi = mMethodList.get(i); + Slog.i(TAG, "Adding: " + imi.getId()); + if (i > 0) sb.append(':'); + sb.append(imi.getId()); + } + putEnabledInputMethodsStr(sb.toString()); + } + } + + private List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() { + ArrayList<Pair<String, ArrayList<String>>> imsList + = new ArrayList<Pair<String, ArrayList<String>>>(); + final String enabledInputMethodsStr = getEnabledInputMethodsStr(); + if (TextUtils.isEmpty(enabledInputMethodsStr)) { + return imsList; + } + mInputMethodSplitter.setString(enabledInputMethodsStr); + while (mInputMethodSplitter.hasNext()) { + String nextImsStr = mInputMethodSplitter.next(); + mSubtypeSplitter.setString(nextImsStr); + if (mSubtypeSplitter.hasNext()) { + ArrayList<String> subtypeHashes = new ArrayList<String>(); + // The first element is ime id. + String imeId = mSubtypeSplitter.next(); + while (mSubtypeSplitter.hasNext()) { + subtypeHashes.add(mSubtypeSplitter.next()); + } + imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes)); + } + } + return imsList; + } + + public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) { + if (reloadInputMethodStr) { + getEnabledInputMethodsStr(); + } + if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) { + // Add in the newly enabled input method. + putEnabledInputMethodsStr(id); + } else { + putEnabledInputMethodsStr( + mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id); + } + } + + /** + * Build and put a string of EnabledInputMethods with removing specified Id. + * @return the specified id was removed or not. + */ + public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked( + StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) { + boolean isRemoved = false; + boolean needsAppendSeparator = false; + for (Pair<String, ArrayList<String>> ims: imsList) { + String curId = ims.first; + if (curId.equals(id)) { // We are disabling this input method, and it is // currently enabled. Skip it to remove from the // new list. - removed = true; - } else if (!enabled) { - // We are building a new list of input methods that - // doesn't contain the given one. - if (firstId == null) firstId = curId; - if (builder.length() > 0) builder.append(':'); - builder.append(curId); + isRemoved = true; + } else { + if (needsAppendSeparator) { + builder.append(INPUT_METHOD_SEPARATER); + } else { + needsAppendSeparator = true; + } + buildEnabledInputMethodsSettingString(builder, ims); } } + if (isRemoved) { + // Update the setting with the new list of input methods. + putEnabledInputMethodsStr(builder.toString()); + } + return isRemoved; } - if (!enabled) { - if (!removed) { - // We are disabling the input method but it is already - // disabled. Nothing to do. The previous state was - // disabled. - return false; + private List<InputMethodInfo> createEnabledInputMethodListLocked( + List<Pair<String, ArrayList<String>>> imsList) { + final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>(); + for (Pair<String, ArrayList<String>> ims: imsList) { + InputMethodInfo info = mMethodMap.get(ims.first); + if (info != null) { + res.add(info); + } } - // Update the setting with the new list of input methods. - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.ENABLED_INPUT_METHODS, builder.toString()); - // We the disabled input method is currently selected, switch - // to another one. - String selId = Settings.Secure.getString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD); - if (id.equals(selId)) { - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD, - firstId != null ? firstId : ""); + return res; + } + + private List<Pair<InputMethodInfo, ArrayList<String>>> + createEnabledInputMethodAndSubtypeHashCodeListLocked( + List<Pair<String, ArrayList<String>>> imsList) { + final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res + = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>(); + for (Pair<String, ArrayList<String>> ims : imsList) { + InputMethodInfo info = mMethodMap.get(ims.first); + if (info != null) { + res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second)); + } } - // Previous state was enabled. - return true; + return res; } - // Add in the newly enabled input method. - if (enabledStr == null || enabledStr.length() == 0) { - enabledStr = id; - } else { - enabledStr = enabledStr + ':' + id; + private void putEnabledInputMethodsStr(String str) { + Settings.Secure.putString(mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str); + mEnabledInputMethodsStrCache = str; } - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.ENABLED_INPUT_METHODS, enabledStr); + private String getEnabledInputMethodsStr() { + mEnabledInputMethodsStrCache = Settings.Secure.getString( + mResolver, Settings.Secure.ENABLED_INPUT_METHODS); + if (DEBUG) { + Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache); + } + return mEnabledInputMethodsStrCache; + } - // Previous state was disabled. - return false; + private void saveSubtypeHistory( + List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) { + StringBuilder builder = new StringBuilder(); + boolean isImeAdded = false; + if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) { + builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append( + newSubtypeId); + isImeAdded = true; + } + for (Pair<String, String> ime: savedImes) { + String imeId = ime.first; + String subtypeId = ime.second; + if (TextUtils.isEmpty(subtypeId)) { + subtypeId = NOT_A_SUBTYPE_ID_STR; + } + if (isImeAdded) { + builder.append(INPUT_METHOD_SEPARATER); + } else { + isImeAdded = true; + } + builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append( + subtypeId); + } + // Remove the last INPUT_METHOD_SEPARATER + putSubtypeHistoryStr(builder.toString()); + } + + public void addSubtypeToHistory(String imeId, String subtypeId) { + List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked(); + for (Pair<String, String> ime: subtypeHistory) { + if (ime.first.equals(imeId)) { + if (DEBUG) { + Slog.v(TAG, "Subtype found in the history: " + imeId + ", " + + ime.second); + } + // We should break here + subtypeHistory.remove(ime); + break; + } + } + if (DEBUG) { + Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId); + } + saveSubtypeHistory(subtypeHistory, imeId, subtypeId); + } + + private void putSubtypeHistoryStr(String str) { + if (DEBUG) { + Slog.d(TAG, "putSubtypeHistoryStr: " + str); + } + Settings.Secure.putString( + mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str); + } + + public Pair<String, String> getLastInputMethodAndSubtypeLocked() { + // Gets the first one from the history + return getLastSubtypeForInputMethodLockedInternal(null); + } + + public String getLastSubtypeForInputMethodLocked(String imeId) { + Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId); + if (ime != null) { + return ime.second; + } else { + return null; + } + } + + private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) { + List<Pair<String, ArrayList<String>>> enabledImes = + getEnabledInputMethodsAndSubtypeListLocked(); + List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked(); + for (Pair<String, String> imeAndSubtype: subtypeHistory) { + final String imeInTheHistory = imeAndSubtype.first; + // If imeId is empty, returns the first IME and subtype in the history + if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) { + final String subtypeInTheHistory = imeAndSubtype.second; + final String subtypeHashCode = + getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked( + enabledImes, imeInTheHistory, subtypeInTheHistory); + if (!TextUtils.isEmpty(subtypeHashCode)) { + if (DEBUG) { + Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode); + } + return new Pair<String, String>(imeInTheHistory, subtypeHashCode); + } + } + } + if (DEBUG) { + Slog.d(TAG, "No enabled IME found in the history"); + } + return null; + } + + private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String, + ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) { + for (Pair<String, ArrayList<String>> enabledIme: enabledImes) { + if (enabledIme.first.equals(imeId)) { + final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second; + if (explicitlyEnabledSubtypes.size() == 0) { + // If there are no explicitly enabled subtypes, applicable subtypes are + // enabled implicitly. + InputMethodInfo ime = mMethodMap.get(imeId); + // If IME is enabled and no subtypes are enabled, applicable subtypes + // are enabled implicitly, so needs to treat them to be enabled. + if (ime != null && ime.getSubtypeCount() > 0) { + List<InputMethodSubtype> implicitlySelectedSubtypes = + getApplicableSubtypesLocked(mRes, getSubtypes(ime)); + if (implicitlySelectedSubtypes != null) { + final int N = implicitlySelectedSubtypes.size(); + for (int i = 0; i < N; ++i) { + final InputMethodSubtype st = implicitlySelectedSubtypes.get(i); + if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) { + return subtypeHashCode; + } + } + } + } + } else { + for (String s: explicitlyEnabledSubtypes) { + if (s.equals(subtypeHashCode)) { + // If both imeId and subtypeId are enabled, return subtypeId. + return s; + } + } + } + // If imeId was enabled but subtypeId was disabled. + return NOT_A_SUBTYPE_ID_STR; + } + } + // If both imeId and subtypeId are disabled, return null + return null; + } + + private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() { + ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>(); + final String subtypeHistoryStr = getSubtypeHistoryStr(); + if (TextUtils.isEmpty(subtypeHistoryStr)) { + return imsList; + } + mInputMethodSplitter.setString(subtypeHistoryStr); + while (mInputMethodSplitter.hasNext()) { + String nextImsStr = mInputMethodSplitter.next(); + mSubtypeSplitter.setString(nextImsStr); + if (mSubtypeSplitter.hasNext()) { + String subtypeId = NOT_A_SUBTYPE_ID_STR; + // The first element is ime id. + String imeId = mSubtypeSplitter.next(); + while (mSubtypeSplitter.hasNext()) { + subtypeId = mSubtypeSplitter.next(); + break; + } + imsList.add(new Pair<String, String>(imeId, subtypeId)); + } + } + return imsList; + } + + private String getSubtypeHistoryStr() { + if (DEBUG) { + Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getString( + mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY)); + } + return Settings.Secure.getString( + mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY); + } + + public void putSelectedInputMethod(String imeId) { + Settings.Secure.putString(mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId); + } + + public void putSelectedSubtype(int subtypeId) { + Settings.Secure.putInt( + mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId); + } } // ---------------------------------------------------------------------- diff --git a/services/java/com/android/server/InputWindow.java b/services/java/com/android/server/InputWindow.java index befc770..2c2cdfe 100644 --- a/services/java/com/android/server/InputWindow.java +++ b/services/java/com/android/server/InputWindow.java @@ -16,68 +16,63 @@ package com.android.server; +import android.graphics.Region; import android.view.InputChannel; /** * Describes input-related window properties for use by the input dispatcher. - * * @hide */ public final class InputWindow { + // The window handle. + public InputWindowHandle inputWindowHandle; + // The input channel associated with the window. public InputChannel inputChannel; - + // The window name. public String name; - + // Window layout params attributes. (WindowManager.LayoutParams) public int layoutParamsFlags; public int layoutParamsType; - + // Dispatching timeout. public long dispatchingTimeoutNanos; - - // Window frame area. + + // Window frame. public int frameLeft; public int frameTop; public int frameRight; public int frameBottom; - - // Window visible frame area. - public int visibleFrameLeft; - public int visibleFrameTop; - public int visibleFrameRight; - public int visibleFrameBottom; - - // Window touchable area. - public int touchableAreaLeft; - public int touchableAreaTop; - public int touchableAreaRight; - public int touchableAreaBottom; - + + // Window touchable region. + public final Region touchableRegion = new Region(); + // Window is visible. public boolean visible; - + // Window can receive keys. public boolean canReceiveKeys; - + // Window has focus. public boolean hasFocus; - + // Window has wallpaper. (window is the current wallpaper target) public boolean hasWallpaper; - + // Input event dispatching is paused. public boolean paused; - + // Window layer. public int layer; - + // Id of process and user that owns the window. public int ownerPid; public int ownerUid; - + public void recycle() { + inputWindowHandle = null; inputChannel = null; } } diff --git a/services/java/com/android/server/InputWindowHandle.java b/services/java/com/android/server/InputWindowHandle.java new file mode 100644 index 0000000..4b92939 --- /dev/null +++ b/services/java/com/android/server/InputWindowHandle.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2011 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.server; + +import android.view.WindowManagerPolicy; + +/** + * Functions as a handle for a window that can receive input. + * Enables the native input dispatcher to refer indirectly to the window manager's window state. + * @hide + */ +public final class InputWindowHandle { + // Pointer to the native input window handle. + // This field is lazily initialized via JNI. + @SuppressWarnings("unused") + private int ptr; + + // The input application handle. + public final InputApplicationHandle inputApplicationHandle; + + // The window manager's window state. + public final WindowManagerPolicy.WindowState windowState; + + private native void nativeDispose(); + + public InputWindowHandle(InputApplicationHandle inputApplicationHandle, + WindowManagerPolicy.WindowState windowState) { + this.inputApplicationHandle = inputApplicationHandle; + this.windowState = windowState; + } + + @Override + protected void finalize() throws Throwable { + nativeDispose(); + super.finalize(); + } +} diff --git a/services/java/com/android/server/LightsService.java b/services/java/com/android/server/LightsService.java index 9b57735..21f2bcf 100644 --- a/services/java/com/android/server/LightsService.java +++ b/services/java/com/android/server/LightsService.java @@ -24,12 +24,12 @@ import android.os.ServiceManager; import android.os.Message; import android.util.Slog; -import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; public class LightsService { private static final String TAG = "LightsService"; + private static final boolean DEBUG = false; static final int LIGHT_ID_BACKLIGHT = 0; static final int LIGHT_ID_KEYBOARD = 1; @@ -115,6 +115,8 @@ public class LightsService { private void setLightLocked(int color, int mode, int onMS, int offMS, int brightnessMode) { if (color != mColor || mode != mMode || onMS != mOnMS || offMS != mOffMS) { + if (DEBUG) Slog.v(TAG, "setLight #" + mId + ": color=#" + + Integer.toHexString(color)); mColor = color; mMode = mode; mOnMS = onMS; diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index 10107c6..656ec4d 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -479,14 +479,17 @@ public class LocationManagerService extends ILocationManager.Stub implements Run mEnabledProviders.add(passiveProvider.getName()); // initialize external network location and geocoder services - if (mNetworkLocationProviderPackageName != null) { + PackageManager pm = mContext.getPackageManager(); + if (mNetworkLocationProviderPackageName != null && + pm.resolveService(new Intent(mNetworkLocationProviderPackageName), 0) != null) { mNetworkLocationProvider = new LocationProviderProxy(mContext, LocationManager.NETWORK_PROVIDER, mNetworkLocationProviderPackageName, mLocationHandler); addProvider(mNetworkLocationProvider); } - if (mGeocodeProviderPackageName != null) { + if (mGeocodeProviderPackageName != null && + pm.resolveService(new Intent(mGeocodeProviderPackageName), 0) != null) { mGeocodeProvider = new GeocoderProxy(mContext, mGeocodeProviderPackageName); } diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java index e6e3e5f..436eff0 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -19,6 +19,7 @@ package com.android.server; import com.android.internal.app.IMediaContainerService; import com.android.server.am.ActivityManagerService; +import android.Manifest; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -45,6 +46,7 @@ import android.os.storage.IMountShutdownObserver; import android.os.storage.IObbActionListener; import android.os.storage.OnObbStateChangeListener; import android.os.storage.StorageResultCode; +import android.text.TextUtils; import android.util.Slog; import java.io.FileDescriptor; @@ -73,8 +75,8 @@ import javax.crypto.spec.PBEKeySpec; * @hide - Applications should use android.os.storage.StorageManager * to access the MountService. */ -class MountService extends IMountService.Stub - implements INativeDaemonConnectorCallbacks { +class MountService extends IMountService.Stub implements INativeDaemonConnectorCallbacks { + private static final boolean LOCAL_LOGD = false; private static final boolean DEBUG_UNMOUNT = false; private static final boolean DEBUG_EVENTS = false; @@ -151,6 +153,8 @@ class MountService extends IMountService.Stub private boolean mBooted = false; private boolean mReady = false; private boolean mSendUmsConnectedOnBoot = false; + // true if we should fake MEDIA_MOUNTED state for external storage + private boolean mEmulateExternalStorage = false; /** * Private hash of currently mounted secure containers. @@ -332,6 +336,7 @@ class MountService extends IMountService.Stub super(l); } + @Override public void handleMessage(Message msg) { switch (msg.what) { case H_UNMOUNT_PM_UPDATE: { @@ -425,6 +430,7 @@ class MountService extends IMountService.Stub } private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); @@ -440,12 +446,15 @@ class MountService extends IMountService.Stub return; } new Thread() { + @Override public void run() { try { String path = Environment.getExternalStorageDirectory().getPath(); String state = getVolumeState(path); - if (state.equals(Environment.MEDIA_UNMOUNTED)) { + if (mEmulateExternalStorage) { + notifyVolumeStateChange(null, path, VolumeState.NoMedia, VolumeState.Mounted); + } else if (state.equals(Environment.MEDIA_UNMOUNTED)) { int rc = doMountVolume(path); if (rc != StorageResultCode.OperationSucceeded) { Slog.e(TAG, String.format("Boot-time mount failed (%d)", rc)); @@ -516,21 +525,21 @@ class MountService extends IMountService.Stub Slog.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state)); return; } + // Update state on PackageManager, but only of real events + if (!mEmulateExternalStorage) { + if (Environment.MEDIA_UNMOUNTED.equals(state)) { + mPms.updateExternalMediaStatus(false, false); - if (Environment.MEDIA_UNMOUNTED.equals(state)) { - // Tell the package manager the media is gone. - mPms.updateExternalMediaStatus(false, false); - - /* - * Some OBBs might have been unmounted when this volume was - * unmounted, so send a message to the handler to let it know to - * remove those from the list of mounted OBBS. - */ - mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_FLUSH_MOUNT_STATE, - path)); - } else if (Environment.MEDIA_MOUNTED.equals(state)) { - // Tell the package manager the media is available for use. - mPms.updateExternalMediaStatus(true, false); + /* + * Some OBBs might have been unmounted when this volume was + * unmounted, so send a message to the handler to let it know to + * remove those from the list of mounted OBBS. + */ + mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_FLUSH_MOUNT_STATE, + path)); + } else if (Environment.MEDIA_MOUNTED.equals(state)) { + mPms.updateExternalMediaStatus(true, false); + } } String oldState = mLegacyState; @@ -561,6 +570,7 @@ class MountService extends IMountService.Stub * we need to do our work in a new thread. */ new Thread() { + @Override public void run() { /** * Determine media state and UMS detection status @@ -610,7 +620,7 @@ class MountService extends IMountService.Stub Slog.w(TAG, "Failed to get share availability"); } /* - * Now that we've done our initialization, release + * Now that we've done our initialization, release * the hounds! */ mReady = true; @@ -674,6 +684,7 @@ class MountService extends IMountService.Stub if (code == VoldResponseCode.VolumeDiskInserted) { new Thread() { + @Override public void run() { try { int rc; @@ -1003,6 +1014,7 @@ class MountService extends IMountService.Stub * USB mass storage disconnected while enabled */ new Thread() { + @Override public void run() { try { int rc; @@ -1040,6 +1052,13 @@ class MountService extends IMountService.Stub public MountService(Context context) { mContext = context; + mEmulateExternalStorage = context.getResources().getBoolean( + com.android.internal.R.bool.config_emulateExternalStorage); + if (mEmulateExternalStorage) { + Slog.d(TAG, "using emulated external storage"); + mLegacyState = Environment.MEDIA_MOUNTED; + } + // XXX: This will go away soon in favor of IMountServiceObserver mPms = (PackageManagerService) ServiceManager.getService("package"); @@ -1220,7 +1239,7 @@ class MountService extends IMountService.Stub waitForReady(); return doGetVolumeShared(Environment.getExternalStorageDirectory().getPath(), "ums"); } - + /** * @return state of the volume at the specified mount point */ @@ -1237,6 +1256,10 @@ class MountService extends IMountService.Stub return mLegacyState; } + public boolean isExternalStorageEmulated() { + return mEmulateExternalStorage; + } + public int mountVolume(String path) { validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); @@ -1386,7 +1409,7 @@ class MountService extends IMountService.Stub return rc; } - + public int mountSecureContainer(String id, String key, int ownerUid) { validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT); waitForReady(); @@ -1474,7 +1497,7 @@ class MountService extends IMountService.Stub synchronized (mAsecMountSet) { /* - * Because a mounted container has active internal state which cannot be + * Because a mounted container has active internal state which cannot be * changed while active, we must ensure both ids are not currently mounted. */ if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) { @@ -1609,6 +1632,101 @@ class MountService extends IMountService.Stub Slog.i(TAG, "Send to OBB handler: " + action.toString()); } + public int decryptStorage(String password) { + if (TextUtils.isEmpty(password)) { + throw new IllegalArgumentException("password cannot be empty"); + } + + mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, + "no permission to access the crypt keeper"); + + waitForReady(); + + if (DEBUG_EVENTS) { + Slog.i(TAG, "decrypting storage..."); + } + + try { + ArrayList<String> rsp = mConnector.doCommand("cryptfs checkpw " + password); + String[] tokens = rsp.get(0).split(" "); + + if (tokens == null || tokens.length != 2) { + return -1; + } + + int code = Integer.parseInt(tokens[1]); + + if (code == 0) { + // Decrypt was successful. Post a delayed message before restarting in order + // to let the UI to clear itself + mHandler.postDelayed(new Runnable() { + public void run() { + mConnector.doCommand(String.format("cryptfs restart")); + } + }, 1000); // 1 second + } + + return code; + } catch (NativeDaemonConnectorException e) { + // Decryption failed + return e.getCode(); + } + } + + public int encryptStorage(String password) { + if (TextUtils.isEmpty(password)) { + throw new IllegalArgumentException("password cannot be empty"); + } + + mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, + "no permission to access the crypt keeper"); + + waitForReady(); + + if (DEBUG_EVENTS) { + Slog.i(TAG, "encrypting storage..."); + } + + try { + mConnector.doCommand(String.format("cryptfs enablecrypto inplace %s", password)); + } catch (NativeDaemonConnectorException e) { + // Encryption failed + return e.getCode(); + } + + return 0; + } + + public int changeEncryptionPassword(String password) { + if (TextUtils.isEmpty(password)) { + throw new IllegalArgumentException("password cannot be empty"); + } + + mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, + "no permission to access the crypt keeper"); + + waitForReady(); + + if (DEBUG_EVENTS) { + Slog.i(TAG, "changing encryption password..."); + } + + try { + ArrayList<String> response = mConnector.doCommand("cryptfs changepw " + password); + + String[] tokens = response.get(0).split(" "); + + if (tokens == null || tokens.length != 2) { + return -1; + } + + return Integer.parseInt(tokens[1]); + } catch (NativeDaemonConnectorException e) { + // Encryption failed + return e.getCode(); + } + } + private void addObbStateLocked(ObbState obbState) throws RemoteException { final IBinder binder = obbState.getBinder(); List<ObbState> obbStates = mObbMounts.get(binder); @@ -1896,6 +2014,7 @@ class MountService extends IMountService.Stub mKey = key; } + @Override public void handleExecute() throws IOException, RemoteException { waitForReady(); warnOnNotMounted(); @@ -1976,6 +2095,7 @@ class MountService extends IMountService.Stub } } + @Override public void handleError() { sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); } @@ -2005,6 +2125,7 @@ class MountService extends IMountService.Stub mForceUnmount = force; } + @Override public void handleExecute() throws IOException { waitForReady(); warnOnNotMounted(); @@ -2059,6 +2180,7 @@ class MountService extends IMountService.Stub } } + @Override public void handleError() { sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); } diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index f0acdc0..152605f 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -219,28 +219,6 @@ class NetworkManagementService extends INetworkManagementService.Stub { } } - private static int stringToIpAddr(String addrString) throws UnknownHostException { - try { - String[] parts = addrString.split("\\."); - if (parts.length != 4) { - throw new UnknownHostException(addrString); - } - - int a = Integer.parseInt(parts[0]) ; - int b = Integer.parseInt(parts[1]) << 8; - int c = Integer.parseInt(parts[2]) << 16; - int d = Integer.parseInt(parts[3]) << 24; - - return a | b | c | d; - } catch (NumberFormatException ex) { - throw new UnknownHostException(addrString); - } - } - - public static String intToIpString(int i) { - return ((i >> 24 ) & 0xFF) + "." + ((i >> 16 ) & 0xFF) + "." + ((i >> 8 ) & 0xFF) + "." + - (i & 0xFF); - } // // INetworkManagementService members @@ -288,18 +266,17 @@ class NetworkManagementService extends INetworkManagementService.Stub { cfg = new InterfaceConfiguration(); cfg.hwAddr = st.nextToken(" "); try { - cfg.ipAddr = stringToIpAddr(st.nextToken(" ")); + cfg.addr = InetAddress.getByName(st.nextToken(" ")); } catch (UnknownHostException uhe) { Slog.e(TAG, "Failed to parse ipaddr", uhe); - cfg.ipAddr = 0; } try { - cfg.netmask = stringToIpAddr(st.nextToken(" ")); + cfg.mask = InetAddress.getByName(st.nextToken(" ")); } catch (UnknownHostException uhe) { Slog.e(TAG, "Failed to parse netmask", uhe); - cfg.netmask = 0; } + cfg.interfaceFlags = st.nextToken("]").trim() +"]"; } catch (NoSuchElementException nsee) { throw new IllegalStateException( @@ -312,12 +289,13 @@ class NetworkManagementService extends INetworkManagementService.Stub { public void setInterfaceConfig( String iface, InterfaceConfiguration cfg) throws IllegalStateException { String cmd = String.format("interface setcfg %s %s %s %s", iface, - intToIpString(cfg.ipAddr), intToIpString(cfg.netmask), cfg.interfaceFlags); + cfg.addr.getHostAddress(), cfg.mask.getHostAddress(), + cfg.interfaceFlags); try { mConnector.doCommand(cmd); } catch (NativeDaemonConnectorException e) { throw new IllegalStateException( - "Unable to communicate with native daemon to interface setcfg"); + "Unable to communicate with native daemon to interface setcfg - " + e); } } diff --git a/services/java/com/android/server/NetworkTimeUpdateService.java b/services/java/com/android/server/NetworkTimeUpdateService.java new file mode 100644 index 0000000..52f84eb --- /dev/null +++ b/services/java/com/android/server/NetworkTimeUpdateService.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2010 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.server; + +import com.android.internal.telephony.TelephonyIntents; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.ContentObserver; +import android.net.SntpClient; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.SystemClock; +import android.provider.Settings; +import android.util.Log; +import android.util.Slog; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; + +/** + * Monitors the network time and updates the system time if it is out of sync + * and there hasn't been any NITZ update from the carrier recently. + * If looking up the network time fails for some reason, it tries a few times with a short + * interval and then resets to checking on longer intervals. + * <p> + * If the user enables AUTO_TIME, it will check immediately for the network time, if NITZ wasn't + * available. + * </p> + */ +public class NetworkTimeUpdateService { + + private static final String TAG = "NetworkTimeUpdateService"; + private static final boolean DBG = false; + + private static final int EVENT_AUTO_TIME_CHANGED = 1; + private static final int EVENT_POLL_NETWORK_TIME = 2; + + /** Normal polling frequency */ + private static final long POLLING_INTERVAL_MS = 24L * 60 * 60 * 1000; // 24 hrs + /** Try-again polling interval, in case the network request failed */ + private static final long POLLING_INTERVAL_SHORTER_MS = 60 * 1000L; // 60 seconds + /** Number of times to try again */ + private static final int TRY_AGAIN_TIMES_MAX = 3; + /** How long to wait for the NTP server to respond. */ + private static final int MAX_NTP_FETCH_WAIT_MS = 20 * 1000; + /** If the time difference is greater than this threshold, then update the time. */ + private static final int TIME_ERROR_THRESHOLD_MS = 5 * 1000; + + private static final String ACTION_POLL = + "com.android.server.NetworkTimeUpdateService.action.POLL"; + private static final String PROPERTIES_FILE = "/etc/gps.conf"; + private static int POLL_REQUEST = 0; + + private static final long NOT_SET = -1; + private long mNitzTimeSetTime = NOT_SET; + // TODO: Have a way to look up the timezone we are in + private long mNitzZoneSetTime = NOT_SET; + + private Context mContext; + // NTP lookup is done on this thread and handler + private Handler mHandler; + private HandlerThread mThread; + private AlarmManager mAlarmManager; + private PendingIntent mPendingPollIntent; + private SettingsObserver mSettingsObserver; + // Address of the NTP server + private String mNtpServer; + // The last time that we successfully fetched the NTP time. + private long mLastNtpFetchTime = NOT_SET; + // Keeps track of how many quick attempts were made to fetch NTP time. + // During bootup, the network may not have been up yet, or it's taking time for the + // connection to happen. + private int mTryAgainCounter; + + public NetworkTimeUpdateService(Context context) { + mContext = context; + mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + Intent pollIntent = new Intent(ACTION_POLL, null); + mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0); + } + + /** Initialize the receivers and initiate the first NTP request */ + public void systemReady() { + mNtpServer = getNtpServerAddress(); + if (mNtpServer == null) { + Slog.e(TAG, "NTP server address not found, not syncing to NTP time"); + return; + } + + registerForTelephonyIntents(); + registerForAlarms(); + + mThread = new HandlerThread(TAG); + mThread.start(); + mHandler = new MyHandler(mThread.getLooper()); + // Check the network time on the new thread + mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget(); + + mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED); + mSettingsObserver.observe(mContext); + } + + private String getNtpServerAddress() { + String serverAddress = null; + FileInputStream stream = null; + try { + Properties properties = new Properties(); + File file = new File(PROPERTIES_FILE); + stream = new FileInputStream(file); + properties.load(stream); + serverAddress = properties.getProperty("NTP_SERVER", null); + } catch (IOException e) { + Slog.e(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (Exception e) {} + } + } + return serverAddress; + } + + private void registerForTelephonyIntents() { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIME); + intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE); + mContext.registerReceiver(mNitzReceiver, intentFilter); + } + + private void registerForAlarms() { + mContext.registerReceiver( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget(); + } + }, new IntentFilter(ACTION_POLL)); + } + + private void onPollNetworkTime(int event) { + // If Automatic time is not set, don't bother. + if (!isAutomaticTimeRequested()) return; + + final long refTime = SystemClock.elapsedRealtime(); + // If NITZ time was received less than POLLING_INTERVAL_MS time ago, + // no need to sync to NTP. + if (mNitzTimeSetTime != NOT_SET && refTime - mNitzTimeSetTime < POLLING_INTERVAL_MS) { + resetAlarm(POLLING_INTERVAL_MS); + return; + } + final long currentTime = System.currentTimeMillis(); + if (DBG) Log.d(TAG, "System time = " + currentTime); + // Get the NTP time + if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + POLLING_INTERVAL_MS + || event == EVENT_AUTO_TIME_CHANGED) { + if (DBG) Log.d(TAG, "Before Ntp fetch"); + long ntp = getNtpTime(); + if (DBG) Log.d(TAG, "Ntp = " + ntp); + if (ntp > 0) { + mTryAgainCounter = 0; + mLastNtpFetchTime = SystemClock.elapsedRealtime(); + if (Math.abs(ntp - currentTime) > TIME_ERROR_THRESHOLD_MS) { + // Set the system time + if (DBG) Log.d(TAG, "Ntp time to be set = " + ntp); + // Make sure we don't overflow, since it's going to be converted to an int + if (ntp / 1000 < Integer.MAX_VALUE) { + SystemClock.setCurrentTimeMillis(ntp); + } + } else { + if (DBG) Log.d(TAG, "Ntp time is close enough = " + ntp); + } + } else { + // Try again shortly + mTryAgainCounter++; + if (mTryAgainCounter <= TRY_AGAIN_TIMES_MAX) { + resetAlarm(POLLING_INTERVAL_SHORTER_MS); + } else { + // Try much later + mTryAgainCounter = 0; + resetAlarm(POLLING_INTERVAL_MS); + } + return; + } + } + resetAlarm(POLLING_INTERVAL_MS); + } + + /** + * Cancel old alarm and starts a new one for the specified interval. + * + * @param interval when to trigger the alarm, starting from now. + */ + private void resetAlarm(long interval) { + mAlarmManager.cancel(mPendingPollIntent); + long now = SystemClock.elapsedRealtime(); + long next = now + interval; + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent); + } + + private long getNtpTime() { + SntpClient client = new SntpClient(); + if (client.requestTime(mNtpServer, MAX_NTP_FETCH_WAIT_MS)) { + return client.getNtpTime(); + } else { + return 0; + } + } + + /** + * Checks if the user prefers to automatically set the time. + */ + private boolean isAutomaticTimeRequested() { + return Settings.System.getInt(mContext.getContentResolver(), Settings.System.AUTO_TIME, 0) + != 0; + } + + /** Receiver for Nitz time events */ + private BroadcastReceiver mNitzReceiver = new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)) { + mNitzTimeSetTime = SystemClock.elapsedRealtime(); + } else if (TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE.equals(action)) { + mNitzZoneSetTime = SystemClock.elapsedRealtime(); + } + } + }; + + /** Handler to do the network accesses on */ + private class MyHandler extends Handler { + + public MyHandler(Looper l) { + super(l); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case EVENT_AUTO_TIME_CHANGED: + case EVENT_POLL_NETWORK_TIME: + onPollNetworkTime(msg.what); + break; + } + } + } + + /** Observer to watch for changes to the AUTO_TIME setting */ + private static class SettingsObserver extends ContentObserver { + + private int mMsg; + private Handler mHandler; + + SettingsObserver(Handler handler, int msg) { + super(handler); + mHandler = handler; + mMsg = msg; + } + + void observe(Context context) { + ContentResolver resolver = context.getContentResolver(); + resolver.registerContentObserver(Settings.System.getUriFor(Settings.System.AUTO_TIME), + false, this); + } + + @Override + public void onChange(boolean selfChange) { + mHandler.obtainMessage(mMsg).sendToTarget(); + } + } +} diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index 4da5eb2..47dce41 100755 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -17,7 +17,6 @@ package com.android.server; import com.android.internal.statusbar.StatusBarNotification; -import com.android.server.StatusBarManagerService; import android.app.ActivityManagerNative; import android.app.IActivityManager; @@ -38,16 +37,14 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.database.ContentObserver; -import android.hardware.Usb; +import android.hardware.UsbManager; import android.media.AudioManager; import android.net.Uri; -import android.os.BatteryManager; -import android.os.Bundle; import android.os.Binder; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; -import android.os.Power; import android.os.Process; import android.os.RemoteException; import android.os.SystemProperties; @@ -56,8 +53,8 @@ import android.provider.Settings; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.EventLog; -import android.util.Slog; import android.util.Log; +import android.util.Slog; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.widget.Toast; @@ -91,8 +88,6 @@ public class NotificationManagerService extends INotificationManager.Stub private WorkerHandler mHandler; private StatusBarManagerService mStatusBar; - private LightsService mLightsService; - private LightsService.Light mBatteryLight; private LightsService.Light mNotificationLight; private LightsService.Light mAttentionLight; @@ -127,18 +122,8 @@ public class NotificationManagerService extends INotificationManager.Stub private ArrayList<ToastRecord> mToastQueue; private ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>(); - - private boolean mBatteryCharging; - private boolean mBatteryLow; - private boolean mBatteryFull; private NotificationRecord mLedNotification; - private static int mBatteryLowARGB; - private static int mBatteryMediumARGB; - private static int mBatteryFullARGB; - private static int mBatteryLedOn; - private static int mBatteryLedOff; - private static String idDebugString(Context baseContext, String packageName, int id) { Context c = null; @@ -171,12 +156,11 @@ public class NotificationManagerService extends INotificationManager.Stub final int id; final int uid; final int initialPid; - ITransientNotification callback; - int duration; + final int priority; final Notification notification; IBinder statusBarKey; - NotificationRecord(String pkg, String tag, int id, int uid, int initialPid, + NotificationRecord(String pkg, String tag, int id, int uid, int initialPid, int priority, Notification notification) { this.pkg = pkg; @@ -184,6 +168,7 @@ public class NotificationManagerService extends INotificationManager.Stub this.id = id; this.uid = uid; this.initialPid = initialPid; + this.priority = priority; this.notification = notification; } @@ -211,7 +196,9 @@ public class NotificationManagerService extends INotificationManager.Stub + Integer.toHexString(System.identityHashCode(this)) + " pkg=" + pkg + " id=" + Integer.toHexString(id) - + " tag=" + tag + "}"; + + " tag=" + tag + + " pri=" + priority + + "}"; } } @@ -282,7 +269,13 @@ public class NotificationManagerService extends INotificationManager.Stub public void onNotificationClick(String pkg, String tag, int id) { cancelNotification(pkg, tag, id, Notification.FLAG_AUTO_CANCEL, - Notification.FLAG_FOREGROUND_SERVICE); + Notification.FLAG_FOREGROUND_SERVICE, true); + } + + public void onNotificationClear(String pkg, String tag, int id) { + cancelNotification(pkg, tag, id, 0, + Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE, + true); } public void onPanelRevealed() { @@ -318,7 +311,7 @@ public class NotificationManagerService extends INotificationManager.Stub int uid, int initialPid, String message) { Slog.d(TAG, "onNotification error pkg=" + pkg + " tag=" + tag + " id=" + id + "; will crashApplication(uid=" + uid + ", pid=" + initialPid + ")"); - cancelNotification(pkg, tag, id, 0, 0); + cancelNotification(pkg, tag, id, 0, 0, false); long ident = Binder.clearCallingIdentity(); try { ActivityManagerNative.getDefault().crashApplication(uid, initialPid, pkg, @@ -337,29 +330,12 @@ public class NotificationManagerService extends INotificationManager.Stub boolean queryRestart = false; - if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { - boolean batteryCharging = (intent.getIntExtra("plugged", 0) != 0); - int level = intent.getIntExtra("level", -1); - boolean batteryLow = (level >= 0 && level <= Power.LOW_BATTERY_THRESHOLD); - int status = intent.getIntExtra("status", BatteryManager.BATTERY_STATUS_UNKNOWN); - boolean batteryFull = (status == BatteryManager.BATTERY_STATUS_FULL || level >= 90); - - if (batteryCharging != mBatteryCharging || - batteryLow != mBatteryLow || - batteryFull != mBatteryFull) { - mBatteryCharging = batteryCharging; - mBatteryLow = batteryLow; - mBatteryFull = batteryFull; - updateLights(); - } - } else if (action.equals(Usb.ACTION_USB_STATE)) { + if (action.equals(UsbManager.ACTION_USB_STATE)) { Bundle extras = intent.getExtras(); - boolean usbConnected = extras.getBoolean(Usb.USB_CONNECTED); - boolean adbEnabled = (Usb.USB_FUNCTION_ENABLED.equals( - extras.getString(Usb.USB_FUNCTION_ADB))); + boolean usbConnected = extras.getBoolean(UsbManager.USB_CONNECTED); + boolean adbEnabled = (UsbManager.USB_FUNCTION_ENABLED.equals( + extras.getString(UsbManager.USB_FUNCTION_ADB))); updateAdbNotification(usbConnected && adbEnabled); - } else if (action.equals(Usb.ACTION_USB_DISCONNECTED)) { - updateAdbNotification(false); } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED) || action.equals(Intent.ACTION_PACKAGE_RESTARTED) || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART)) @@ -430,7 +406,6 @@ public class NotificationManagerService extends INotificationManager.Stub { super(); mContext = context; - mLightsService = lights; mAm = ActivityManagerNative.getDefault(); mSound = new NotificationPlayer(TAG); mSound.setUsesWakeLock(context); @@ -440,7 +415,6 @@ public class NotificationManagerService extends INotificationManager.Stub mStatusBar = statusBar; statusBar.setNotificationCallbacks(mNotificationCallbacks); - mBatteryLight = lights.getLight(LightsService.LIGHT_ID_BATTERY); mNotificationLight = lights.getLight(LightsService.LIGHT_ID_NOTIFICATIONS); mAttentionLight = lights.getLight(LightsService.LIGHT_ID_ATTENTION); @@ -452,17 +426,6 @@ public class NotificationManagerService extends INotificationManager.Stub mDefaultNotificationLedOff = resources.getInteger( com.android.internal.R.integer.config_defaultNotificationLedOff); - mBatteryLowARGB = mContext.getResources().getInteger( - com.android.internal.R.integer.config_notificationsBatteryLowARGB); - mBatteryMediumARGB = mContext.getResources().getInteger( - com.android.internal.R.integer.config_notificationsBatteryMediumARGB); - mBatteryFullARGB = mContext.getResources().getInteger( - com.android.internal.R.integer.config_notificationsBatteryFullARGB); - mBatteryLedOn = mContext.getResources().getInteger( - com.android.internal.R.integer.config_notificationsBatteryLedOn); - mBatteryLedOff = mContext.getResources().getInteger( - com.android.internal.R.integer.config_notificationsBatteryLedOff); - // Don't start allowing notifications until the setup wizard has run once. // After that, including subsequent boots, init with notifications turned on. // This works on the first boot because the setup wizard will toggle this @@ -472,10 +435,9 @@ public class NotificationManagerService extends INotificationManager.Stub mDisabledNotifications = StatusBarManager.DISABLE_NOTIFICATION_ALERTS; } - // register for battery changed notifications + // register for various Intents IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_BATTERY_CHANGED); - filter.addAction(Usb.ACTION_USB_STATE); + filter.addAction(UsbManager.ACTION_USB_STATE); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); @@ -678,6 +640,7 @@ public class NotificationManagerService extends INotificationManager.Stub // Notifications // ============================================================================ + @Deprecated public void enqueueNotification(String pkg, int id, Notification notification, int[] idOut) { enqueueNotificationWithTag(pkg, null /* tag */, id, notification, idOut); @@ -690,11 +653,27 @@ public class NotificationManagerService extends INotificationManager.Stub tag, id, notification, idOut); } + public void enqueueNotificationWithTagPriority(String pkg, String tag, int id, int priority, + Notification notification, int[] idOut) + { + enqueueNotificationInternal(pkg, Binder.getCallingUid(), Binder.getCallingPid(), + tag, id, priority, notification, idOut); + } + // Not exposed via Binder; for system use only (otherwise malicious apps could spoof the // uid/pid of another application) public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid, String tag, int id, Notification notification, int[] idOut) { + enqueueNotificationInternal(pkg, callingUid, callingPid, tag, id, + ((notification.flags & Notification.FLAG_ONGOING_EVENT) != 0) + ? StatusBarNotification.PRIORITY_ONGOING + : StatusBarNotification.PRIORITY_NORMAL, + notification, idOut); + } + public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid, + String tag, int id, int priority, Notification notification, int[] idOut) + { checkIncomingCall(pkg); // Limit the number of notifications that any given package except the android @@ -733,15 +712,13 @@ public class NotificationManagerService extends INotificationManager.Stub throw new IllegalArgumentException("contentView required: pkg=" + pkg + " id=" + id + " notification=" + notification); } - if (notification.contentIntent == null) { - throw new IllegalArgumentException("contentIntent required: pkg=" + pkg - + " id=" + id + " notification=" + notification); - } } synchronized (mNotificationList) { - NotificationRecord r = new NotificationRecord(pkg, tag, id, - callingUid, callingPid, notification); + NotificationRecord r = new NotificationRecord(pkg, tag, id, + callingUid, callingPid, + priority, + notification); NotificationRecord old = null; int index = indexOfNotificationLocked(pkg, tag, id); @@ -767,6 +744,8 @@ public class NotificationManagerService extends INotificationManager.Stub if (notification.icon != 0) { StatusBarNotification n = new StatusBarNotification(pkg, id, tag, r.uid, r.initialPid, notification); + n.priority = r.priority; + if (old != null && old.statusBarKey != null) { r.statusBarKey = old.statusBarKey; long identity = Binder.clearCallingIdentity(); @@ -788,6 +767,7 @@ public class NotificationManagerService extends INotificationManager.Stub } sendAccessibilityEvent(notification, pkg); } else { + Slog.e(TAG, "Ignoring notification with icon==0: " + notification); if (old != null && old.statusBarKey != null) { long identity = Binder.clearCallingIdentity(); try { @@ -894,7 +874,20 @@ public class NotificationManagerService extends INotificationManager.Stub manager.sendAccessibilityEvent(event); } - private void cancelNotificationLocked(NotificationRecord r) { + private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete) { + // tell the app + if (sendDelete) { + if (r.notification.deleteIntent != null) { + try { + r.notification.deleteIntent.send(); + } catch (PendingIntent.CanceledException ex) { + // do nothing - there's no relevant way to recover, and + // no reason to let this propagate + Slog.w(TAG, "canceled PendingIntent for " + r.pkg, ex); + } + } + } + // status bar if (r.notification.icon != 0) { long identity = Binder.clearCallingIdentity(); @@ -943,7 +936,7 @@ public class NotificationManagerService extends INotificationManager.Stub * and none of the {@code mustNotHaveFlags}. */ private void cancelNotification(String pkg, String tag, int id, int mustHaveFlags, - int mustNotHaveFlags) { + int mustNotHaveFlags, boolean sendDelete) { EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL, pkg, id, mustHaveFlags); synchronized (mNotificationList) { @@ -960,7 +953,7 @@ public class NotificationManagerService extends INotificationManager.Stub mNotificationList.remove(index); - cancelNotificationLocked(r); + cancelNotificationLocked(r, sendDelete); updateLightsLocked(); } } @@ -993,7 +986,7 @@ public class NotificationManagerService extends INotificationManager.Stub return true; } mNotificationList.remove(i); - cancelNotificationLocked(r); + cancelNotificationLocked(r, false); } if (canceledSomething) { updateLightsLocked(); @@ -1002,7 +995,7 @@ public class NotificationManagerService extends INotificationManager.Stub } } - + @Deprecated public void cancelNotification(String pkg, int id) { cancelNotificationWithTag(pkg, null /* tag */, id); } @@ -1012,7 +1005,7 @@ public class NotificationManagerService extends INotificationManager.Stub // Don't allow client applications to cancel foreground service notis. cancelNotification(pkg, tag, id, 0, Binder.getCallingUid() == Process.SYSTEM_UID - ? 0 : Notification.FLAG_FOREGROUND_SERVICE); + ? 0 : Notification.FLAG_FOREGROUND_SERVICE, false); } public void cancelAllNotifications(String pkg) { @@ -1048,17 +1041,8 @@ public class NotificationManagerService extends INotificationManager.Stub if ((r.notification.flags & (Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR)) == 0) { - if (r.notification.deleteIntent != null) { - try { - r.notification.deleteIntent.send(); - } catch (PendingIntent.CanceledException ex) { - // do nothing - there's no relevant way to recover, and - // no reason to let this propagate - Slog.w(TAG, "canceled PendingIntent for " + r.pkg, ex); - } - } mNotificationList.remove(i); - cancelNotificationLocked(r); + cancelNotificationLocked(r, true); } } @@ -1066,34 +1050,9 @@ public class NotificationManagerService extends INotificationManager.Stub } } - private void updateLights() { - synchronized (mNotificationList) { - updateLightsLocked(); - } - } - // lock on mNotificationList private void updateLightsLocked() { - // Battery low always shows, other states only show if charging. - if (mBatteryLow) { - if (mBatteryCharging) { - mBatteryLight.setColor(mBatteryLowARGB); - } else { - // Flash when battery is low and not charging - mBatteryLight.setFlashing(mBatteryLowARGB, LightsService.LIGHT_FLASH_TIMED, - mBatteryLedOn, mBatteryLedOff); - } - } else if (mBatteryCharging) { - if (mBatteryFull) { - mBatteryLight.setColor(mBatteryFullARGB); - } else { - mBatteryLight.setColor(mBatteryMediumARGB); - } - } else { - mBatteryLight.turnOff(); - } - // clear pending pulse notification if screen is on if (mScreenOn || mLedNotification == null) { mPendingPulseNotification = false; diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index 8c74566..9ee71e8 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -1050,7 +1050,7 @@ class PackageManagerService extends IPackageManager.Stub { void cleanupInstallFailedPackage(PackageSetting ps) { Slog.i(TAG, "Cleaning up incompletely installed app: " + ps.name); if (mInstaller != null) { - boolean useSecureFS = useEncryptedFilesystemForPackage(ps.pkg); + boolean useSecureFS = false; int retCode = mInstaller.remove(ps.name, useSecureFS); if (retCode < 0) { Slog.w(TAG, "Couldn't remove app data directory for package: " @@ -1225,6 +1225,7 @@ class PackageManagerService extends IPackageManager.Stub { } } + permReader.close(); } catch (XmlPullParserException e) { Slog.w(TAG, "Got execption parsing permissions.", e); } catch (IOException e) { @@ -2086,7 +2087,7 @@ class PackageManagerService extends IPackageManager.Stub { return (List<ResolveInfo>) mActivities.queryIntentForPackage(intent, resolvedType, flags, pkg.activities); } - return null; + return new ArrayList<ResolveInfo>(); } } @@ -2779,11 +2780,6 @@ class PackageManagerService extends IPackageManager.Stub { return performed ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED; } - private static boolean useEncryptedFilesystemForPackage(PackageParser.Package pkg) { - return Environment.isEncryptedFilesystemEnabled() && - ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_NEVER_ENCRYPT) == 0); - } - private boolean verifyPackageUpdate(PackageSetting oldPkg, PackageParser.Package newPkg) { if ((oldPkg.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) { Slog.w(TAG, "Unable to update from " + oldPkg.name @@ -2800,7 +2796,7 @@ class PackageManagerService extends IPackageManager.Stub { } private File getDataPathForPackage(PackageParser.Package pkg) { - boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(pkg); + boolean useEncryptedFSDir = false; File dataPath; if (useEncryptedFSDir) { dataPath = new File(mSecureAppDataDir, pkg.packageName); @@ -2852,7 +2848,7 @@ class PackageManagerService extends IPackageManager.Stub { mResolveActivity.processName = mAndroidApplication.processName; mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE; mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS; - mResolveActivity.theme = com.android.internal.R.style.Theme_Dialog_Alert; + mResolveActivity.theme = com.android.internal.R.style.Theme_Holo_Dialog_Alert; mResolveActivity.exported = true; mResolveActivity.enabled = true; mResolveInfo.activityInfo = mResolveActivity; @@ -3136,7 +3132,7 @@ class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.dataDir = dataPath.getPath(); } else { // This is a normal package, need to make its data directory. - boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(pkg); + boolean useEncryptedFSDir = false; dataPath = getDataPathForPackage(pkg); boolean uidError = false; @@ -4391,10 +4387,19 @@ class PackageManagerService extends IPackageManager.Stub { } } } - + + /** + * Check if the external storage media is available. This is true if there + * is a mounted external storage medium or if the external storage is + * emulated. + */ + private boolean isExternalMediaAvailable() { + return mMediaMounted || Environment.isExternalStorageEmulated(); + } + public String nextPackageToClean(String lastPackage) { synchronized (mPackages) { - if (!mMediaMounted) { + if (!isExternalMediaAvailable()) { // If the external storage is no longer mounted at this point, // the caller may not have been able to delete all of this // packages files and can not delete any more. Bail. @@ -4414,7 +4419,7 @@ class PackageManagerService extends IPackageManager.Stub { void startCleaningPackages() { synchronized (mPackages) { - if (!mMediaMounted) { + if (!isExternalMediaAvailable()) { return; } if (mSettings.mPackagesToBeCleaned.size() <= 0) { @@ -4549,6 +4554,80 @@ class PackageManagerService extends IPackageManager.Stub { mHandler.sendMessage(msg); } + public void setInstallerPackageName(String targetPackage, + String installerPackageName) { + PackageSetting pkgSetting; + final int uid = Binder.getCallingUid(); + final int permission = mContext.checkCallingPermission( + android.Manifest.permission.INSTALL_PACKAGES); + final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED); + synchronized (mPackages) { + PackageSetting targetPackageSetting = mSettings.mPackages.get(targetPackage); + if (targetPackageSetting == null) { + throw new IllegalArgumentException("Unknown target package: " + targetPackage); + } + + PackageSetting installerPackageSetting; + if (installerPackageName != null) { + installerPackageSetting = mSettings.mPackages.get(installerPackageName); + if (installerPackageSetting == null) { + throw new IllegalArgumentException("Unknown installer package: " + + installerPackageName); + } + } else { + installerPackageSetting = null; + } + + Signature[] callerSignature; + Object obj = mSettings.getUserIdLP(uid); + if (obj != null) { + if (obj instanceof SharedUserSetting) { + callerSignature = ((SharedUserSetting)obj).signatures.mSignatures; + } else if (obj instanceof PackageSetting) { + callerSignature = ((PackageSetting)obj).signatures.mSignatures; + } else { + throw new SecurityException("Bad object " + obj + " for uid " + uid); + } + } else { + throw new SecurityException("Unknown calling uid " + uid); + } + + // Verify: can't set installerPackageName to a package that is + // not signed with the same cert as the caller. + if (installerPackageSetting != null) { + if (checkSignaturesLP(callerSignature, + installerPackageSetting.signatures.mSignatures) + != PackageManager.SIGNATURE_MATCH) { + throw new SecurityException( + "Caller does not have same cert as new installer package " + + installerPackageName); + } + } + + // Verify: if target already has an installer package, it must + // be signed with the same cert as the caller. + if (targetPackageSetting.installerPackageName != null) { + PackageSetting setting = mSettings.mPackages.get( + targetPackageSetting.installerPackageName); + // If the currently set package isn't valid, then it's always + // okay to change it. + if (setting != null) { + if (checkSignaturesLP(callerSignature, + setting.signatures.mSignatures) + != PackageManager.SIGNATURE_MATCH) { + throw new SecurityException( + "Caller does not have same cert as old installer package " + + targetPackageSetting.installerPackageName); + } + } + } + + // Okay! + targetPackageSetting.installerPackageName = installerPackageName; + scheduleWriteSettingsLocked(); + } + } + private void processPendingInstall(final InstallArgs args, final int currentStatus) { // Queue up an async operation since the package installation may take a little while. mHandler.post(new Runnable() { @@ -4656,6 +4735,79 @@ class PackageManagerService extends IPackageManager.Stub { abstract void handleReturnCode(); } + class MeasureParams extends HandlerParams { + private final PackageStats mStats; + private boolean mSuccess; + + private final IPackageStatsObserver mObserver; + + public MeasureParams(PackageStats stats, boolean success, + IPackageStatsObserver observer) { + mObserver = observer; + mStats = stats; + mSuccess = success; + } + + @Override + void handleStartCopy() throws RemoteException { + final boolean mounted; + + if (Environment.isExternalStorageEmulated()) { + mounted = true; + } else { + final String status = Environment.getExternalStorageState(); + + mounted = status.equals(Environment.MEDIA_MOUNTED) + || status.equals(Environment.MEDIA_MOUNTED_READ_ONLY); + } + + if (mounted) { + final File externalCacheDir = Environment + .getExternalStorageAppCacheDirectory(mStats.packageName); + final long externalCacheSize = mContainerService + .calculateDirectorySize(externalCacheDir.getPath()); + mStats.externalCacheSize = externalCacheSize; + + final File externalDataDir = Environment + .getExternalStorageAppDataDirectory(mStats.packageName); + long externalDataSize = mContainerService.calculateDirectorySize(externalDataDir + .getPath()); + + if (externalCacheDir.getParentFile().equals(externalDataDir)) { + externalDataSize -= externalCacheSize; + } + mStats.externalDataSize = externalDataSize; + + final File externalMediaDir = Environment + .getExternalStorageAppMediaDirectory(mStats.packageName); + mStats.externalMediaSize = mContainerService + .calculateDirectorySize(externalCacheDir.getPath()); + + final File externalObbDir = Environment + .getExternalStorageAppObbDirectory(mStats.packageName); + mStats.externalObbSize = mContainerService.calculateDirectorySize(externalObbDir + .getPath()); + } + } + + @Override + void handleReturnCode() { + if (mObserver != null) { + try { + mObserver.onGetStatsCompleted(mStats, mSuccess); + } catch (RemoteException e) { + Slog.i(TAG, "Observer no longer exists."); + } + } + } + + @Override + void handleServiceError() { + Slog.e(TAG, "Could not measure application " + mStats.packageName + + " external storage"); + } + } + class InstallParams extends HandlerParams { final IPackageInstallObserver observer; int flags; @@ -6141,7 +6293,7 @@ class PackageManagerService extends IPackageManager.Stub { deletedPs = mSettings.mPackages.get(packageName); } if ((flags&PackageManager.DONT_DELETE_DATA) == 0) { - boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(p); + boolean useEncryptedFSDir = false; if (mInstaller != null) { int retCode = mInstaller.remove(packageName, useEncryptedFSDir); if (retCode < 0) { @@ -6385,7 +6537,7 @@ class PackageManagerService extends IPackageManager.Stub { } boolean useEncryptedFSDir = false; - if(!dataOnly) { + if (!dataOnly) { //need to check this only for fully installed applications if (p == null) { Slog.w(TAG, "Package named '" + packageName +"' doesn't exist."); @@ -6396,7 +6548,6 @@ class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Package " + packageName + " has no applicationInfo."); return false; } - useEncryptedFSDir = useEncryptedFilesystemForPackage(p); } if (mInstaller != null) { int retCode = mInstaller.clearUserData(packageName, useEncryptedFSDir); @@ -6450,7 +6601,7 @@ class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Package " + packageName + " has no applicationInfo."); return false; } - boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(p); + boolean useEncryptedFSDir = false; if (mInstaller != null) { int retCode = mInstaller.deleteCacheFiles(packageName, useEncryptedFSDir); if (retCode < 0) { @@ -6470,18 +6621,16 @@ class PackageManagerService extends IPackageManager.Stub { mHandler.post(new Runnable() { public void run() { mHandler.removeCallbacks(this); - PackageStats lStats = new PackageStats(packageName); - final boolean succeded; + PackageStats stats = new PackageStats(packageName); + + final boolean success; synchronized (mInstallLock) { - succeded = getPackageSizeInfoLI(packageName, lStats); + success = getPackageSizeInfoLI(packageName, stats); } - if(observer != null) { - try { - observer.onGetStatsCompleted(lStats, succeded); - } catch (RemoteException e) { - Log.i(TAG, "Observer no longer exists."); - } - } //end if observer + + Message msg = mHandler.obtainMessage(INIT_COPY); + msg.obj = new MeasureParams(stats, success, observer); + mHandler.sendMessage(msg); } //end run }); } @@ -6514,7 +6663,7 @@ class PackageManagerService extends IPackageManager.Stub { } publicSrcDir = isForwardLocked(p) ? applicationInfo.publicSourceDir : null; } - boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(p); + boolean useEncryptedFSDir = false; if (mInstaller != null) { int res = mInstaller.getSizeInfo(packageName, p.mPath, publicSrcDir, pStats, useEncryptedFSDir); @@ -7278,7 +7427,7 @@ class PackageManagerService extends IPackageManager.Stub { pw.println(" "); pw.println("Package warning messages:"); File fname = getSettingsProblemFile(); - FileInputStream in; + FileInputStream in = null; try { in = new FileInputStream(fname); int avail = in.available(); @@ -7287,6 +7436,13 @@ class PackageManagerService extends IPackageManager.Stub { pw.print(new String(data)); } catch (FileNotFoundException e) { } catch (IOException e) { + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + } + } } } } @@ -7679,8 +7835,7 @@ class PackageManagerService extends IPackageManager.Stub { this.pkgFlags = pkgFlags & ( ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_FORWARD_LOCK | - ApplicationInfo.FLAG_EXTERNAL_STORAGE | - ApplicationInfo.FLAG_NEVER_ENCRYPT); + ApplicationInfo.FLAG_EXTERNAL_STORAGE); } } @@ -9715,7 +9870,8 @@ class PackageManagerService extends IPackageManager.Stub { * Update media status on PackageManager. */ public void updateExternalMediaStatus(final boolean mediaStatus, final boolean reportStatus) { - if (Binder.getCallingUid() != Process.SYSTEM_UID) { + int callingUid = Binder.getCallingUid(); + if (callingUid != 0 && callingUid != Process.SYSTEM_UID) { throw new SecurityException("Media status can only be updated by the system"); } synchronized (mPackages) { diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java index a6daaef..caf6376 100644 --- a/services/java/com/android/server/PowerManagerService.java +++ b/services/java/com/android/server/PowerManagerService.java @@ -219,6 +219,8 @@ class PowerManagerService extends IPowerManager.Stub private float mLightSensorValue = -1; private boolean mProxIgnoredBecauseScreenTurnedOff = false; private int mHighestLightSensorValue = -1; + private boolean mLightSensorPendingDecrease = false; + private boolean mLightSensorPendingIncrease = false; private float mLightSensorPendingValue = -1; private int mLightSensorScreenBrightness = -1; private int mLightSensorButtonBrightness = -1; @@ -1141,6 +1143,8 @@ class PowerManagerService extends IPowerManager.Stub pw.println(" mLightSensorEnabled=" + mLightSensorEnabled); pw.println(" mLightSensorValue=" + mLightSensorValue + " mLightSensorPendingValue=" + mLightSensorPendingValue); + pw.println(" mLightSensorPendingDecrease=" + mLightSensorPendingDecrease + + " mLightSensorPendingIncrease=" + mLightSensorPendingIncrease); pw.println(" mLightSensorScreenBrightness=" + mLightSensorScreenBrightness + " mLightSensorButtonBrightness=" + mLightSensorButtonBrightness + " mLightSensorKeyboardBrightness=" + mLightSensorKeyboardBrightness); @@ -1171,10 +1175,8 @@ class PowerManagerService extends IPowerManager.Stub pw.println("mPokeLocks.size=" + mPokeLocks.size() + ":"); for (PokeLock p: mPokeLocks.values()) { pw.println(" poke lock '" + p.tag + "':" - + ((p.pokey & POKE_LOCK_IGNORE_CHEEK_EVENTS) != 0 - ? " POKE_LOCK_IGNORE_CHEEK_EVENTS" : "") - + ((p.pokey & POKE_LOCK_IGNORE_TOUCH_AND_CHEEK_EVENTS) != 0 - ? " POKE_LOCK_IGNORE_TOUCH_AND_CHEEK_EVENTS" : "") + + ((p.pokey & POKE_LOCK_IGNORE_TOUCH_EVENTS) != 0 + ? " POKE_LOCK_IGNORE_TOUCH_EVENTS" : "") + ((p.pokey & POKE_LOCK_SHORT_TIMEOUT) != 0 ? " POKE_LOCK_SHORT_TIMEOUT" : "") + ((p.pokey & POKE_LOCK_MEDIUM_TIMEOUT) != 0 @@ -1609,7 +1611,7 @@ class PowerManagerService extends IPowerManager.Stub if (err == 0) { mLastScreenOnTime = (on ? SystemClock.elapsedRealtime() : 0); if (mUseSoftwareAutoBrightness) { - enableLightSensor(on); + enableLightSensorLocked(on); if (!on) { // make sure button and key backlights are off too mButtonLight.turnOff(); @@ -1742,6 +1744,8 @@ class PowerManagerService extends IPowerManager.Stub } else { // cancel light sensor task mHandler.removeCallbacks(mAutoBrightnessTask); + mLightSensorPendingDecrease = false; + mLightSensorPendingIncrease = false; mScreenOffTime = SystemClock.elapsedRealtime(); long identity = Binder.clearCallingIdentity(); try { @@ -2212,31 +2216,13 @@ class PowerManagerService extends IPowerManager.Stub private void userActivity(long time, long timeoutOverride, boolean noChangeLights, int eventType, boolean force) { - if (((mPokey & POKE_LOCK_IGNORE_CHEEK_EVENTS) != 0) - && (eventType == CHEEK_EVENT)) { - if (false) { - Slog.d(TAG, "dropping cheek event mPokey=0x" + Integer.toHexString(mPokey)); - } - return; - } - - if (((mPokey & POKE_LOCK_IGNORE_TOUCH_AND_CHEEK_EVENTS) != 0) - && (eventType == TOUCH_EVENT || eventType == TOUCH_UP_EVENT - || eventType == LONG_TOUCH_EVENT || eventType == CHEEK_EVENT)) { + if (((mPokey & POKE_LOCK_IGNORE_TOUCH_EVENTS) != 0) && (eventType == TOUCH_EVENT)) { if (false) { Slog.d(TAG, "dropping touch mPokey=0x" + Integer.toHexString(mPokey)); } return; } - if (false) { - if (((mPokey & POKE_LOCK_IGNORE_CHEEK_EVENTS) != 0)) { - Slog.d(TAG, "userActivity !!!");//, new RuntimeException()); - } else { - Slog.d(TAG, "mPokey=0x" + Integer.toHexString(mPokey)); - } - } - synchronized (mLocks) { if (mSpew) { Slog.d(TAG, "userActivity mLastEventTime=" + mLastEventTime + " time=" + time @@ -2325,9 +2311,10 @@ class PowerManagerService extends IPowerManager.Stub private Runnable mAutoBrightnessTask = new Runnable() { public void run() { synchronized (mLocks) { - int value = (int)mLightSensorPendingValue; - if (value >= 0) { - mLightSensorPendingValue = -1; + if (mLightSensorPendingDecrease || mLightSensorPendingIncrease) { + int value = (int)mLightSensorPendingValue; + mLightSensorPendingDecrease = false; + mLightSensorPendingIncrease = false; lightSensorChangedLocked(value); } } @@ -2569,16 +2556,12 @@ class PowerManagerService extends IPowerManager.Stub } private void setScreenBrightnessMode(int mode) { - boolean enabled = (mode == SCREEN_BRIGHTNESS_MODE_AUTOMATIC); - if (mUseSoftwareAutoBrightness && mAutoBrightessEnabled != enabled) { - mAutoBrightessEnabled = enabled; - if (isScreenOn()) { - // force recompute of backlight values - if (mLightSensorValue >= 0) { - int value = (int)mLightSensorValue; - mLightSensorValue = -1; - lightSensorChangedLocked(value); - } + synchronized (mLocks) { + boolean enabled = (mode == SCREEN_BRIGHTNESS_MODE_AUTOMATIC); + if (mUseSoftwareAutoBrightness && mAutoBrightessEnabled != enabled) { + mAutoBrightessEnabled = enabled; + // This will get us a new value + enableLightSensorLocked(mAutoBrightessEnabled && isScreenOn()); } } } @@ -2604,7 +2587,8 @@ class PowerManagerService extends IPowerManager.Stub } mKeylightDelay = LONG_KEYLIGHT_DELAY; if (totalDelay < 0) { - mScreenOffDelay = Integer.MAX_VALUE; + // negative number means stay on as long as possible. + mScreenOffDelay = mMaximumScreenOffTimeout; } else if (mKeylightDelay < totalDelay) { // subtract the time that the keylight delay. This will give us the // remainder of the time that we need to sleep to get the accurate @@ -2729,7 +2713,6 @@ class PowerManagerService extends IPowerManager.Stub // don't bother with the light sensor if auto brightness is handled in hardware if (mUseSoftwareAutoBrightness) { mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); - enableLightSensor(true); } // wait until sensors are enabled before turning on screen. @@ -2747,6 +2730,8 @@ class PowerManagerService extends IPowerManager.Stub Slog.d(TAG, "system ready!"); mDoneBooting = true; + enableLightSensorLocked(mUseSoftwareAutoBrightness && mAutoBrightessEnabled); + long identity = Binder.clearCallingIdentity(); try { mBatteryStats.noteScreenBrightness(getPreferredBrightness()); @@ -2901,9 +2886,13 @@ class PowerManagerService extends IPowerManager.Stub } } - private void enableLightSensor(boolean enable) { + private void enableLightSensorLocked(boolean enable) { if (mDebugLightSensor) { - Slog.d(TAG, "enableLightSensor " + enable); + Slog.d(TAG, "enableLightSensorLocked enable=" + enable + + " mAutoBrightessEnabled=" + mAutoBrightessEnabled); + } + if (!mAutoBrightessEnabled) { + enable = false; } if (mSensorManager != null && mLightSensorEnabled != enable) { mLightSensorEnabled = enable; @@ -2980,19 +2969,29 @@ class PowerManagerService extends IPowerManager.Stub if (mDebugLightSensor) { Slog.d(TAG, "onSensorChanged: light value: " + value); } - mHandler.removeCallbacks(mAutoBrightnessTask); - if (mLightSensorValue != value) { - if (mLightSensorValue == -1 || - milliseconds < mLastScreenOnTime + mLightSensorWarmupTime) { - // process the value immediately if screen has just turned on - lightSensorChangedLocked(value); - } else { + if (mLightSensorValue == -1 || + milliseconds < mLastScreenOnTime + mLightSensorWarmupTime) { + // process the value immediately if screen has just turned on + mHandler.removeCallbacks(mAutoBrightnessTask); + mLightSensorPendingDecrease = false; + mLightSensorPendingIncrease = false; + lightSensorChangedLocked(value); + } else { + if ((value > mLightSensorValue && mLightSensorPendingDecrease) || + (value < mLightSensorValue && mLightSensorPendingIncrease) || + (value == mLightSensorValue) || + (!mLightSensorPendingDecrease && !mLightSensorPendingIncrease)) { // delay processing to debounce the sensor + mHandler.removeCallbacks(mAutoBrightnessTask); + mLightSensorPendingDecrease = (value < mLightSensorValue); + mLightSensorPendingIncrease = (value > mLightSensorValue); + if (mLightSensorPendingDecrease || mLightSensorPendingIncrease) { + mLightSensorPendingValue = value; + mHandler.postDelayed(mAutoBrightnessTask, LIGHT_SENSOR_DELAY); + } + } else { mLightSensorPendingValue = value; - mHandler.postDelayed(mAutoBrightnessTask, LIGHT_SENSOR_DELAY); } - } else { - mLightSensorPendingValue = -1; } } } diff --git a/services/java/com/android/server/ProcessStats.java b/services/java/com/android/server/ProcessStats.java index 43dbcc0..1a12a84 100644 --- a/services/java/com/android/server/ProcessStats.java +++ b/services/java/com/android/server/ProcessStats.java @@ -799,8 +799,9 @@ public class ProcessStats { } private String readFile(String file, char endChar) { + FileInputStream is = null; try { - FileInputStream is = new FileInputStream(file); + is = new FileInputStream(file); int len = is.read(mBuffer); is.close(); @@ -815,6 +816,13 @@ public class ProcessStats { } } catch (java.io.FileNotFoundException e) { } catch (java.io.IOException e) { + } finally { + if (is != null) { + try { + is.close(); + } catch (java.io.IOException e) { + } + } } return null; } @@ -841,4 +849,3 @@ public class ProcessStats { } } } - diff --git a/services/java/com/android/server/SamplingProfilerService.java b/services/java/com/android/server/SamplingProfilerService.java new file mode 100644 index 0000000..61267d0 --- /dev/null +++ b/services/java/com/android/server/SamplingProfilerService.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2010 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.server; + +import android.content.ContentResolver; +import android.os.DropBoxManager; +import android.os.FileObserver; +import android.os.Binder; + +import android.util.Slog; +import android.content.Context; +import android.database.ContentObserver; +import android.os.SystemProperties; +import android.provider.Settings; +import com.android.internal.os.SamplingProfilerIntegration; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; + +public class SamplingProfilerService extends Binder { + + private static final String TAG = "SamplingProfilerService"; + private static final boolean LOCAL_LOGV = false; + public static final String SNAPSHOT_DIR = SamplingProfilerIntegration.SNAPSHOT_DIR; + + private FileObserver snapshotObserver; + + public SamplingProfilerService(Context context) { + registerSettingObserver(context); + startWorking(context); + } + + private void startWorking(Context context) { + if (LOCAL_LOGV) Slog.v(TAG, "starting SamplingProfilerService!"); + + final DropBoxManager dropbox = + (DropBoxManager) context.getSystemService(Context.DROPBOX_SERVICE); + + // before FileObserver is ready, there could have already been some snapshots + // in the directory, we don't want to miss them + File[] snapshotFiles = new File(SNAPSHOT_DIR).listFiles(); + for (int i = 0; snapshotFiles != null && i < snapshotFiles.length; i++) { + handleSnapshotFile(snapshotFiles[i], dropbox); + } + + // detect new snapshot and put it in dropbox + // delete it afterwards no matter what happened before + // Note: needs listening at event ATTRIB rather than CLOSE_WRITE, because we set the + // readability of snapshot files after writing them! + snapshotObserver = new FileObserver(SNAPSHOT_DIR, FileObserver.ATTRIB) { + @Override + public void onEvent(int event, String path) { + handleSnapshotFile(new File(SNAPSHOT_DIR, path), dropbox); + } + }; + snapshotObserver.startWatching(); + + if (LOCAL_LOGV) Slog.v(TAG, "SamplingProfilerService activated"); + } + + private void handleSnapshotFile(File file, DropBoxManager dropbox) { + try { + dropbox.addFile(TAG, file, 0); + if (LOCAL_LOGV) Slog.v(TAG, file.getPath() + " added to dropbox"); + } catch (IOException e) { + Slog.e(TAG, "Can't add " + file.getPath() + " to dropbox", e); + } finally { + file.delete(); + } + } + + private void registerSettingObserver(Context context) { + ContentResolver contentResolver = context.getContentResolver(); + contentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.SAMPLING_PROFILER_MS), + false, new SamplingProfilerSettingsObserver(contentResolver)); + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("SamplingProfilerService:"); + pw.println("Watching directory: " + SNAPSHOT_DIR); + } + + private class SamplingProfilerSettingsObserver extends ContentObserver { + private ContentResolver mContentResolver; + public SamplingProfilerSettingsObserver(ContentResolver contentResolver) { + super(null); + mContentResolver = contentResolver; + onChange(false); + } + @Override + public void onChange(boolean selfChange) { + Integer samplingProfilerMs = Settings.Secure.getInt( + mContentResolver, Settings.Secure.SAMPLING_PROFILER_MS, 0); + // setting this secure property will start or stop sampling profiler, + // as well as adjust the the time between taking snapshots. + SystemProperties.set("persist.sys.profiler_ms", samplingProfilerMs.toString()); + } + } +} diff --git a/services/java/com/android/server/ScreenRotationAnimation.java b/services/java/com/android/server/ScreenRotationAnimation.java new file mode 100644 index 0000000..ef00b08 --- /dev/null +++ b/services/java/com/android/server/ScreenRotationAnimation.java @@ -0,0 +1,420 @@ +/* + * Copyright (C) 2010 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.server; // TODO: use com.android.server.wm, once things move there + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.util.DisplayMetrics; +import android.util.Slog; +import android.view.Display; +import android.view.Surface; +import android.view.SurfaceSession; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.view.animation.Transformation; + +class ScreenRotationAnimation { + static final String TAG = "ScreenRotationAnimation"; + static final boolean DEBUG = false; + + static final int FREEZE_LAYER = WindowManagerService.TYPE_LAYER_MULTIPLIER * 200; + + class BlackSurface { + final int left; + final int top; + final Surface surface; + + BlackSurface(SurfaceSession session, int layer, int l, int t, int w, int h) + throws Surface.OutOfResourcesException { + left = l; + top = t; + surface = new Surface(session, 0, "BlackSurface", + -1, w, h, PixelFormat.OPAQUE, Surface.FX_SURFACE_DIM); + surface.setAlpha(1.0f); + surface.setLayer(FREEZE_LAYER); + } + + void setMatrix(Matrix matrix) { + mTmpMatrix.setTranslate(left, top); + mTmpMatrix.postConcat(matrix); + mTmpMatrix.getValues(mTmpFloats); + surface.setPosition((int)mTmpFloats[Matrix.MTRANS_X], + (int)mTmpFloats[Matrix.MTRANS_Y]); + surface.setMatrix( + mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y], + mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]); + if (false) { + Slog.i(TAG, "Black Surface @ (" + left + "," + top + "): (" + + mTmpFloats[Matrix.MTRANS_X] + "," + + mTmpFloats[Matrix.MTRANS_Y] + ") matrix=[" + + mTmpFloats[Matrix.MSCALE_X] + "," + + mTmpFloats[Matrix.MSCALE_Y] + "][" + + mTmpFloats[Matrix.MSKEW_X] + "," + + mTmpFloats[Matrix.MSKEW_Y] + "]"); + } + } + } + + final Context mContext; + final Display mDisplay; + Surface mSurface; + BlackSurface[] mBlackSurfaces; + int mWidth, mHeight; + + int mSnapshotRotation; + int mSnapshotDeltaRotation; + int mOriginalRotation; + int mOriginalWidth, mOriginalHeight; + int mCurRotation; + + Animation mExitAnimation; + final Transformation mExitTransformation = new Transformation(); + Animation mEnterAnimation; + final Transformation mEnterTransformation = new Transformation(); + boolean mStarted; + + final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); + final Matrix mSnapshotInitialMatrix = new Matrix(); + final Matrix mSnapshotFinalMatrix = new Matrix(); + final Matrix mTmpMatrix = new Matrix(); + final float[] mTmpFloats = new float[9]; + + public ScreenRotationAnimation(Context context, Display display, SurfaceSession session, + boolean inTransaction) { + mContext = context; + mDisplay = display; + + display.getMetrics(mDisplayMetrics); + + Bitmap screenshot = Surface.screenshot(0, 0); + + if (screenshot != null) { + // Screenshot does NOT include rotation! + mSnapshotRotation = 0; + mWidth = screenshot.getWidth(); + mHeight = screenshot.getHeight(); + } else { + // Just in case. + mSnapshotRotation = display.getRotation(); + mWidth = mDisplayMetrics.widthPixels; + mHeight = mDisplayMetrics.heightPixels; + } + + mOriginalRotation = display.getRotation(); + mOriginalWidth = mDisplayMetrics.widthPixels; + mOriginalHeight = mDisplayMetrics.heightPixels; + + if (!inTransaction) { + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, + ">>> OPEN TRANSACTION ScreenRotationAnimation"); + Surface.openTransaction(); + } + + try { + try { + mSurface = new Surface(session, 0, "FreezeSurface", + -1, mWidth, mHeight, PixelFormat.OPAQUE, 0); + mSurface.setLayer(FREEZE_LAYER + 1); + } catch (Surface.OutOfResourcesException e) { + Slog.w(TAG, "Unable to allocate freeze surface", e); + } + + setRotation(display.getRotation()); + + if (mSurface != null) { + Rect dirty = new Rect(0, 0, mWidth, mHeight); + Canvas c = null; + try { + c = mSurface.lockCanvas(dirty); + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Unable to lock surface", e); + return; + } catch (Surface.OutOfResourcesException e) { + Slog.w(TAG, "Unable to lock surface", e); + return; + } + if (c == null) { + Slog.w(TAG, "Null surface"); + return; + } + + if (screenshot != null) { + Paint paint = new Paint(0); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); + c.drawBitmap(screenshot, 0, 0, paint); + } else { + c.drawColor(Color.BLACK, PorterDuff.Mode.SRC); + } + + mSurface.unlockCanvasAndPost(c); + } + } finally { + if (!inTransaction) { + Surface.closeTransaction(); + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, + "<<< CLOSE TRANSACTION ScreenRotationAnimation"); + } + + if (screenshot != null) { + screenshot.recycle(); + } + } + } + + static int deltaRotation(int oldRotation, int newRotation) { + int delta = newRotation - oldRotation; + if (delta < 0) delta += 4; + return delta; + } + + void setSnapshotTransform(Matrix matrix, float alpha) { + if (mSurface != null) { + matrix.getValues(mTmpFloats); + mSurface.setPosition((int)mTmpFloats[Matrix.MTRANS_X], + (int)mTmpFloats[Matrix.MTRANS_Y]); + mSurface.setMatrix( + mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y], + mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]); + mSurface.setAlpha(alpha); + if (DEBUG) { + float[] srcPnts = new float[] { 0, 0, mWidth, mHeight }; + float[] dstPnts = new float[4]; + matrix.mapPoints(dstPnts, srcPnts); + Slog.i(TAG, "Original : (" + srcPnts[0] + "," + srcPnts[1] + + ")-(" + srcPnts[2] + "," + srcPnts[3] + ")"); + Slog.i(TAG, "Transformed: (" + dstPnts[0] + "," + dstPnts[1] + + ")-(" + dstPnts[2] + "," + dstPnts[3] + ")"); + } + } + } + + public static void createRotationMatrix(int rotation, int width, int height, + Matrix outMatrix) { + switch (rotation) { + case Surface.ROTATION_0: + outMatrix.reset(); + break; + case Surface.ROTATION_90: + outMatrix.setRotate(90, 0, 0); + outMatrix.postTranslate(height, 0); + break; + case Surface.ROTATION_180: + outMatrix.setRotate(180, 0, 0); + outMatrix.postTranslate(width, height); + break; + case Surface.ROTATION_270: + outMatrix.setRotate(270, 0, 0); + outMatrix.postTranslate(0, width); + break; + } + } + + // Must be called while in a transaction. + public void setRotation(int rotation) { + mCurRotation = rotation; + + // Compute the transformation matrix that must be applied + // to the snapshot to make it stay in the same original position + // with the current screen rotation. + int delta = deltaRotation(rotation, mSnapshotRotation); + createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix); + + if (DEBUG) Slog.v(TAG, "**** ROTATION: " + delta); + setSnapshotTransform(mSnapshotInitialMatrix, 1.0f); + } + + /** + * Returns true if animating. + */ + public boolean dismiss(SurfaceSession session, long maxAnimationDuration, + float animationScale) { + // Figure out how the screen has moved from the original rotation. + int delta = deltaRotation(mCurRotation, mOriginalRotation); + if (false && delta == 0) { + // Nothing changed, just remove the snapshot. + if (mSurface != null) { + mSurface.destroy(); + mSurface = null; + } + return false; + } + + switch (delta) { + case Surface.ROTATION_0: + mExitAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_0_exit); + mEnterAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_0_enter); + break; + case Surface.ROTATION_90: + mExitAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_plus_90_exit); + mEnterAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_plus_90_enter); + break; + case Surface.ROTATION_180: + mExitAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_180_exit); + mEnterAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_180_enter); + break; + case Surface.ROTATION_270: + mExitAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_minus_90_exit); + mEnterAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_minus_90_enter); + break; + } + + mDisplay.getMetrics(mDisplayMetrics); + + // Initialize the animations. This is a hack, redefining what "parent" + // means to allow supplying the last and next size. In this definition + // "%p" is the original (let's call it "previous") size, and "%" is the + // screen's current/new size. + mEnterAnimation.initialize(mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels, + mOriginalWidth, mOriginalHeight); + mExitAnimation.initialize(mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels, + mOriginalWidth, mOriginalHeight); + mStarted = false; + + mExitAnimation.restrictDuration(maxAnimationDuration); + mExitAnimation.scaleCurrentDuration(animationScale); + mEnterAnimation.restrictDuration(maxAnimationDuration); + mEnterAnimation.scaleCurrentDuration(animationScale); + + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, + ">>> OPEN TRANSACTION ScreenRotationAnimation.dismiss"); + Surface.openTransaction(); + + mBlackSurfaces = new BlackSurface[4]; + try { + final int w = mDisplayMetrics.widthPixels; + final int h = mDisplayMetrics.heightPixels; + mBlackSurfaces[0] = new BlackSurface(session, FREEZE_LAYER, -w, -h, w, h*2); + mBlackSurfaces[1] = new BlackSurface(session, FREEZE_LAYER, 0, -h, w*2, h); + mBlackSurfaces[2] = new BlackSurface(session, FREEZE_LAYER, w, 0, w, h*2); + mBlackSurfaces[3] = new BlackSurface(session, FREEZE_LAYER, -w, h, w*2, h); + } catch (Surface.OutOfResourcesException e) { + Slog.w(TAG, "Unable to allocate black surface", e); + } finally { + Surface.closeTransaction(); + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, + "<<< CLOSE TRANSACTION ScreenRotationAnimation.dismiss"); + } + + return true; + } + + public void kill() { + if (mSurface != null) { + mSurface.destroy(); + mSurface = null; + } + if (mBlackSurfaces != null) { + for (int i=0; i<mBlackSurfaces.length; i++) { + if (mBlackSurfaces[i] != null) { + mBlackSurfaces[i].surface.destroy(); + } + } + mBlackSurfaces = null; + } + if (mExitAnimation != null) { + mExitAnimation.cancel(); + mExitAnimation = null; + } + if (mEnterAnimation != null) { + mEnterAnimation.cancel(); + mEnterAnimation = null; + } + } + + public boolean isAnimating() { + return mEnterAnimation != null || mExitAnimation != null; + } + + public boolean stepAnimation(long now) { + if (mEnterAnimation == null && mExitAnimation == null) { + return false; + } + + if (!mStarted) { + mEnterAnimation.setStartTime(now); + mExitAnimation.setStartTime(now); + mStarted = true; + } + + mExitTransformation.clear(); + boolean moreExit = false; + if (mExitAnimation != null) { + moreExit = mExitAnimation.getTransformation(now, mExitTransformation); + if (DEBUG) Slog.v(TAG, "Stepped exit: " + mExitTransformation); + if (!moreExit) { + if (DEBUG) Slog.v(TAG, "Exit animation done!"); + mExitAnimation.cancel(); + mExitAnimation = null; + mExitTransformation.clear(); + if (mSurface != null) { + mSurface.hide(); + } + } + } + + mEnterTransformation.clear(); + boolean moreEnter = false; + if (mEnterAnimation != null) { + moreEnter = mEnterAnimation.getTransformation(now, mEnterTransformation); + if (!moreEnter) { + mEnterAnimation.cancel(); + mEnterAnimation = null; + mEnterTransformation.clear(); + if (mBlackSurfaces != null) { + for (int i=0; i<mBlackSurfaces.length; i++) { + if (mBlackSurfaces[i] != null) { + mBlackSurfaces[i].surface.hide(); + } + } + } + } else { + if (mBlackSurfaces != null) { + for (int i=0; i<mBlackSurfaces.length; i++) { + if (mBlackSurfaces[i] != null) { + mBlackSurfaces[i].setMatrix(mEnterTransformation.getMatrix()); + } + } + } + } + } + + mSnapshotFinalMatrix.setConcat(mExitTransformation.getMatrix(), mSnapshotInitialMatrix); + setSnapshotTransform(mSnapshotFinalMatrix, mExitTransformation.getAlpha()); + + return moreEnter || moreExit; + } + + public Transformation getEnterTransformation() { + return mEnterTransformation; + } +} diff --git a/services/java/com/android/server/ShutdownActivity.java b/services/java/com/android/server/ShutdownActivity.java index 64b9c5d..c9d4d01 100644 --- a/services/java/com/android/server/ShutdownActivity.java +++ b/services/java/com/android/server/ShutdownActivity.java @@ -27,19 +27,26 @@ import com.android.internal.app.ShutdownThread; public class ShutdownActivity extends Activity { private static final String TAG = "ShutdownActivity"; + private boolean mReboot; private boolean mConfirm; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mConfirm = getIntent().getBooleanExtra(Intent.EXTRA_KEY_CONFIRM, false); + Intent intent = getIntent(); + mReboot = Intent.ACTION_REBOOT.equals(intent.getAction()); + mConfirm = intent.getBooleanExtra(Intent.EXTRA_KEY_CONFIRM, false); Slog.i(TAG, "onCreate(): confirm=" + mConfirm); Handler h = new Handler(); h.post(new Runnable() { public void run() { - ShutdownThread.shutdown(ShutdownActivity.this, mConfirm); + if (mReboot) { + ShutdownThread.reboot(ShutdownActivity.this, null, mConfirm); + } else { + ShutdownThread.shutdown(ShutdownActivity.this, mConfirm); + } } }); } diff --git a/services/java/com/android/server/StatusBarManagerService.java b/services/java/com/android/server/StatusBarManagerService.java index e5dfb18..5ada77b 100644 --- a/services/java/com/android/server/StatusBarManagerService.java +++ b/services/java/com/android/server/StatusBarManagerService.java @@ -32,6 +32,7 @@ import android.os.Binder; import android.os.Handler; import android.os.SystemClock; import android.util.Slog; +import android.view.View; import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.IStatusBarService; @@ -52,11 +53,13 @@ import java.util.Map; * if they are local, that they just enqueue messages to not deadlock. */ public class StatusBarManagerService extends IStatusBarService.Stub + implements WindowManagerService.OnHardKeyboardStatusChangeListener { static final String TAG = "StatusBarManagerService"; static final boolean SPEW = false; final Context mContext; + final WindowManagerService mWindowManager; Handler mHandler = new Handler(); NotificationCallbacks mNotificationCallbacks; volatile IStatusBar mBar; @@ -66,8 +69,17 @@ public class StatusBarManagerService extends IStatusBarService.Stub // for disabling the status bar ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>(); + IBinder mSysUiVisToken = new Binder(); int mDisabled = 0; + Object mLock = new Object(); + // We usually call it lights out mode, but double negatives are annoying + boolean mLightsOn = true; + boolean mMenuVisible = false; + int mImeWindowVis = 0; + int mImeBackDisposition; + IBinder mImeToken = null; + private class DisableRecord implements IBinder.DeathRecipient { String pkg; int what; @@ -84,6 +96,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub void onSetDisabled(int status); void onClearAll(); void onNotificationClick(String pkg, String tag, int id); + void onNotificationClear(String pkg, String tag, int id); void onPanelRevealed(); void onNotificationError(String pkg, String tag, int id, int uid, int initialPid, String message); @@ -92,8 +105,10 @@ public class StatusBarManagerService extends IStatusBarService.Stub /** * Construct the service, add the status bar view to the window manager */ - public StatusBarManagerService(Context context) { + public StatusBarManagerService(Context context, WindowManagerService windowManager) { mContext = context; + mWindowManager = windowManager; + mWindowManager.setOnHardKeyboardStatusChangeListener(this); final Resources res = context.getResources(); mIcons.defineSlots(res.getStringArray(com.android.internal.R.array.config_statusBarIcons)); @@ -104,22 +119,6 @@ public class StatusBarManagerService extends IStatusBarService.Stub } // ================================================================================ - // Constructing the view - // ================================================================================ - - public void systemReady() { - } - - public void systemReady2() { - ComponentName cn = ComponentName.unflattenFromString( - mContext.getString(com.android.internal.R.string.config_statusBarComponent)); - Intent intent = new Intent(); - intent.setComponent(cn); - Slog.i(TAG, "Starting service: " + cn); - mContext.startService(intent); - } - - // ================================================================================ // From IStatusBarService // ================================================================================ public void expand() { @@ -147,25 +146,29 @@ public class StatusBarManagerService extends IStatusBarService.Stub public void disable(int what, IBinder token, String pkg) { enforceStatusBar(); + synchronized (mLock) { + disableLocked(what, token, pkg); + } + } + + private void disableLocked(int what, IBinder token, String pkg) { // It's important that the the callback and the call to mBar get done // in the same order when multiple threads are calling this function // so they are paired correctly. The messages on the handler will be // handled in the order they were enqueued, but will be outside the lock. - synchronized (mDisableRecords) { - manageDisableListLocked(what, token, pkg); - final int net = gatherDisableActionsLocked(); - if (net != mDisabled) { - mDisabled = net; - mHandler.post(new Runnable() { - public void run() { - mNotificationCallbacks.onSetDisabled(net); - } - }); - if (mBar != null) { - try { - mBar.disable(net); - } catch (RemoteException ex) { + manageDisableListLocked(what, token, pkg); + final int net = gatherDisableActionsLocked(); + if (net != mDisabled) { + mDisabled = net; + mHandler.post(new Runnable() { + public void run() { + mNotificationCallbacks.onSetDisabled(net); } + }); + if (mBar != null) { + try { + mBar.disable(net); + } catch (RemoteException ex) { } } } @@ -240,6 +243,109 @@ public class StatusBarManagerService extends IStatusBarService.Stub } } + /** + * Hide or show the on-screen Menu key. Only call this from the window manager, typically in + * response to a window with FLAG_NEEDS_MENU_KEY set. + */ + public void setMenuKeyVisible(final boolean visible) { + enforceStatusBar(); + + if (SPEW) Slog.d(TAG, (visible?"showing":"hiding") + " MENU key"); + + synchronized(mLock) { + if (mMenuVisible != visible) { + mMenuVisible = visible; + mHandler.post(new Runnable() { + public void run() { + if (mBar != null) { + try { + mBar.setMenuKeyVisible(visible); + } catch (RemoteException ex) { + } + } + } + }); + } + } + } + + public void setImeWindowStatus(final IBinder token, final int vis, final int backDisposition) { + enforceStatusBar(); + + if (SPEW) { + Slog.d(TAG, "swetImeWindowStatus vis=" + vis + " backDisposition=" + backDisposition); + } + + synchronized(mLock) { + // In case of IME change, we need to call up setImeWindowStatus() regardless of + // mImeWindowVis because mImeWindowVis may not have been set to false when the + // previous IME was destroyed. + mImeWindowVis = vis; + mImeBackDisposition = backDisposition; + mImeToken = token; + mHandler.post(new Runnable() { + public void run() { + if (mBar != null) { + try { + mBar.setImeWindowStatus(token, vis, backDisposition); + } catch (RemoteException ex) { + } + } + } + }); + } + } + + public void setSystemUiVisibility(int vis) { + // also allows calls from window manager which is in this process. + enforceStatusBarService(); + + synchronized (mLock) { + final boolean lightsOn = (vis & View.STATUS_BAR_HIDDEN) == 0; + updateLightsOnLocked(lightsOn); + disableLocked(vis & StatusBarManager.DISABLE_MASK, mSysUiVisToken, + "WindowManager.LayoutParams"); + } + } + + private void updateLightsOnLocked(final boolean lightsOn) { + if (mLightsOn != lightsOn) { + mLightsOn = lightsOn; + mHandler.post(new Runnable() { + public void run() { + if (mBar != null) { + try { + mBar.setLightsOn(lightsOn); + } catch (RemoteException ex) { + } + } + } + }); + } + } + + public void setHardKeyboardEnabled(final boolean enabled) { + mHandler.post(new Runnable() { + public void run() { + mWindowManager.setHardKeyboardEnabled(enabled); + } + }); + } + + @Override + public void onHardKeyboardStatusChange(final boolean available, final boolean enabled) { + mHandler.post(new Runnable() { + public void run() { + if (mBar != null) { + try { + mBar.setHardKeyboardStatus(available, enabled); + } catch (RemoteException ex) { + } + } + } + }); + } + private void enforceStatusBar() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR, "StatusBarManagerService"); @@ -255,12 +361,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub "StatusBarManagerService"); } - // ================================================================================ // Callbacks from the status bar service. // ================================================================================ public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList, - List<IBinder> notificationKeys, List<StatusBarNotification> notifications) { + List<IBinder> notificationKeys, List<StatusBarNotification> notifications, + int switches[], List<IBinder> binders) { enforceStatusBarService(); Slog.i(TAG, "registerStatusBar bar=" + bar); @@ -274,6 +380,16 @@ public class StatusBarManagerService extends IStatusBarService.Stub notifications.add(e.getValue()); } } + synchronized (mLock) { + switches[0] = gatherDisableActionsLocked(); + switches[1] = mLightsOn ? 1 : 0; + switches[2] = mMenuVisible ? 1 : 0; + switches[3] = mImeWindowVis; + switches[4] = mImeBackDisposition; + binders.add(mImeToken); + } + switches[5] = mWindowManager.isHardKeyboardAvailable() ? 1 : 0; + switches[6] = mWindowManager.isHardKeyboardEnabled() ? 1 : 0; } /** @@ -301,6 +417,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub mNotificationCallbacks.onNotificationError(pkg, tag, id, uid, initialPid, message); } + public void onNotificationClear(String pkg, String tag, int id) { + enforceStatusBarService(); + + mNotificationCallbacks.onNotificationClear(pkg, tag, id); + } + public void onClearAllNotifications() { enforceStatusBarService(); @@ -364,37 +486,35 @@ public class StatusBarManagerService extends IStatusBarService.Stub Slog.d(TAG, "manageDisableList what=0x" + Integer.toHexString(what) + " pkg=" + pkg); } // update the list - synchronized (mDisableRecords) { - final int N = mDisableRecords.size(); - DisableRecord tok = null; - int i; - for (i=0; i<N; i++) { - DisableRecord t = mDisableRecords.get(i); - if (t.token == token) { - tok = t; - break; - } + final int N = mDisableRecords.size(); + DisableRecord tok = null; + int i; + for (i=0; i<N; i++) { + DisableRecord t = mDisableRecords.get(i); + if (t.token == token) { + tok = t; + break; + } + } + if (what == 0 || !token.isBinderAlive()) { + if (tok != null) { + mDisableRecords.remove(i); + tok.token.unlinkToDeath(tok, 0); } - if (what == 0 || !token.isBinderAlive()) { - if (tok != null) { - mDisableRecords.remove(i); - tok.token.unlinkToDeath(tok, 0); + } else { + if (tok == null) { + tok = new DisableRecord(); + try { + token.linkToDeath(tok, 0); } - } else { - if (tok == null) { - tok = new DisableRecord(); - try { - token.linkToDeath(tok, 0); - } - catch (RemoteException ex) { - return; // give up - } - mDisableRecords.add(tok); + catch (RemoteException ex) { + return; // give up } - tok.what = what; - tok.token = token; - tok.pkg = pkg; + mDisableRecords.add(tok); } + tok.what = what; + tok.token = token; + tok.pkg = pkg; } } @@ -435,7 +555,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub } } - synchronized (mDisableRecords) { + synchronized (mLock) { final int N = mDisableRecords.size(); pw.println(" mDisableRecords.size=" + N + " mDisabled=0x" + Integer.toHexString(mDisabled)); diff --git a/services/java/com/android/server/StrictModeFlash.java b/services/java/com/android/server/StrictModeFlash.java new file mode 100644 index 0000000..0a6c625 --- /dev/null +++ b/services/java/com/android/server/StrictModeFlash.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2010 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.server; // TODO: use com.android.server.wm, once things move there + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.Region; +import android.util.DisplayMetrics; +import android.util.Slog; +import android.view.Display; +import android.view.Surface; +import android.view.SurfaceSession; + +class StrictModeFlash { + private static final String TAG = "StrictModeFlash"; + + Surface mSurface; + int mLastDW; + int mLastDH; + boolean mDrawNeeded; + final int mThickness = 20; + + public StrictModeFlash(Display display, SurfaceSession session) { + final DisplayMetrics dm = new DisplayMetrics(); + display.getMetrics(dm); + + try { + mSurface = new Surface(session, 0, "StrictModeFlash", -1, 1, 1, PixelFormat.TRANSLUCENT, 0); + } catch (Surface.OutOfResourcesException e) { + return; + } + + mSurface.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER * 101); // one more than Watermark? arbitrary. + mSurface.setPosition(0, 0); + mDrawNeeded = true; + } + + private void drawIfNeeded() { + if (!mDrawNeeded) { + return; + } + mDrawNeeded = false; + final int dw = mLastDW; + final int dh = mLastDH; + + Rect dirty = new Rect(0, 0, dw, dh); + Canvas c = null; + try { + c = mSurface.lockCanvas(dirty); + } catch (IllegalArgumentException e) { + } catch (Surface.OutOfResourcesException e) { + } + if (c == null) { + return; + } + + // Top + c.clipRect(new Rect(0, 0, dw, mThickness), Region.Op.REPLACE); + c.drawColor(Color.RED); + // Left + c.clipRect(new Rect(0, 0, mThickness, dh), Region.Op.REPLACE); + c.drawColor(Color.RED); + // Right + c.clipRect(new Rect(dw - mThickness, 0, dw, dh), Region.Op.REPLACE); + c.drawColor(Color.RED); + // Bottom + c.clipRect(new Rect(0, dh - mThickness, dw, dh), Region.Op.REPLACE); + c.drawColor(Color.RED); + + mSurface.unlockCanvasAndPost(c); + } + + // Note: caller responsible for being inside + // Surface.openTransaction() / closeTransaction() + public void setVisibility(boolean on) { + if (mSurface == null) { + return; + } + drawIfNeeded(); + if (on) { + mSurface.show(); + } else { + mSurface.hide(); + } + } + + void positionSurface(int dw, int dh) { + if (mLastDW == dw && mLastDH == dh) { + return; + } + mLastDW = dw; + mLastDH = dh; + mSurface.setSize(dw, dh); + mDrawNeeded = true; + } + +} diff --git a/services/java/com/android/server/SystemBackupAgent.java b/services/java/com/android/server/SystemBackupAgent.java index fff1874..a1f43b4 100644 --- a/services/java/com/android/server/SystemBackupAgent.java +++ b/services/java/com/android/server/SystemBackupAgent.java @@ -16,18 +16,16 @@ package com.android.server; -import android.app.backup.AbsoluteFileBackupHelper; import android.app.backup.BackupDataInput; -import android.app.backup.BackupDataInputStream; import android.app.backup.BackupDataOutput; -import android.app.backup.BackupHelper; import android.app.backup.BackupAgentHelper; +import android.app.backup.WallpaperBackupHelper; import android.content.Context; import android.os.ParcelFileDescriptor; import android.os.ServiceManager; -import android.os.SystemService; import android.util.Slog; + import java.io.File; import java.io.IOException; @@ -54,7 +52,7 @@ public class SystemBackupAgent extends BackupAgentHelper { // TODO: Send a delete for any stored wallpaper image in this case? files = new String[] { WALLPAPER_INFO }; } - addHelper("wallpaper", new AbsoluteFileBackupHelper(SystemBackupAgent.this, files)); + addHelper("wallpaper", new WallpaperBackupHelper(SystemBackupAgent.this, files)); super.onBackup(oldState, data, newState); } @@ -62,12 +60,11 @@ public class SystemBackupAgent extends BackupAgentHelper { public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException { // On restore, we also support a previous data schema "system_files" - addHelper("wallpaper", new AbsoluteFileBackupHelper(SystemBackupAgent.this, + addHelper("wallpaper", new WallpaperBackupHelper(SystemBackupAgent.this, new String[] { WALLPAPER_IMAGE, WALLPAPER_INFO })); - addHelper("system_files", new AbsoluteFileBackupHelper(SystemBackupAgent.this, + addHelper("system_files", new WallpaperBackupHelper(SystemBackupAgent.this, new String[] { WALLPAPER_IMAGE })); - boolean success = false; try { super.onRestore(data, appVersionCode, newState); @@ -75,7 +72,7 @@ public class SystemBackupAgent extends BackupAgentHelper { Context.WALLPAPER_SERVICE); wallpaper.settingsRestored(); } catch (IOException ex) { - // If there was a failure, delete everything for the wallpaper, this is too aggresive, + // If there was a failure, delete everything for the wallpaper, this is too aggressive, // but this is hopefully a rare failure. Slog.d(TAG, "restore failed", ex); (new File(WALLPAPER_IMAGE)).delete(); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index df69b76..33e9908 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -24,6 +24,7 @@ import com.android.internal.os.SamplingProfilerIntegration; import dalvik.system.VMRuntime; import dalvik.system.Zygote; +import android.accounts.AccountManagerService; import android.app.ActivityManagerNative; import android.bluetooth.BluetoothAdapter; import android.content.ComponentName; @@ -32,9 +33,10 @@ import android.content.ContentService; import android.content.Context; import android.content.Intent; import android.content.pm.IPackageManager; +import android.content.res.Configuration; import android.database.ContentObserver; -import android.database.Cursor; import android.media.AudioService; +import android.os.Build; import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; @@ -46,10 +48,12 @@ import android.provider.Settings; import android.server.BluetoothA2dpService; import android.server.BluetoothService; import android.server.search.SearchManagerService; +import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.util.Slog; -import android.accounts.AccountManagerService; +import android.view.Display; +import android.view.WindowManager; import java.io.File; import java.util.Timer; @@ -57,11 +61,8 @@ import java.util.TimerTask; class ServerThread extends Thread { private static final String TAG = "SystemServer"; - private final static boolean INCLUDE_DEMO = false; - private static final int LOG_BOOT_PROGRESS_SYSTEM_RUN = 3010; - - private ContentResolver mContentResolver; + ContentResolver mContentResolver; private class AdbSettingsObserver extends ContentObserver { public AdbSettingsObserver() { @@ -120,12 +121,13 @@ class ServerThread extends Thread { WindowManagerService wm = null; BluetoothService bluetooth = null; BluetoothA2dpService bluetoothA2dp = null; - HeadsetObserver headset = null; + WiredAccessoryObserver wiredAccessory = null; DockObserver dock = null; - UsbObserver usb = null; + UsbService usb = null; UiModeManagerService uiMode = null; RecognitionManagerService recognition = null; ThrottleService throttle = null; + NetworkTimeUpdateService networkTimeUpdater = null; // Critical services... try { @@ -168,13 +170,13 @@ class ServerThread extends Thread { Slog.i(TAG, "System Content Providers"); ActivityManagerService.installSystemProviders(); - Slog.i(TAG, "Battery Service"); - battery = new BatteryService(context); - ServiceManager.addService("battery", battery); - Slog.i(TAG, "Lights Service"); lights = new LightsService(context); + Slog.i(TAG, "Battery Service"); + battery = new BatteryService(context, lights); + ServiceManager.addService("battery", battery); + Slog.i(TAG, "Vibrator Service"); ServiceManager.addService("vibrator", new VibratorService(context)); @@ -202,11 +204,9 @@ class ServerThread extends Thread { // TODO: Use a more reliable check to see if this product should // support Bluetooth - see bug 988521 if (SystemProperties.get("ro.kernel.qemu").equals("1")) { - Slog.i(TAG, "Registering null Bluetooth Service (emulator)"); - ServiceManager.addService(BluetoothAdapter.BLUETOOTH_SERVICE, null); + Slog.i(TAG, "No Bluetooh Service (emulator)"); } else if (factoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL) { - Slog.i(TAG, "Registering null Bluetooth Service (factory test)"); - ServiceManager.addService(BluetoothAdapter.BLUETOOTH_SERVICE, null); + Slog.i(TAG, "No Bluetooth Service (factory test)"); } else { Slog.i(TAG, "Bluetooth Service"); bluetooth = new BluetoothService(context); @@ -215,6 +215,7 @@ class ServerThread extends Thread { bluetoothA2dp = new BluetoothA2dpService(context, bluetooth); ServiceManager.addService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE, bluetoothA2dp); + bluetooth.initAfterA2dpRegistration(); int bluetoothOn = Settings.Secure.getInt(mContentResolver, Settings.Secure.BLUETOOTH_ON, 0); @@ -234,6 +235,7 @@ class ServerThread extends Thread { NotificationManagerService notification = null; WallpaperManagerService wallpaper = null; LocationManagerService location = null; + CountryDetectorService countryDetector = null; if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { try { @@ -246,7 +248,7 @@ class ServerThread extends Thread { try { Slog.i(TAG, "Status Bar"); - statusBar = new StatusBarManagerService(context); + statusBar = new StatusBarManagerService(context, wm); ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar); } catch (Throwable e) { Slog.e(TAG, "Failure starting StatusBarManagerService", e); @@ -345,6 +347,14 @@ class ServerThread extends Thread { } try { + Slog.i(TAG, "Country Detector"); + countryDetector = new CountryDetectorService(context); + ServiceManager.addService(Context.COUNTRY_DETECTOR, countryDetector); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting Country Detector", e); + } + + try { Slog.i(TAG, "Search Service"); ServiceManager.addService(Context.SEARCH_SERVICE, new SearchManagerService(context)); @@ -352,11 +362,6 @@ class ServerThread extends Thread { Slog.e(TAG, "Failure starting Search Service", e); } - if (INCLUDE_DEMO) { - Slog.i(TAG, "Installing demo data..."); - (new DemoThread(context)).start(); - } - try { Slog.i(TAG, "DropBox Service"); ServiceManager.addService(Context.DROPBOX_SERVICE, @@ -381,14 +386,6 @@ class ServerThread extends Thread { } try { - Slog.i(TAG, "Headset Observer"); - // Listen for wired headset changes - headset = new HeadsetObserver(context); - } catch (Throwable e) { - Slog.e(TAG, "Failure starting HeadsetObserver", e); - } - - try { Slog.i(TAG, "Dock Observer"); // Listen for dock station changes dock = new DockObserver(context, power); @@ -397,11 +394,19 @@ class ServerThread extends Thread { } try { + Slog.i(TAG, "Wired Accessory Observer"); + // Listen for wired headset changes + wiredAccessory = new WiredAccessoryObserver(context); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting WiredAccessoryObserver", e); + } + + try { Slog.i(TAG, "USB Observer"); // Listen for USB changes - usb = new UsbObserver(context); + usb = new UsbService(context); } catch (Throwable e) { - Slog.e(TAG, "Failure starting UsbObserver", e); + Slog.e(TAG, "Failure starting UsbService", e); } try { @@ -434,13 +439,32 @@ class ServerThread extends Thread { } catch (Throwable e) { Slog.e(TAG, "Failure starting Recognition Service", e); } - + try { Slog.i(TAG, "DiskStats Service"); ServiceManager.addService("diskstats", new DiskStatsService(context)); } catch (Throwable e) { Slog.e(TAG, "Failure starting DiskStats Service", e); } + + try { + // need to add this service even if SamplingProfilerIntegration.isEnabled() + // is false, because it is this service that detects system property change and + // turns on SamplingProfilerIntegration. Plus, when sampling profiler doesn't work, + // there is little overhead for running this service. + Slog.i(TAG, "SamplingProfiler Service"); + ServiceManager.addService("samplingprofiler", + new SamplingProfilerService(context)); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting SamplingProfiler Service", e); + } + + try { + Slog.i(TAG, "NetworkTimeUpdateService"); + networkTimeUpdater = new NetworkTimeUpdateService(context); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting NetworkTimeUpdate service"); + } } // make sure the ADB_ENABLED setting value matches the secure property value @@ -455,14 +479,11 @@ class ServerThread extends Thread { // we are in safe mode. final boolean safeMode = wm.detectSafeMode(); if (safeMode) { - try { - ActivityManagerNative.getDefault().enterSafeMode(); - // Post the safe mode state in the Zygote class - Zygote.systemInSafeMode = true; - // Disable the JIT for the system_server process - VMRuntime.getRuntime().disableJitCompilation(); - } catch (RemoteException e) { - } + ActivityManagerService.self().enterSafeMode(); + // Post the safe mode state in the Zygote class + Zygote.systemInSafeMode = true; + // Disable the JIT for the system_server process + VMRuntime.getRuntime().disableJitCompilation(); } else { // Enable the JIT for the system_server process VMRuntime.getRuntime().startJitCompilation(); @@ -478,10 +499,21 @@ class ServerThread extends Thread { notification.systemReady(); } - if (statusBar != null) { - statusBar.systemReady(); - } wm.systemReady(); + + if (safeMode) { + ActivityManagerService.self().showSafeModeOverlay(); + } + + // Update the configuration for this context by hand, because we're going + // to start using it before the config change done in wm.systemReady() will + // propagate to it. + Configuration config = wm.computeNewConfiguration(); + DisplayMetrics metrics = new DisplayMetrics(); + WindowManager w = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); + w.getDefaultDisplay().getMetrics(metrics); + context.getResources().updateConfiguration(config, metrics); + power.systemReady(); try { pm.systemReady(); @@ -489,11 +521,11 @@ class ServerThread extends Thread { } // These are needed to propagate to the runnable below. - final StatusBarManagerService statusBarF = statusBar; + final Context contextF = context; final BatteryService batteryF = battery; final ConnectivityService connectivityF = connectivity; final DockObserver dockF = dock; - final UsbObserver usbF = usb; + final UsbService usbF = usb; final ThrottleService throttleF = throttle; final UiModeManagerService uiModeF = uiMode; final AppWidgetService appWidgetF = appWidget; @@ -501,6 +533,8 @@ class ServerThread extends Thread { final InputMethodManagerService immF = imm; final RecognitionManagerService recognitionF = recognition; final LocationManagerService locationF = location; + final CountryDetectorService countryDetectorF = countryDetector; + final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater; // We now tell the activity manager it is okay to run third party // code. It will call back into us once it has gotten to the state @@ -512,7 +546,7 @@ class ServerThread extends Thread { public void run() { Slog.i(TAG, "Making services ready"); - if (statusBarF != null) statusBarF.systemReady2(); + startSystemUi(contextF); if (batteryF != null) batteryF.systemReady(); if (connectivityF != null) connectivityF.systemReady(); if (dockF != null) dockF.systemReady(); @@ -528,7 +562,9 @@ class ServerThread extends Thread { if (wallpaperF != null) wallpaperF.systemReady(); if (immF != null) immF.systemReady(); if (locationF != null) locationF.systemReady(); + if (countryDetectorF != null) countryDetectorF.systemReady(); if (throttleF != null) throttleF.systemReady(); + if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemReady(); } }); @@ -540,39 +576,17 @@ class ServerThread extends Thread { Looper.loop(); Slog.d(TAG, "System ServerThread is exiting!"); } -} -class DemoThread extends Thread -{ - DemoThread(Context context) - { - mContext = context; + static final void startSystemUi(Context context) { + Intent intent = new Intent(); + intent.setComponent(new ComponentName("com.android.systemui", + "com.android.systemui.SystemUIService")); + Slog.d(TAG, "Starting service: " + intent); + context.startService(intent); } - - @Override - public void run() - { - try { - Cursor c = mContext.getContentResolver().query(People.CONTENT_URI, null, null, null, null); - boolean hasData = c != null && c.moveToFirst(); - if (c != null) { - c.deactivate(); - } - if (!hasData) { - DemoDataSet dataset = new DemoDataSet(); - dataset.add(mContext); - } - } catch (Throwable e) { - Slog.e("SystemServer", "Failure installing demo data", e); - } - - } - - Context mContext; } -public class SystemServer -{ +public class SystemServer { private static final String TAG = "SystemServer"; public static final int FACTORY_TEST_OFF = 0; @@ -610,15 +624,18 @@ public class SystemServer timer.schedule(new TimerTask() { @Override public void run() { - SamplingProfilerIntegration.writeSnapshot("system_server"); + SamplingProfilerIntegration.writeSnapshot("system_server", null); } }, SNAPSHOT_INTERVAL, SNAPSHOT_INTERVAL); } + // Mmmmmm... more memory! + dalvik.system.VMRuntime.getRuntime().clearGrowthLimit(); + // The system server has to run all of the time, so it needs to be // as efficient as possible with its memory usage. VMRuntime.getRuntime().setTargetHeapUtilization(0.8f); - + System.loadLibrary("android_servers"); init1(args); } diff --git a/services/java/com/android/server/TelephonyRegistry.java b/services/java/com/android/server/TelephonyRegistry.java index 3370279..eb14180 100644 --- a/services/java/com/android/server/TelephonyRegistry.java +++ b/services/java/com/android/server/TelephonyRegistry.java @@ -19,7 +19,8 @@ package com.android.server; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.net.NetworkUtils; +import android.net.LinkCapabilities; +import android.net.LinkProperties; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; @@ -35,12 +36,14 @@ import android.util.Slog; import java.util.ArrayList; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.net.NetworkInterface; import com.android.internal.app.IBatteryStats; import com.android.internal.telephony.ITelephonyRegistry; import com.android.internal.telephony.IPhoneStateListener; import com.android.internal.telephony.DefaultPhoneNotifier; import com.android.internal.telephony.Phone; +import com.android.internal.telephony.ServiceStateTracker; import com.android.internal.telephony.TelephonyIntents; import com.android.server.am.BatteryStatsService; @@ -63,7 +66,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private final Context mContext; - private final ArrayList<Record> mRecords = new ArrayList(); + // access should be inside synchronized (mRecords) for these two fields + private final ArrayList<IBinder> mRemoveList = new ArrayList<IBinder>(); + private final ArrayList<Record> mRecords = new ArrayList<Record>(); private final IBatteryStats mBatteryStats; @@ -81,7 +86,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private int mDataActivity = TelephonyManager.DATA_ACTIVITY_NONE; - private int mDataConnectionState = TelephonyManager.DATA_CONNECTED; + private int mDataConnectionState = TelephonyManager.DATA_UNKNOWN; private boolean mDataConnectionPossible = false; @@ -89,14 +94,18 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private String mDataConnectionApn = ""; - private String[] mDataConnectionApnTypes = null; + private ArrayList<String> mConnectedApns; - private String mDataConnectionInterfaceName = ""; + private LinkProperties mDataConnectionLinkProperties; + + private LinkCapabilities mDataConnectionLinkCapabilities; private Bundle mCellLocation = new Bundle(); private int mDataConnectionNetworkType; + private int mOtaspMode = ServiceStateTracker.OTASP_UNKNOWN; + static final int PHONE_STATE_PERMISSION_MASK = PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR | PhoneStateListener.LISTEN_CALL_STATE | @@ -121,6 +130,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } mContext = context; mBatteryStats = BatteryStatsService.getService(); + mConnectedApns = new ArrayList<String>(); } public void listen(String pkgForDebug, IPhoneStateListener callback, int events, @@ -153,7 +163,11 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { r.events = events; if (notifyNow) { if ((events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) { - sendServiceState(r, mServiceState); + try { + r.callback.onServiceStateChanged(new ServiceState(mServiceState)); + } catch (RemoteException ex) { + remove(r.binder); + } } if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) { try { @@ -179,7 +193,11 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } } if ((events & PhoneStateListener.LISTEN_CELL_LOCATION) != 0) { - sendCellLocation(r, mCellLocation); + try { + r.callback.onCellLocationChanged(new Bundle(mCellLocation)); + } catch (RemoteException ex) { + remove(r.binder); + } } if ((events & PhoneStateListener.LISTEN_CALL_STATE) != 0) { try { @@ -210,6 +228,13 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } + if ((events & PhoneStateListener.LISTEN_OTASP_CHANGED) != 0) { + try { + r.callback.onOtaspChanged(mOtaspMode); + } catch (RemoteException ex) { + remove(r.binder); + } + } } } } else { @@ -236,16 +261,16 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { synchronized (mRecords) { mCallState = state; mCallIncomingNumber = incomingNumber; - for (int i = mRecords.size() - 1; i >= 0; i--) { - Record r = mRecords.get(i); + for (Record r : mRecords) { if ((r.events & PhoneStateListener.LISTEN_CALL_STATE) != 0) { try { r.callback.onCallStateChanged(state, incomingNumber); } catch (RemoteException ex) { - remove(r.binder); + mRemoveList.add(r.binder); } } } + handleRemoveListLocked(); } broadcastCallStateChanged(state, incomingNumber); } @@ -257,12 +282,16 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { Slog.i(TAG, "notifyServiceState: " + state); synchronized (mRecords) { mServiceState = state; - for (int i = mRecords.size() - 1; i >= 0; i--) { - Record r = mRecords.get(i); + for (Record r : mRecords) { if ((r.events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) { - sendServiceState(r, state); + try { + r.callback.onServiceStateChanged(new ServiceState(state)); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } } } + handleRemoveListLocked(); } broadcastServiceStateChanged(state); } @@ -273,10 +302,13 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } synchronized (mRecords) { mSignalStrength = signalStrength; - for (int i = mRecords.size() - 1; i >= 0; i--) { - Record r = mRecords.get(i); + for (Record r : mRecords) { if ((r.events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) { - sendSignalStrength(r, signalStrength); + try { + r.callback.onSignalStrengthsChanged(new SignalStrength(signalStrength)); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } } if ((r.events & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) { try { @@ -284,10 +316,11 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { r.callback.onSignalStrengthChanged((gsmSignalStrength == 99 ? -1 : gsmSignalStrength)); } catch (RemoteException ex) { - remove(r.binder); + mRemoveList.add(r.binder); } } } + handleRemoveListLocked(); } broadcastSignalStrengthChanged(signalStrength); } @@ -296,19 +329,18 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (!checkNotifyPermission("notifyMessageWaitingChanged()")) { return; } - Slog.i(TAG, "notifyMessageWaitingChanged: " + mwi); synchronized (mRecords) { mMessageWaiting = mwi; - for (int i = mRecords.size() - 1; i >= 0; i--) { - Record r = mRecords.get(i); + for (Record r : mRecords) { if ((r.events & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) { try { r.callback.onMessageWaitingIndicatorChanged(mwi); } catch (RemoteException ex) { - remove(r.binder); + mRemoveList.add(r.binder); } } } + handleRemoveListLocked(); } } @@ -316,19 +348,18 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (!checkNotifyPermission("notifyCallForwardingChanged()")) { return; } - Slog.i(TAG, "notifyCallForwardingChanged: " + cfi); synchronized (mRecords) { mCallForwarding = cfi; - for (int i = mRecords.size() - 1; i >= 0; i--) { - Record r = mRecords.get(i); + for (Record r : mRecords) { if ((r.events & PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR) != 0) { try { r.callback.onCallForwardingIndicatorChanged(cfi); } catch (RemoteException ex) { - remove(r.binder); + mRemoveList.add(r.binder); } } } + handleRemoveListLocked(); } } @@ -338,58 +369,83 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } synchronized (mRecords) { mDataActivity = state; - for (int i = mRecords.size() - 1; i >= 0; i--) { - Record r = mRecords.get(i); + for (Record r : mRecords) { if ((r.events & PhoneStateListener.LISTEN_DATA_ACTIVITY) != 0) { try { r.callback.onDataActivity(state); } catch (RemoteException ex) { - remove(r.binder); + mRemoveList.add(r.binder); } } } + handleRemoveListLocked(); } } public void notifyDataConnection(int state, boolean isDataConnectivityPossible, - String reason, String apn, String[] apnTypes, String interfaceName, int networkType, - String gateway) { + String reason, String apn, String apnType, LinkProperties linkProperties, + LinkCapabilities linkCapabilities, int networkType) { if (!checkNotifyPermission("notifyDataConnection()" )) { return; } Slog.i(TAG, "notifyDataConnection: state=" + state + " isDataConnectivityPossible=" - + isDataConnectivityPossible + " reason=" + reason - + " interfaceName=" + interfaceName + " networkType=" + networkType); + + isDataConnectivityPossible + " reason='" + reason + + "' apn='" + apn + "' apnType=" + apnType + " networkType=" + networkType); synchronized (mRecords) { - mDataConnectionState = state; + boolean modified = false; + if (state == TelephonyManager.DATA_CONNECTED) { + if (!mConnectedApns.contains(apnType)) { + mConnectedApns.add(apnType); + if (mDataConnectionState != state) { + mDataConnectionState = state; + modified = true; + } + } + } else { + if (mConnectedApns.remove(apnType)) { + if (mConnectedApns.isEmpty()) { + mDataConnectionState = state; + modified = true; + } else { + // leave mDataConnectionState as is and + // send out the new status for the APN in question. + } + } + } mDataConnectionPossible = isDataConnectivityPossible; mDataConnectionReason = reason; - mDataConnectionApn = apn; - mDataConnectionApnTypes = apnTypes; - mDataConnectionInterfaceName = interfaceName; - mDataConnectionNetworkType = networkType; - for (int i = mRecords.size() - 1; i >= 0; i--) { - Record r = mRecords.get(i); - if ((r.events & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) { - try { - r.callback.onDataConnectionStateChanged(state, networkType); - } catch (RemoteException ex) { - remove(r.binder); + mDataConnectionLinkProperties = linkProperties; + mDataConnectionLinkCapabilities = linkCapabilities; + if (mDataConnectionNetworkType != networkType) { + mDataConnectionNetworkType = networkType; + // need to tell registered listeners about the new network type + modified = true; + } + if (modified) { + Slog.d(TAG, "onDataConnectionStateChanged(" + state + ", " + networkType + ")"); + for (Record r : mRecords) { + if ((r.events & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) { + try { + r.callback.onDataConnectionStateChanged(state, networkType); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } } } + handleRemoveListLocked(); } } broadcastDataConnectionStateChanged(state, isDataConnectivityPossible, reason, apn, - apnTypes, interfaceName, gateway); + apnType, linkProperties, linkCapabilities); } - public void notifyDataConnectionFailed(String reason) { + public void notifyDataConnectionFailed(String reason, String apnType) { if (!checkNotifyPermission("notifyDataConnectionFailed()")) { return; } /* - * This is commented out because there is on onDataConnectionFailed callback - * on PhoneStateListener. There should be + * This is commented out because there is no onDataConnectionFailed callback + * in PhoneStateListener. There should be. synchronized (mRecords) { mDataConnectionFailedReason = reason; final int N = mRecords.size(); @@ -401,7 +457,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } } */ - broadcastDataConnectionFailed(reason); + broadcastDataConnectionFailed(reason, apnType); } public void notifyCellLocation(Bundle cellLocation) { @@ -410,39 +466,36 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } synchronized (mRecords) { mCellLocation = cellLocation; - for (int i = mRecords.size() - 1; i >= 0; i--) { - Record r = mRecords.get(i); + for (Record r : mRecords) { if ((r.events & PhoneStateListener.LISTEN_CELL_LOCATION) != 0) { - sendCellLocation(r, cellLocation); + try { + r.callback.onCellLocationChanged(new Bundle(cellLocation)); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } } + handleRemoveListLocked(); } } - /** - * Copy the service state object so they can't mess it up in the local calls - */ - public void sendServiceState(Record r, ServiceState state) { - try { - r.callback.onServiceStateChanged(new ServiceState(state)); - } catch (RemoteException ex) { - remove(r.binder); - } - } - - private void sendCellLocation(Record r, Bundle cellLocation) { - try { - r.callback.onCellLocationChanged(new Bundle(cellLocation)); - } catch (RemoteException ex) { - remove(r.binder); + public void notifyOtaspChanged(int otaspMode) { + if (!checkNotifyPermission("notifyOtaspChanged()" )) { + return; } - } - - private void sendSignalStrength(Record r, SignalStrength signalStrength) { - try { - r.callback.onSignalStrengthsChanged(new SignalStrength(signalStrength)); - } catch (RemoteException ex) { - remove(r.binder); + synchronized (mRecords) { + mOtaspMode = otaspMode; + for (Record r : mRecords) { + if ((r.events & PhoneStateListener.LISTEN_OTASP_CHANGED) != 0) { + try { + r.callback.onOtaspChanged(otaspMode); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + } + handleRemoveListLocked(); } } @@ -468,11 +521,11 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { pw.println(" mDataConnectionPossible=" + mDataConnectionPossible); pw.println(" mDataConnectionReason=" + mDataConnectionReason); pw.println(" mDataConnectionApn=" + mDataConnectionApn); - pw.println(" mDataConnectionInterfaceName=" + mDataConnectionInterfaceName); + pw.println(" mDataConnectionLinkProperties=" + mDataConnectionLinkProperties); + pw.println(" mDataConnectionLinkCapabilities=" + mDataConnectionLinkCapabilities); pw.println(" mCellLocation=" + mCellLocation); pw.println("registrations: count=" + recordCount); - for (int i = 0; i < recordCount; i++) { - Record r = mRecords.get(i); + for (Record r : mRecords) { pw.println(" " + r.pkgForDebug + " 0x" + Integer.toHexString(r.events)); } } @@ -543,7 +596,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private void broadcastDataConnectionStateChanged(int state, boolean isDataConnectivityPossible, - String reason, String apn, String[] apnTypes, String interfaceName, String gateway) { + String reason, String apn, String apnType, LinkProperties linkProperties, + LinkCapabilities linkCapabilities) { // Note: not reporting to the battery stats service here, because the // status bar takes care of that after taking into account all of the // required info. @@ -556,29 +610,26 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (reason != null) { intent.putExtra(Phone.STATE_CHANGE_REASON_KEY, reason); } - intent.putExtra(Phone.DATA_APN_KEY, apn); - String types = new String(""); - if (apnTypes.length > 0) { - types = apnTypes[0]; - for (int i = 1; i < apnTypes.length; i++) { - types = types+","+apnTypes[i]; + if (linkProperties != null) { + intent.putExtra(Phone.DATA_LINK_PROPERTIES_KEY, linkProperties); + String iface = linkProperties.getInterfaceName(); + if (iface != null) { + intent.putExtra(Phone.DATA_IFACE_NAME_KEY, iface); } } - intent.putExtra(Phone.DATA_APN_TYPES_KEY, types); - intent.putExtra(Phone.DATA_IFACE_NAME_KEY, interfaceName); - int gatewayAddr = 0; - if (gateway != null) { - gatewayAddr = NetworkUtils.v4StringToInt(gateway); + if (linkCapabilities != null) { + intent.putExtra(Phone.DATA_LINK_CAPABILITIES_KEY, linkCapabilities); } - intent.putExtra(Phone.DATA_GATEWAY_KEY, gatewayAddr); - + intent.putExtra(Phone.DATA_APN_KEY, apn); + intent.putExtra(Phone.DATA_APN_TYPE_KEY, apnType); mContext.sendStickyBroadcast(intent); } - private void broadcastDataConnectionFailed(String reason) { + private void broadcastDataConnectionFailed(String reason, String apnType) { Intent intent = new Intent(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); intent.putExtra(Phone.FAILURE_REASON_KEY, reason); + intent.putExtra(Phone.DATA_APN_TYPE_KEY, apnType); mContext.sendStickyBroadcast(intent); } @@ -605,4 +656,13 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { android.Manifest.permission.READ_PHONE_STATE, null); } } + + private void handleRemoveListLocked() { + if (mRemoveList.size() > 0) { + for (IBinder b: mRemoveList) { + remove(b); + } + mRemoveList.clear(); + } + } } diff --git a/services/java/com/android/server/ThrottleService.java b/services/java/com/android/server/ThrottleService.java index a93a6ee..d841cb3 100644 --- a/services/java/com/android/server/ThrottleService.java +++ b/services/java/com/android/server/ThrottleService.java @@ -60,6 +60,8 @@ import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.Calendar; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import java.util.GregorianCalendar; import java.util.Properties; import java.util.Random; @@ -83,8 +85,8 @@ public class ThrottleService extends IThrottleManager.Stub { private static final long TESTING_THRESHOLD = 1 * 1024 * 1024; private int mPolicyPollPeriodSec; - private long mPolicyThreshold; - private int mPolicyThrottleValue; + private AtomicLong mPolicyThreshold; + private AtomicInteger mPolicyThrottleValue; private int mPolicyResetDay; // 1-28 private int mPolicyNotificationsAllowedMask; @@ -114,7 +116,7 @@ public class ThrottleService extends IThrottleManager.Stub { private InterfaceObserver mInterfaceObserver; private SettingsObserver mSettingsObserver; - private int mThrottleIndex; // 0 for none, 1 for first throttle val, 2 for next, etc + private AtomicInteger mThrottleIndex; // 0 for none, 1 for first throttle val, 2 for next, etc private static final int THROTTLE_INDEX_UNINITIALIZED = -1; private static final int THROTTLE_INDEX_UNTHROTTLED = 0; @@ -126,6 +128,10 @@ public class ThrottleService extends IThrottleManager.Stub { if (VDBG) Slog.v(TAG, "Starting ThrottleService"); mContext = context; + mPolicyThreshold = new AtomicLong(); + mPolicyThrottleValue = new AtomicInteger(); + mThrottleIndex = new AtomicInteger(); + mNtpActive = false; mIface = mContext.getResources().getString(R.string.config_datause_iface); @@ -214,7 +220,7 @@ public class ThrottleService extends IThrottleManager.Stub { } private long ntpToWallTime(long ntpTime) { - long bestNow = getBestTime(); + long bestNow = getBestTime(true); // do it quickly long localNow = System.currentTimeMillis(); return localNow + (ntpTime - bestNow); } @@ -222,40 +228,42 @@ public class ThrottleService extends IThrottleManager.Stub { // TODO - fetch for the iface // return time in the local, system wall time, correcting for the use of ntp - public synchronized long getResetTime(String iface) { + public long getResetTime(String iface) { enforceAccessPermission(); long resetTime = 0; if (mRecorder != null) { - resetTime = ntpToWallTime(mRecorder.getPeriodEnd()); + resetTime = mRecorder.getPeriodEnd(); } + resetTime = ntpToWallTime(resetTime); return resetTime; } // TODO - fetch for the iface // return time in the local, system wall time, correcting for the use of ntp - public synchronized long getPeriodStartTime(String iface) { - enforceAccessPermission(); + public long getPeriodStartTime(String iface) { long startTime = 0; + enforceAccessPermission(); if (mRecorder != null) { - startTime = ntpToWallTime(mRecorder.getPeriodStart()); + startTime = mRecorder.getPeriodStart(); } + startTime = ntpToWallTime(startTime); return startTime; } //TODO - a better name? getCliffByteCountThreshold? // TODO - fetch for the iface - public synchronized long getCliffThreshold(String iface, int cliff) { + public long getCliffThreshold(String iface, int cliff) { enforceAccessPermission(); if (cliff == 1) { - return mPolicyThreshold; + return mPolicyThreshold.get(); } return 0; } // TODO - a better name? getThrottleRate? // TODO - fetch for the iface - public synchronized int getCliffLevel(String iface, int cliff) { + public int getCliffLevel(String iface, int cliff) { enforceAccessPermission(); if (cliff == 1) { - return mPolicyThrottleValue; + return mPolicyThrottleValue.get(); } return 0; } @@ -267,10 +275,9 @@ public class ThrottleService extends IThrottleManager.Stub { } // TODO - fetch for the iface - public synchronized long getByteCount(String iface, int dir, int period, int ago) { + public long getByteCount(String iface, int dir, int period, int ago) { enforceAccessPermission(); - if ((period == ThrottleManager.PERIOD_CYCLE) && - (mRecorder != null)) { + if ((period == ThrottleManager.PERIOD_CYCLE) && (mRecorder != null)) { if (dir == ThrottleManager.DIRECTION_TX) return mRecorder.getPeriodTx(ago); if (dir == ThrottleManager.DIRECTION_RX) return mRecorder.getPeriodRx(ago); } @@ -279,10 +286,10 @@ public class ThrottleService extends IThrottleManager.Stub { // TODO - a better name - getCurrentThrottleRate? // TODO - fetch for the iface - public synchronized int getThrottle(String iface) { + public int getThrottle(String iface) { enforceAccessPermission(); - if (mThrottleIndex == 1) { - return mPolicyThrottleValue; + if (mThrottleIndex.get() == 1) { + return mPolicyThrottleValue.get(); } return 0; } @@ -305,22 +312,6 @@ public class ThrottleService extends IThrottleManager.Stub { } }, new IntentFilter(ACTION_RESET)); - // use a new thread as we don't want to stall the system for file writes - mThread = new HandlerThread(TAG); - mThread.start(); - mHandler = new MyHandler(mThread.getLooper()); - mHandler.obtainMessage(EVENT_REBOOT_RECOVERY).sendToTarget(); - - mInterfaceObserver = new InterfaceObserver(mHandler, EVENT_IFACE_UP, mIface); - try { - mNMService.registerObserver(mInterfaceObserver); - } catch (RemoteException e) { - Slog.e(TAG, "Could not register InterfaceObserver " + e); - } - - mSettingsObserver = new SettingsObserver(mHandler, EVENT_POLICY_CHANGED); - mSettingsObserver.observe(mContext); - FileInputStream stream = null; try { Properties properties = new Properties(); @@ -337,6 +328,22 @@ public class ThrottleService extends IThrottleManager.Stub { } catch (Exception e) {} } } + + // use a new thread as we don't want to stall the system for file writes + mThread = new HandlerThread(TAG); + mThread.start(); + mHandler = new MyHandler(mThread.getLooper()); + mHandler.obtainMessage(EVENT_REBOOT_RECOVERY).sendToTarget(); + + mInterfaceObserver = new InterfaceObserver(mHandler, EVENT_IFACE_UP, mIface); + try { + mNMService.registerObserver(mInterfaceObserver); + } catch (RemoteException e) { + Slog.e(TAG, "Could not register InterfaceObserver " + e); + } + + mSettingsObserver = new SettingsObserver(mHandler, EVENT_POLICY_CHANGED); + mSettingsObserver.observe(mContext); } @@ -375,7 +382,7 @@ public class ThrottleService extends IThrottleManager.Stub { // check for sim change TODO // reregister for notification of policy change - mThrottleIndex = THROTTLE_INDEX_UNINITIALIZED; + mThrottleIndex.set(THROTTLE_INDEX_UNINITIALIZED); mRecorder = new DataRecorder(mContext, ThrottleService.this); @@ -403,15 +410,16 @@ public class ThrottleService extends IThrottleManager.Stub { R.integer.config_datause_threshold_bytes); int defaultValue = mContext.getResources().getInteger( R.integer.config_datause_throttle_kbitsps); - synchronized (ThrottleService.this) { - mPolicyThreshold = Settings.Secure.getLong(mContext.getContentResolver(), - Settings.Secure.THROTTLE_THRESHOLD_BYTES, defaultThreshold); - mPolicyThrottleValue = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.THROTTLE_VALUE_KBITSPS, defaultValue); - if (testing) { - mPolicyPollPeriodSec = TESTING_POLLING_PERIOD_SEC; - mPolicyThreshold = TESTING_THRESHOLD; - } + long threshold = Settings.Secure.getLong(mContext.getContentResolver(), + Settings.Secure.THROTTLE_THRESHOLD_BYTES, defaultThreshold); + int value = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.THROTTLE_VALUE_KBITSPS, defaultValue); + + mPolicyThreshold.set(threshold); + mPolicyThrottleValue.set(value); + if (testing) { + mPolicyPollPeriodSec = TESTING_POLLING_PERIOD_SEC; + mPolicyThreshold.set(TESTING_THRESHOLD); } mPolicyResetDay = Settings.Secure.getInt(mContext.getContentResolver(), @@ -423,10 +431,8 @@ public class ThrottleService extends IThrottleManager.Stub { Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.THROTTLE_RESET_DAY, mPolicyResetDay); } - synchronized (ThrottleService.this) { - if (mIface == null) { - mPolicyThreshold = 0; - } + if (mIface == null) { + mPolicyThreshold.set(0); } int defaultNotificationType = mContext.getResources().getInteger( @@ -437,15 +443,16 @@ public class ThrottleService extends IThrottleManager.Stub { mMaxNtpCacheAgeSec = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.THROTTLE_MAX_NTP_CACHE_AGE_SEC, MAX_NTP_CACHE_AGE_SEC); - if (VDBG || (mPolicyThreshold != 0)) { + if (VDBG || (mPolicyThreshold.get() != 0)) { Slog.d(TAG, "onPolicyChanged testing=" + testing +", period=" + - mPolicyPollPeriodSec + ", threshold=" + mPolicyThreshold + ", value=" + - mPolicyThrottleValue + ", resetDay=" + mPolicyResetDay + ", noteType=" + - mPolicyNotificationsAllowedMask + ", maxNtpCacheAge=" + mMaxNtpCacheAgeSec); + mPolicyPollPeriodSec + ", threshold=" + mPolicyThreshold.get() + + ", value=" + mPolicyThrottleValue.get() + ", resetDay=" + mPolicyResetDay + + ", noteType=" + mPolicyNotificationsAllowedMask + ", maxNtpCacheAge=" + + mMaxNtpCacheAgeSec); } // force updates - mThrottleIndex = THROTTLE_INDEX_UNINITIALIZED; + mThrottleIndex.set(THROTTLE_INDEX_UNINITIALIZED); onResetAlarm(); @@ -487,7 +494,7 @@ public class ThrottleService extends IThrottleManager.Stub { long periodRx = mRecorder.getPeriodRx(0); long periodTx = mRecorder.getPeriodTx(0); long total = periodRx + periodTx; - if (VDBG || (mPolicyThreshold != 0)) { + if (VDBG || (mPolicyThreshold.get() != 0)) { Slog.d(TAG, "onPollAlarm - roaming =" + roaming + ", read =" + incRead + ", written =" + incWrite + ", new total =" + total); } @@ -510,11 +517,11 @@ public class ThrottleService extends IThrottleManager.Stub { private void onIfaceUp() { // if we were throttled before, be sure and set it again - the iface went down // (and may have disappeared all together) and these settings were lost - if (mThrottleIndex == 1) { + if (mThrottleIndex.get() == 1) { try { mNMService.setInterfaceThrottle(mIface, -1, -1); mNMService.setInterfaceThrottle(mIface, - mPolicyThrottleValue, mPolicyThrottleValue); + mPolicyThrottleValue.get(), mPolicyThrottleValue.get()); } catch (Exception e) { Slog.e(TAG, "error setting Throttle: " + e); } @@ -523,7 +530,8 @@ public class ThrottleService extends IThrottleManager.Stub { private void checkThrottleAndPostNotification(long currentTotal) { // is throttling enabled? - if (mPolicyThreshold == 0) { + long threshold = mPolicyThreshold.get(); + if (threshold == 0) { clearThrottleAndNotification(); return; } @@ -535,15 +543,13 @@ public class ThrottleService extends IThrottleManager.Stub { } // check if we need to throttle - if (currentTotal > mPolicyThreshold) { - if (mThrottleIndex != 1) { - synchronized (ThrottleService.this) { - mThrottleIndex = 1; - } - if (DBG) Slog.d(TAG, "Threshold " + mPolicyThreshold + " exceeded!"); + if (currentTotal > threshold) { + if (mThrottleIndex.get() != 1) { + mThrottleIndex.set(1); + if (DBG) Slog.d(TAG, "Threshold " + threshold + " exceeded!"); try { mNMService.setInterfaceThrottle(mIface, - mPolicyThrottleValue, mPolicyThrottleValue); + mPolicyThrottleValue.get(), mPolicyThrottleValue.get()); } catch (Exception e) { Slog.e(TAG, "error setting Throttle: " + e); } @@ -556,7 +562,8 @@ public class ThrottleService extends IThrottleManager.Stub { Notification.FLAG_ONGOING_EVENT); Intent broadcast = new Intent(ThrottleManager.THROTTLE_ACTION); - broadcast.putExtra(ThrottleManager.EXTRA_THROTTLE_LEVEL, mPolicyThrottleValue); + broadcast.putExtra(ThrottleManager.EXTRA_THROTTLE_LEVEL, + mPolicyThrottleValue.get()); mContext.sendStickyBroadcast(broadcast); } // else already up! @@ -579,8 +586,8 @@ public class ThrottleService extends IThrottleManager.Stub { long periodLength = end - start; long now = System.currentTimeMillis(); long timeUsed = now - start; - long warningThreshold = 2*mPolicyThreshold*timeUsed/(timeUsed+periodLength); - if ((currentTotal > warningThreshold) && (currentTotal > mPolicyThreshold/4)) { + long warningThreshold = 2*threshold*timeUsed/(timeUsed+periodLength); + if ((currentTotal > warningThreshold) && (currentTotal > threshold/4)) { if (mWarningNotificationSent == false) { mWarningNotificationSent = true; mNotificationManager.cancel(R.drawable.stat_sys_throttled); @@ -625,11 +632,9 @@ public class ThrottleService extends IThrottleManager.Stub { } - private synchronized void clearThrottleAndNotification() { - if (mThrottleIndex != THROTTLE_INDEX_UNTHROTTLED) { - synchronized (ThrottleService.this) { - mThrottleIndex = THROTTLE_INDEX_UNTHROTTLED; - } + private void clearThrottleAndNotification() { + if (mThrottleIndex.get() != THROTTLE_INDEX_UNTHROTTLED) { + mThrottleIndex.set(THROTTLE_INDEX_UNTHROTTLED); try { mNMService.setInterfaceThrottle(mIface, -1, -1); } catch (Exception e) { @@ -687,12 +692,12 @@ public class ThrottleService extends IThrottleManager.Stub { } private void onResetAlarm() { - if (VDBG || (mPolicyThreshold != 0)) { + if (VDBG || (mPolicyThreshold.get() != 0)) { Slog.d(TAG, "onResetAlarm - last period had " + mRecorder.getPeriodRx(0) + " bytes read and " + mRecorder.getPeriodTx(0) + " written"); } - long now = getBestTime(); + long now = getBestTime(false); if (mNtpActive || (mNtpServer == null)) { Calendar end = calculatePeriodEnd(now); @@ -719,20 +724,23 @@ public class ThrottleService extends IThrottleManager.Stub { // will try to get the ntp time and switch to it if found. // will also cache the time so we don't fetch it repeatedly. - getBestTime(); + getBestTime(false); } private static final int MAX_NTP_CACHE_AGE_SEC = 60 * 60 * 24; // 1 day - private static final int MAX_NTP_FETCH_WAIT = 10 * 1000; + private static final int MAX_NTP_FETCH_WAIT = 20 * 1000; private int mMaxNtpCacheAgeSec = MAX_NTP_CACHE_AGE_SEC; private long cachedNtp; private long cachedNtpTimestamp; - private long getBestTime() { + // if the request is tied to UI and ANR's are a danger, request a fast result + // the regular polling should have updated the cached time recently using the + // slower method (!fast) + private long getBestTime(boolean fast) { if (mNtpServer != null) { if (mNtpActive) { long ntpAge = SystemClock.elapsedRealtime() - cachedNtpTimestamp; - if (ntpAge < mMaxNtpCacheAgeSec * 1000) { + if (ntpAge < mMaxNtpCacheAgeSec * 1000 || fast) { if (VDBG) Slog.v(TAG, "using cached time"); return cachedNtp + ntpAge; } @@ -1025,39 +1033,57 @@ public class ThrottleService extends IThrottleManager.Stub { if (DBG) Slog.d(TAG, "data file empty"); return; } - synchronized (mParent) { - String[] parsed = data.split(":"); - int parsedUsed = 0; - if (parsed.length < 6) { - Slog.e(TAG, "reading data file with insufficient length - ignoring"); - return; - } + String[] parsed = data.split(":"); + int parsedUsed = 0; + if (parsed.length < 6) { + Slog.e(TAG, "reading data file with insufficient length - ignoring"); + return; + } + int periodCount; + long[] periodRxData; + long[] periodTxData; + int currentPeriod; + Calendar periodStart; + Calendar periodEnd; + try { if (Integer.parseInt(parsed[parsedUsed++]) != DATA_FILE_VERSION) { Slog.e(TAG, "reading data file with bad version - ignoring"); return; } - mPeriodCount = Integer.parseInt(parsed[parsedUsed++]); - if (parsed.length != 5 + (2 * mPeriodCount)) { + periodCount = Integer.parseInt(parsed[parsedUsed++]); + if (parsed.length != 5 + (2 * periodCount)) { Slog.e(TAG, "reading data file with bad length (" + parsed.length + - " != " + (5+(2*mPeriodCount)) + ") - ignoring"); + " != " + (5 + (2 * periodCount)) + ") - ignoring"); return; } - - mPeriodRxData = new long[mPeriodCount]; - for(int i = 0; i < mPeriodCount; i++) { - mPeriodRxData[i] = Long.parseLong(parsed[parsedUsed++]); + periodRxData = new long[periodCount]; + for (int i = 0; i < periodCount; i++) { + periodRxData[i] = Long.parseLong(parsed[parsedUsed++]); } - mPeriodTxData = new long[mPeriodCount]; - for(int i = 0; i < mPeriodCount; i++) { - mPeriodTxData[i] = Long.parseLong(parsed[parsedUsed++]); + periodTxData = new long[periodCount]; + for (int i = 0; i < periodCount; i++) { + periodTxData[i] = Long.parseLong(parsed[parsedUsed++]); } - mCurrentPeriod = Integer.parseInt(parsed[parsedUsed++]); - mPeriodStart = new GregorianCalendar(); - mPeriodStart.setTimeInMillis(Long.parseLong(parsed[parsedUsed++])); - mPeriodEnd = new GregorianCalendar(); - mPeriodEnd.setTimeInMillis(Long.parseLong(parsed[parsedUsed++])); + + currentPeriod = Integer.parseInt(parsed[parsedUsed++]); + + periodStart = new GregorianCalendar(); + periodStart.setTimeInMillis(Long.parseLong(parsed[parsedUsed++])); + periodEnd = new GregorianCalendar(); + periodEnd.setTimeInMillis(Long.parseLong(parsed[parsedUsed++])); + } catch (Exception e) { + Slog.e(TAG, "Error parsing data file - ignoring"); + return; + } + synchronized (mParent) { + mPeriodCount = periodCount; + mPeriodRxData = periodRxData; + mPeriodTxData = periodTxData; + mCurrentPeriod = currentPeriod; + mPeriodStart = periodStart; + mPeriodEnd = periodEnd; } } @@ -1091,15 +1117,15 @@ public class ThrottleService extends IThrottleManager.Stub { } pw.println(); - pw.println("The threshold is " + mPolicyThreshold + + pw.println("The threshold is " + mPolicyThreshold.get() + ", after which you experince throttling to " + - mPolicyThrottleValue + "kbps"); + mPolicyThrottleValue.get() + "kbps"); pw.println("Current period is " + (mRecorder.getPeriodEnd() - mRecorder.getPeriodStart())/1000 + " seconds long " + "and ends in " + (getResetTime(mIface) - System.currentTimeMillis()) / 1000 + " seconds."); pw.println("Polling every " + mPolicyPollPeriodSec + " seconds"); - pw.println("Current Throttle Index is " + mThrottleIndex); + pw.println("Current Throttle Index is " + mThrottleIndex.get()); pw.println("Max NTP Cache Age is " + mMaxNtpCacheAgeSec); for (int i = 0; i < mRecorder.getPeriodCount(); i++) { diff --git a/services/java/com/android/server/UsbObserver.java b/services/java/com/android/server/UsbObserver.java deleted file mode 100644 index d08fe9b..0000000 --- a/services/java/com/android/server/UsbObserver.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) 2010 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.server; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.hardware.Usb; -import android.net.Uri; -import android.os.Handler; -import android.os.Message; -import android.os.UEventObserver; -import android.provider.Settings; -import android.util.Log; -import android.util.Slog; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.util.ArrayList; - -/** - * <p>UsbObserver monitors for changes to USB state. - */ -class UsbObserver extends UEventObserver { - private static final String TAG = UsbObserver.class.getSimpleName(); - private static final boolean LOG = false; - - private static final String USB_CONFIGURATION_MATCH = "DEVPATH=/devices/virtual/switch/usb_configuration"; - private static final String USB_FUNCTIONS_MATCH = "DEVPATH=/devices/virtual/usb_composite/"; - private static final String USB_CONFIGURATION_PATH = "/sys/class/switch/usb_configuration/state"; - private static final String USB_COMPOSITE_CLASS_PATH = "/sys/class/usb_composite"; - - private static final int MSG_UPDATE = 0; - - private int mUsbConfig = 0; - private int mPreviousUsbConfig = 0; - - // lists of enabled and disabled USB functions - private final ArrayList<String> mEnabledFunctions = new ArrayList<String>(); - private final ArrayList<String> mDisabledFunctions = new ArrayList<String>(); - - private boolean mSystemReady; - - private final Context mContext; - - private PowerManagerService mPowerManager; - - public UsbObserver(Context context) { - mContext = context; - init(); // set initial status - - startObserving(USB_CONFIGURATION_MATCH); - startObserving(USB_FUNCTIONS_MATCH); - } - - @Override - public void onUEvent(UEventObserver.UEvent event) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Slog.v(TAG, "USB UEVENT: " + event.toString()); - } - - synchronized (this) { - String switchState = event.get("SWITCH_STATE"); - if (switchState != null) { - try { - int newConfig = Integer.parseInt(switchState); - if (newConfig != mUsbConfig) { - mPreviousUsbConfig = mUsbConfig; - mUsbConfig = newConfig; - // trigger an Intent broadcast - if (mSystemReady) { - update(); - } - } - } catch (NumberFormatException e) { - Slog.e(TAG, "Could not parse switch state from event " + event); - } - } else { - String function = event.get("FUNCTION"); - String enabledStr = event.get("ENABLED"); - if (function != null && enabledStr != null) { - // Note: we do not broadcast a change when a function is enabled or disabled. - // We just record the state change for the next broadcast. - boolean enabled = "1".equals(enabledStr); - if (enabled) { - if (!mEnabledFunctions.contains(function)) { - mEnabledFunctions.add(function); - } - mDisabledFunctions.remove(function); - } else { - if (!mDisabledFunctions.contains(function)) { - mDisabledFunctions.add(function); - } - mEnabledFunctions.remove(function); - } - } - } - } - } - private final void init() { - char[] buffer = new char[1024]; - - try { - FileReader file = new FileReader(USB_CONFIGURATION_PATH); - int len = file.read(buffer, 0, 1024); - mPreviousUsbConfig = mUsbConfig = Integer.valueOf((new String(buffer, 0, len)).trim()); - - } catch (FileNotFoundException e) { - Slog.w(TAG, "This kernel does not have USB configuration switch support"); - } catch (Exception e) { - Slog.e(TAG, "" , e); - } - - try { - File[] files = new File(USB_COMPOSITE_CLASS_PATH).listFiles(); - for (int i = 0; i < files.length; i++) { - File file = new File(files[i], "enable"); - FileReader reader = new FileReader(file); - int len = reader.read(buffer, 0, 1024); - int value = Integer.valueOf((new String(buffer, 0, len)).trim()); - String functionName = files[i].getName(); - if (value == 1) { - mEnabledFunctions.add(functionName); - } else { - mDisabledFunctions.add(functionName); - } - } - } catch (FileNotFoundException e) { - Slog.w(TAG, "This kernel does not have USB composite class support"); - } catch (Exception e) { - Slog.e(TAG, "" , e); - } - } - - void systemReady() { - synchronized (this) { - update(); - mSystemReady = true; - } - } - - private final void update() { - mHandler.sendEmptyMessage(MSG_UPDATE); - } - - private final Handler mHandler = new Handler() { - private void addEnabledFunctions(Intent intent) { - // include state of all USB functions in our extras - for (int i = 0; i < mEnabledFunctions.size(); i++) { - intent.putExtra(mEnabledFunctions.get(i), Usb.USB_FUNCTION_ENABLED); - } - for (int i = 0; i < mDisabledFunctions.size(); i++) { - intent.putExtra(mDisabledFunctions.get(i), Usb.USB_FUNCTION_DISABLED); - } - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_UPDATE: - synchronized (this) { - final ContentResolver cr = mContext.getContentResolver(); - - if (Settings.Secure.getInt(cr, - Settings.Secure.DEVICE_PROVISIONED, 0) == 0) { - Slog.i(TAG, "Device not provisioned, skipping USB broadcast"); - return; - } - // Send an Intent containing connected/disconnected state - // and the enabled/disabled state of all USB functions - Intent intent; - boolean usbConnected = (mUsbConfig != 0); - if (usbConnected) { - intent = new Intent(Usb.ACTION_USB_CONNECTED); - addEnabledFunctions(intent); - } else { - intent = new Intent(Usb.ACTION_USB_DISCONNECTED); - } - mContext.sendBroadcast(intent); - - // send a sticky broadcast for clients interested in both connect and disconnect - intent = new Intent(Usb.ACTION_USB_STATE); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - intent.putExtra(Usb.USB_CONNECTED, usbConnected); - addEnabledFunctions(intent); - mContext.sendStickyBroadcast(intent); - } - break; - } - } - }; -} diff --git a/services/java/com/android/server/UsbService.java b/services/java/com/android/server/UsbService.java new file mode 100644 index 0000000..8ef03d4 --- /dev/null +++ b/services/java/com/android/server/UsbService.java @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2010 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.server; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.hardware.UsbManager; +import android.net.Uri; +import android.os.Handler; +import android.os.Message; +import android.os.UEventObserver; +import android.provider.Settings; +import android.util.Log; +import android.util.Slog; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.util.ArrayList; + +/** + * <p>UsbService monitors for changes to USB state. + */ +class UsbService { + private static final String TAG = UsbService.class.getSimpleName(); + private static final boolean LOG = false; + + private static final String USB_CONNECTED_MATCH = + "DEVPATH=/devices/virtual/switch/usb_connected"; + private static final String USB_CONFIGURATION_MATCH = + "DEVPATH=/devices/virtual/switch/usb_configuration"; + private static final String USB_FUNCTIONS_MATCH = + "DEVPATH=/devices/virtual/usb_composite/"; + private static final String USB_CONNECTED_PATH = + "/sys/class/switch/usb_connected/state"; + private static final String USB_CONFIGURATION_PATH = + "/sys/class/switch/usb_configuration/state"; + private static final String USB_COMPOSITE_CLASS_PATH = + "/sys/class/usb_composite"; + + private static final int MSG_UPDATE = 0; + + // Delay for debouncing USB disconnects. + // We often get rapid connect/disconnect events when enabling USB functions, + // which need debouncing. + private static final int UPDATE_DELAY = 1000; + + // current connected and configuration state + private int mConnected; + private int mConfiguration; + + // last broadcasted connected and configuration state + private int mLastConnected = -1; + private int mLastConfiguration = -1; + + // lists of enabled and disabled USB functions + private final ArrayList<String> mEnabledFunctions = new ArrayList<String>(); + private final ArrayList<String> mDisabledFunctions = new ArrayList<String>(); + + private boolean mSystemReady; + + private final Context mContext; + + private final UEventObserver mUEventObserver = new UEventObserver() { + @Override + public void onUEvent(UEventObserver.UEvent event) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Slog.v(TAG, "USB UEVENT: " + event.toString()); + } + + synchronized (this) { + String name = event.get("SWITCH_NAME"); + String state = event.get("SWITCH_STATE"); + if (name != null && state != null) { + try { + int intState = Integer.parseInt(state); + if ("usb_connected".equals(name)) { + mConnected = intState; + // trigger an Intent broadcast + if (mSystemReady) { + // debounce disconnects + update(mConnected == 0); + } + } else if ("usb_configuration".equals(name)) { + mConfiguration = intState; + // trigger an Intent broadcast + if (mSystemReady) { + update(mConnected == 0); + } + } + } catch (NumberFormatException e) { + Slog.e(TAG, "Could not parse switch state from event " + event); + } + } else { + String function = event.get("FUNCTION"); + String enabledStr = event.get("ENABLED"); + if (function != null && enabledStr != null) { + // Note: we do not broadcast a change when a function is enabled or disabled. + // We just record the state change for the next broadcast. + boolean enabled = "1".equals(enabledStr); + if (enabled) { + if (!mEnabledFunctions.contains(function)) { + mEnabledFunctions.add(function); + } + mDisabledFunctions.remove(function); + } else { + if (!mDisabledFunctions.contains(function)) { + mDisabledFunctions.add(function); + } + mEnabledFunctions.remove(function); + } + } + } + } + } + }; + + public UsbService(Context context) { + mContext = context; + init(); // set initial status + + if (mConfiguration >= 0) { + mUEventObserver.startObserving(USB_CONNECTED_MATCH); + mUEventObserver.startObserving(USB_CONFIGURATION_MATCH); + mUEventObserver.startObserving(USB_FUNCTIONS_MATCH); + } + } + + private final void init() { + char[] buffer = new char[1024]; + + mConfiguration = -1; + try { + FileReader file = new FileReader(USB_CONNECTED_PATH); + int len = file.read(buffer, 0, 1024); + file.close(); + mConnected = Integer.valueOf((new String(buffer, 0, len)).trim()); + + file = new FileReader(USB_CONFIGURATION_PATH); + len = file.read(buffer, 0, 1024); + file.close(); + mConfiguration = Integer.valueOf((new String(buffer, 0, len)).trim()); + + } catch (FileNotFoundException e) { + Slog.i(TAG, "This kernel does not have USB configuration switch support"); + } catch (Exception e) { + Slog.e(TAG, "" , e); + } + if (mConfiguration < 0) + return; + + try { + File[] files = new File(USB_COMPOSITE_CLASS_PATH).listFiles(); + for (int i = 0; i < files.length; i++) { + File file = new File(files[i], "enable"); + FileReader reader = new FileReader(file); + int len = reader.read(buffer, 0, 1024); + reader.close(); + int value = Integer.valueOf((new String(buffer, 0, len)).trim()); + String functionName = files[i].getName(); + if (value == 1) { + mEnabledFunctions.add(functionName); + } else { + mDisabledFunctions.add(functionName); + } + } + } catch (FileNotFoundException e) { + Slog.w(TAG, "This kernel does not have USB composite class support"); + } catch (Exception e) { + Slog.e(TAG, "" , e); + } + } + + private void initHostSupport() { + // temporarily disabled + } + + void systemReady() { + synchronized (this) { + if (mContext.getResources().getBoolean( + com.android.internal.R.bool.config_hasUsbHostSupport)) { + // start monitoring for connected USB devices + initHostSupport(); + } + + update(false); + mSystemReady = true; + } + } + + private final void update(boolean delayed) { + mHandler.removeMessages(MSG_UPDATE); + mHandler.sendEmptyMessageDelayed(MSG_UPDATE, delayed ? UPDATE_DELAY : 0); + } + + private final Handler mHandler = new Handler() { + private void addEnabledFunctions(Intent intent) { + // include state of all USB functions in our extras + for (int i = 0; i < mEnabledFunctions.size(); i++) { + intent.putExtra(mEnabledFunctions.get(i), UsbManager.USB_FUNCTION_ENABLED); + } + for (int i = 0; i < mDisabledFunctions.size(); i++) { + intent.putExtra(mDisabledFunctions.get(i), UsbManager.USB_FUNCTION_DISABLED); + } + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE: + synchronized (this) { + if (mConnected != mLastConnected || mConfiguration != mLastConfiguration) { + + final ContentResolver cr = mContext.getContentResolver(); + if (Settings.Secure.getInt(cr, + Settings.Secure.DEVICE_PROVISIONED, 0) == 0) { + Slog.i(TAG, "Device not provisioned, skipping USB broadcast"); + return; + } + + mLastConnected = mConnected; + mLastConfiguration = mConfiguration; + + // send a sticky broadcast containing current USB state + Intent intent = new Intent(UsbManager.ACTION_USB_STATE); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra(UsbManager.USB_CONNECTED, mConnected != 0); + intent.putExtra(UsbManager.USB_CONFIGURATION, mConfiguration); + addEnabledFunctions(intent); + mContext.sendStickyBroadcast(intent); + } + } + break; + } + } + }; +} diff --git a/services/java/com/android/server/VibratorService.java b/services/java/com/android/server/VibratorService.java index f0b5955..2fcdb5d 100755 --- a/services/java/com/android/server/VibratorService.java +++ b/services/java/com/android/server/VibratorService.java @@ -112,6 +112,10 @@ public class VibratorService extends IVibratorService.Stub { context.registerReceiver(mIntentReceiver, filter); } + public boolean hasVibrator() { + return vibratorExists(); + } + public void vibrate(long milliseconds, IBinder token) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) { @@ -380,6 +384,7 @@ public class VibratorService extends IVibratorService.Stub { volatile VibrateThread mThread; + native static boolean vibratorExists(); native static void vibratorOn(long milliseconds); native static void vibratorOff(); } diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index 5aa0111..22583f7 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2010 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. @@ -16,92 +16,76 @@ package com.android.server; -import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED; -import static android.net.wifi.WifiManager.WIFI_STATE_DISABLING; -import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED; -import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING; -import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN; - -import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED; -import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING; -import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; -import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING; -import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED; - import android.app.AlarmManager; +import android.app.Notification; +import android.app.NotificationManager; import android.app.PendingIntent; -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.database.ContentObserver; import android.net.wifi.IWifiManager; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.net.wifi.WifiNative; -import android.net.wifi.WifiStateTracker; +import android.net.wifi.WifiStateMachine; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.SupplicantState; import android.net.wifi.WifiConfiguration.KeyMgmt; +import android.net.wifi.WpsConfiguration; +import android.net.wifi.WpsResult; import android.net.ConnectivityManager; import android.net.InterfaceConfiguration; -import android.net.NetworkStateTracker; import android.net.DhcpInfo; -import android.net.NetworkUtils; +import android.net.NetworkInfo; +import android.net.NetworkInfo.State; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.INetworkManagementService; -import android.os.Looper; import android.os.Message; -import android.os.PowerManager; -import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.WorkSource; import android.provider.Settings; -import android.util.Slog; import android.text.TextUtils; +import android.util.Slog; +import java.net.InetAddress; import java.util.ArrayList; -import java.util.BitSet; -import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.regex.Pattern; +import java.util.concurrent.atomic.AtomicBoolean; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.net.UnknownHostException; import com.android.internal.app.IBatteryStats; -import android.app.backup.IBackupManager; +import com.android.internal.util.AsyncChannel; import com.android.server.am.BatteryStatsService; import com.android.internal.R; /** * WifiService handles remote WiFi operation requests by implementing - * the IWifiManager interface. It also creates a WifiMonitor to listen - * for Wifi-related events. + * the IWifiManager interface. * * @hide */ +//TODO: Clean up multiple locks and implement WifiService +// as a SM to track soft AP/client/adhoc bring up based +// on device idle state, airplane mode and boot. + public class WifiService extends IWifiManager.Stub { private static final String TAG = "WifiService"; - private static final boolean DBG = false; - private static final Pattern scanResultPattern = Pattern.compile("\t+"); - private final WifiStateTracker mWifiStateTracker; - /* TODO: fetch a configurable interface */ - private static final String SOFTAP_IFACE = "wl0.1"; + private static final boolean DBG = true; + + private final WifiStateMachine mWifiStateMachine; private Context mContext; - private int mWifiApState; private AlarmManager mAlarmManager; private PendingIntent mIdleIntent; @@ -110,10 +94,11 @@ public class WifiService extends IWifiManager.Stub { private boolean mDeviceIdle; private int mPluggedType; - private enum DriverAction {DRIVER_UNLOAD, NO_DRIVER_UNLOAD}; + /* Chipset supports background scan */ + private final boolean mBackgroundScanSupported; // true if the user enabled Wifi while in airplane mode - private boolean mAirplaneModeOverwridden; + private AtomicBoolean mAirplaneModeOverwridden = new AtomicBoolean(false); private final LockList mLocks = new LockList(); // some wifi lock statistics @@ -131,9 +116,7 @@ public class WifiService extends IWifiManager.Stub { private final IBatteryStats mBatteryStats; - private INetworkManagementService nwService; ConnectivityManager mCm; - private WifiWatchdogService mWifiWatchdogService = null; private String[] mWifiRegexs; /** @@ -143,122 +126,130 @@ public class WifiService extends IWifiManager.Stub { * being enabled but not active exceeds the battery drain caused by * re-establishing a connection to the mobile data network. */ - private static final long DEFAULT_IDLE_MILLIS = 15 * 60 * 1000; /* 15 minutes */ + private static final long DEFAULT_IDLE_MS = 15 * 60 * 1000; /* 15 minutes */ - private static final String WAKELOCK_TAG = "*wifi*"; + private static final String ACTION_DEVICE_IDLE = + "com.android.server.WifiManager.action.DEVICE_IDLE"; + + private boolean mIsReceiverRegistered = false; + + NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", ""); + + // Variables relating to the 'available networks' notification /** - * The maximum amount of time to hold the wake lock after a disconnect - * caused by stopping the driver. Establishing an EDGE connection has been - * observed to take about 5 seconds under normal circumstances. This - * provides a bit of extra margin. - * <p> - * See {@link android.provider.Settings.Secure#WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS}. - * This is the default value if a Settings.Secure value is not present. + * The icon to show in the 'available networks' notification. This will also + * be the ID of the Notification given to the NotificationManager. */ - private static final int DEFAULT_WAKELOCK_TIMEOUT = 8000; - - // Wake lock used by driver-stop operation - private static PowerManager.WakeLock sDriverStopWakeLock; - // Wake lock used by other operations - private static PowerManager.WakeLock sWakeLock; - - private static final int MESSAGE_ENABLE_WIFI = 0; - private static final int MESSAGE_DISABLE_WIFI = 1; - private static final int MESSAGE_STOP_WIFI = 2; - private static final int MESSAGE_START_WIFI = 3; - private static final int MESSAGE_RELEASE_WAKELOCK = 4; - private static final int MESSAGE_UPDATE_STATE = 5; - private static final int MESSAGE_START_ACCESS_POINT = 6; - private static final int MESSAGE_STOP_ACCESS_POINT = 7; - private static final int MESSAGE_SET_CHANNELS = 8; - private static final int MESSAGE_ENABLE_NETWORKS = 9; - private static final int MESSAGE_START_SCAN = 10; - private static final int MESSAGE_REPORT_WORKSOURCE = 11; - private static final int MESSAGE_ENABLE_RSSI_POLLING = 12; - - - private final WifiHandler mWifiHandler; - - /* - * Cache of scan results objects (size is somewhat arbitrary) + private static final int ICON_NETWORKS_AVAILABLE = + com.android.internal.R.drawable.stat_notify_wifi_in_range; + /** + * When a notification is shown, we wait this amount before possibly showing it again. */ - private static final int SCAN_RESULT_CACHE_SIZE = 80; - private final LinkedHashMap<String, ScanResult> mScanResultCache; - - /* - * Character buffer used to parse scan results (optimization) + private final long NOTIFICATION_REPEAT_DELAY_MS; + /** + * Whether the user has set the setting to show the 'available networks' notification. */ - private static final int SCAN_RESULT_BUFFER_SIZE = 512; - private boolean mNeedReconfig; - + private boolean mNotificationEnabled; /** - * Temporary for computing UIDS that are responsible for starting WIFI. - * Protected by mWifiStateTracker lock. + * Observes the user setting to keep {@link #mNotificationEnabled} in sync. */ - private final WorkSource mTmpWorkSource = new WorkSource(); - - /* - * Last UID that asked to enable WIFI. + private NotificationEnabledSettingObserver mNotificationEnabledSettingObserver; + /** + * The {@link System#currentTimeMillis()} must be at least this value for us + * to show the notification again. */ - private int mLastEnableUid = Process.myUid(); - - /* - * Last UID that asked to enable WIFI AP. + private long mNotificationRepeatTime; + /** + * The Notification object given to the NotificationManager. */ - private int mLastApEnableUid = Process.myUid(); - - + private Notification mNotification; + /** + * Whether the notification is being shown, as set by us. That is, if the + * user cancels the notification, we will not receive the callback so this + * will still be true. We only guarantee if this is false, then the + * notification is not showing. + */ + private boolean mNotificationShown; /** - * Number of allowed radio frequency channels in various regulatory domains. - * This list is sufficient for 802.11b/g networks (2.4GHz range). + * The number of continuous scans that must occur before consider the + * supplicant in a scanning state. This allows supplicant to associate with + * remembered networks that are in the scan results. */ - private static int[] sValidRegulatoryChannelCounts = new int[] {11, 13, 14}; + private static final int NUM_SCANS_BEFORE_ACTUALLY_SCANNING = 3; + /** + * The number of scans since the last network state change. When this + * exceeds {@link #NUM_SCANS_BEFORE_ACTUALLY_SCANNING}, we consider the + * supplicant to actually be scanning. When the network state changes to + * something other than scanning, we reset this to 0. + */ + private int mNumScansSinceNetworkStateChange; - private static final String ACTION_DEVICE_IDLE = - "com.android.server.WifiManager.action.DEVICE_IDLE"; + /** + * Asynchronous channel to WifiStateMachine + */ + private AsyncChannel mChannel; - WifiService(Context context, WifiStateTracker tracker) { - mContext = context; - mWifiStateTracker = tracker; - mWifiStateTracker.enableRssiPolling(true); - mBatteryStats = BatteryStatsService.getService(); + /** + * TODO: Possibly change WifiService into an AsyncService. + */ + private class WifiServiceHandler extends Handler { + private AsyncChannel mWshChannel; - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - nwService = INetworkManagementService.Stub.asInterface(b); + WifiServiceHandler(android.os.Looper looper, Context context) { + super(looper); + mWshChannel = new AsyncChannel(); + mWshChannel.connect(context, this, mWifiStateMachine.getHandler()); + } - mScanResultCache = new LinkedHashMap<String, ScanResult>( - SCAN_RESULT_CACHE_SIZE, 0.75f, true) { - /* - * Limit the cache size by SCAN_RESULT_CACHE_SIZE - * elements - */ - public boolean removeEldestEntry(Map.Entry eldest) { - return SCAN_RESULT_CACHE_SIZE < this.size(); + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: { + if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { + mChannel = mWshChannel; + } else { + Slog.d(TAG, "WifiServicehandler.handleMessage could not connect error=" + + msg.arg1); + mChannel = null; + } + break; + } + default: { + Slog.d(TAG, "WifiServicehandler.handleMessage ignoring msg=" + msg); + break; } - }; + } + } + } + WifiServiceHandler mHandler; - HandlerThread wifiThread = new HandlerThread("WifiService"); - wifiThread.start(); - mWifiHandler = new WifiHandler(wifiThread.getLooper()); + /** + * Temporary for computing UIDS that are responsible for starting WIFI. + * Protected by mWifiStateTracker lock. + */ + private final WorkSource mTmpWorkSource = new WorkSource(); - mWifiStateTracker.setWifiState(WIFI_STATE_DISABLED); - mWifiApState = WIFI_AP_STATE_DISABLED; + WifiService(Context context) { + mContext = context; + mWifiStateMachine = new WifiStateMachine(mContext); + mWifiStateMachine.enableRssiPolling(true); + mBatteryStats = BatteryStatsService.getService(); mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); Intent idleIntent = new Intent(ACTION_DEVICE_IDLE, null); mIdleIntent = PendingIntent.getBroadcast(mContext, IDLE_REQUEST, idleIntent, 0); - PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); - sWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG); - sDriverStopWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG); + HandlerThread wifiThread = new HandlerThread("WifiService"); + wifiThread.start(); + mHandler = new WifiServiceHandler(wifiThread.getLooper(), context); mContext.registerReceiver( new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // clear our flag indicating the user has overwridden airplane mode - mAirplaneModeOverwridden = false; + mAirplaneModeOverwridden.set(false); // on airplane disable, restore Wifi if the saved state indicates so if (!isAirplaneModeOn() && testAndClearWifiSavedState()) { persistWifiEnabled(true); @@ -273,14 +264,53 @@ public class WifiService extends IWifiManager.Stub { @Override public void onReceive(Context context, Intent intent) { - ArrayList<String> available = intent.getStringArrayListExtra( - ConnectivityManager.EXTRA_AVAILABLE_TETHER); - ArrayList<String> active = intent.getStringArrayListExtra( - ConnectivityManager.EXTRA_ACTIVE_TETHER); - updateTetherState(available, active); + ArrayList<String> available = intent.getStringArrayListExtra( + ConnectivityManager.EXTRA_AVAILABLE_TETHER); + ArrayList<String> active = intent.getStringArrayListExtra( + ConnectivityManager.EXTRA_ACTIVE_TETHER); + updateTetherState(available, active); } },new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED)); + + IntentFilter filter = new IntentFilter(); + filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); + filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + + mContext.registerReceiver( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { + // reset & clear notification on any wifi state change + resetNotification(); + } else if (intent.getAction().equals( + WifiManager.NETWORK_STATE_CHANGED_ACTION)) { + mNetworkInfo = (NetworkInfo) intent.getParcelableExtra( + WifiManager.EXTRA_NETWORK_INFO); + // reset & clear notification on a network connect & disconnect + switch(mNetworkInfo.getDetailedState()) { + case CONNECTED: + case DISCONNECTED: + resetNotification(); + break; + } + } else if (intent.getAction().equals( + WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { + checkAndSetNotification(); + } + } + }, filter); + + // Setting is in seconds + NOTIFICATION_REPEAT_DELAY_MS = Settings.Secure.getInt(context.getContentResolver(), + Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000l; + mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(new Handler()); + mNotificationEnabledSettingObserver.register(); + + mBackgroundScanSupported = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_wifi_background_scan_support); } /** @@ -289,7 +319,7 @@ public class WifiService extends IWifiManager.Stub { * * This function is used only at boot time */ - public void startWifi() { + public void checkAndStartWifi() { /* Start if Wi-Fi is enabled or the saved state indicates Wi-Fi was on */ boolean wifiEnabled = !isAirplaneModeOn() && (getPersistedWifiEnabled() || testAndClearWifiSavedState()); @@ -306,7 +336,10 @@ public class WifiService extends IWifiManager.Stub { IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); - mCm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + if (mCm == null) { + mCm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + } + mWifiRegexs = mCm.getTetherableWifiRegexs(); for (String intf : available) { @@ -318,25 +351,22 @@ public class WifiService extends IWifiManager.Stub { ifcg = service.getInterfaceConfig(intf); if (ifcg != null) { /* IP/netmask: 192.168.43.1/255.255.255.0 */ - ifcg.ipAddr = (192 << 24) + (168 << 16) + (43 << 8) + 1; - ifcg.netmask = (255 << 24) + (255 << 16) + (255 << 8) + 0; - ifcg.interfaceFlags = "up"; + ifcg.addr = InetAddress.getByName("192.168.43.1"); + ifcg.mask = InetAddress.getByName("255.255.255.0"); + ifcg.interfaceFlags = "[up]"; service.setInterfaceConfig(intf, ifcg); } } catch (Exception e) { Slog.e(TAG, "Error configuring interface " + intf + ", :" + e); - try { - nwService.stopAccessPoint(); - } catch (Exception ee) { - Slog.e(TAG, "Could not stop AP, :" + ee); - } - setWifiApEnabledState(WIFI_AP_STATE_FAILED, 0, DriverAction.DRIVER_UNLOAD); + setWifiApEnabled(null, false); return; } if(mCm.tether(intf) != ConnectivityManager.TETHER_ERROR_NO_ERROR) { - Slog.e(TAG, "Error tethering "+intf); + Slog.e(TAG, "Error tethering on " + intf); + setWifiApEnabled(null, false); + return; } break; } @@ -372,18 +402,18 @@ public class WifiService extends IWifiManager.Stub { Settings.Secure.putInt(cr, Settings.Secure.WIFI_ON, enabled ? 1 : 0); } - NetworkStateTracker getNetworkStateTracker() { - return mWifiStateTracker; - } - /** * see {@link android.net.wifi.WifiManager#pingSupplicant()} - * @return {@code true} if the operation succeeds + * @return {@code true} if the operation succeeds, {@code false} otherwise */ public boolean pingSupplicant() { - enforceChangePermission(); - - return mWifiStateTracker.ping(); + enforceAccessPermission(); + if (mChannel != null) { + return mWifiStateMachine.syncPingSupplicant(mChannel); + } else { + Slog.e(TAG, "mChannel is not initialized"); + return false; + } } /** @@ -391,9 +421,24 @@ public class WifiService extends IWifiManager.Stub { */ public void startScan(boolean forceActive) { enforceChangePermission(); - if (mWifiHandler == null) return; + mWifiStateMachine.startScan(forceActive); + } + + private void enforceAccessPermission() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE, + "WifiService"); + } + + private void enforceChangePermission() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE, + "WifiService"); - Message.obtain(mWifiHandler, MESSAGE_START_SCAN, forceActive ? 1 : 0, 0).sendToTarget(); + } + + private void enforceMulticastChangePermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, + "WifiService"); } /** @@ -402,168 +447,44 @@ public class WifiService extends IWifiManager.Stub { * @return {@code true} if the enable/disable operation was * started or is already in the queue. */ - public boolean setWifiEnabled(boolean enable) { + public synchronized boolean setWifiEnabled(boolean enable) { enforceChangePermission(); - if (mWifiHandler == null) return false; - - synchronized (mWifiHandler) { - // caller may not have WAKE_LOCK permission - it's not required here - long ident = Binder.clearCallingIdentity(); - sWakeLock.acquire(); - Binder.restoreCallingIdentity(ident); - mLastEnableUid = Binder.getCallingUid(); - // set a flag if the user is enabling Wifi while in airplane mode - mAirplaneModeOverwridden = (enable && isAirplaneModeOn() && isAirplaneToggleable()); - sendEnableMessage(enable, true, Binder.getCallingUid()); + if (DBG) { + Slog.e(TAG, "Invoking mWifiStateMachine.setWifiEnabled\n"); } - return true; - } - - /** - * Enables/disables Wi-Fi synchronously. - * @param enable {@code true} to turn Wi-Fi on, {@code false} to turn it off. - * @param persist {@code true} if the setting should be persisted. - * @param uid The UID of the process making the request. - * @return {@code true} if the operation succeeds (or if the existing state - * is the same as the requested state) - */ - private boolean setWifiEnabledBlocking(boolean enable, boolean persist, int uid) { - final int eventualWifiState = enable ? WIFI_STATE_ENABLED : WIFI_STATE_DISABLED; - final int wifiState = mWifiStateTracker.getWifiState(); - - if (wifiState == eventualWifiState) { - return true; - } - if (enable && isAirplaneModeOn() && !mAirplaneModeOverwridden) { - return false; + // set a flag if the user is enabling Wifi while in airplane mode + if (enable && isAirplaneModeOn() && isAirplaneToggleable()) { + mAirplaneModeOverwridden.set(true); } - /** - * Multiple calls to unregisterReceiver() cause exception and a system crash. - * This can happen if a supplicant is lost (or firmware crash occurs) and user indicates - * disable wifi at the same time. - * Avoid doing a disable when the current Wifi state is UNKNOWN - * TODO: Handle driver load fail and supplicant lost as seperate states - */ - if ((wifiState == WIFI_STATE_UNKNOWN) && !enable) { - return false; + if (enable) { + reportStartWorkSource(); } + mWifiStateMachine.setWifiEnabled(enable); - /** - * Fail Wifi if AP is enabled - * TODO: Deprecate WIFI_STATE_UNKNOWN and rename it - * WIFI_STATE_FAILED + /* + * Caller might not have WRITE_SECURE_SETTINGS, + * only CHANGE_WIFI_STATE is enforced */ - if ((mWifiApState == WIFI_AP_STATE_ENABLED) && enable) { - setWifiEnabledState(WIFI_STATE_UNKNOWN, uid); - return false; - } - - setWifiEnabledState(enable ? WIFI_STATE_ENABLING : WIFI_STATE_DISABLING, uid); + long ident = Binder.clearCallingIdentity(); + persistWifiEnabled(enable); + Binder.restoreCallingIdentity(ident); if (enable) { - if (!mWifiStateTracker.loadDriver()) { - Slog.e(TAG, "Failed to load Wi-Fi driver."); - setWifiEnabledState(WIFI_STATE_UNKNOWN, uid); - return false; - } - if (!mWifiStateTracker.startSupplicant()) { - mWifiStateTracker.unloadDriver(); - Slog.e(TAG, "Failed to start supplicant daemon."); - setWifiEnabledState(WIFI_STATE_UNKNOWN, uid); - return false; + if (!mIsReceiverRegistered) { + registerForBroadcasts(); + mIsReceiverRegistered = true; } - - registerForBroadcasts(); - mWifiStateTracker.startEventLoop(); - - } else { - + } else if (mIsReceiverRegistered){ mContext.unregisterReceiver(mReceiver); - // Remove notification (it will no-op if it isn't visible) - mWifiStateTracker.setNotificationVisible(false, 0, false, 0); - - boolean failedToStopSupplicantOrUnloadDriver = false; - - if (!mWifiStateTracker.stopSupplicant()) { - Slog.e(TAG, "Failed to stop supplicant daemon."); - setWifiEnabledState(WIFI_STATE_UNKNOWN, uid); - failedToStopSupplicantOrUnloadDriver = true; - } - - /** - * Reset connections and disable interface - * before we unload the driver - */ - mWifiStateTracker.resetConnections(true); - - if (!mWifiStateTracker.unloadDriver()) { - Slog.e(TAG, "Failed to unload Wi-Fi driver."); - if (!failedToStopSupplicantOrUnloadDriver) { - setWifiEnabledState(WIFI_STATE_UNKNOWN, uid); - failedToStopSupplicantOrUnloadDriver = true; - } - } - - if (failedToStopSupplicantOrUnloadDriver) { - return false; - } + mIsReceiverRegistered = false; } - // Success! - - if (persist) { - persistWifiEnabled(enable); - } - setWifiEnabledState(eventualWifiState, uid); return true; } - private void setWifiEnabledState(int wifiState, int uid) { - final int previousWifiState = mWifiStateTracker.getWifiState(); - - long ident = Binder.clearCallingIdentity(); - try { - if (wifiState == WIFI_STATE_ENABLED) { - mBatteryStats.noteWifiOn(); - } else if (wifiState == WIFI_STATE_DISABLED) { - mBatteryStats.noteWifiOff(); - } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(ident); - } - - // Update state - mWifiStateTracker.setWifiState(wifiState); - - // Broadcast - final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - intent.putExtra(WifiManager.EXTRA_WIFI_STATE, wifiState); - intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, previousWifiState); - mContext.sendStickyBroadcast(intent); - } - - private void enforceAccessPermission() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE, - "WifiService"); - } - - private void enforceChangePermission() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE, - "WifiService"); - - } - - private void enforceMulticastChangePermission() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, - "WifiService"); - } - /** * see {@link WifiManager#getWifiState()} * @return One of {@link WifiManager#WIFI_STATE_DISABLED}, @@ -574,66 +495,59 @@ public class WifiService extends IWifiManager.Stub { */ public int getWifiEnabledState() { enforceAccessPermission(); - return mWifiStateTracker.getWifiState(); - } - - /** - * see {@link android.net.wifi.WifiManager#disconnect()} - * @return {@code true} if the operation succeeds - */ - public boolean disconnect() { - enforceChangePermission(); - - return mWifiStateTracker.disconnect(); - } - - /** - * see {@link android.net.wifi.WifiManager#reconnect()} - * @return {@code true} if the operation succeeds - */ - public boolean reconnect() { - enforceChangePermission(); - - return mWifiStateTracker.reconnectCommand(); - } - - /** - * see {@link android.net.wifi.WifiManager#reassociate()} - * @return {@code true} if the operation succeeds - */ - public boolean reassociate() { - enforceChangePermission(); - - return mWifiStateTracker.reassociate(); + return mWifiStateMachine.syncGetWifiState(); } /** * see {@link android.net.wifi.WifiManager#setWifiApEnabled(WifiConfiguration, boolean)} * @param wifiConfig SSID, security and channel details as * part of WifiConfiguration - * @param enabled, true to enable and false to disable + * @param enabled true to enable and false to disable * @return {@code true} if the start operation was * started or is already in the queue. */ - public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) { + public synchronized boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) { enforceChangePermission(); - if (mWifiHandler == null) return false; - - synchronized (mWifiHandler) { + if (enabled) { + /* Use default config if there is no existing config */ + if (wifiConfig == null && ((wifiConfig = getWifiApConfiguration()) == null)) { + wifiConfig = new WifiConfiguration(); + wifiConfig.SSID = mContext.getString(R.string.wifi_tether_configure_ssid_default); + wifiConfig.allowedKeyManagement.set(KeyMgmt.NONE); + } + /* + * Caller might not have WRITE_SECURE_SETTINGS, + * only CHANGE_WIFI_STATE is enforced + */ long ident = Binder.clearCallingIdentity(); - sWakeLock.acquire(); + setWifiApConfiguration(wifiConfig); Binder.restoreCallingIdentity(ident); - - mLastApEnableUid = Binder.getCallingUid(); - sendAccessPointMessage(enabled, wifiConfig, Binder.getCallingUid()); } + mWifiStateMachine.setWifiApEnabled(wifiConfig, enabled); + return true; } - public WifiConfiguration getWifiApConfiguration() { + /** + * see {@link WifiManager#getWifiApState()} + * @return One of {@link WifiManager#WIFI_AP_STATE_DISABLED}, + * {@link WifiManager#WIFI_AP_STATE_DISABLING}, + * {@link WifiManager#WIFI_AP_STATE_ENABLED}, + * {@link WifiManager#WIFI_AP_STATE_ENABLING}, + * {@link WifiManager#WIFI_AP_STATE_FAILED} + */ + public int getWifiApEnabledState() { enforceAccessPermission(); + return mWifiStateMachine.syncGetWifiApState(); + } + + /** + * see {@link WifiManager#getWifiApConfiguration()} + * @return soft access point configuration + */ + public synchronized WifiConfiguration getWifiApConfiguration() { final ContentResolver cr = mContext.getContentResolver(); WifiConfiguration wifiConfig = new WifiConfiguration(); int authType; @@ -651,7 +565,11 @@ public class WifiService extends IWifiManager.Stub { } } - public void setWifiApConfiguration(WifiConfiguration wifiConfig) { + /** + * see {@link WifiManager#setWifiApConfiguration(WifiConfiguration)} + * @param wifiConfig WifiConfiguration details for soft access point + */ + public synchronized void setWifiApConfiguration(WifiConfiguration wifiConfig) { enforceChangePermission(); final ContentResolver cr = mContext.getContentResolver(); boolean isWpa; @@ -667,143 +585,27 @@ public class WifiService extends IWifiManager.Stub { } /** - * Enables/disables Wi-Fi AP synchronously. The driver is loaded - * and soft access point configured as a single operation. - * @param enable {@code true} to turn Wi-Fi on, {@code false} to turn it off. - * @param uid The UID of the process making the request. - * @param wifiConfig The WifiConfiguration for AP - * @return {@code true} if the operation succeeds (or if the existing state - * is the same as the requested state) + * see {@link android.net.wifi.WifiManager#disconnect()} */ - private boolean setWifiApEnabledBlocking(boolean enable, - int uid, WifiConfiguration wifiConfig) { - final int eventualWifiApState = enable ? WIFI_AP_STATE_ENABLED : WIFI_AP_STATE_DISABLED; - - if (mWifiApState == eventualWifiApState) { - /* Configuration changed on a running access point */ - if(enable && (wifiConfig != null)) { - try { - nwService.setAccessPoint(wifiConfig, mWifiStateTracker.getInterfaceName(), - SOFTAP_IFACE); - setWifiApConfiguration(wifiConfig); - return true; - } catch(Exception e) { - Slog.e(TAG, "Exception in nwService during AP restart"); - try { - nwService.stopAccessPoint(); - } catch (Exception ee) { - Slog.e(TAG, "Could not stop AP, :" + ee); - } - setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.DRIVER_UNLOAD); - return false; - } - } else { - return true; - } - } - - /** - * Fail AP if Wifi is enabled - */ - if ((mWifiStateTracker.getWifiState() == WIFI_STATE_ENABLED) && enable) { - setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.NO_DRIVER_UNLOAD); - return false; - } - - setWifiApEnabledState(enable ? WIFI_AP_STATE_ENABLING : - WIFI_AP_STATE_DISABLING, uid, DriverAction.NO_DRIVER_UNLOAD); - - if (enable) { - - /* Use default config if there is no existing config */ - if (wifiConfig == null && ((wifiConfig = getWifiApConfiguration()) == null)) { - wifiConfig = new WifiConfiguration(); - wifiConfig.SSID = mContext.getString(R.string.wifi_tether_configure_ssid_default); - wifiConfig.allowedKeyManagement.set(KeyMgmt.NONE); - } - - if (!mWifiStateTracker.loadDriver()) { - Slog.e(TAG, "Failed to load Wi-Fi driver for AP mode"); - setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.NO_DRIVER_UNLOAD); - return false; - } - - try { - nwService.startAccessPoint(wifiConfig, mWifiStateTracker.getInterfaceName(), - SOFTAP_IFACE); - } catch(Exception e) { - Slog.e(TAG, "Exception in startAccessPoint()"); - setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.DRIVER_UNLOAD); - return false; - } - - setWifiApConfiguration(wifiConfig); - - } else { - - try { - nwService.stopAccessPoint(); - } catch(Exception e) { - Slog.e(TAG, "Exception in stopAccessPoint()"); - setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.DRIVER_UNLOAD); - return false; - } - - if (!mWifiStateTracker.unloadDriver()) { - Slog.e(TAG, "Failed to unload Wi-Fi driver for AP mode"); - setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.NO_DRIVER_UNLOAD); - return false; - } - } - - setWifiApEnabledState(eventualWifiApState, uid, DriverAction.NO_DRIVER_UNLOAD); - return true; + public void disconnect() { + enforceChangePermission(); + mWifiStateMachine.disconnectCommand(); } /** - * see {@link WifiManager#getWifiApState()} - * @return One of {@link WifiManager#WIFI_AP_STATE_DISABLED}, - * {@link WifiManager#WIFI_AP_STATE_DISABLING}, - * {@link WifiManager#WIFI_AP_STATE_ENABLED}, - * {@link WifiManager#WIFI_AP_STATE_ENABLING}, - * {@link WifiManager#WIFI_AP_STATE_FAILED} + * see {@link android.net.wifi.WifiManager#reconnect()} */ - public int getWifiApEnabledState() { - enforceAccessPermission(); - return mWifiApState; + public void reconnect() { + enforceChangePermission(); + mWifiStateMachine.reconnectCommand(); } - private void setWifiApEnabledState(int wifiAPState, int uid, DriverAction flag) { - final int previousWifiApState = mWifiApState; - - /** - * Unload the driver if going to a failed state - */ - if ((mWifiApState == WIFI_AP_STATE_FAILED) && (flag == DriverAction.DRIVER_UNLOAD)) { - mWifiStateTracker.unloadDriver(); - } - - long ident = Binder.clearCallingIdentity(); - try { - if (wifiAPState == WIFI_AP_STATE_ENABLED) { - mBatteryStats.noteWifiOn(); - } else if (wifiAPState == WIFI_AP_STATE_DISABLED) { - mBatteryStats.noteWifiOff(); - } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(ident); - } - - // Update state - mWifiApState = wifiAPState; - - // Broadcast - final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, wifiAPState); - intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, previousWifiApState); - mContext.sendStickyBroadcast(intent); + /** + * see {@link android.net.wifi.WifiManager#reassociate()} + */ + public void reassociate() { + enforceChangePermission(); + mWifiStateMachine.reassociateCommand(); } /** @@ -812,217 +614,7 @@ public class WifiService extends IWifiManager.Stub { */ public List<WifiConfiguration> getConfiguredNetworks() { enforceAccessPermission(); - String listStr; - - /* - * We don't cache the list, because we want to allow - * for the possibility that the configuration file - * has been modified through some external means, - * such as the wpa_cli command line program. - */ - listStr = mWifiStateTracker.listNetworks(); - - List<WifiConfiguration> networks = - new ArrayList<WifiConfiguration>(); - if (listStr == null) - return networks; - - String[] lines = listStr.split("\n"); - // Skip the first line, which is a header - for (int i = 1; i < lines.length; i++) { - String[] result = lines[i].split("\t"); - // network-id | ssid | bssid | flags - WifiConfiguration config = new WifiConfiguration(); - try { - config.networkId = Integer.parseInt(result[0]); - } catch(NumberFormatException e) { - continue; - } - if (result.length > 3) { - if (result[3].indexOf("[CURRENT]") != -1) - config.status = WifiConfiguration.Status.CURRENT; - else if (result[3].indexOf("[DISABLED]") != -1) - config.status = WifiConfiguration.Status.DISABLED; - else - config.status = WifiConfiguration.Status.ENABLED; - } else { - config.status = WifiConfiguration.Status.ENABLED; - } - readNetworkVariables(config); - networks.add(config); - } - - return networks; - } - - /** - * Read the variables from the supplicant daemon that are needed to - * fill in the WifiConfiguration object. - * <p/> - * The caller must hold the synchronization monitor. - * @param config the {@link WifiConfiguration} object to be filled in. - */ - private void readNetworkVariables(WifiConfiguration config) { - - int netId = config.networkId; - if (netId < 0) - return; - - /* - * TODO: maybe should have a native method that takes an array of - * variable names and returns an array of values. But we'd still - * be doing a round trip to the supplicant daemon for each variable. - */ - String value; - - value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.ssidVarName); - if (!TextUtils.isEmpty(value)) { - config.SSID = value; - } else { - config.SSID = null; - } - - value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.bssidVarName); - if (!TextUtils.isEmpty(value)) { - config.BSSID = value; - } else { - config.BSSID = null; - } - - value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.priorityVarName); - config.priority = -1; - if (!TextUtils.isEmpty(value)) { - try { - config.priority = Integer.parseInt(value); - } catch (NumberFormatException ignore) { - } - } - - value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.hiddenSSIDVarName); - config.hiddenSSID = false; - if (!TextUtils.isEmpty(value)) { - try { - config.hiddenSSID = Integer.parseInt(value) != 0; - } catch (NumberFormatException ignore) { - } - } - - value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.wepTxKeyIdxVarName); - config.wepTxKeyIndex = -1; - if (!TextUtils.isEmpty(value)) { - try { - config.wepTxKeyIndex = Integer.parseInt(value); - } catch (NumberFormatException ignore) { - } - } - - /* - * Get up to 4 WEP keys. Note that the actual keys are not passed back, - * just a "*" if the key is set, or the null string otherwise. - */ - for (int i = 0; i < 4; i++) { - value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.wepKeyVarNames[i]); - if (!TextUtils.isEmpty(value)) { - config.wepKeys[i] = value; - } else { - config.wepKeys[i] = null; - } - } - - /* - * Get the private shared key. Note that the actual keys are not passed back, - * just a "*" if the key is set, or the null string otherwise. - */ - value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.pskVarName); - if (!TextUtils.isEmpty(value)) { - config.preSharedKey = value; - } else { - config.preSharedKey = null; - } - - value = mWifiStateTracker.getNetworkVariable(config.networkId, - WifiConfiguration.Protocol.varName); - if (!TextUtils.isEmpty(value)) { - String vals[] = value.split(" "); - for (String val : vals) { - int index = - lookupString(val, WifiConfiguration.Protocol.strings); - if (0 <= index) { - config.allowedProtocols.set(index); - } - } - } - - value = mWifiStateTracker.getNetworkVariable(config.networkId, - WifiConfiguration.KeyMgmt.varName); - if (!TextUtils.isEmpty(value)) { - String vals[] = value.split(" "); - for (String val : vals) { - int index = - lookupString(val, WifiConfiguration.KeyMgmt.strings); - if (0 <= index) { - config.allowedKeyManagement.set(index); - } - } - } - - value = mWifiStateTracker.getNetworkVariable(config.networkId, - WifiConfiguration.AuthAlgorithm.varName); - if (!TextUtils.isEmpty(value)) { - String vals[] = value.split(" "); - for (String val : vals) { - int index = - lookupString(val, WifiConfiguration.AuthAlgorithm.strings); - if (0 <= index) { - config.allowedAuthAlgorithms.set(index); - } - } - } - - value = mWifiStateTracker.getNetworkVariable(config.networkId, - WifiConfiguration.PairwiseCipher.varName); - if (!TextUtils.isEmpty(value)) { - String vals[] = value.split(" "); - for (String val : vals) { - int index = - lookupString(val, WifiConfiguration.PairwiseCipher.strings); - if (0 <= index) { - config.allowedPairwiseCiphers.set(index); - } - } - } - - value = mWifiStateTracker.getNetworkVariable(config.networkId, - WifiConfiguration.GroupCipher.varName); - if (!TextUtils.isEmpty(value)) { - String vals[] = value.split(" "); - for (String val : vals) { - int index = - lookupString(val, WifiConfiguration.GroupCipher.strings); - if (0 <= index) { - config.allowedGroupCiphers.set(index); - } - } - } - - for (WifiConfiguration.EnterpriseField field : - config.enterpriseFields) { - value = mWifiStateTracker.getNetworkVariable(netId, - field.varName()); - if (!TextUtils.isEmpty(value)) { - if (field != config.eap) value = removeDoubleQuotes(value); - field.setValue(value); - } - } - } - - private static String removeDoubleQuotes(String string) { - if (string.length() <= 2) return ""; - return string.substring(1, string.length() - 1); - } - - private static String convertToQuotedString(String string) { - return "\"" + string + "\""; + return mWifiStateMachine.syncGetConfiguredNetworks(); } /** @@ -1032,280 +624,15 @@ public class WifiService extends IWifiManager.Stub { */ public int addOrUpdateNetwork(WifiConfiguration config) { enforceChangePermission(); - - /* - * If the supplied networkId is -1, we create a new empty - * network configuration. Otherwise, the networkId should - * refer to an existing configuration. - */ - int netId = config.networkId; - boolean newNetwork = netId == -1; - boolean doReconfig = false; - // networkId of -1 means we want to create a new network - synchronized (mWifiStateTracker) { - if (newNetwork) { - netId = mWifiStateTracker.addNetwork(); - if (netId < 0) { - if (DBG) { - Slog.d(TAG, "Failed to add a network!"); - } - return -1; - } - doReconfig = true; - } - mNeedReconfig = mNeedReconfig || doReconfig; - } - - setVariables: { - /* - * Note that if a networkId for a non-existent network - * was supplied, then the first setNetworkVariable() - * will fail, so we don't bother to make a separate check - * for the validity of the ID up front. - */ - if (config.SSID != null && - !mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.ssidVarName, - config.SSID)) { - if (DBG) { - Slog.d(TAG, "failed to set SSID: "+config.SSID); - } - break setVariables; - } - - if (config.BSSID != null && - !mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.bssidVarName, - config.BSSID)) { - if (DBG) { - Slog.d(TAG, "failed to set BSSID: "+config.BSSID); - } - break setVariables; - } - - String allowedKeyManagementString = - makeString(config.allowedKeyManagement, WifiConfiguration.KeyMgmt.strings); - if (config.allowedKeyManagement.cardinality() != 0 && - !mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.KeyMgmt.varName, - allowedKeyManagementString)) { - if (DBG) { - Slog.d(TAG, "failed to set key_mgmt: "+ - allowedKeyManagementString); - } - break setVariables; - } - - String allowedProtocolsString = - makeString(config.allowedProtocols, WifiConfiguration.Protocol.strings); - if (config.allowedProtocols.cardinality() != 0 && - !mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.Protocol.varName, - allowedProtocolsString)) { - if (DBG) { - Slog.d(TAG, "failed to set proto: "+ - allowedProtocolsString); - } - break setVariables; - } - - String allowedAuthAlgorithmsString = - makeString(config.allowedAuthAlgorithms, WifiConfiguration.AuthAlgorithm.strings); - if (config.allowedAuthAlgorithms.cardinality() != 0 && - !mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.AuthAlgorithm.varName, - allowedAuthAlgorithmsString)) { - if (DBG) { - Slog.d(TAG, "failed to set auth_alg: "+ - allowedAuthAlgorithmsString); - } - break setVariables; - } - - String allowedPairwiseCiphersString = - makeString(config.allowedPairwiseCiphers, WifiConfiguration.PairwiseCipher.strings); - if (config.allowedPairwiseCiphers.cardinality() != 0 && - !mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.PairwiseCipher.varName, - allowedPairwiseCiphersString)) { - if (DBG) { - Slog.d(TAG, "failed to set pairwise: "+ - allowedPairwiseCiphersString); - } - break setVariables; - } - - String allowedGroupCiphersString = - makeString(config.allowedGroupCiphers, WifiConfiguration.GroupCipher.strings); - if (config.allowedGroupCiphers.cardinality() != 0 && - !mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.GroupCipher.varName, - allowedGroupCiphersString)) { - if (DBG) { - Slog.d(TAG, "failed to set group: "+ - allowedGroupCiphersString); - } - break setVariables; - } - - // Prevent client screw-up by passing in a WifiConfiguration we gave it - // by preventing "*" as a key. - if (config.preSharedKey != null && !config.preSharedKey.equals("*") && - !mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.pskVarName, - config.preSharedKey)) { - if (DBG) { - Slog.d(TAG, "failed to set psk: "+config.preSharedKey); - } - break setVariables; - } - - boolean hasSetKey = false; - if (config.wepKeys != null) { - for (int i = 0; i < config.wepKeys.length; i++) { - // Prevent client screw-up by passing in a WifiConfiguration we gave it - // by preventing "*" as a key. - if (config.wepKeys[i] != null && !config.wepKeys[i].equals("*")) { - if (!mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.wepKeyVarNames[i], - config.wepKeys[i])) { - if (DBG) { - Slog.d(TAG, - "failed to set wep_key"+i+": " + - config.wepKeys[i]); - } - break setVariables; - } - hasSetKey = true; - } - } - } - - if (hasSetKey) { - if (!mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.wepTxKeyIdxVarName, - Integer.toString(config.wepTxKeyIndex))) { - if (DBG) { - Slog.d(TAG, - "failed to set wep_tx_keyidx: "+ - config.wepTxKeyIndex); - } - break setVariables; - } - } - - if (!mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.priorityVarName, - Integer.toString(config.priority))) { - if (DBG) { - Slog.d(TAG, config.SSID + ": failed to set priority: " - +config.priority); - } - break setVariables; - } - - if (config.hiddenSSID && !mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.hiddenSSIDVarName, - Integer.toString(config.hiddenSSID ? 1 : 0))) { - if (DBG) { - Slog.d(TAG, config.SSID + ": failed to set hiddenSSID: "+ - config.hiddenSSID); - } - break setVariables; - } - - for (WifiConfiguration.EnterpriseField field - : config.enterpriseFields) { - String varName = field.varName(); - String value = field.value(); - if (value != null) { - if (field != config.eap) { - value = (value.length() == 0) ? "NULL" : convertToQuotedString(value); - } - if (!mWifiStateTracker.setNetworkVariable( - netId, - varName, - value)) { - if (DBG) { - Slog.d(TAG, config.SSID + ": failed to set " + varName + - ": " + value); - } - break setVariables; - } - } - } - return netId; - } - - /* - * For an update, if one of the setNetworkVariable operations fails, - * we might want to roll back all the changes already made. But the - * chances are that if anything is going to go wrong, it'll happen - * the first time we try to set one of the variables. - */ - if (newNetwork) { - removeNetwork(netId); - if (DBG) { - Slog.d(TAG, - "Failed to set a network variable, removed network: " - + netId); - } - } - return -1; - } - - private static String makeString(BitSet set, String[] strings) { - StringBuffer buf = new StringBuffer(); - int nextSetBit = -1; - - /* Make sure all set bits are in [0, strings.length) to avoid - * going out of bounds on strings. (Shouldn't happen, but...) */ - set = set.get(0, strings.length); - - while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) { - buf.append(strings[nextSetBit].replace('_', '-')).append(' '); - } - - // remove trailing space - if (set.cardinality() > 0) { - buf.setLength(buf.length() - 1); - } - - return buf.toString(); - } - - private static int lookupString(String string, String[] strings) { - int size = strings.length; - - string = string.replace('-', '_'); - - for (int i = 0; i < size; i++) - if (string.equals(strings[i])) - return i; - - if (DBG) { - // if we ever get here, we should probably add the - // value to WifiConfiguration to reflect that it's - // supported by the WPA supplicant - Slog.w(TAG, "Failed to look-up a string: " + string); + if (mChannel != null) { + return mWifiStateMachine.syncAddOrUpdateNetwork(mChannel, config); + } else { + Slog.e(TAG, "mChannel is not initialized"); + return -1; } - - return -1; } - /** + /** * See {@link android.net.wifi.WifiManager#removeNetwork(int)} * @param netId the integer that identifies the network configuration * to the supplicant @@ -1313,8 +640,12 @@ public class WifiService extends IWifiManager.Stub { */ public boolean removeNetwork(int netId) { enforceChangePermission(); - - return mWifiStateTracker.removeNetwork(netId); + if (mChannel != null) { + return mWifiStateMachine.syncRemoveNetwork(mChannel, netId); + } else { + Slog.e(TAG, "mChannel is not initialized"); + return false; + } } /** @@ -1326,14 +657,12 @@ public class WifiService extends IWifiManager.Stub { */ public boolean enableNetwork(int netId, boolean disableOthers) { enforceChangePermission(); - - String ifname = mWifiStateTracker.getInterfaceName(); - NetworkUtils.enableInterface(ifname); - boolean result = mWifiStateTracker.enableNetwork(netId, disableOthers); - if (!result) { - NetworkUtils.disableInterface(ifname); + if (mChannel != null) { + return mWifiStateMachine.syncEnableNetwork(mChannel, netId, disableOthers); + } else { + Slog.e(TAG, "mChannel is not initialized"); + return false; } - return result; } /** @@ -1344,8 +673,12 @@ public class WifiService extends IWifiManager.Stub { */ public boolean disableNetwork(int netId) { enforceChangePermission(); - - return mWifiStateTracker.disableNetwork(netId); + if (mChannel != null) { + return mWifiStateMachine.syncDisableNetwork(mChannel, netId); + } else { + Slog.e(TAG, "mChannel is not initialized"); + return false; + } } /** @@ -1358,7 +691,7 @@ public class WifiService extends IWifiManager.Stub { * Make sure we have the latest information, by sending * a status request to the supplicant. */ - return mWifiStateTracker.requestConnectionInfo(); + return mWifiStateMachine.syncRequestConnectionInfo(); } /** @@ -1368,282 +701,161 @@ public class WifiService extends IWifiManager.Stub { */ public List<ScanResult> getScanResults() { enforceAccessPermission(); - String reply; + return mWifiStateMachine.syncGetScanResultsList(); + } - reply = mWifiStateTracker.scanResults(); - if (reply == null) { - return null; + /** + * Tell the supplicant to persist the current list of configured networks. + * @return {@code true} if the operation succeeded + * + * TODO: deprecate this + */ + public boolean saveConfiguration() { + boolean result = true; + enforceChangePermission(); + if (mChannel != null) { + return mWifiStateMachine.syncSaveConfig(mChannel); + } else { + Slog.e(TAG, "mChannel is not initialized"); + return false; } + } - List<ScanResult> scanList = new ArrayList<ScanResult>(); - - int lineCount = 0; - - int replyLen = reply.length(); - // Parse the result string, keeping in mind that the last line does - // not end with a newline. - for (int lineBeg = 0, lineEnd = 0; lineEnd <= replyLen; ++lineEnd) { - if (lineEnd == replyLen || reply.charAt(lineEnd) == '\n') { - ++lineCount; - /* - * Skip the first line, which is a header - */ - if (lineCount == 1) { - lineBeg = lineEnd + 1; - continue; - } - if (lineEnd > lineBeg) { - String line = reply.substring(lineBeg, lineEnd); - ScanResult scanResult = parseScanResult(line); - if (scanResult != null) { - scanList.add(scanResult); - } else if (DBG) { - Slog.w(TAG, "misformatted scan result for: " + line); - } - } - lineBeg = lineEnd + 1; - } - } - mWifiStateTracker.setScanResultsList(scanList); - return scanList; + /** + * Set the country code + * @param countryCode ISO 3166 country code. + * @param persist {@code true} if the setting should be remembered. + * + * The persist behavior exists so that wifi can fall back to the last + * persisted country code on a restart, when the locale information is + * not available from telephony. + */ + public void setCountryCode(String countryCode, boolean persist) { + Slog.i(TAG, "WifiService trying to set country code to " + countryCode + + " with persist set to " + persist); + enforceChangePermission(); + mWifiStateMachine.setCountryCode(countryCode, persist); } /** - * Parse the scan result line passed to us by wpa_supplicant (helper). - * @param line the line to parse - * @return the {@link ScanResult} object + * Set the operational frequency band + * @param band One of + * {@link WifiManager#WIFI_FREQUENCY_BAND_AUTO}, + * {@link WifiManager#WIFI_FREQUENCY_BAND_5GHZ}, + * {@link WifiManager#WIFI_FREQUENCY_BAND_2GHZ}, + * @param persist {@code true} if the setting should be remembered. + * */ - private ScanResult parseScanResult(String line) { - ScanResult scanResult = null; - if (line != null) { - /* - * Cache implementation (LinkedHashMap) is not synchronized, thus, - * must synchronized here! - */ - synchronized (mScanResultCache) { - String[] result = scanResultPattern.split(line); - if (3 <= result.length && result.length <= 5) { - String bssid = result[0]; - // bssid | frequency | level | flags | ssid - int frequency; - int level; - try { - frequency = Integer.parseInt(result[1]); - level = Integer.parseInt(result[2]); - /* some implementations avoid negative values by adding 256 - * so we need to adjust for that here. - */ - if (level > 0) level -= 256; - } catch (NumberFormatException e) { - frequency = 0; - level = 0; - } + public void setFrequencyBand(int band, boolean persist) { + enforceChangePermission(); + if (!isDualBandSupported()) return; + Slog.i(TAG, "WifiService trying to set frequency band to " + band + + " with persist set to " + persist); + mWifiStateMachine.setFrequencyBand(band, persist); + } - /* - * The formatting of the results returned by - * wpa_supplicant is intended to make the fields - * line up nicely when printed, - * not to make them easy to parse. So we have to - * apply some heuristics to figure out which field - * is the SSID and which field is the flags. - */ - String ssid; - String flags; - if (result.length == 4) { - if (result[3].charAt(0) == '[') { - flags = result[3]; - ssid = ""; - } else { - flags = ""; - ssid = result[3]; - } - } else if (result.length == 5) { - flags = result[3]; - ssid = result[4]; - } else { - // Here, we must have 3 fields: no flags and ssid - // set - flags = ""; - ssid = ""; - } - // bssid + ssid is the hash key - String key = bssid + ssid; - scanResult = mScanResultCache.get(key); - if (scanResult != null) { - scanResult.level = level; - scanResult.SSID = ssid; - scanResult.capabilities = flags; - scanResult.frequency = frequency; - } else { - // Do not add scan results that have no SSID set - if (0 < ssid.trim().length()) { - scanResult = - new ScanResult( - ssid, bssid, flags, level, frequency); - mScanResultCache.put(key, scanResult); - } - } - } else { - Slog.w(TAG, "Misformatted scan result text with " + - result.length + " fields: " + line); - } - } - } + /** + * Get the operational frequency band + */ + public int getFrequencyBand() { + enforceAccessPermission(); + return mWifiStateMachine.getFrequencyBand(); + } - return scanResult; + public boolean isDualBandSupported() { + //TODO: Should move towards adding a driver API that checks at runtime + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_wifi_dual_band_support); } /** - * Parse the "flags" field passed back in a scan result by wpa_supplicant, - * and construct a {@code WifiConfiguration} that describes the encryption, - * key management, and authenticaion capabilities of the access point. - * @param flags the string returned by wpa_supplicant - * @return the {@link WifiConfiguration} object, filled in + * Return the DHCP-assigned addresses from the last successful DHCP request, + * if any. + * @return the DHCP information */ - WifiConfiguration parseScanFlags(String flags) { - WifiConfiguration config = new WifiConfiguration(); - - if (flags.length() == 0) { - config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); - } - // ... to be implemented - return config; + public DhcpInfo getDhcpInfo() { + enforceAccessPermission(); + return mWifiStateMachine.syncGetDhcpInfo(); } /** - * Tell the supplicant to persist the current list of configured networks. - * @return {@code true} if the operation succeeded + * see {@link android.net.wifi.WifiManager#startWifi} + * */ - public boolean saveConfiguration() { - boolean result; + public void startWifi() { enforceChangePermission(); + /* TODO: may be add permissions for access only to connectivity service + * TODO: if a start issued, keep wifi alive until a stop issued irrespective + * of WifiLock & device idle status unless wifi enabled status is toggled + */ - synchronized (mWifiStateTracker) { - result = mWifiStateTracker.saveConfig(); - if (result && mNeedReconfig) { - mNeedReconfig = false; - result = mWifiStateTracker.reloadConfig(); - - if (result) { - Intent intent = new Intent(WifiManager.NETWORK_IDS_CHANGED_ACTION); - mContext.sendBroadcast(intent); - } - } - } - // Inform the backup manager about a data change - IBackupManager ibm = IBackupManager.Stub.asInterface( - ServiceManager.getService(Context.BACKUP_SERVICE)); - if (ibm != null) { - try { - ibm.dataChanged("com.android.providers.settings"); - } catch (Exception e) { - // Try again later - } - } - return result; + mWifiStateMachine.setDriverStart(true); + mWifiStateMachine.reconnectCommand(); } /** - * Set the number of radio frequency channels that are allowed to be used - * in the current regulatory domain. This method should be used only - * if the correct number of channels cannot be determined automatically - * for some reason. If the operation is successful, the new value may be - * persisted as a Secure setting. - * @param numChannels the number of allowed channels. Must be greater than 0 - * and less than or equal to 16. - * @param persist {@code true} if the setting should be remembered. - * @return {@code true} if the operation succeeds, {@code false} otherwise, e.g., - * {@code numChannels} is outside the valid range. + * see {@link android.net.wifi.WifiManager#stopWifi} + * */ - public boolean setNumAllowedChannels(int numChannels, boolean persist) { - Slog.i(TAG, "WifiService trying to setNumAllowed to "+numChannels+ - " with persist set to "+persist); + public void stopWifi() { enforceChangePermission(); - - /* - * Validate the argument. We'd like to let the Wi-Fi driver do this, - * but if Wi-Fi isn't currently enabled, that's not possible, and - * we want to persist the setting anyway,so that it will take - * effect when Wi-Fi does become enabled. + /* TODO: may be add permissions for access only to connectivity service + * TODO: if a stop is issued, wifi is brought up only by startWifi + * unless wifi enabled status is toggled */ - boolean found = false; - for (int validChan : sValidRegulatoryChannelCounts) { - if (validChan == numChannels) { - found = true; - break; - } - } - if (!found) { - return false; - } + mWifiStateMachine.setDriverStart(false); + } - if (mWifiHandler == null) return false; - Message.obtain(mWifiHandler, - MESSAGE_SET_CHANNELS, numChannels, (persist ? 1 : 0)).sendToTarget(); + /** + * see {@link android.net.wifi.WifiManager#addToBlacklist} + * + */ + public void addToBlacklist(String bssid) { + enforceChangePermission(); - return true; + mWifiStateMachine.addToBlacklist(bssid); } /** - * sets the number of allowed radio frequency channels synchronously - * @param numChannels the number of allowed channels. Must be greater than 0 - * and less than or equal to 16. - * @param persist {@code true} if the setting should be remembered. - * @return {@code true} if the operation succeeds, {@code false} otherwise + * see {@link android.net.wifi.WifiManager#clearBlacklist} + * */ - private boolean setNumAllowedChannelsBlocking(int numChannels, boolean persist) { - if (persist) { - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS, - numChannels); - } - return mWifiStateTracker.setNumAllowedChannels(numChannels); + public void clearBlacklist() { + enforceChangePermission(); + + mWifiStateMachine.clearBlacklist(); } - /** - * Return the number of frequency channels that are allowed - * to be used in the current regulatory domain. - * @return the number of allowed channels, or {@code -1} if an error occurs - */ - public int getNumAllowedChannels() { - int numChannels; + public void connectNetworkWithId(int networkId) { + enforceChangePermission(); + mWifiStateMachine.connectNetwork(networkId); + } - enforceAccessPermission(); + public void connectNetworkWithConfig(WifiConfiguration config) { + enforceChangePermission(); + mWifiStateMachine.connectNetwork(config); + } - /* - * If we can't get the value from the driver (e.g., because - * Wi-Fi is not currently enabled), get the value from - * Settings. - */ - numChannels = mWifiStateTracker.getNumAllowedChannels(); - if (numChannels < 0) { - numChannels = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS, - -1); - } - return numChannels; + public void saveNetwork(WifiConfiguration config) { + enforceChangePermission(); + mWifiStateMachine.saveNetwork(config); } - /** - * Return the list of valid values for the number of allowed radio channels - * for various regulatory domains. - * @return the list of channel counts - */ - public int[] getValidChannelCounts() { - enforceAccessPermission(); - return sValidRegulatoryChannelCounts; + public void forgetNetwork(int netId) { + enforceChangePermission(); + mWifiStateMachine.forgetNetwork(netId); } - /** - * Return the DHCP-assigned addresses from the last successful DHCP request, - * if any. - * @return the DHCP information - */ - public DhcpInfo getDhcpInfo() { - enforceAccessPermission(); - return mWifiStateTracker.getDhcpInfo(); + public WpsResult startWps(WpsConfiguration config) { + enforceChangePermission(); + if (mChannel != null) { + return mWifiStateMachine.startWps(mChannel, config); + } else { + Slog.e(TAG, "mChannel is not initialized"); + return new WpsResult(WpsResult.Status.FAILURE); + } } private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @@ -1653,7 +865,7 @@ public class WifiService extends IWifiManager.Stub { long idleMillis = Settings.Secure.getLong(mContext.getContentResolver(), - Settings.Secure.WIFI_IDLE_MS, DEFAULT_IDLE_MILLIS); + Settings.Secure.WIFI_IDLE_MS, DEFAULT_IDLE_MS); int stayAwakeConditions = Settings.System.getInt(mContext.getContentResolver(), Settings.System.STAY_ON_WHILE_PLUGGED_IN, 0); @@ -1666,20 +878,22 @@ public class WifiService extends IWifiManager.Stub { mScreenOff = false; // Once the screen is on, we are not keeping WIFI running // because of any locks so clear that tracking immediately. - sendReportWorkSourceMessage(); - sendEnableRssiPollingMessage(true); - /* DHCP or other temporary failures in the past can prevent - * a disabled network from being connected to, enable on screen on - */ - if (mWifiStateTracker.isAnyNetworkDisabled()) { - sendEnableNetworksMessage(); + reportStartWorkSource(); + mWifiStateMachine.enableRssiPolling(true); + if (mBackgroundScanSupported) { + mWifiStateMachine.enableBackgroundScan(false); } + mWifiStateMachine.enableAllNetworks(); + updateWifiState(); } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { if (DBG) { Slog.d(TAG, "ACTION_SCREEN_OFF"); } mScreenOff = true; - sendEnableRssiPollingMessage(false); + mWifiStateMachine.enableRssiPolling(false); + if (mBackgroundScanSupported) { + mWifiStateMachine.enableBackgroundScan(true); + } /* * Set a timer to put Wi-Fi to sleep, but only if the screen is off * AND the "stay on while plugged in" setting doesn't match the @@ -1687,11 +901,11 @@ public class WifiService extends IWifiManager.Stub { * or plugged in to AC). */ if (!shouldWifiStayAwake(stayAwakeConditions, mPluggedType)) { - WifiInfo info = mWifiStateTracker.requestConnectionInfo(); + WifiInfo info = mWifiStateMachine.syncRequestConnectionInfo(); if (info.getSupplicantState() != SupplicantState.COMPLETED) { // we used to go to sleep immediately, but this caused some race conditions - // we don't have time to track down for this release. Delay instead, but not - // as long as we would if connected (below) + // we don't have time to track down for this release. Delay instead, + // but not as long as we would if connected (below) // TODO - fix the race conditions and switch back to the immediate turn-off long triggerTime = System.currentTimeMillis() + (2*60*1000); // 2 min if (DBG) { @@ -1710,14 +924,13 @@ public class WifiService extends IWifiManager.Stub { mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent); } } - /* we can return now -- there's nothing to do until we get the idle intent back */ - return; } else if (action.equals(ACTION_DEVICE_IDLE)) { if (DBG) { Slog.d(TAG, "got ACTION_DEVICE_IDLE"); } mDeviceIdle = true; - sendReportWorkSourceMessage(); + reportStartWorkSource(); + updateWifiState(); } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { /* * Set a timer to put Wi-Fi to sleep, but only if the screen is off @@ -1737,26 +950,13 @@ public class WifiService extends IWifiManager.Stub { Slog.d(TAG, "setting ACTION_DEVICE_IDLE timer for " + idleMillis + "ms"); } mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent); - mPluggedType = pluggedType; - return; } mPluggedType = pluggedType; - } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) { - BluetoothA2dp a2dp = new BluetoothA2dp(mContext); - Set<BluetoothDevice> sinks = a2dp.getConnectedSinks(); - boolean isBluetoothPlaying = false; - for (BluetoothDevice sink : sinks) { - if (a2dp.getSinkState(sink) == BluetoothA2dp.STATE_PLAYING) { - isBluetoothPlaying = true; - } - } - mWifiStateTracker.setBluetoothScanMode(isBluetoothPlaying); - - } else { - return; + } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, + BluetoothAdapter.STATE_DISCONNECTED); + mWifiStateMachine.sendBluetoothAdapterStateChange(state); } - - updateWifiState(); } /** @@ -1767,8 +967,10 @@ public class WifiService extends IWifiManager.Stub { * @see #shouldDeviceStayAwake(int, int) */ private boolean shouldWifiStayAwake(int stayAwakeConditions, int pluggedType) { + //Never sleep as long as the user has not changed the settings int wifiSleepPolicy = Settings.System.getInt(mContext.getContentResolver(), - Settings.System.WIFI_SLEEP_POLICY, Settings.System.WIFI_SLEEP_POLICY_DEFAULT); + Settings.System.WIFI_SLEEP_POLICY, + Settings.System.WIFI_SLEEP_POLICY_NEVER); if (wifiSleepPolicy == Settings.System.WIFI_SLEEP_POLICY_NEVER) { // Never sleep @@ -1789,7 +991,7 @@ public class WifiService extends IWifiManager.Stub { * of {@code 0} isn't really a plugged type, but rather an indication that the * device isn't plugged in at all, there is no bit value corresponding to a * {@code pluggedType} value of {@code 0}. That is why we shift by - * {@code pluggedType — 1} instead of by {@code pluggedType}. + * {@code pluggedType - 1} instead of by {@code pluggedType}. * @param stayAwakeConditions a bit string specifying which "plugged types" should * keep the device (and hence Wi-Fi) awake. * @param pluggedType the type of plug (USB, AC, or none) for which the check is @@ -1802,63 +1004,20 @@ public class WifiService extends IWifiManager.Stub { } }; - private void sendEnableMessage(boolean enable, boolean persist, int uid) { - Message msg = Message.obtain(mWifiHandler, - (enable ? MESSAGE_ENABLE_WIFI : MESSAGE_DISABLE_WIFI), - (persist ? 1 : 0), uid); - msg.sendToTarget(); - } - - private void sendStartMessage(int lockMode) { - Message.obtain(mWifiHandler, MESSAGE_START_WIFI, lockMode, 0).sendToTarget(); - } - - private void sendAccessPointMessage(boolean enable, WifiConfiguration wifiConfig, int uid) { - Message.obtain(mWifiHandler, - (enable ? MESSAGE_START_ACCESS_POINT : MESSAGE_STOP_ACCESS_POINT), - uid, 0, wifiConfig).sendToTarget(); - } - - private void sendEnableNetworksMessage() { - Message.obtain(mWifiHandler, MESSAGE_ENABLE_NETWORKS).sendToTarget(); - } - - private void sendReportWorkSourceMessage() { - Message.obtain(mWifiHandler, MESSAGE_REPORT_WORKSOURCE).sendToTarget(); - } - - private void sendEnableRssiPollingMessage(boolean enable) { - Message.obtain(mWifiHandler, MESSAGE_ENABLE_RSSI_POLLING, enable ? 1 : 0, 0).sendToTarget(); - } - - - private void reportStartWorkSource() { - synchronized (mWifiStateTracker) { - mTmpWorkSource.clear(); - if (mDeviceIdle) { - for (int i=0; i<mLocks.mList.size(); i++) { - mTmpWorkSource.add(mLocks.mList.get(i).mWorkSource); - } + private synchronized void reportStartWorkSource() { + mTmpWorkSource.clear(); + if (mDeviceIdle) { + for (int i=0; i<mLocks.mList.size(); i++) { + mTmpWorkSource.add(mLocks.mList.get(i).mWorkSource); } - mWifiStateTracker.updateBatteryWorkSourceLocked(mTmpWorkSource); - sWakeLock.setWorkSource(mTmpWorkSource); } + mWifiStateMachine.updateBatteryWorkSource(mTmpWorkSource); } private void updateWifiState() { - // send a message so it's all serialized - Message.obtain(mWifiHandler, MESSAGE_UPDATE_STATE, 0, 0).sendToTarget(); - } - - private void doUpdateWifiState() { boolean wifiEnabled = getPersistedWifiEnabled(); - boolean airplaneMode = isAirplaneModeOn() && !mAirplaneModeOverwridden; - - boolean lockHeld; - synchronized (mLocks) { - lockHeld = mLocks.hasLocks(); - } - + boolean airplaneMode = isAirplaneModeOn() && !mAirplaneModeOverwridden.get(); + boolean lockHeld = mLocks.hasLocks(); int strongestLockMode = WifiManager.WIFI_MODE_FULL; boolean wifiShouldBeEnabled = wifiEnabled && !airplaneMode; boolean wifiShouldBeStarted = !mDeviceIdle || lockHeld; @@ -1871,43 +1030,26 @@ public class WifiService extends IWifiManager.Stub { strongestLockMode = WifiManager.WIFI_MODE_FULL; } - synchronized (mWifiHandler) { - if ((mWifiStateTracker.getWifiState() == WIFI_STATE_ENABLING) && !airplaneMode) { - return; - } - - /* Disable tethering when airplane mode is enabled */ - if (airplaneMode && - (mWifiApState == WIFI_AP_STATE_ENABLING || mWifiApState == WIFI_AP_STATE_ENABLED)) { - sWakeLock.acquire(); - sendAccessPointMessage(false, null, mLastApEnableUid); - } + /* Disable tethering when airplane mode is enabled */ + if (airplaneMode) { + mWifiStateMachine.setWifiApEnabled(null, false); + } - if (wifiShouldBeEnabled) { - if (wifiShouldBeStarted) { - sWakeLock.acquire(); - sendEnableMessage(true, false, mLastEnableUid); - sWakeLock.acquire(); - sendStartMessage(strongestLockMode); - } else if (!mWifiStateTracker.isDriverStopped()) { - int wakeLockTimeout = - Settings.Secure.getInt( - mContext.getContentResolver(), - Settings.Secure.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS, - DEFAULT_WAKELOCK_TIMEOUT); - /* - * We are assuming that ConnectivityService can make - * a transition to cellular data within wakeLockTimeout time. - * The wakelock is released by the delayed message. - */ - sDriverStopWakeLock.acquire(); - mWifiHandler.sendEmptyMessage(MESSAGE_STOP_WIFI); - mWifiHandler.sendEmptyMessageDelayed(MESSAGE_RELEASE_WAKELOCK, wakeLockTimeout); - } + if (wifiShouldBeEnabled) { + if (wifiShouldBeStarted) { + reportStartWorkSource(); + mWifiStateMachine.setWifiEnabled(true); + mWifiStateMachine.setScanOnlyMode( + strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY); + mWifiStateMachine.setDriverStart(true); + mWifiStateMachine.setHighPerfModeEnabled(strongestLockMode + == WifiManager.WIFI_MODE_FULL_HIGH_PERF); } else { - sWakeLock.acquire(); - sendEnableMessage(false, false, mLastEnableUid); + mWifiStateMachine.requestCmWakeLock(); + mWifiStateMachine.setDriverStart(false); } + } else { + mWifiStateMachine.setWifiEnabled(false); } } @@ -1917,7 +1059,7 @@ public class WifiService extends IWifiManager.Stub { intentFilter.addAction(Intent.ACTION_SCREEN_OFF); intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); intentFilter.addAction(ACTION_DEVICE_IDLE); - intentFilter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); + intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); mContext.registerReceiver(mReceiver, intentFilter); } @@ -1945,103 +1087,6 @@ public class WifiService extends IWifiManager.Stub { Settings.System.AIRPLANE_MODE_ON, 0) == 1; } - /** - * Handler that allows posting to the WifiThread. - */ - private class WifiHandler extends Handler { - public WifiHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - - case MESSAGE_ENABLE_WIFI: - setWifiEnabledBlocking(true, msg.arg1 == 1, msg.arg2); - if (mWifiWatchdogService == null) { - mWifiWatchdogService = new WifiWatchdogService(mContext, mWifiStateTracker); - } - sWakeLock.release(); - break; - - case MESSAGE_START_WIFI: - reportStartWorkSource(); - mWifiStateTracker.setScanOnlyMode(msg.arg1 == WifiManager.WIFI_MODE_SCAN_ONLY); - mWifiStateTracker.restart(); - mWifiStateTracker.setHighPerfMode(msg.arg1 == - WifiManager.WIFI_MODE_FULL_HIGH_PERF); - sWakeLock.release(); - break; - - case MESSAGE_UPDATE_STATE: - doUpdateWifiState(); - break; - - case MESSAGE_DISABLE_WIFI: - // a non-zero msg.arg1 value means the "enabled" setting - // should be persisted - setWifiEnabledBlocking(false, msg.arg1 == 1, msg.arg2); - mWifiWatchdogService = null; - sWakeLock.release(); - break; - - case MESSAGE_STOP_WIFI: - mWifiStateTracker.disconnectAndStop(); - // don't release wakelock - break; - - case MESSAGE_RELEASE_WAKELOCK: - sDriverStopWakeLock.release(); - break; - - case MESSAGE_START_ACCESS_POINT: - setWifiApEnabledBlocking(true, - msg.arg1, - (WifiConfiguration) msg.obj); - sWakeLock.release(); - break; - - case MESSAGE_STOP_ACCESS_POINT: - setWifiApEnabledBlocking(false, - msg.arg1, - (WifiConfiguration) msg.obj); - sWakeLock.release(); - break; - - case MESSAGE_SET_CHANNELS: - setNumAllowedChannelsBlocking(msg.arg1, msg.arg2 == 1); - break; - - case MESSAGE_ENABLE_NETWORKS: - mWifiStateTracker.enableAllNetworks(getConfiguredNetworks()); - break; - - case MESSAGE_START_SCAN: - boolean forceActive = (msg.arg1 == 1); - switch (mWifiStateTracker.getSupplicantState()) { - case DISCONNECTED: - case INACTIVE: - case SCANNING: - case DORMANT: - break; - default: - mWifiStateTracker.setScanResultHandling( - WifiStateTracker.SUPPL_SCAN_HANDLING_LIST_ONLY); - break; - } - mWifiStateTracker.scan(forceActive); - break; - case MESSAGE_REPORT_WORKSOURCE: - reportStartWorkSource(); - break; - case MESSAGE_ENABLE_RSSI_POLLING: - mWifiStateTracker.enableRssiPolling(msg.arg1 == 1); - break; - } - } - } - @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) @@ -2051,17 +1096,17 @@ public class WifiService extends IWifiManager.Stub { + ", uid=" + Binder.getCallingUid()); return; } - pw.println("Wi-Fi is " + stateName(mWifiStateTracker.getWifiState())); + pw.println("Wi-Fi is " + mWifiStateMachine.syncGetWifiStateByName()); pw.println("Stay-awake conditions: " + Settings.System.getInt(mContext.getContentResolver(), Settings.System.STAY_ON_WHILE_PLUGGED_IN, 0)); pw.println(); pw.println("Internal state:"); - pw.println(mWifiStateTracker); + pw.println(mWifiStateMachine); pw.println(); pw.println("Latest scan results:"); - List<ScanResult> scanResults = mWifiStateTracker.getScanResultsList(); + List<ScanResult> scanResults = mWifiStateMachine.syncGetScanResultsList(); if (scanResults != null && scanResults.size() != 0) { pw.println(" BSSID Frequency RSSI Flags SSID"); for (ScanResult r : scanResults) { @@ -2085,23 +1130,6 @@ public class WifiService extends IWifiManager.Stub { mLocks.dump(pw); } - private static String stateName(int wifiState) { - switch (wifiState) { - case WIFI_STATE_DISABLING: - return "disabling"; - case WIFI_STATE_DISABLED: - return "disabled"; - case WIFI_STATE_ENABLING: - return "enabling"; - case WIFI_STATE_ENABLED: - return "enabled"; - case WIFI_STATE_UNKNOWN: - return "unknown state"; - default: - return "[invalid state]"; - } - } - private class WifiLock extends DeathRecipient { WifiLock(int lockMode, String tag, IBinder binder, WorkSource ws) { super(lockMode, tag, binder, ws); @@ -2179,7 +1207,7 @@ public class WifiService extends IWifiManager.Stub { } void enforceWakeSourcePermission(int uid, int pid) { - if (uid == Process.myUid()) { + if (uid == android.os.Process.myUid()) { return; } mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS, @@ -2213,10 +1241,7 @@ public class WifiService extends IWifiManager.Stub { private void noteAcquireWifiLock(WifiLock wifiLock) throws RemoteException { switch(wifiLock.mMode) { case WifiManager.WIFI_MODE_FULL: - mBatteryStats.noteFullWifiLockAcquiredFromSource(wifiLock.mWorkSource); - break; case WifiManager.WIFI_MODE_FULL_HIGH_PERF: - /* Treat high power as a full lock for battery stats */ mBatteryStats.noteFullWifiLockAcquiredFromSource(wifiLock.mWorkSource); break; case WifiManager.WIFI_MODE_SCAN_ONLY: @@ -2228,10 +1253,7 @@ public class WifiService extends IWifiManager.Stub { private void noteReleaseWifiLock(WifiLock wifiLock) throws RemoteException { switch(wifiLock.mMode) { case WifiManager.WIFI_MODE_FULL: - mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource); - break; case WifiManager.WIFI_MODE_FULL_HIGH_PERF: - /* Treat high power as a full lock for battery stats */ mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource); break; case WifiManager.WIFI_MODE_SCAN_ONLY: @@ -2255,6 +1277,7 @@ public class WifiService extends IWifiManager.Stub { case WifiManager.WIFI_MODE_FULL_HIGH_PERF: ++mFullHighPerfLocksAcquired; break; + case WifiManager.WIFI_MODE_SCAN_ONLY: ++mScanLocksAcquired; break; @@ -2262,7 +1285,7 @@ public class WifiService extends IWifiManager.Stub { // Be aggressive about adding new locks into the accounted state... // we want to over-report rather than under-report. - sendReportWorkSourceMessage(); + reportStartWorkSource(); updateWifiState(); return true; @@ -2401,7 +1424,7 @@ public class WifiService extends IWifiManager.Stub { if (mMulticasters.size() != 0) { return; } else { - mWifiStateTracker.startPacketFiltering(); + mWifiStateMachine.startPacketFiltering(); } } } @@ -2416,7 +1439,7 @@ public class WifiService extends IWifiManager.Stub { // our new size == 1 (first call), but this function won't // be called often and by making the stopPacket call each // time we're less fragile and self-healing. - mWifiStateTracker.stopPacketFiltering(); + mWifiStateMachine.stopPacketFiltering(); } int uid = Binder.getCallingUid(); @@ -2453,7 +1476,7 @@ public class WifiService extends IWifiManager.Stub { removed.unlinkDeathRecipient(); } if (mMulticasters.size() == 0) { - mWifiStateTracker.startPacketFiltering(); + mWifiStateMachine.startPacketFiltering(); } Long ident = Binder.clearCallingIdentity(); @@ -2472,4 +1495,144 @@ public class WifiService extends IWifiManager.Stub { return (mMulticasters.size() > 0); } } + + private void checkAndSetNotification() { + // If we shouldn't place a notification on available networks, then + // don't bother doing any of the following + if (!mNotificationEnabled) return; + + State state = mNetworkInfo.getState(); + if ((state == NetworkInfo.State.DISCONNECTED) + || (state == NetworkInfo.State.UNKNOWN)) { + // Look for an open network + List<ScanResult> scanResults = mWifiStateMachine.syncGetScanResultsList(); + if (scanResults != null) { + int numOpenNetworks = 0; + for (int i = scanResults.size() - 1; i >= 0; i--) { + ScanResult scanResult = scanResults.get(i); + + if (TextUtils.isEmpty(scanResult.capabilities)) { + numOpenNetworks++; + } + } + + if (numOpenNetworks > 0) { + if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING) { + /* + * We've scanned continuously at least + * NUM_SCANS_BEFORE_NOTIFICATION times. The user + * probably does not have a remembered network in range, + * since otherwise supplicant would have tried to + * associate and thus resetting this counter. + */ + setNotificationVisible(true, numOpenNetworks, false, 0); + } + return; + } + } + } + + // No open networks in range, remove the notification + setNotificationVisible(false, 0, false, 0); + } + + /** + * Clears variables related to tracking whether a notification has been + * shown recently and clears the current notification. + */ + private void resetNotification() { + mNotificationRepeatTime = 0; + mNumScansSinceNetworkStateChange = 0; + setNotificationVisible(false, 0, false, 0); + } + + /** + * Display or don't display a notification that there are open Wi-Fi networks. + * @param visible {@code true} if notification should be visible, {@code false} otherwise + * @param numNetworks the number networks seen + * @param force {@code true} to force notification to be shown/not-shown, + * even if it is already shown/not-shown. + * @param delay time in milliseconds after which the notification should be made + * visible or invisible. + */ + private void setNotificationVisible(boolean visible, int numNetworks, boolean force, + int delay) { + + // Since we use auto cancel on the notification, when the + // mNetworksAvailableNotificationShown is true, the notification may + // have actually been canceled. However, when it is false we know + // for sure that it is not being shown (it will not be shown any other + // place than here) + + // If it should be hidden and it is already hidden, then noop + if (!visible && !mNotificationShown && !force) { + return; + } + + NotificationManager notificationManager = (NotificationManager) mContext + .getSystemService(Context.NOTIFICATION_SERVICE); + + Message message; + if (visible) { + + // Not enough time has passed to show the notification again + if (System.currentTimeMillis() < mNotificationRepeatTime) { + return; + } + + if (mNotification == null) { + // Cache the Notification object. + mNotification = new Notification(); + mNotification.when = 0; + mNotification.icon = ICON_NETWORKS_AVAILABLE; + mNotification.flags = Notification.FLAG_AUTO_CANCEL; + mNotification.contentIntent = PendingIntent.getActivity(mContext, 0, + new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK), 0); + } + + CharSequence title = mContext.getResources().getQuantityText( + com.android.internal.R.plurals.wifi_available, numNetworks); + CharSequence details = mContext.getResources().getQuantityText( + com.android.internal.R.plurals.wifi_available_detailed, numNetworks); + mNotification.tickerText = title; + mNotification.setLatestEventInfo(mContext, title, details, mNotification.contentIntent); + + mNotificationRepeatTime = System.currentTimeMillis() + NOTIFICATION_REPEAT_DELAY_MS; + + notificationManager.notify(ICON_NETWORKS_AVAILABLE, mNotification); + } else { + notificationManager.cancel(ICON_NETWORKS_AVAILABLE); + } + + mNotificationShown = visible; + } + + private class NotificationEnabledSettingObserver extends ContentObserver { + + public NotificationEnabledSettingObserver(Handler handler) { + super(handler); + } + + public void register() { + ContentResolver cr = mContext.getContentResolver(); + cr.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this); + mNotificationEnabled = getValue(); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + + mNotificationEnabled = getValue(); + resetNotification(); + } + + private boolean getValue() { + return Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1; + } + } + + } diff --git a/services/java/com/android/server/WifiStateTracker.java b/services/java/com/android/server/WifiStateTracker.java new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/services/java/com/android/server/WifiStateTracker.java diff --git a/services/java/com/android/server/WifiWatchdogService.java b/services/java/com/android/server/WifiWatchdogService.java index 445dd03..46d6bef 100644 --- a/services/java/com/android/server/WifiWatchdogService.java +++ b/services/java/com/android/server/WifiWatchdogService.java @@ -27,7 +27,6 @@ import android.net.DhcpInfo; import android.net.wifi.ScanResult; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.net.wifi.WifiStateTracker; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -77,7 +76,6 @@ public class WifiWatchdogService { private Context mContext; private ContentResolver mContentResolver; - private WifiStateTracker mWifiStateTracker; private WifiManager mWifiManager; /** @@ -108,10 +106,9 @@ public class WifiWatchdogService { /** Whether the current AP check should be canceled. */ private boolean mShouldCancel; - WifiWatchdogService(Context context, WifiStateTracker wifiStateTracker) { + WifiWatchdogService(Context context) { mContext = context; mContentResolver = context.getContentResolver(); - mWifiStateTracker = wifiStateTracker; mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); createThread(); @@ -275,12 +272,13 @@ public class WifiWatchdogService { /** * Unregister broadcasts and quit the watchdog thread */ - private void quit() { - unregisterForWifiBroadcasts(); - mContext.getContentResolver().unregisterContentObserver(mContentObserver); - mHandler.removeAllActions(); - mHandler.getLooper().quit(); - } + //TODO: Change back to running WWS when needed +// private void quit() { +// unregisterForWifiBroadcasts(); +// mContext.getContentResolver().unregisterContentObserver(mContentObserver); +// mHandler.removeAllActions(); +// mHandler.getLooper().quit(); +// } /** * Waits for the main watchdog thread to create the handler. @@ -751,7 +749,7 @@ public class WifiWatchdogService { // Black list this "bad" AP, this will cause an attempt to connect to another blacklistAp(ap.bssid); // Initiate an association to an alternate AP - mWifiStateTracker.reassociate(); + mWifiManager.reassociate(); } private void blacklistAp(String bssid) { @@ -762,10 +760,7 @@ public class WifiWatchdogService { // Before taking action, make sure we should not cancel our processing if (shouldCancel()) return; - if (!mWifiStateTracker.addToBlacklist(bssid)) { - // There's a known bug where this method returns failure on success - //Slog.e(TAG, "Blacklisting " + bssid + " failed"); - } + mWifiManager.addToBlacklist(bssid); if (D) { myLogD("Blacklisting " + bssid); @@ -860,10 +855,7 @@ public class WifiWatchdogService { * (and blacklisted them). Clear the blacklist so the AP with best * signal is chosen. */ - if (!mWifiStateTracker.clearBlacklist()) { - // There's a known bug where this method returns failure on success - //Slog.e(TAG, "Clearing blacklist failed"); - } + mWifiManager.clearBlacklist(); if (V) { myLogV("handleSleep: Set state to SLEEP and cleared blacklist"); @@ -934,7 +926,7 @@ public class WifiWatchdogService { * should revert anything done by the watchdog monitoring. */ private void handleReset() { - mWifiStateTracker.clearBlacklist(); + mWifiManager.clearBlacklist(); setIdleState(true); } @@ -1151,7 +1143,7 @@ public class WifiWatchdogService { private void handleWifiStateChanged(int wifiState) { if (wifiState == WifiManager.WIFI_STATE_DISABLED) { - quit(); + onDisconnected(); } else if (wifiState == WifiManager.WIFI_STATE_ENABLED) { onEnabled(); } diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java index 667b544..2613988 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -23,13 +23,11 @@ import static android.view.WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; -import static android.view.WindowManager.LayoutParams.FLAG_SYSTEM_ERROR; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; -import static android.view.WindowManager.LayoutParams.MEMORY_TYPE_PUSH_BUFFERS; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; @@ -39,6 +37,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import com.android.internal.app.IBatteryStats; import com.android.internal.policy.PolicyManager; import com.android.internal.policy.impl.PhoneWindowManager; +import com.android.internal.view.BaseInputHandler; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethodClient; import com.android.internal.view.IInputMethodManager; @@ -48,8 +47,11 @@ import com.android.server.am.BatteryStatsService; import android.Manifest; import android.app.ActivityManagerNative; import android.app.IActivityManager; +import android.app.StatusBarManager; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; +import android.content.ClipData; +import android.content.ClipDescription; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -57,10 +59,13 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PixelFormat; +import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.Region; import android.graphics.Typeface; @@ -81,6 +86,7 @@ import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; import android.os.TokenWatcher; @@ -92,8 +98,8 @@ import android.util.Slog; import android.util.SparseIntArray; import android.util.TypedValue; import android.view.Display; +import android.view.DragEvent; import android.view.Gravity; -import android.view.HapticFeedbackConstants; import android.view.IApplicationToken; import android.view.IOnKeyguardExitResult; import android.view.IRotationWatcher; @@ -103,6 +109,8 @@ import android.view.IWindowSession; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; +import android.view.InputHandler; +import android.view.InputQueue; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; @@ -119,7 +127,6 @@ import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.Transformation; -import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.DataInputStream; import java.io.File; @@ -143,6 +150,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { static final String TAG = "WindowManager"; static final boolean DEBUG = false; + static final boolean DEBUG_ADD_REMOVE = false; static final boolean DEBUG_FOCUS = false; static final boolean DEBUG_ANIM = false; static final boolean DEBUG_LAYOUT = false; @@ -152,13 +160,14 @@ public class WindowManagerService extends IWindowManager.Stub static final boolean DEBUG_INPUT_METHOD = false; static final boolean DEBUG_VISIBILITY = false; static final boolean DEBUG_WINDOW_MOVEMENT = false; + static final boolean DEBUG_TOKEN_MOVEMENT = false; static final boolean DEBUG_ORIENTATION = false; static final boolean DEBUG_CONFIGURATION = false; static final boolean DEBUG_APP_TRANSITIONS = false; static final boolean DEBUG_STARTING_WINDOW = false; static final boolean DEBUG_REORDER = false; static final boolean DEBUG_WALLPAPER = false; - static final boolean DEBUG_FREEZE = false; + static final boolean DEBUG_DRAG = false; static final boolean SHOW_TRANSACTIONS = false; static final boolean HIDE_STACK_CRAWLS = true; @@ -195,13 +204,19 @@ public class WindowManagerService extends IWindowManager.Stub */ static final int DEFAULT_FADE_IN_OUT_DURATION = 400; - /** Adjustment to time to perform a dim, to make it more dramatic. + /** + * If true, the window manager will do its own custom freezing and general + * management of the screen during rotation. */ - static final int DIM_DURATION_MULTIPLIER = 6; - + static final boolean CUSTOM_SCREEN_ROTATION = true; + // Maximum number of milliseconds to wait for input event injection. // FIXME is this value reasonable? private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000; + + // Maximum number of milliseconds to wait for input devices to be enumerated before + // proceding with safe mode detection. + private static final int INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS = 1000; // Default input dispatching timeout in nanoseconds. private static final long DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS = 5000 * 1000000L; @@ -288,12 +303,6 @@ public class WindowManagerService extends IWindowManager.Stub new HashMap<IBinder, WindowToken>(); /** - * The same tokens as mTokenMap, stored in a list for efficient iteration - * over them. - */ - final ArrayList<WindowToken> mTokenList = new ArrayList<WindowToken>(); - - /** * Window tokens that are in the process of exiting, but still * on screen for animations. */ @@ -302,7 +311,7 @@ public class WindowManagerService extends IWindowManager.Stub /** * Z-ordered (bottom-most first) list of all application tokens, for * controlling the ordering of windows in different applications. This - * contains WindowToken objects. + * contains AppWindowToken objects. */ final ArrayList<AppWindowToken> mAppTokens = new ArrayList<AppWindowToken>(); @@ -319,18 +328,6 @@ public class WindowManagerService extends IWindowManager.Stub final ArrayList<AppWindowToken> mFinishedStarting = new ArrayList<AppWindowToken>(); /** - * This was the app token that was used to retrieve the last enter - * animation. It will be used for the next exit animation. - */ - AppWindowToken mLastEnterAnimToken; - - /** - * These were the layout params used to retrieve the last enter animation. - * They will be used for the next exit animation. - */ - LayoutParams mLastEnterAnimParams; - - /** * Z-ordered (bottom-most first) list of all Window objects. */ final ArrayList<WindowState> mWindows = new ArrayList<WindowState>(); @@ -348,6 +345,11 @@ public class WindowManagerService extends IWindowManager.Stub final ArrayList<WindowState> mPendingRemove = new ArrayList<WindowState>(); /** + * Used when processing mPendingRemove to avoid working on the original array. + */ + WindowState[] mPendingRemoveTmp = new WindowState[20]; + + /** * Windows whose surface should be destroyed. */ final ArrayList<WindowState> mDestroySurface = new ArrayList<WindowState>(); @@ -364,6 +366,12 @@ public class WindowManagerService extends IWindowManager.Stub */ ArrayList<WindowState> mForceRemoves; + /** + * Used when rebuilding window list to keep track of windows that have + * been removed. + */ + WindowState[] mRebuildTmp = new WindowState[20]; + IInputMethodManager mInputMethodManager; SurfaceSession mFxSession; @@ -371,6 +379,8 @@ public class WindowManagerService extends IWindowManager.Stub Surface mBlurSurface; boolean mBlurShown; Watermark mWatermark; + StrictModeFlash mStrictModeFlash; + ScreenRotationAnimation mScreenRotationAnimation; int mTransactionSequence = 0; @@ -387,6 +397,8 @@ public class WindowManagerService extends IWindowManager.Stub int mLastRotationFlags; ArrayList<IRotationWatcher> mRotationWatchers = new ArrayList<IRotationWatcher>(); + int mDeferredRotation; + int mDeferredRotationAnimFlags; boolean mLayoutNeeded = true; boolean mAnimationPending = false; @@ -437,13 +449,16 @@ public class WindowManagerService extends IWindowManager.Stub // This just indicates the window the input method is on top of, not // necessarily the window its input is going to. WindowState mInputMethodTarget = null; - WindowState mUpcomingInputMethodTarget = null; boolean mInputMethodTargetWaitingAnim; int mInputMethodAnimLayerAdjustment; WindowState mInputMethodWindow = null; final ArrayList<WindowState> mInputMethodDialogs = new ArrayList<WindowState>(); + boolean mHardKeyboardAvailable; + boolean mHardKeyboardEnabled; + OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener; + final ArrayList<WindowToken> mWallpaperTokens = new ArrayList<WindowToken>(); // If non-null, this is the currently visible window that is associated @@ -455,6 +470,11 @@ public class WindowManagerService extends IWindowManager.Stub // If non-null, we are in the middle of animating from one wallpaper target // to another, and this is the higher one in Z-order. WindowState mUpperWallpaperTarget = null; + // Window currently running an animation that has requested it be detached + // from the wallpaper. This means we need to ensure the wallpaper is + // visible behind it in case it animates in a way that would allow it to be + // seen. + WindowState mWindowDetachedWallpaper = null; int mWallpaperAnimLayerAdjustment; float mLastWallpaperX = -1; float mLastWallpaperY = -1; @@ -486,6 +506,392 @@ public class WindowManagerService extends IWindowManager.Stub boolean mTurnOnScreen; /** + * Drag/drop state + */ + class DragState { + IBinder mToken; + Surface mSurface; + int mFlags; + IBinder mLocalWin; + ClipData mData; + ClipDescription mDataDescription; + boolean mDragResult; + float mCurrentX, mCurrentY; + float mThumbOffsetX, mThumbOffsetY; + InputChannel mServerChannel, mClientChannel; + WindowState mTargetWindow; + ArrayList<WindowState> mNotifiedWindows; + boolean mDragInProgress; + + private final Region mTmpRegion = new Region(); + + DragState(IBinder token, Surface surface, int flags, IBinder localWin) { + mToken = token; + mSurface = surface; + mFlags = flags; + mLocalWin = localWin; + mNotifiedWindows = new ArrayList<WindowState>(); + } + + void reset() { + if (mSurface != null) { + mSurface.destroy(); + } + mSurface = null; + mFlags = 0; + mLocalWin = null; + mToken = null; + mData = null; + mThumbOffsetX = mThumbOffsetY = 0; + mNotifiedWindows = null; + } + + void register() { + if (DEBUG_DRAG) Slog.d(TAG, "registering drag input channel"); + if (mClientChannel != null) { + Slog.e(TAG, "Duplicate register of drag input channel"); + } else { + InputChannel[] channels = InputChannel.openInputChannelPair("drag"); + mServerChannel = channels[0]; + mClientChannel = channels[1]; + mInputManager.registerInputChannel(mServerChannel, null); + InputQueue.registerInputChannel(mClientChannel, mDragInputHandler, + mH.getLooper().getQueue()); + } + } + + void unregister() { + if (DEBUG_DRAG) Slog.d(TAG, "unregistering drag input channel"); + if (mClientChannel == null) { + Slog.e(TAG, "Unregister of nonexistent drag input channel"); + } else { + mInputManager.unregisterInputChannel(mServerChannel); + InputQueue.unregisterInputChannel(mClientChannel); + mClientChannel.dispose(); + mServerChannel.dispose(); + mClientChannel = null; + mServerChannel = null; + } + } + + int getDragLayerLw() { + return mPolicy.windowTypeToLayerLw(WindowManager.LayoutParams.TYPE_DRAG) + * TYPE_LAYER_MULTIPLIER + + TYPE_LAYER_OFFSET; + } + + /* call out to each visible window/session informing it about the drag + */ + void broadcastDragStartedLw(final float touchX, final float touchY) { + // Cache a base-class instance of the clip metadata so that parceling + // works correctly in calling out to the apps. + mDataDescription = (mData != null) ? mData.getDescription() : null; + mNotifiedWindows.clear(); + mDragInProgress = true; + + if (DEBUG_DRAG) { + Slog.d(TAG, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")"); + } + + final int N = mWindows.size(); + for (int i = 0; i < N; i++) { + sendDragStartedLw(mWindows.get(i), touchX, touchY, mDataDescription); + } + } + + /* helper - send a caller-provided event, presumed to be DRAG_STARTED, if the + * designated window is potentially a drop recipient. There are race situations + * around DRAG_ENDED broadcast, so we make sure that once we've declared that + * the drag has ended, we never send out another DRAG_STARTED for this drag action. + * + * This method clones the 'event' parameter if it's being delivered to the same + * process, so it's safe for the caller to call recycle() on the event afterwards. + */ + private void sendDragStartedLw(WindowState newWin, float touchX, float touchY, + ClipDescription desc) { + // Don't actually send the event if the drag is supposed to be pinned + // to the originating window but 'newWin' is not that window. + if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0) { + final IBinder winBinder = newWin.mClient.asBinder(); + if (winBinder != mLocalWin) { + if (DEBUG_DRAG) { + Slog.d(TAG, "Not dispatching local DRAG_STARTED to " + newWin); + } + return; + } + } + + if (mDragInProgress && newWin.isPotentialDragTarget()) { + DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_STARTED, + touchX - newWin.mFrame.left, touchY - newWin.mFrame.top, + null, desc, null, false); + try { + newWin.mClient.dispatchDragEvent(event); + // track each window that we've notified that the drag is starting + mNotifiedWindows.add(newWin); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to drag-start window " + newWin); + } finally { + // if the callee was local, the dispatch has already recycled the event + if (Process.myPid() != newWin.mSession.mPid) { + event.recycle(); + } + } + } + } + + /* helper - construct and send a DRAG_STARTED event only if the window has not + * previously been notified, i.e. it became visible after the drag operation + * was begun. This is a rare case. + */ + private void sendDragStartedIfNeededLw(WindowState newWin) { + if (mDragInProgress) { + // If we have sent the drag-started, we needn't do so again + for (WindowState ws : mNotifiedWindows) { + if (ws == newWin) { + return; + } + } + if (DEBUG_DRAG) { + Slog.d(TAG, "need to send DRAG_STARTED to new window " + newWin); + } + sendDragStartedLw(newWin, mCurrentX, mCurrentY, mDataDescription); + } + } + + void broadcastDragEndedLw() { + if (DEBUG_DRAG) { + Slog.d(TAG, "broadcasting DRAG_ENDED"); + } + DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED, + 0, 0, null, null, null, mDragResult); + for (WindowState ws: mNotifiedWindows) { + try { + ws.mClient.dispatchDragEvent(evt); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to drag-end window " + ws); + } + } + mNotifiedWindows.clear(); + mDragInProgress = false; + evt.recycle(); + } + + void endDragLw() { + mDragState.broadcastDragEndedLw(); + + // stop intercepting input + mDragState.unregister(); + mInputMonitor.updateInputWindowsLw(true /*force*/); + + // free our resources and drop all the object references + mDragState.reset(); + mDragState = null; + + if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-drag rotation"); + boolean changed = setRotationUncheckedLocked( + WindowManagerPolicy.USE_LAST_ROTATION, 0, false); + if (changed) { + mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION); + } + } + + void notifyMoveLw(float x, float y) { + final int myPid = Process.myPid(); + + // Move the surface to the given touch + if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION notifyMoveLw"); + Surface.openTransaction(); + try { + mSurface.setPosition((int)(x - mThumbOffsetX), (int)(y - mThumbOffsetY)); + if (SHOW_TRANSACTIONS) Slog.i(TAG, " DRAG " + + mSurface + ": pos=(" + + (int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")"); + } finally { + Surface.closeTransaction(); + if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION notifyMoveLw"); + } + + // Tell the affected window + WindowState touchedWin = getTouchedWinAtPointLw(x, y); + if (touchedWin == null) { + if (DEBUG_DRAG) Slog.d(TAG, "No touched win at x=" + x + " y=" + y); + return; + } + if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0) { + final IBinder touchedBinder = touchedWin.mClient.asBinder(); + if (touchedBinder != mLocalWin) { + // This drag is pinned only to the originating window, but the drag + // point is outside that window. Pretend it's over empty space. + touchedWin = null; + } + } + try { + // have we dragged over a new window? + if ((touchedWin != mTargetWindow) && (mTargetWindow != null)) { + if (DEBUG_DRAG) { + Slog.d(TAG, "sending DRAG_EXITED to " + mTargetWindow); + } + // force DRAG_EXITED_EVENT if appropriate + DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_EXITED, + x - mTargetWindow.mFrame.left, y - mTargetWindow.mFrame.top, + null, null, null, false); + mTargetWindow.mClient.dispatchDragEvent(evt); + if (myPid != mTargetWindow.mSession.mPid) { + evt.recycle(); + } + } + if (touchedWin != null) { + if (false && DEBUG_DRAG) { + Slog.d(TAG, "sending DRAG_LOCATION to " + touchedWin); + } + DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_LOCATION, + x - touchedWin.mFrame.left, y - touchedWin.mFrame.top, + null, null, null, false); + touchedWin.mClient.dispatchDragEvent(evt); + if (myPid != touchedWin.mSession.mPid) { + evt.recycle(); + } + } + } catch (RemoteException e) { + Slog.w(TAG, "can't send drag notification to windows"); + } + mTargetWindow = touchedWin; + } + + // Tell the drop target about the data. Returns 'true' if we can immediately + // dispatch the global drag-ended message, 'false' if we need to wait for a + // result from the recipient. + boolean notifyDropLw(float x, float y) { + WindowState touchedWin = getTouchedWinAtPointLw(x, y); + if (touchedWin == null) { + // "drop" outside a valid window -- no recipient to apply a + // timeout to, and we can send the drag-ended message immediately. + mDragResult = false; + return true; + } + + if (DEBUG_DRAG) { + Slog.d(TAG, "sending DROP to " + touchedWin); + } + final int myPid = Process.myPid(); + final IBinder token = touchedWin.mClient.asBinder(); + DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DROP, + x - touchedWin.mFrame.left, y - touchedWin.mFrame.top, + null, null, mData, false); + try { + touchedWin.mClient.dispatchDragEvent(evt); + + // 5 second timeout for this window to respond to the drop + mH.removeMessages(H.DRAG_END_TIMEOUT, token); + Message msg = mH.obtainMessage(H.DRAG_END_TIMEOUT, token); + mH.sendMessageDelayed(msg, 5000); + } catch (RemoteException e) { + Slog.w(TAG, "can't send drop notification to win " + touchedWin); + return true; + } finally { + if (myPid != touchedWin.mSession.mPid) { + evt.recycle(); + } + } + mToken = token; + return false; + } + + // Find the visible, touch-deliverable window under the given point + private WindowState getTouchedWinAtPointLw(float xf, float yf) { + WindowState touchedWin = null; + final int x = (int) xf; + final int y = (int) yf; + final ArrayList<WindowState> windows = mWindows; + final int N = windows.size(); + for (int i = N - 1; i >= 0; i--) { + WindowState child = windows.get(i); + final int flags = child.mAttrs.flags; + if (!child.isVisibleLw()) { + // not visible == don't tell about drags + continue; + } + if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) { + // not touchable == don't tell about drags + continue; + } + + child.getTouchableRegion(mTmpRegion); + + final int touchFlags = flags & + (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL); + if (mTmpRegion.contains(x, y) || touchFlags == 0) { + // Found it + touchedWin = child; + break; + } + } + + return touchedWin; + } + } + + DragState mDragState = null; + private final InputHandler mDragInputHandler = new BaseInputHandler() { + @Override + public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) { + boolean handled = false; + try { + if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0 + && mDragState != null) { + boolean endDrag = false; + final float newX = event.getRawX(); + final float newY = event.getRawY(); + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: { + if (DEBUG_DRAG) { + Slog.w(TAG, "Unexpected ACTION_DOWN in drag layer"); + } + } break; + + case MotionEvent.ACTION_MOVE: { + synchronized (mWindowMap) { + // move the surface and tell the involved window(s) where we are + mDragState.notifyMoveLw(newX, newY); + } + } break; + + case MotionEvent.ACTION_UP: { + if (DEBUG_DRAG) Slog.d(TAG, "Got UP on move channel; dropping at " + + newX + "," + newY); + synchronized (mWindowMap) { + endDrag = mDragState.notifyDropLw(newX, newY); + } + } break; + + case MotionEvent.ACTION_CANCEL: { + if (DEBUG_DRAG) Slog.d(TAG, "Drag cancelled!"); + endDrag = true; + } break; + } + + if (endDrag) { + if (DEBUG_DRAG) Slog.d(TAG, "Drag ended; tearing down state"); + // tell all the windows that the drag has ended + synchronized (mWindowMap) { + mDragState.endDragLw(); + } + } + + handled = true; + } + } catch (Exception e) { + Slog.e(TAG, "Exception caught by drag handleMotion", e); + } finally { + finishedCallback.finished(handled); + } + } + }; + + /** * Whether the UI is currently running in touch mode (not showing * navigational focus because the user is directly pressing the screen). */ @@ -508,7 +914,7 @@ public class WindowManagerService extends IWindowManager.Stub Rect mCompatibleScreenFrame = new Rect(); // The surface used to fill the outer rim of the app running in compatibility mode. Surface mBackgroundFillerSurface = null; - boolean mBackgroundFillerShown = false; + WindowState mBackgroundFillerTarget = null; public static WindowManagerService main(Context context, PowerManagerService pm, boolean haveInputMethods) { @@ -554,6 +960,11 @@ public class WindowManagerService extends IWindowManager.Stub notifyAll(); } + // For debug builds, log event loop stalls to dropbox for analysis. + if (StrictMode.conditionallyEnableDebugLogging()) { + Slog.i(TAG, "Enabled StrictMode logging for WMThread's Looper"); + } + Looper.loop(); } } @@ -591,6 +1002,11 @@ public class WindowManagerService extends IWindowManager.Stub notifyAll(); } + // For debug builds, log event loop stalls to dropbox for analysis. + if (StrictMode.conditionallyEnableDebugLogging()) { + Slog.i(TAG, "Enabled StrictMode for PolicyThread's Looper"); + } + Looper.loop(); } } @@ -664,7 +1080,7 @@ public class WindowManagerService extends IWindowManager.Stub private void placeWindowAfter(WindowState pos, WindowState window) { final int i = mWindows.indexOf(pos); - if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Slog.v( + if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v( TAG, "Adding window " + window + " at " + (i+1) + " of " + mWindows.size() + " (after " + pos + ")"); mWindows.add(i+1, window); @@ -673,7 +1089,7 @@ public class WindowManagerService extends IWindowManager.Stub private void placeWindowBefore(WindowState pos, WindowState window) { final int i = mWindows.indexOf(pos); - if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Slog.v( + if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v( TAG, "Adding window " + window + " at " + i + " of " + mWindows.size() + " (before " + pos + ")"); mWindows.add(i, window); @@ -731,9 +1147,10 @@ public class WindowManagerService extends IWindowManager.Stub //apptoken note that the window could be a floating window //that was created later or a window at the top of the list of //windows associated with this token. - if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Slog.v( - TAG, "Adding window " + win + " at " - + (newIdx+1) + " of " + N); + if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) { + Slog.v(TAG, "Adding window " + win + " at " + + (newIdx+1) + " of " + N); + } localmWindows.add(newIdx+1, win); mWindowsChanged = true; } @@ -812,9 +1229,10 @@ public class WindowManagerService extends IWindowManager.Stub break; } } - if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Slog.v( - TAG, "Adding window " + win + " at " - + i + " of " + N); + if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) { + Slog.v(TAG, "Adding window " + win + " at " + + i + " of " + N); + } localmWindows.add(i, win); mWindowsChanged = true; } @@ -830,13 +1248,14 @@ public class WindowManagerService extends IWindowManager.Stub } } if (i < 0) i = 0; - if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Slog.v( + if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v( TAG, "Adding window " + win + " at " + i + " of " + N); localmWindows.add(i, win); mWindowsChanged = true; } if (addToToken) { + if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token); token.windows.add(tokenWindowsPos, win); } @@ -859,6 +1278,7 @@ public class WindowManagerService extends IWindowManager.Stub // in the same sublayer. if (wSublayer >= sublayer) { if (addToToken) { + if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token); token.windows.add(i, win); } placeWindowBefore( @@ -870,6 +1290,7 @@ public class WindowManagerService extends IWindowManager.Stub // in the same sublayer. if (wSublayer > sublayer) { if (addToToken) { + if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token); token.windows.add(i, win); } placeWindowBefore(w, win); @@ -879,6 +1300,7 @@ public class WindowManagerService extends IWindowManager.Stub } if (i >= NA) { if (addToToken) { + if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token); token.windows.add(win); } if (sublayer < 0) { @@ -900,7 +1322,20 @@ public class WindowManagerService extends IWindowManager.Stub static boolean canBeImeTarget(WindowState w) { final int fl = w.mAttrs.flags & (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM); - if (fl == 0 || fl == (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM)) { + if (fl == 0 || fl == (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM) + || w.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) { + if (DEBUG_INPUT_METHOD) { + Slog.i(TAG, "isVisibleOrAdding " + w + ": " + w.isVisibleOrAdding()); + if (!w.isVisibleOrAdding()) { + Slog.i(TAG, " mSurface=" + w.mSurface + " reportDestroy=" + w.mReportDestroySurface + + " relayoutCalled=" + w.mRelayoutCalled + " viewVis=" + w.mViewVisibility + + " policyVis=" + w.mPolicyVisibility + " attachHid=" + w.mAttachedHidden + + " exiting=" + w.mExiting + " destroying=" + w.mDestroying); + if (w.mAppToken != null) { + Slog.i(TAG, " mAppToken.hiddenRequested=" + w.mAppToken.hiddenRequested); + } + } + } return w.isVisibleOrAdding(); } return false; @@ -915,8 +1350,8 @@ public class WindowManagerService extends IWindowManager.Stub i--; w = localmWindows.get(i); - //Slog.i(TAG, "Checking window @" + i + " " + w + " fl=0x" - // + Integer.toHexString(w.mAttrs.flags)); + if (DEBUG_INPUT_METHOD && willMove) Slog.i(TAG, "Checking window @" + i + + " " + w + " fl=0x" + Integer.toHexString(w.mAttrs.flags)); if (canBeImeTarget(w)) { //Slog.i(TAG, "Putting input method here!"); @@ -938,7 +1373,23 @@ public class WindowManagerService extends IWindowManager.Stub } } - mUpcomingInputMethodTarget = w; + if (DEBUG_INPUT_METHOD && willMove) Slog.v(TAG, "Proposed new IME target: " + w); + + // Now, a special case -- if the last target's window is in the + // process of exiting, and is above the new target, keep on the + // last target to avoid flicker. Consider for example a Dialog with + // the IME shown: when the Dialog is dismissed, we want to keep + // the IME above it until it is completely gone so it doesn't drop + // behind the dialog or its full-screen scrim. + if (mInputMethodTarget != null && w != null + && mInputMethodTarget.isDisplayedLw() + && mInputMethodTarget.mExiting) { + if (mInputMethodTarget.mAnimLayer > w.mAnimLayer) { + w = mInputMethodTarget; + i = localmWindows.indexOf(w); + if (DEBUG_INPUT_METHOD) Slog.v(TAG, "Current target higher, switching to: " + w); + } + } if (DEBUG_INPUT_METHOD) Slog.v(TAG, "Desired input method target=" + w + " willMove=" + willMove); @@ -992,6 +1443,7 @@ public class WindowManagerService extends IWindowManager.Stub // with an animation, and it is on top of the next target // we will be over, then hold off on moving until // that is done. + mInputMethodTargetWaitingAnim = true; mInputMethodTarget = highestTarget; return highestPos + 1; } @@ -1012,6 +1464,7 @@ public class WindowManagerService extends IWindowManager.Stub + mInputMethodTarget + " to " + w, e); } mInputMethodTarget = w; + mInputMethodTargetWaitingAnim = false; if (w.mAppToken != null) { setInputMethodAnimLayerAdjustment(w.mAppToken.animLayerAdjustment); } else { @@ -1040,7 +1493,7 @@ public class WindowManagerService extends IWindowManager.Stub int pos = findDesiredInputMethodWindowIndexLocked(true); if (pos >= 0) { win.mTargetAppToken = mInputMethodTarget.mAppToken; - if (DEBUG_WINDOW_MOVEMENT) Slog.v( + if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v( TAG, "Adding input method window " + win + " at " + pos); mWindows.add(pos, win); mWindowsChanged = true; @@ -1227,7 +1680,7 @@ public class WindowManagerService extends IWindowManager.Stub } imPos = tmpRemoveWindowLocked(imPos, imWin); if (DEBUG_INPUT_METHOD) { - Slog.v(TAG, "List after moving with new pos " + imPos + ":"); + Slog.v(TAG, "List after removing with new pos " + imPos + ":"); logWindowList(" "); } imWin.mTargetAppToken = mInputMethodTarget.mAppToken; @@ -1304,6 +1757,7 @@ public class WindowManagerService extends IWindowManager.Stub int foundI = 0; WindowState topCurW = null; int topCurI = 0; + int windowDetachedI = -1; int i = N; while (i > 0) { i--; @@ -1316,13 +1770,12 @@ public class WindowManagerService extends IWindowManager.Stub continue; } topCurW = null; - if (w.mAppToken != null) { + if (w != mWindowDetachedWallpaper && w.mAppToken != null) { // If this window's app token is hidden and not animating, // it is of no interest to us. if (w.mAppToken.hidden && w.mAppToken.animation == null) { if (DEBUG_WALLPAPER) Slog.v(TAG, - "Skipping hidden or animating token: " + w); - topCurW = null; + "Skipping not hidden or animating token: " + w); continue; } } @@ -1347,9 +1800,18 @@ public class WindowManagerService extends IWindowManager.Stub continue; } break; + } else if (w == mWindowDetachedWallpaper) { + windowDetachedI = i; } } + if (foundW == null && windowDetachedI >= 0) { + if (DEBUG_WALLPAPER) Slog.v(TAG, + "Found animating detached wallpaper activity: #" + i + "=" + w); + foundW = w; + foundI = windowDetachedI; + } + if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { // If we are currently waiting for an app transition, and either // the current target or the next target are involved with it, @@ -1481,9 +1943,10 @@ public class WindowManagerService extends IWindowManager.Stub WindowState wb = localmWindows.get(foundI-1); if (wb.mBaseLayer < maxLayer && wb.mAttachedWindow != foundW && - wb.mAttachedWindow != foundW.mAttachedWindow && + (foundW.mAttachedWindow == null || + wb.mAttachedWindow != foundW.mAttachedWindow) && (wb.mAttrs.type != TYPE_APPLICATION_STARTING || - wb.mToken != foundW.mToken)) { + foundW.mToken == null || wb.mToken != foundW.mToken)) { // This window is not related to the previous one in any // interesting way, so stop here. break; @@ -1581,9 +2044,10 @@ public class WindowManagerService extends IWindowManager.Stub } // Now stick it in. - if (DEBUG_WALLPAPER || DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, - "Moving wallpaper " + wallpaper - + " from " + oldIndex + " to " + foundI); + if (DEBUG_WALLPAPER || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) { + Slog.v(TAG, "Moving wallpaper " + wallpaper + + " from " + oldIndex + " to " + foundI); + } localmWindows.add(foundI, wallpaper); mWindowsChanged = true; @@ -1788,18 +2252,11 @@ public class WindowManagerService extends IWindowManager.Stub boolean reportNewConfig = false; WindowState attachedWindow = null; WindowState win = null; + long origId; synchronized(mWindowMap) { - // Instantiating a Display requires talking with the simulator, - // so don't do it until we know the system is mostly up and - // running. if (mDisplay == null) { - WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); - mDisplay = wm.getDefaultDisplay(); - mInitialDisplayWidth = mDisplay.getWidth(); - mInitialDisplayHeight = mDisplay.getHeight(); - mInputManager.setDisplaySize(0, mInitialDisplayWidth, mInitialDisplayHeight); - reportNewConfig = true; + throw new IllegalStateException("Display has not been initialialized"); } if (mWindowMap.containsKey(client.asBinder())) { @@ -1898,18 +2355,17 @@ public class WindowManagerService extends IWindowManager.Stub win.mInputChannel = inputChannels[0]; inputChannels[1].transferToBinderOutParameter(outInputChannel); - mInputManager.registerInputChannel(win.mInputChannel); + mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle); } // From now on, no exceptions or errors allowed! res = WindowManagerImpl.ADD_OKAY; - final long origId = Binder.clearCallingIdentity(); + origId = Binder.clearCallingIdentity(); if (addToken) { mTokenMap.put(attrs.token, token); - mTokenList.add(token); } win.attach(); mWindowMap.put(client.asBinder(), win); @@ -1951,9 +2407,12 @@ public class WindowManagerService extends IWindowManager.Stub res |= WindowManagerImpl.ADD_FLAG_APP_VISIBLE; } + mInputMonitor.setUpdateInputWindowsNeededLw(); + boolean focusChanged = false; if (win.canReceiveKeys()) { - focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS); + focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS, + false /*updateInputWindows*/); if (focusChanged) { imMayMove = false; } @@ -1970,26 +2429,23 @@ public class WindowManagerService extends IWindowManager.Stub //dump(); if (focusChanged) { - finishUpdateFocusedWindowAfterAssignLayersLocked(); + finishUpdateFocusedWindowAfterAssignLayersLocked(false /*updateInputWindows*/); } - + mInputMonitor.updateInputWindowsLw(false /*force*/); + if (localLOGV) Slog.v( TAG, "New client " + client.asBinder() + ": window=" + win); - if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked()) { + if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(false)) { reportNewConfig = true; } } - // sendNewConfiguration() checks caller permissions so we must call it with - // privilege. updateOrientationFromAppTokens() clears and resets the caller - // identity anyway, so it's safe to just clear & restore around this whole - // block. - final long origId = Binder.clearCallingIdentity(); if (reportNewConfig) { sendNewConfiguration(); } + Binder.restoreCallingIdentity(origId); return res; @@ -2052,8 +2508,10 @@ public class WindowManagerService extends IWindowManager.Stub win.mExiting = true; win.mRemoveOnExit = true; mLayoutNeeded = true; - updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES); + updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, + false /*updateInputWindows*/); performLayoutAndPlaceSurfacesLocked(); + mInputMonitor.updateInputWindowsLw(false /*force*/); if (win.mAppToken != null) { win.mAppToken.updateReportedVisibilityLocked(); } @@ -2068,14 +2526,26 @@ public class WindowManagerService extends IWindowManager.Stub // So just update orientation if needed. if (wasVisible && computeForcedAppOrientationLocked() != mForcedAppOrientation - && updateOrientationFromAppTokensLocked()) { + && updateOrientationFromAppTokensLocked(false)) { mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION); } - updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL); + updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/); Binder.restoreCallingIdentity(origId); } private void removeWindowInnerLocked(Session session, WindowState win) { + if (win.mRemoved) { + // Nothing to do. + return; + } + + for (int i=win.mChildWindows.size()-1; i>=0; i--) { + WindowState cwin = win.mChildWindows.get(i); + Slog.w(TAG, "Force-removing child win " + cwin + " from container " + + win); + removeWindowInnerLocked(cwin.mSession, cwin); + } + win.mRemoved = true; if (mInputMethodTarget == win) { @@ -2091,8 +2561,10 @@ public class WindowManagerService extends IWindowManager.Stub mPolicy.removeWindowLw(win); win.removeLocked(); + if (DEBUG_ADD_REMOVE) Slog.v(TAG, "removeWindowInnerLocked: " + win); mWindowMap.remove(win.mClient.asBinder()); mWindows.remove(win); + mPendingRemove.remove(win); mWindowsChanged = true; if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Final remove of window: " + win); @@ -2104,6 +2576,7 @@ public class WindowManagerService extends IWindowManager.Stub final WindowToken token = win.mToken; final AppWindowToken atoken = win.mAppToken; + if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Removing " + win + " from " + token); token.windows.remove(win); if (atoken != null) { atoken.allAppWindows.remove(win); @@ -2114,7 +2587,6 @@ public class WindowManagerService extends IWindowManager.Stub if (token.windows.size() == 0) { if (!token.explicit) { mTokenMap.remove(token.token); - mTokenList.remove(token); } else if (atoken != null) { atoken.firstWindowDrawn = false; } @@ -2155,7 +2627,7 @@ public class WindowManagerService extends IWindowManager.Stub } } - mInputMonitor.updateInputWindowsLw(); + mInputMonitor.updateInputWindowsLw(true /*force*/); } private static void logSurface(WindowState w, String msg, RuntimeException where) { @@ -2174,15 +2646,17 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { WindowState w = windowForClientLocked(session, client, false); if ((w != null) && (w.mSurface != null)) { - if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION"); + if (SHOW_TRANSACTIONS) Slog.i(TAG, + ">>> OPEN TRANSACTION setTransparentRegion"); Surface.openTransaction(); try { if (SHOW_TRANSACTIONS) logSurface(w, "transparentRegionHint=" + region, null); w.mSurface.setTransparentRegionHint(region); } finally { - if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION"); Surface.closeTransaction(); + if (SHOW_TRANSACTIONS) Slog.i(TAG, + "<<< CLOSE TRANSACTION setTransparentRegion"); } } } @@ -2193,7 +2667,7 @@ public class WindowManagerService extends IWindowManager.Stub void setInsetsWindow(Session session, IWindow client, int touchableInsets, Rect contentInsets, - Rect visibleInsets) { + Rect visibleInsets, Region touchableRegion) { long origId = Binder.clearCallingIdentity(); try { synchronized (mWindowMap) { @@ -2202,6 +2676,7 @@ public class WindowManagerService extends IWindowManager.Stub w.mGivenInsetsPending = false; w.mGivenContentInsets.set(contentInsets); w.mGivenVisibleInsets.set(visibleInsets); + w.mGivenTouchableRegion.set(touchableRegion); w.mTouchableInsets = touchableInsets; mLayoutNeeded = true; performLayoutAndPlaceSurfacesLocked(); @@ -2286,6 +2761,15 @@ public class WindowManagerService extends IWindowManager.Stub boolean displayed = false; boolean inTouchMode; boolean configChanged; + + // if they don't have this permission, mask out the status bar bits + if (attrs != null) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR) + != PackageManager.PERMISSION_GRANTED) { + attrs.systemUiVisibility &= ~StatusBarManager.DISABLE_MASK; + attrs.subtreeSystemUiVisibility &= ~StatusBarManager.DISABLE_MASK; + } + } long origId = Binder.clearCallingIdentity(); synchronized(mWindowMap) { @@ -2346,7 +2830,10 @@ public class WindowManagerService extends IWindowManager.Stub displayed = !win.isVisibleLw(); if (win.mExiting) { win.mExiting = false; - win.mAnimation = null; + if (win.mAnimation != null) { + win.mAnimation.cancel(); + win.mAnimation = null; + } } if (win.mDestroying) { win.mDestroying = false; @@ -2399,7 +2886,7 @@ public class WindowManagerService extends IWindowManager.Stub outSurface.release(); } } catch (Exception e) { - mInputMonitor.updateInputWindowsLw(); + mInputMonitor.updateInputWindowsLw(true /*force*/); Slog.w(TAG, "Exception thrown when creating surface for client " + client + " (" + win.mAttrs.getTitle() + ")", @@ -2486,7 +2973,8 @@ public class WindowManagerService extends IWindowManager.Stub if (focusMayChange) { //System.out.println("Focus may change: " + win.mAttrs.getTitle()); - if (updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES)) { + if (updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, + false /*updateInputWindows*/)) { imMayMove = false; } //System.out.println("Relayout " + win + ": focus=" + mCurrentFocus); @@ -2517,7 +3005,7 @@ public class WindowManagerService extends IWindowManager.Stub if (assignLayers) { assignLayersLocked(); } - configChanged = updateOrientationFromAppTokensLocked(); + configChanged = updateOrientationFromAppTokensLocked(false); performLayoutAndPlaceSurfacesLocked(); if (displayed && win.mIsWallpaper) { updateWallpaperOffsetLocked(win, mDisplay.getWidth(), @@ -2542,7 +3030,7 @@ public class WindowManagerService extends IWindowManager.Stub inTouchMode = mInTouchMode; - mInputMonitor.updateInputWindowsLw(); + mInputMonitor.updateInputWindowsLw(true /*force*/); } if (configChanged) { @@ -2571,7 +3059,7 @@ public class WindowManagerService extends IWindowManager.Stub } private AttributeCache.Entry getCachedAnimations(WindowManager.LayoutParams lp) { - if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: params package=" + if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: layout params pkg=" + (lp != null ? lp.packageName : null) + " resId=0x" + (lp != null ? Integer.toHexString(lp.windowAnimations) : null)); if (lp != null && lp.windowAnimations != 0) { @@ -2592,7 +3080,7 @@ public class WindowManagerService extends IWindowManager.Stub } private AttributeCache.Entry getCachedAnimations(String packageName, int resId) { - if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: params package=" + if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: package=" + packageName + " resId=0x" + Integer.toHexString(resId)); if (packageName != null) { if ((resId&0xFF000000) == 0x01000000) { @@ -2874,7 +3362,6 @@ public class WindowManagerService extends IWindowManager.Stub } wtoken = new WindowToken(token, type, true); mTokenMap.put(token, wtoken); - mTokenList.add(wtoken); if (type == TYPE_WALLPAPER) { mWallpaperTokens.add(wtoken); } @@ -2890,7 +3377,6 @@ public class WindowManagerService extends IWindowManager.Stub final long origId = Binder.clearCallingIdentity(); synchronized(mWindowMap) { WindowToken wtoken = mTokenMap.remove(token); - mTokenList.remove(wtoken); if (wtoken != null) { boolean delayed = false; if (!wtoken.hidden) { @@ -2916,7 +3402,8 @@ public class WindowManagerService extends IWindowManager.Stub if (changed) { mLayoutNeeded = true; performLayoutAndPlaceSurfacesLocked(); - updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL); + updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, + false /*updateInputWindows*/); } if (delayed) { @@ -2926,7 +3413,7 @@ public class WindowManagerService extends IWindowManager.Stub } } - mInputMonitor.updateInputWindowsLw(); + mInputMonitor.updateInputWindowsLw(true /*force*/); } else { Slog.w(TAG, "Attempted to remove non-existing token: " + token); } @@ -2966,10 +3453,9 @@ public class WindowManagerService extends IWindowManager.Stub wtoken.groupId = groupId; wtoken.appFullscreen = fullscreen; wtoken.requestedOrientation = requestedOrientation; + if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG, "addAppToken: " + wtoken); mAppTokens.add(addPos, wtoken); - if (localLOGV) Slog.v(TAG, "Adding new app token: " + wtoken); mTokenMap.put(token.asBinder(), wtoken); - mTokenList.add(wtoken); // Application tokens start out hidden. wtoken.hidden = true; @@ -3086,7 +3572,7 @@ public class WindowManagerService extends IWindowManager.Stub long ident = Binder.clearCallingIdentity(); synchronized(mWindowMap) { - if (updateOrientationFromAppTokensLocked()) { + if (updateOrientationFromAppTokensLocked(false)) { if (freezeThisOneIfNeeded != null) { AppWindowToken wtoken = findAppWindowToken( freezeThisOneIfNeeded); @@ -3108,7 +3594,7 @@ public class WindowManagerService extends IWindowManager.Stub if (currentConfig.diff(mTempConfiguration) != 0) { mWaitingForConfig = true; mLayoutNeeded = true; - startFreezingDisplayLocked(); + startFreezingDisplayLocked(false); config = new Configuration(mTempConfiguration); } } @@ -3133,8 +3619,8 @@ public class WindowManagerService extends IWindowManager.Stub * @see android.view.IWindowManager#updateOrientationFromAppTokens( * android.os.IBinder) */ - boolean updateOrientationFromAppTokensLocked() { - if (mDisplayFrozen) { + boolean updateOrientationFromAppTokensLocked(boolean inTransaction) { + if (mDisplayFrozen || mOpeningApps.size() > 0 || mClosingApps.size() > 0) { // If the display is frozen, some activities may be in the middle // of restarting, and thus have removed their old window. If the // window has the flag to hide the lock screen, then the lock screen @@ -3154,7 +3640,8 @@ public class WindowManagerService extends IWindowManager.Stub //action like disabling/enabling sensors etc., mPolicy.setCurrentOrientationLw(req); if (setRotationUncheckedLocked(WindowManagerPolicy.USE_LAST_ROTATION, - mLastRotationFlags | Surface.FLAGS_ORIENTATION_ANIMATION_DISABLE)) { + mLastRotationFlags | Surface.FLAGS_ORIENTATION_ANIMATION_DISABLE, + inTransaction)) { changed = true; } } @@ -3245,13 +3732,13 @@ public class WindowManagerService extends IWindowManager.Stub if (moveFocusNow && changed) { final long origId = Binder.clearCallingIdentity(); - updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL); + updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/); Binder.restoreCallingIdentity(origId); } } } - public void prepareAppTransition(int transit) { + public void prepareAppTransition(int transit, boolean alwaysKeepCurrent) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "prepareAppTransition()")) { throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); @@ -3265,14 +3752,16 @@ public class WindowManagerService extends IWindowManager.Stub if (mNextAppTransition == WindowManagerPolicy.TRANSIT_UNSET || mNextAppTransition == WindowManagerPolicy.TRANSIT_NONE) { mNextAppTransition = transit; - } else if (transit == WindowManagerPolicy.TRANSIT_TASK_OPEN - && mNextAppTransition == WindowManagerPolicy.TRANSIT_TASK_CLOSE) { - // Opening a new task always supersedes a close for the anim. - mNextAppTransition = transit; - } else if (transit == WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN - && mNextAppTransition == WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE) { - // Opening a new activity always supersedes a close for the anim. - mNextAppTransition = transit; + } else if (!alwaysKeepCurrent) { + if (transit == WindowManagerPolicy.TRANSIT_TASK_OPEN + && mNextAppTransition == WindowManagerPolicy.TRANSIT_TASK_CLOSE) { + // Opening a new task always supersedes a close for the anim. + mNextAppTransition = transit; + } else if (transit == WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN + && mNextAppTransition == WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE) { + // Opening a new activity always supersedes a close for the anim. + mNextAppTransition = transit; + } } mAppTransitionReady = false; mAppTransitionTimeout = false; @@ -3322,7 +3811,7 @@ public class WindowManagerService extends IWindowManager.Stub public void setAppStartingWindow(IBinder token, String pkg, int theme, CharSequence nonLocalizedLabel, int labelRes, int icon, - IBinder transferFrom, boolean createIfNeeded) { + int windowFlags, IBinder transferFrom, boolean createIfNeeded) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "setAppStartingIcon()")) { throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); @@ -3378,10 +3867,12 @@ public class WindowManagerService extends IWindowManager.Stub startingWindow.mToken = wtoken; startingWindow.mRootToken = wtoken; startingWindow.mAppToken = wtoken; - if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, + if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG, "Removing starting window: " + startingWindow); mWindows.remove(startingWindow); mWindowsChanged = true; + if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Removing starting " + startingWindow + + " from " + ttoken); ttoken.windows.remove(startingWindow); ttoken.allAppWindows.remove(startingWindow); addWindowToListInOrderLocked(startingWindow, true); @@ -3416,7 +3907,8 @@ public class WindowManagerService extends IWindowManager.Stub ttoken.updateLayers(); } - updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES); + updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, + true /*updateInputWindows*/); mLayoutNeeded = true; performLayoutAndPlaceSurfacesLocked(); Binder.restoreCallingIdentity(origId); @@ -3470,7 +3962,7 @@ public class WindowManagerService extends IWindowManager.Stub mStartingIconInTransition = true; wtoken.startingData = new StartingData( pkg, theme, nonLocalizedLabel, - labelRes, icon); + labelRes, icon, windowFlags); Message m = mH.obtainMessage(H.ADD_STARTING, wtoken); // Note: we really want to do sendMessageAtFrontOfQueue() because we // want to process the message ASAP, before any other queued @@ -3576,12 +4068,13 @@ public class WindowManagerService extends IWindowManager.Stub if (changed) { mLayoutNeeded = true; + mInputMonitor.setUpdateInputWindowsNeededLw(); if (performLayout) { - updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES); + updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, + false /*updateInputWindows*/); performLayoutAndPlaceSurfacesLocked(); - } else { - mInputMonitor.updateInputWindowsLw(); } + mInputMonitor.updateInputWindowsLw(false /*force*/); } } @@ -3727,7 +4220,7 @@ public class WindowManagerService extends IWindowManager.Stub wtoken.freezingScreen = true; mAppsFreezingScreen++; if (mAppsFreezingScreen == 1) { - startFreezingDisplayLocked(); + startFreezingDisplayLocked(false); mH.removeMessages(H.APP_FREEZE_TIMEOUT); mH.sendMessageDelayed(mH.obtainMessage(H.APP_FREEZE_TIMEOUT), 5000); @@ -3796,7 +4289,6 @@ public class WindowManagerService extends IWindowManager.Stub final long origId = Binder.clearCallingIdentity(); synchronized(mWindowMap) { WindowToken basewtoken = mTokenMap.remove(token); - mTokenList.remove(basewtoken); if (basewtoken != null && (wtoken=basewtoken.appWindowToken) != null) { if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Removing app token: " + wtoken); delayed = setTokenVisibilityLocked(wtoken, null, false, WindowManagerPolicy.TRANSIT_UNSET, true); @@ -3816,6 +4308,8 @@ public class WindowManagerService extends IWindowManager.Stub + " animating=" + wtoken.animating); if (delayed) { // set the token aside because it has an active animation to be finished + if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, + "removeAppToken make exiting: " + wtoken); mExitingAppTokens.add(wtoken); } else { // Make sure there is no animation running on this token, @@ -3824,11 +4318,9 @@ public class WindowManagerService extends IWindowManager.Stub wtoken.animation = null; wtoken.animating = false; } + if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, + "removeAppToken: " + wtoken); mAppTokens.remove(wtoken); - if (mLastEnterAnimToken == wtoken) { - mLastEnterAnimToken = null; - mLastEnterAnimParams = null; - } wtoken.removed = true; if (wtoken.startingData != null) { startingToken = wtoken; @@ -3837,7 +4329,7 @@ public class WindowManagerService extends IWindowManager.Stub if (mFocusedApp == wtoken) { if (DEBUG_FOCUS) Slog.v(TAG, "Removing focused app token:" + wtoken); mFocusedApp = null; - updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL); + updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/); mInputMonitor.setFocusedAppLw(null); } } else { @@ -3954,18 +4446,21 @@ public class WindowManagerService extends IWindowManager.Stub if (!added && cwin.mSubLayer >= 0) { if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Re-adding child window at " + index + ": " + cwin); + win.mRebuilding = false; mWindows.add(index, win); index++; added = true; } if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Re-adding window at " + index + ": " + cwin); + cwin.mRebuilding = false; mWindows.add(index, cwin); index++; } if (!added) { if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Re-adding window at " + index + ": " + win); + win.mRebuilding = false; mWindows.add(index, win); index++; } @@ -3991,6 +4486,9 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_REORDER) Slog.v(TAG, "Initial app tokens:"); if (DEBUG_REORDER) dumpAppTokensLocked(); final AppWindowToken wtoken = findAppWindowToken(token); + if (DEBUG_TOKEN_MOVEMENT || DEBUG_REORDER) Slog.v(TAG, + "Start moving token " + wtoken + " initially at " + + mAppTokens.indexOf(wtoken)); if (wtoken == null || !mAppTokens.remove(wtoken)) { Slog.w(TAG, "Attempting to reorder token that doesn't exist: " + token + " (" + wtoken + ")"); @@ -3998,6 +4496,7 @@ public class WindowManagerService extends IWindowManager.Stub } mAppTokens.add(index, wtoken); if (DEBUG_REORDER) Slog.v(TAG, "Moved " + token + " to " + index + ":"); + else if (DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, "Moved " + token + " to " + index); if (DEBUG_REORDER) dumpAppTokensLocked(); final long origId = Binder.clearCallingIdentity(); @@ -4009,9 +4508,12 @@ public class WindowManagerService extends IWindowManager.Stub reAddAppWindowsLocked(findWindowOffsetLocked(index), wtoken); if (DEBUG_REORDER) Slog.v(TAG, "Final window list:"); if (DEBUG_REORDER) dumpWindowsLocked(); - updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES); + updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, + false /*updateInputWindows*/); mLayoutNeeded = true; + mInputMonitor.setUpdateInputWindowsNeededLw(); performLayoutAndPlaceSurfacesLocked(); + mInputMonitor.updateInputWindowsLw(false /*force*/); } Binder.restoreCallingIdentity(origId); } @@ -4025,6 +4527,8 @@ public class WindowManagerService extends IWindowManager.Stub for (int i=0; i<N; i++) { IBinder token = tokens.get(i); final AppWindowToken wtoken = findAppWindowToken(token); + if (DEBUG_REORDER || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, + "Temporarily removing " + wtoken + " from " + mAppTokens.indexOf(wtoken)); if (!mAppTokens.remove(wtoken)) { Slog.w(TAG, "Attempting to reorder token that doesn't exist: " + token + " (" + wtoken + ")"); @@ -4046,11 +4550,14 @@ public class WindowManagerService extends IWindowManager.Stub pos = reAddAppWindowsLocked(pos, wtoken); if (updateFocusAndLayout) { - if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES)) { + mInputMonitor.setUpdateInputWindowsNeededLw(); + if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, + false /*updateInputWindows*/)) { assignLayersLocked(); } mLayoutNeeded = true; performLayoutAndPlaceSurfacesLocked(); + mInputMonitor.updateInputWindowsLw(false /*force*/); } } @@ -4076,11 +4583,14 @@ public class WindowManagerService extends IWindowManager.Stub } } - if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES)) { + mInputMonitor.setUpdateInputWindowsNeededLw(); + if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, + false /*updateInputWindows*/)) { assignLayersLocked(); } mLayoutNeeded = true; performLayoutAndPlaceSurfacesLocked(); + mInputMonitor.updateInputWindowsLw(false /*force*/); //dump(); } @@ -4098,6 +4608,8 @@ public class WindowManagerService extends IWindowManager.Stub for (int i=0; i<N; i++) { AppWindowToken wt = findAppWindowToken(tokens.get(i)); if (wt != null) { + if (DEBUG_TOKEN_MOVEMENT || DEBUG_REORDER) Slog.v(TAG, + "Adding next to top: " + wt); mAppTokens.add(wt); if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { mToTopApps.remove(wt); @@ -4130,6 +4642,8 @@ public class WindowManagerService extends IWindowManager.Stub for (int i=0; i<N; i++) { AppWindowToken wt = findAppWindowToken(tokens.get(i)); if (wt != null) { + if (DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, + "Adding next to bottom: " + wt + " at " + pos); mAppTokens.add(pos, wt); if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { mToTopApps.remove(wt); @@ -4231,6 +4745,14 @@ public class WindowManagerService extends IWindowManager.Stub return mPolicy.inKeyguardRestrictedKeyInputMode(); } + public boolean isKeyguardLocked() { + return mPolicy.isKeyguardLocked(); + } + + public boolean isKeyguardSecure() { + return mPolicy.isKeyguardSecure(); + } + public void closeSystemDialogs(String reason) { synchronized(mWindowMap) { for (int i=mWindows.size()-1; i>=0; i--) { @@ -4435,8 +4957,7 @@ public class WindowManagerService extends IWindowManager.Stub final int N = mWindows.size(); for (int i=0; i<N; i++) { WindowState w = mWindows.get(i); - if (w.isVisibleLw() && !w.mObscured - && (w.mOrientationChanging || !w.isDrawnLw())) { + if (w.isVisibleLw() && !w.mObscured && !w.isDrawnLw()) { return; } } @@ -4477,6 +4998,191 @@ public class WindowManagerService extends IWindowManager.Stub } } + // TODO: more accounting of which pid(s) turned it on, keep count, + // only allow disables from pids which have count on, etc. + public void showStrictModeViolation(boolean on) { + int pid = Binder.getCallingPid(); + synchronized(mWindowMap) { + // Ignoring requests to enable the red border from clients + // which aren't on screen. (e.g. Broadcast Receivers in + // the background..) + if (on) { + boolean isVisible = false; + for (WindowState ws : mWindows) { + if (ws.mSession.mPid == pid && ws.isVisibleLw()) { + isVisible = true; + break; + } + } + if (!isVisible) { + return; + } + } + + if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION showStrictModeViolation"); + Surface.openTransaction(); + try { + if (mStrictModeFlash == null) { + mStrictModeFlash = new StrictModeFlash(mDisplay, mFxSession); + } + mStrictModeFlash.setVisibility(on); + } finally { + Surface.closeTransaction(); + if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION showStrictModeViolation"); + } + } + } + + public void setStrictModeVisualIndicatorPreference(String value) { + SystemProperties.set(StrictMode.VISUAL_PROPERTY, value); + } + + public Bitmap screenshotApplications(IBinder appToken, int maxWidth, int maxHeight) { + if (!checkCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER, + "screenshotApplications()")) { + throw new SecurityException("Requires READ_FRAME_BUFFER permission"); + } + + Bitmap rawss; + + int maxLayer = 0; + final Rect frame = new Rect(); + + float scale; + int sw, sh, dw, dh; + int rot; + + synchronized(mWindowMap) { + long ident = Binder.clearCallingIdentity(); + + dw = mDisplay.getWidth(); + dh = mDisplay.getHeight(); + + int aboveAppLayer = mPolicy.windowTypeToLayerLw( + WindowManager.LayoutParams.TYPE_APPLICATION) * TYPE_LAYER_MULTIPLIER + + TYPE_LAYER_OFFSET; + aboveAppLayer += TYPE_LAYER_MULTIPLIER; + + boolean isImeTarget = mInputMethodTarget != null + && mInputMethodTarget.mAppToken != null + && mInputMethodTarget.mAppToken.appToken != null + && mInputMethodTarget.mAppToken.appToken.asBinder() == appToken; + + // Figure out the part of the screen that is actually the app. + boolean including = false; + for (int i=mWindows.size()-1; i>=0; i--) { + WindowState ws = mWindows.get(i); + if (ws.mSurface == null) { + continue; + } + if (ws.mLayer >= aboveAppLayer) { + continue; + } + // When we will skip windows: when we are not including + // ones behind a window we didn't skip, and we are actually + // taking a screenshot of a specific app. + if (!including && appToken != null) { + // Also, we can possibly skip this window if it is not + // an IME target or the application for the screenshot + // is not the current IME target. + if (!ws.mIsImWindow || !isImeTarget) { + // And finally, this window is of no interest if it + // is not associated with the screenshot app. + if (ws.mAppToken == null || ws.mAppToken.token != appToken) { + continue; + } + } + } + + // We keep on including windows until we go past a full-screen + // window. + including = !ws.mIsImWindow && !ws.isFullscreen(dw, dh); + + if (maxLayer < ws.mAnimLayer) { + maxLayer = ws.mAnimLayer; + } + final Rect wf = ws.mFrame; + final Rect cr = ws.mContentInsets; + int left = wf.left + cr.left; + int top = wf.top + cr.top; + int right = wf.right - cr.right; + int bottom = wf.bottom - cr.bottom; + frame.union(left, top, right, bottom); + } + Binder.restoreCallingIdentity(ident); + + // Constrain frame to the screen size. + frame.intersect(0, 0, dw, dh); + + if (frame.isEmpty() || maxLayer == 0) { + return null; + } + + // The screenshot API does not apply the current screen rotation. + rot = mDisplay.getRotation(); + int fw = frame.width(); + int fh = frame.height(); + + // First try reducing to fit in x dimension. + scale = maxWidth/(float)fw; + sw = maxWidth; + sh = (int)(fh*scale); + if (sh > maxHeight) { + // y dimension became too long; constrain by that. + scale = maxHeight/(float)fh; + sw = (int)(fw*scale); + sh = maxHeight; + } + + // The screen shot will contain the entire screen. + dw = (int)(dw*scale); + dh = (int)(dh*scale); + if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) { + int tmp = dw; + dw = dh; + dh = tmp; + rot = (rot == Surface.ROTATION_90) ? Surface.ROTATION_270 : Surface.ROTATION_90; + } + rawss = Surface.screenshot(dw, dh, 0, maxLayer); + } + + if (rawss == null) { + Log.w(TAG, "Failure taking screenshot for (" + dw + "x" + dh + + ") to layer " + maxLayer); + return null; + } + + Bitmap bm = Bitmap.createBitmap(sw, sh, rawss.getConfig()); + Matrix matrix = new Matrix(); + ScreenRotationAnimation.createRotationMatrix(rot, dw, dh, matrix); + matrix.postTranslate(-(int)(frame.left*scale), -(int)(frame.top*scale)); + Canvas canvas = new Canvas(bm); + canvas.drawBitmap(rawss, matrix, null); + + rawss.recycle(); + return bm; + } + + public void freezeRotation() { + if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION, + "setRotation()")) { + throw new SecurityException("Requires SET_ORIENTATION permission"); + } + + mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_LOCKED, mRotation); + setRotationUnchecked(WindowManagerPolicy.USE_LAST_ROTATION, false, 0); + } + + public void thawRotation() { + if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION, + "setRotation()")) { + throw new SecurityException("Requires SET_ORIENTATION permission"); + } + + mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_FREE, 0); + setRotationUnchecked(WindowManagerPolicy.USE_LAST_ROTATION, false, 0); + } + public void setRotation(int rotation, boolean alwaysSendConfiguration, int animFlags) { if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION, @@ -4495,7 +5201,7 @@ public class WindowManagerService extends IWindowManager.Stub long origId = Binder.clearCallingIdentity(); boolean changed; synchronized(mWindowMap) { - changed = setRotationUncheckedLocked(rotation, animFlags); + changed = setRotationUncheckedLocked(rotation, animFlags, false); } if (changed || alwaysSendConfiguration) { @@ -4513,14 +5219,31 @@ public class WindowManagerService extends IWindowManager.Stub * Returns null if the rotation has been changed. In this case YOU * MUST CALL setNewConfiguration() TO UNFREEZE THE SCREEN. */ - public boolean setRotationUncheckedLocked(int rotation, int animFlags) { + public boolean setRotationUncheckedLocked(int rotation, int animFlags, boolean inTransaction) { + if (mDragState != null || mScreenRotationAnimation != null) { + // Potential rotation during a drag. Don't do the rotation now, but make + // a note to perform the rotation later. + if (DEBUG_ORIENTATION) Slog.v(TAG, "Deferring rotation."); + if (rotation != WindowManagerPolicy.USE_LAST_ROTATION) { + mDeferredRotation = rotation; + mDeferredRotationAnimFlags = animFlags; + } + return false; + } + boolean changed; if (rotation == WindowManagerPolicy.USE_LAST_ROTATION) { + if (mDeferredRotation != WindowManagerPolicy.USE_LAST_ROTATION) { + rotation = mDeferredRotation; + mRequestedRotation = rotation; + mLastRotationFlags = mDeferredRotationAnimFlags; + } rotation = mRequestedRotation; } else { mRequestedRotation = rotation; mLastRotationFlags = animFlags; } + mDeferredRotation = WindowManagerPolicy.USE_LAST_ROTATION; if (DEBUG_ORIENTATION) Slog.v(TAG, "Overwriting rotation value from " + rotation); rotation = mPolicy.rotationForOrientationLw(mForcedAppOrientation, mRotation, mDisplayEnabled); @@ -4540,11 +5263,33 @@ public class WindowManagerService extends IWindowManager.Stub 2000); mWaitingForConfig = true; mLayoutNeeded = true; - startFreezingDisplayLocked(); + startFreezingDisplayLocked(inTransaction); Slog.i(TAG, "Setting rotation to " + rotation + ", animFlags=" + animFlags); mInputManager.setDisplayOrientation(0, rotation); if (mDisplayEnabled) { - Surface.setOrientation(0, rotation, animFlags); + if (CUSTOM_SCREEN_ROTATION) { + Surface.freezeDisplay(0); + if (!inTransaction) { + if (SHOW_TRANSACTIONS) Slog.i(TAG, + ">>> OPEN TRANSACTION setRotationUnchecked"); + Surface.openTransaction(); + } + try { + if (mScreenRotationAnimation != null) { + mScreenRotationAnimation.setRotation(rotation); + } + } finally { + if (!inTransaction) { + Surface.closeTransaction(); + if (SHOW_TRANSACTIONS) Slog.i(TAG, + "<<< CLOSE TRANSACTION setRotationUnchecked"); + } + } + Surface.setOrientation(0, rotation, animFlags); + Surface.unfreezeDisplay(0); + } else { + Surface.setOrientation(0, rotation, animFlags); + } } for (int i=mWindows.size()-1; i>=0; i--) { WindowState w = mWindows.get(i); @@ -4958,7 +5703,13 @@ public class WindowManagerService extends IWindowManager.Stub public Configuration computeNewConfiguration() { synchronized (mWindowMap) { - return computeNewConfigurationLocked(); + Configuration config = computeNewConfigurationLocked(); + if (config == null && mWaitingForConfig) { + // Nothing changed but we are waiting for something... stop that! + mWaitingForConfig = false; + performLayoutAndPlaceSurfacesLocked(); + } + return config; } } @@ -5049,12 +5800,124 @@ public class WindowManagerService extends IWindowManager.Stub } config.screenLayout = mScreenLayout; + // Determine whether a hard keyboard is available and enabled. + boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS; + if (hardKeyboardAvailable != mHardKeyboardAvailable) { + mHardKeyboardAvailable = hardKeyboardAvailable; + mHardKeyboardEnabled = hardKeyboardAvailable; + + mH.removeMessages(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); + mH.sendEmptyMessage(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); + } + if (!mHardKeyboardEnabled) { + config.keyboard = Configuration.KEYBOARD_NOKEYS; + } + + // Update value of keyboardHidden, hardKeyboardHidden and navigationHidden + // based on whether a hard or soft keyboard is present, whether navigation keys + // are present and the lid switch state. config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO; config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO; + config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO; mPolicy.adjustConfigurationLw(config); return true; } - + + public boolean isHardKeyboardAvailable() { + synchronized (mWindowMap) { + return mHardKeyboardAvailable; + } + } + + public boolean isHardKeyboardEnabled() { + synchronized (mWindowMap) { + return mHardKeyboardEnabled; + } + } + + public void setHardKeyboardEnabled(boolean enabled) { + synchronized (mWindowMap) { + if (mHardKeyboardEnabled != enabled) { + mHardKeyboardEnabled = enabled; + mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION); + } + } + } + + public void setOnHardKeyboardStatusChangeListener( + OnHardKeyboardStatusChangeListener listener) { + synchronized (mWindowMap) { + mHardKeyboardStatusChangeListener = listener; + } + } + + void notifyHardKeyboardStatusChange() { + final boolean available, enabled; + final OnHardKeyboardStatusChangeListener listener; + synchronized (mWindowMap) { + listener = mHardKeyboardStatusChangeListener; + available = mHardKeyboardAvailable; + enabled = mHardKeyboardEnabled; + } + if (listener != null) { + listener.onHardKeyboardStatusChange(available, enabled); + } + } + + // ------------------------------------------------------------- + // Drag and drop + // ------------------------------------------------------------- + + IBinder prepareDragSurface(IWindow window, SurfaceSession session, + int flags, int width, int height, Surface outSurface) { + if (DEBUG_DRAG) { + Slog.d(TAG, "prepare drag surface: w=" + width + " h=" + height + + " flags=" + Integer.toHexString(flags) + " win=" + window + + " asbinder=" + window.asBinder()); + } + + final int callerPid = Binder.getCallingPid(); + final long origId = Binder.clearCallingIdentity(); + IBinder token = null; + + try { + synchronized (mWindowMap) { + try { + if (mDragState == null) { + Surface surface = new Surface(session, callerPid, "drag surface", 0, + width, height, PixelFormat.TRANSLUCENT, Surface.HIDDEN); + if (SHOW_TRANSACTIONS) Slog.i(TAG, " DRAG " + + surface + ": CREATE"); + outSurface.copyFrom(surface); + final IBinder winBinder = window.asBinder(); + token = new Binder(); + // TODO: preserve flags param in DragState + mDragState = new DragState(token, surface, 0, winBinder); + mDragState.mSurface = surface; + token = mDragState.mToken = new Binder(); + + // 5 second timeout for this window to actually begin the drag + mH.removeMessages(H.DRAG_START_TIMEOUT, winBinder); + Message msg = mH.obtainMessage(H.DRAG_START_TIMEOUT, winBinder); + mH.sendMessageDelayed(msg, 5000); + } else { + Slog.w(TAG, "Drag already in progress"); + } + } catch (Surface.OutOfResourcesException e) { + Slog.e(TAG, "Can't allocate drag surface w=" + width + " h=" + height, e); + if (mDragState != null) { + mDragState.reset(); + mDragState = null; + } + } + } + } finally { + Binder.restoreCallingIdentity(origId); + } + + return token; + } + // ------------------------------------------------------------- // Input Events and Focus Management // ------------------------------------------------------------- @@ -5074,23 +5937,31 @@ public class WindowManagerService extends IWindowManager.Stub // When true, input dispatch proceeds normally. Otherwise all events are dropped. private boolean mInputDispatchEnabled = true; + // When true, need to call updateInputWindowsLw(). + private boolean mUpdateInputWindowsNeeded = true; + // Temporary list of windows information to provide to the input dispatcher. private InputWindowList mTempInputWindows = new InputWindowList(); // Temporary input application object to provide to the input dispatcher. private InputApplication mTempInputApplication = new InputApplication(); + // Set to true when the first input device configuration change notification + // is received to indicate that the input devices are ready. + private final Object mInputDevicesReadyMonitor = new Object(); + private boolean mInputDevicesReady; + /* Notifies the window manager about a broken input channel. * * Called by the InputManager. */ - public void notifyInputChannelBroken(InputChannel inputChannel) { + public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle) { + if (inputWindowHandle == null) { + return; + } + synchronized (mWindowMap) { - WindowState windowState = getWindowStateForInputChannelLocked(inputChannel); - if (windowState == null) { - return; // irrelevant - } - + WindowState windowState = (WindowState) inputWindowHandle.windowState; Slog.i(TAG, "WINDOW DIED " + windowState); removeWindowLocked(windowState.mSession, windowState); } @@ -5101,11 +5972,12 @@ public class WindowManagerService extends IWindowManager.Stub * * Called by the InputManager. */ - public long notifyANR(Object token, InputChannel inputChannel) { + public long notifyANR(InputApplicationHandle inputApplicationHandle, + InputWindowHandle inputWindowHandle) { AppWindowToken appWindowToken = null; - if (inputChannel != null) { + if (inputWindowHandle != null) { synchronized (mWindowMap) { - WindowState windowState = getWindowStateForInputChannelLocked(inputChannel); + WindowState windowState = (WindowState) inputWindowHandle.windowState; if (windowState != null) { Slog.i(TAG, "Input event dispatching timed out sending to " + windowState.mAttrs.getTitle()); @@ -5114,8 +5986,8 @@ public class WindowManagerService extends IWindowManager.Stub } } - if (appWindowToken == null && token != null) { - appWindowToken = (AppWindowToken) token; + if (appWindowToken == null && inputApplicationHandle != null) { + appWindowToken = inputApplicationHandle.appWindowToken; Slog.i(TAG, "Input event dispatching timed out sending to application " + appWindowToken.stringName); } @@ -5135,33 +6007,60 @@ public class WindowManagerService extends IWindowManager.Stub } return 0; // abort dispatching } - - private WindowState getWindowStateForInputChannel(InputChannel inputChannel) { - synchronized (mWindowMap) { - return getWindowStateForInputChannelLocked(inputChannel); - } + + private void addDragInputWindowLw(InputWindowList windowList) { + final InputWindow inputWindow = windowList.add(); + inputWindow.inputChannel = mDragState.mServerChannel; + inputWindow.name = "drag"; + inputWindow.layoutParamsFlags = 0; + inputWindow.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG; + inputWindow.dispatchingTimeoutNanos = DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; + inputWindow.visible = true; + inputWindow.canReceiveKeys = false; + inputWindow.hasFocus = true; + inputWindow.hasWallpaper = false; + inputWindow.paused = false; + inputWindow.layer = mDragState.getDragLayerLw(); + inputWindow.ownerPid = Process.myPid(); + inputWindow.ownerUid = Process.myUid(); + + // The drag window covers the entire display + inputWindow.frameLeft = 0; + inputWindow.frameTop = 0; + inputWindow.frameRight = mDisplay.getWidth(); + inputWindow.frameBottom = mDisplay.getHeight(); + + // The drag window cannot receive new touches. + inputWindow.touchableRegion.setEmpty(); } - - private WindowState getWindowStateForInputChannelLocked(InputChannel inputChannel) { - int windowCount = mWindows.size(); - for (int i = 0; i < windowCount; i++) { - WindowState windowState = mWindows.get(i); - if (windowState.mInputChannel == inputChannel) { - return windowState; - } - } - - return null; + + public void setUpdateInputWindowsNeededLw() { + mUpdateInputWindowsNeeded = true; } - + /* Updates the cached window information provided to the input dispatcher. */ - public void updateInputWindowsLw() { + public void updateInputWindowsLw(boolean force) { + if (!force && !mUpdateInputWindowsNeeded) { + return; + } + mUpdateInputWindowsNeeded = false; + // Populate the input window list with information about all of the windows that // could potentially receive input. // As an optimization, we could try to prune the list of windows but this turns // out to be difficult because only the native code knows for sure which window // currently has touch focus. final ArrayList<WindowState> windows = mWindows; + + // If there's a drag in flight, provide a pseudowindow to catch drag input + final boolean inDrag = (mDragState != null); + if (inDrag) { + if (DEBUG_DRAG) { + Log.d(TAG, "Inserting drag window"); + } + addDragInputWindowLw(mTempInputWindows); + } + final int N = windows.size(); for (int i = N - 1; i >= 0; i--) { final WindowState child = windows.get(i); @@ -5177,9 +6076,16 @@ public class WindowManagerService extends IWindowManager.Stub final boolean isVisible = child.isVisibleLw(); final boolean hasWallpaper = (child == mWallpaperTarget) && (type != WindowManager.LayoutParams.TYPE_KEYGUARD); - + + // If there's a drag in progress and 'child' is a potential drop target, + // make sure it's been told about the drag + if (inDrag && isVisible) { + mDragState.sendDragStartedIfNeededLw(child); + } + // Add a window to our list of input windows. final InputWindow inputWindow = mTempInputWindows.add(); + inputWindow.inputWindowHandle = child.mInputWindowHandle; inputWindow.inputChannel = child.mInputChannel; inputWindow.name = child.toString(); inputWindow.layoutParamsFlags = flags; @@ -5199,40 +6105,8 @@ public class WindowManagerService extends IWindowManager.Stub inputWindow.frameTop = frame.top; inputWindow.frameRight = frame.right; inputWindow.frameBottom = frame.bottom; - - final Rect visibleFrame = child.mVisibleFrame; - inputWindow.visibleFrameLeft = visibleFrame.left; - inputWindow.visibleFrameTop = visibleFrame.top; - inputWindow.visibleFrameRight = visibleFrame.right; - inputWindow.visibleFrameBottom = visibleFrame.bottom; - - switch (child.mTouchableInsets) { - default: - case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME: - inputWindow.touchableAreaLeft = frame.left; - inputWindow.touchableAreaTop = frame.top; - inputWindow.touchableAreaRight = frame.right; - inputWindow.touchableAreaBottom = frame.bottom; - break; - - case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT: { - Rect inset = child.mGivenContentInsets; - inputWindow.touchableAreaLeft = frame.left + inset.left; - inputWindow.touchableAreaTop = frame.top + inset.top; - inputWindow.touchableAreaRight = frame.right - inset.right; - inputWindow.touchableAreaBottom = frame.bottom - inset.bottom; - break; - } - - case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE: { - Rect inset = child.mGivenVisibleInsets; - inputWindow.touchableAreaLeft = frame.left + inset.left; - inputWindow.touchableAreaTop = frame.top + inset.top; - inputWindow.touchableAreaRight = frame.right - inset.right; - inputWindow.touchableAreaBottom = frame.bottom - inset.bottom; - break; - } - } + + child.getTouchableRegion(inputWindow.touchableRegion); } // Send windows to native code. @@ -5242,7 +6116,32 @@ public class WindowManagerService extends IWindowManager.Stub // Also avoids keeping InputChannel objects referenced unnecessarily. mTempInputWindows.clear(); } - + + /* Notifies that the input device configuration has changed. */ + public void notifyConfigurationChanged() { + sendNewConfiguration(); + + synchronized (mInputDevicesReadyMonitor) { + if (!mInputDevicesReady) { + mInputDevicesReady = true; + mInputDevicesReadyMonitor.notifyAll(); + } + } + } + + /* Waits until the built-in input devices have been configured. */ + public boolean waitForInputDevicesReady(long timeoutMillis) { + synchronized (mInputDevicesReadyMonitor) { + if (!mInputDevicesReady) { + try { + mInputDevicesReadyMonitor.wait(timeoutMillis); + } catch (InterruptedException ex) { + } + } + return mInputDevicesReady; + } + } + /* Notifies that the lid switch changed state. */ public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) { mPolicy.notifyLidSwitchChanged(whenNanos, lidOpen); @@ -5250,26 +6149,31 @@ public class WindowManagerService extends IWindowManager.Stub /* Provides an opportunity for the window manager policy to intercept early key * processing as soon as the key has been read from the device. */ - public int interceptKeyBeforeQueueing(long whenNanos, int action, int flags, - int keyCode, int scanCode, int policyFlags, boolean isScreenOn) { - return mPolicy.interceptKeyBeforeQueueing(whenNanos, action, flags, - keyCode, scanCode, policyFlags, isScreenOn); + public int interceptKeyBeforeQueueing( + KeyEvent event, int policyFlags, boolean isScreenOn) { + return mPolicy.interceptKeyBeforeQueueing(event, policyFlags, isScreenOn); } /* Provides an opportunity for the window manager policy to process a key before * ordinary dispatch. */ - public boolean interceptKeyBeforeDispatching(InputChannel focus, - int action, int flags, int keyCode, int scanCode, int metaState, int repeatCount, - int policyFlags) { - WindowState windowState = getWindowStateForInputChannel(focus); - return mPolicy.interceptKeyBeforeDispatching(windowState, action, flags, - keyCode, scanCode, metaState, repeatCount, policyFlags); + public boolean interceptKeyBeforeDispatching( + InputWindowHandle focus, KeyEvent event, int policyFlags) { + WindowState windowState = focus != null ? (WindowState) focus.windowState : null; + return mPolicy.interceptKeyBeforeDispatching(windowState, event, policyFlags); + } + + /* Provides an opportunity for the window manager policy to process a key that + * the application did not handle. */ + public KeyEvent dispatchUnhandledKey( + InputWindowHandle focus, KeyEvent event, int policyFlags) { + WindowState windowState = focus != null ? (WindowState) focus.windowState : null; + return mPolicy.dispatchUnhandledKey(windowState, event, policyFlags); } /* Called when the current input focus changes. * Layer assignment is assumed to be complete by the time this is called. */ - public void setInputFocusLw(WindowState newWindow) { + public void setInputFocusLw(WindowState newWindow, boolean updateInputWindows) { if (DEBUG_INPUT) { Slog.d(TAG, "Input focus has changed to " + newWindow); } @@ -5281,9 +6185,13 @@ public class WindowManagerService extends IWindowManager.Stub // forgets to resume. newWindow.mToken.paused = false; } - + mInputFocus = newWindow; - updateInputWindowsLw(); + setUpdateInputWindowsNeededLw(); + + if (updateInputWindows) { + updateInputWindowsLw(false /*force*/); + } } } @@ -5292,12 +6200,14 @@ public class WindowManagerService extends IWindowManager.Stub if (newApp == null) { mInputManager.setFocusedApplication(null); } else { + mTempInputApplication.inputApplicationHandle = newApp.mInputApplicationHandle; mTempInputApplication.name = newApp.toString(); mTempInputApplication.dispatchingTimeoutNanos = newApp.inputDispatchingTimeoutNanos; - mTempInputApplication.token = newApp; - + mInputManager.setFocusedApplication(mTempInputApplication); + + mTempInputApplication.recycle(); } } @@ -5308,7 +6218,7 @@ public class WindowManagerService extends IWindowManager.Stub } window.paused = true; - updateInputWindowsLw(); + updateInputWindowsLw(true /*force*/); } } @@ -5319,7 +6229,7 @@ public class WindowManagerService extends IWindowManager.Stub } window.paused = false; - updateInputWindowsLw(); + updateInputWindowsLw(true /*force*/); } } @@ -5557,11 +6467,35 @@ public class WindowManagerService extends IWindowManager.Stub } public boolean detectSafeMode() { + if (!mInputMonitor.waitForInputDevicesReady( + INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS)) { + Slog.w(TAG, "Devices still not ready after waiting " + + INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS + + " milliseconds before attempting to detect safe mode."); + } + mSafeMode = mPolicy.detectSafeMode(); return mSafeMode; } public void systemReady() { + synchronized(mWindowMap) { + if (mDisplay != null) { + throw new IllegalStateException("Display already initialized"); + } + WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); + mDisplay = wm.getDefaultDisplay(); + mInitialDisplayWidth = mDisplay.getWidth(); + mInitialDisplayHeight = mDisplay.getHeight(); + mInputManager.setDisplaySize(0, Display.unmapDisplaySize(mInitialDisplayWidth), + Display.unmapDisplaySize(mInitialDisplayHeight)); + } + + try { + mActivityManager.updateConfiguration(null); + } catch (RemoteException e) { + } + mPolicy.systemReady(); } @@ -5686,9 +6620,9 @@ public class WindowManagerService extends IWindowManager.Stub } public void setInsets(IWindow window, int touchableInsets, - Rect contentInsets, Rect visibleInsets) { + Rect contentInsets, Rect visibleInsets, Region touchableArea) { setInsetsWindow(this, window, touchableInsets, contentInsets, - visibleInsets); + visibleInsets, touchableArea); } public void getDisplayFrame(IWindow window, Rect outDisplayFrame) { @@ -5727,6 +6661,132 @@ public class WindowManagerService extends IWindowManager.Stub } } + /* Drag/drop */ + public IBinder prepareDrag(IWindow window, int flags, + int width, int height, Surface outSurface) { + return prepareDragSurface(window, mSurfaceSession, flags, + width, height, outSurface); + } + + public boolean performDrag(IWindow window, IBinder dragToken, + float touchX, float touchY, float thumbCenterX, float thumbCenterY, + ClipData data) { + if (DEBUG_DRAG) { + Slog.d(TAG, "perform drag: win=" + window + " data=" + data); + } + + synchronized (mWindowMap) { + if (mDragState == null) { + Slog.w(TAG, "No drag prepared"); + throw new IllegalStateException("performDrag() without prepareDrag()"); + } + + if (dragToken != mDragState.mToken) { + Slog.w(TAG, "Performing mismatched drag"); + throw new IllegalStateException("performDrag() does not match prepareDrag()"); + } + + WindowState callingWin = windowForClientLocked(null, window, false); + if (callingWin == null) { + Slog.w(TAG, "Bad requesting window " + window); + return false; // !!! TODO: throw here? + } + + // !!! TODO: if input is not still focused on the initiating window, fail + // the drag initiation (e.g. an alarm window popped up just as the application + // called performDrag() + + mH.removeMessages(H.DRAG_START_TIMEOUT, window.asBinder()); + + // !!! TODO: extract the current touch (x, y) in screen coordinates. That + // will let us eliminate the (touchX,touchY) parameters from the API. + + // !!! FIXME: put all this heavy stuff onto the mH looper, as well as + // the actual drag event dispatch stuff in the dragstate + + mDragState.register(); + mInputMonitor.updateInputWindowsLw(true /*force*/); + if (!mInputManager.transferTouchFocus(callingWin.mInputChannel, + mDragState.mServerChannel)) { + Slog.e(TAG, "Unable to transfer touch focus"); + mDragState.unregister(); + mDragState = null; + mInputMonitor.updateInputWindowsLw(true /*force*/); + return false; + } + + mDragState.mData = data; + mDragState.mCurrentX = touchX; + mDragState.mCurrentY = touchY; + mDragState.broadcastDragStartedLw(touchX, touchY); + + // remember the thumb offsets for later + mDragState.mThumbOffsetX = thumbCenterX; + mDragState.mThumbOffsetY = thumbCenterY; + + // Make the surface visible at the proper location + final Surface surface = mDragState.mSurface; + if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION performDrag"); + Surface.openTransaction(); + try { + surface.setPosition((int)(touchX - thumbCenterX), + (int)(touchY - thumbCenterY)); + surface.setAlpha(.7071f); + surface.setLayer(mDragState.getDragLayerLw()); + surface.show(); + } finally { + Surface.closeTransaction(); + if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION performDrag"); + } + } + + return true; // success! + } + + public void reportDropResult(IWindow window, boolean consumed) { + IBinder token = window.asBinder(); + if (DEBUG_DRAG) { + Slog.d(TAG, "Drop result=" + consumed + " reported by " + token); + } + + synchronized (mWindowMap) { + long ident = Binder.clearCallingIdentity(); + try { + if (mDragState == null || mDragState.mToken != token) { + Slog.w(TAG, "Invalid drop-result claim by " + window); + throw new IllegalStateException("reportDropResult() by non-recipient"); + } + + // The right window has responded, even if it's no longer around, + // so be sure to halt the timeout even if the later WindowState + // lookup fails. + mH.removeMessages(H.DRAG_END_TIMEOUT, window.asBinder()); + WindowState callingWin = windowForClientLocked(null, window, false); + if (callingWin == null) { + Slog.w(TAG, "Bad result-reporting window " + window); + return; // !!! TODO: throw here? + } + + mDragState.mDragResult = consumed; + mDragState.endDragLw(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + public void dragRecipientEntered(IWindow window) { + if (DEBUG_DRAG) { + Slog.d(TAG, "Drag into new candidate view @ " + window.asBinder()); + } + } + + public void dragRecipientExited(IWindow window) { + if (DEBUG_DRAG) { + Slog.d(TAG, "Drag from old candidate view @ " + window.asBinder()); + } + } + public void setWallpaperPosition(IBinder window, float x, float y, float xStep, float yStep) { synchronized(mWindowMap) { long ident = Binder.clearCallingIdentity(); @@ -5901,6 +6961,11 @@ public class WindowManagerService extends IWindowManager.Stub final Rect mGivenVisibleInsets = new Rect(); /** + * This is the given touchable area relative to the window frame, or null if none. + */ + final Region mGivenTouchableRegion = new Region(); + + /** * Flag indicating whether the touchable region should be adjusted by * the visible insets; if false the area outside the visible insets is * NOT touchable, so we must use those to adjust the frame during hit @@ -5909,6 +6974,7 @@ public class WindowManagerService extends IWindowManager.Stub int mTouchableInsets = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME; // Current transformation being applied. + boolean mHaveMatrix; float mDsDx=1, mDtDx=0, mDsDy=0, mDtDy=1; float mLastDsDx=1, mLastDtDx=0, mLastDsDy=0, mLastDtDy=1; float mHScale=1, mVScale=1; @@ -5922,8 +6988,11 @@ public class WindowManagerService extends IWindowManager.Stub final Rect mContainingFrame = new Rect(); final Rect mDisplayFrame = new Rect(); final Rect mContentFrame = new Rect(); + final Rect mParentFrame = new Rect(); final Rect mVisibleFrame = new Rect(); + boolean mContentChanged; + float mShownAlpha = 1; float mAlpha = 1; float mLastAlpha = 1; @@ -5995,13 +7064,18 @@ public class WindowManagerService extends IWindowManager.Stub // Is this window now (or just being) removed? boolean mRemoved; + // Temp for keeping track of windows that have been removed when + // rebuilding window list. + boolean mRebuilding; + // For debugging, this is the last information given to the surface flinger. boolean mSurfaceShown; int mSurfaceX, mSurfaceY, mSurfaceW, mSurfaceH; int mSurfaceLayer; float mSurfaceAlpha; - // Input channel + // Input channel and input window handle used by the input dispatcher. + InputWindowHandle mInputWindowHandle; InputChannel mInputChannel; // Used to improve performance of toString() @@ -6046,6 +7120,7 @@ public class WindowManagerService extends IWindowManager.Stub + TYPE_LAYER_OFFSET; mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type); mAttachedWindow = attachedWindow; + if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + this + " to " + mAttachedWindow); mAttachedWindow.mChildWindows.add(this); mLayoutAttached = mAttrs.type != WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; @@ -6093,6 +7168,8 @@ public class WindowManagerService extends IWindowManager.Stub mLayer = 0; mAnimLayer = 0; mLastLayer = 0; + mInputWindowHandle = new InputWindowHandle( + mAppToken != null ? mAppToken.mInputApplicationHandle : null, this); } void attach() { @@ -6130,6 +7207,13 @@ public class WindowManagerService extends IWindowManager.Stub h = mAttrs.height== mAttrs.MATCH_PARENT ? ph : mRequestedHeight; } + if (!mParentFrame.equals(pf)) { + //Slog.i(TAG, "Window " + this + " content frame from " + mParentFrame + // + " to " + pf); + mParentFrame.set(pf); + mContentChanged = true; + } + final Rect content = mContentFrame; content.set(cf); @@ -6261,6 +7345,7 @@ public class WindowManagerService extends IWindowManager.Stub if (mAnimation != null) { mAnimating = true; mLocalAnimating = false; + mAnimation.cancel(); mAnimation = null; } } @@ -6277,9 +7362,6 @@ public class WindowManagerService extends IWindowManager.Stub } int flags = 0; - if (mAttrs.memoryType == MEMORY_TYPE_PUSH_BUFFERS) { - flags |= Surface.PUSH_BUFFERS; - } if ((mAttrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) { flags |= Surface.SECURE; @@ -6313,10 +7395,16 @@ public class WindowManagerService extends IWindowManager.Stub mSurfaceW = w; mSurfaceH = h; try { + final boolean isHwAccelerated = (mAttrs.flags & + WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0; + final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : mAttrs.format; + if (isHwAccelerated && mAttrs.format == PixelFormat.OPAQUE) { + flags |= Surface.OPAQUE; + } mSurface = new Surface( mSession.mSurfaceSession, mSession.mPid, mAttrs.getTitle().toString(), - 0, w, h, mAttrs.format, flags); + 0, w, h, format, flags); if (SHOW_TRANSACTIONS) Slog.i(TAG, " CREATE SURFACE " + mSurface + " IN SESSION " + mSession.mSurfaceSession @@ -6338,9 +7426,8 @@ public class WindowManagerService extends IWindowManager.Stub + ", set left=" + mFrame.left + " top=" + mFrame.top + ", animLayer=" + mAnimLayer); if (SHOW_TRANSACTIONS) { - Slog.i(TAG, ">>> OPEN TRANSACTION"); - if (SHOW_TRANSACTIONS) logSurface(this, - "CREATE pos=(" + mFrame.left + "," + mFrame.top + ") (" + + Slog.i(TAG, ">>> OPEN TRANSACTION createSurfaceLocked"); + logSurface(this, "CREATE pos=(" + mFrame.left + "," + mFrame.top + ") (" + mFrame.width() + "x" + mFrame.height() + "), layer=" + mAnimLayer + " HIDE", null); } @@ -6365,8 +7452,8 @@ public class WindowManagerService extends IWindowManager.Stub } mLastHidden = true; } finally { - if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION"); Surface.closeTransaction(); + if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION createSurfaceLocked"); } if (localLOGV) Slog.v( TAG, "Created surface " + this); @@ -6526,6 +7613,7 @@ public class WindowManagerService extends IWindowManager.Stub // starting window, so there is no need for it to also // be doing its own stuff. if (mAnimation != null) { + mAnimation.cancel(); mAnimation = null; // Make sure we clean up the animation. mAnimating = true; @@ -6571,7 +7659,11 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_ANIM) Slog.v( TAG, "Finished animation in " + this + " @ " + currentTime); - mAnimation = null; + + if (mAnimation != null) { + mAnimation.cancel(); + mAnimation = null; + } //WindowManagerService.this.dump(); } mHasLocalTransformation = false; @@ -6600,6 +7692,7 @@ public class WindowManagerService extends IWindowManager.Stub // clear it and make sure we run the cleanup code. mAnimating = true; mLocalAnimating = true; + mAnimation.cancel(); mAnimation = null; } @@ -6614,7 +7707,10 @@ public class WindowManagerService extends IWindowManager.Stub mAnimating = false; mLocalAnimating = false; - mAnimation = null; + if (mAnimation != null) { + mAnimation.cancel(); + mAnimation = null; + } mAnimLayer = mLayer; if (mIsImWindow) { mAnimLayer += mInputMethodAnimLayerAdjustment; @@ -6744,8 +7840,10 @@ public class WindowManagerService extends IWindowManager.Stub } } + final boolean screenAnimation = mScreenRotationAnimation != null + && mScreenRotationAnimation.isAnimating(); if (selfTransformation || attachedTransformation != null - || appTransformation != null) { + || appTransformation != null || screenAnimation) { // cache often used attributes locally final Rect frame = mFrame; final float tmpFloats[] = mTmpFloats; @@ -6756,13 +7854,17 @@ public class WindowManagerService extends IWindowManager.Stub if (selfTransformation) { tmpMatrix.postConcat(mTransformation.getMatrix()); } - tmpMatrix.postTranslate(frame.left, frame.top); + tmpMatrix.postTranslate(frame.left + mXOffset, frame.top + mYOffset); if (attachedTransformation != null) { tmpMatrix.postConcat(attachedTransformation.getMatrix()); } if (appTransformation != null) { tmpMatrix.postConcat(appTransformation.getMatrix()); } + if (screenAnimation) { + tmpMatrix.postConcat( + mScreenRotationAnimation.getEnterTransformation().getMatrix()); + } // "convert" it into SurfaceFlinger's format // (a 2x2 matrix + an offset) @@ -6770,13 +7872,14 @@ public class WindowManagerService extends IWindowManager.Stub // since it is already included in the transformation. //Slog.i(TAG, "Transform: " + matrix); + mHaveMatrix = true; tmpMatrix.getValues(tmpFloats); mDsDx = tmpFloats[Matrix.MSCALE_X]; - mDtDx = tmpFloats[Matrix.MSKEW_X]; - mDsDy = tmpFloats[Matrix.MSKEW_Y]; + mDtDx = tmpFloats[Matrix.MSKEW_Y]; + mDsDy = tmpFloats[Matrix.MSKEW_X]; mDtDy = tmpFloats[Matrix.MSCALE_Y]; - int x = (int)tmpFloats[Matrix.MTRANS_X] + mXOffset; - int y = (int)tmpFloats[Matrix.MTRANS_Y] + mYOffset; + int x = (int)tmpFloats[Matrix.MTRANS_X]; + int y = (int)tmpFloats[Matrix.MTRANS_Y]; int w = frame.width(); int h = frame.height(); mShownFrame.set(x, y, x+w, y+h); @@ -6801,6 +7904,10 @@ public class WindowManagerService extends IWindowManager.Stub if (appTransformation != null) { mShownAlpha *= appTransformation.getAlpha(); } + if (screenAnimation) { + mShownAlpha *= + mScreenRotationAnimation.getEnterTransformation().getAlpha(); + } } else { //Slog.i(TAG, "Not applying alpha transform"); } @@ -6817,6 +7924,7 @@ public class WindowManagerService extends IWindowManager.Stub mShownFrame.offset(mXOffset, mYOffset); } mShownAlpha = mAlpha; + mHaveMatrix = false; mDsDx = 1; mDtDx = 0; mDsDy = 0; @@ -6847,7 +7955,7 @@ public class WindowManagerService extends IWindowManager.Stub final AppWindowToken atoken = mAppToken; return mSurface != null && !mAttachedHidden && (atoken == null ? mPolicyVisibility : !atoken.hiddenRequested) - && (mOrientationChanging || (!mDrawPending && !mCommitDrawPending)) + && !mDrawPending && !mCommitDrawPending && !mExiting && !mDestroying; } @@ -6873,6 +7981,15 @@ public class WindowManagerService extends IWindowManager.Stub } /** + * Can this window possibly be a drag/drop target? The test here is + * a combination of the above "visible now" with the check that the + * Input Manager uses when discarding windows from input consideration. + */ + boolean isPotentialDragTarget() { + return isVisibleNow() && (mInputChannel != null) && !mRemoved; + } + + /** * Same as isVisible(), but we also count it as visible between the * call to IWindowSession.add() and the first relayout(). */ @@ -6951,14 +8068,12 @@ public class WindowManagerService extends IWindowManager.Stub /** * Returns true if the window has a surface that it has drawn a - * complete UI in to. Note that this returns true if the orientation - * is changing even if the window hasn't redrawn because we don't want - * to stop things from executing during that time. + * complete UI in to. */ public boolean isDrawnLw() { final AppWindowToken atoken = mAppToken; return mSurface != null && !mDestroying - && (mOrientationChanging || (!mDrawPending && !mCommitDrawPending)); + && !mDrawPending && !mCommitDrawPending; } /** @@ -6973,6 +8088,19 @@ public class WindowManagerService extends IWindowManager.Stub && !mDrawPending && !mCommitDrawPending; } + /** + * Return whether this window is wanting to have a translation + * animation applied to it for an in-progress move. (Only makes + * sense to call from performLayoutAndPlaceSurfacesLockedInner().) + */ + boolean shouldAnimateMove() { + return mContentChanged && !mExiting && !mLastHidden && !mDisplayFrozen + && (mFrame.top != mLastFrame.top + || mFrame.left != mLastFrame.left) + && (mAttachedWindow == null || !mAttachedWindow.shouldAnimateMove()) + && mPolicy.isScreenOn(); + } + boolean needsBackgroundFiller(int screenWidth, int screenHeight) { return // only if the application is requesting compatible window @@ -6983,9 +8111,7 @@ public class WindowManagerService extends IWindowManager.Stub mFrame.left <= mCompatibleScreenFrame.left && mFrame.top <= mCompatibleScreenFrame.top && mFrame.right >= mCompatibleScreenFrame.right && - mFrame.bottom >= mCompatibleScreenFrame.bottom && - // and starting window do not need background filler - mAttrs.type != mAttrs.TYPE_APPLICATION_STARTING; + mFrame.bottom >= mCompatibleScreenFrame.bottom; } boolean isFullscreen(int screenWidth, int screenHeight) { @@ -6997,6 +8123,7 @@ public class WindowManagerService extends IWindowManager.Stub disposeInputChannel(); if (mAttachedWindow != null) { + if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Removing " + this + " from " + mAttachedWindow); mAttachedWindow.mChildWindows.remove(this); } destroySurfaceLocked(); @@ -7119,6 +8246,36 @@ public class WindowManagerService extends IWindowManager.Stub return true; } + public void getTouchableRegion(Region outRegion) { + final Rect frame = mFrame; + switch (mTouchableInsets) { + default: + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME: + outRegion.set(frame); + break; + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT: { + final Rect inset = mGivenContentInsets; + outRegion.set( + frame.left + inset.left, frame.top + inset.top, + frame.right - inset.right, frame.bottom - inset.bottom); + break; + } + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE: { + final Rect inset = mGivenVisibleInsets; + outRegion.set( + frame.left + inset.left, frame.top + inset.top, + frame.right - inset.right, frame.bottom - inset.bottom); + break; + } + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION: { + final Region givenTouchableRegion = mGivenTouchableRegion; + outRegion.set(givenTouchableRegion); + outRegion.translate(frame.left, frame.top); + break; + } + } + } + void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("mSession="); pw.print(mSession); pw.print(" mClient="); pw.println(mClient.asBinder()); @@ -7199,6 +8356,8 @@ public class WindowManagerService extends IWindowManager.Stub pw.println(); pw.print(prefix); pw.print("mContainingFrame="); mContainingFrame.printShortString(pw); + pw.print(" mParentFrame="); + mParentFrame.printShortString(pw); pw.print(" mDisplayFrame="); mDisplayFrame.printShortString(pw); pw.println(); @@ -7210,11 +8369,6 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mVisibleInsets="); mVisibleInsets.printShortString(pw); pw.print(" last="); mLastVisibleInsets.printShortString(pw); pw.println(); - if (mShownAlpha != 1 || mAlpha != 1 || mLastAlpha != 1) { - pw.print(prefix); pw.print("mShownAlpha="); pw.print(mShownAlpha); - pw.print(" mAlpha="); pw.print(mAlpha); - pw.print(" mLastAlpha="); pw.println(mLastAlpha); - } if (mAnimating || mLocalAnimating || mAnimationIsEntrance || mAnimation != null) { pw.print(prefix); pw.print("mAnimating="); pw.print(mAnimating); @@ -7229,6 +8383,17 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" "); mTransformation.printShortString(pw); pw.println(); } + if (mShownAlpha != 1 || mAlpha != 1 || mLastAlpha != 1) { + pw.print(prefix); pw.print("mShownAlpha="); pw.print(mShownAlpha); + pw.print(" mAlpha="); pw.print(mAlpha); + pw.print(" mLastAlpha="); pw.println(mLastAlpha); + } + if (mHaveMatrix) { + pw.print(prefix); pw.print("mDsDx="); pw.print(mDsDx); + pw.print(" mDtDx="); pw.print(mDtDx); + pw.print(" mDsDy="); pw.print(mDsDy); + pw.print(" mDtDy="); pw.println(mDtDy); + } pw.print(prefix); pw.print("mDrawPending="); pw.print(mDrawPending); pw.print(" mCommitDrawPending="); pw.print(mCommitDrawPending); pw.print(" mReadyToShow="); pw.print(mReadyToShow); @@ -7422,11 +8587,15 @@ public class WindowManagerService extends IWindowManager.Stub boolean startingMoved; boolean firstWindowDrawn; + // Input application handle used by the input dispatcher. + InputApplicationHandle mInputApplicationHandle; + AppWindowToken(IApplicationToken _token) { super(_token.asBinder(), WindowManager.LayoutParams.TYPE_APPLICATION, true); appWindowToken = this; appToken = _token; + mInputApplicationHandle = new InputApplicationHandle(this); } public void setAnimation(Animation anim) { @@ -7473,7 +8642,7 @@ public class WindowManagerService extends IWindowManager.Stub w.mAnimLayer = w.mLayer + adj; if (DEBUG_LAYERS) Slog.v(TAG, "Updating layer " + w + ": " + w.mAnimLayer); - if (w == mInputMethodTarget) { + if (w == mInputMethodTarget && !mInputMethodTargetWaitingAnim) { setInputMethodAnimLayerAdjustment(adj); } if (w == mWallpaperTarget && mLowerWallpaperTarget == null) { @@ -7564,6 +8733,10 @@ public class WindowManagerService extends IWindowManager.Stub clearAnimation(); animating = false; + if (animLayerAdjustment != 0) { + animLayerAdjustment = 0; + updateLayers(); + } if (mInputMethodTarget != null && mInputMethodTarget.mAppToken == this) { moveInputMethodWindowsIfNeededLocked(true); } @@ -7573,10 +8746,6 @@ public class WindowManagerService extends IWindowManager.Stub + ": reportedVisible=" + reportedVisible); transformation.clear(); - if (animLayerAdjustment != 0) { - animLayerAdjustment = 0; - updateLayers(); - } final int N = windows.size(); for (int i=0; i<N; i++) { @@ -7694,14 +8863,14 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(prefix); pw.print("animating="); pw.print(animating); pw.print(" animation="); pw.println(animation); } - if (animLayerAdjustment != 0) { - pw.print(prefix); pw.print("animLayerAdjustment="); pw.println(animLayerAdjustment); - } if (hasTransformation) { - pw.print(prefix); pw.print("hasTransformation="); pw.print(hasTransformation); - pw.print(" transformation="); transformation.printShortString(pw); + pw.print(prefix); pw.print("XForm: "); + transformation.printShortString(pw); pw.println(); } + if (animLayerAdjustment != 0) { + pw.print(prefix); pw.print("animLayerAdjustment="); pw.println(animLayerAdjustment); + } if (startingData != null || removed || firstWindowDrawn) { pw.print(prefix); pw.print("startingData="); pw.print(startingData); pw.print(" removed="); pw.print(removed); @@ -7753,14 +8922,16 @@ public class WindowManagerService extends IWindowManager.Stub final CharSequence nonLocalizedLabel; final int labelRes; final int icon; + final int windowFlags; StartingData(String _pkg, int _theme, CharSequence _nonLocalizedLabel, - int _labelRes, int _icon) { + int _labelRes, int _icon, int _windowFlags) { pkg = _pkg; theme = _theme; nonLocalizedLabel = _nonLocalizedLabel; labelRes = _labelRes; icon = _icon; + windowFlags = _windowFlags; } } @@ -7781,6 +8952,9 @@ public class WindowManagerService extends IWindowManager.Stub public static final int APP_FREEZE_TIMEOUT = 17; public static final int SEND_NEW_CONFIGURATION = 18; public static final int REPORT_WINDOWS_CHANGE = 19; + public static final int DRAG_START_TIMEOUT = 20; + public static final int DRAG_END_TIMEOUT = 21; + public static final int REPORT_HARD_KEYBOARD_STATUS_CHANGE = 22; private Session mLastReportedHold; @@ -7833,6 +9007,8 @@ public class WindowManagerService extends IWindowManager.Stub // Ignore if process has died. } } + + mPolicy.focusChanged(lastFocus, newFocus); } } break; @@ -7879,7 +9055,7 @@ public class WindowManagerService extends IWindowManager.Stub view = mPolicy.addStartingWindow( wtoken.token, sd.pkg, sd.theme, sd.nonLocalizedLabel, sd.labelRes, - sd.icon); + sd.icon, sd.windowFlags); } catch (Exception e) { Slog.w(TAG, "Exception when adding starting window", e); } @@ -8123,6 +9299,40 @@ public class WindowManagerService extends IWindowManager.Stub break; } + case DRAG_START_TIMEOUT: { + IBinder win = (IBinder)msg.obj; + if (DEBUG_DRAG) { + Slog.w(TAG, "Timeout starting drag by win " + win); + } + synchronized (mWindowMap) { + // !!! TODO: ANR the app that has failed to start the drag in time + if (mDragState != null) { + mDragState.unregister(); + mInputMonitor.updateInputWindowsLw(true /*force*/); + mDragState.reset(); + mDragState = null; + } + } + break; + } + + case DRAG_END_TIMEOUT: { + IBinder win = (IBinder)msg.obj; + if (DEBUG_DRAG) { + Slog.w(TAG, "Timeout ending drag to win " + win); + } + synchronized (mWindowMap) { + // !!! TODO: ANR the drag-receiving app + mDragState.mDragResult = false; + mDragState.endDragLw(); + } + break; + } + + case REPORT_HARD_KEYBOARD_STATUS_CHANGE: { + notifyHardKeyboardStatusChange(); + break; + } } } } @@ -8147,11 +9357,47 @@ public class WindowManagerService extends IWindowManager.Stub WindowState imFocus; if (idx > 0) { imFocus = mWindows.get(idx-1); + //Log.i(TAG, "Desired input method target: " + imFocus); + //Log.i(TAG, "Current focus: " + this.mCurrentFocus); + //Log.i(TAG, "Last focus: " + this.mLastFocus); if (imFocus != null) { + // This may be a starting window, in which case we still want + // to count it as okay. + if (imFocus.mAttrs.type == LayoutParams.TYPE_APPLICATION_STARTING + && imFocus.mAppToken != null) { + // The client has definitely started, so it really should + // have a window in this app token. Let's look for it. + for (int i=0; i<imFocus.mAppToken.windows.size(); i++) { + WindowState w = imFocus.mAppToken.windows.get(i); + if (w != imFocus) { + //Log.i(TAG, "Switching to real app window: " + w); + imFocus = w; + break; + } + } + } + //Log.i(TAG, "IM target client: " + imFocus.mSession.mClient); + //if (imFocus.mSession.mClient != null) { + // Log.i(TAG, "IM target client binder: " + imFocus.mSession.mClient.asBinder()); + // Log.i(TAG, "Requesting client binder: " + client.asBinder()); + //} if (imFocus.mSession.mClient != null && imFocus.mSession.mClient.asBinder() == client.asBinder()) { return true; } + + // Okay, how about this... what is the current focus? + // It seems in some cases we may not have moved the IM + // target window, such as when it was in a pop-up window, + // so let's also look at the current focus. (An example: + // go to Gmail, start searching so the keyboard goes up, + // press home. Sometimes the IME won't go down.) + // Would be nice to fix this more correctly, but it's + // way at the end of a release, and this should be good enough. + if (mCurrentFocus != null && mCurrentFocus.mSession.mClient != null && + mCurrentFocus.mSession.mClient.asBinder() == client.asBinder()) { + return true; + } } } } @@ -8201,12 +9447,18 @@ public class WindowManagerService extends IWindowManager.Stub int lastWallpaper = -1; int numRemoved = 0; + if (mRebuildTmp.length < NW) { + mRebuildTmp = new WindowState[NW+10]; + } + // First remove all existing app windows. i=0; while (i < NW) { WindowState w = mWindows.get(i); if (w.mAppToken != null) { WindowState win = mWindows.remove(i); + win.mRebuilding = true; + mRebuildTmp[numRemoved] = win; mWindowsChanged = true; if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Rebuild removing window: " + win); @@ -8244,6 +9496,21 @@ public class WindowManagerService extends IWindowManager.Stub if (i != numRemoved) { Slog.w(TAG, "Rebuild removed " + numRemoved + " windows but added " + i); + for (i=0; i<numRemoved; i++) { + WindowState ws = mRebuildTmp[i]; + if (ws.mRebuilding) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + ws.dump(pw, ""); + pw.flush(); + Slog.w(TAG, "This window was lost: " + ws); + Slog.w(TAG, sw.toString()); + } + } + Slog.w(TAG, "Current app token list:"); + dumpAppTokensLocked(); + Slog.w(TAG, "Final window list:"); + dumpWindowsLocked(); } } @@ -8253,6 +9520,12 @@ public class WindowManagerService extends IWindowManager.Stub int curLayer = 0; int i; + if (DEBUG_LAYERS) { + RuntimeException here = new RuntimeException("here"); + here.fillInStackTrace(); + Log.v(TAG, "Assigning layers", here); + } + for (i=0; i<N; i++) { WindowState w = mWindows.get(i); if (w.mBaseLayer == curBaseLayer || w.mIsImWindow @@ -8304,38 +9577,46 @@ public class WindowManagerService extends IWindowManager.Stub return; } + mInLayout = true; boolean recoveringMemory = false; - if (mForceRemoves != null) { - recoveringMemory = true; - // Wait a little it for things to settle down, and off we go. - for (int i=0; i<mForceRemoves.size(); i++) { - WindowState ws = mForceRemoves.get(i); - Slog.i(TAG, "Force removing: " + ws); - removeWindowInnerLocked(ws.mSession, ws); - } - mForceRemoves = null; - Slog.w(TAG, "Due to memory failure, waiting a bit for next layout"); - Object tmp = new Object(); - synchronized (tmp) { - try { - tmp.wait(250); - } catch (InterruptedException e) { + + try { + if (mForceRemoves != null) { + recoveringMemory = true; + // Wait a little bit for things to settle down, and off we go. + for (int i=0; i<mForceRemoves.size(); i++) { + WindowState ws = mForceRemoves.get(i); + Slog.i(TAG, "Force removing: " + ws); + removeWindowInnerLocked(ws.mSession, ws); + } + mForceRemoves = null; + Slog.w(TAG, "Due to memory failure, waiting a bit for next layout"); + Object tmp = new Object(); + synchronized (tmp) { + try { + tmp.wait(250); + } catch (InterruptedException e) { + } } } + } catch (RuntimeException e) { + Slog.e(TAG, "Unhandled exception while force removing for memory", e); } - - mInLayout = true; + try { performLayoutAndPlaceSurfacesLockedInner(recoveringMemory); - int i = mPendingRemove.size()-1; - if (i >= 0) { - while (i >= 0) { - WindowState w = mPendingRemove.get(i); - removeWindowInnerLocked(w.mSession, w); - i--; + int N = mPendingRemove.size(); + if (N > 0) { + if (mPendingRemoveTmp.length < N) { + mPendingRemoveTmp = new WindowState[N+10]; } + mPendingRemove.toArray(mPendingRemoveTmp); mPendingRemove.clear(); + for (int i=0; i<N; i++) { + WindowState w = mPendingRemoveTmp[i]; + removeWindowInnerLocked(w.mSession, w); + } mInLayout = false; assignLayersLocked(); @@ -8358,7 +9639,7 @@ public class WindowManagerService extends IWindowManager.Stub } } - private final int performLayoutLockedInner() { + private final int performLayoutLockedInner(boolean initial, boolean updateInputWindows) { if (!mLayoutNeeded) { return 0; } @@ -8392,16 +9673,16 @@ public class WindowManagerService extends IWindowManager.Stub final AppWindowToken atoken = win.mAppToken; final boolean gone = win.mViewVisibility == View.GONE || !win.mRelayoutCalled - || win.mRootToken.hidden + || (atoken == null && win.mRootToken.hidden) || (atoken != null && atoken.hiddenRequested) || win.mAttachedHidden || win.mExiting || win.mDestroying; - if (!win.mLayoutAttached) { - if (DEBUG_LAYOUT) Slog.v(TAG, "First pass " + win + if (DEBUG_LAYOUT && !win.mLayoutAttached) { + Slog.v(TAG, "First pass " + win + ": gone=" + gone + " mHaveFrame=" + win.mHaveFrame + " mLayoutAttached=" + win.mLayoutAttached); - if (DEBUG_LAYOUT && gone) Slog.v(TAG, " (mViewVisibility=" + if (gone) Slog.v(TAG, " (mViewVisibility=" + win.mViewVisibility + " mRelayoutCalled=" + win.mRelayoutCalled + " hidden=" + win.mRootToken.hidden + " hiddenRequested=" @@ -8416,6 +9697,10 @@ public class WindowManagerService extends IWindowManager.Stub // just don't display"). if (!gone || !win.mHaveFrame) { if (!win.mLayoutAttached) { + if (initial) { + //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial"); + win.mContentChanged = false; + } mPolicy.layoutWindowLw(win, win.mAttrs, null); win.mLayoutSeq = seq; if (DEBUG_LAYOUT) Slog.v(TAG, "-> mFrame=" @@ -8435,18 +9720,22 @@ public class WindowManagerService extends IWindowManager.Stub for (i = topAttached; i >= 0; i--) { WindowState win = mWindows.get(i); - // If this view is GONE, then skip it -- keep the current - // frame, and let the caller know so they can ignore it - // if they want. (We do the normal layout for INVISIBLE - // windows, since that means "perform layout as normal, - // just don't display"). if (win.mLayoutAttached) { if (DEBUG_LAYOUT) Slog.v(TAG, "Second pass " + win + " mHaveFrame=" + win.mHaveFrame + " mViewVisibility=" + win.mViewVisibility + " mRelayoutCalled=" + win.mRelayoutCalled); + // If this view is GONE, then skip it -- keep the current + // frame, and let the caller know so they can ignore it + // if they want. (We do the normal layout for INVISIBLE + // windows, since that means "perform layout as normal, + // just don't display"). if ((win.mViewVisibility != View.GONE && win.mRelayoutCalled) || !win.mHaveFrame) { + if (initial) { + //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial"); + win.mContentChanged = false; + } mPolicy.layoutWindowLw(win, win.mAttrs, win.mAttachedWindow); win.mLayoutSeq = seq; if (DEBUG_LAYOUT) Slog.v(TAG, "-> mFrame=" @@ -8458,13 +9747,22 @@ public class WindowManagerService extends IWindowManager.Stub } // Window frames may have changed. Tell the input dispatcher about it. - mInputMonitor.updateInputWindowsLw(); + mInputMonitor.setUpdateInputWindowsNeededLw(); + if (updateInputWindows) { + mInputMonitor.updateInputWindowsLw(false /*force*/); + } return mPolicy.finishLayoutLw(); } + // "Something has changed! Let's make it correct now." private final void performLayoutAndPlaceSurfacesLockedInner( boolean recoveringMemory) { + if (mDisplay == null) { + Slog.i(TAG, "skipping performLayoutAndPlaceSurfacesLockedInner with no mDisplay"); + return; + } + final long currentTime = SystemClock.uptimeMillis(); final int dw = mDisplay.getWidth(); final int dh = mDisplay.getHeight(); @@ -8473,7 +9771,8 @@ public class WindowManagerService extends IWindowManager.Stub if (mFocusMayChange) { mFocusMayChange = false; - updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES); + updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, + false /*updateInputWindows*/); } // Initialize state of exiting tokens. @@ -8493,13 +9792,15 @@ public class WindowManagerService extends IWindowManager.Stub boolean focusDisplayed = false; boolean animating = false; boolean createWatermark = false; + boolean updateRotation = false; + boolean screenRotationFinished = false; if (mFxSession == null) { mFxSession = new SurfaceSession(); createWatermark = true; } - if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION"); + if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces"); Surface.openTransaction(); @@ -8509,6 +9810,9 @@ public class WindowManagerService extends IWindowManager.Stub if (mWatermark != null) { mWatermark.positionSurface(dw, dh); } + if (mStrictModeFlash != null) { + mStrictModeFlash.positionSurface(dw, dh); + } try { boolean wallpaperForceHidingChanged = false; @@ -8534,7 +9838,7 @@ public class WindowManagerService extends IWindowManager.Stub } if ((changes&WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG) != 0) { if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout"); - if (updateOrientationFromAppTokensLocked()) { + if (updateOrientationFromAppTokensLocked(true)) { mLayoutNeeded = true; mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION); } @@ -8546,7 +9850,7 @@ public class WindowManagerService extends IWindowManager.Stub // FIRST LOOP: Perform a layout, if needed. if (repeats < 4) { - changes = performLayoutLockedInner(); + changes = performLayoutLockedInner(repeats == 0, false /*updateInputWindows*/); if (changes != 0) { continue; } @@ -8581,9 +9885,21 @@ public class WindowManagerService extends IWindowManager.Stub animating = tokensAnimating; + if (mScreenRotationAnimation != null) { + if (mScreenRotationAnimation.isAnimating()) { + if (mScreenRotationAnimation.stepAnimation(currentTime)) { + animating = true; + } else { + screenRotationFinished = true; + updateRotation = true; + } + } + } + boolean tokenMayBeDrawn = false; boolean wallpaperMayChange = false; boolean forceHiding = false; + WindowState windowDetachedWallpaper = null; mPolicy.beginAnimationLw(dw, dh); @@ -8595,7 +9911,7 @@ public class WindowManagerService extends IWindowManager.Stub final WindowManager.LayoutParams attrs = w.mAttrs; if (w.mSurface != null) { - // Execute animation. + // Take care of the window being ready to display. if (w.commitFinishDrawingLocked(currentTime)) { if ((w.mAttrs.flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) { @@ -8605,19 +9921,56 @@ public class WindowManagerService extends IWindowManager.Stub } } - boolean wasAnimating = w.mAnimating; - if (w.stepAnimationLocked(currentTime, dw, dh)) { + final boolean wasAnimating = w.mAnimating; + + int animDw = dw; + int animDh = dh; + + // If the window has moved due to its containing + // content frame changing, then we'd like to animate + // it. The checks here are ordered by what is least + // likely to be true first. + if (w.shouldAnimateMove()) { + // Frame has moved, containing content frame + // has also moved, and we're not currently animating... + // let's do something. + Animation a = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.window_move_from_decor); + w.setAnimation(a); + animDw = w.mLastFrame.left - w.mFrame.left; + animDh = w.mLastFrame.top - w.mFrame.top; + } + + // Execute animation. + final boolean nowAnimating = w.stepAnimationLocked(currentTime, + animDw, animDh); + + // If this window is animating, make a note that we have + // an animating window and take care of a request to run + // a detached wallpaper animation. + if (nowAnimating) { + if (w.mAnimation != null && w.mAnimation.getDetachWallpaper()) { + windowDetachedWallpaper = w; + } animating = true; - //w.dump(" "); } + + // If this window's app token is running a detached wallpaper + // animation, make a note so we can ensure the wallpaper is + // displayed behind it. + if (w.mAppToken != null && w.mAppToken.animation != null + && w.mAppToken.animation.getDetachWallpaper()) { + windowDetachedWallpaper = w; + } + if (wasAnimating && !w.mAnimating && mWallpaperTarget == w) { wallpaperMayChange = true; } if (mPolicy.doesForceHide(w, attrs)) { - if (!wasAnimating && animating) { + if (!wasAnimating && nowAnimating) { if (DEBUG_VISIBILITY) Slog.v(TAG, - "Animation done that could impact force hide: " + "Animation started that could impact force hide: " + w); wallpaperForceHidingChanged = true; mFocusMayChange = true; @@ -8714,12 +10067,9 @@ public class WindowManagerService extends IWindowManager.Stub if (tokenMayBeDrawn) { // See if any windows have been drawn, so they (and others // associated with them) can now be shown. - final int NT = mTokenList.size(); + final int NT = mAppTokens.size(); for (i=0; i<NT; i++) { - AppWindowToken wtoken = mTokenList.get(i).appWindowToken; - if (wtoken == null) { - continue; - } + AppWindowToken wtoken = mAppTokens.get(i); if (wtoken.freezingScreen) { int numInteresting = wtoken.numInterestingWindows; if (numInteresting > 0 && wtoken.numDrawnWindows >= numInteresting) { @@ -8816,8 +10166,8 @@ public class WindowManagerService extends IWindowManager.Stub // The top-most window will supply the layout params, // and we will determine it below. LayoutParams animLp = null; - AppWindowToken animToken = null; int bestAnimLayer = -1; + boolean fullscreenAnim = false; if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New wallpaper target=" + mWallpaperTarget @@ -8859,11 +10209,18 @@ public class WindowManagerService extends IWindowManager.Stub // window, we will always use its anim. if ((ws.mAttrs.flags&FLAG_COMPATIBLE_WINDOW) != 0) { animLp = ws.mAttrs; - animToken = ws.mAppToken; bestAnimLayer = Integer.MAX_VALUE; - } else if (ws.mLayer > bestAnimLayer) { + } else if (!fullscreenAnim || ws.mLayer > bestAnimLayer) { + animLp = ws.mAttrs; + bestAnimLayer = ws.mLayer; + } + fullscreenAnim = true; + } + } else if (!fullscreenAnim) { + WindowState ws = wtoken.findMainWindow(); + if (ws != null) { + if (ws.mLayer > bestAnimLayer) { animLp = ws.mAttrs; - animToken = ws.mAppToken; bestAnimLayer = ws.mLayer; } } @@ -8901,15 +10258,6 @@ public class WindowManagerService extends IWindowManager.Stub "New transit into wallpaper: " + transit); } - if ((transit&WindowManagerPolicy.TRANSIT_ENTER_MASK) != 0) { - mLastEnterAnimToken = animToken; - mLastEnterAnimParams = animLp; - } else if (mLastEnterAnimParams != null) { - animLp = mLastEnterAnimParams; - mLastEnterAnimToken = null; - mLastEnterAnimParams = null; - } - // If all closing windows are obscured, then there is // no need to do an animation. This is the case, for // example, when this transition is being done behind @@ -8954,12 +10302,14 @@ public class WindowManagerService extends IWindowManager.Stub // This has changed the visibility of windows, so perform // a new layout to get them all up-to-date. - changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; + changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT + | WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG; mLayoutNeeded = true; if (!moveInputMethodWindowsIfNeededLocked(true)) { assignLayersLocked(); } - updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES); + updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, + false /*updateInputWindows*/); mFocusMayChange = false; } } @@ -9041,6 +10391,14 @@ public class WindowManagerService extends IWindowManager.Stub } } + if (mWindowDetachedWallpaper != windowDetachedWallpaper) { + if (DEBUG_WALLPAPER) Slog.v(TAG, + "Detached wallpaper changed from " + mWindowDetachedWallpaper + + windowDetachedWallpaper); + mWindowDetachedWallpaper = windowDetachedWallpaper; + wallpaperMayChange = true; + } + if (wallpaperMayChange) { if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper may change! Adjusting"); @@ -9060,7 +10418,8 @@ public class WindowManagerService extends IWindowManager.Stub if (mFocusMayChange) { mFocusMayChange = false; - if (updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES)) { + if (updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, + false /*updateInputWindows*/)) { changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_ANIM; adjResult = 0; } @@ -9072,8 +10431,6 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** ANIM STEP: changes=0x" + Integer.toHexString(changes)); - - mInputMonitor.updateInputWindowsLw(); } while (changes != 0); // THIRD LOOP: Update the surfaces of all windows. @@ -9085,7 +10442,8 @@ public class WindowManagerService extends IWindowManager.Stub boolean dimming = false; boolean covered = false; boolean syswin = false; - boolean backgroundFillerShown = false; + boolean backgroundFillerWasShown = mBackgroundFillerTarget != null; + mBackgroundFillerTarget = null; final int N = mWindows.size(); @@ -9117,11 +10475,8 @@ public class WindowManagerService extends IWindowManager.Stub + ": new=" + w.mShownFrame + ", old=" + w.mLastShownFrame); - boolean resize; int width, height; if ((w.mAttrs.flags & w.mAttrs.FLAG_SCALED) != 0) { - resize = w.mLastRequestedWidth != w.mRequestedWidth || - w.mLastRequestedHeight != w.mRequestedHeight; // for a scaled surface, we just want to use // the requested size. width = w.mRequestedWidth; @@ -9129,58 +10484,61 @@ public class WindowManagerService extends IWindowManager.Stub w.mLastRequestedWidth = width; w.mLastRequestedHeight = height; w.mLastShownFrame.set(w.mShownFrame); - try { - if (SHOW_TRANSACTIONS) logSurface(w, - "POS " + w.mShownFrame.left - + ", " + w.mShownFrame.top, null); - w.mSurfaceX = w.mShownFrame.left; - w.mSurfaceY = w.mShownFrame.top; - w.mSurface.setPosition(w.mShownFrame.left, w.mShownFrame.top); - } catch (RuntimeException e) { - Slog.w(TAG, "Error positioning surface in " + w, e); - if (!recoveringMemory) { - reclaimSomeSurfaceMemoryLocked(w, "position"); - } - } } else { - resize = !w.mLastShownFrame.equals(w.mShownFrame); width = w.mShownFrame.width(); height = w.mShownFrame.height(); w.mLastShownFrame.set(w.mShownFrame); } - if (resize) { - if (width < 1) width = 1; - if (height < 1) height = 1; - if (w.mSurface != null) { + if (w.mSurface != null) { + if (w.mSurfaceX != w.mShownFrame.left + || w.mSurfaceY != w.mShownFrame.top) { try { if (SHOW_TRANSACTIONS) logSurface(w, - "POS " + w.mShownFrame.left + "," - + w.mShownFrame.top + " SIZE " - + w.mShownFrame.width() + "x" + "POS " + w.mShownFrame.left + + ", " + w.mShownFrame.top, null); + w.mSurfaceX = w.mShownFrame.left; + w.mSurfaceY = w.mShownFrame.top; + w.mSurface.setPosition(w.mShownFrame.left, w.mShownFrame.top); + } catch (RuntimeException e) { + Slog.w(TAG, "Error positioning surface of " + w + + " pos=(" + w.mShownFrame.left + + "," + w.mShownFrame.top + ")", e); + if (!recoveringMemory) { + reclaimSomeSurfaceMemoryLocked(w, "position"); + } + } + } + + if (width < 1) { + width = 1; + } + if (height < 1) { + height = 1; + } + + if (w.mSurfaceW != width || w.mSurfaceH != height) { + try { + if (SHOW_TRANSACTIONS) logSurface(w, + "SIZE " + w.mShownFrame.width() + "x" + w.mShownFrame.height(), null); w.mSurfaceResized = true; w.mSurfaceW = width; w.mSurfaceH = height; w.mSurface.setSize(width, height); - w.mSurfaceX = w.mShownFrame.left; - w.mSurfaceY = w.mShownFrame.top; - w.mSurface.setPosition(w.mShownFrame.left, - w.mShownFrame.top); } catch (RuntimeException e) { // If something goes wrong with the surface (such // as running out of memory), don't take down the // entire system. - Slog.e(TAG, "Failure updating surface of " + w - + "size=(" + width + "x" + height - + "), pos=(" + w.mShownFrame.left - + "," + w.mShownFrame.top + ")", e); + Slog.e(TAG, "Error resizing surface of " + w + + " size=(" + width + "x" + height + ")", e); if (!recoveringMemory) { reclaimSomeSurfaceMemoryLocked(w, "size"); } } } } + if (!w.mAppFreezing && w.mLayoutSeq == mLayoutSeq) { w.mContentInsetsChanged = !w.mLastContentInsets.equals(w.mContentInsets); @@ -9197,11 +10555,21 @@ public class WindowManagerService extends IWindowManager.Stub if (localLOGV) Slog.v(TAG, "Resizing " + w + ": configChanged=" + configChanged + " last=" + w.mLastFrame + " frame=" + w.mFrame); - if (!w.mLastFrame.equals(w.mFrame) + boolean frameChanged = !w.mLastFrame.equals(w.mFrame); + if (frameChanged || w.mContentInsetsChanged || w.mVisibleInsetsChanged || w.mSurfaceResized || configChanged) { + if (DEBUG_RESIZE || DEBUG_ORIENTATION) { + Slog.v(TAG, "Resize reasons: " + + "frameChanged=" + frameChanged + + " contentInsetsChanged=" + w.mContentInsetsChanged + + " visibleInsetsChanged=" + w.mVisibleInsetsChanged + + " surfaceResized=" + w.mSurfaceResized + + " configChanged=" + configChanged); + } + w.mLastFrame.set(w.mFrame); w.mLastContentInsets.set(w.mContentInsets); w.mLastVisibleInsets.set(w.mVisibleInsets); @@ -9253,12 +10621,6 @@ public class WindowManagerService extends IWindowManager.Stub if (w.mAttachedHidden || !w.isReadyForDisplay()) { if (!w.mLastHidden) { //dump(); - if (DEBUG_CONFIGURATION) Slog.v(TAG, "Window hiding: waitingToShow=" - + w.mRootToken.waitingToShow + " polvis=" - + w.mPolicyVisibility + " atthid=" - + w.mAttachedHidden + " tokhid=" - + w.mRootToken.hidden + " vis=" - + w.mViewVisibility); w.mLastHidden = true; if (SHOW_TRANSACTIONS) logSurface(w, "HIDE (performLayout)", null); @@ -9370,6 +10732,11 @@ public class WindowManagerService extends IWindowManager.Stub w.mOrientationChanging = false; } + if (w.mContentChanged) { + //Slog.i(TAG, "Window " + this + " clearing mContentChanged - done placing"); + w.mContentChanged = false; + } + final boolean canBeSeen = w.isDisplayedLw(); if (someoneLosingFocus && w == mCurrentFocus && canBeSeen) { @@ -9378,6 +10745,16 @@ public class WindowManagerService extends IWindowManager.Stub final boolean obscuredChanged = w.mObscured != obscured; + if (mBackgroundFillerTarget != null) { + if (w.isAnimating()) { + // Background filler is below all other windows that + // are animating. + mBackgroundFillerTarget = w; + } else if (w.mIsWallpaper) { + mBackgroundFillerTarget = w; + } + } + // Update effect. if (!(w.mObscured=obscured)) { if (w.mSurface != null) { @@ -9406,33 +10783,10 @@ public class WindowManagerService extends IWindowManager.Stub // so we want to leave all of them as unblurred (for // performance reasons). obscured = true; - } else if (opaqueDrawn && w.needsBackgroundFiller(dw, dh)) { - if (SHOW_TRANSACTIONS) Slog.d(TAG, "showing background filler"); + } else if (w.needsBackgroundFiller(dw, dh) && (canBeSeen || w.isAnimating())) { // This window is in compatibility mode, and needs background filler. obscured = true; - if (mBackgroundFillerSurface == null) { - try { - mBackgroundFillerSurface = new Surface(mFxSession, 0, - "BackGroundFiller", - 0, dw, dh, - PixelFormat.OPAQUE, - Surface.FX_SURFACE_NORMAL); - } catch (Exception e) { - Slog.e(TAG, "Exception creating filler surface", e); - } - } - try { - mBackgroundFillerSurface.setPosition(0, 0); - mBackgroundFillerSurface.setSize(dw, dh); - // Using the same layer as Dim because they will never be shown at the - // same time. - mBackgroundFillerSurface.setLayer(w.mAnimLayer - 1); - mBackgroundFillerSurface.show(); - } catch (RuntimeException e) { - Slog.e(TAG, "Exception showing filler surface"); - } - backgroundFillerShown = true; - mBackgroundFillerShown = true; + mBackgroundFillerTarget = w; } else if (canBeSeen && !obscured && (attrFlags&FLAG_BLUR_BEHIND|FLAG_DIM_BEHIND) != 0) { if (localLOGV) Slog.v(TAG, "Win " + w @@ -9447,7 +10801,8 @@ public class WindowManagerService extends IWindowManager.Stub mDimAnimator = new DimAnimator(mFxSession); } mDimAnimator.show(dw, dh); - mDimAnimator.updateParameters(w, currentTime); + mDimAnimator.updateParameters(mContext.getResources(), + w, currentTime); } } if ((attrFlags&FLAG_BLUR_BEHIND) != 0) { @@ -9455,8 +10810,6 @@ public class WindowManagerService extends IWindowManager.Stub //Slog.i(TAG, "BLUR BEHIND: " + w); blurring = true; if (mBlurSurface == null) { - if (SHOW_TRANSACTIONS) Slog.i(TAG, " BLUR " - + mBlurSurface + ": CREATE"); try { mBlurSurface = new Surface(mFxSession, 0, "BlurSurface", @@ -9466,6 +10819,8 @@ public class WindowManagerService extends IWindowManager.Stub } catch (Exception e) { Slog.e(TAG, "Exception creating Blur surface", e); } + if (SHOW_TRANSACTIONS) Slog.i(TAG, " BLUR " + + mBlurSurface + ": CREATE"); } if (mBlurSurface != null) { if (SHOW_TRANSACTIONS) Slog.i(TAG, " BLUR " @@ -9498,9 +10853,39 @@ public class WindowManagerService extends IWindowManager.Stub } } - if (backgroundFillerShown == false && mBackgroundFillerShown) { - mBackgroundFillerShown = false; - if (SHOW_TRANSACTIONS) Slog.d(TAG, "hiding background filler"); + if (mBackgroundFillerTarget != null) { + if (mBackgroundFillerSurface == null) { + try { + mBackgroundFillerSurface = new Surface(mFxSession, 0, + "BackGroundFiller", + 0, dw, dh, + PixelFormat.OPAQUE, + Surface.FX_SURFACE_NORMAL); + } catch (Exception e) { + Slog.e(TAG, "Exception creating filler surface", e); + } + if (SHOW_TRANSACTIONS) Slog.i(TAG, " BG FILLER " + + mBackgroundFillerSurface + ": CREATE"); + } + try { + if (SHOW_TRANSACTIONS) Slog.i(TAG, " BG FILLER " + + mBackgroundFillerSurface + " SHOW: pos=(0,0) (" + + dw + "x" + dh + ") layer=" + + (mBackgroundFillerTarget.mLayer - 1)); + mBackgroundFillerSurface.setPosition(0, 0); + mBackgroundFillerSurface.setSize(dw, dh); + // Using the same layer as Dim because they will never be shown at the + // same time. NOTE: we do NOT use mAnimLayer, because we don't + // want this surface dragged up in front of stuff that is animating. + mBackgroundFillerSurface.setLayer(mBackgroundFillerTarget.mLayer - 1); + mBackgroundFillerSurface.show(); + } catch (RuntimeException e) { + Slog.e(TAG, "Exception showing filler surface"); + } + } else if (backgroundFillerWasShown) { + mBackgroundFillerTarget = null; + if (SHOW_TRANSACTIONS) Slog.i(TAG, " BG FILLER " + + mBackgroundFillerSurface + " HIDE"); try { mBackgroundFillerSurface.hide(); } catch (RuntimeException e) { @@ -9523,16 +10908,14 @@ public class WindowManagerService extends IWindowManager.Stub } mBlurShown = false; } - - if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION"); } catch (RuntimeException e) { Slog.e(TAG, "Unhandled exception in Window Manager", e); } - mInputMonitor.updateInputWindowsLw(); - Surface.closeTransaction(); + if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces"); + if (mWatermark != null) { mWatermark.drawIfNeeded(); } @@ -9622,12 +11005,10 @@ public class WindowManagerService extends IWindowManager.Stub // soon as their animations are complete token.animation = null; token.animating = false; + if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, + "performLayout: App token exiting now removed" + token); mAppTokens.remove(token); mExitingAppTokens.remove(i); - if (mLastEnterAnimToken == token) { - mLastEnterAnimToken = null; - mLastEnterAnimParams = null; - } } } @@ -9659,13 +11040,12 @@ public class WindowManagerService extends IWindowManager.Stub } else if (animating) { requestAnimationLocked(currentTime+(1000/60)-SystemClock.uptimeMillis()); } - - mInputMonitor.updateInputWindowsLw(); - - if (DEBUG_FREEZE) Slog.v(TAG, "Layout: mDisplayFrozen=" + mDisplayFrozen - + " holdScreen=" + holdScreen); + + // Finally update all input windows now that the window changes have stabilized. + mInputMonitor.updateInputWindowsLw(true /*force*/); + + setHoldScreenLocked(holdScreen != null); if (!mDisplayFrozen) { - setHoldScreenLocked(holdScreen != null); if (screenBrightness < 0 || screenBrightness > 1.0f) { mPowerManager.setScreenBrightnessOverride(-1); } else { @@ -9678,11 +11058,11 @@ public class WindowManagerService extends IWindowManager.Stub mPowerManager.setButtonBrightnessOverride((int) (buttonBrightness * Power.BRIGHTNESS_ON)); } - if (holdScreen != mHoldingScreenOn) { - mHoldingScreenOn = holdScreen; - Message m = mH.obtainMessage(H.HOLD_SCREEN_CHANGED, holdScreen); - mH.sendMessage(m); - } + } + if (holdScreen != mHoldingScreenOn) { + mHoldingScreenOn = holdScreen; + Message m = mH.obtainMessage(H.HOLD_SCREEN_CHANGED, holdScreen); + mH.sendMessage(m); } if (mTurnOnScreen) { @@ -9692,6 +11072,20 @@ public class WindowManagerService extends IWindowManager.Stub mTurnOnScreen = false; } + if (screenRotationFinished && mScreenRotationAnimation != null) { + mScreenRotationAnimation.kill(); + mScreenRotationAnimation = null; + } + + if (updateRotation) { + if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation"); + boolean changed = setRotationUncheckedLocked( + WindowManagerPolicy.USE_LAST_ROTATION, 0, false); + if (changed) { + mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION); + } + } + // Check to see if we are now in a state where the screen should // be enabled, because the window obscured flags have changed. enableScreenIfNeededLocked(); @@ -9741,7 +11135,7 @@ public class WindowManagerService extends IWindowManager.Stub } return true; } catch (RuntimeException e) { - Slog.w(TAG, "Failure showing surface " + win.mSurface + " in " + win); + Slog.w(TAG, "Failure showing surface " + win.mSurface + " in " + win, e); } reclaimSomeSurfaceMemoryLocked(win, "show"); @@ -9776,6 +11170,7 @@ public class WindowManagerService extends IWindowManager.Stub + " token=" + win.mToken + " pid=" + ws.mSession.mPid + " uid=" + ws.mSession.mUid); + if (SHOW_TRANSACTIONS) logSurface(ws, "LEAK DESTROY", null); ws.mSurface.destroy(); ws.mSurfaceShown = false; ws.mSurface = null; @@ -9783,10 +11178,11 @@ public class WindowManagerService extends IWindowManager.Stub i--; N--; leakedSurface = true; - } else if (win.mAppToken != null && win.mAppToken.clientHidden) { + } else if (ws.mAppToken != null && ws.mAppToken.clientHidden) { Slog.w(TAG, "LEAKED SURFACE (app token hidden): " + ws + " surface=" + ws.mSurface + " token=" + win.mAppToken); + if (SHOW_TRANSACTIONS) logSurface(ws, "LEAK DESTROY", null); ws.mSurface.destroy(); ws.mSurfaceShown = false; ws.mSurface = null; @@ -9824,6 +11220,7 @@ public class WindowManagerService extends IWindowManager.Stub // surface and ask the app to request another one. Slog.w(TAG, "Looks like we have reclaimed some memory, clearing surface for retry."); if (surface != null) { + if (SHOW_TRANSACTIONS) logSurface(win, "RECOVER DESTROY", null); surface.destroy(); win.mSurfaceShown = false; win.mSurface = null; @@ -9839,7 +11236,7 @@ public class WindowManagerService extends IWindowManager.Stub } } - private boolean updateFocusedWindowLocked(int mode) { + private boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) { WindowState newFocus = computeFocusedWindowLocked(); if (mCurrentFocus != newFocus) { // This check makes sure that we don't already have the focus @@ -9860,7 +11257,7 @@ public class WindowManagerService extends IWindowManager.Stub mLayoutNeeded = true; } if (mode == UPDATE_FOCUS_PLACING_SURFACES) { - performLayoutLockedInner(); + performLayoutLockedInner(true /*initial*/, updateInputWindows); } else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) { // Client will do the layout, but we need to assign layers // for handleNewWindowLocked() below. @@ -9871,15 +11268,15 @@ public class WindowManagerService extends IWindowManager.Stub if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) { // If we defer assigning layers, then the caller is responsible for // doing this part. - finishUpdateFocusedWindowAfterAssignLayersLocked(); + finishUpdateFocusedWindowAfterAssignLayersLocked(updateInputWindows); } return true; } return false; } - private void finishUpdateFocusedWindowAfterAssignLayersLocked() { - mInputMonitor.setInputFocusLw(mCurrentFocus); + private void finishUpdateFocusedWindowAfterAssignLayersLocked(boolean updateInputWindows) { + mInputMonitor.setInputFocusLw(mCurrentFocus, updateInputWindows); } private WindowState computeFocusedWindowLocked() { @@ -9951,7 +11348,7 @@ public class WindowManagerService extends IWindowManager.Stub return result; } - private void startFreezingDisplayLocked() { + private void startFreezingDisplayLocked(boolean inTransaction) { if (mDisplayFrozen) { return; } @@ -9971,8 +11368,6 @@ public class WindowManagerService extends IWindowManager.Stub mFreezeGcPending = now; } - if (DEBUG_FREEZE) Slog.v(TAG, "*** FREEZING DISPLAY", new RuntimeException()); - mDisplayFrozen = true; mInputMonitor.freezeInputDispatchingLw(); @@ -9987,7 +11382,19 @@ public class WindowManagerService extends IWindowManager.Stub File file = new File("/data/system/frozen"); Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024); } - Surface.freezeDisplay(0); + + if (CUSTOM_SCREEN_ROTATION) { + if (mScreenRotationAnimation != null && mScreenRotationAnimation.isAnimating()) { + mScreenRotationAnimation.kill(); + mScreenRotationAnimation = null; + } + if (mScreenRotationAnimation == null) { + mScreenRotationAnimation = new ScreenRotationAnimation(mContext, + mDisplay, mFxSession, inTransaction); + } + } else { + Surface.freezeDisplay(0); + } } private void stopFreezingDisplayLocked() { @@ -9999,24 +11406,37 @@ public class WindowManagerService extends IWindowManager.Stub return; } - if (DEBUG_FREEZE) Slog.v(TAG, "*** UNFREEZING DISPLAY", new RuntimeException()); - mDisplayFrozen = false; mH.removeMessages(H.APP_FREEZE_TIMEOUT); if (PROFILE_ORIENTATION) { Debug.stopMethodTracing(); } - Surface.unfreezeDisplay(0); + + boolean updateRotation = false; + + if (CUSTOM_SCREEN_ROTATION) { + if (mScreenRotationAnimation != null) { + if (mScreenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION, + mTransitionAnimationScale)) { + requestAnimationLocked(0); + } else { + mScreenRotationAnimation = null; + updateRotation = true; + } + } + } else { + Surface.unfreezeDisplay(0); + } mInputMonitor.thawInputDispatchingLw(); + boolean configChanged; + // While the display is frozen we don't re-compute the orientation // to avoid inconsistent states. However, something interesting // could have actually changed during that time so re-evaluate it // now to catch that. - if (updateOrientationFromAppTokensLocked()) { - mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION); - } + configChanged = updateOrientationFromAppTokensLocked(false); // A little kludge: a lot could have happened while the // display was frozen, so now that we are coming back we @@ -10028,6 +11448,16 @@ public class WindowManagerService extends IWindowManager.Stub 2000); mScreenFrozenLock.release(); + + if (updateRotation) { + if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation"); + configChanged |= setRotationUncheckedLocked( + WindowManagerPolicy.USE_LAST_ROTATION, 0, false); + } + + if (configChanged) { + mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION); + } } static int getPropertyInt(String[] tokens, int index, int defUnits, int defDps, @@ -10049,7 +11479,7 @@ public class WindowManagerService extends IWindowManager.Stub return val; } - class Watermark { + static class Watermark { final String[] mTokens; final String mText; final Paint mTextPaint; @@ -10065,9 +11495,9 @@ public class WindowManagerService extends IWindowManager.Stub int mLastDH; boolean mDrawNeeded; - Watermark(SurfaceSession session, String[] tokens) { + Watermark(Display display, SurfaceSession session, String[] tokens) { final DisplayMetrics dm = new DisplayMetrics(); - mDisplay.getMetrics(dm); + display.getMetrics(dm); if (false) { Log.i(TAG, "*********************** WATERMARK"); @@ -10161,6 +11591,8 @@ public class WindowManagerService extends IWindowManager.Stub } catch (OutOfResourcesException e) { } if (c != null) { + c.drawColor(0, PorterDuff.Mode.CLEAR); + int deltaX = mDeltaX; int deltaY = mDeltaY; @@ -10203,7 +11635,7 @@ public class WindowManagerService extends IWindowManager.Stub if (line != null) { String[] toks = line.split("%"); if (toks != null && toks.length > 0) { - mWatermark = new Watermark(mFxSession, toks); + mWatermark = new Watermark(mDisplay, mFxSession, toks); } } } catch (FileNotFoundException e) { @@ -10219,6 +11651,23 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public void statusBarVisibilityChanged(int visibility) { + synchronized (mWindowMap) { + final int N = mWindows.size(); + for (int i = 0; i < N; i++) { + WindowState ws = mWindows.get(i); + try { + if (ws.getAttrs().hasSystemUiListeners) { + ws.mClient.dispatchSystemUiVisibilityChanged(visibility); + } + } catch (RemoteException e) { + // so sorry + } + } + } + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission("android.permission.DUMP") != PackageManager.PERMISSION_GRANTED) { @@ -10317,14 +11766,6 @@ public class WindowManagerService extends IWindowManager.Stub token.dump(pw, " "); } } - if (mTokenList.size() > 0) { - pw.println(" "); - pw.println(" Window token list:"); - for (int i=0; i<mTokenList.size(); i++) { - pw.print(" #"); pw.print(i); pw.print(": "); - pw.println(mTokenList.get(i)); - } - } if (mWallpaperTokens.size() > 0) { pw.println(" "); pw.println(" Wallpaper tokens:"); @@ -10384,6 +11825,9 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mLowerWallpaperTarget="); pw.println(mLowerWallpaperTarget); pw.print(" mUpperWallpaperTarget="); pw.println(mUpperWallpaperTarget); } + if (mWindowDetachedWallpaper != null) { + pw.print(" mWindowDetachedWallpaper="); pw.println(mWindowDetachedWallpaper); + } pw.print(" mCurConfiguration="); pw.println(this.mCurConfiguration); pw.print(" mInTouchMode="); pw.print(mInTouchMode); pw.print(" mLayoutSeq="); pw.println(mLayoutSeq); @@ -10409,6 +11853,8 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mRotation="); pw.print(mRotation); pw.print(", mForcedAppOrientation="); pw.print(mForcedAppOrientation); pw.print(", mRequestedRotation="); pw.println(mRequestedRotation); + pw.print(" mDeferredRotation="); pw.print(mDeferredRotation); + pw.print(", mDeferredRotationAnimFlags="); pw.print(mDeferredRotationAnimFlags); pw.print(" mAnimationPending="); pw.print(mAnimationPending); pw.print(" mWindowAnimationScale="); pw.print(mWindowAnimationScale); pw.print(" mTransitionWindowAnimationScale="); pw.println(mTransitionAnimationScale); @@ -10427,10 +11873,6 @@ public class WindowManagerService extends IWindowManager.Stub } pw.print(" mStartingIconInTransition="); pw.print(mStartingIconInTransition); pw.print(", mSkipAppTransitionAnimation="); pw.println(mSkipAppTransitionAnimation); - if (mLastEnterAnimToken != null || mLastEnterAnimToken != null) { - pw.print(" mLastEnterAnimToken="); pw.print(mLastEnterAnimToken); - pw.print(", mLastEnterAnimParams="); pw.println(mLastEnterAnimParams); - } if (mOpeningApps.size() > 0) { pw.print(" mOpeningApps="); pw.println(mOpeningApps); } @@ -10443,8 +11885,14 @@ public class WindowManagerService extends IWindowManager.Stub if (mToBottomApps.size() > 0) { pw.print(" mToBottomApps="); pw.println(mToBottomApps); } - pw.print(" DisplayWidth="); pw.print(mDisplay.getWidth()); - pw.print(" DisplayHeight="); pw.println(mDisplay.getHeight()); + if (mDisplay != null) { + pw.print(" DisplayWidth="); pw.print(mDisplay.getWidth()); + pw.print(" DisplayHeight="); pw.println(mDisplay.getHeight()); + } else { + pw.println(" NO DISPLAY"); + } + pw.println(" Policy:"); + mPolicy.dump(" ", fd, pw, args); } } @@ -10512,7 +11960,7 @@ public class WindowManagerService extends IWindowManager.Stub * Set's the dim surface's layer and update dim parameters that will be used in * {@link updateSurface} after all windows are examined. */ - void updateParameters(WindowState w, long currentTime) { + void updateParameters(Resources res, WindowState w, long currentTime) { mDimSurface.setLayer(w.mAnimLayer-1); final float target = w.mExiting ? 0 : w.mAttrs.dimAmount; @@ -10526,11 +11974,15 @@ public class WindowManagerService extends IWindowManager.Stub ? w.mAnimation.computeDurationHint() : DEFAULT_DIM_DURATION; if (target > mDimTargetAlpha) { - // This is happening behind the activity UI, - // so we can make it run a little longer to - // give a stronger impression without disrupting - // the user. - duration *= DIM_DURATION_MULTIPLIER; + TypedValue tv = new TypedValue(); + res.getValue(com.android.internal.R.fraction.config_dimBehindFadeDuration, + tv, true); + if (tv.type == TypedValue.TYPE_FRACTION) { + duration = (long)tv.getFraction((float)duration, (float)duration); + } else if (tv.type >= TypedValue.TYPE_FIRST_INT + && tv.type <= TypedValue.TYPE_LAST_INT) { + duration = tv.data; + } } if (duration < 1) { // Don't divide by zero @@ -10616,7 +12068,6 @@ public class WindowManagerService extends IWindowManager.Stub * This is used for opening/closing transition for apps in compatible mode. */ private static class FadeInOutAnimation extends Animation { - int mWidth; boolean mFadeIn; public FadeInOutAnimation(boolean fadeIn) { @@ -10631,24 +12082,11 @@ public class WindowManagerService extends IWindowManager.Stub if (!mFadeIn) { x = 1.0f - x; // reverse the interpolation for fade out } - if (x < 0.5) { - // move the window out of the screen. - t.getMatrix().setTranslate(mWidth, 0); - } else { - t.getMatrix().setTranslate(0, 0);// show - t.setAlpha((x - 0.5f) * 2); - } - } - - @Override - public void initialize(int width, int height, int parentWidth, int parentHeight) { - // width is the screen width {@see AppWindowToken#stepAnimatinoLocked} - mWidth = width; + t.setAlpha(x); } + } - @Override - public int getZAdjustment() { - return Animation.ZORDER_TOP; - } + public interface OnHardKeyboardStatusChangeListener { + public void onHardKeyboardStatusChange(boolean available, boolean enabled); } } diff --git a/services/java/com/android/server/WiredAccessoryObserver.java b/services/java/com/android/server/WiredAccessoryObserver.java new file mode 100644 index 0000000..e45c368 --- /dev/null +++ b/services/java/com/android/server/WiredAccessoryObserver.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2008 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.server; + +import android.app.ActivityManagerNative; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.os.Message; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.os.UEventObserver; +import android.util.Slog; +import android.media.AudioManager; +import android.util.Log; + +import java.io.FileReader; +import java.io.FileNotFoundException; + +/** + * <p>WiredAccessoryObserver monitors for a wired headset on the main board or dock. + */ +class WiredAccessoryObserver extends UEventObserver { + private static final String TAG = WiredAccessoryObserver.class.getSimpleName(); + private static final boolean LOG = true; + private static final int MAX_AUDIO_PORTS = 3; /* h2w, USB Audio & hdmi */ + private static final String uEventInfo[][] = { {"DEVPATH=/devices/virtual/switch/h2w", + "/sys/class/switch/h2w/state", + "/sys/class/switch/h2w/name"}, + {"DEVPATH=/devices/virtual/switch/usb_audio", + "/sys/class/switch/usb_audio/state", + "/sys/class/switch/usb_audio/name"}, + {"DEVPATH=/devices/virtual/switch/hdmi", + "/sys/class/switch/hdmi/state", + "/sys/class/switch/hdmi/name"} }; + + private static final int BIT_HEADSET = (1 << 0); + private static final int BIT_HEADSET_NO_MIC = (1 << 1); + private static final int BIT_USB_HEADSET_ANLG = (1 << 2); + private static final int BIT_USB_HEADSET_DGTL = (1 << 3); + private static final int BIT_HDMI_AUDIO = (1 << 4); + private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC| + BIT_USB_HEADSET_ANLG|BIT_USB_HEADSET_DGTL| + BIT_HDMI_AUDIO); + private static final int HEADSETS_WITH_MIC = BIT_HEADSET; + + private int mHeadsetState; + private int mPrevHeadsetState; + private String mHeadsetName; + private int switchState; + + private final Context mContext; + private final WakeLock mWakeLock; // held while there is a pending route change + + public WiredAccessoryObserver(Context context) { + mContext = context; + PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryObserver"); + mWakeLock.setReferenceCounted(false); + + context.registerReceiver(new BootCompletedReceiver(), + new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); + } + + private final class BootCompletedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + // At any given time accessories could be inserted + // one on the board, one on the dock and one on HDMI: + // observe three UEVENTs + init(); // set initial status + for (int i = 0; i < MAX_AUDIO_PORTS; i++) { + startObserving(uEventInfo[i][0]); + } + } + } + + @Override + public void onUEvent(UEventObserver.UEvent event) { + if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString()); + + try { + String name = event.get("SWITCH_NAME"); + int state = Integer.parseInt(event.get("SWITCH_STATE")); + updateState(name, state); + } catch (NumberFormatException e) { + Slog.e(TAG, "Could not parse switch state from event " + event); + } + } + + private synchronized final void updateState(String name, int state) + { + if (name.equals("usb_audio")) { + if (state == 1) { + switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC| + BIT_USB_HEADSET_DGTL|BIT_HDMI_AUDIO)) | + (state << 2)); + } else if (state == 2) { + switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC| + BIT_USB_HEADSET_ANLG|BIT_HDMI_AUDIO)) | + (state << 3)); + } else switchState = (mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC|BIT_HDMI_AUDIO)); + } + else if (name.equals("hdmi")) { + switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC| + BIT_USB_HEADSET_DGTL|BIT_USB_HEADSET_ANLG)) | + (state << 4)); + } + else { + switchState = ((mHeadsetState & (BIT_HDMI_AUDIO|BIT_USB_HEADSET_ANLG| + BIT_USB_HEADSET_DGTL)) | + state); + } + update(name, switchState); + } + + private synchronized final void init() { + char[] buffer = new char[1024]; + + String newName = mHeadsetName; + int newState = mHeadsetState; + mPrevHeadsetState = mHeadsetState; + + if (LOG) Slog.v(TAG, "init()"); + + for (int i = 0; i < MAX_AUDIO_PORTS; i++) { + try { + FileReader file = new FileReader(uEventInfo[i][1]); + int len = file.read(buffer, 0, 1024); + file.close(); + newState = Integer.valueOf((new String(buffer, 0, len)).trim()); + + file = new FileReader(uEventInfo[i][2]); + len = file.read(buffer, 0, 1024); + file.close(); + newName = new String(buffer, 0, len).trim(); + + if (newState > 0) { + updateState(newName, newState); + } + + } catch (FileNotFoundException e) { + Slog.w(TAG, "This kernel does not have wired headset support"); + } catch (Exception e) { + Slog.e(TAG, "" , e); + } + } + } + + private synchronized final void update(String newName, int newState) { + // Retain only relevant bits + int headsetState = newState & SUPPORTED_HEADSETS; + int newOrOld = headsetState | mHeadsetState; + int delay = 0; + int usb_headset_anlg = headsetState & BIT_USB_HEADSET_ANLG; + int usb_headset_dgtl = headsetState & BIT_USB_HEADSET_DGTL; + int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC); + boolean h2wStateChange = true; + boolean usbStateChange = true; + // reject all suspect transitions: only accept state changes from: + // - a: 0 heaset to 1 headset + // - b: 1 headset to 0 headset + if (LOG) Slog.v(TAG, "newState = "+newState+", headsetState = "+headsetState+"," + + "mHeadsetState = "+mHeadsetState); + if (mHeadsetState == headsetState || ((h2w_headset & (h2w_headset - 1)) != 0)) { + Log.e(TAG, "unsetting h2w flag"); + h2wStateChange = false; + } + // - c: 0 usb headset to 1 usb headset + // - d: 1 usb headset to 0 usb headset + if ((usb_headset_anlg >> 2) == 1 && (usb_headset_dgtl >> 3) == 1) { + Log.e(TAG, "unsetting usb flag"); + usbStateChange = false; + } + if (!h2wStateChange && !usbStateChange) { + Log.e(TAG, "invalid transition, returning ..."); + return; + } + + mHeadsetName = newName; + mPrevHeadsetState = mHeadsetState; + mHeadsetState = headsetState; + + if (headsetState == 0) { + Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY); + mContext.sendBroadcast(intent); + // It can take hundreds of ms flush the audio pipeline after + // apps pause audio playback, but audio route changes are + // immediate, so delay the route change by 1000ms. + // This could be improved once the audio sub-system provides an + // interface to clear the audio pipeline. + delay = 1000; + } else { + // Insert the same delay for headset connection so that the connection event is not + // broadcast before the disconnection event in case of fast removal/insertion + if (mHandler.hasMessages(0)) { + delay = 1000; + } + } + mWakeLock.acquire(); + mHandler.sendMessageDelayed(mHandler.obtainMessage(0, + mHeadsetState, + mPrevHeadsetState, + mHeadsetName), + delay); + } + + private synchronized final void sendIntents(int headsetState, int prevHeadsetState, String headsetName) { + int allHeadsets = SUPPORTED_HEADSETS; + for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) { + if ((curHeadset & allHeadsets) != 0) { + sendIntent(curHeadset, headsetState, prevHeadsetState, headsetName); + allHeadsets &= ~curHeadset; + } + } + } + + private final void sendIntent(int headset, int headsetState, int prevHeadsetState, String headsetName) { + if ((headsetState & headset) != (prevHeadsetState & headset)) { + + int state = 0; + if ((headsetState & headset) != 0) { + state = 1; + } + if((headset == BIT_USB_HEADSET_ANLG) || (headset == BIT_USB_HEADSET_DGTL) || + (headset == BIT_HDMI_AUDIO)) { + Intent intent; + + // Pack up the values and broadcast them to everyone + if (headset == BIT_USB_HEADSET_ANLG) { + intent = new Intent(Intent.ACTION_USB_ANLG_HEADSET_PLUG); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + intent.putExtra("state", state); + intent.putExtra("name", headsetName); + ActivityManagerNative.broadcastStickyIntent(intent, null); + } else if (headset == BIT_USB_HEADSET_DGTL) { + intent = new Intent(Intent.ACTION_USB_DGTL_HEADSET_PLUG); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + intent.putExtra("state", state); + intent.putExtra("name", headsetName); + ActivityManagerNative.broadcastStickyIntent(intent, null); + } else if (headset == BIT_HDMI_AUDIO) { + intent = new Intent(Intent.ACTION_HDMI_AUDIO_PLUG); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + intent.putExtra("state", state); + intent.putExtra("name", headsetName); + ActivityManagerNative.broadcastStickyIntent(intent, null); + } + + if (LOG) Slog.v(TAG, "Intent.ACTION_USB_HEADSET_PLUG: state: "+state+" name: "+headsetName); + // TODO: Should we require a permission? + } + if((headset == BIT_HEADSET) || (headset == BIT_HEADSET_NO_MIC)) { + + // Pack up the values and broadcast them to everyone + Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + //int state = 0; + int microphone = 0; + + if ((headset & HEADSETS_WITH_MIC) != 0) { + microphone = 1; + } + + intent.putExtra("state", state); + intent.putExtra("name", headsetName); + intent.putExtra("microphone", microphone); + + if (LOG) Slog.v(TAG, "Intent.ACTION_HEADSET_PLUG: state: "+state+" name: "+headsetName+" mic: "+microphone); + // TODO: Should we require a permission? + ActivityManagerNative.broadcastStickyIntent(intent, null); + } + } + } + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + sendIntents(msg.arg1, msg.arg2, (String)msg.obj); + mWakeLock.release(); + } + }; +} diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 0e38e10..c24fe8e 100755 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -76,6 +76,8 @@ import android.content.pm.ServiceInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.graphics.Bitmap; +import android.net.Proxy; +import android.net.ProxyProperties; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -554,7 +556,7 @@ public final class ActivityManagerService extends ActivityManagerNative = new HashMap<PendingIntentRecord.Key, WeakReference<PendingIntentRecord>>(); /** - * Fingerprints (String.hashCode()) of stack traces that we've + * Fingerprints (hashCode()) of stack traces that we've * already logged DropBox entries for. Guarded by itself. If * something (rogue user app) forces this over * MAX_DUP_SUPPRESSED_STACKS entries, the contents are cleared. @@ -959,6 +961,8 @@ public final class ActivityManagerService extends ActivityManagerNative static final int CANCEL_HEAVY_NOTIFICATION_MSG = 25; static final int SHOW_STRICT_MODE_VIOLATION_MSG = 26; static final int CHECK_EXCESSIVE_WAKE_LOCKS_MSG = 27; + static final int CLEAR_DNS_CACHE = 28; + static final int UPDATE_HTTP_PROXY = 29; AlertDialog mUidAlert; @@ -1110,6 +1114,44 @@ public final class ActivityManagerService extends ActivityManagerNative } } } break; + case CLEAR_DNS_CACHE: { + synchronized (ActivityManagerService.this) { + for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) { + ProcessRecord r = mLruProcesses.get(i); + if (r.thread != null) { + try { + r.thread.clearDnsCache(); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to clear dns cache for: " + r.info.processName); + } + } + } + } + } break; + case UPDATE_HTTP_PROXY: { + ProxyProperties proxy = (ProxyProperties)msg.obj; + String host = ""; + String port = ""; + String exclList = ""; + if (proxy != null) { + host = proxy.getHost(); + port = Integer.toString(proxy.getPort()); + exclList = proxy.getExclusionList(); + } + synchronized (ActivityManagerService.this) { + for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) { + ProcessRecord r = mLruProcesses.get(i); + if (r.thread != null) { + try { + r.thread.setHttpProxy(host, port, exclList); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to update http proxy for: " + + r.info.processName); + } + } + } + } + } break; case SHOW_UID_ERROR_MSG: { // XXX This is a temporary dialog, no need to localize. AlertDialog d = new BaseErrorDialog(mContext); @@ -1282,6 +1324,7 @@ public final class ActivityManagerService extends ActivityManagerNative ActivityThread at = ActivityThread.systemMain(); mSystemThread = at; Context context = at.getSystemContext(); + context.setTheme(android.R.style.Theme_Holo); m.mContext = context; m.mFactoryTest = factoryTest; m.mMainStack = new ActivityStack(m, context, true); @@ -1334,6 +1377,11 @@ public final class ActivityManagerService extends ActivityManagerNative } } + // For debug builds, log event loop stalls to dropbox for analysis. + if (StrictMode.conditionallyEnableDebugLogging()) { + Slog.i(TAG, "Enabled StrictMode logging for AThread's Looper"); + } + Looper.loop(); } } @@ -1978,7 +2026,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (app == null || app.instrumentationClass == null) { intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); mMainStack.startActivityLocked(null, intent, null, null, 0, aInfo, - null, null, 0, 0, 0, false, false); + null, null, 0, 0, 0, false, false, null); } } @@ -2034,7 +2082,7 @@ public final class ActivityManagerService extends ActivityManagerNative intent.setComponent(new ComponentName( ri.activityInfo.packageName, ri.activityInfo.name)); mMainStack.startActivityLocked(null, intent, null, null, 0, ri.activityInfo, - null, null, 0, 0, 0, false, false); + null, null, 0, 0, 0, false, false, null); } } } @@ -2073,13 +2121,13 @@ public final class ActivityManagerService extends ActivityManagerNative } mPendingActivityLaunches.clear(); } - + public final int startActivity(IApplicationThread caller, Intent intent, String resolvedType, Uri[] grantedUriPermissions, int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug) { - return mMainStack.startActivityMayWait(caller, intent, resolvedType, + return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, debug, null, null); } @@ -2090,7 +2138,7 @@ public final class ActivityManagerService extends ActivityManagerNative String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug) { WaitResult res = new WaitResult(); - mMainStack.startActivityMayWait(caller, intent, resolvedType, + mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, debug, res, null); return res; @@ -2101,12 +2149,12 @@ public final class ActivityManagerService extends ActivityManagerNative int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, Configuration config) { - return mMainStack.startActivityMayWait(caller, intent, resolvedType, + return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, debug, null, config); } - public int startActivityIntentSender(IApplicationThread caller, + public int startActivityIntentSender(IApplicationThread caller, IntentSender intent, Intent fillInIntent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flagsMask, int flagsValues) { @@ -2219,7 +2267,7 @@ public final class ActivityManagerService extends ActivityManagerNative // those are not yet exposed to user code, so there is no need. int res = mMainStack.startActivityLocked(r.app.thread, intent, r.resolvedType, null, 0, aInfo, resultTo, resultWho, - requestCode, -1, r.launchedFromUid, false, false); + requestCode, -1, r.launchedFromUid, false, false, null); Binder.restoreCallingIdentity(origId); r.finishing = wasFinishing; @@ -2241,43 +2289,37 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException( "startActivityInPackage only available to the system"); } - - final boolean componentSpecified = intent.getComponent() != null; - - // Don't modify the client's object! - intent = new Intent(intent); - // Collect information about the target of the Intent. - ActivityInfo aInfo; - try { - ResolveInfo rInfo = - AppGlobals.getPackageManager().resolveIntent( - intent, resolvedType, - PackageManager.MATCH_DEFAULT_ONLY | STOCK_PM_FLAGS); - aInfo = rInfo != null ? rInfo.activityInfo : null; - } catch (RemoteException e) { - aInfo = null; - } + return mMainStack.startActivityMayWait(null, uid, intent, resolvedType, + null, 0, resultTo, resultWho, requestCode, onlyIfNeeded, false, null, null); + } - if (aInfo != null) { - // Store the found target back into the intent, because now that - // we have it we never want to do this again. For example, if the - // user navigates back to this point in the history, we should - // always restart the exact same activity. - intent.setComponent(new ComponentName( - aInfo.applicationInfo.packageName, aInfo.name)); - } + public final int startActivities(IApplicationThread caller, + Intent[] intents, String[] resolvedTypes, IBinder resultTo) { + return mMainStack.startActivities(caller, -1, intents, resolvedTypes, resultTo); + } - synchronized(this) { - return mMainStack.startActivityLocked(null, intent, resolvedType, - null, 0, aInfo, resultTo, resultWho, requestCode, -1, uid, - onlyIfNeeded, componentSpecified); + public final int startActivitiesInPackage(int uid, + Intent[] intents, String[] resolvedTypes, IBinder resultTo) { + + // This is so super not safe, that only the system (or okay root) + // can do it. + final int callingUid = Binder.getCallingUid(); + if (callingUid != 0 && callingUid != Process.myUid()) { + throw new SecurityException( + "startActivityInPackage only available to the system"); } + + return mMainStack.startActivities(null, uid, intents, resolvedTypes, resultTo); } final void addRecentTaskLocked(TaskRecord task) { - // Remove any existing entries that are the same kind of task. int N = mRecentTasks.size(); + // Quick case: check if the top-most recent task is the same. + if (N > 0 && mRecentTasks.get(0) == task) { + return; + } + // Remove any existing entries that are the same kind of task. for (int i=0; i<N; i++) { TaskRecord tr = mRecentTasks.get(i); if ((task.affinity != null && task.affinity.equals(tr.affinity)) @@ -2451,6 +2493,10 @@ public final class ActivityManagerService extends ActivityManagerNative } if (proc.thread != null) { + if (proc.pid == Process.myPid()) { + Log.w(TAG, "crashApplication: trying to crash self!"); + return; + } long ident = Binder.clearCallingIdentity(); try { proc.thread.scheduleCrash(message); @@ -2566,6 +2612,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (localLOGV) Slog.v( TAG, "Removing this entry! frozen=" + r.haveState + " finishing=" + r.finishing); + r.makeFinishing(); mMainStack.mHistory.remove(i); r.inHistory = false; @@ -3013,7 +3060,7 @@ public final class ActivityManagerService extends ActivityManagerNative } if (uid == pkgUid || checkComponentPermission( android.Manifest.permission.CLEAR_APP_USER_DATA, - pid, uid, -1) + pid, uid, -1, true) == PackageManager.PERMISSION_GRANTED) { forceStopPackageLocked(packageName, pkgUid); } else { @@ -3738,22 +3785,22 @@ public final class ActivityManagerService extends ActivityManagerNative } } - public final void activityPaused(IBinder token, Bundle icicle) { - // Refuse possible leaked file descriptors - if (icicle != null && icicle.hasFileDescriptors()) { - throw new IllegalArgumentException("File descriptors passed in Bundle"); - } - + public final void activityPaused(IBinder token) { final long origId = Binder.clearCallingIdentity(); - mMainStack.activityPaused(token, icicle, false); + mMainStack.activityPaused(token, false); Binder.restoreCallingIdentity(origId); } - public final void activityStopped(IBinder token, Bitmap thumbnail, + public final void activityStopped(IBinder token, Bundle icicle, Bitmap thumbnail, CharSequence description) { if (localLOGV) Slog.v( TAG, "Activity stopped: token=" + token); + // Refuse possible leaked file descriptors + if (icicle != null && icicle.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Bundle"); + } + ActivityRecord r = null; final long origId = Binder.clearCallingIdentity(); @@ -3762,8 +3809,18 @@ public final class ActivityManagerService extends ActivityManagerNative int index = mMainStack.indexOfTokenLocked(token); if (index >= 0) { r = (ActivityRecord)mMainStack.mHistory.get(index); - r.thumbnail = thumbnail; + r.icicle = icicle; + r.haveState = true; + if (thumbnail != null) { + r.thumbnail = thumbnail; + if (r.task != null) { + r.task.lastThumbnail = r.thumbnail; + } + } r.description = description; + if (r.task != null) { + r.task.lastDescription = r.description; + } r.stopped = true; r.state = ActivityState.STOPPED; if (!r.finishing) { @@ -3838,16 +3895,30 @@ public final class ActivityManagerService extends ActivityManagerNative public IIntentSender getIntentSender(int type, String packageName, IBinder token, String resultWho, - int requestCode, Intent intent, String resolvedType, int flags) { + int requestCode, Intent[] intents, String[] resolvedTypes, int flags) { // Refuse possible leaked file descriptors - if (intent != null && intent.hasFileDescriptors() == true) { - throw new IllegalArgumentException("File descriptors passed in Intent"); - } - - if (type == INTENT_SENDER_BROADCAST) { - if ((intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) { + if (intents != null) { + if (intents.length < 1) { + throw new IllegalArgumentException("Intents array length must be >= 1"); + } + for (int i=0; i<intents.length; i++) { + Intent intent = intents[i]; + if (intent == null) { + throw new IllegalArgumentException("Null intent at index " + i); + } + if (intent.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + if (type == INTENT_SENDER_BROADCAST && + (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) { + throw new IllegalArgumentException( + "Can't use FLAG_RECEIVER_BOOT_UPGRADE here"); + } + intents[i] = new Intent(intent); + } + if (resolvedTypes != null && resolvedTypes.length != intents.length) { throw new IllegalArgumentException( - "Can't use FLAG_RECEIVER_BOOT_UPGRADE here"); + "Intent array length does not match resolvedTypes length"); } } @@ -3870,7 +3941,7 @@ public final class ActivityManagerService extends ActivityManagerNative } return getIntentSenderLocked(type, packageName, callingUid, - token, resultWho, requestCode, intent, resolvedType, flags); + token, resultWho, requestCode, intents, resolvedTypes, flags); } catch (RemoteException e) { throw new SecurityException(e); @@ -3880,7 +3951,7 @@ public final class ActivityManagerService extends ActivityManagerNative IIntentSender getIntentSenderLocked(int type, String packageName, int callingUid, IBinder token, String resultWho, - int requestCode, Intent intent, String resolvedType, int flags) { + int requestCode, Intent[] intents, String[] resolvedTypes, int flags) { ActivityRecord activity = null; if (type == INTENT_SENDER_ACTIVITY_RESULT) { int index = mMainStack.indexOfTokenLocked(token); @@ -3901,14 +3972,24 @@ public final class ActivityManagerService extends ActivityManagerNative PendingIntentRecord.Key key = new PendingIntentRecord.Key( type, packageName, activity, resultWho, - requestCode, intent, resolvedType, flags); + requestCode, intents, resolvedTypes, flags); WeakReference<PendingIntentRecord> ref; ref = mIntentSenderRecords.get(key); PendingIntentRecord rec = ref != null ? ref.get() : null; if (rec != null) { if (!cancelCurrent) { if (updateCurrent) { - rec.key.requestIntent.replaceExtras(intent); + if (rec.key.requestIntent != null) { + rec.key.requestIntent.replaceExtras(intents != null ? intents[0] : null); + } + if (intents != null) { + intents[intents.length-1] = rec.key.requestIntent; + rec.key.allIntents = intents; + rec.key.allResolvedTypes = resolvedTypes; + } else { + rec.key.allIntents = null; + rec.key.allResolvedTypes = null; + } } return rec; } @@ -4070,7 +4151,7 @@ public final class ActivityManagerService extends ActivityManagerNative * This can be called with or without the global lock held. */ int checkComponentPermission(String permission, int pid, int uid, - int reqUid) { + int owningUid, boolean exported) { // We might be performing an operation on behalf of an indirect binder // invocation, e.g. via {@link #openContentUri}. Check and adjust the // client identity accordingly before proceeding. @@ -4087,9 +4168,14 @@ public final class ActivityManagerService extends ActivityManagerNative !Process.supportsProcesses()) { return PackageManager.PERMISSION_GRANTED; } - // If the target requires a specific UID, always fail for others. - if (reqUid >= 0 && uid != reqUid) { - Slog.w(TAG, "Permission denied: checkComponentPermission() reqUid=" + reqUid); + // If there is a uid that owns whatever is being accessed, it has + // blanket access to it regardless of the permissions it requires. + if (owningUid >= 0 && uid == owningUid) { + return PackageManager.PERMISSION_GRANTED; + } + // If the target is not exported, then nobody else can get to it. + if (!exported) { + Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid); return PackageManager.PERMISSION_DENIED; } if (permission == null) { @@ -4118,7 +4204,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (permission == null) { return PackageManager.PERMISSION_DENIED; } - return checkComponentPermission(permission, pid, uid, -1); + return checkComponentPermission(permission, pid, uid, -1, true); } /** @@ -4268,8 +4354,10 @@ public final class ActivityManagerService extends ActivityManagerNative return -1; } - if (DEBUG_URI_PERMISSION) Slog.v(TAG, - "Checking grant " + targetPkg + " permission to " + uri); + if (targetPkg != null) { + if (DEBUG_URI_PERMISSION) Slog.v(TAG, + "Checking grant " + targetPkg + " permission to " + uri); + } final IPackageManager pm = AppGlobals.getPackageManager(); @@ -4298,23 +4386,45 @@ public final class ActivityManagerService extends ActivityManagerNative } int targetUid; - try { - targetUid = pm.getPackageUid(targetPkg); - if (targetUid < 0) { - if (DEBUG_URI_PERMISSION) Slog.v(TAG, - "Can't grant URI permission no uid for: " + targetPkg); + if (targetPkg != null) { + try { + targetUid = pm.getPackageUid(targetPkg); + if (targetUid < 0) { + if (DEBUG_URI_PERMISSION) Slog.v(TAG, + "Can't grant URI permission no uid for: " + targetPkg); + return -1; + } + } catch (RemoteException ex) { return -1; } - } catch (RemoteException ex) { - return -1; + } else { + targetUid = -1; } - // First... does the target actually need this permission? - if (checkHoldingPermissionsLocked(pm, pi, uri, targetUid, modeFlags)) { - // No need to grant the target this permission. - if (DEBUG_URI_PERMISSION) Slog.v(TAG, - "Target " + targetPkg + " already has full permission to " + uri); - return -1; + if (targetUid >= 0) { + // First... does the target actually need this permission? + if (checkHoldingPermissionsLocked(pm, pi, uri, targetUid, modeFlags)) { + // No need to grant the target this permission. + if (DEBUG_URI_PERMISSION) Slog.v(TAG, + "Target " + targetPkg + " already has full permission to " + uri); + return -1; + } + } else { + // First... there is no target package, so can anyone access it? + boolean allowed = pi.exported; + if ((modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) { + if (pi.readPermission != null) { + allowed = false; + } + } + if ((modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) { + if (pi.writePermission != null) { + allowed = false; + } + } + if (allowed) { + return -1; + } } // Second... is the provider allowing granting of URI permissions? @@ -4344,16 +4454,25 @@ public final class ActivityManagerService extends ActivityManagerNative // Third... does the caller itself have permission to access // this uri? - if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) { - if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) { - throw new SecurityException("Uid " + callingUid - + " does not have permission to uri " + uri); + if (callingUid != Process.myUid()) { + if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) { + if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) { + throw new SecurityException("Uid " + callingUid + + " does not have permission to uri " + uri); + } } } return targetUid; } + public int checkGrantUriPermission(int callingUid, String targetPkg, + Uri uri, int modeFlags) { + synchronized(this) { + return checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags); + } + } + void grantUriPermissionUncheckedLocked(int targetUid, String targetPkg, Uri uri, int modeFlags, UriPermissionOwner owner) { modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION @@ -4399,6 +4518,10 @@ public final class ActivityManagerService extends ActivityManagerNative void grantUriPermissionLocked(int callingUid, String targetPkg, Uri uri, int modeFlags, UriPermissionOwner owner) { + if (targetPkg == null) { + throw new NullPointerException("targetPkg"); + } + int targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags); if (targetUid < 0) { return; @@ -4417,6 +4540,10 @@ public final class ActivityManagerService extends ActivityManagerNative + " from " + intent + "; flags=0x" + Integer.toHexString(intent != null ? intent.getFlags() : 0)); + if (targetPkg == null) { + throw new NullPointerException("targetPkg"); + } + if (intent == null) { return -1; } @@ -4718,6 +4845,11 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException(msg); } + final boolean canReadFb = (flags&ActivityManager.TASKS_GET_THUMBNAILS) != 0 + && checkCallingPermission( + android.Manifest.permission.READ_FRAME_BUFFER) + == PackageManager.PERMISSION_GRANTED; + int pos = mMainStack.mHistory.size()-1; ActivityRecord next = pos >= 0 ? (ActivityRecord)mMainStack.mHistory.get(pos) : null; @@ -4762,7 +4894,13 @@ public final class ActivityManagerService extends ActivityManagerNative ci.id = curTask.taskId; ci.baseActivity = r.intent.getComponent(); ci.topActivity = top.intent.getComponent(); - ci.thumbnail = top.thumbnail; + if (canReadFb) { + if (top.thumbnail != null) { + ci.thumbnail = top.thumbnail; + } else if (top.state == ActivityState.RESUMED) { + ci.thumbnail = top.stack.screenshotActivities(top); + } + } ci.description = topDescription; ci.numActivities = numActivities; ci.numRunning = numRunning; @@ -4831,8 +4969,15 @@ public final class ActivityManagerService extends ActivityManagerNative enforceCallingPermission(android.Manifest.permission.GET_TASKS, "getRecentTasks()"); + final boolean canReadFb = (flags&ActivityManager.TASKS_GET_THUMBNAILS) != 0 + && checkCallingPermission( + android.Manifest.permission.READ_FRAME_BUFFER) + == PackageManager.PERMISSION_GRANTED; + IPackageManager pm = AppGlobals.getPackageManager(); + ActivityRecord resumed = mMainStack.mResumedActivity; + final int N = mRecentTasks.size(); ArrayList<ActivityManager.RecentTaskInfo> res = new ArrayList<ActivityManager.RecentTaskInfo>( @@ -4850,6 +4995,15 @@ public final class ActivityManagerService extends ActivityManagerNative tr.intent != null ? tr.intent : tr.affinityIntent); rti.origActivity = tr.origActivity; + if (canReadFb) { + if (resumed != null && resumed.task == tr) { + rti.thumbnail = resumed.stack.screenshotActivities(resumed); + } else { + rti.thumbnail = tr.lastThumbnail; + } + } + rti.description = tr.lastDescription; + if ((flags&ActivityManager.RECENT_IGNORE_UNAVAILABLE) != 0) { // Check whether this activity is currently available. try { @@ -4916,7 +5070,7 @@ public final class ActivityManagerService extends ActivityManagerNative /** * TODO: Add mController hook */ - public void moveTaskToFront(int task) { + public void moveTaskToFront(int task, int flags) { enforceCallingPermission(android.Manifest.permission.REORDER_TASKS, "moveTaskToFront()"); @@ -4931,6 +5085,11 @@ public final class ActivityManagerService extends ActivityManagerNative for (int i=0; i<N; i++) { TaskRecord tr = mRecentTasks.get(i); if (tr.taskId == task) { + if ((flags&ActivityManager.MOVE_TASK_WITH_HOME) != 0) { + // Caller wants the home activity moved with it. To accomplish this, + // we'll just move the home task to the top first. + mMainStack.moveHomeToFrontLocked(); + } mMainStack.moveTaskToFrontLocked(tr, null); return; } @@ -4938,6 +5097,11 @@ public final class ActivityManagerService extends ActivityManagerNative for (int i=mMainStack.mHistory.size()-1; i>=0; i--) { ActivityRecord hr = (ActivityRecord)mMainStack.mHistory.get(i); if (hr.task.taskId == task) { + if ((flags&ActivityManager.MOVE_TASK_WITH_HOME) != 0) { + // Caller wants the home activity moved with it. To accomplish this, + // we'll just move the home task to the top first. + mMainStack.moveHomeToFrontLocked(); + } mMainStack.moveTaskToFrontLocked(hr.task, null); return; } @@ -5166,12 +5330,12 @@ public final class ActivityManagerService extends ActivityManagerNative final int callingPid = (r != null) ? r.pid : Binder.getCallingPid(); final int callingUid = (r != null) ? r.info.uid : Binder.getCallingUid(); if (checkComponentPermission(cpi.readPermission, callingPid, callingUid, - cpi.exported ? -1 : cpi.applicationInfo.uid) + cpi.applicationInfo.uid, cpi.exported) == PackageManager.PERMISSION_GRANTED) { return null; } if (checkComponentPermission(cpi.writePermission, callingPid, callingUid, - cpi.exported ? -1 : cpi.applicationInfo.uid) + cpi.applicationInfo.uid, cpi.exported) == PackageManager.PERMISSION_GRANTED) { return null; } @@ -5183,12 +5347,12 @@ public final class ActivityManagerService extends ActivityManagerNative i--; PathPermission pp = pps[i]; if (checkComponentPermission(pp.getReadPermission(), callingPid, callingUid, - cpi.exported ? -1 : cpi.applicationInfo.uid) + cpi.applicationInfo.uid, cpi.exported) == PackageManager.PERMISSION_GRANTED) { return null; } if (checkComponentPermission(pp.getWritePermission(), callingPid, callingUid, - cpi.exported ? -1 : cpi.applicationInfo.uid) + cpi.applicationInfo.uid, cpi.exported) == PackageManager.PERMISSION_GRANTED) { return null; } @@ -5204,10 +5368,18 @@ public final class ActivityManagerService extends ActivityManagerNative } } - String msg = "Permission Denial: opening provider " + cpi.name - + " from " + (r != null ? r : "(null)") + " (pid=" + callingPid - + ", uid=" + callingUid + ") requires " - + cpi.readPermission + " or " + cpi.writePermission; + String msg; + if (!cpi.exported) { + msg = "Permission Denial: opening provider " + cpi.name + + " from " + (r != null ? r : "(null)") + " (pid=" + callingPid + + ", uid=" + callingUid + ") that is not exported from uid " + + cpi.applicationInfo.uid; + } else { + msg = "Permission Denial: opening provider " + cpi.name + + " from " + (r != null ? r : "(null)") + " (pid=" + callingPid + + ", uid=" + callingUid + ") requires " + + cpi.readPermission + " or " + cpi.writePermission; + } Slog.w(TAG, msg); return msg; } @@ -5689,16 +5861,18 @@ public final class ActivityManagerService extends ActivityManagerNative return pfd; } + // Actually is sleeping or shutting down or whatever else in the future + // is an inactive state. + public boolean isSleeping() { + return mSleeping || mShuttingDown; + } + public void goingToSleep() { synchronized(this) { mSleeping = true; mWindowManager.setEventDispatching(false); - if (mMainStack.mResumedActivity != null) { - mMainStack.pauseIfSleepingLocked(); - } else { - Slog.w(TAG, "goingToSleep with no resumed activity!"); - } + mMainStack.stopIfSleepingLocked(); // Initialize the wake times of all processes. checkExcessivePowerUsageLocked(false); @@ -5722,7 +5896,7 @@ public final class ActivityManagerService extends ActivityManagerNative mWindowManager.setEventDispatching(false); if (mMainStack.mResumedActivity != null) { - mMainStack.pauseIfSleepingLocked(); + mMainStack.stopIfSleepingLocked(); final long endTime = System.currentTimeMillis() + timeout; while (mMainStack.mResumedActivity != null || mMainStack.mPausingActivity != null) { @@ -5746,13 +5920,30 @@ public final class ActivityManagerService extends ActivityManagerNative return timedout; } + public final void activitySlept(IBinder token) { + if (localLOGV) Slog.v( + TAG, "Activity slept: token=" + token); + + ActivityRecord r = null; + + final long origId = Binder.clearCallingIdentity(); + + synchronized (this) { + int index = mMainStack.indexOfTokenLocked(token); + if (index >= 0) { + r = (ActivityRecord)mMainStack.mHistory.get(index); + mMainStack.activitySleptLocked(r); + } + } + + Binder.restoreCallingIdentity(origId); + } + public void wakingUp() { synchronized(this) { - if (mMainStack.mGoingToSleep.isHeld()) { - mMainStack.mGoingToSleep.release(); - } mWindowManager.setEventDispatching(true); mSleeping = false; + mMainStack.awakeFromSleepingLocked(); mMainStack.resumeTopActivityLocked(null); } } @@ -5797,7 +5988,7 @@ public final class ActivityManagerService extends ActivityManagerNative final int perm = checkComponentPermission( android.Manifest.permission.STOP_APP_SWITCHES, callingPid, - callingUid, -1); + callingUid, -1, true); if (perm == PackageManager.PERMISSION_GRANTED) { return true; } @@ -5881,6 +6072,35 @@ public final class ActivityManagerService extends ActivityManagerNative } } + public void setImmersive(IBinder token, boolean immersive) { + synchronized(this) { + int index = (token != null) ? mMainStack.indexOfTokenLocked(token) : -1; + if (index < 0) { + throw new IllegalArgumentException(); + } + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index); + r.immersive = immersive; + } + } + + public boolean isImmersive(IBinder token) { + synchronized (this) { + int index = (token != null) ? mMainStack.indexOfTokenLocked(token) : -1; + if (index < 0) { + throw new IllegalArgumentException(); + } + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index); + return r.immersive; + } + } + + public boolean isTopActivityImmersive() { + synchronized (this) { + ActivityRecord r = mMainStack.topRunningActivityLocked(null); + return (r != null) ? r.immersive : false; + } + } + public final void enterSafeMode() { synchronized(this) { // It only makes sense to do this before the system is ready @@ -5890,23 +6110,25 @@ public final class ActivityManagerService extends ActivityManagerNative AppGlobals.getPackageManager().enterSafeMode(); } catch (RemoteException e) { } - - View v = LayoutInflater.from(mContext).inflate( - com.android.internal.R.layout.safe_mode, null); - WindowManager.LayoutParams lp = new WindowManager.LayoutParams(); - lp.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY; - lp.width = WindowManager.LayoutParams.WRAP_CONTENT; - lp.height = WindowManager.LayoutParams.WRAP_CONTENT; - lp.gravity = Gravity.BOTTOM | Gravity.LEFT; - lp.format = v.getBackground().getOpacity(); - lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; - ((WindowManager)mContext.getSystemService( - Context.WINDOW_SERVICE)).addView(v, lp); } } } + public final void showSafeModeOverlay() { + View v = LayoutInflater.from(mContext).inflate( + com.android.internal.R.layout.safe_mode, null); + WindowManager.LayoutParams lp = new WindowManager.LayoutParams(); + lp.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY; + lp.width = WindowManager.LayoutParams.WRAP_CONTENT; + lp.height = WindowManager.LayoutParams.WRAP_CONTENT; + lp.gravity = Gravity.BOTTOM | Gravity.LEFT; + lp.format = v.getBackground().getOpacity(); + lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + ((WindowManager)mContext.getSystemService( + Context.WINDOW_SERVICE)).addView(v, lp); + } + public void noteWakeupAlarm(IIntentSender sender) { if (!(sender instanceof PendingIntentRecord)) { return; @@ -6378,7 +6600,6 @@ public final class ActivityManagerService extends ActivityManagerNative + " has crashed too many times: killing!"); EventLog.writeEvent(EventLogTags.AM_PROCESS_CRASHED_TOO_MUCH, app.info.processName, app.info.uid); - killServicesLocked(app, false); for (int i=mMainStack.mHistory.size()-1; i>=0; i--) { ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); if (r.app == app) { @@ -6388,6 +6609,10 @@ public final class ActivityManagerService extends ActivityManagerNative } } if (!app.persistent) { + // Don't let services in this process be restarted and potentially + // annoy the user repeatedly. Unless it is persistent, since those + // processes run critical code. + killServicesLocked(app, false); // We don't want to start this process again until the user // explicitly does so... but for persistent process, we really // need to keep it running. If a persistent process is actually @@ -6411,7 +6636,7 @@ public final class ActivityManagerService extends ActivityManagerNative int index = mMainStack.indexOfTokenLocked(r); r.stack.finishActivityLocked(r, index, Activity.RESULT_CANCELED, null, "crashed"); - // Also terminate an activities below it that aren't yet + // Also terminate any activities below it that aren't yet // stopped, to avoid a situation where one will get // re-start our crashing activity once it gets resumed again. index--; @@ -6420,7 +6645,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (r.state == ActivityState.RESUMED || r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED) { - if (!r.isHomeActivity) { + if (!r.isHomeActivity || mHomeProcess != r.app) { Slog.w(TAG, " Force finishing activity " + r.intent.getComponent().flattenToShortString()); r.stack.finishActivityLocked(r, index, @@ -6528,7 +6753,7 @@ public final class ActivityManagerService extends ActivityManagerNative ProcessRecord r = findAppProcess(app); if ((violationMask & StrictMode.PENALTY_DROPBOX) != 0) { - Integer stackFingerprint = info.crashInfo.stackTrace.hashCode(); + Integer stackFingerprint = info.hashCode(); boolean logIt = true; synchronized (mAlreadyLoggedViolatedStacks) { if (mAlreadyLoggedViolatedStacks.contains(stackFingerprint)) { @@ -6605,9 +6830,23 @@ public final class ActivityManagerService extends ActivityManagerNative if (info.violationNumThisLoop != 0) { sb.append("Loop-Violation-Number: ").append(info.violationNumThisLoop).append("\n"); } - if (info != null && info.durationMillis != -1) { + if (info.numAnimationsRunning != 0) { + sb.append("Animations-Running: ").append(info.numAnimationsRunning).append("\n"); + } + if (info.broadcastIntentAction != null) { + sb.append("Broadcast-Intent-Action: ").append(info.broadcastIntentAction).append("\n"); + } + if (info.durationMillis != -1) { sb.append("Duration-Millis: ").append(info.durationMillis).append("\n"); } + if (info.numInstances != -1) { + sb.append("Instance-Count: ").append(info.numInstances).append("\n"); + } + if (info.tags != null) { + for (String tag : info.tags) { + sb.append("Span-Tag: ").append(tag).append("\n"); + } + } sb.append("\n"); if (info.crashInfo != null && info.crashInfo.stackTrace != null) { sb.append(info.crashInfo.stackTrace); @@ -7172,6 +7411,9 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println(" prov[iders]: content provider state"); pw.println(" s[ervices]: service state"); pw.println(" service [name]: service client-side state"); + pw.println(" cmd may also be a component name (com.foo/.myApp),"); + pw.println(" a partial substring in a component name, or an"); + pw.println(" ActivityRecord hex object identifier."); return; } else { pw.println("Unknown argument: " + opt + "; use -h for help"); @@ -7213,13 +7455,21 @@ public final class ActivityManagerService extends ActivityManagerNative } return; } else if ("service".equals(cmd)) { - dumpService(fd, pw, args, opti, dumpAll); + dumpService(fd, pw, args, opti); return; } else if ("services".equals(cmd) || "s".equals(cmd)) { synchronized (this) { dumpServicesLocked(fd, pw, args, opti, true); } return; + } else { + // Dumping a single activity? + if (dumpActivity(fd, pw, cmd, args, opti, dumpAll)) { + return; + } + pw.println("Bad activity command, or no activities match: " + cmd); + pw.println("Use -h for help."); + return; } } @@ -7292,6 +7542,11 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println(" Activities waiting to stop:"); dumpHistoryList(pw, mMainStack.mStoppingActivities, " ", "Stop", false); } + if (mMainStack.mGoingToSleepActivities.size() > 0) { + pw.println(" "); + pw.println(" Activities waiting to sleep:"); + dumpHistoryList(pw, mMainStack.mGoingToSleepActivities, " ", "Sleep", false); + } if (mMainStack.mFinishingActivities.size() > 0) { pw.println(" "); pw.println(" Activities waiting to finish:"); @@ -7303,6 +7558,7 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println(" mResumedActivity: " + mMainStack.mResumedActivity); pw.println(" mFocusedActivity: " + mFocusedActivity); pw.println(" mLastPausedActivity: " + mMainStack.mLastPausedActivity); + pw.println(" mSleepTimeout: " + mMainStack.mSleepTimeout); if (dumpAll && mRecentTasks.size() > 0) { pw.println(" "); @@ -7566,8 +7822,7 @@ public final class ActivityManagerService extends ActivityManagerNative * - the first arg isn't the flattened component name of an existing service: * dump all services whose component contains the first arg as a substring */ - protected void dumpService(FileDescriptor fd, PrintWriter pw, String[] args, - int opti, boolean dumpAll) { + protected void dumpService(FileDescriptor fd, PrintWriter pw, String[] args, int opti) { String[] newArgs; String componentNameString; ServiceRecord r; @@ -7587,7 +7842,7 @@ public final class ActivityManagerService extends ActivityManagerNative } if (r != null) { - dumpService(fd, pw, r, newArgs, dumpAll); + dumpService(fd, pw, r, newArgs); } else { ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); synchronized (this) { @@ -7599,7 +7854,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } for (int i=0; i<services.size(); i++) { - dumpService(fd, pw, services.get(i), newArgs, dumpAll); + dumpService(fd, pw, services.get(i), newArgs); } } } @@ -7608,16 +7863,10 @@ public final class ActivityManagerService extends ActivityManagerNative * Invokes IApplicationThread.dumpService() on the thread of the specified service if * there is a thread associated with the service. */ - private void dumpService(FileDescriptor fd, PrintWriter pw, ServiceRecord r, String[] args, - boolean dumpAll) { - pw.println(" Service " + r.name.flattenToString()); - if (dumpAll) { - synchronized (this) { - pw.print(" * "); pw.println(r); - r.dump(pw, " "); - } - pw.println(""); - } + private void dumpService(FileDescriptor fd, PrintWriter pw, ServiceRecord r, String[] args) { + pw.println("------------------------------------------------------------" + + "-------------------"); + pw.println("APP SERVICE: " + r.name.flattenToString()); if (r.app != null && r.app.thread != null) { try { // flush anything that is already in the PrintWriter since the thread is going @@ -7632,6 +7881,96 @@ public final class ActivityManagerService extends ActivityManagerNative } } + /** + * There are three things that cmd can be: + * - a flattened component name that matched an existing activity + * - the cmd arg isn't the flattened component name of an existing activity: + * dump all activity whose component contains the cmd as a substring + * - A hex number of the ActivityRecord object instance. + */ + protected boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args, + int opti, boolean dumpAll) { + String[] newArgs; + ComponentName componentName = ComponentName.unflattenFromString(name); + int objectId = 0; + if (componentName == null) { + // Not a '/' separated full component name; maybe an object ID? + try { + objectId = Integer.parseInt(name, 16); + name = null; + componentName = null; + } catch (RuntimeException e) { + } + } + newArgs = new String[args.length - opti]; + if (args.length > 2) System.arraycopy(args, opti, newArgs, 0, args.length - opti); + + ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>(); + synchronized (this) { + for (ActivityRecord r1 : (ArrayList<ActivityRecord>)mMainStack.mHistory) { + if (componentName != null) { + if (r1.intent.getComponent().equals(componentName)) { + activities.add(r1); + } + } else if (name != null) { + if (r1.intent.getComponent().flattenToString().contains(name)) { + activities.add(r1); + } + } else if (System.identityHashCode(r1) == objectId) { + activities.add(r1); + } + } + } + + if (activities.size() <= 0) { + return false; + } + + TaskRecord lastTask = null; + for (int i=activities.size()-1; i>=0; i--) { + ActivityRecord r = (ActivityRecord)activities.get(i); + if (lastTask != r.task) { + lastTask = r.task; + pw.print("* Task "); pw.print(lastTask.affinity); + pw.print(" id="); pw.println(lastTask.taskId); + if (dumpAll) { + lastTask.dump(pw, " "); + } + } + dumpActivity(" ", fd, pw, activities.get(i), newArgs, dumpAll); + } + return true; + } + + /** + * Invokes IApplicationThread.dumpActivity() on the thread of the specified activity if + * there is a thread associated with the activity. + */ + private void dumpActivity(String prefix, FileDescriptor fd, PrintWriter pw, + ActivityRecord r, String[] args, boolean dumpAll) { + synchronized (this) { + pw.print(prefix); pw.print("* Activity "); + pw.print(Integer.toHexString(System.identityHashCode(r))); + pw.print(" "); pw.print(r.shortComponentName); pw.print(" pid="); + if (r.app != null) pw.println(r.app.pid); + else pw.println("(not running)"); + if (dumpAll) { + r.dump(pw, prefix + " "); + } + } + if (r.app != null && r.app.thread != null) { + try { + // flush anything that is already in the PrintWriter since the thread is going + // to write to the file descriptor directly + pw.flush(); + r.app.thread.dumpActivity(fd, r, prefix + " ", args); + pw.flush(); + } catch (RemoteException e) { + pw.println("got a RemoteException while dumping the activity"); + } + } + } + boolean dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll) { boolean needSep = false; @@ -8188,7 +8527,8 @@ public final class ActivityManagerService extends ActivityManagerNative } } - if (sr.crashCount >= 2) { + if (sr.crashCount >= 2 && (sr.serviceInfo.applicationInfo.flags + &ApplicationInfo.FLAG_PERSISTENT) == 0) { Slog.w(TAG, "Service crashed " + sr.crashCount + " times, stopping: " + sr); EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH, @@ -8596,8 +8936,16 @@ public final class ActivityManagerService extends ActivityManagerNative int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); if (checkComponentPermission(r.permission, - callingPid, callingUid, r.exported ? -1 : r.appInfo.uid) + callingPid, callingUid, r.appInfo.uid, r.exported) != PackageManager.PERMISSION_GRANTED) { + if (!r.exported) { + Slog.w(TAG, "Permission Denial: Accessing service " + r.name + + " from pid=" + callingPid + + ", uid=" + callingUid + + " that is not exported from uid " + r.appInfo.uid); + return new ServiceLookupResult(null, "not exported from uid " + + r.appInfo.uid); + } Slog.w(TAG, "Permission Denial: Accessing service " + r.name + " from pid=" + callingPid + ", uid=" + callingUid @@ -8679,11 +9027,19 @@ public final class ActivityManagerService extends ActivityManagerNative } if (r != null) { if (checkComponentPermission(r.permission, - callingPid, callingUid, r.exported ? -1 : r.appInfo.uid) + callingPid, callingUid, r.appInfo.uid, r.exported) != PackageManager.PERMISSION_GRANTED) { + if (!r.exported) { + Slog.w(TAG, "Permission Denial: Accessing service " + r.name + + " from pid=" + callingPid + + ", uid=" + callingUid + + " that is not exported from uid " + r.appInfo.uid); + return new ServiceLookupResult(null, "not exported from uid " + + r.appInfo.uid); + } Slog.w(TAG, "Permission Denial: Accessing service " + r.name - + " from pid=" + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid() + + " from pid=" + callingPid + + ", uid=" + callingUid + " requires " + r.permission); return new ServiceLookupResult(null, r.permission); } @@ -8850,6 +9206,11 @@ public final class ActivityManagerService extends ActivityManagerNative long minDuration = SERVICE_RESTART_DURATION; long resetTime = SERVICE_RESET_RUN_DURATION; + if ((r.serviceInfo.applicationInfo.flags + &ApplicationInfo.FLAG_PERSISTENT) != 0) { + minDuration /= 4; + } + // Any delivered but not yet finished starts should be put back // on the pending list. final int N = r.deliveredStarts.size(); @@ -8889,9 +9250,16 @@ public final class ActivityManagerService extends ActivityManagerNative r.restartCount = 1; r.restartDelay = minDuration; } else { - r.restartDelay *= SERVICE_RESTART_DURATION_FACTOR; - if (r.restartDelay < minDuration) { - r.restartDelay = minDuration; + if ((r.serviceInfo.applicationInfo.flags + &ApplicationInfo.FLAG_PERSISTENT) != 0) { + // Services in peristent processes will restart much more + // quickly, since they are pretty important. (Think SystemUI). + r.restartDelay += minDuration/2; + } else { + r.restartDelay *= SERVICE_RESTART_DURATION_FACTOR; + if (r.restartDelay < minDuration) { + r.restartDelay = minDuration; + } } } } @@ -10171,7 +10539,7 @@ public final class ActivityManagerService extends ActivityManagerNative || uidRemoved) { if (checkComponentPermission( android.Manifest.permission.BROADCAST_PACKAGE_REMOVED, - callingPid, callingUid, -1) + callingPid, callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) { if (uidRemoved) { final Bundle intentExtras = intent.getExtras(); @@ -10230,6 +10598,15 @@ public final class ActivityManagerService extends ActivityManagerNative mHandler.sendEmptyMessage(UPDATE_TIME_ZONE); } + if (intent.ACTION_CLEAR_DNS_CACHE.equals(intent.getAction())) { + mHandler.sendEmptyMessage(CLEAR_DNS_CACHE); + } + + if (Proxy.PROXY_CHANGE_ACTION.equals(intent.getAction())) { + ProxyProperties proxy = intent.getParcelableExtra("proxy"); + mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY, proxy)); + } + /* * Prevent non-system code (defined here to be non-persistent * processes) from sending protected broadcasts. @@ -10830,7 +11207,7 @@ public final class ActivityManagerService extends ActivityManagerNative boolean skip = false; if (filter.requiredPermission != null) { int perm = checkComponentPermission(filter.requiredPermission, - r.callingPid, r.callingUid, -1); + r.callingPid, r.callingUid, -1, true); if (perm != PackageManager.PERMISSION_GRANTED) { Slog.w(TAG, "Permission Denial: broadcasting " + r.intent.toString() @@ -10843,7 +11220,7 @@ public final class ActivityManagerService extends ActivityManagerNative } if (r.requiredPermission != null) { int perm = checkComponentPermission(r.requiredPermission, - filter.receiverList.pid, filter.receiverList.uid, -1); + filter.receiverList.pid, filter.receiverList.uid, -1, true); if (perm != PackageManager.PERMISSION_GRANTED) { Slog.w(TAG, "Permission Denial: receiving " + r.intent.toString() @@ -11109,17 +11486,26 @@ public final class ActivityManagerService extends ActivityManagerNative boolean skip = false; int perm = checkComponentPermission(info.activityInfo.permission, - r.callingPid, r.callingUid, - info.activityInfo.exported - ? -1 : info.activityInfo.applicationInfo.uid); + r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid, + info.activityInfo.exported); if (perm != PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "Permission Denial: broadcasting " - + r.intent.toString() - + " from " + r.callerPackage + " (pid=" + r.callingPid - + ", uid=" + r.callingUid + ")" - + " requires " + info.activityInfo.permission - + " due to receiver " + info.activityInfo.packageName - + "/" + info.activityInfo.name); + if (!info.activityInfo.exported) { + Slog.w(TAG, "Permission Denial: broadcasting " + + r.intent.toString() + + " from " + r.callerPackage + " (pid=" + r.callingPid + + ", uid=" + r.callingUid + ")" + + " is not exported from uid " + info.activityInfo.applicationInfo.uid + + " due to receiver " + info.activityInfo.packageName + + "/" + info.activityInfo.name); + } else { + Slog.w(TAG, "Permission Denial: broadcasting " + + r.intent.toString() + + " from " + r.callerPackage + " (pid=" + r.callingPid + + ", uid=" + r.callingUid + ")" + + " requires " + info.activityInfo.permission + + " due to receiver " + info.activityInfo.packageName + + "/" + info.activityInfo.name); + } skip = true; } if (r.callingUid != Process.SYSTEM_UID && @@ -11464,14 +11850,9 @@ public final class ActivityManagerService extends ActivityManagerNative if (starting != null) { kept = mMainStack.ensureActivityConfigurationLocked(starting, changes); - if (kept) { - // If this didn't result in the starting activity being - // destroyed, then we need to make sure at this point that all - // other activities are made visible. - if (DEBUG_SWITCH) Slog.i(TAG, "Config didn't destroy " + starting - + ", ensuring others are correct."); - mMainStack.ensureActivitiesVisibleLocked(starting, changes); - } + // And we need to make sure at this point that all other activities + // are made visible with the correct configuration. + mMainStack.ensureActivitiesVisibleLocked(starting, changes); } if (values != null && mWindowManager != null) { @@ -11566,28 +11947,6 @@ public final class ActivityManagerService extends ActivityManagerNative adj = FOREGROUND_APP_ADJ; schedGroup = Process.THREAD_GROUP_DEFAULT; app.adjType = "exec-service"; - } else if (app.foregroundServices) { - // The user is aware of this app, so make it visible. - adj = PERCEPTIBLE_APP_ADJ; - schedGroup = Process.THREAD_GROUP_DEFAULT; - app.adjType = "foreground-service"; - } else if (app.forcingToForeground != null) { - // The user is aware of this app, so make it visible. - adj = PERCEPTIBLE_APP_ADJ; - schedGroup = Process.THREAD_GROUP_DEFAULT; - app.adjType = "force-foreground"; - app.adjSource = app.forcingToForeground; - } else if (app == mHeavyWeightProcess) { - // We don't want to kill the current heavy-weight process. - adj = HEAVY_WEIGHT_APP_ADJ; - schedGroup = Process.THREAD_GROUP_DEFAULT; - app.adjType = "heavy"; - } else if (app == mHomeProcess) { - // This process is hosting what we currently consider to be the - // home app, so we don't want to let it go into the background. - adj = HOME_APP_ADJ; - schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE; - app.adjType = "home"; } else if ((N=app.activities.size()) != 0) { // This app is in the background with paused activities. app.hidden = true; @@ -11596,13 +11955,19 @@ public final class ActivityManagerService extends ActivityManagerNative app.adjType = "bg-activities"; N = app.activities.size(); for (int j=0; j<N; j++) { - if (app.activities.get(j).visible) { + ActivityRecord r = app.activities.get(j); + if (r.visible) { // This app has a visible activity! app.hidden = false; adj = VISIBLE_APP_ADJ; schedGroup = Process.THREAD_GROUP_DEFAULT; app.adjType = "visible"; break; + } else if (r.state == ActivityState.PAUSING + || r.state == ActivityState.PAUSED + || r.state == ActivityState.STOPPING) { + adj = PERCEPTIBLE_APP_ADJ; + app.adjType = "stopping"; } } } else { @@ -11614,7 +11979,37 @@ public final class ActivityManagerService extends ActivityManagerNative adj = hiddenAdj; app.adjType = "bg-empty"; } + + if (adj > PERCEPTIBLE_APP_ADJ) { + if (app.foregroundServices) { + // The user is aware of this app, so make it visible. + adj = PERCEPTIBLE_APP_ADJ; + schedGroup = Process.THREAD_GROUP_DEFAULT; + app.adjType = "foreground-service"; + } else if (app.forcingToForeground != null) { + // The user is aware of this app, so make it visible. + adj = PERCEPTIBLE_APP_ADJ; + schedGroup = Process.THREAD_GROUP_DEFAULT; + app.adjType = "force-foreground"; + app.adjSource = app.forcingToForeground; + } + } + + if (adj > HEAVY_WEIGHT_APP_ADJ && app == mHeavyWeightProcess) { + // We don't want to kill the current heavy-weight process. + adj = HEAVY_WEIGHT_APP_ADJ; + schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE; + app.adjType = "heavy"; + } + if (adj > HOME_APP_ADJ && app == mHomeProcess) { + // This process is hosting what we currently consider to be the + // home app, so we don't want to let it go into the background. + adj = HOME_APP_ADJ; + schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE; + app.adjType = "home"; + } + //Slog.i(TAG, "OOM " + app + ": initial adj=" + adj); // By default, we use the computed adjustment. It may be changed if @@ -12505,7 +12900,69 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - + + public boolean dumpHeap(String process, boolean managed, + String path, ParcelFileDescriptor fd) throws RemoteException { + + try { + synchronized (this) { + // note: hijacking SET_ACTIVITY_WATCHER, but should be changed to + // its own permission (same as profileControl). + if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires permission " + + android.Manifest.permission.SET_ACTIVITY_WATCHER); + } + + if (fd == null) { + throw new IllegalArgumentException("null fd"); + } + + ProcessRecord proc = null; + try { + int pid = Integer.parseInt(process); + synchronized (mPidsSelfLocked) { + proc = mPidsSelfLocked.get(pid); + } + } catch (NumberFormatException e) { + } + + if (proc == null) { + HashMap<String, SparseArray<ProcessRecord>> all + = mProcessNames.getMap(); + SparseArray<ProcessRecord> procs = all.get(process); + if (procs != null && procs.size() > 0) { + proc = procs.valueAt(0); + } + } + + if (proc == null || proc.thread == null) { + throw new IllegalArgumentException("Unknown process: " + process); + } + + boolean isSecure = "1".equals(SystemProperties.get(SYSTEM_SECURE, "0")); + if (isSecure) { + if ((proc.info.flags&ApplicationInfo.FLAG_DEBUGGABLE) == 0) { + throw new SecurityException("Process not debuggable: " + proc); + } + } + + proc.thread.dumpHeap(managed, path, fd); + fd = null; + return true; + } + } catch (RemoteException e) { + throw new IllegalStateException("Process disappeared"); + } finally { + if (fd != null) { + try { + fd.close(); + } catch (IOException e) { + } + } + } + } + /** In this method we try to acquire our lock to make sure that we have not deadlocked */ public void monitor() { synchronized (this) { } diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java index 4d773e4..0fb30ff 100644 --- a/services/java/com/android/server/am/ActivityRecord.java +++ b/services/java/com/android/server/am/ActivityRecord.java @@ -26,6 +26,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.graphics.Bitmap; +import android.os.Build; import android.os.Bundle; import android.os.Message; import android.os.Process; @@ -36,6 +37,7 @@ import android.util.Log; import android.util.Slog; import android.util.TimeUtils; import android.view.IApplicationToken; +import android.view.WindowManager; import java.io.PrintWriter; import java.lang.ref.WeakReference; @@ -58,7 +60,8 @@ class ActivityRecord extends IApplicationToken.Stub { final String processName; // process where this component wants to run final String taskAffinity; // as per ActivityInfo.taskAffinity final boolean stateNotNeeded; // As per ActivityInfo.flags - final boolean fullscreen; // covers the full screen? + final boolean fullscreen; // covers the full screen? + final boolean noDisplay; // activity is not displayed? final boolean componentSpecified; // did caller specifiy an explicit component? final boolean isHomeActivity; // do we consider this to be a home activity? final String baseDir; // where activity source (resources etc) located @@ -68,6 +71,8 @@ class ActivityRecord extends IApplicationToken.Stub { int labelRes; // the label information from the package mgr. int icon; // resource identifier of activity's icon. int theme; // resource identifier of activity's theme. + int realTheme; // actual theme resource we will use, never 0. + int windowFlags; // custom window flags for preview window. TaskRecord task; // the task this is in. long launchTime; // when we starting launching this activity long startTime; // last time this activity was started @@ -98,12 +103,14 @@ class ActivityRecord extends IApplicationToken.Stub { boolean inHistory; // are we in the history stack? int launchMode; // the launch mode activity attribute. boolean visible; // does this activity's window need to be shown? + boolean sleeping; // have we told the activity to sleep? boolean waitingVisible; // true if waiting for a new act to become vis boolean nowVisible; // is this activity's window visible? boolean thumbnailNeeded;// has someone requested a thumbnail? boolean idle; // has the activity gone idle? boolean hasBeenLaunched;// has this activity ever been launched? boolean frozenBeforeDestroy;// has been frozen but not yet destroyed. + boolean immersive; // immersive mode (don't interrupt if possible) String stringName; // for caching of toString(). @@ -159,12 +166,15 @@ class ActivityRecord extends IApplicationToken.Stub { pw.print(" finishing="); pw.println(finishing); pw.print(prefix); pw.print("keysPaused="); pw.print(keysPaused); pw.print(" inHistory="); pw.print(inHistory); - pw.print(" launchMode="); pw.println(launchMode); - pw.print(prefix); pw.print("fullscreen="); pw.print(fullscreen); pw.print(" visible="); pw.print(visible); - pw.print(" frozenBeforeDestroy="); pw.print(frozenBeforeDestroy); - pw.print(" thumbnailNeeded="); pw.print(thumbnailNeeded); + pw.print(" sleeping="); pw.print(sleeping); pw.print(" idle="); pw.println(idle); + pw.print(prefix); pw.print("fullscreen="); pw.print(fullscreen); + pw.print(" noDisplay="); pw.print(noDisplay); + pw.print(" immersive="); pw.print(immersive); + pw.print(" launchMode="); pw.println(launchMode); + pw.print(prefix); pw.print("frozenBeforeDestroy="); pw.print(frozenBeforeDestroy); + pw.print(" thumbnailNeeded="); pw.println(thumbnailNeeded); if (launchTime != 0 || startTime != 0) { pw.print(prefix); pw.print("launchTime="); TimeUtils.formatDuration(launchTime, pw); pw.print(" startTime="); @@ -242,6 +252,16 @@ class ActivityRecord extends IApplicationToken.Stub { } icon = aInfo.getIconResource(); theme = aInfo.getThemeResource(); + realTheme = theme; + if (realTheme == 0) { + realTheme = aInfo.applicationInfo.targetSdkVersion + < Build.VERSION_CODES.HONEYCOMB + ? android.R.style.Theme + : android.R.style.Theme_Holo; + } + if ((aInfo.flags&ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0) { + windowFlags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; + } if ((aInfo.flags&ActivityInfo.FLAG_MULTIPROCESS) != 0 && _caller != null && (aInfo.applicationInfo.uid == Process.SYSTEM_UID @@ -259,12 +279,13 @@ class ActivityRecord extends IApplicationToken.Stub { launchMode = aInfo.launchMode; AttributeCache.Entry ent = AttributeCache.instance().get(packageName, - theme != 0 ? theme : android.R.style.Theme, - com.android.internal.R.styleable.Window); + realTheme, com.android.internal.R.styleable.Window); fullscreen = ent != null && !ent.array.getBoolean( com.android.internal.R.styleable.Window_windowIsFloating, false) && !ent.array.getBoolean( com.android.internal.R.styleable.Window_windowIsTranslucent, false); + noDisplay = ent != null && ent.array.getBoolean( + com.android.internal.R.styleable.Window_windowNoDisplay, false); if (!_componentSpecified || _launchedFromUid == Process.myUid() || _launchedFromUid == 0) { @@ -289,6 +310,8 @@ class ActivityRecord extends IApplicationToken.Stub { } else { isHomeActivity = false; } + + immersive = (aInfo.flags & ActivityInfo.FLAG_IMMERSIVE) != 0; } else { realActivity = null; taskAffinity = null; @@ -299,7 +322,18 @@ class ActivityRecord extends IApplicationToken.Stub { processName = null; packageName = null; fullscreen = true; + noDisplay = false; isHomeActivity = false; + immersive = false; + } + } + + void makeFinishing() { + if (!finishing) { + finishing = true; + if (task != null) { + task.numActivities--; + } } } @@ -403,7 +437,7 @@ class ActivityRecord extends IApplicationToken.Stub { // an application, and that application is not blocked or unresponding. // In any other case, we can't count on getting the screen unfrozen, // so it is best to leave as-is. - return app == null || (!app.crashing && !app.notResponding); + return app != null && !app.crashing && !app.notResponding; } public void startFreezingScreenLocked(ProcessRecord app, int configChanges) { @@ -570,14 +604,32 @@ class ActivityRecord extends IApplicationToken.Stub { public boolean isInterestingToUserLocked() { return visible || nowVisible || state == ActivityState.PAUSING || state == ActivityState.RESUMED; - } + } + + public void setSleeping(boolean _sleeping) { + if (sleeping == _sleeping) { + return; + } + if (app != null && app.thread != null) { + try { + app.thread.scheduleSleeping(this, _sleeping); + if (sleeping && !stack.mGoingToSleepActivities.contains(this)) { + stack.mGoingToSleepActivities.add(this); + } + sleeping = _sleeping; + } catch (RemoteException e) { + Slog.w(ActivityStack.TAG, "Exception thrown when sleeping: " + + intent.getComponent(), e); + } + } + } public String toString() { if (stringName != null) { return stringName; } StringBuilder sb = new StringBuilder(128); - sb.append("HistoryRecord{"); + sb.append("ActivityRecord{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(' '); sb.append(intent.getComponent().flattenToShortString()); diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index 463493b..5b44d39 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -46,9 +46,10 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Bitmap; import android.net.Uri; import android.os.Binder; -import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; @@ -91,6 +92,9 @@ public class ActivityStack { // next activity. static final int PAUSE_TIMEOUT = 500; + // How long we can hold the sleep wake lock before giving up. + static final int SLEEP_TIMEOUT = 5*1000; + // How long we can hold the launch wake lock before giving up. static final int LAUNCH_TIMEOUT = 10*1000; @@ -99,8 +103,8 @@ public class ActivityStack { static final int DESTROY_TIMEOUT = 10*1000; // How long until we reset a task when the user returns to it. Currently - // 30 minutes. - static final long ACTIVITY_INACTIVE_RESET_TIME = 1000*60*30; + // disabled. + static final long ACTIVITY_INACTIVE_RESET_TIME = 0; // How long between activity launches that we consider safe to not warn // the user about an unexpected activity being launched on top. @@ -157,6 +161,12 @@ public class ActivityStack { = new ArrayList<ActivityRecord>(); /** + * List of activities that are in the process of going to sleep. + */ + final ArrayList<ActivityRecord> mGoingToSleepActivities + = new ArrayList<ActivityRecord>(); + + /** * Animations that for the current transition have requested not to * be considered for the transition animation. */ @@ -237,6 +247,15 @@ public class ActivityStack { long mInitialStartTime = 0; + /** + * Set when we have taken too long waiting to go to sleep. + */ + boolean mSleepTimeout = false; + + int mThumbnailWidth = -1; + int mThumbnailHeight = -1; + + static final int SLEEP_TIMEOUT_MSG = 8; static final int PAUSE_TIMEOUT_MSG = 9; static final int IDLE_TIMEOUT_MSG = 10; static final int IDLE_NOW_MSG = 11; @@ -251,12 +270,19 @@ public class ActivityStack { public void handleMessage(Message msg) { switch (msg.what) { + case SLEEP_TIMEOUT_MSG: { + if (mService.isSleeping()) { + Slog.w(TAG, "Sleep timeout! Sleeping now."); + mSleepTimeout = true; + checkReadyForSleepLocked(); + } + } break; case PAUSE_TIMEOUT_MSG: { IBinder token = (IBinder)msg.obj; // We don't at this point know if the activity is fullscreen, // so we need to be conservative and assume it isn't. Slog.w(TAG, "Activity pause timeout for " + token); - activityPaused(token, null, true); + activityPaused(token, true); } break; case IDLE_TIMEOUT_MSG: { if (mService.mDidDexOpt) { @@ -510,6 +536,7 @@ public class ActivityStack { mService.mHomeProcess = app; } mService.ensurePackageDexOpt(r.intent.getComponent().getPackageName()); + r.sleeping = false; app.thread.scheduleLaunchActivity(new Intent(r.intent), r, System.identityHashCode(r), r.info, r.icicle, results, newIntents, !andResume, @@ -564,13 +591,14 @@ public class ActivityStack { // As part of the process of launching, ActivityThread also performs // a resume. r.state = ActivityState.RESUMED; - r.icicle = null; - r.haveState = false; r.stopped = false; mResumedActivity = r; r.task.touchActiveTime(); + if (mMainStack) { + mService.addRecentTaskLocked(r.task); + } completeResumeLocked(r); - pauseIfSleepingLocked(); + checkReadyForSleepLocked(); } else { // This activity is not starting in the resumed state... which // should look like we asked it to pause+stop (but remain visible), @@ -580,6 +608,9 @@ public class ActivityStack { r.stopped = true; } + r.icicle = null; + r.haveState = false; + // Launch the new version setup screen if needed. We do this -after- // launching the initial activity (that is, home), so that it can have // a chance to initialize itself while in the background, making the @@ -623,8 +654,8 @@ public class ActivityStack { "activity", r.intent.getComponent(), false); } - void pauseIfSleepingLocked() { - if (mService.mSleeping || mService.mShuttingDown) { + void stopIfSleepingLocked() { + if (mService.isSleeping()) { if (!mGoingToSleep.isHeld()) { mGoingToSleep.acquire(); if (mLaunchingActivity.isHeld()) { @@ -632,18 +663,113 @@ public class ActivityStack { mService.mHandler.removeMessages(LAUNCH_TIMEOUT_MSG); } } + mHandler.removeMessages(SLEEP_TIMEOUT_MSG); + Message msg = mHandler.obtainMessage(SLEEP_TIMEOUT_MSG); + mHandler.sendMessageDelayed(msg, SLEEP_TIMEOUT); + checkReadyForSleepLocked(); + } + } - // If we are not currently pausing an activity, get the current - // one to pause. If we are pausing one, we will just let that stuff - // run and release the wake lock when all done. - if (mPausingActivity == null) { - if (DEBUG_PAUSE) Slog.v(TAG, "Sleep needs to pause..."); + void awakeFromSleepingLocked() { + mHandler.removeMessages(SLEEP_TIMEOUT_MSG); + mSleepTimeout = false; + if (mGoingToSleep.isHeld()) { + mGoingToSleep.release(); + } + // Ensure activities are no longer sleeping. + for (int i=mHistory.size()-1; i>=0; i--) { + ActivityRecord r = (ActivityRecord)mHistory.get(i); + r.setSleeping(false); + } + mGoingToSleepActivities.clear(); + } + + void activitySleptLocked(ActivityRecord r) { + mGoingToSleepActivities.remove(r); + checkReadyForSleepLocked(); + } + + void checkReadyForSleepLocked() { + if (!mService.isSleeping()) { + // Do not care. + return; + } + + if (!mSleepTimeout) { + if (mResumedActivity != null) { + // Still have something resumed; can't sleep until it is paused. + if (DEBUG_PAUSE) Slog.v(TAG, "Sleep needs to pause " + mResumedActivity); if (DEBUG_USER_LEAVING) Slog.v(TAG, "Sleep => pause with userLeaving=false"); startPausingLocked(false, true); + return; + } + if (mPausingActivity != null) { + // Still waiting for something to pause; can't sleep yet. + if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still waiting to pause " + mPausingActivity); + return; + } + + if (mStoppingActivities.size() > 0) { + // Still need to tell some activities to stop; can't sleep yet. + if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still need to stop " + + mStoppingActivities.size() + " activities"); + Message msg = Message.obtain(); + msg.what = IDLE_NOW_MSG; + mHandler.sendMessage(msg); + return; } + + ensureActivitiesVisibleLocked(null, 0); + + // Make sure any stopped but visible activities are now sleeping. + // This ensures that the activity's onStop() is called. + for (int i=mHistory.size()-1; i>=0; i--) { + ActivityRecord r = (ActivityRecord)mHistory.get(i); + if (r.state == ActivityState.STOPPING || r.state == ActivityState.STOPPED) { + r.setSleeping(true); + } + } + + if (mGoingToSleepActivities.size() > 0) { + // Still need to tell some activities to sleep; can't sleep yet. + if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still need to sleep " + + mGoingToSleepActivities.size() + " activities"); + return; + } + } + + mHandler.removeMessages(SLEEP_TIMEOUT_MSG); + + if (mGoingToSleep.isHeld()) { + mGoingToSleep.release(); + } + if (mService.mShuttingDown) { + mService.notifyAll(); } + } + public final Bitmap screenshotActivities(ActivityRecord who) { + if (who.noDisplay) { + return null; + } + + Resources res = mService.mContext.getResources(); + int w = mThumbnailWidth; + int h = mThumbnailHeight; + if (w < 0) { + mThumbnailWidth = w = + res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width); + mThumbnailHeight = h = + res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height); + } + + if (w > 0) { + return mService.mWindowManager.screenshotApplications(who, w, h); + } + return null; + } + private final void startPausingLocked(boolean userLeaving, boolean uiSleeping) { if (mPausingActivity != null) { RuntimeException e = new RuntimeException(); @@ -663,6 +789,10 @@ public class ActivityStack { mLastPausedActivity = prev; prev.state = ActivityState.PAUSING; prev.task.touchActiveTime(); + prev.thumbnail = screenshotActivities(prev); + if (prev.task != null) { + prev.task.lastThumbnail = prev.thumbnail; + } mService.updateCpuStats(); @@ -726,10 +856,9 @@ public class ActivityStack { } } - final void activityPaused(IBinder token, Bundle icicle, boolean timeout) { + final void activityPaused(IBinder token, boolean timeout) { if (DEBUG_PAUSE) Slog.v( - TAG, "Activity paused: token=" + token + ", icicle=" + icicle - + ", timeout=" + timeout); + TAG, "Activity paused: token=" + token + ", timeout=" + timeout); ActivityRecord r = null; @@ -737,10 +866,6 @@ public class ActivityStack { int index = indexOfTokenLocked(token); if (index >= 0) { r = (ActivityRecord)mHistory.get(index); - if (!timeout) { - r.icicle = icicle; - r.haveState = true; - } mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); if (mPausingActivity == r) { r.state = ActivityState.PAUSED; @@ -789,6 +914,8 @@ public class ActivityStack { Message msg = Message.obtain(); msg.what = IDLE_NOW_MSG; mHandler.sendMessage(msg); + } else { + checkReadyForSleepLocked(); } } } else { @@ -798,15 +925,10 @@ public class ActivityStack { mPausingActivity = null; } - if (!mService.mSleeping && !mService.mShuttingDown) { + if (!mService.isSleeping()) { resumeTopActivityLocked(prev); } else { - if (mGoingToSleep.isHeld()) { - mGoingToSleep.release(); - } - if (mService.mShuttingDown) { - mService.notifyAll(); - } + checkReadyForSleepLocked(); } if (prev != null) { @@ -961,6 +1083,7 @@ public class ActivityStack { TAG, "Making visible and scheduling visibility: " + r); try { mService.mWindowManager.setAppVisibility(r, true); + r.sleeping = false; r.app.thread.scheduleWindowVisibility(r, true); r.stopFreezingScreenLocked(false); } catch (Exception e) { @@ -1090,6 +1213,8 @@ public class ActivityStack { // The activity may be waiting for stop, but that is no longer // appropriate for it. mStoppingActivities.remove(next); + mGoingToSleepActivities.remove(next); + next.sleeping = false; mWaitingVisibleActivities.remove(next); if (DEBUG_SWITCH) Slog.v(TAG, "Resuming " + next); @@ -1176,11 +1301,12 @@ public class ActivityStack { if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare close transition: prev=" + prev); if (mNoAnimActivities.contains(prev)) { - mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + mService.mWindowManager.prepareAppTransition( + WindowManagerPolicy.TRANSIT_NONE, false); } else { mService.mWindowManager.prepareAppTransition(prev.task == next.task ? WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE - : WindowManagerPolicy.TRANSIT_TASK_CLOSE); + : WindowManagerPolicy.TRANSIT_TASK_CLOSE, false); } mService.mWindowManager.setAppWillBeHidden(prev); mService.mWindowManager.setAppVisibility(prev, false); @@ -1188,11 +1314,12 @@ public class ActivityStack { if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare open transition: prev=" + prev); if (mNoAnimActivities.contains(next)) { - mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + mService.mWindowManager.prepareAppTransition( + WindowManagerPolicy.TRANSIT_NONE, false); } else { mService.mWindowManager.prepareAppTransition(prev.task == next.task ? WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN - : WindowManagerPolicy.TRANSIT_TASK_OPEN); + : WindowManagerPolicy.TRANSIT_TASK_OPEN, false); } } if (false) { @@ -1203,9 +1330,11 @@ public class ActivityStack { if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare open transition: no previous"); if (mNoAnimActivities.contains(next)) { - mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + mService.mWindowManager.prepareAppTransition( + WindowManagerPolicy.TRANSIT_NONE, false); } else { - mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN); + mService.mWindowManager.prepareAppTransition( + WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN, false); } } @@ -1223,6 +1352,9 @@ public class ActivityStack { next.state = ActivityState.RESUMED; mResumedActivity = next; next.task.touchActiveTime(); + if (mMainStack) { + mService.addRecentTaskLocked(next.task); + } mService.updateLruProcessLocked(next.app, true, true); updateLRUListLocked(next); @@ -1284,10 +1416,11 @@ public class ActivityStack { System.identityHashCode(next), next.task.taskId, next.shortComponentName); + next.sleeping = false; next.app.thread.scheduleResumeActivity(next, mService.isNextTransitionForward()); - pauseIfSleepingLocked(); + checkReadyForSleepLocked(); } catch (Exception e) { // Whoops, need to restart this activity! @@ -1301,7 +1434,8 @@ public class ActivityStack { mService.mWindowManager.setAppStartingWindow( next, next.packageName, next.theme, next.nonLocalizedLabel, - next.labelRes, next.icon, null, true); + next.labelRes, next.icon, next.windowFlags, + null, true); } } startSpecificActivityLocked(next, true, false); @@ -1336,7 +1470,8 @@ public class ActivityStack { mService.mWindowManager.setAppStartingWindow( next, next.packageName, next.theme, next.nonLocalizedLabel, - next.labelRes, next.icon, null, true); + next.labelRes, next.icon, next.windowFlags, + null, true); } if (DEBUG_SWITCH) Slog.v(TAG, "Restarting: " + next); } @@ -1347,7 +1482,7 @@ public class ActivityStack { } private final void startActivityLocked(ActivityRecord r, boolean newTask, - boolean doResume) { + boolean doResume, boolean keepCurTransition) { final int NH = mHistory.size(); int addPos = -1; @@ -1418,16 +1553,17 @@ public class ActivityStack { if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare open transition: starting " + r); if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { - mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + mService.mWindowManager.prepareAppTransition( + WindowManagerPolicy.TRANSIT_NONE, keepCurTransition); mNoAnimActivities.add(r); } else if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) { mService.mWindowManager.prepareAppTransition( - WindowManagerPolicy.TRANSIT_TASK_OPEN); + WindowManagerPolicy.TRANSIT_TASK_OPEN, keepCurTransition); mNoAnimActivities.remove(r); } else { mService.mWindowManager.prepareAppTransition(newTask ? WindowManagerPolicy.TRANSIT_TASK_OPEN - : WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN); + : WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN, keepCurTransition); mNoAnimActivities.remove(r); } mService.mWindowManager.addAppToken( @@ -1460,7 +1596,7 @@ public class ActivityStack { } mService.mWindowManager.setAppStartingWindow( r, r.packageName, r.theme, r.nonLocalizedLabel, - r.labelRes, r.icon, prev, showStartingIcon); + r.labelRes, r.icon, r.windowFlags, prev, showStartingIcon); } } else { // If this is the first activity, don't do any fancy animations, @@ -1485,7 +1621,8 @@ public class ActivityStack { ActivityRecord newActivity) { boolean forceReset = (newActivity.info.flags &ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0; - if (taskTop.task.getInactiveDuration() > ACTIVITY_INACTIVE_RESET_TIME) { + if (ACTIVITY_INACTIVE_RESET_TIME > 0 + && taskTop.task.getInactiveDuration() > ACTIVITY_INACTIVE_RESET_TIME) { if ((newActivity.info.flags &ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE) == 0) { forceReset = true; @@ -1571,8 +1708,7 @@ public class ActivityStack { if (mService.mCurTask <= 0) { mService.mCurTask = 1; } - target.task = new TaskRecord(mService.mCurTask, target.info, null, - (target.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0); + target.task = new TaskRecord(mService.mCurTask, target.info, null); target.task.affinityIntent = target.intent; if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target + " out to new task " + target.task); @@ -1609,9 +1745,6 @@ public class ActivityStack { taskTopI = -1; } replyChainEnd = -1; - if (mMainStack) { - mService.addRecentTaskLocked(target.task); - } } else if (forceReset || finishOnTaskLaunch || clearWhenTaskReset) { // If the activity should just be removed -- either @@ -1774,11 +1907,11 @@ public class ActivityStack { * activities on top of it and return the instance. * * @param newR Description of the new activity being started. - * @return Returns the old activity that should be continue to be used, + * @return Returns the old activity that should be continued to be used, * or null if none was found. */ private final ActivityRecord performClearTaskLocked(int taskId, - ActivityRecord newR, int launchFlags, boolean doClear) { + ActivityRecord newR, int launchFlags) { int i = mHistory.size(); // First find the requested task. @@ -1804,17 +1937,18 @@ public class ActivityStack { if (r.realActivity.equals(newR.realActivity)) { // Here it is! Now finish everything in front... ActivityRecord ret = r; - if (doClear) { - while (i < (mHistory.size()-1)) { - i++; - r = (ActivityRecord)mHistory.get(i); - if (r.finishing) { - continue; - } - if (finishActivityLocked(r, i, Activity.RESULT_CANCELED, - null, "clear")) { - i--; - } + while (i < (mHistory.size()-1)) { + i++; + r = (ActivityRecord)mHistory.get(i); + if (r.task.taskId != taskId) { + break; + } + if (r.finishing) { + continue; + } + if (finishActivityLocked(r, i, Activity.RESULT_CANCELED, + null, "clear")) { + i--; } } @@ -1841,6 +1975,51 @@ public class ActivityStack { } /** + * Completely remove all activities associated with an existing task. + */ + private final void performClearTaskLocked(int taskId) { + int i = mHistory.size(); + + // First find the requested task. + while (i > 0) { + i--; + ActivityRecord r = (ActivityRecord)mHistory.get(i); + if (r.task.taskId == taskId) { + i++; + break; + } + } + + // Now clear it. + while (i > 0) { + i--; + ActivityRecord r = (ActivityRecord)mHistory.get(i); + if (r.finishing) { + continue; + } + if (r.task.taskId != taskId) { + // We hit the bottom. Now finish it all... + while (i < (mHistory.size()-1)) { + i++; + r = (ActivityRecord)mHistory.get(i); + if (r.task.taskId != taskId) { + // Whoops hit the end. + return; + } + if (r.finishing) { + continue; + } + if (finishActivityLocked(r, i, Activity.RESULT_CANCELED, + null, "clear")) { + i--; + } + } + return; + } + } + } + + /** * Find the activity in the history stack within the given task. Returns * the index within the history at which it's found, or < 0 if not found. */ @@ -1880,7 +2059,7 @@ public class ActivityStack { int grantedMode, ActivityInfo aInfo, IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid, boolean onlyIfNeeded, - boolean componentSpecified) { + boolean componentSpecified, ActivityRecord[] outActivity) { int err = START_SUCCESS; @@ -1958,17 +2137,25 @@ public class ActivityStack { } final int perm = mService.checkComponentPermission(aInfo.permission, callingPid, - callingUid, aInfo.exported ? -1 : aInfo.applicationInfo.uid); + callingUid, aInfo.applicationInfo.uid, aInfo.exported); if (perm != PackageManager.PERMISSION_GRANTED) { if (resultRecord != null) { sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, Activity.RESULT_CANCELED, null); } - String msg = "Permission Denial: starting " + intent.toString() - + " from " + callerApp + " (pid=" + callingPid - + ", uid=" + callingUid + ")" - + " requires " + aInfo.permission; + String msg; + if (!aInfo.exported) { + msg = "Permission Denial: starting " + intent.toString() + + " from " + callerApp + " (pid=" + callingPid + + ", uid=" + callingUid + ")" + + " not exported from uid " + aInfo.applicationInfo.uid; + } else { + msg = "Permission Denial: starting " + intent.toString() + + " from " + callerApp + " (pid=" + callingPid + + ", uid=" + callingUid + ")" + + " requires " + aInfo.permission; + } Slog.w(TAG, msg); throw new SecurityException(msg); } @@ -2002,6 +2189,9 @@ public class ActivityStack { ActivityRecord r = new ActivityRecord(mService, this, callerApp, callingUid, intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho, requestCode, componentSpecified); + if (outActivity != null) { + outActivity[0] = r; + } if (mMainStack) { if (mResumedActivity == null @@ -2036,6 +2226,16 @@ public class ActivityStack { grantedUriPermissions, grantedMode, onlyIfNeeded, true); } + final void moveHomeToFrontFromLaunchLocked(int launchFlags) { + if ((launchFlags & + (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) + == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) { + // Caller wants to appear on home activity, so before starting + // their own activity we will bring home to the front. + moveHomeToFrontLocked(); + } + } + final int startActivityUncheckedLocked(ActivityRecord r, ActivityRecord sourceRecord, Uri[] grantedUriPermissions, int grantedMode, boolean onlyIfNeeded, boolean doResume) { @@ -2109,6 +2309,7 @@ public class ActivityStack { } boolean addingToTask = false; + TaskRecord reuseTask = null; if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 && (launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0) || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK @@ -2146,6 +2347,7 @@ public class ActivityStack { if (callerAtFront) { // We really do want to push this one into the // user's face, right now. + moveHomeToFrontFromLaunchLocked(launchFlags); moveTaskToFrontLocked(taskTop.task, r); } } @@ -2164,7 +2366,16 @@ public class ActivityStack { } return START_RETURN_INTENT_TO_CALLER; } - if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0 + if ((launchFlags & + (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) + == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) { + // The caller has requested to completely replace any + // existing task with its new activity. Well that should + // not be too hard... + reuseTask = taskTop.task; + performClearTaskLocked(taskTop.task.taskId); + reuseTask.setIntent(r.intent, r.info); + } else if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0 || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { // In this situation we want to remove all activities @@ -2172,7 +2383,7 @@ public class ActivityStack { // cases this means we are resetting the task to its // initial state. ActivityRecord top = performClearTaskLocked( - taskTop.task.taskId, r, launchFlags, true); + taskTop.task.taskId, r, launchFlags); if (top != null) { if (top.frontOfTask) { // Activity aliases may mean we use different @@ -2233,7 +2444,7 @@ public class ActivityStack { // for now we'll just drop it. taskTop.task.setIntent(r.intent, r.info); } - if (!addingToTask) { + if (!addingToTask && reuseTask == null) { // We didn't do anything... but it was needed (a.k.a., client // don't use that intent!) And for paranoia, make // sure we have correctly resumed the top activity. @@ -2292,23 +2503,25 @@ public class ActivityStack { } boolean newTask = false; + boolean keepCurTransition = false; // Should this be considered a new task? if (r.resultTo == null && !addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { - // todo: should do better management of integers. - mService.mCurTask++; - if (mService.mCurTask <= 0) { - mService.mCurTask = 1; + if (reuseTask == null) { + // todo: should do better management of integers. + mService.mCurTask++; + if (mService.mCurTask <= 0) { + mService.mCurTask = 1; + } + r.task = new TaskRecord(mService.mCurTask, r.info, intent); + if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + + " in new task " + r.task); + } else { + r.task = reuseTask; } - r.task = new TaskRecord(mService.mCurTask, r.info, intent, - (r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0); - if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r - + " in new task " + r.task); newTask = true; - if (mMainStack) { - mService.addRecentTaskLocked(r.task); - } + moveHomeToFrontFromLaunchLocked(launchFlags); } else if (sourceRecord != null) { if (!addingToTask && @@ -2317,7 +2530,8 @@ public class ActivityStack { // task, but the caller has asked to clear that task if the // activity is already running. ActivityRecord top = performClearTaskLocked( - sourceRecord.task.taskId, r, launchFlags, true); + sourceRecord.task.taskId, r, launchFlags); + keepCurTransition = true; if (top != null) { logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); top.deliverNewIntentLocked(callingUid, r.intent); @@ -2359,9 +2573,8 @@ public class ActivityStack { ActivityRecord prev = N > 0 ? (ActivityRecord)mHistory.get(N-1) : null; r.task = prev != null - ? prev.task - : new TaskRecord(mService.mCurTask, r.info, intent, - (r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0); + ? prev.task + : new TaskRecord(mService.mCurTask, r.info, intent); if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new guessed " + r.task); } @@ -2380,25 +2593,11 @@ public class ActivityStack { EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.task.taskId); } logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task); - startActivityLocked(r, newTask, doResume); + startActivityLocked(r, newTask, doResume, keepCurTransition); return START_SUCCESS; } - final int startActivityMayWait(IApplicationThread caller, - Intent intent, String resolvedType, Uri[] grantedUriPermissions, - int grantedMode, IBinder resultTo, - String resultWho, int requestCode, boolean onlyIfNeeded, - boolean debug, WaitResult outResult, Configuration config) { - // Refuse possible leaked file descriptors - if (intent != null && intent.hasFileDescriptors()) { - throw new IllegalArgumentException("File descriptors passed in Intent"); - } - - boolean componentSpecified = intent.getComponent() != null; - - // Don't modify the client's object! - intent = new Intent(intent); - + ActivityInfo resolveActivity(Intent intent, String resolvedType, boolean debug) { // Collect information about the target of the Intent. ActivityInfo aInfo; try { @@ -2427,11 +2626,32 @@ public class ActivityStack { } } } + return aInfo; + } + + final int startActivityMayWait(IApplicationThread caller, int callingUid, + Intent intent, String resolvedType, Uri[] grantedUriPermissions, + int grantedMode, IBinder resultTo, + String resultWho, int requestCode, boolean onlyIfNeeded, + boolean debug, WaitResult outResult, Configuration config) { + // Refuse possible leaked file descriptors + if (intent != null && intent.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + + boolean componentSpecified = intent.getComponent() != null; + + // Don't modify the client's object! + intent = new Intent(intent); + + // Collect information about the target of the Intent. + ActivityInfo aInfo = resolveActivity(intent, resolvedType, debug); synchronized (mService) { int callingPid; - int callingUid; - if (caller == null) { + if (callingUid >= 0) { + callingPid = -1; + } else if (caller == null) { callingPid = Binder.getCallingPid(); callingUid = Binder.getCallingUid(); } else { @@ -2470,8 +2690,8 @@ public class ActivityStack { IIntentSender target = mService.getIntentSenderLocked( IActivityManager.INTENT_SENDER_ACTIVITY, "android", - realCallingUid, null, null, 0, intent, - resolvedType, PendingIntent.FLAG_CANCEL_CURRENT + realCallingUid, null, null, 0, new Intent[] { intent }, + new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); Intent newIntent = new Intent(); @@ -2516,7 +2736,7 @@ public class ActivityStack { int res = startActivityLocked(caller, intent, resolvedType, grantedUriPermissions, grantedMode, aInfo, resultTo, resultWho, requestCode, callingPid, callingUid, - onlyIfNeeded, componentSpecified); + onlyIfNeeded, componentSpecified, null); if (mConfigWillChange && mMainStack) { // If the caller also wants to switch to a new configuration, @@ -2567,6 +2787,75 @@ public class ActivityStack { } } + final int startActivities(IApplicationThread caller, int callingUid, + Intent[] intents, String[] resolvedTypes, IBinder resultTo) { + if (intents == null) { + throw new NullPointerException("intents is null"); + } + if (resolvedTypes == null) { + throw new NullPointerException("resolvedTypes is null"); + } + if (intents.length != resolvedTypes.length) { + throw new IllegalArgumentException("intents are length different than resolvedTypes"); + } + + ActivityRecord[] outActivity = new ActivityRecord[1]; + + int callingPid; + if (callingUid >= 0) { + callingPid = -1; + } else if (caller == null) { + callingPid = Binder.getCallingPid(); + callingUid = Binder.getCallingUid(); + } else { + callingPid = callingUid = -1; + } + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mService) { + + for (int i=0; i<intents.length; i++) { + Intent intent = intents[i]; + if (intent == null) { + continue; + } + + // Refuse possible leaked file descriptors + if (intent != null && intent.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + + boolean componentSpecified = intent.getComponent() != null; + + // Don't modify the client's object! + intent = new Intent(intent); + + // Collect information about the target of the Intent. + ActivityInfo aInfo = resolveActivity(intent, resolvedTypes[i], false); + + if (mMainStack && aInfo != null && (aInfo.applicationInfo.flags + & ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { + throw new IllegalArgumentException( + "FLAG_CANT_SAVE_STATE not supported here"); + } + + int res = startActivityLocked(caller, intent, resolvedTypes[i], + null, 0, aInfo, resultTo, null, -1, callingPid, callingUid, + false, componentSpecified, outActivity); + if (res < 0) { + return res; + } + + resultTo = outActivity[0]; + } + } + } finally { + Binder.restoreCallingIdentity(origId); + } + + return IActivityManager.START_SUCCESS; + } + void reportActivityLaunchedLocked(boolean timeout, ActivityRecord r, long thisTime, long totalTime) { for (int i=mWaitingActivityLaunched.size()-1; i>=0; i--) { @@ -2644,6 +2933,9 @@ public class ActivityStack { mService.mWindowManager.setAppVisibility(r, false); } r.app.thread.scheduleStopActivity(r, r.visible, r.configChangeFlags); + if (mService.isSleeping()) { + r.setSleeping(true); + } } catch (Exception e) { // Maybe just ignore exceptions here... if the process // has crashed, our death notification will clean things @@ -2687,7 +2979,7 @@ public class ActivityStack { mService.mWindowManager.setAppVisibility(s, false); } } - if (!s.waitingVisible && remove) { + if ((!s.waitingVisible || mService.isSleeping()) && remove) { if (localLOGV) Slog.v(TAG, "Ready to stop: " + s); if (stops == null) { stops = new ArrayList<ActivityRecord>(); @@ -2891,11 +3183,10 @@ public class ActivityStack { return false; } - r.finishing = true; + r.makeFinishing(); EventLog.writeEvent(EventLogTags.AM_FINISH_ACTIVITY, System.identityHashCode(r), r.task.taskId, r.shortComponentName, reason); - r.task.numActivities--; if (index < (mHistory.size()-1)) { ActivityRecord next = (ActivityRecord)mHistory.get(index+1); if (next.task == r.task) { @@ -2958,7 +3249,7 @@ public class ActivityStack { "Prepare close transition: finishing " + r); mService.mWindowManager.prepareAppTransition(endTask ? WindowManagerPolicy.TRANSIT_TASK_CLOSE - : WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE); + : WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE, false); // Tell window manager to prepare for this one to be removed. mService.mWindowManager.setAppVisibility(r, false); @@ -3011,6 +3302,8 @@ public class ActivityStack { Message msg = Message.obtain(); msg.what = IDLE_NOW_MSG; mHandler.sendMessage(msg); + } else { + checkReadyForSleepLocked(); } } r.state = ActivityState.STOPPING; @@ -3020,6 +3313,7 @@ public class ActivityStack { // make sure the record is cleaned out of other places. mStoppingActivities.remove(r); + mGoingToSleepActivities.remove(r); mWaitingVisibleActivities.remove(r); if (mResumedActivity == r) { mResumedActivity = null; @@ -3096,6 +3390,7 @@ public class ActivityStack { private final void removeActivityFromHistoryLocked(ActivityRecord r) { if (r.state != ActivityState.DESTROYED) { + r.makeFinishing(); mHistory.remove(r); r.inHistory = false; r.state = ActivityState.DESTROYED; @@ -3246,10 +3541,30 @@ public class ActivityStack { void removeHistoryRecordsForAppLocked(ProcessRecord app) { removeHistoryRecordsForAppLocked(mLRUActivities, app); removeHistoryRecordsForAppLocked(mStoppingActivities, app); + removeHistoryRecordsForAppLocked(mGoingToSleepActivities, app); removeHistoryRecordsForAppLocked(mWaitingVisibleActivities, app); removeHistoryRecordsForAppLocked(mFinishingActivities, app); } + /** + * Move the current home activity's task (if one exists) to the front + * of the stack. + */ + final void moveHomeToFrontLocked() { + TaskRecord homeTask = null; + for (int i=mHistory.size()-1; i>=0; i--) { + ActivityRecord hr = (ActivityRecord)mHistory.get(i); + if (hr.isHomeActivity) { + homeTask = hr.task; + break; + } + } + if (homeTask != null) { + moveTaskToFrontLocked(homeTask, null); + } + } + + final void moveTaskToFrontLocked(TaskRecord tr, ActivityRecord reason) { if (DEBUG_SWITCH) Slog.v(TAG, "moveTaskToFront: " + tr); @@ -3274,17 +3589,12 @@ public class ActivityStack { ActivityRecord r = (ActivityRecord)mHistory.get(pos); if (localLOGV) Slog.v( TAG, "At " + pos + " ckp " + r.task + ": " + r); - boolean first = true; if (r.task.taskId == task) { if (localLOGV) Slog.v(TAG, "Removing and adding at " + top); mHistory.remove(pos); mHistory.add(top, r); moved.add(0, r); top--; - if (first && mMainStack) { - mService.addRecentTaskLocked(r.task); - first = false; - } } pos--; } @@ -3293,13 +3603,15 @@ public class ActivityStack { "Prepare to front transition: task=" + tr); if (reason != null && (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { - mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + mService.mWindowManager.prepareAppTransition( + WindowManagerPolicy.TRANSIT_NONE, false); ActivityRecord r = topRunningActivityLocked(null); if (r != null) { mNoAnimActivities.add(r); } } else { - mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_TO_FRONT); + mService.mWindowManager.prepareAppTransition( + WindowManagerPolicy.TRANSIT_TASK_TO_FRONT, false); } mService.mWindowManager.moveAppTokensToTop(moved); @@ -3378,13 +3690,15 @@ public class ActivityStack { if (reason != null && (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { - mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + mService.mWindowManager.prepareAppTransition( + WindowManagerPolicy.TRANSIT_NONE, false); ActivityRecord r = topRunningActivityLocked(null); if (r != null) { mNoAnimActivities.add(r); } } else { - mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_TO_BACK); + mService.mWindowManager.prepareAppTransition( + WindowManagerPolicy.TRANSIT_TASK_TO_BACK, false); } mService.mWindowManager.moveAppTokensToBottom(moved); if (VALIDATE_TOKENS) { diff --git a/services/java/com/android/server/am/BaseErrorDialog.java b/services/java/com/android/server/am/BaseErrorDialog.java index 03e3272..d1e89bc 100644 --- a/services/java/com/android/server/am/BaseErrorDialog.java +++ b/services/java/com/android/server/am/BaseErrorDialog.java @@ -34,7 +34,7 @@ class BaseErrorDialog extends AlertDialog { getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); getWindow().setTitle("Error Dialog"); - setIcon(R.drawable.ic_dialog_alert); + setIconAttribute(R.attr.alertDialogIcon); } public void onStart() { diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java index 367c4cf..963a691 100644 --- a/services/java/com/android/server/am/BatteryStatsService.java +++ b/services/java/com/android/server/am/BatteryStatsService.java @@ -16,7 +16,9 @@ package com.android.server.am; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.pm.ApplicationInfo; import android.os.Binder; @@ -46,6 +48,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub { final BatteryStatsImpl mStats; Context mContext; + private boolean mBluetoothPendingStats; + private BluetoothHeadset mBluetoothHeadset; BatteryStatsService(String filename) { mStats = new BatteryStatsImpl(filename); @@ -287,16 +291,43 @@ public final class BatteryStatsService extends IBatteryStats.Stub { public void noteBluetoothOn() { enforceCallingPermission(); - BluetoothHeadset headset = new BluetoothHeadset(mContext, null); + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, + BluetoothProfile.HEADSET); + } synchronized (mStats) { - mStats.noteBluetoothOnLocked(); - mStats.setBtHeadset(headset); + if (mBluetoothHeadset != null) { + mStats.noteBluetoothOnLocked(); + mStats.setBtHeadset(mBluetoothHeadset); + } else { + mBluetoothPendingStats = true; + } } } - + + private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = + new BluetoothProfile.ServiceListener() { + public void onServiceConnected(int profile, BluetoothProfile proxy) { + mBluetoothHeadset = (BluetoothHeadset) proxy; + synchronized (mStats) { + if (mBluetoothPendingStats) { + mStats.noteBluetoothOnLocked(); + mStats.setBtHeadset(mBluetoothHeadset); + mBluetoothPendingStats = false; + } + } + } + + public void onServiceDisconnected(int profile) { + mBluetoothHeadset = null; + } + }; + public void noteBluetoothOff() { enforceCallingPermission(); synchronized (mStats) { + mBluetoothPendingStats = false; mStats.noteBluetoothOffLocked(); } } diff --git a/services/java/com/android/server/am/LaunchWarningWindow.java b/services/java/com/android/server/am/LaunchWarningWindow.java index 4130e33..1114a31 100644 --- a/services/java/com/android/server/am/LaunchWarningWindow.java +++ b/services/java/com/android/server/am/LaunchWarningWindow.java @@ -4,6 +4,7 @@ import com.android.internal.R; import android.app.Dialog; import android.content.Context; +import android.util.TypedValue; import android.view.Window; import android.view.WindowManager; import android.widget.ImageView; @@ -20,8 +21,11 @@ public class LaunchWarningWindow extends Dialog { setContentView(R.layout.launch_warning); setTitle(context.getText(R.string.launch_warning_title)); - getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, - R.drawable.ic_dialog_alert); + + TypedValue out = new TypedValue(); + getContext().getTheme().resolveAttribute(android.R.attr.alertDialogIcon, out, true); + getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, out.resourceId); + ImageView icon = (ImageView)findViewById(R.id.replace_app_icon); icon.setImageDrawable(next.info.applicationInfo.loadIcon(context.getPackageManager())); TextView text = (TextView)findViewById(R.id.replace_message); diff --git a/services/java/com/android/server/am/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java index 7a85eb8..ee6e420 100644 --- a/services/java/com/android/server/am/PendingIntentRecord.java +++ b/services/java/com/android/server/am/PendingIntentRecord.java @@ -47,20 +47,24 @@ class PendingIntentRecord extends IIntentSender.Stub { final int requestCode; final Intent requestIntent; final String requestResolvedType; + Intent[] allIntents; + String[] allResolvedTypes; final int flags; final int hashCode; private static final int ODD_PRIME_NUMBER = 37; Key(int _t, String _p, ActivityRecord _a, String _w, - int _r, Intent _i, String _it, int _f) { + int _r, Intent[] _i, String[] _it, int _f) { type = _t; packageName = _p; activity = _a; who = _w; requestCode = _r; - requestIntent = _i; - requestResolvedType = _it; + requestIntent = _i != null ? _i[_i.length-1] : null; + requestResolvedType = _it != null ? _it[_it.length-1] : null; + allIntents = _i; + allResolvedTypes = _it; flags = _f; int hash = 23; @@ -72,11 +76,11 @@ class PendingIntentRecord extends IIntentSender.Stub { if (_a != null) { hash = (ODD_PRIME_NUMBER*hash) + _a.hashCode(); } - if (_i != null) { - hash = (ODD_PRIME_NUMBER*hash) + _i.filterHashCode(); + if (requestIntent != null) { + hash = (ODD_PRIME_NUMBER*hash) + requestIntent.filterHashCode(); } - if (_it != null) { - hash = (ODD_PRIME_NUMBER*hash) + _it.hashCode(); + if (requestResolvedType != null) { + hash = (ODD_PRIME_NUMBER*hash) + requestResolvedType.hashCode(); } hash = (ODD_PRIME_NUMBER*hash) + _p.hashCode(); hash = (ODD_PRIME_NUMBER*hash) + _t; @@ -209,9 +213,24 @@ class PendingIntentRecord extends IIntentSender.Stub { switch (key.type) { case IActivityManager.INTENT_SENDER_ACTIVITY: try { - owner.startActivityInPackage(uid, - finalIntent, resolvedType, - resultTo, resultWho, requestCode, false); + if (key.allIntents != null && key.allIntents.length > 1) { + Intent[] allIntents = new Intent[key.allIntents.length]; + String[] allResolvedTypes = new String[key.allIntents.length]; + System.arraycopy(key.allIntents, 0, allIntents, 0, + key.allIntents.length); + if (key.allResolvedTypes != null) { + System.arraycopy(key.allResolvedTypes, 0, allResolvedTypes, 0, + key.allResolvedTypes.length); + } + allIntents[allIntents.length-1] = finalIntent; + allResolvedTypes[allResolvedTypes.length-1] = resolvedType; + owner.startActivitiesInPackage(uid, allIntents, + allResolvedTypes, resultTo); + } else { + owner.startActivityInPackage(uid, + finalIntent, resolvedType, + resultTo, resultWho, requestCode, false); + } } catch (RuntimeException e) { Slog.w(ActivityManagerService.TAG, "Unable to send startActivity intent", e); diff --git a/services/java/com/android/server/am/TaskRecord.java b/services/java/com/android/server/am/TaskRecord.java index bcb8f54..86cec42 100644 --- a/services/java/com/android/server/am/TaskRecord.java +++ b/services/java/com/android/server/am/TaskRecord.java @@ -19,14 +19,13 @@ package com.android.server.am; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; -import android.os.SystemClock; +import android.graphics.Bitmap; import java.io.PrintWriter; class TaskRecord { final int taskId; // Unique identifier for this task. final String affinity; // The affinity name for this task, or null. - final boolean clearOnBackground; // As per the original activity. Intent intent; // The original intent that started the task. Intent affinityIntent; // Intent of affinity-moved activity that started this task. ComponentName origActivity; // The non-alias activity component of the intent. @@ -35,14 +34,14 @@ class TaskRecord { long lastActiveTime; // Last time this task was active, including sleep. boolean rootWasReset; // True if the intent at the root of the task had // the FLAG_ACTIVITY_RESET_TASK_IF_NEEDED flag. + Bitmap lastThumbnail; // Last thumbnail captured for this task. + CharSequence lastDescription; // Last description captured for this task. String stringName; // caching of toString() result. - TaskRecord(int _taskId, ActivityInfo info, Intent _intent, - boolean _clearOnBackground) { + TaskRecord(int _taskId, ActivityInfo info, Intent _intent) { taskId = _taskId; affinity = info.taskAffinity; - clearOnBackground = _clearOnBackground; setIntent(_intent, info); } @@ -86,9 +85,8 @@ class TaskRecord { } void dump(PrintWriter pw, String prefix) { - if (clearOnBackground || numActivities != 0 || rootWasReset) { - pw.print(prefix); pw.print("clearOnBackground="); pw.print(clearOnBackground); - pw.print(" numActivities="); pw.print(numActivities); + if (numActivities != 0 || rootWasReset) { + pw.print(prefix); pw.print("numActivities="); pw.print(numActivities); pw.print(" rootWasReset="); pw.println(rootWasReset); } if (affinity != null) { diff --git a/services/java/com/android/server/am/UriPermissionOwner.java b/services/java/com/android/server/am/UriPermissionOwner.java index 99c82e6..68a2e0f 100644 --- a/services/java/com/android/server/am/UriPermissionOwner.java +++ b/services/java/com/android/server/am/UriPermissionOwner.java @@ -45,7 +45,7 @@ class UriPermissionOwner { } Binder getExternalTokenLocked() { - if (externalToken != null) { + if (externalToken == null) { externalToken = new ExternalToken(); } return externalToken; diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index a73a4ce..26c7e71 100644 --- a/services/java/com/android/server/connectivity/Tethering.java +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -19,20 +19,20 @@ package com.android.server.connectivity; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.bluetooth.BluetoothPan; import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; -import android.hardware.Usb; +import android.hardware.UsbManager; import android.net.ConnectivityManager; import android.net.InterfaceConfiguration; import android.net.IConnectivityManager; import android.net.INetworkManagementEventObserver; +import android.net.LinkProperties; import android.net.NetworkInfo; -import android.net.NetworkUtils; import android.os.Binder; import android.os.Environment; import android.os.Handler; @@ -52,8 +52,10 @@ import com.android.internal.util.HierarchicalStateMachine; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.net.InetAddress; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedList; import java.util.Set; /** * @hide @@ -66,7 +68,8 @@ import java.util.Set; public class Tethering extends INetworkManagementEventObserver.Stub { private Context mContext; - private final String TAG = "Tethering"; + private final static String TAG = "Tethering"; + private final static boolean DEBUG = true; private boolean mBooted = false; //used to remember if we got connected before boot finished @@ -75,6 +78,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { // TODO - remove both of these - should be part of interface inspection/selection stuff private String[] mTetherableUsbRegexs; private String[] mTetherableWifiRegexs; + private String[] mTetherableBluetoothRegexs; private String[] mUpstreamIfaceRegexs; private Looper mLooper; @@ -87,17 +91,31 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private static final String USB_NEAR_IFACE_ADDR = "192.168.42.129"; private static final String USB_NETMASK = "255.255.255.0"; - // FYI - the default wifi is 192.168.43.1 and 255.255.255.0 + // USB is 192.168.42.1 and 255.255.255.0 + // Wifi is 192.168.43.1 and 255.255.255.0 + // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1 + // with 255.255.255.0 private String[] mDhcpRange; private static final String DHCP_DEFAULT_RANGE1_START = "192.168.42.2"; private static final String DHCP_DEFAULT_RANGE1_STOP = "192.168.42.254"; private static final String DHCP_DEFAULT_RANGE2_START = "192.168.43.2"; private static final String DHCP_DEFAULT_RANGE2_STOP = "192.168.43.254"; + private static final String DHCP_DEFAULT_RANGE3_START = "192.168.44.2"; + private static final String DHCP_DEFAULT_RANGE3_STOP = "192.168.44.254"; + private static final String DHCP_DEFAULT_RANGE4_START = "192.168.45.2"; + private static final String DHCP_DEFAULT_RANGE4_STOP = "192.168.45.254"; + private static final String DHCP_DEFAULT_RANGE5_START = "192.168.46.2"; + private static final String DHCP_DEFAULT_RANGE5_STOP = "192.168.46.254"; + private static final String DHCP_DEFAULT_RANGE6_START = "192.168.47.2"; + private static final String DHCP_DEFAULT_RANGE6_STOP = "192.168.47.254"; + private static final String DHCP_DEFAULT_RANGE7_START = "192.168.48.2"; + private static final String DHCP_DEFAULT_RANGE7_STOP = "192.168.48.254"; + private String[] mDnsServers; private static final String DNS_DEFAULT_SERVER1 = "8.8.8.8"; - private static final String DNS_DEFAULT_SERVER2 = "4.2.2.2"; + private static final String DNS_DEFAULT_SERVER2 = "8.8.4.4"; // resampled each time we turn on tethering - used as cache for settings/config-val private boolean mDunRequired; // configuration info - must use DUN apn on 3g @@ -111,14 +129,6 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private boolean mUsbMassStorageOff; // track the status of USB Mass Storage private boolean mUsbConnected; // track the status of USB connection - // mUsbHandler message - static final int USB_STATE_CHANGE = 1; - static final int USB_DISCONNECTED = 0; - static final int USB_CONNECTED = 1; - - // Time to delay before processing USB disconnect events - static final long USB_DISCONNECT_DELAY = 1000; - public Tethering(Context context, Looper looper) { mContext = context; mLooper = looper; @@ -143,7 +153,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { mStateReceiver = new StateReceiver(); IntentFilter filter = new IntentFilter(); - filter.addAction(Usb.ACTION_USB_STATE); + filter.addAction(UsbManager.ACTION_USB_STATE); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); filter.addAction(Intent.ACTION_BOOT_COMPLETED); mContext.registerReceiver(mStateReceiver, filter); @@ -160,11 +170,21 @@ public class Tethering extends INetworkManagementEventObserver.Stub { mDhcpRange = context.getResources().getStringArray( com.android.internal.R.array.config_tether_dhcp_range); if ((mDhcpRange.length == 0) || (mDhcpRange.length % 2 ==1)) { - mDhcpRange = new String[4]; + mDhcpRange = new String[14]; mDhcpRange[0] = DHCP_DEFAULT_RANGE1_START; mDhcpRange[1] = DHCP_DEFAULT_RANGE1_STOP; mDhcpRange[2] = DHCP_DEFAULT_RANGE2_START; mDhcpRange[3] = DHCP_DEFAULT_RANGE2_STOP; + mDhcpRange[4] = DHCP_DEFAULT_RANGE3_START; + mDhcpRange[5] = DHCP_DEFAULT_RANGE3_STOP; + mDhcpRange[6] = DHCP_DEFAULT_RANGE4_START; + mDhcpRange[7] = DHCP_DEFAULT_RANGE4_STOP; + mDhcpRange[8] = DHCP_DEFAULT_RANGE5_START; + mDhcpRange[9] = DHCP_DEFAULT_RANGE5_STOP; + mDhcpRange[10] = DHCP_DEFAULT_RANGE6_START; + mDhcpRange[11] = DHCP_DEFAULT_RANGE6_STOP; + mDhcpRange[12] = DHCP_DEFAULT_RANGE7_START; + mDhcpRange[13] = DHCP_DEFAULT_RANGE7_STOP; } mDunRequired = false; // resample when we turn on @@ -172,6 +192,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { com.android.internal.R.array.config_tether_usb_regexs); mTetherableWifiRegexs = context.getResources().getStringArray( com.android.internal.R.array.config_tether_wifi_regexs); + mTetherableBluetoothRegexs = context.getResources().getStringArray( + com.android.internal.R.array.config_tether_bluetooth_regexs); mUpstreamIfaceRegexs = context.getResources().getStringArray( com.android.internal.R.array.config_tether_upstream_regexs); @@ -182,7 +204,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } public void interfaceLinkStatusChanged(String iface, boolean link) { - Log.d(TAG, "interfaceLinkStatusChanged " + iface + ", " + link); + if (DEBUG) Log.d(TAG, "interfaceLinkStatusChanged " + iface + ", " + link); boolean found = false; boolean usb = false; if (isWifi(iface)) { @@ -190,6 +212,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } else if (isUsb(iface)) { found = true; usb = true; + } else if (isBluetooth(iface)) { + found = true; } if (found == false) return; @@ -224,6 +248,12 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return false; } + public boolean isBluetooth(String iface) { + for (String regex : mTetherableBluetoothRegexs) { + if (iface.matches(regex)) return true; + } + return false; + } public void interfaceAdded(String iface) { IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); @@ -236,29 +266,34 @@ public class Tethering extends INetworkManagementEventObserver.Stub { found = true; usb = true; } + if (isBluetooth(iface)) { + found = true; + } if (found == false) { - Log.d(TAG, iface + " is not a tetherable iface, ignoring"); + if (DEBUG) Log.d(TAG, iface + " is not a tetherable iface, ignoring"); return; } synchronized (mIfaces) { TetherInterfaceSM sm = mIfaces.get(iface); if (sm != null) { - Log.e(TAG, "active iface (" + iface + ") reported as added, ignoring"); + if (DEBUG) Log.d(TAG, "active iface (" + iface + ") reported as added, ignoring"); return; } sm = new TetherInterfaceSM(iface, mLooper, usb); mIfaces.put(iface, sm); sm.start(); } - Log.d(TAG, "interfaceAdded :" + iface); + if (DEBUG) Log.d(TAG, "interfaceAdded :" + iface); } public void interfaceRemoved(String iface) { synchronized (mIfaces) { TetherInterfaceSM sm = mIfaces.get(iface); if (sm == null) { - Log.e(TAG, "attempting to remove unknown iface (" + iface + "), ignoring"); + if (DEBUG) { + Log.e(TAG, "attempting to remove unknown iface (" + iface + "), ignoring"); + } return; } sm.sendMessage(TetherInterfaceSM.CMD_INTERFACE_DOWN); @@ -329,13 +364,14 @@ public class Tethering extends INetworkManagementEventObserver.Stub { boolean wifiTethered = false; boolean usbTethered = false; + boolean bluetoothTethered = false; synchronized (mIfaces) { Set ifaces = mIfaces.keySet(); for (Object iface : ifaces) { TetherInterfaceSM sm = mIfaces.get(iface); if (sm != null) { - if(sm.isErrored()) { + if (sm.isErrored()) { erroredList.add((String)iface); } else if (sm.isAvailable()) { availableList.add((String)iface); @@ -344,6 +380,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { usbTethered = true; } else if (isWifi((String)iface)) { wifiTethered = true; + } else if (isBluetooth((String)iface)) { + bluetoothTethered = true; } activeList.add((String)iface); } @@ -358,17 +396,25 @@ public class Tethering extends INetworkManagementEventObserver.Stub { broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ERRORED_TETHER, erroredList); mContext.sendStickyBroadcast(broadcast); - Log.d(TAG, "sendTetherStateChangedBroadcast " + availableList.size() + ", " + - activeList.size() + ", " + erroredList.size()); + if (DEBUG) { + Log.d(TAG, "sendTetherStateChangedBroadcast " + availableList.size() + ", " + + activeList.size() + ", " + erroredList.size()); + } if (usbTethered) { - if (wifiTethered) { + if (wifiTethered || bluetoothTethered) { showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_general); } else { showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_usb); } } else if (wifiTethered) { - showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_wifi); + if (bluetoothTethered) { + showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_general); + } else { + showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_wifi); + } + } else if (bluetoothTethered) { + showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_bluetooth); } else { clearTetheredNotification(); } @@ -399,7 +445,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { CharSequence message = r.getText(com.android.internal.R.string. tethered_notification_message); - if(mTetheredNotification == null) { + if (mTetheredNotification == null) { mTetheredNotification = new Notification(); mTetheredNotification.when = 0; } @@ -429,25 +475,12 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } - private Handler mUsbHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - mUsbConnected = (msg.arg1 == USB_CONNECTED); - updateUsbStatus(); - } - }; - private class StateReceiver extends BroadcastReceiver { public void onReceive(Context content, Intent intent) { String action = intent.getAction(); - if (action.equals(Usb.ACTION_USB_STATE)) { - // process connect events immediately, but delay handling disconnects - // to debounce USB configuration changes - boolean connected = intent.getExtras().getBoolean(Usb.USB_CONNECTED); - Message msg = Message.obtain(mUsbHandler, USB_STATE_CHANGE, - (connected ? USB_CONNECTED : USB_DISCONNECTED), 0); - mUsbHandler.removeMessages(USB_STATE_CHANGE); - mUsbHandler.sendMessageDelayed(msg, connected ? 0 : USB_DISCONNECT_DELAY); + if (action.equals(UsbManager.ACTION_USB_STATE)) { + mUsbConnected = intent.getExtras().getBoolean(UsbManager.USB_CONNECTED); + updateUsbStatus(); } else if (action.equals(Intent.ACTION_MEDIA_SHARED)) { mUsbMassStorageOff = false; updateUsbStatus(); @@ -456,6 +489,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { mUsbMassStorageOff = true; updateUsbStatus(); } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { + if (DEBUG) Log.d(TAG, "Tethering got CONNECTIVITY_ACTION"); mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED); } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { mBooted = true; @@ -486,9 +520,9 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } - // toggled when we enter/leave the fully teathered state + // toggled when we enter/leave the fully tethered state private boolean enableUsbRndis(boolean enabled) { - Log.d(TAG, "enableUsbRndis(" + enabled + ")"); + if (DEBUG) Log.d(TAG, "enableUsbRndis(" + enabled + ")"); IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); @@ -513,7 +547,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { // configured when we start tethering and unconfig'd on error or conclusion private boolean configureUsbIface(boolean enabled) { - Log.d(TAG, "configureUsbIface(" + enabled + ")"); + if (DEBUG) Log.d(TAG, "configureUsbIface(" + enabled + ")"); IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); @@ -532,16 +566,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { try { ifcg = service.getInterfaceConfig(iface); if (ifcg != null) { - String[] addr = USB_NEAR_IFACE_ADDR.split("\\."); - ifcg.ipAddr = (Integer.parseInt(addr[0]) << 24) + - (Integer.parseInt(addr[1]) << 16) + - (Integer.parseInt(addr[2]) << 8) + - (Integer.parseInt(addr[3])); - addr = USB_NETMASK.split("\\."); - ifcg.netmask = (Integer.parseInt(addr[0]) << 24) + - (Integer.parseInt(addr[1]) << 16) + - (Integer.parseInt(addr[2]) << 8) + - (Integer.parseInt(addr[3])); + ifcg.addr = InetAddress.getByName(USB_NEAR_IFACE_ADDR); + ifcg.mask = InetAddress.getByName(USB_NETMASK); if (enabled) { ifcg.interfaceFlags = ifcg.interfaceFlags.replace("down", "up"); } else { @@ -569,6 +595,10 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return mTetherableWifiRegexs; } + public String[] getTetherableBluetoothRegexs() { + return mTetherableBluetoothRegexs; + } + public String[] getUpstreamIfaceRegexs() { return mUpstreamIfaceRegexs; } @@ -760,7 +790,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { @Override public boolean processMessage(Message message) { - Log.d(TAG, "InitialState.processMessage what=" + message.what); + if (DEBUG) Log.d(TAG, "InitialState.processMessage what=" + message.what); boolean retValue = true; switch (message.what) { case CMD_TETHER_REQUESTED: @@ -801,7 +831,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } @Override public boolean processMessage(Message message) { - Log.d(TAG, "StartingState.processMessage what=" + message.what); + if (DEBUG) Log.d(TAG, "StartingState.processMessage what=" + message.what); boolean retValue = true; switch (message.what) { // maybe a parent class? @@ -853,7 +883,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return; } if (mUsb) Tethering.this.enableUsbRndis(true); - Log.d(TAG, "Tethered " + mIfaceName); + if (DEBUG) Log.d(TAG, "Tethered " + mIfaceName); setAvailable(false); setTethered(true); sendTetherStateChangedBroadcast(); @@ -864,7 +894,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } @Override public boolean processMessage(Message message) { - Log.d(TAG, "TetheredState.processMessage what=" + message.what); + if (DEBUG) Log.d(TAG, "TetheredState.processMessage what=" + message.what); boolean retValue = true; boolean error = false; switch (message.what) { @@ -907,13 +937,18 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } else if (message.what == CMD_INTERFACE_DOWN) { transitionTo(mUnavailableState); } - Log.d(TAG, "Untethered " + mIfaceName); + if (DEBUG) Log.d(TAG, "Untethered " + mIfaceName); break; case CMD_TETHER_CONNECTION_CHANGED: String newUpstreamIfaceName = (String)(message.obj); b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); service = INetworkManagementService.Stub.asInterface(b); - + if ((mMyUpstreamIfaceName == null && newUpstreamIfaceName == null) || + (mMyUpstreamIfaceName != null && + mMyUpstreamIfaceName.equals(newUpstreamIfaceName))) { + if (DEBUG) Log.d(TAG, "Connection changed noop - dropping"); + break; + } if (mMyUpstreamIfaceName != null) { try { service.disableNat(mIfaceName, mMyUpstreamIfaceName); @@ -980,7 +1015,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { ConnectivityManager.TETHER_ERROR_MASTER_ERROR); break; } - Log.d(TAG, "Tether lost upstream connection " + mIfaceName); + if (DEBUG) Log.d(TAG, "Tether lost upstream connection " + mIfaceName); sendTetherStateChangedBroadcast(); if (mUsb) { if (!Tethering.this.configureUsbIface(false)) { @@ -1056,7 +1091,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private ArrayList mNotifyList; - private boolean mConnectionRequested = false; + private int mCurrentConnectionSequence; + private boolean mMobileReserved = false; private String mUpstreamIfaceName = null; @@ -1095,43 +1131,47 @@ public class Tethering extends INetworkManagementEventObserver.Stub { public boolean processMessage(Message m) { return false; } - protected int turnOnMobileConnection() { + protected boolean turnOnMobileConnection() { + boolean retValue = true; + if (mMobileReserved) return retValue; IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); - int retValue = Phone.APN_REQUEST_FAILED; + int result = Phone.APN_REQUEST_FAILED; try { - retValue = service.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, - (mDunRequired ? Phone.FEATURE_ENABLE_DUN : Phone.FEATURE_ENABLE_HIPRI), - new Binder()); + result = service.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, + (mDunRequired ? Phone.FEATURE_ENABLE_DUN_ALWAYS : + Phone.FEATURE_ENABLE_HIPRI), new Binder()); } catch (Exception e) { } - switch (retValue) { + switch (result) { case Phone.APN_ALREADY_ACTIVE: case Phone.APN_REQUEST_STARTED: - sendMessageDelayed(CMD_CELL_CONNECTION_RENEW, CELL_CONNECTION_RENEW_MS); - mConnectionRequested = true; + mMobileReserved = true; + Message m = obtainMessage(CMD_CELL_CONNECTION_RENEW); + m.arg1 = ++mCurrentConnectionSequence; + sendMessageDelayed(m, CELL_CONNECTION_RENEW_MS); break; case Phone.APN_REQUEST_FAILED: default: - mConnectionRequested = false; + retValue = false; break; } return retValue; } protected boolean turnOffMobileConnection() { - if (mConnectionRequested) { + if (mMobileReserved) { IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); try { service.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, - (mDunRequired? Phone.FEATURE_ENABLE_DUN : + (mDunRequired? Phone.FEATURE_ENABLE_DUN_ALWAYS : Phone.FEATURE_ENABLE_HIPRI)); } catch (Exception e) { return false; } - mConnectionRequested = false; + mMobileReserved = false; } return true; } @@ -1180,7 +1220,20 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } protected String findActiveUpstreamIface() { // check for what iface we can use - if none found switch to error. - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); + IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b); + + try { + LinkProperties defaultProp = cm.getActiveLinkProperties(); + if (defaultProp != null) { + String iface = defaultProp.getInterfaceName(); + for(String regex : mUpstreamIfaceRegexs) { + if (iface.matches(regex)) return iface; + } + } + } catch (RemoteException e) { } + + b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); String[] ifaces = new String[0]; @@ -1194,32 +1247,35 @@ public class Tethering extends INetworkManagementEventObserver.Stub { for (String iface : ifaces) { for (String regex : mUpstreamIfaceRegexs) { if (iface.matches(regex)) { - // verify it is up! + // verify it is active InterfaceConfiguration ifcg = null; try { ifcg = service.getInterfaceConfig(iface); + if (ifcg.isActive()) { + return iface; + } } catch (Exception e) { Log.e(TAG, "Error getting iface config :" + e); // ignore - try next continue; } - if (ifcg.interfaceFlags.contains("up")) { - return iface; - } } } } return null; } + protected void chooseUpstreamType(boolean tryCell) { // decide if the current upstream is good or not and if not // do something about it (start up DUN if required or HiPri if not) String iface = findActiveUpstreamIface(); IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b); - mConnectionRequested = false; - Log.d(TAG, "chooseUpstreamType(" + tryCell + "), dunRequired =" - + mDunRequired + ", iface=" + iface); + mMobileReserved = false; + if (DEBUG) { + Log.d(TAG, "chooseUpstreamType(" + tryCell + "), dunRequired =" + + mDunRequired + ", iface=" + iface); + } if (iface != null) { try { if (mDunRequired) { @@ -1227,7 +1283,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { NetworkInfo info = cm.getNetworkInfo( ConnectivityManager.TYPE_MOBILE_DUN); if (info.isConnected()) { - Log.d(TAG, "setting dun ifacename =" + iface); + if (DEBUG) Log.d(TAG, "setting dun ifacename =" + iface); // even if we're already connected - it may be somebody else's // refcount, so add our own turnOnMobileConnection(); @@ -1239,11 +1295,11 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } } else { - Log.d(TAG, "checking if hipri brought us this connection"); + if (DEBUG) Log.d(TAG, "checking if hipri brought us this connection"); NetworkInfo info = cm.getNetworkInfo( ConnectivityManager.TYPE_MOBILE_HIPRI); if (info.isConnected()) { - Log.d(TAG, "yes - hipri in use"); + if (DEBUG) Log.d(TAG, "yes - hipri in use"); // even if we're already connected - it may be sombody else's // refcount, so add our own turnOnMobileConnection(); @@ -1256,16 +1312,19 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } // may have been set to null in the if above if (iface == null ) { + boolean success = false; if (tryCell == TRY_TO_SETUP_MOBILE_CONNECTION) { - turnOnMobileConnection(); + success = turnOnMobileConnection(); + } + if (!success) { + // wait for things to settle and retry + sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS); } - // wait for things to settle and retry - sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS); } notifyTetheredOfNewUpstreamIface(iface); } protected void notifyTetheredOfNewUpstreamIface(String ifaceName) { - Log.d(TAG, "notifying tethered with iface =" + ifaceName); + if (DEBUG) Log.d(TAG, "notifying tethered with iface =" + ifaceName); mUpstreamIfaceName = ifaceName; for (Object o : mNotifyList) { TetherInterfaceSM sm = (TetherInterfaceSM)o; @@ -1278,23 +1337,23 @@ public class Tethering extends INetworkManagementEventObserver.Stub { class InitialState extends TetherMasterUtilState { @Override public void enter() { - mConnectionRequested = false; + mMobileReserved = false; } @Override public boolean processMessage(Message message) { - Log.d(TAG, "MasterInitialState.processMessage what=" + message.what); + if (DEBUG) Log.d(TAG, "MasterInitialState.processMessage what=" + message.what); boolean retValue = true; switch (message.what) { case CMD_TETHER_MODE_REQUESTED: mDunRequired = isDunRequired(); TetherInterfaceSM who = (TetherInterfaceSM)message.obj; - Log.d(TAG, "Tether Mode requested by " + who.toString()); + if (DEBUG) Log.d(TAG, "Tether Mode requested by " + who.toString()); mNotifyList.add(who); transitionTo(mTetherModeAliveState); break; case CMD_TETHER_MODE_UNREQUESTED: who = (TetherInterfaceSM)message.obj; - Log.d(TAG, "Tether Mode unrequested by " + who.toString()); + if (DEBUG) Log.d(TAG, "Tether Mode unrequested by " + who.toString()); int index = mNotifyList.indexOf(who); if (index != -1) { mNotifyList.remove(who); @@ -1309,10 +1368,11 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } class TetherModeAliveState extends TetherMasterUtilState { - boolean mTryCell = WAIT_FOR_NETWORK_TO_SETTLE; + boolean mTryCell = !WAIT_FOR_NETWORK_TO_SETTLE; @Override public void enter() { - mTryCell = WAIT_FOR_NETWORK_TO_SETTLE; // first pass lets just see what we have. + mTryCell = !WAIT_FOR_NETWORK_TO_SETTLE; // better try something first pass + // or crazy tests cases will fail chooseUpstreamType(mTryCell); mTryCell = !mTryCell; turnOnMasterTetherSettings(); // may transition us out @@ -1324,7 +1384,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } @Override public boolean processMessage(Message message) { - Log.d(TAG, "TetherModeAliveState.processMessage what=" + message.what); + if (DEBUG) Log.d(TAG, "TetherModeAliveState.processMessage what=" + message.what); boolean retValue = true; switch (message.what) { case CMD_TETHER_MODE_REQUESTED: @@ -1344,16 +1404,20 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } break; case CMD_UPSTREAM_CHANGED: - mTryCell = WAIT_FOR_NETWORK_TO_SETTLE; + // need to try DUN immediately if Wifi goes down + mTryCell = !WAIT_FOR_NETWORK_TO_SETTLE; chooseUpstreamType(mTryCell); mTryCell = !mTryCell; break; case CMD_CELL_CONNECTION_RENEW: // make sure we're still using a requested connection - may have found // wifi or something since then. - if (mConnectionRequested) { - Log.d(TAG, "renewing mobile connection - requeuing for another " + - CELL_CONNECTION_RENEW_MS + "ms"); + if (mCurrentConnectionSequence == message.arg1) { + if (DEBUG) { + Log.d(TAG, "renewing mobile connection - requeuing for another " + + CELL_CONNECTION_RENEW_MS + "ms"); + } + mMobileReserved = false; // need to renew it turnOnMobileConnection(); } break; diff --git a/services/java/com/android/server/location/ComprehensiveCountryDetector.java b/services/java/com/android/server/location/ComprehensiveCountryDetector.java new file mode 100755 index 0000000..e9ce3ce --- /dev/null +++ b/services/java/com/android/server/location/ComprehensiveCountryDetector.java @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2010 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.server.location; + +import android.content.Context; +import android.location.Country; +import android.location.CountryListener; +import android.location.Geocoder; +import android.provider.Settings; +import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Slog; + +import java.util.Locale; +import java.util.Timer; +import java.util.TimerTask; + +/** + * This class is used to detect the country where the user is. The sources of + * country are queried in order of reliability, like + * <ul> + * <li>Mobile network</li> + * <li>Location</li> + * <li>SIM's country</li> + * <li>Phone's locale</li> + * </ul> + * <p> + * Call the {@link #detectCountry()} to get the available country immediately. + * <p> + * To be notified of the future country change, using the + * {@link #setCountryListener(CountryListener)} + * <p> + * Using the {@link #stop()} to stop listening to the country change. + * <p> + * The country information will be refreshed every + * {@link #LOCATION_REFRESH_INTERVAL} once the location based country is used. + * + * @hide + */ +public class ComprehensiveCountryDetector extends CountryDetectorBase { + + private final static String TAG = "ComprehensiveCountryDetector"; + + /** + * The refresh interval when the location based country was used + */ + private final static long LOCATION_REFRESH_INTERVAL = 1000 * 60 * 60 * 24; // 1 day + + protected CountryDetectorBase mLocationBasedCountryDetector; + protected Timer mLocationRefreshTimer; + + private final int mPhoneType; + private Country mCountry; + private TelephonyManager mTelephonyManager; + private Country mCountryFromLocation; + private boolean mStopped = false; + private ServiceState mLastState; + + private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + @Override + public void onServiceStateChanged(ServiceState serviceState) { + // TODO: Find out how often we will be notified, if this method is called too + // many times, let's consider querying the network. + Slog.d(TAG, "onServiceStateChanged"); + // We only care the state change + if (mLastState == null || mLastState.getState() != serviceState.getState()) { + detectCountry(true, true); + mLastState = new ServiceState(serviceState); + } + } + }; + + /** + * The listener for receiving the notification from LocationBasedCountryDetector. + */ + private CountryListener mLocationBasedCountryDetectionListener = new CountryListener() { + public void onCountryDetected(Country country) { + mCountryFromLocation = country; + // Don't start the LocationBasedCountryDetector. + detectCountry(true, false); + stopLocationBasedDetector(); + } + }; + + public ComprehensiveCountryDetector(Context context) { + super(context); + mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + mPhoneType = mTelephonyManager.getPhoneType(); + } + + @Override + public Country detectCountry() { + // Don't start the LocationBasedCountryDetector if we have been stopped. + return detectCountry(false, !mStopped); + } + + @Override + public void stop() { + Slog.i(TAG, "Stop the detector."); + cancelLocationRefresh(); + removePhoneStateListener(); + stopLocationBasedDetector(); + mListener = null; + mStopped = true; + } + + /** + * Get the country from different sources in order of the reliability. + */ + private Country getCountry() { + Country result = null; + result = getNetworkBasedCountry(); + if (result == null) { + result = getLastKnownLocationBasedCountry(); + } + if (result == null) { + result = getSimBasedCountry(); + } + if (result == null) { + result = getLocaleCountry(); + } + return result; + } + + /** + * @return the country from the mobile network. + */ + protected Country getNetworkBasedCountry() { + String countryIso = null; + // TODO: The document says the result may be unreliable on CDMA networks. Shall we use + // it on CDMA phone? We may test the Android primarily used countries. + if (mPhoneType == TelephonyManager.PHONE_TYPE_GSM) { + countryIso = mTelephonyManager.getNetworkCountryIso(); + if (!TextUtils.isEmpty(countryIso)) { + return new Country(countryIso, Country.COUNTRY_SOURCE_NETWORK); + } + } + return null; + } + + /** + * @return the cached location based country. + */ + protected Country getLastKnownLocationBasedCountry() { + return mCountryFromLocation; + } + + /** + * @return the country from SIM card + */ + protected Country getSimBasedCountry() { + String countryIso = null; + countryIso = mTelephonyManager.getSimCountryIso(); + if (!TextUtils.isEmpty(countryIso)) { + return new Country(countryIso, Country.COUNTRY_SOURCE_SIM); + } + return null; + } + + /** + * @return the country from the system's locale. + */ + protected Country getLocaleCountry() { + Locale defaultLocale = Locale.getDefault(); + if (defaultLocale != null) { + return new Country(defaultLocale.getCountry(), Country.COUNTRY_SOURCE_LOCALE); + } else { + return null; + } + } + + /** + * @param notifyChange indicates whether the listener should be notified the change of the + * country + * @param startLocationBasedDetection indicates whether the LocationBasedCountryDetector could + * be started if the current country source is less reliable than the location. + * @return the current available UserCountry + */ + private Country detectCountry(boolean notifyChange, boolean startLocationBasedDetection) { + Country country = getCountry(); + runAfterDetectionAsync(mCountry != null ? new Country(mCountry) : mCountry, country, + notifyChange, startLocationBasedDetection); + mCountry = country; + return mCountry; + } + + /** + * Run the tasks in the service's thread. + */ + protected void runAfterDetectionAsync(final Country country, final Country detectedCountry, + final boolean notifyChange, final boolean startLocationBasedDetection) { + mHandler.post(new Runnable() { + public void run() { + runAfterDetection( + country, detectedCountry, notifyChange, startLocationBasedDetection); + } + }); + } + + @Override + public void setCountryListener(CountryListener listener) { + CountryListener prevListener = mListener; + mListener = listener; + if (mListener == null) { + // Stop listening all services + removePhoneStateListener(); + stopLocationBasedDetector(); + cancelLocationRefresh(); + } else if (prevListener == null) { + addPhoneStateListener(); + detectCountry(false, true); + } + } + + void runAfterDetection(final Country country, final Country detectedCountry, + final boolean notifyChange, final boolean startLocationBasedDetection) { + if (notifyChange) { + notifyIfCountryChanged(country, detectedCountry); + } + if (startLocationBasedDetection && (detectedCountry == null + || detectedCountry.getSource() > Country.COUNTRY_SOURCE_LOCATION) + && isAirplaneModeOff() && mListener != null && isGeoCoderImplemented()) { + // Start finding location when the source is less reliable than the + // location and the airplane mode is off (as geocoder will not + // work). + // TODO : Shall we give up starting the detector within a + // period of time? + startLocationBasedDetector(mLocationBasedCountryDetectionListener); + } + if (detectedCountry == null + || detectedCountry.getSource() >= Country.COUNTRY_SOURCE_LOCATION) { + // Schedule the location refresh if the country source is + // not more reliable than the location or no country is + // found. + // TODO: Listen to the preference change of GPS, Wifi etc, + // and start detecting the country. + scheduleLocationRefresh(); + } else { + // Cancel the location refresh once the current source is + // more reliable than the location. + cancelLocationRefresh(); + stopLocationBasedDetector(); + } + } + + /** + * Find the country from LocationProvider. + */ + private synchronized void startLocationBasedDetector(CountryListener listener) { + if (mLocationBasedCountryDetector != null) { + return; + } + mLocationBasedCountryDetector = createLocationBasedCountryDetector(); + mLocationBasedCountryDetector.setCountryListener(listener); + mLocationBasedCountryDetector.detectCountry(); + } + + private synchronized void stopLocationBasedDetector() { + if (mLocationBasedCountryDetector != null) { + mLocationBasedCountryDetector.stop(); + mLocationBasedCountryDetector = null; + } + } + + protected CountryDetectorBase createLocationBasedCountryDetector() { + return new LocationBasedCountryDetector(mContext); + } + + protected boolean isAirplaneModeOff() { + return Settings.System.getInt( + mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) == 0; + } + + /** + * Notify the country change. + */ + private void notifyIfCountryChanged(final Country country, final Country detectedCountry) { + if (detectedCountry != null && mListener != null + && (country == null || !country.equals(detectedCountry))) { + Slog.d(TAG, + "The country was changed from " + country != null ? country.getCountryIso() : + country + " to " + detectedCountry.getCountryIso()); + notifyListener(detectedCountry); + } + } + + /** + * Schedule the next location refresh. We will do nothing if the scheduled task exists. + */ + private synchronized void scheduleLocationRefresh() { + if (mLocationRefreshTimer != null) return; + mLocationRefreshTimer = new Timer(); + mLocationRefreshTimer.schedule(new TimerTask() { + @Override + public void run() { + mLocationRefreshTimer = null; + detectCountry(false, true); + } + }, LOCATION_REFRESH_INTERVAL); + } + + /** + * Cancel the scheduled refresh task if it exists + */ + private synchronized void cancelLocationRefresh() { + if (mLocationRefreshTimer != null) { + mLocationRefreshTimer.cancel(); + mLocationRefreshTimer = null; + } + } + + protected synchronized void addPhoneStateListener() { + if (mPhoneStateListener == null && mPhoneType == TelephonyManager.PHONE_TYPE_GSM) { + mLastState = null; + mPhoneStateListener = new PhoneStateListener() { + @Override + public void onServiceStateChanged(ServiceState serviceState) { + // TODO: Find out how often we will be notified, if this + // method is called too + // many times, let's consider querying the network. + Slog.d(TAG, "onServiceStateChanged"); + // We only care the state change + if (mLastState == null || mLastState.getState() != serviceState.getState()) { + detectCountry(true, true); + mLastState = new ServiceState(serviceState); + } + } + }; + mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE); + } + } + + protected synchronized void removePhoneStateListener() { + if (mPhoneStateListener != null) { + mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); + mPhoneStateListener = null; + } + } + + protected boolean isGeoCoderImplemented() { + return Geocoder.isPresent(); + } +} diff --git a/services/java/com/android/server/location/CountryDetectorBase.java b/services/java/com/android/server/location/CountryDetectorBase.java new file mode 100644 index 0000000..8326ef9 --- /dev/null +++ b/services/java/com/android/server/location/CountryDetectorBase.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2010 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.server.location; + +import android.content.Context; +import android.location.Country; +import android.location.CountryListener; +import android.os.Handler; + +/** + * This class defines the methods need to be implemented by the country + * detector. + * <p> + * Calling {@link #detectCountry} to start detecting the country. The country + * could be returned immediately if it is available. + * + * @hide + */ +public abstract class CountryDetectorBase { + protected final Handler mHandler; + protected final Context mContext; + protected CountryListener mListener; + protected Country mDetectedCountry; + + public CountryDetectorBase(Context ctx) { + mContext = ctx; + mHandler = new Handler(); + } + + /** + * Start detecting the country that the user is in. + * + * @return the country if it is available immediately, otherwise null should + * be returned. + */ + public abstract Country detectCountry(); + + /** + * Register a listener to receive the notification when the country is detected or changed. + * <p> + * The previous listener will be replaced if it exists. + */ + public void setCountryListener(CountryListener listener) { + mListener = listener; + } + + /** + * Stop detecting the country. The detector should release all system services and be ready to + * be freed + */ + public abstract void stop(); + + protected void notifyListener(Country country) { + if (mListener != null) { + mListener.onCountryDetected(country); + } + } +} diff --git a/services/java/com/android/server/location/LocationBasedCountryDetector.java b/services/java/com/android/server/location/LocationBasedCountryDetector.java new file mode 100755 index 0000000..139f05d --- /dev/null +++ b/services/java/com/android/server/location/LocationBasedCountryDetector.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2010 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.server.location; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +import android.content.Context; +import android.location.Address; +import android.location.Country; +import android.location.Geocoder; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; +import android.util.Slog; + +/** + * This class detects which country the user currently is in through the enabled + * location providers and the GeoCoder + * <p> + * Use {@link #detectCountry} to start querying. If the location can not be + * resolved within the given time, the last known location will be used to get + * the user country through the GeoCoder. The IllegalStateException will be + * thrown if there is a ongoing query. + * <p> + * The current query can be stopped by {@link #stop()} + * + * @hide + */ +public class LocationBasedCountryDetector extends CountryDetectorBase { + private final static String TAG = "LocationBasedCountryDetector"; + private final static long QUERY_LOCATION_TIMEOUT = 1000 * 60 * 5; // 5 mins + + /** + * Used for canceling location query + */ + protected Timer mTimer; + + /** + * The thread to query the country from the GeoCoder. + */ + protected Thread mQueryThread; + protected List<LocationListener> mLocationListeners; + + private LocationManager mLocationManager; + private List<String> mEnabledProviders; + + public LocationBasedCountryDetector(Context ctx) { + super(ctx); + mLocationManager = (LocationManager) ctx.getSystemService(Context.LOCATION_SERVICE); + } + + /** + * @return the ISO 3166-1 two letters country code from the location + */ + protected String getCountryFromLocation(Location location) { + String country = null; + Geocoder geoCoder = new Geocoder(mContext); + try { + List<Address> addresses = geoCoder.getFromLocation( + location.getLatitude(), location.getLongitude(), 1); + if (addresses != null && addresses.size() > 0) { + country = addresses.get(0).getCountryCode(); + } + } catch (IOException e) { + Slog.w(TAG, "Exception occurs when getting country from location"); + } + return country; + } + + /** + * Register the listeners with the location providers + */ + protected void registerEnabledProviders(List<LocationListener> listeners) { + int total = listeners.size(); + for (int i = 0; i< total; i++) { + mLocationManager.requestLocationUpdates( + mEnabledProviders.get(i), 0, 0, listeners.get(i)); + } + } + + /** + * Unregister the listeners with the location providers + */ + protected void unregisterProviders(List<LocationListener> listeners) { + for (LocationListener listener : listeners) { + mLocationManager.removeUpdates(listener); + } + } + + /** + * @return the last known location from all providers + */ + protected Location getLastKnownLocation() { + List<String> providers = mLocationManager.getAllProviders(); + Location bestLocation = null; + for (String provider : providers) { + Location lastKnownLocation = mLocationManager.getLastKnownLocation(provider); + if (lastKnownLocation != null) { + if (bestLocation == null || bestLocation.getTime() < lastKnownLocation.getTime()) { + bestLocation = lastKnownLocation; + } + } + } + return bestLocation; + } + + /** + * @return the timeout for querying the location. + */ + protected long getQueryLocationTimeout() { + return QUERY_LOCATION_TIMEOUT; + } + + /** + * @return the total number of enabled location providers + */ + protected int getTotalEnabledProviders() { + if (mEnabledProviders == null) { + mEnabledProviders = mLocationManager.getProviders(true); + } + return mEnabledProviders.size(); + } + + /** + * Start detecting the country. + * <p> + * Queries the location from all location providers, then starts a thread to query the + * country from GeoCoder. + */ + @Override + public synchronized Country detectCountry() { + if (mLocationListeners != null) { + throw new IllegalStateException(); + } + // Request the location from all enabled providers. + int totalProviders = getTotalEnabledProviders(); + if (totalProviders > 0) { + mLocationListeners = new ArrayList<LocationListener>(totalProviders); + for (int i = 0; i < totalProviders; i++) { + LocationListener listener = new LocationListener () { + public void onLocationChanged(Location location) { + if (location != null) { + LocationBasedCountryDetector.this.stop(); + queryCountryCode(location); + } + } + public void onProviderDisabled(String provider) { + } + public void onProviderEnabled(String provider) { + } + public void onStatusChanged(String provider, int status, Bundle extras) { + } + }; + mLocationListeners.add(listener); + } + registerEnabledProviders(mLocationListeners); + mTimer = new Timer(); + mTimer.schedule(new TimerTask() { + @Override + public void run() { + mTimer = null; + LocationBasedCountryDetector.this.stop(); + // Looks like no provider could provide the location, let's try the last + // known location. + queryCountryCode(getLastKnownLocation()); + } + }, getQueryLocationTimeout()); + } else { + // There is no provider enabled. + queryCountryCode(getLastKnownLocation()); + } + return mDetectedCountry; + } + + /** + * Stop the current query without notifying the listener. + */ + @Override + public synchronized void stop() { + if (mLocationListeners != null) { + unregisterProviders(mLocationListeners); + mLocationListeners = null; + } + if (mTimer != null) { + mTimer.cancel(); + mTimer = null; + } + } + + /** + * Start a new thread to query the country from Geocoder. + */ + private synchronized void queryCountryCode(final Location location) { + if (location == null) { + notifyListener(null); + return; + } + if (mQueryThread != null) return; + mQueryThread = new Thread(new Runnable() { + public void run() { + String countryIso = null; + if (location != null) { + countryIso = getCountryFromLocation(location); + } + if (countryIso != null) { + mDetectedCountry = new Country(countryIso, Country.COUNTRY_SOURCE_LOCATION); + } else { + mDetectedCountry = null; + } + notifyListener(mDetectedCountry); + mQueryThread = null; + } + }); + mQueryThread.start(); + } +} diff --git a/services/jni/Android.mk b/services/jni/Android.mk index c90879d..f5a5b4d 100644 --- a/services/jni/Android.mk +++ b/services/jni/Android.mk @@ -4,27 +4,37 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ com_android_server_AlarmManagerService.cpp \ com_android_server_BatteryService.cpp \ + com_android_server_InputApplication.cpp \ + com_android_server_InputApplicationHandle.cpp \ com_android_server_InputManager.cpp \ + com_android_server_InputWindow.cpp \ + com_android_server_InputWindowHandle.cpp \ com_android_server_LightsService.cpp \ com_android_server_PowerManagerService.cpp \ com_android_server_SystemServer.cpp \ com_android_server_VibratorService.cpp \ - com_android_server_location_GpsLocationProvider.cpp \ + com_android_server_location_GpsLocationProvider.cpp \ onload.cpp LOCAL_C_INCLUDES += \ - $(JNI_H_INCLUDE) + $(JNI_H_INCLUDE) \ + frameworks/base/services \ + frameworks/base/core/jni \ + external/skia/include/core LOCAL_SHARED_LIBRARIES := \ libandroid_runtime \ - libcutils \ - libhardware \ - libhardware_legacy \ - libnativehelper \ + libcutils \ + libhardware \ + libhardware_legacy \ + libnativehelper \ libsystem_server \ - libutils \ - libui \ - libsurfaceflinger_client + libutils \ + libui \ + libinput \ + libskia \ + libsurfaceflinger_client \ + libusbhost ifeq ($(TARGET_SIMULATOR),true) ifeq ($(TARGET_OS),linux) @@ -35,10 +45,9 @@ endif endif ifeq ($(WITH_MALLOC_LEAK_CHECK),true) - LOCAL_CFLAGS += -DMALLOC_LEAK_CHECK + LOCAL_CFLAGS += -DMALLOC_LEAK_CHECK endif LOCAL_MODULE:= libandroid_servers include $(BUILD_SHARED_LIBRARY) - diff --git a/services/jni/com_android_server_InputApplication.cpp b/services/jni/com_android_server_InputApplication.cpp new file mode 100644 index 0000000..a46a162 --- /dev/null +++ b/services/jni/com_android_server_InputApplication.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2011 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. + */ + +#define LOG_TAG "InputApplication" + +#include "JNIHelp.h" +#include "jni.h" +#include <android_runtime/AndroidRuntime.h> + +#include "com_android_server_InputApplication.h" +#include "com_android_server_InputApplicationHandle.h" + +namespace android { + +static struct { + jclass clazz; + + jfieldID inputApplicationHandle; + jfieldID name; + jfieldID dispatchingTimeoutNanos; +} gInputApplicationClassInfo; + + +// --- Global functions --- + +void android_server_InputApplication_toNative( + JNIEnv* env, jobject inputApplicationObj, InputApplication* outInputApplication) { + jobject inputApplicationHandleObj = env->GetObjectField(inputApplicationObj, + gInputApplicationClassInfo.inputApplicationHandle); + if (inputApplicationHandleObj) { + outInputApplication->inputApplicationHandle = + android_server_InputApplicationHandle_getHandle(env, inputApplicationHandleObj); + env->DeleteLocalRef(inputApplicationHandleObj); + } else { + outInputApplication->inputApplicationHandle = NULL; + } + + jstring nameObj = jstring(env->GetObjectField(inputApplicationObj, + gInputApplicationClassInfo.name)); + if (nameObj) { + const char* nameStr = env->GetStringUTFChars(nameObj, NULL); + outInputApplication->name.setTo(nameStr); + env->ReleaseStringUTFChars(nameObj, nameStr); + env->DeleteLocalRef(nameObj); + } else { + LOGE("InputApplication.name should not be null."); + outInputApplication->name.setTo("unknown"); + } + + outInputApplication->dispatchingTimeout = env->GetLongField(inputApplicationObj, + gInputApplicationClassInfo.dispatchingTimeoutNanos); +} + + +// --- JNI --- + +#define FIND_CLASS(var, className) \ + var = env->FindClass(className); \ + LOG_FATAL_IF(! var, "Unable to find class " className); \ + var = jclass(env->NewGlobalRef(var)); + +#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ + var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find field " fieldName); + +int register_android_server_InputApplication(JNIEnv* env) { + FIND_CLASS(gInputApplicationClassInfo.clazz, "com/android/server/InputApplication"); + + GET_FIELD_ID(gInputApplicationClassInfo.inputApplicationHandle, + gInputApplicationClassInfo.clazz, + "inputApplicationHandle", "Lcom/android/server/InputApplicationHandle;"); + + GET_FIELD_ID(gInputApplicationClassInfo.name, gInputApplicationClassInfo.clazz, + "name", "Ljava/lang/String;"); + + GET_FIELD_ID(gInputApplicationClassInfo.dispatchingTimeoutNanos, + gInputApplicationClassInfo.clazz, + "dispatchingTimeoutNanos", "J"); + return 0; +} + +} /* namespace android */ diff --git a/services/jni/com_android_server_InputApplication.h b/services/jni/com_android_server_InputApplication.h new file mode 100644 index 0000000..85fb891 --- /dev/null +++ b/services/jni/com_android_server_InputApplication.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef _ANDROID_SERVER_INPUT_APPLICATION_H +#define _ANDROID_SERVER_INPUT_APPLICATION_H + +#include <input/InputApplication.h> + +#include "JNIHelp.h" +#include "jni.h" + +namespace android { + +extern void android_server_InputApplication_toNative( + JNIEnv* env, jobject inputApplicationObj, InputApplication* outInputApplication); + +} // namespace android + +#endif // _ANDROID_SERVER_INPUT_APPLICATION_H diff --git a/services/jni/com_android_server_InputApplicationHandle.cpp b/services/jni/com_android_server_InputApplicationHandle.cpp new file mode 100644 index 0000000..ab82635 --- /dev/null +++ b/services/jni/com_android_server_InputApplicationHandle.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2011 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. + */ + +#define LOG_TAG "InputApplicationHandle" + +#include "JNIHelp.h" +#include "jni.h" +#include <android_runtime/AndroidRuntime.h> +#include <utils/threads.h> + +#include "com_android_server_InputApplicationHandle.h" + +namespace android { + +static struct { + jclass clazz; + + jfieldID ptr; +} gInputApplicationHandleClassInfo; + +static Mutex gHandleMutex; + + +// --- NativeInputApplicationHandle --- + +NativeInputApplicationHandle::NativeInputApplicationHandle(jweak objWeak) : + mObjWeak(objWeak) { +} + +NativeInputApplicationHandle::~NativeInputApplicationHandle() { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->DeleteWeakGlobalRef(mObjWeak); +} + +jobject NativeInputApplicationHandle::getInputApplicationHandleObjLocalRef(JNIEnv* env) { + return env->NewLocalRef(mObjWeak); +} + + +// --- Global functions --- + +sp<InputApplicationHandle> android_server_InputApplicationHandle_getHandle( + JNIEnv* env, jobject inputApplicationHandleObj) { + if (!inputApplicationHandleObj) { + return NULL; + } + + AutoMutex _l(gHandleMutex); + + int ptr = env->GetIntField(inputApplicationHandleObj, gInputApplicationHandleClassInfo.ptr); + NativeInputApplicationHandle* handle; + if (ptr) { + handle = reinterpret_cast<NativeInputApplicationHandle*>(ptr); + } else { + jweak objWeak = env->NewWeakGlobalRef(inputApplicationHandleObj); + handle = new NativeInputApplicationHandle(objWeak); + handle->incStrong(inputApplicationHandleObj); + env->SetIntField(inputApplicationHandleObj, gInputApplicationHandleClassInfo.ptr, + reinterpret_cast<int>(handle)); + } + return handle; +} + + +// --- JNI --- + +static void android_server_InputApplicationHandle_nativeDispose(JNIEnv* env, jobject obj) { + AutoMutex _l(gHandleMutex); + + int ptr = env->GetIntField(obj, gInputApplicationHandleClassInfo.ptr); + if (ptr) { + env->SetIntField(obj, gInputApplicationHandleClassInfo.ptr, 0); + + NativeInputApplicationHandle* handle = reinterpret_cast<NativeInputApplicationHandle*>(ptr); + handle->decStrong(obj); + } +} + + +static JNINativeMethod gInputApplicationHandleMethods[] = { + /* name, signature, funcPtr */ + { "nativeDispose", "()V", + (void*) android_server_InputApplicationHandle_nativeDispose }, +}; + +#define FIND_CLASS(var, className) \ + var = env->FindClass(className); \ + LOG_FATAL_IF(! var, "Unable to find class " className); \ + var = jclass(env->NewGlobalRef(var)); + +#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ + var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find field " fieldName); + +int register_android_server_InputApplicationHandle(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "com/android/server/InputApplicationHandle", + gInputApplicationHandleMethods, NELEM(gInputApplicationHandleMethods)); + LOG_FATAL_IF(res < 0, "Unable to register native methods."); + + FIND_CLASS(gInputApplicationHandleClassInfo.clazz, "com/android/server/InputApplicationHandle"); + + GET_FIELD_ID(gInputApplicationHandleClassInfo.ptr, gInputApplicationHandleClassInfo.clazz, + "ptr", "I"); + + return 0; +} + +} /* namespace android */ diff --git a/services/jni/com_android_server_InputApplicationHandle.h b/services/jni/com_android_server_InputApplicationHandle.h new file mode 100644 index 0000000..9d18721 --- /dev/null +++ b/services/jni/com_android_server_InputApplicationHandle.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef _ANDROID_SERVER_INPUT_APPLICATION_HANDLE_H +#define _ANDROID_SERVER_INPUT_APPLICATION_HANDLE_H + +#include <input/InputApplication.h> + +#include "JNIHelp.h" +#include "jni.h" + +namespace android { + +class NativeInputApplicationHandle : public InputApplicationHandle { +public: + NativeInputApplicationHandle(jweak objWeak); + virtual ~NativeInputApplicationHandle(); + + jobject getInputApplicationHandleObjLocalRef(JNIEnv* env); + +private: + jweak mObjWeak; +}; + + +extern sp<InputApplicationHandle> android_server_InputApplicationHandle_getHandle( + JNIEnv* env, jobject inputApplicationHandleObj); + +} // namespace android + +#endif // _ANDROID_SERVER_INPUT_APPLICATION_HANDLE_H diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp index 171471e..5b329bb 100644 --- a/services/jni/com_android_server_InputManager.cpp +++ b/services/jni/com_android_server_InputManager.cpp @@ -24,25 +24,31 @@ // Log debug messages about InputDispatcherPolicy #define DEBUG_INPUT_DISPATCHER_POLICY 0 + #include "JNIHelp.h" #include "jni.h" #include <limits.h> #include <android_runtime/AndroidRuntime.h> -#include <ui/InputReader.h> -#include <ui/InputDispatcher.h> -#include <ui/InputManager.h> -#include <ui/InputTransport.h> + #include <utils/Log.h> #include <utils/threads.h> -#include "../../core/jni/android_view_KeyEvent.h" -#include "../../core/jni/android_view_MotionEvent.h" -#include "../../core/jni/android_view_InputChannel.h" + +#include <input/InputManager.h> +#include <input/PointerController.h> + +#include <android_view_KeyEvent.h> +#include <android_view_MotionEvent.h> +#include <android_view_InputChannel.h> +#include <android/graphics/GraphicsJNI.h> + #include "com_android_server_PowerManagerService.h" +#include "com_android_server_InputApplication.h" +#include "com_android_server_InputApplicationHandle.h" +#include "com_android_server_InputWindow.h" +#include "com_android_server_InputWindowHandle.h" namespace android { -// ---------------------------------------------------------------------------- - static struct { jclass clazz; @@ -52,73 +58,19 @@ static struct { jmethodID notifyANR; jmethodID interceptKeyBeforeQueueing; jmethodID interceptKeyBeforeDispatching; + jmethodID dispatchUnhandledKey; jmethodID checkInjectEventsPermission; jmethodID filterTouchEvents; jmethodID filterJumpyTouchEvents; jmethodID getVirtualKeyQuietTimeMillis; - jmethodID getVirtualKeyDefinitions; - jmethodID getInputDeviceCalibration; jmethodID getExcludedDeviceNames; jmethodID getMaxEventsPerSecond; + jmethodID getPointerLayer; + jmethodID getPointerIcon; } gCallbacksClassInfo; static struct { jclass clazz; - - jfieldID scanCode; - jfieldID centerX; - jfieldID centerY; - jfieldID width; - jfieldID height; -} gVirtualKeyDefinitionClassInfo; - -static struct { - jclass clazz; - - jfieldID keys; - jfieldID values; -} gInputDeviceCalibrationClassInfo; - -static struct { - jclass clazz; - - jfieldID inputChannel; - jfieldID name; - jfieldID layoutParamsFlags; - jfieldID layoutParamsType; - jfieldID dispatchingTimeoutNanos; - jfieldID frameLeft; - jfieldID frameTop; - jfieldID frameRight; - jfieldID frameBottom; - jfieldID visibleFrameLeft; - jfieldID visibleFrameTop; - jfieldID visibleFrameRight; - jfieldID visibleFrameBottom; - jfieldID touchableAreaLeft; - jfieldID touchableAreaTop; - jfieldID touchableAreaRight; - jfieldID touchableAreaBottom; - jfieldID visible; - jfieldID canReceiveKeys; - jfieldID hasFocus; - jfieldID hasWallpaper; - jfieldID paused; - jfieldID layer; - jfieldID ownerPid; - jfieldID ownerUid; -} gInputWindowClassInfo; - -static struct { - jclass clazz; - - jfieldID name; - jfieldID dispatchingTimeoutNanos; - jfieldID token; -} gInputApplicationClassInfo; - -static struct { - jclass clazz; } gKeyEventClassInfo; static struct { @@ -146,13 +98,37 @@ static struct { jfieldID navigation; } gConfigurationClassInfo; -// ---------------------------------------------------------------------------- +static struct { + jclass clazz; + + jfieldID bitmap; + jfieldID hotSpotX; + jfieldID hotSpotY; +} gPointerIconClassInfo; + -static inline nsecs_t now() { - return systemTime(SYSTEM_TIME_MONOTONIC); +// --- Global functions --- + +static jobject getInputApplicationHandleObjLocalRef(JNIEnv* env, + const sp<InputApplicationHandle>& inputApplicationHandle) { + if (inputApplicationHandle == NULL) { + return NULL; + } + return static_cast<NativeInputApplicationHandle*>(inputApplicationHandle.get())-> + getInputApplicationHandleObjLocalRef(env); } -// ---------------------------------------------------------------------------- +static jobject getInputWindowHandleObjLocalRef(JNIEnv* env, + const sp<InputWindowHandle>& inputWindowHandle) { + if (inputWindowHandle == NULL) { + return NULL; + } + return static_cast<NativeInputWindowHandle*>(inputWindowHandle.get())-> + getInputWindowHandleObjLocalRef(env); +} + + +// --- NativeInputManager --- class NativeInputManager : public virtual RefBase, public virtual InputReaderPolicyInterface, @@ -171,7 +147,7 @@ public: void setDisplayOrientation(int32_t displayId, int32_t orientation); status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel, - jweak inputChannelObjWeak, bool monitor); + const sp<InputWindowHandle>& inputWindowHandle, bool monitor); status_t unregisterInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel); void setInputWindows(JNIEnv* env, jobjectArray windowObjArray); @@ -185,11 +161,8 @@ public: virtual bool filterTouchEvents(); virtual bool filterJumpyTouchEvents(); virtual nsecs_t getVirtualKeyQuietTime(); - virtual void getVirtualKeyDefinitions(const String8& deviceName, - Vector<VirtualKeyDefinition>& outVirtualKeyDefinitions); - virtual void getInputDeviceCalibration(const String8& deviceName, - InputDeviceCalibration& outCalibration); virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames); + virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId); /* --- InputDispatcherPolicyInterface implementation --- */ @@ -197,37 +170,22 @@ public: uint32_t policyFlags); virtual void notifyConfigurationChanged(nsecs_t when); virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle, - const sp<InputChannel>& inputChannel); - virtual void notifyInputChannelBroken(const sp<InputChannel>& inputChannel); + const sp<InputWindowHandle>& inputWindowHandle); + virtual void notifyInputChannelBroken(const sp<InputWindowHandle>& inputWindowHandle); virtual nsecs_t getKeyRepeatTimeout(); virtual nsecs_t getKeyRepeatDelay(); virtual int32_t getMaxEventsPerSecond(); - virtual void interceptKeyBeforeQueueing(nsecs_t when, int32_t deviceId, - int32_t action, int32_t& flags, int32_t keyCode, int32_t scanCode, - uint32_t& policyFlags); + virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags); virtual void interceptGenericBeforeQueueing(nsecs_t when, uint32_t& policyFlags); - virtual bool interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel, + virtual bool interceptKeyBeforeDispatching(const sp<InputWindowHandle>& inputWindowHandle, const KeyEvent* keyEvent, uint32_t policyFlags); + virtual bool dispatchUnhandledKey(const sp<InputWindowHandle>& inputWindowHandle, + const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent); virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType); virtual bool checkInjectEventsPermissionNonReentrant( int32_t injectorPid, int32_t injectorUid); private: - class ApplicationToken : public InputApplicationHandle { - jweak mTokenObjWeak; - - public: - ApplicationToken(jweak tokenObjWeak) : - mTokenObjWeak(tokenObjWeak) { } - - virtual ~ApplicationToken() { - JNIEnv* env = NativeInputManager::jniEnv(); - env->DeleteWeakGlobalRef(mTokenObjWeak); - } - - inline jweak getTokenObj() { return mTokenObjWeak; } - }; - sp<InputManager> mInputManager; jobject mCallbacksObj; @@ -240,23 +198,20 @@ private: // Cached throttling policy. int32_t mMaxEventsPerSecond; - // Cached display state. (lock mDisplayLock) - Mutex mDisplayLock; - int32_t mDisplayWidth, mDisplayHeight; - int32_t mDisplayOrientation; + Mutex mLock; + struct Locked { + // Display size information. + int32_t displayWidth, displayHeight; // -1 when initialized + int32_t displayOrientation; + + // Pointer controller singleton, created and destroyed as needed. + wp<PointerController> pointerController; + } mLocked; // Power manager interactions. bool isScreenOn(); bool isScreenBright(); - // Weak references to all currently registered input channels by connection pointer. - Mutex mInputChannelRegistryLock; - KeyedVector<InputChannel*, jweak> mInputChannelObjWeakTable; - - jobject getInputChannelObjLocal(JNIEnv* env, const sp<InputChannel>& inputChannel); - - static bool populateWindow(JNIEnv* env, jobject windowObj, InputWindow& outWindow); - static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName); static inline JNIEnv* jniEnv() { @@ -264,16 +219,22 @@ private: } }; -// ---------------------------------------------------------------------------- + NativeInputManager::NativeInputManager(jobject callbacksObj) : mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1), mVirtualKeyQuietTime(-1), - mMaxEventsPerSecond(-1), - mDisplayWidth(-1), mDisplayHeight(-1), mDisplayOrientation(ROTATION_0) { + mMaxEventsPerSecond(-1) { JNIEnv* env = jniEnv(); mCallbacksObj = env->NewGlobalRef(callbacksObj); + { + AutoMutex _l(mLock); + mLocked.displayWidth = -1; + mLocked.displayHeight = -1; + mLocked.displayOrientation = ROTATION_0; + } + sp<EventHub> eventHub = new EventHub(); mInputManager = new InputManager(eventHub, this, this); } @@ -304,119 +265,62 @@ bool NativeInputManager::checkAndClearExceptionFromCallback(JNIEnv* env, const c void NativeInputManager::setDisplaySize(int32_t displayId, int32_t width, int32_t height) { if (displayId == 0) { - AutoMutex _l(mDisplayLock); + AutoMutex _l(mLock); - mDisplayWidth = width; - mDisplayHeight = height; + if (mLocked.displayWidth != width || mLocked.displayHeight != height) { + mLocked.displayWidth = width; + mLocked.displayHeight = height; + + sp<PointerController> controller = mLocked.pointerController.promote(); + if (controller != NULL) { + controller->setDisplaySize(width, height); + } + } } } void NativeInputManager::setDisplayOrientation(int32_t displayId, int32_t orientation) { if (displayId == 0) { - AutoMutex _l(mDisplayLock); + AutoMutex _l(mLock); - mDisplayOrientation = orientation; - } -} + if (mLocked.displayOrientation != orientation) { + mLocked.displayOrientation = orientation; -status_t NativeInputManager::registerInputChannel(JNIEnv* env, - const sp<InputChannel>& inputChannel, jobject inputChannelObj, bool monitor) { - jweak inputChannelObjWeak = env->NewWeakGlobalRef(inputChannelObj); - if (! inputChannelObjWeak) { - LOGE("Could not create weak reference for input channel."); - LOGE_EX(env); - return NO_MEMORY; - } - - status_t status; - { - AutoMutex _l(mInputChannelRegistryLock); - - ssize_t index = mInputChannelObjWeakTable.indexOfKey(inputChannel.get()); - if (index >= 0) { - LOGE("Input channel object '%s' has already been registered", - inputChannel->getName().string()); - status = INVALID_OPERATION; - goto DeleteWeakRef; + sp<PointerController> controller = mLocked.pointerController.promote(); + if (controller != NULL) { + controller->setDisplayOrientation(orientation); + } } - - mInputChannelObjWeakTable.add(inputChannel.get(), inputChannelObjWeak); - } - - status = mInputManager->getDispatcher()->registerInputChannel(inputChannel, monitor); - if (! status) { - // Success. - return OK; - } - - // Failed! - { - AutoMutex _l(mInputChannelRegistryLock); - mInputChannelObjWeakTable.removeItem(inputChannel.get()); } +} -DeleteWeakRef: - env->DeleteWeakGlobalRef(inputChannelObjWeak); - return status; +status_t NativeInputManager::registerInputChannel(JNIEnv* env, + const sp<InputChannel>& inputChannel, + const sp<InputWindowHandle>& inputWindowHandle, bool monitor) { + return mInputManager->getDispatcher()->registerInputChannel( + inputChannel, inputWindowHandle, monitor); } status_t NativeInputManager::unregisterInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel) { - jweak inputChannelObjWeak; - { - AutoMutex _l(mInputChannelRegistryLock); - - ssize_t index = mInputChannelObjWeakTable.indexOfKey(inputChannel.get()); - if (index < 0) { - LOGE("Input channel object '%s' is not currently registered", - inputChannel->getName().string()); - return INVALID_OPERATION; - } - - inputChannelObjWeak = mInputChannelObjWeakTable.valueAt(index); - mInputChannelObjWeakTable.removeItemsAt(index); - } - - env->DeleteWeakGlobalRef(inputChannelObjWeak); - return mInputManager->getDispatcher()->unregisterInputChannel(inputChannel); } -jobject NativeInputManager::getInputChannelObjLocal(JNIEnv* env, - const sp<InputChannel>& inputChannel) { - InputChannel* inputChannelPtr = inputChannel.get(); - if (! inputChannelPtr) { - return NULL; - } - - { - AutoMutex _l(mInputChannelRegistryLock); - - ssize_t index = mInputChannelObjWeakTable.indexOfKey(inputChannelPtr); - if (index < 0) { - return NULL; - } - - jweak inputChannelObjWeak = mInputChannelObjWeakTable.valueAt(index); - return env->NewLocalRef(inputChannelObjWeak); - } -} - bool NativeInputManager::getDisplayInfo(int32_t displayId, int32_t* width, int32_t* height, int32_t* orientation) { bool result = false; if (displayId == 0) { - AutoMutex _l(mDisplayLock); + AutoMutex _l(mLock); - if (mDisplayWidth > 0) { + if (mLocked.displayWidth > 0 && mLocked.displayHeight > 0) { if (width) { - *width = mDisplayWidth; + *width = mLocked.displayWidth; } if (height) { - *height = mDisplayHeight; + *height = mLocked.displayHeight; } if (orientation) { - *orientation = mDisplayOrientation; + *orientation = mLocked.displayOrientation; } result = true; } @@ -472,83 +376,6 @@ nsecs_t NativeInputManager::getVirtualKeyQuietTime() { return mVirtualKeyQuietTime; } -void NativeInputManager::getVirtualKeyDefinitions(const String8& deviceName, - Vector<VirtualKeyDefinition>& outVirtualKeyDefinitions) { - outVirtualKeyDefinitions.clear(); - - JNIEnv* env = jniEnv(); - - jstring deviceNameStr = env->NewStringUTF(deviceName.string()); - if (! checkAndClearExceptionFromCallback(env, "getVirtualKeyDefinitions")) { - jobjectArray result = jobjectArray(env->CallObjectMethod(mCallbacksObj, - gCallbacksClassInfo.getVirtualKeyDefinitions, deviceNameStr)); - if (! checkAndClearExceptionFromCallback(env, "getVirtualKeyDefinitions") && result) { - jsize length = env->GetArrayLength(result); - for (jsize i = 0; i < length; i++) { - jobject item = env->GetObjectArrayElement(result, i); - - outVirtualKeyDefinitions.add(); - outVirtualKeyDefinitions.editTop().scanCode = - int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.scanCode)); - outVirtualKeyDefinitions.editTop().centerX = - int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.centerX)); - outVirtualKeyDefinitions.editTop().centerY = - int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.centerY)); - outVirtualKeyDefinitions.editTop().width = - int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.width)); - outVirtualKeyDefinitions.editTop().height = - int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.height)); - - env->DeleteLocalRef(item); - } - env->DeleteLocalRef(result); - } - env->DeleteLocalRef(deviceNameStr); - } -} - -void NativeInputManager::getInputDeviceCalibration(const String8& deviceName, - InputDeviceCalibration& outCalibration) { - outCalibration.clear(); - - JNIEnv* env = jniEnv(); - - jstring deviceNameStr = env->NewStringUTF(deviceName.string()); - if (! checkAndClearExceptionFromCallback(env, "getInputDeviceCalibration")) { - jobject result = env->CallObjectMethod(mCallbacksObj, - gCallbacksClassInfo.getInputDeviceCalibration, deviceNameStr); - if (! checkAndClearExceptionFromCallback(env, "getInputDeviceCalibration") && result) { - jobjectArray keys = jobjectArray(env->GetObjectField(result, - gInputDeviceCalibrationClassInfo.keys)); - jobjectArray values = jobjectArray(env->GetObjectField(result, - gInputDeviceCalibrationClassInfo.values)); - - jsize length = env->GetArrayLength(keys); - for (jsize i = 0; i < length; i++) { - jstring keyStr = jstring(env->GetObjectArrayElement(keys, i)); - jstring valueStr = jstring(env->GetObjectArrayElement(values, i)); - - const char* keyChars = env->GetStringUTFChars(keyStr, NULL); - String8 key(keyChars); - env->ReleaseStringUTFChars(keyStr, keyChars); - - const char* valueChars = env->GetStringUTFChars(valueStr, NULL); - String8 value(valueChars); - env->ReleaseStringUTFChars(valueStr, valueChars); - - outCalibration.addProperty(key, value); - - env->DeleteLocalRef(keyStr); - env->DeleteLocalRef(valueStr); - } - env->DeleteLocalRef(keys); - env->DeleteLocalRef(values); - env->DeleteLocalRef(result); - } - env->DeleteLocalRef(deviceNameStr); - } -} - void NativeInputManager::getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames) { outExcludedDeviceNames.clear(); @@ -571,6 +398,41 @@ void NativeInputManager::getExcludedDeviceNames(Vector<String8>& outExcludedDevi } } +sp<PointerControllerInterface> NativeInputManager::obtainPointerController(int32_t deviceId) { + AutoMutex _l(mLock); + + sp<PointerController> controller = mLocked.pointerController.promote(); + if (controller == NULL) { + JNIEnv* env = jniEnv(); + jint layer = env->CallIntMethod(mCallbacksObj, gCallbacksClassInfo.getPointerLayer); + if (checkAndClearExceptionFromCallback(env, "getPointerLayer")) { + layer = -1; + } + + controller = new PointerController(layer); + mLocked.pointerController = controller; + + controller->setDisplaySize(mLocked.displayWidth, mLocked.displayHeight); + controller->setDisplayOrientation(mLocked.displayOrientation); + + jobject iconObj = env->CallObjectMethod(mCallbacksObj, gCallbacksClassInfo.getPointerIcon); + if (!checkAndClearExceptionFromCallback(env, "getPointerIcon") && iconObj) { + jfloat iconHotSpotX = env->GetFloatField(iconObj, gPointerIconClassInfo.hotSpotX); + jfloat iconHotSpotY = env->GetFloatField(iconObj, gPointerIconClassInfo.hotSpotY); + jobject iconBitmapObj = env->GetObjectField(iconObj, gPointerIconClassInfo.bitmap); + if (iconBitmapObj) { + SkBitmap* iconBitmap = GraphicsJNI::getNativeBitmap(env, iconBitmapObj); + if (iconBitmap) { + controller->setPointerIcon(iconBitmap, iconHotSpotX, iconHotSpotY); + } + env->DeleteLocalRef(iconBitmapObj); + } + env->DeleteLocalRef(iconObj); + } + } + return controller; +} + void NativeInputManager::notifySwitch(nsecs_t when, int32_t switchCode, int32_t switchValue, uint32_t policyFlags) { #if DEBUG_INPUT_DISPATCHER_POLICY @@ -601,50 +463,46 @@ void NativeInputManager::notifyConfigurationChanged(nsecs_t when) { } nsecs_t NativeInputManager::notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle, - const sp<InputChannel>& inputChannel) { + const sp<InputWindowHandle>& inputWindowHandle) { #if DEBUG_INPUT_DISPATCHER_POLICY LOGD("notifyANR"); #endif JNIEnv* env = jniEnv(); - jobject tokenObjLocal; - if (inputApplicationHandle.get()) { - ApplicationToken* token = static_cast<ApplicationToken*>(inputApplicationHandle.get()); - jweak tokenObjWeak = token->getTokenObj(); - tokenObjLocal = env->NewLocalRef(tokenObjWeak); - } else { - tokenObjLocal = NULL; - } + jobject inputApplicationHandleObj = + getInputApplicationHandleObjLocalRef(env, inputApplicationHandle); + jobject inputWindowHandleObj = + getInputWindowHandleObjLocalRef(env, inputWindowHandle); - jobject inputChannelObjLocal = getInputChannelObjLocal(env, inputChannel); jlong newTimeout = env->CallLongMethod(mCallbacksObj, - gCallbacksClassInfo.notifyANR, tokenObjLocal, inputChannelObjLocal); + gCallbacksClassInfo.notifyANR, inputApplicationHandleObj, inputWindowHandleObj); if (checkAndClearExceptionFromCallback(env, "notifyANR")) { newTimeout = 0; // abort dispatch } else { assert(newTimeout >= 0); } - env->DeleteLocalRef(tokenObjLocal); - env->DeleteLocalRef(inputChannelObjLocal); + env->DeleteLocalRef(inputWindowHandleObj); + env->DeleteLocalRef(inputApplicationHandleObj); return newTimeout; } -void NativeInputManager::notifyInputChannelBroken(const sp<InputChannel>& inputChannel) { +void NativeInputManager::notifyInputChannelBroken(const sp<InputWindowHandle>& inputWindowHandle) { #if DEBUG_INPUT_DISPATCHER_POLICY - LOGD("notifyInputChannelBroken - inputChannel='%s'", inputChannel->getName().string()); + LOGD("notifyInputChannelBroken"); #endif JNIEnv* env = jniEnv(); - jobject inputChannelObjLocal = getInputChannelObjLocal(env, inputChannel); - if (inputChannelObjLocal) { + jobject inputWindowHandleObj = + getInputWindowHandleObjLocalRef(env, inputWindowHandle); + if (inputWindowHandleObj) { env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyInputChannelBroken, - inputChannelObjLocal); + inputWindowHandleObj); checkAndClearExceptionFromCallback(env, "notifyInputChannelBroken"); - env->DeleteLocalRef(inputChannelObjLocal); + env->DeleteLocalRef(inputWindowHandleObj); } } @@ -682,159 +540,32 @@ void NativeInputManager::setInputWindows(JNIEnv* env, jobjectArray windowObjArra jsize length = env->GetArrayLength(windowObjArray); for (jsize i = 0; i < length; i++) { - jobject inputTargetObj = env->GetObjectArrayElement(windowObjArray, i); - if (! inputTargetObj) { + jobject windowObj = env->GetObjectArrayElement(windowObjArray, i); + if (! windowObj) { break; // found null element indicating end of used portion of the array } windows.push(); InputWindow& window = windows.editTop(); - bool valid = populateWindow(env, inputTargetObj, window); - if (! valid) { + android_server_InputWindow_toNative(env, windowObj, &window); + if (window.inputChannel == NULL) { windows.pop(); } - - env->DeleteLocalRef(inputTargetObj); + env->DeleteLocalRef(windowObj); } mInputManager->getDispatcher()->setInputWindows(windows); } -bool NativeInputManager::populateWindow(JNIEnv* env, jobject windowObj, - InputWindow& outWindow) { - bool valid = false; - - jobject inputChannelObj = env->GetObjectField(windowObj, - gInputWindowClassInfo.inputChannel); - if (inputChannelObj) { - sp<InputChannel> inputChannel = - android_view_InputChannel_getInputChannel(env, inputChannelObj); - if (inputChannel != NULL) { - jstring name = jstring(env->GetObjectField(windowObj, - gInputWindowClassInfo.name)); - jint layoutParamsFlags = env->GetIntField(windowObj, - gInputWindowClassInfo.layoutParamsFlags); - jint layoutParamsType = env->GetIntField(windowObj, - gInputWindowClassInfo.layoutParamsType); - jlong dispatchingTimeoutNanos = env->GetLongField(windowObj, - gInputWindowClassInfo.dispatchingTimeoutNanos); - jint frameLeft = env->GetIntField(windowObj, - gInputWindowClassInfo.frameLeft); - jint frameTop = env->GetIntField(windowObj, - gInputWindowClassInfo.frameTop); - jint frameRight = env->GetIntField(windowObj, - gInputWindowClassInfo.frameRight); - jint frameBottom = env->GetIntField(windowObj, - gInputWindowClassInfo.frameBottom); - jint visibleFrameLeft = env->GetIntField(windowObj, - gInputWindowClassInfo.visibleFrameLeft); - jint visibleFrameTop = env->GetIntField(windowObj, - gInputWindowClassInfo.visibleFrameTop); - jint visibleFrameRight = env->GetIntField(windowObj, - gInputWindowClassInfo.visibleFrameRight); - jint visibleFrameBottom = env->GetIntField(windowObj, - gInputWindowClassInfo.visibleFrameBottom); - jint touchableAreaLeft = env->GetIntField(windowObj, - gInputWindowClassInfo.touchableAreaLeft); - jint touchableAreaTop = env->GetIntField(windowObj, - gInputWindowClassInfo.touchableAreaTop); - jint touchableAreaRight = env->GetIntField(windowObj, - gInputWindowClassInfo.touchableAreaRight); - jint touchableAreaBottom = env->GetIntField(windowObj, - gInputWindowClassInfo.touchableAreaBottom); - jboolean visible = env->GetBooleanField(windowObj, - gInputWindowClassInfo.visible); - jboolean canReceiveKeys = env->GetBooleanField(windowObj, - gInputWindowClassInfo.canReceiveKeys); - jboolean hasFocus = env->GetBooleanField(windowObj, - gInputWindowClassInfo.hasFocus); - jboolean hasWallpaper = env->GetBooleanField(windowObj, - gInputWindowClassInfo.hasWallpaper); - jboolean paused = env->GetBooleanField(windowObj, - gInputWindowClassInfo.paused); - jint layer = env->GetIntField(windowObj, - gInputWindowClassInfo.layer); - jint ownerPid = env->GetIntField(windowObj, - gInputWindowClassInfo.ownerPid); - jint ownerUid = env->GetIntField(windowObj, - gInputWindowClassInfo.ownerUid); - - const char* nameStr = env->GetStringUTFChars(name, NULL); - - outWindow.inputChannel = inputChannel; - outWindow.name.setTo(nameStr); - outWindow.layoutParamsFlags = layoutParamsFlags; - outWindow.layoutParamsType = layoutParamsType; - outWindow.dispatchingTimeout = dispatchingTimeoutNanos; - outWindow.frameLeft = frameLeft; - outWindow.frameTop = frameTop; - outWindow.frameRight = frameRight; - outWindow.frameBottom = frameBottom; - outWindow.visibleFrameLeft = visibleFrameLeft; - outWindow.visibleFrameTop = visibleFrameTop; - outWindow.visibleFrameRight = visibleFrameRight; - outWindow.visibleFrameBottom = visibleFrameBottom; - outWindow.touchableAreaLeft = touchableAreaLeft; - outWindow.touchableAreaTop = touchableAreaTop; - outWindow.touchableAreaRight = touchableAreaRight; - outWindow.touchableAreaBottom = touchableAreaBottom; - outWindow.visible = visible; - outWindow.canReceiveKeys = canReceiveKeys; - outWindow.hasFocus = hasFocus; - outWindow.hasWallpaper = hasWallpaper; - outWindow.paused = paused; - outWindow.layer = layer; - outWindow.ownerPid = ownerPid; - outWindow.ownerUid = ownerUid; - - env->ReleaseStringUTFChars(name, nameStr); - valid = true; - } else { - LOGW("Dropping input target because its input channel is not initialized."); - } - - env->DeleteLocalRef(inputChannelObj); - } else { - LOGW("Dropping input target because the input channel object was null."); - } - return valid; -} - void NativeInputManager::setFocusedApplication(JNIEnv* env, jobject applicationObj) { if (applicationObj) { - jstring nameObj = jstring(env->GetObjectField(applicationObj, - gInputApplicationClassInfo.name)); - jlong dispatchingTimeoutNanos = env->GetLongField(applicationObj, - gInputApplicationClassInfo.dispatchingTimeoutNanos); - jobject tokenObj = env->GetObjectField(applicationObj, - gInputApplicationClassInfo.token); - jweak tokenObjWeak = env->NewWeakGlobalRef(tokenObj); - if (! tokenObjWeak) { - LOGE("Could not create weak reference for application token."); - LOGE_EX(env); - env->ExceptionClear(); - } - env->DeleteLocalRef(tokenObj); - - String8 name; - if (nameObj) { - const char* nameStr = env->GetStringUTFChars(nameObj, NULL); - name.setTo(nameStr); - env->ReleaseStringUTFChars(nameObj, nameStr); - env->DeleteLocalRef(nameObj); - } else { - LOGE("InputApplication.name should not be null."); - name.setTo("unknown"); - } - InputApplication application; - application.name = name; - application.dispatchingTimeout = dispatchingTimeoutNanos; - application.handle = new ApplicationToken(tokenObjWeak); - mInputManager->getDispatcher()->setFocusedApplication(& application); - } else { - mInputManager->getDispatcher()->setFocusedApplication(NULL); + android_server_InputApplication_toNative(env, applicationObj, &application); + if (application.inputApplicationHandle != NULL) { + mInputManager->getDispatcher()->setFocusedApplication(&application); + } } + mInputManager->getDispatcher()->setFocusedApplication(NULL); } void NativeInputManager::setInputDispatchMode(bool enabled, bool frozen) { @@ -849,20 +580,8 @@ bool NativeInputManager::isScreenBright() { return android_server_PowerManagerService_isScreenBright(); } -void NativeInputManager::interceptKeyBeforeQueueing(nsecs_t when, - int32_t deviceId, int32_t action, int32_t &flags, - int32_t keyCode, int32_t scanCode, uint32_t& policyFlags) { -#if DEBUG_INPUT_DISPATCHER_POLICY - LOGD("interceptKeyBeforeQueueing - when=%lld, deviceId=%d, action=%d, flags=%d, " - "keyCode=%d, scanCode=%d, policyFlags=0x%x", - when, deviceId, action, flags, keyCode, scanCode, policyFlags); -#endif - - if ((policyFlags & POLICY_FLAG_VIRTUAL) || (flags & AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY)) { - policyFlags |= POLICY_FLAG_VIRTUAL; - flags |= AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY; - } - +void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent, + uint32_t& policyFlags) { // Policy: // - Ignore untrusted events and pass them along. // - Ask the window manager what to do with normal events and trusted injected events. @@ -872,21 +591,30 @@ void NativeInputManager::interceptKeyBeforeQueueing(nsecs_t when, const int32_t WM_ACTION_POKE_USER_ACTIVITY = 2; const int32_t WM_ACTION_GO_TO_SLEEP = 4; + nsecs_t when = keyEvent->getEventTime(); bool isScreenOn = this->isScreenOn(); bool isScreenBright = this->isScreenBright(); JNIEnv* env = jniEnv(); - jint wmActions = env->CallIntMethod(mCallbacksObj, - gCallbacksClassInfo.interceptKeyBeforeQueueing, - when, action, flags, keyCode, scanCode, policyFlags, isScreenOn); - if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeQueueing")) { + jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent); + jint wmActions; + if (keyEventObj) { + wmActions = env->CallIntMethod(mCallbacksObj, + gCallbacksClassInfo.interceptKeyBeforeQueueing, + keyEventObj, policyFlags, isScreenOn); + if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeQueueing")) { + wmActions = 0; + } + android_view_KeyEvent_recycle(env, keyEventObj); + env->DeleteLocalRef(keyEventObj); + } else { + LOGE("Failed to obtain key event object for interceptKeyBeforeQueueing."); wmActions = 0; } - if (!(flags & POLICY_FLAG_INJECTED)) { + if (!(policyFlags & POLICY_FLAG_INJECTED)) { if (!isScreenOn) { policyFlags |= POLICY_FLAG_WOKE_HERE; - flags |= AKEY_EVENT_FLAG_WOKE_HERE; } if (!isScreenBright) { @@ -911,10 +639,6 @@ void NativeInputManager::interceptKeyBeforeQueueing(nsecs_t when, } void NativeInputManager::interceptGenericBeforeQueueing(nsecs_t when, uint32_t& policyFlags) { -#if DEBUG_INPUT_DISPATCHER_POLICY - LOGD("interceptGenericBeforeQueueing - when=%lld, policyFlags=0x%x", when, policyFlags); -#endif - // Policy: // - Ignore untrusted events and pass them along. // - No special filtering for injected events required at this time. @@ -933,29 +657,70 @@ void NativeInputManager::interceptGenericBeforeQueueing(nsecs_t when, uint32_t& } } -bool NativeInputManager::interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel, +bool NativeInputManager::interceptKeyBeforeDispatching( + const sp<InputWindowHandle>& inputWindowHandle, const KeyEvent* keyEvent, uint32_t policyFlags) { // Policy: // - Ignore untrusted events and pass them along. // - Filter normal events and trusted injected events through the window manager policy to // handle the HOME key and the like. + bool result = false; if (policyFlags & POLICY_FLAG_TRUSTED) { JNIEnv* env = jniEnv(); - // Note: inputChannel may be null. - jobject inputChannelObj = getInputChannelObjLocal(env, inputChannel); - jboolean consumed = env->CallBooleanMethod(mCallbacksObj, - gCallbacksClassInfo.interceptKeyBeforeDispatching, - inputChannelObj, keyEvent->getAction(), keyEvent->getFlags(), - keyEvent->getKeyCode(), keyEvent->getScanCode(), keyEvent->getMetaState(), - keyEvent->getRepeatCount(), policyFlags); - bool error = checkAndClearExceptionFromCallback(env, "interceptKeyBeforeDispatching"); - - env->DeleteLocalRef(inputChannelObj); - return consumed && ! error; - } else { - return false; + // Note: inputWindowHandle may be null. + jobject inputWindowHandleObj = getInputWindowHandleObjLocalRef(env, inputWindowHandle); + jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent); + if (keyEventObj) { + jboolean consumed = env->CallBooleanMethod(mCallbacksObj, + gCallbacksClassInfo.interceptKeyBeforeDispatching, + inputWindowHandleObj, keyEventObj, policyFlags); + bool error = checkAndClearExceptionFromCallback(env, "interceptKeyBeforeDispatching"); + android_view_KeyEvent_recycle(env, keyEventObj); + env->DeleteLocalRef(keyEventObj); + result = consumed && !error; + } else { + LOGE("Failed to obtain key event object for interceptKeyBeforeDispatching."); + } + env->DeleteLocalRef(inputWindowHandleObj); } + return result; +} + +bool NativeInputManager::dispatchUnhandledKey(const sp<InputWindowHandle>& inputWindowHandle, + const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) { + // Policy: + // - Ignore untrusted events and do not perform default handling. + bool result = false; + if (policyFlags & POLICY_FLAG_TRUSTED) { + JNIEnv* env = jniEnv(); + + // Note: inputWindowHandle may be null. + jobject inputWindowHandleObj = getInputWindowHandleObjLocalRef(env, inputWindowHandle); + jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent); + if (keyEventObj) { + jobject fallbackKeyEventObj = env->CallObjectMethod(mCallbacksObj, + gCallbacksClassInfo.dispatchUnhandledKey, + inputWindowHandleObj, keyEventObj, policyFlags); + checkAndClearExceptionFromCallback(env, "dispatchUnhandledKey"); + android_view_KeyEvent_recycle(env, keyEventObj); + env->DeleteLocalRef(keyEventObj); + + if (fallbackKeyEventObj) { + // Note: outFallbackKeyEvent may be the same object as keyEvent. + if (!android_view_KeyEvent_toNative(env, fallbackKeyEventObj, + outFallbackKeyEvent)) { + result = true; + } + android_view_KeyEvent_recycle(env, fallbackKeyEventObj); + env->DeleteLocalRef(fallbackKeyEventObj); + } + } else { + LOGE("Failed to obtain key event object for dispatchUnhandledKey."); + } + env->DeleteLocalRef(inputWindowHandleObj); + } + return result; } void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType) { @@ -972,6 +737,7 @@ bool NativeInputManager::checkInjectEventsPermissionNonReentrant( return result; } + // ---------------------------------------------------------------------------- static sp<NativeInputManager> gNativeInputManager; @@ -1096,7 +862,7 @@ static void android_server_InputManager_handleInputChannelDisposed(JNIEnv* env, } static void android_server_InputManager_nativeRegisterInputChannel(JNIEnv* env, jclass clazz, - jobject inputChannelObj, jboolean monitor) { + jobject inputChannelObj, jobject inputWindowHandleObj, jboolean monitor) { if (checkInputManagerUnitialized(env)) { return; } @@ -1108,9 +874,11 @@ static void android_server_InputManager_nativeRegisterInputChannel(JNIEnv* env, return; } + sp<InputWindowHandle> inputWindowHandle = + android_server_InputWindowHandle_getHandle(env, inputWindowHandleObj); status_t status = gNativeInputManager->registerInputChannel( - env, inputChannel, inputChannelObj, monitor); + env, inputChannel, inputWindowHandle, monitor); if (status) { jniThrowRuntimeException(env, "Failed to register input channel. " "Check logs for details."); @@ -1154,13 +922,21 @@ static jint android_server_InputManager_nativeInjectInputEvent(JNIEnv* env, jcla if (env->IsInstanceOf(inputEventObj, gKeyEventClassInfo.clazz)) { KeyEvent keyEvent; - android_view_KeyEvent_toNative(env, inputEventObj, & keyEvent); + status_t status = android_view_KeyEvent_toNative(env, inputEventObj, & keyEvent); + if (status) { + jniThrowRuntimeException(env, "Could not read contents of KeyEvent object."); + return INPUT_EVENT_INJECTION_FAILED; + } return gNativeInputManager->getInputManager()->getDispatcher()->injectInputEvent( & keyEvent, injectorPid, injectorUid, syncMode, timeoutMillis); } else if (env->IsInstanceOf(inputEventObj, gMotionEventClassInfo.clazz)) { MotionEvent motionEvent; - android_view_MotionEvent_toNative(env, inputEventObj, & motionEvent); + status_t status = android_view_MotionEvent_toNative(env, inputEventObj, & motionEvent); + if (status) { + jniThrowRuntimeException(env, "Could not read contents of MotionEvent object."); + return INPUT_EVENT_INJECTION_FAILED; + } return gNativeInputManager->getInputManager()->getDispatcher()->injectInputEvent( & motionEvent, injectorPid, injectorUid, syncMode, timeoutMillis); @@ -1271,6 +1047,25 @@ static void android_server_InputManager_nativeGetInputConfiguration(JNIEnv* env, env->SetIntField(configObj, gConfigurationClassInfo.navigation, config.navigation); } +static jboolean android_server_InputManager_nativeTransferTouchFocus(JNIEnv* env, + jclass clazz, jobject fromChannelObj, jobject toChannelObj) { + if (checkInputManagerUnitialized(env)) { + return false; + } + + sp<InputChannel> fromChannel = + android_view_InputChannel_getInputChannel(env, fromChannelObj); + sp<InputChannel> toChannel = + android_view_InputChannel_getInputChannel(env, toChannelObj); + + if (fromChannel == NULL || toChannel == NULL) { + return false; + } + + return gNativeInputManager->getInputManager()->getDispatcher()-> + transferTouchFocus(fromChannel, toChannel); +} + static jstring android_server_InputManager_nativeDump(JNIEnv* env, jclass clazz) { if (checkInputManagerUnitialized(env)) { return NULL; @@ -1301,7 +1096,8 @@ static JNINativeMethod gInputManagerMethods[] = { (void*) android_server_InputManager_nativeGetSwitchState }, { "nativeHasKeys", "(II[I[Z)Z", (void*) android_server_InputManager_nativeHasKeys }, - { "nativeRegisterInputChannel", "(Landroid/view/InputChannel;Z)V", + { "nativeRegisterInputChannel", + "(Landroid/view/InputChannel;Lcom/android/server/InputWindowHandle;Z)V", (void*) android_server_InputManager_nativeRegisterInputChannel }, { "nativeUnregisterInputChannel", "(Landroid/view/InputChannel;)V", (void*) android_server_InputManager_nativeUnregisterInputChannel }, @@ -1319,6 +1115,8 @@ static JNINativeMethod gInputManagerMethods[] = { (void*) android_server_InputManager_nativeGetInputDeviceIds }, { "nativeGetInputConfiguration", "(Landroid/content/res/Configuration;)V", (void*) android_server_InputManager_nativeGetInputConfiguration }, + { "nativeTransferTouchFocus", "(Landroid/view/InputChannel;Landroid/view/InputChannel;)Z", + (void*) android_server_InputManager_nativeTransferTouchFocus }, { "nativeDump", "()Ljava/lang/String;", (void*) android_server_InputManager_nativeDump }, }; @@ -1352,16 +1150,22 @@ int register_android_server_InputManager(JNIEnv* env) { "notifyLidSwitchChanged", "(JZ)V"); GET_METHOD_ID(gCallbacksClassInfo.notifyInputChannelBroken, gCallbacksClassInfo.clazz, - "notifyInputChannelBroken", "(Landroid/view/InputChannel;)V"); + "notifyInputChannelBroken", "(Lcom/android/server/InputWindowHandle;)V"); GET_METHOD_ID(gCallbacksClassInfo.notifyANR, gCallbacksClassInfo.clazz, - "notifyANR", "(Ljava/lang/Object;Landroid/view/InputChannel;)J"); + "notifyANR", + "(Lcom/android/server/InputApplicationHandle;Lcom/android/server/InputWindowHandle;)J"); GET_METHOD_ID(gCallbacksClassInfo.interceptKeyBeforeQueueing, gCallbacksClassInfo.clazz, - "interceptKeyBeforeQueueing", "(JIIIIIZ)I"); + "interceptKeyBeforeQueueing", "(Landroid/view/KeyEvent;IZ)I"); GET_METHOD_ID(gCallbacksClassInfo.interceptKeyBeforeDispatching, gCallbacksClassInfo.clazz, - "interceptKeyBeforeDispatching", "(Landroid/view/InputChannel;IIIIIII)Z"); + "interceptKeyBeforeDispatching", + "(Lcom/android/server/InputWindowHandle;Landroid/view/KeyEvent;I)Z"); + + GET_METHOD_ID(gCallbacksClassInfo.dispatchUnhandledKey, gCallbacksClassInfo.clazz, + "dispatchUnhandledKey", + "(Lcom/android/server/InputWindowHandle;Landroid/view/KeyEvent;I)Landroid/view/KeyEvent;"); GET_METHOD_ID(gCallbacksClassInfo.checkInjectEventsPermission, gCallbacksClassInfo.clazz, "checkInjectEventsPermission", "(II)Z"); @@ -1375,143 +1179,17 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gCallbacksClassInfo.getVirtualKeyQuietTimeMillis, gCallbacksClassInfo.clazz, "getVirtualKeyQuietTimeMillis", "()I"); - GET_METHOD_ID(gCallbacksClassInfo.getVirtualKeyDefinitions, gCallbacksClassInfo.clazz, - "getVirtualKeyDefinitions", - "(Ljava/lang/String;)[Lcom/android/server/InputManager$VirtualKeyDefinition;"); - - GET_METHOD_ID(gCallbacksClassInfo.getInputDeviceCalibration, gCallbacksClassInfo.clazz, - "getInputDeviceCalibration", - "(Ljava/lang/String;)Lcom/android/server/InputManager$InputDeviceCalibration;"); - GET_METHOD_ID(gCallbacksClassInfo.getExcludedDeviceNames, gCallbacksClassInfo.clazz, "getExcludedDeviceNames", "()[Ljava/lang/String;"); GET_METHOD_ID(gCallbacksClassInfo.getMaxEventsPerSecond, gCallbacksClassInfo.clazz, "getMaxEventsPerSecond", "()I"); - // VirtualKeyDefinition - - FIND_CLASS(gVirtualKeyDefinitionClassInfo.clazz, - "com/android/server/InputManager$VirtualKeyDefinition"); - - GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.scanCode, gVirtualKeyDefinitionClassInfo.clazz, - "scanCode", "I"); - - GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.centerX, gVirtualKeyDefinitionClassInfo.clazz, - "centerX", "I"); - - GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.centerY, gVirtualKeyDefinitionClassInfo.clazz, - "centerY", "I"); - - GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.width, gVirtualKeyDefinitionClassInfo.clazz, - "width", "I"); - - GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.height, gVirtualKeyDefinitionClassInfo.clazz, - "height", "I"); - - // InputDeviceCalibration - - FIND_CLASS(gInputDeviceCalibrationClassInfo.clazz, - "com/android/server/InputManager$InputDeviceCalibration"); - - GET_FIELD_ID(gInputDeviceCalibrationClassInfo.keys, gInputDeviceCalibrationClassInfo.clazz, - "keys", "[Ljava/lang/String;"); - - GET_FIELD_ID(gInputDeviceCalibrationClassInfo.values, gInputDeviceCalibrationClassInfo.clazz, - "values", "[Ljava/lang/String;"); - - // InputWindow - - FIND_CLASS(gInputWindowClassInfo.clazz, "com/android/server/InputWindow"); - - GET_FIELD_ID(gInputWindowClassInfo.inputChannel, gInputWindowClassInfo.clazz, - "inputChannel", "Landroid/view/InputChannel;"); - - GET_FIELD_ID(gInputWindowClassInfo.name, gInputWindowClassInfo.clazz, - "name", "Ljava/lang/String;"); - - GET_FIELD_ID(gInputWindowClassInfo.layoutParamsFlags, gInputWindowClassInfo.clazz, - "layoutParamsFlags", "I"); - - GET_FIELD_ID(gInputWindowClassInfo.layoutParamsType, gInputWindowClassInfo.clazz, - "layoutParamsType", "I"); - - GET_FIELD_ID(gInputWindowClassInfo.dispatchingTimeoutNanos, gInputWindowClassInfo.clazz, - "dispatchingTimeoutNanos", "J"); - - GET_FIELD_ID(gInputWindowClassInfo.frameLeft, gInputWindowClassInfo.clazz, - "frameLeft", "I"); - - GET_FIELD_ID(gInputWindowClassInfo.frameTop, gInputWindowClassInfo.clazz, - "frameTop", "I"); + GET_METHOD_ID(gCallbacksClassInfo.getPointerLayer, gCallbacksClassInfo.clazz, + "getPointerLayer", "()I"); - GET_FIELD_ID(gInputWindowClassInfo.frameRight, gInputWindowClassInfo.clazz, - "frameRight", "I"); - - GET_FIELD_ID(gInputWindowClassInfo.frameBottom, gInputWindowClassInfo.clazz, - "frameBottom", "I"); - - GET_FIELD_ID(gInputWindowClassInfo.visibleFrameLeft, gInputWindowClassInfo.clazz, - "visibleFrameLeft", "I"); - - GET_FIELD_ID(gInputWindowClassInfo.visibleFrameTop, gInputWindowClassInfo.clazz, - "visibleFrameTop", "I"); - - GET_FIELD_ID(gInputWindowClassInfo.visibleFrameRight, gInputWindowClassInfo.clazz, - "visibleFrameRight", "I"); - - GET_FIELD_ID(gInputWindowClassInfo.visibleFrameBottom, gInputWindowClassInfo.clazz, - "visibleFrameBottom", "I"); - - GET_FIELD_ID(gInputWindowClassInfo.touchableAreaLeft, gInputWindowClassInfo.clazz, - "touchableAreaLeft", "I"); - - GET_FIELD_ID(gInputWindowClassInfo.touchableAreaTop, gInputWindowClassInfo.clazz, - "touchableAreaTop", "I"); - - GET_FIELD_ID(gInputWindowClassInfo.touchableAreaRight, gInputWindowClassInfo.clazz, - "touchableAreaRight", "I"); - - GET_FIELD_ID(gInputWindowClassInfo.touchableAreaBottom, gInputWindowClassInfo.clazz, - "touchableAreaBottom", "I"); - - GET_FIELD_ID(gInputWindowClassInfo.visible, gInputWindowClassInfo.clazz, - "visible", "Z"); - - GET_FIELD_ID(gInputWindowClassInfo.canReceiveKeys, gInputWindowClassInfo.clazz, - "canReceiveKeys", "Z"); - - GET_FIELD_ID(gInputWindowClassInfo.hasFocus, gInputWindowClassInfo.clazz, - "hasFocus", "Z"); - - GET_FIELD_ID(gInputWindowClassInfo.hasWallpaper, gInputWindowClassInfo.clazz, - "hasWallpaper", "Z"); - - GET_FIELD_ID(gInputWindowClassInfo.paused, gInputWindowClassInfo.clazz, - "paused", "Z"); - - GET_FIELD_ID(gInputWindowClassInfo.layer, gInputWindowClassInfo.clazz, - "layer", "I"); - - GET_FIELD_ID(gInputWindowClassInfo.ownerPid, gInputWindowClassInfo.clazz, - "ownerPid", "I"); - - GET_FIELD_ID(gInputWindowClassInfo.ownerUid, gInputWindowClassInfo.clazz, - "ownerUid", "I"); - - // InputApplication - - FIND_CLASS(gInputApplicationClassInfo.clazz, "com/android/server/InputApplication"); - - GET_FIELD_ID(gInputApplicationClassInfo.name, gInputApplicationClassInfo.clazz, - "name", "Ljava/lang/String;"); - - GET_FIELD_ID(gInputApplicationClassInfo.dispatchingTimeoutNanos, - gInputApplicationClassInfo.clazz, - "dispatchingTimeoutNanos", "J"); - - GET_FIELD_ID(gInputApplicationClassInfo.token, gInputApplicationClassInfo.clazz, - "token", "Ljava/lang/Object;"); + GET_METHOD_ID(gCallbacksClassInfo.getPointerIcon, gCallbacksClassInfo.clazz, + "getPointerIcon", "()Lcom/android/server/InputManager$PointerIcon;"); // KeyEvent @@ -1559,6 +1237,19 @@ int register_android_server_InputManager(JNIEnv* env) { GET_FIELD_ID(gConfigurationClassInfo.navigation, gConfigurationClassInfo.clazz, "navigation", "I"); + // PointerIcon + + FIND_CLASS(gPointerIconClassInfo.clazz, "com/android/server/InputManager$PointerIcon"); + + GET_FIELD_ID(gPointerIconClassInfo.bitmap, gPointerIconClassInfo.clazz, + "bitmap", "Landroid/graphics/Bitmap;"); + + GET_FIELD_ID(gPointerIconClassInfo.hotSpotX, gPointerIconClassInfo.clazz, + "hotSpotX", "F"); + + GET_FIELD_ID(gPointerIconClassInfo.hotSpotY, gPointerIconClassInfo.clazz, + "hotSpotY", "F"); + return 0; } diff --git a/services/jni/com_android_server_InputWindow.cpp b/services/jni/com_android_server_InputWindow.cpp new file mode 100644 index 0000000..7515456 --- /dev/null +++ b/services/jni/com_android_server_InputWindow.cpp @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2011 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. + */ + +#define LOG_TAG "InputWindow" + +#include "JNIHelp.h" +#include "jni.h" +#include <android_runtime/AndroidRuntime.h> + +#include <android_view_InputChannel.h> +#include <android/graphics/Region.h> +#include "com_android_server_InputWindow.h" +#include "com_android_server_InputWindowHandle.h" + +namespace android { + +static struct { + jclass clazz; + + jfieldID inputWindowHandle; + jfieldID inputChannel; + jfieldID name; + jfieldID layoutParamsFlags; + jfieldID layoutParamsType; + jfieldID dispatchingTimeoutNanos; + jfieldID frameLeft; + jfieldID frameTop; + jfieldID frameRight; + jfieldID frameBottom; + jfieldID touchableRegion; + jfieldID visible; + jfieldID canReceiveKeys; + jfieldID hasFocus; + jfieldID hasWallpaper; + jfieldID paused; + jfieldID layer; + jfieldID ownerPid; + jfieldID ownerUid; +} gInputWindowClassInfo; + + +// --- Global functions --- + +void android_server_InputWindow_toNative( + JNIEnv* env, jobject inputWindowObj, InputWindow* outInputWindow) { + jobject inputWindowHandleObj = env->GetObjectField(inputWindowObj, + gInputWindowClassInfo.inputWindowHandle); + if (inputWindowHandleObj) { + outInputWindow->inputWindowHandle = + android_server_InputWindowHandle_getHandle(env, inputWindowHandleObj); + env->DeleteLocalRef(inputWindowHandleObj); + } else { + outInputWindow->inputWindowHandle = NULL; + } + + jobject inputChannelObj = env->GetObjectField(inputWindowObj, + gInputWindowClassInfo.inputChannel); + if (inputChannelObj) { + outInputWindow->inputChannel = + android_view_InputChannel_getInputChannel(env, inputChannelObj); + env->DeleteLocalRef(inputChannelObj); + } else { + outInputWindow->inputChannel = NULL; + } + + jstring nameObj = jstring(env->GetObjectField(inputWindowObj, + gInputWindowClassInfo.name)); + if (nameObj) { + const char* nameStr = env->GetStringUTFChars(nameObj, NULL); + outInputWindow->name.setTo(nameStr); + env->ReleaseStringUTFChars(nameObj, nameStr); + env->DeleteLocalRef(nameObj); + } else { + LOGE("InputWindow.name should not be null."); + outInputWindow->name.setTo("unknown"); + } + + outInputWindow->layoutParamsFlags = env->GetIntField(inputWindowObj, + gInputWindowClassInfo.layoutParamsFlags); + outInputWindow->layoutParamsType = env->GetIntField(inputWindowObj, + gInputWindowClassInfo.layoutParamsType); + outInputWindow->dispatchingTimeout = env->GetLongField(inputWindowObj, + gInputWindowClassInfo.dispatchingTimeoutNanos); + outInputWindow->frameLeft = env->GetIntField(inputWindowObj, + gInputWindowClassInfo.frameLeft); + outInputWindow->frameTop = env->GetIntField(inputWindowObj, + gInputWindowClassInfo.frameTop); + outInputWindow->frameRight = env->GetIntField(inputWindowObj, + gInputWindowClassInfo.frameRight); + outInputWindow->frameBottom = env->GetIntField(inputWindowObj, + gInputWindowClassInfo.frameBottom); + + jobject regionObj = env->GetObjectField(inputWindowObj, + gInputWindowClassInfo.touchableRegion); + if (regionObj) { + SkRegion* region = android_graphics_Region_getSkRegion(env, regionObj); + outInputWindow->touchableRegion.set(*region); + env->DeleteLocalRef(regionObj); + } else { + outInputWindow->touchableRegion.setEmpty(); + } + + outInputWindow->visible = env->GetBooleanField(inputWindowObj, + gInputWindowClassInfo.visible); + outInputWindow->canReceiveKeys = env->GetBooleanField(inputWindowObj, + gInputWindowClassInfo.canReceiveKeys); + outInputWindow->hasFocus = env->GetBooleanField(inputWindowObj, + gInputWindowClassInfo.hasFocus); + outInputWindow->hasWallpaper = env->GetBooleanField(inputWindowObj, + gInputWindowClassInfo.hasWallpaper); + outInputWindow->paused = env->GetBooleanField(inputWindowObj, + gInputWindowClassInfo.paused); + outInputWindow->layer = env->GetIntField(inputWindowObj, + gInputWindowClassInfo.layer); + outInputWindow->ownerPid = env->GetIntField(inputWindowObj, + gInputWindowClassInfo.ownerPid); + outInputWindow->ownerUid = env->GetIntField(inputWindowObj, + gInputWindowClassInfo.ownerUid); +} + + +// --- JNI --- + +#define FIND_CLASS(var, className) \ + var = env->FindClass(className); \ + LOG_FATAL_IF(! var, "Unable to find class " className); \ + var = jclass(env->NewGlobalRef(var)); + +#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ + var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find field " fieldName); + +int register_android_server_InputWindow(JNIEnv* env) { + FIND_CLASS(gInputWindowClassInfo.clazz, "com/android/server/InputWindow"); + + GET_FIELD_ID(gInputWindowClassInfo.inputWindowHandle, gInputWindowClassInfo.clazz, + "inputWindowHandle", "Lcom/android/server/InputWindowHandle;"); + + GET_FIELD_ID(gInputWindowClassInfo.inputChannel, gInputWindowClassInfo.clazz, + "inputChannel", "Landroid/view/InputChannel;"); + + GET_FIELD_ID(gInputWindowClassInfo.name, gInputWindowClassInfo.clazz, + "name", "Ljava/lang/String;"); + + GET_FIELD_ID(gInputWindowClassInfo.layoutParamsFlags, gInputWindowClassInfo.clazz, + "layoutParamsFlags", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.layoutParamsType, gInputWindowClassInfo.clazz, + "layoutParamsType", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.dispatchingTimeoutNanos, gInputWindowClassInfo.clazz, + "dispatchingTimeoutNanos", "J"); + + GET_FIELD_ID(gInputWindowClassInfo.frameLeft, gInputWindowClassInfo.clazz, + "frameLeft", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.frameTop, gInputWindowClassInfo.clazz, + "frameTop", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.frameRight, gInputWindowClassInfo.clazz, + "frameRight", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.frameBottom, gInputWindowClassInfo.clazz, + "frameBottom", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.touchableRegion, gInputWindowClassInfo.clazz, + "touchableRegion", "Landroid/graphics/Region;"); + + GET_FIELD_ID(gInputWindowClassInfo.visible, gInputWindowClassInfo.clazz, + "visible", "Z"); + + GET_FIELD_ID(gInputWindowClassInfo.canReceiveKeys, gInputWindowClassInfo.clazz, + "canReceiveKeys", "Z"); + + GET_FIELD_ID(gInputWindowClassInfo.hasFocus, gInputWindowClassInfo.clazz, + "hasFocus", "Z"); + + GET_FIELD_ID(gInputWindowClassInfo.hasWallpaper, gInputWindowClassInfo.clazz, + "hasWallpaper", "Z"); + + GET_FIELD_ID(gInputWindowClassInfo.paused, gInputWindowClassInfo.clazz, + "paused", "Z"); + + GET_FIELD_ID(gInputWindowClassInfo.layer, gInputWindowClassInfo.clazz, + "layer", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.ownerPid, gInputWindowClassInfo.clazz, + "ownerPid", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.ownerUid, gInputWindowClassInfo.clazz, + "ownerUid", "I"); + return 0; +} + +} /* namespace android */ diff --git a/services/jni/com_android_server_InputWindow.h b/services/jni/com_android_server_InputWindow.h new file mode 100644 index 0000000..eaf7bde --- /dev/null +++ b/services/jni/com_android_server_InputWindow.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef _ANDROID_SERVER_INPUT_WINDOW_H +#define _ANDROID_SERVER_INPUT_WINDOW_H + +#include <input/InputWindow.h> + +#include "JNIHelp.h" +#include "jni.h" + +namespace android { + +extern void android_server_InputWindow_toNative( + JNIEnv* env, jobject inputWindowObj, InputWindow* outInputWindow); + +} // namespace android + +#endif // _ANDROID_SERVER_INPUT_WINDOW_H diff --git a/services/jni/com_android_server_InputWindowHandle.cpp b/services/jni/com_android_server_InputWindowHandle.cpp new file mode 100644 index 0000000..4d66212 --- /dev/null +++ b/services/jni/com_android_server_InputWindowHandle.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2011 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. + */ + +#define LOG_TAG "InputWindowHandle" + +#include "JNIHelp.h" +#include "jni.h" +#include <android_runtime/AndroidRuntime.h> +#include <utils/threads.h> + +#include "com_android_server_InputWindowHandle.h" +#include "com_android_server_InputApplicationHandle.h" + +namespace android { + +static struct { + jclass clazz; + + jfieldID ptr; + jfieldID inputApplicationHandle; +} gInputWindowHandleClassInfo; + +static Mutex gHandleMutex; + + +// --- NativeInputWindowHandle --- + +NativeInputWindowHandle::NativeInputWindowHandle( + const sp<InputApplicationHandle>& inputApplicationHandle, jweak objWeak) : + InputWindowHandle(inputApplicationHandle), + mObjWeak(objWeak) { +} + +NativeInputWindowHandle::~NativeInputWindowHandle() { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->DeleteWeakGlobalRef(mObjWeak); +} + +jobject NativeInputWindowHandle::getInputWindowHandleObjLocalRef(JNIEnv* env) { + return env->NewLocalRef(mObjWeak); +} + + +// --- Global functions --- + +sp<NativeInputWindowHandle> android_server_InputWindowHandle_getHandle( + JNIEnv* env, jobject inputWindowHandleObj) { + if (!inputWindowHandleObj) { + return NULL; + } + + AutoMutex _l(gHandleMutex); + + int ptr = env->GetIntField(inputWindowHandleObj, gInputWindowHandleClassInfo.ptr); + NativeInputWindowHandle* handle; + if (ptr) { + handle = reinterpret_cast<NativeInputWindowHandle*>(ptr); + } else { + jobject inputApplicationHandleObj = env->GetObjectField(inputWindowHandleObj, + gInputWindowHandleClassInfo.inputApplicationHandle); + sp<InputApplicationHandle> inputApplicationHandle = + android_server_InputApplicationHandle_getHandle(env, inputApplicationHandleObj); + env->DeleteLocalRef(inputApplicationHandleObj); + + jweak objWeak = env->NewWeakGlobalRef(inputWindowHandleObj); + handle = new NativeInputWindowHandle(inputApplicationHandle, objWeak); + handle->incStrong(inputWindowHandleObj); + env->SetIntField(inputWindowHandleObj, gInputWindowHandleClassInfo.ptr, + reinterpret_cast<int>(handle)); + } + return handle; +} + + +// --- JNI --- + +static void android_server_InputWindowHandle_nativeDispose(JNIEnv* env, jobject obj) { + AutoMutex _l(gHandleMutex); + + int ptr = env->GetIntField(obj, gInputWindowHandleClassInfo.ptr); + if (ptr) { + env->SetIntField(obj, gInputWindowHandleClassInfo.ptr, 0); + + NativeInputWindowHandle* handle = reinterpret_cast<NativeInputWindowHandle*>(ptr); + handle->decStrong(obj); + } +} + + +static JNINativeMethod gInputWindowHandleMethods[] = { + /* name, signature, funcPtr */ + { "nativeDispose", "()V", + (void*) android_server_InputWindowHandle_nativeDispose }, +}; + +#define FIND_CLASS(var, className) \ + var = env->FindClass(className); \ + LOG_FATAL_IF(! var, "Unable to find class " className); \ + var = jclass(env->NewGlobalRef(var)); + +#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ + var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find field " fieldName); + +int register_android_server_InputWindowHandle(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "com/android/server/InputWindowHandle", + gInputWindowHandleMethods, NELEM(gInputWindowHandleMethods)); + LOG_FATAL_IF(res < 0, "Unable to register native methods."); + + FIND_CLASS(gInputWindowHandleClassInfo.clazz, "com/android/server/InputWindowHandle"); + + GET_FIELD_ID(gInputWindowHandleClassInfo.ptr, gInputWindowHandleClassInfo.clazz, + "ptr", "I"); + + GET_FIELD_ID(gInputWindowHandleClassInfo.inputApplicationHandle, + gInputWindowHandleClassInfo.clazz, + "inputApplicationHandle", "Lcom/android/server/InputApplicationHandle;"); + + return 0; +} + +} /* namespace android */ diff --git a/services/jni/com_android_server_InputWindowHandle.h b/services/jni/com_android_server_InputWindowHandle.h new file mode 100644 index 0000000..43f2a6b --- /dev/null +++ b/services/jni/com_android_server_InputWindowHandle.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef _ANDROID_SERVER_INPUT_WINDOW_HANDLE_H +#define _ANDROID_SERVER_INPUT_WINDOW_HANDLE_H + +#include <input/InputWindow.h> + +#include "JNIHelp.h" +#include "jni.h" + +namespace android { + +class NativeInputWindowHandle : public InputWindowHandle { +public: + NativeInputWindowHandle(const sp<InputApplicationHandle>& inputApplicationHandle, + jweak objWeak); + virtual ~NativeInputWindowHandle(); + + jobject getInputWindowHandleObjLocalRef(JNIEnv* env); + +private: + jweak mObjWeak; +}; + + +extern sp<NativeInputWindowHandle> android_server_InputWindowHandle_getHandle( + JNIEnv* env, jobject inputWindowHandleObj); + +} // namespace android + +#endif // _ANDROID_SERVER_INPUT_WINDOW_HANDLE_H diff --git a/services/jni/com_android_server_VibratorService.cpp b/services/jni/com_android_server_VibratorService.cpp index 6ec5c07..0912d43 100644 --- a/services/jni/com_android_server_VibratorService.cpp +++ b/services/jni/com_android_server_VibratorService.cpp @@ -29,6 +29,11 @@ namespace android { +static jboolean vibratorExists(JNIEnv *env, jobject clazz) +{ + return vibrator_exists() > 0 ? JNI_TRUE : JNI_FALSE; +} + static void vibratorOn(JNIEnv *env, jobject clazz, jlong timeout_ms) { // LOGI("vibratorOn\n"); @@ -42,6 +47,7 @@ static void vibratorOff(JNIEnv *env, jobject clazz) } static JNINativeMethod method_table[] = { + { "vibratorExists", "()Z", (void*)vibratorExists }, { "vibratorOn", "(J)V", (void*)vibratorOn }, { "vibratorOff", "()V", (void*)vibratorOff } }; diff --git a/services/jni/onload.cpp b/services/jni/onload.cpp index cd4f0a4..bdd6d80 100644 --- a/services/jni/onload.cpp +++ b/services/jni/onload.cpp @@ -6,6 +6,10 @@ namespace android { int register_android_server_AlarmManagerService(JNIEnv* env); int register_android_server_BatteryService(JNIEnv* env); +int register_android_server_InputApplication(JNIEnv* env); +int register_android_server_InputApplicationHandle(JNIEnv* env); +int register_android_server_InputWindow(JNIEnv* env); +int register_android_server_InputWindowHandle(JNIEnv* env); int register_android_server_InputManager(JNIEnv* env); int register_android_server_LightsService(JNIEnv* env); int register_android_server_PowerManagerService(JNIEnv* env); @@ -28,6 +32,10 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) LOG_ASSERT(env, "Could not retrieve the env!"); register_android_server_PowerManagerService(env); + register_android_server_InputApplication(env); + register_android_server_InputApplicationHandle(env); + register_android_server_InputWindow(env); + register_android_server_InputWindowHandle(env); register_android_server_InputManager(env); register_android_server_LightsService(env); register_android_server_AlarmManagerService(env); diff --git a/services/surfaceflinger/Android.mk b/services/surfaceflinger/Android.mk index c5bdaa1..8a00a2e 100644 --- a/services/surfaceflinger/Android.mk +++ b/services/surfaceflinger/Android.mk @@ -5,12 +5,10 @@ LOCAL_SRC_FILES:= \ clz.cpp.arm \ DisplayHardware/DisplayHardware.cpp \ DisplayHardware/DisplayHardwareBase.cpp \ - BlurFilter.cpp.arm \ + DisplayHardware/HWComposer.cpp \ GLExtensions.cpp \ Layer.cpp \ LayerBase.cpp \ - LayerBuffer.cpp \ - LayerBlur.cpp \ LayerDim.cpp \ MessageQueue.cpp \ SurfaceFlinger.cpp \ diff --git a/services/surfaceflinger/BlurFilter.cpp b/services/surfaceflinger/BlurFilter.cpp deleted file mode 100644 index 1ffbd5b..0000000 --- a/services/surfaceflinger/BlurFilter.cpp +++ /dev/null @@ -1,376 +0,0 @@ -/* -** -** Copyright 2006, 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. -*/ - - -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <stdint.h> -#include <utils/Errors.h> - -#include <pixelflinger/pixelflinger.h> - -#include "clz.h" - -#define LIKELY( exp ) (__builtin_expect( (exp) != 0, true )) -#define UNLIKELY( exp ) (__builtin_expect( (exp) != 0, false )) - -namespace android { - -#if BYTE_ORDER == LITTLE_ENDIAN -inline uint32_t BLUR_RGBA_TO_HOST(uint32_t v) { - return v; -} -inline uint32_t BLUR_HOST_TO_RGBA(uint32_t v) { - return v; -} -#else -inline uint32_t BLUR_RGBA_TO_HOST(uint32_t v) { - return (v<<24) | (v>>24) | ((v<<8)&0xff0000) | ((v>>8)&0xff00); -} -inline uint32_t BLUR_HOST_TO_RGBA(uint32_t v) { - return (v<<24) | (v>>24) | ((v<<8)&0xff0000) | ((v>>8)&0xff00); -} -#endif - -const int BLUR_DITHER_BITS = 6; // dither weights stored on 6 bits -const int BLUR_DITHER_ORDER_SHIFT= 3; -const int BLUR_DITHER_ORDER = (1<<BLUR_DITHER_ORDER_SHIFT); -const int BLUR_DITHER_SIZE = BLUR_DITHER_ORDER * BLUR_DITHER_ORDER; -const int BLUR_DITHER_MASK = BLUR_DITHER_ORDER-1; - -static const uint8_t gDitherMatrix[BLUR_DITHER_SIZE] = { - 0, 32, 8, 40, 2, 34, 10, 42, - 48, 16, 56, 24, 50, 18, 58, 26, - 12, 44, 4, 36, 14, 46, 6, 38, - 60, 28, 52, 20, 62, 30, 54, 22, - 3, 35, 11, 43, 1, 33, 9, 41, - 51, 19, 59, 27, 49, 17, 57, 25, - 15, 47, 7, 39, 13, 45, 5, 37, - 63, 31, 55, 23, 61, 29, 53, 21 -}; - - -template <int FACTOR = 0> -struct BlurColor565 -{ - typedef uint16_t type; - int r, g, b; - inline BlurColor565() { } - inline BlurColor565(uint16_t v) { - r = v >> 11; - g = (v >> 5) & 0x3E; - b = v & 0x1F; - } - inline void clear() { r=g=b=0; } - inline uint16_t to(int shift, int last, int dither) const { - int R = r; - int G = g; - int B = b; - if (UNLIKELY(last)) { - if (FACTOR>0) { - int L = (R+G+B)>>1; - R += (((L>>1) - R) * FACTOR) >> 8; - G += (((L ) - G) * FACTOR) >> 8; - B += (((L>>1) - B) * FACTOR) >> 8; - } - R += (dither << shift) >> BLUR_DITHER_BITS; - G += (dither << shift) >> BLUR_DITHER_BITS; - B += (dither << shift) >> BLUR_DITHER_BITS; - } - R >>= shift; - G >>= shift; - B >>= shift; - return (R<<11) | (G<<5) | B; - } - inline BlurColor565& operator += (const BlurColor565& rhs) { - r += rhs.r; - g += rhs.g; - b += rhs.b; - return *this; - } - inline BlurColor565& operator -= (const BlurColor565& rhs) { - r -= rhs.r; - g -= rhs.g; - b -= rhs.b; - return *this; - } -}; - -template <int FACTOR = 0> -struct BlurColor888X -{ - typedef uint32_t type; - int r, g, b; - inline BlurColor888X() { } - inline BlurColor888X(uint32_t v) { - v = BLUR_RGBA_TO_HOST(v); - r = v & 0xFF; - g = (v >> 8) & 0xFF; - b = (v >> 16) & 0xFF; - } - inline void clear() { r=g=b=0; } - inline uint32_t to(int shift, int last, int dither) const { - int R = r; - int G = g; - int B = b; - if (UNLIKELY(last)) { - if (FACTOR>0) { - int L = (R+G+G+B)>>2; - R += ((L - R) * FACTOR) >> 8; - G += ((L - G) * FACTOR) >> 8; - B += ((L - B) * FACTOR) >> 8; - } - } - R >>= shift; - G >>= shift; - B >>= shift; - return BLUR_HOST_TO_RGBA((0xFF<<24) | (B<<16) | (G<<8) | R); - } - inline BlurColor888X& operator += (const BlurColor888X& rhs) { - r += rhs.r; - g += rhs.g; - b += rhs.b; - return *this; - } - inline BlurColor888X& operator -= (const BlurColor888X& rhs) { - r -= rhs.r; - g -= rhs.g; - b -= rhs.b; - return *this; - } -}; - -struct BlurGray565 -{ - typedef uint16_t type; - int l; - inline BlurGray565() { } - inline BlurGray565(uint16_t v) { - int r = v >> 11; - int g = (v >> 5) & 0x3F; - int b = v & 0x1F; - l = (r + g + b + 1)>>1; - } - inline void clear() { l=0; } - inline uint16_t to(int shift, int last, int dither) const { - int L = l; - if (UNLIKELY(last)) { - L += (dither << shift) >> BLUR_DITHER_BITS; - } - L >>= shift; - return ((L>>1)<<11) | (L<<5) | (L>>1); - } - inline BlurGray565& operator += (const BlurGray565& rhs) { - l += rhs.l; - return *this; - } - inline BlurGray565& operator -= (const BlurGray565& rhs) { - l -= rhs.l; - return *this; - } -}; - -struct BlurGray8888 -{ - typedef uint32_t type; - int l, a; - inline BlurGray8888() { } - inline BlurGray8888(uint32_t v) { - v = BLUR_RGBA_TO_HOST(v); - int r = v & 0xFF; - int g = (v >> 8) & 0xFF; - int b = (v >> 16) & 0xFF; - a = v >> 24; - l = r + g + g + b; - } - inline void clear() { l=a=0; } - inline uint32_t to(int shift, int last, int dither) const { - int L = l; - int A = a; - if (UNLIKELY(last)) { - L += (dither << (shift+2)) >> BLUR_DITHER_BITS; - A += (dither << shift) >> BLUR_DITHER_BITS; - } - L >>= (shift+2); - A >>= shift; - return BLUR_HOST_TO_RGBA((A<<24) | (L<<16) | (L<<8) | L); - } - inline BlurGray8888& operator += (const BlurGray8888& rhs) { - l += rhs.l; - a += rhs.a; - return *this; - } - inline BlurGray8888& operator -= (const BlurGray8888& rhs) { - l -= rhs.l; - a -= rhs.a; - return *this; - } -}; - - -template<typename PIXEL> -static status_t blurFilter( - GGLSurface const* dst, - GGLSurface const* src, - int kernelSizeUser, - int repeat) -{ - typedef typename PIXEL::type TYPE; - - const int shift = 31 - clz(kernelSizeUser); - const int areaShift = shift*2; - const int kernelSize = 1<<shift; - const int kernelHalfSize = kernelSize/2; - const int mask = kernelSize-1; - const int w = src->width; - const int h = src->height; - const uint8_t* ditherMatrix = gDitherMatrix; - - // we need a temporary buffer to store one line of blurred columns - // as well as kernelSize lines of source pixels organized as a ring buffer. - void* const temporary_buffer = malloc( - (w + kernelSize) * sizeof(PIXEL) + - (src->stride * kernelSize) * sizeof(TYPE)); - if (!temporary_buffer) - return NO_MEMORY; - - PIXEL* const sums = (PIXEL*)temporary_buffer; - TYPE* const scratch = (TYPE*)(sums + w + kernelSize); - - // Apply the blur 'repeat' times, this is used to approximate - // gaussian blurs. 3 times gives good results. - for (int k=0 ; k<repeat ; k++) { - - // Clear the columns sums for this round - memset(sums, 0, (w + kernelSize) * sizeof(PIXEL)); - TYPE* head; - TYPE pixel; - PIXEL current; - - // Since we're going to override the source data we need - // to copy it in a temporary buffer. Only kernelSize lines are - // required. But since we start in the center of the kernel, - // we only copy half of the data, and fill the rest with zeros - // (assuming black/transparent pixels). - memcpy( scratch + src->stride*kernelHalfSize, - src->data, - src->stride*kernelHalfSize*sizeof(TYPE)); - - // sum half of each column, because we assume the first half is - // zeros (black/transparent). - for (int y=0 ; y<kernelHalfSize ; y++) { - head = (TYPE*)src->data + y*src->stride; - for (int x=0 ; x<w ; x++) - sums[x] += PIXEL( *head++ ); - } - - for (int y=0 ; y<h ; y++) { - TYPE* fb = (TYPE*)dst->data + y*dst->stride; - - // compute the dither matrix line - uint8_t const * ditherY = ditherMatrix - + (y & BLUR_DITHER_MASK)*BLUR_DITHER_ORDER; - - // Horizontal blur pass on the columns sums - int count, dither, x=0; - PIXEL const * out= sums; - PIXEL const * in = sums; - current.clear(); - - count = kernelHalfSize; - do { - current += *in; - in++; - } while (--count); - - count = kernelHalfSize; - do { - current += *in; - dither = *(ditherY + ((x++)&BLUR_DITHER_MASK)); - *fb++ = current.to(areaShift, k==repeat-1, dither); - in++; - } while (--count); - - count = w-kernelSize; - do { - current += *in; - current -= *out; - dither = *(ditherY + ((x++)&BLUR_DITHER_MASK)); - *fb++ = current.to(areaShift, k==repeat-1, dither); - in++, out++; - } while (--count); - - count = kernelHalfSize; - do { - current -= *out; - dither = *(ditherY + ((x++)&BLUR_DITHER_MASK)); - *fb++ = current.to(areaShift, k==repeat-1, dither); - out++; - } while (--count); - - // vertical blur pass, subtract the oldest line from each columns - // and add a new line. Subtract or add zeros at the top - // and bottom edges. - TYPE* const tail = scratch + (y & mask) * src->stride; - if (y >= kernelHalfSize) { - for (int x=0 ; x<w ; x++) - sums[x] -= PIXEL( tail[x] ); - } - if (y < h-kernelSize) { - memcpy( tail, - (TYPE*)src->data + (y+kernelHalfSize)*src->stride, - src->stride*sizeof(TYPE)); - for (int x=0 ; x<w ; x++) - sums[x] += PIXEL( tail[x] ); - } - } - - // The subsequent passes are always done in-place. - src = dst; - } - - free(temporary_buffer); - - return NO_ERROR; -} - -template status_t blurFilter< BlurColor565<0x80> >( - GGLSurface const* dst, - GGLSurface const* src, - int kernelSizeUser, - int repeat); - -status_t blurFilter( - GGLSurface const* image, - int kernelSizeUser, - int repeat) -{ - status_t err = BAD_VALUE; - if (image->format == GGL_PIXEL_FORMAT_RGB_565) { - err = blurFilter< BlurColor565<0x80> >(image, image, kernelSizeUser, repeat); - } else if (image->format == GGL_PIXEL_FORMAT_RGBX_8888) { - err = blurFilter< BlurColor888X<0x80> >(image, image, kernelSizeUser, repeat); - } - return err; -} - -} // namespace android - -//err = blur< BlurColor565<0x80> >(dst, src, kernelSizeUser, repeat); -//err = blur<BlurGray565>(dst, src, kernelSizeUser, repeat); -//err = blur<BlurGray8888>(dst, src, kernelSizeUser, repeat); diff --git a/services/surfaceflinger/BlurFilter.h b/services/surfaceflinger/BlurFilter.h deleted file mode 100644 index 294db43..0000000 --- a/services/surfaceflinger/BlurFilter.h +++ /dev/null @@ -1,35 +0,0 @@ -/* -** -** Copyright 2006, 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. -*/ - -#ifndef ANDROID_BLUR_FILTER_H -#define ANDROID_BLUR_FILTER_H - -#include <stdint.h> -#include <utils/Errors.h> - -#include <pixelflinger/pixelflinger.h> - -namespace android { - -status_t blurFilter( - GGLSurface const* image, - int kernelSizeUser, - int repeat); - -} // namespace android - -#endif // ANDROID_BLUR_FILTER_H diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp index 818774d..64cff96 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp +++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp @@ -36,11 +36,10 @@ #include "DisplayHardware/DisplayHardware.h" -#include <hardware/copybit.h> -#include <hardware/overlay.h> #include <hardware/gralloc.h> #include "GLExtensions.h" +#include "HWComposer.h" using namespace android; @@ -76,7 +75,7 @@ DisplayHardware::DisplayHardware( const sp<SurfaceFlinger>& flinger, uint32_t dpy) : DisplayHardwareBase(flinger, dpy), - mFlags(0) + mFlags(0), mHwc(0) { init(dpy); } @@ -104,12 +103,6 @@ void DisplayHardware::init(uint32_t dpy) mDpiY = mNativeWindow->ydpi; mRefreshRate = fbDev->fps; - mOverlayEngine = NULL; - hw_module_t const* module; - if (hw_get_module(OVERLAY_HARDWARE_MODULE_ID, &module) == 0) { - overlay_control_open(module, &mOverlayEngine); - } - EGLint w, h, dummy; EGLint numConfigs=0; EGLSurface surface; @@ -272,6 +265,17 @@ void DisplayHardware::init(uint32_t dpy) // Unbind the context from this thread eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + + // initialize the H/W composer + mHwc = new HWComposer(); + if (mHwc->initCheck() == NO_ERROR) { + mHwc->setFrameBuffer(mDisplay, mSurface); + } +} + +HWComposer& DisplayHardware::getHwComposer() const { + return *mHwc; } /* @@ -285,12 +289,14 @@ void DisplayHardware::fini() { eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglTerminate(mDisplay); - overlay_control_close(mOverlayEngine); } void DisplayHardware::releaseScreen() const { DisplayHardwareBase::releaseScreen(); + if (mHwc->initCheck() == NO_ERROR) { + mHwc->release(); + } } void DisplayHardware::acquireScreen() const @@ -331,7 +337,12 @@ void DisplayHardware::flip(const Region& dirty) const } mPageFlipCount++; - eglSwapBuffers(dpy, surface); + + if (mHwc->initCheck() == NO_ERROR) { + mHwc->commit(); + } else { + eglSwapBuffers(dpy, surface); + } checkEGLErrors("eglSwapBuffers"); // for debugging @@ -339,12 +350,6 @@ void DisplayHardware::flip(const Region& dirty) const //glClear(GL_COLOR_BUFFER_BIT); } -status_t DisplayHardware::postBypassBuffer(const native_handle_t* handle) const -{ - framebuffer_device_t *fbDev = (framebuffer_device_t *)mNativeWindow->getDevice(); - return fbDev->post(fbDev, handle); -} - uint32_t DisplayHardware::getFlags() const { return mFlags; @@ -354,3 +359,8 @@ void DisplayHardware::makeCurrent() const { eglMakeCurrent(mDisplay, mSurface, mSurface, mContext); } + +void DisplayHardware::dump(String8& res) const +{ + mNativeWindow->dump(res); +} diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.h b/services/surfaceflinger/DisplayHardware/DisplayHardware.h index 79ef2a7..ee7a2af 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardware.h +++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.h @@ -33,13 +33,10 @@ #include "DisplayHardware/DisplayHardwareBase.h" -struct overlay_control_device_t; -struct framebuffer_device_t; -struct copybit_image_t; - namespace android { class FramebufferNativeWindow; +class HWComposer; class DisplayHardware : public DisplayHardwareBase { @@ -64,7 +61,6 @@ public: // Flip the front and back buffers if the back buffer is "dirty". Might // be instantaneous, might involve copying the frame buffer around. void flip(const Region& dirty) const; - status_t postBypassBuffer(const native_handle_t* handle) const; float getDpiX() const; float getDpiY() const; @@ -80,7 +76,11 @@ public: uint32_t getPageFlipCount() const; EGLDisplay getEGLDisplay() const { return mDisplay; } - overlay_control_device_t* getOverlayEngine() const { return mOverlayEngine; } + + void dump(String8& res) const; + + // Hardware Composer + HWComposer& getHwComposer() const; status_t compositionComplete() const; @@ -111,8 +111,9 @@ private: GLint mMaxViewportDims; GLint mMaxTextureSize; + HWComposer* mHwc; + sp<FramebufferNativeWindow> mNativeWindow; - overlay_control_device_t* mOverlayEngine; }; }; // namespace android diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp new file mode 100644 index 0000000..4a3b20d --- /dev/null +++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2010 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. + */ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#include <utils/Errors.h> +#include <utils/String8.h> + +#include <hardware/hardware.h> + +#include <cutils/log.h> + +#include <EGL/egl.h> + +#include "HWComposer.h" + +namespace android { +// --------------------------------------------------------------------------- + +HWComposer::HWComposer() + : mModule(0), mHwc(0), mList(0), mCapacity(0), + mDpy(EGL_NO_DISPLAY), mSur(EGL_NO_SURFACE) +{ + int err = hw_get_module(HWC_HARDWARE_MODULE_ID, &mModule); + LOGW_IF(err, "%s module not found", HWC_HARDWARE_MODULE_ID); + if (err == 0) { + err = hwc_open(mModule, &mHwc); + LOGE_IF(err, "%s device failed to initialize (%s)", + HWC_HARDWARE_COMPOSER, strerror(-err)); + } +} + +HWComposer::~HWComposer() { + free(mList); + if (mHwc) { + hwc_close(mHwc); + } +} + +status_t HWComposer::initCheck() const { + return mHwc ? NO_ERROR : NO_INIT; +} + +void HWComposer::setFrameBuffer(EGLDisplay dpy, EGLSurface sur) { + mDpy = (hwc_display_t)dpy; + mSur = (hwc_surface_t)sur; +} + +status_t HWComposer::createWorkList(size_t numLayers) { + if (mHwc) { + if (!mList || mCapacity < numLayers) { + free(mList); + size_t size = sizeof(hwc_layer_list) + numLayers*sizeof(hwc_layer_t); + mList = (hwc_layer_list_t*)malloc(size); + mCapacity = numLayers; + } + mList->flags = HWC_GEOMETRY_CHANGED; + mList->numHwLayers = numLayers; + } + return NO_ERROR; +} + +status_t HWComposer::prepare() const { + int err = mHwc->prepare(mHwc, mList); + return (status_t)err; +} + +status_t HWComposer::commit() const { + int err = mHwc->set(mHwc, mDpy, mSur, mList); + if (mList) { + mList->flags &= ~HWC_GEOMETRY_CHANGED; + } + return (status_t)err; +} + +status_t HWComposer::release() const { + int err = mHwc->set(mHwc, NULL, NULL, NULL); + return (status_t)err; +} + +size_t HWComposer::getNumLayers() const { + return mList ? mList->numHwLayers : 0; +} + +hwc_layer_t* HWComposer::getLayers() const { + return mList ? mList->hwLayers : 0; +} + +void HWComposer::dump(String8& result, char* buffer, size_t SIZE) const { + if (mHwc && mList) { + result.append("Hardware Composer state:\n"); + + snprintf(buffer, SIZE, " numHwLayers=%u, flags=%08x\n", + mList->numHwLayers, mList->flags); + result.append(buffer); + + for (size_t i=0 ; i<mList->numHwLayers ; i++) { + const hwc_layer_t& l(mList->hwLayers[i]); + snprintf(buffer, SIZE, " %8s | %08x | %08x | %02x | %04x | [%5d,%5d,%5d,%5d] | [%5d,%5d,%5d,%5d]\n", + l.compositionType ? "OVERLAY" : "FB", + l.hints, l.flags, l.transform, l.blending, + l.sourceCrop.left, l.sourceCrop.top, l.sourceCrop.right, l.sourceCrop.bottom, + l.displayFrame.left, l.displayFrame.top, l.displayFrame.right, l.displayFrame.bottom); + result.append(buffer); + } + + } + if (mHwc && mHwc->common.version >= 1 && mHwc->dump) { + mHwc->dump(mHwc, buffer, SIZE); + result.append(buffer); + } +} + +// --------------------------------------------------------------------------- +}; // namespace android diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h new file mode 100644 index 0000000..5a9e9eb --- /dev/null +++ b/services/surfaceflinger/DisplayHardware/HWComposer.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2010 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. + */ + +#ifndef ANDROID_SF_HWCOMPOSER_H +#define ANDROID_SF_HWCOMPOSER_H + +#include <stdint.h> +#include <sys/types.h> + +#include <EGL/egl.h> + +#include <hardware/hwcomposer.h> + +namespace android { +// --------------------------------------------------------------------------- + +class String8; + +class HWComposer +{ +public: + + HWComposer(); + ~HWComposer(); + + status_t initCheck() const; + + // tells the HAL what the framebuffer is + void setFrameBuffer(EGLDisplay dpy, EGLSurface sur); + + // create a work list for numLayers layer + status_t createWorkList(size_t numLayers); + + // Asks the HAL what it can do + status_t prepare() const; + + // commits the list + status_t commit() const; + + // release hardware resources + status_t release() const; + + size_t getNumLayers() const; + hwc_layer_t* getLayers() const; + + // for debugging + void dump(String8& out, char* scratch, size_t SIZE) const; + +private: + hw_module_t const* mModule; + hwc_composer_device_t* mHwc; + hwc_layer_list_t* mList; + size_t mCapacity; + hwc_display_t mDpy; + hwc_surface_t mSur; +}; + + +// --------------------------------------------------------------------------- +}; // namespace android + +#endif // ANDROID_SF_HWCOMPOSER_H diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index c9dcef3..f64fd7b 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -35,6 +35,7 @@ #include "Layer.h" #include "SurfaceFlinger.h" #include "DisplayHardware/DisplayHardware.h" +#include "DisplayHardware/HWComposer.h" #define DEBUG_RESIZE 0 @@ -57,8 +58,7 @@ Layer::Layer(SurfaceFlinger* flinger, mSecure(false), mTextureManager(), mBufferManager(mTextureManager), - mWidth(0), mHeight(0), mNeedsScaling(false), mFixedSize(false), - mBypassState(false) + mWidth(0), mHeight(0), mNeedsScaling(false), mFixedSize(false) { } @@ -83,8 +83,28 @@ status_t Layer::setToken(const sp<UserClient>& userClient, sharedClient, token, mBufferManager.getDefaultBufferCount(), getIdentity()); - status_t err = mUserClientRef.setToken(userClient, lcblk, token); + sp<UserClient> ourClient(mUserClientRef.getClient()); + + /* + * Here it is guaranteed that userClient != ourClient + * (see UserClient::getTokenForSurface()). + * + * We release the token used by this surface in ourClient below. + * This should be safe to do so now, since this layer won't be attached + * to this client, it should be okay to reuse that id. + * + * If this causes problems, an other solution would be to keep a list + * of all the {UserClient, token} ever used and release them when the + * Layer is destroyed. + * + */ + + if (ourClient != 0) { + ourClient->detachLayer(this); + } + + status_t err = mUserClientRef.setToken(userClient, lcblk, token); LOGE_IF(err != NO_ERROR, "ClientRef::setToken(%p, %p, %u) failed", userClient.get(), lcblk.get(), token); @@ -130,8 +150,7 @@ status_t Layer::ditch() // the layer is not on screen anymore. free as much resources as possible mFreezeLock.clear(); - EGLDisplay dpy(mFlinger->graphicPlane(0).getEGLDisplay()); - mBufferManager.destroy(dpy); + // Free our own reference to ISurface mSurface.clear(); Mutex::Autolock _l(mLock); @@ -171,7 +190,8 @@ status_t Layer::setBuffers( uint32_t w, uint32_t h, mReqHeight = h; mSecure = (flags & ISurfaceComposer::eSecure) ? true : false; - mNeedsBlending = (info.h_alpha - info.l_alpha) > 0; + mNeedsBlending = (info.h_alpha - info.l_alpha) > 0 && + (flags & ISurfaceComposer::eOpaque) == 0; // we use the red index int displayRedSize = displayInfo.getSize(PixelFormatInfo::INDEX_RED); @@ -182,6 +202,71 @@ status_t Layer::setBuffers( uint32_t w, uint32_t h, return NO_ERROR; } +void Layer::setGeometry(hwc_layer_t* hwcl) +{ + hwcl->compositionType = HWC_FRAMEBUFFER; + hwcl->hints = 0; + hwcl->flags = 0; + hwcl->transform = 0; + hwcl->blending = HWC_BLENDING_NONE; + + // we can't do alpha-fade with the hwc HAL + const State& s(drawingState()); + if (s.alpha < 0xFF) { + hwcl->flags = HWC_SKIP_LAYER; + return; + } + + // we can only handle simple transformation + if (mOrientation & Transform::ROT_INVALID) { + hwcl->flags = HWC_SKIP_LAYER; + return; + } + + Transform tr(Transform(mOrientation) * Transform(mBufferTransform)); + hwcl->transform = tr.getOrientation(); + + if (needsBlending()) { + hwcl->blending = mPremultipliedAlpha ? + HWC_BLENDING_PREMULT : HWC_BLENDING_COVERAGE; + } + + hwcl->displayFrame.left = mTransformedBounds.left; + hwcl->displayFrame.top = mTransformedBounds.top; + hwcl->displayFrame.right = mTransformedBounds.right; + hwcl->displayFrame.bottom = mTransformedBounds.bottom; + + hwcl->visibleRegionScreen.rects = + reinterpret_cast<hwc_rect_t const *>( + visibleRegionScreen.getArray( + &hwcl->visibleRegionScreen.numRects)); +} + +void Layer::setPerFrameData(hwc_layer_t* hwcl) { + sp<GraphicBuffer> buffer(mBufferManager.getActiveBuffer()); + if (buffer == NULL) { + // this can happen if the client never drew into this layer yet, + // or if we ran out of memory. In that case, don't let + // HWC handle it. + hwcl->flags |= HWC_SKIP_LAYER; + hwcl->handle = NULL; + return; + } + hwcl->handle = buffer->handle; + + if (!mBufferCrop.isEmpty()) { + hwcl->sourceCrop.left = mBufferCrop.left; + hwcl->sourceCrop.top = mBufferCrop.top; + hwcl->sourceCrop.right = mBufferCrop.right; + hwcl->sourceCrop.bottom = mBufferCrop.bottom; + } else { + hwcl->sourceCrop.left = 0; + hwcl->sourceCrop.top = 0; + hwcl->sourceCrop.right = buffer->width; + hwcl->sourceCrop.bottom = buffer->height; + } +} + void Layer::reloadTexture(const Region& dirty) { sp<GraphicBuffer> buffer(mBufferManager.getActiveBuffer()); @@ -252,29 +337,6 @@ void Layer::onDraw(const Region& clip) const } return; } - -#ifdef USE_COMPOSITION_BYPASS - sp<GraphicBuffer> buffer(mBufferManager.getActiveBuffer()); - if ((buffer != NULL) && (buffer->transform)) { - // Here we have a "bypass" buffer, but we need to composite it - // most likely because it's not fullscreen anymore. - // Since the buffer may have a transformation applied by the client - // we need to inverse this transformation here. - - // calculate the inverse of the buffer transform - const uint32_t mask = HAL_TRANSFORM_FLIP_V | HAL_TRANSFORM_FLIP_H; - const uint32_t bufferTransformInverse = buffer->transform ^ mask; - - // To accomplish the inverse transform, we use "mBufferTransform" - // which is not used by Layer.cpp - const_cast<Layer*>(this)->mBufferTransform = bufferTransformInverse; - drawWithOpenGL(clip, tex); - // reset to "no transfrom" - const_cast<Layer*>(this)->mBufferTransform = 0; - return; - } -#endif - drawWithOpenGL(clip, tex); } @@ -302,8 +364,10 @@ status_t Layer::setBufferCount(int bufferCount) // NOTE: lcblk->resize() is protected by an internal lock status_t err = lcblk->resize(bufferCount); - if (err == NO_ERROR) - mBufferManager.resize(bufferCount); + if (err == NO_ERROR) { + EGLDisplay dpy(mFlinger->graphicPlane(0).getEGLDisplay()); + mBufferManager.resize(bufferCount, mFlinger, dpy); + } return err; } @@ -335,14 +399,13 @@ sp<GraphicBuffer> Layer::requestBuffer(int index, * buffer 'index' as our front buffer. */ - uint32_t w, h, f, bypass; + status_t err = NO_ERROR; + uint32_t w, h, f; { // scope for the lock Mutex::Autolock _l(mLock); - bypass = mBypassState; - // zero means default - mFixedSize = reqWidth && reqHeight; + const bool fixedSize = reqWidth && reqHeight; if (!reqFormat) reqFormat = mFormat; if (!reqWidth) reqWidth = mWidth; if (!reqHeight) reqHeight = mHeight; @@ -356,6 +419,7 @@ sp<GraphicBuffer> Layer::requestBuffer(int index, mReqWidth = reqWidth; mReqHeight = reqHeight; mReqFormat = reqFormat; + mFixedSize = fixedSize; mNeedsScaling = mWidth != mReqWidth || mHeight != mReqHeight; lcblk->reallocateAllExcept(index); @@ -365,40 +429,9 @@ sp<GraphicBuffer> Layer::requestBuffer(int index, // here we have to reallocate a new buffer because the buffer could be // used as the front buffer, or by a client in our process // (eg: status bar), and we can't release the handle under its feet. - uint32_t effectiveUsage = getEffectiveUsage(usage); - - status_t err = NO_MEMORY; - -#ifdef USE_COMPOSITION_BYPASS - if (!mSecure && bypass && (effectiveUsage & GRALLOC_USAGE_HW_RENDER)) { - // always allocate a buffer matching the screen size. the size - // may be different from (w,h) if the buffer is rotated. - const DisplayHardware& hw(graphicPlane(0).displayHardware()); - int32_t w = hw.getWidth(); - int32_t h = hw.getHeight(); - int32_t f = hw.getFormat(); - - buffer = new GraphicBuffer(w, h, f, effectiveUsage | GRALLOC_USAGE_HW_FB); - err = buffer->initCheck(); - buffer->transform = uint8_t(getOrientation()); - - if (err != NO_ERROR) { - // allocation didn't succeed, probably because an older bypass - // window hasn't released all its resources yet. - ClientRef::Access sharedClient(mUserClientRef); - SharedBufferServer* lcblk(sharedClient.get()); - if (lcblk) { - // all buffers need reallocation - lcblk->reallocateAll(); - } - } - } -#endif - - if (err != NO_ERROR) { - buffer = new GraphicBuffer(w, h, f, effectiveUsage); - err = buffer->initCheck(); - } + const uint32_t effectiveUsage = getEffectiveUsage(usage); + buffer = new GraphicBuffer(w, h, f, effectiveUsage); + err = buffer->initCheck(); if (err || buffer->handle == 0) { GraphicBuffer::dumpAllocationsToSystemLog(); @@ -445,39 +478,6 @@ uint32_t Layer::getEffectiveUsage(uint32_t usage) const return usage; } -bool Layer::setBypass(bool enable) -{ - Mutex::Autolock _l(mLock); - - if (mNeedsScaling || mNeedsFiltering) { - return false; - } - - if (mBypassState != enable) { - mBypassState = enable; - ClientRef::Access sharedClient(mUserClientRef); - SharedBufferServer* lcblk(sharedClient.get()); - if (lcblk) { - // all buffers need reallocation - lcblk->reallocateAll(); - } - } - - return true; -} - -void Layer::updateBuffersOrientation() -{ - sp<GraphicBuffer> buffer(getBypassBuffer()); - if (buffer != NULL && mOrientation != buffer->transform) { - ClientRef::Access sharedClient(mUserClientRef); - SharedBufferServer* lcblk(sharedClient.get()); - if (lcblk) { // all buffers need reallocation - lcblk->reallocateAll(); - } - } -} - uint32_t Layer::doTransaction(uint32_t flags) { const Layer::State& front(drawingState()); @@ -581,12 +581,20 @@ void Layer::lockPageFlip(bool& recomputeVisibleRegions) } // we retired a buffer, which becomes the new front buffer + + const bool noActiveBuffer = !mBufferManager.hasActiveBuffer(); if (mBufferManager.setActiveBufferIndex(buf) < NO_ERROR) { LOGE("retireAndLock() buffer index (%d) out of range", int(buf)); mPostedDirtyRegion.clear(); return; } + if (noActiveBuffer) { + // we didn't have an active buffer, we need to recompute + // our visible region + recomputeVisibleRegions = true; + } + sp<GraphicBuffer> newFrontBuffer(getBuffer(buf)); if (newFrontBuffer != NULL) { // get the dirty region @@ -712,9 +720,9 @@ void Layer::dump(String8& result, char* buffer, size_t SIZE) const snprintf(buffer, SIZE, " " "format=%2d, [%3ux%3u:%3u] [%3ux%3u:%3u]," - " freezeLock=%p, bypass=%d, dq-q-time=%u us\n", + " freezeLock=%p, dq-q-time=%u us\n", mFormat, w0, h0, s0, w1, h1, s1, - getFreezeLock().get(), mBypassState, totalTime); + getFreezeLock().get(), totalTime); result.append(buffer); } @@ -779,7 +787,7 @@ Layer::ClientRef::Access::~Access() Layer::BufferManager::BufferManager(TextureManager& tm) : mNumBuffers(NUM_BUFFERS), mTextureManager(tm), - mActiveBuffer(-1), mFailover(false) + mActiveBufferIndex(-1), mFailover(false) { } @@ -787,9 +795,52 @@ Layer::BufferManager::~BufferManager() { } -status_t Layer::BufferManager::resize(size_t size) +status_t Layer::BufferManager::resize(size_t size, + const sp<SurfaceFlinger>& flinger, EGLDisplay dpy) { Mutex::Autolock _l(mLock); + + if (size < mNumBuffers) { + // Move the active texture into slot 0 + BufferData activeBufferData = mBufferData[mActiveBufferIndex]; + mBufferData[mActiveBufferIndex] = mBufferData[0]; + mBufferData[0] = activeBufferData; + mActiveBufferIndex = 0; + + // Free the buffers that are no longer needed. + for (size_t i = size; i < mNumBuffers; i++) { + mBufferData[i].buffer = 0; + + // Create a message to destroy the textures on SurfaceFlinger's GL + // thread. + class MessageDestroyTexture : public MessageBase { + Image mTexture; + EGLDisplay mDpy; + public: + MessageDestroyTexture(const Image& texture, EGLDisplay dpy) + : mTexture(texture), mDpy(dpy) { } + virtual bool handler() { + status_t err = Layer::BufferManager::destroyTexture( + &mTexture, mDpy); + LOGE_IF(err<0, "error destroying texture: %d (%s)", + mTexture.name, strerror(-err)); + return true; // XXX: err == 0; ???? + } + }; + + MessageDestroyTexture *msg = new MessageDestroyTexture( + mBufferData[i].texture, dpy); + + // Don't allow this texture to be cleaned up by + // BufferManager::destroy. + mBufferData[i].texture.name = -1U; + mBufferData[i].texture.image = EGL_NO_IMAGE_KHR; + + // Post the message to the SurfaceFlinger object. + flinger->postMessageAsync(msg); + } + } + mNumBuffers = size; return NO_ERROR; } @@ -800,33 +851,33 @@ sp<GraphicBuffer> Layer::BufferManager::getBuffer(size_t index) const { } status_t Layer::BufferManager::setActiveBufferIndex(size_t index) { - mActiveBuffer = index; + BufferData const * const buffers = mBufferData; + Mutex::Autolock _l(mLock); + mActiveBuffer = buffers[index].buffer; + mActiveBufferIndex = index; return NO_ERROR; } size_t Layer::BufferManager::getActiveBufferIndex() const { - return mActiveBuffer; + return mActiveBufferIndex; } Texture Layer::BufferManager::getActiveTexture() const { Texture res; - if (mFailover || mActiveBuffer<0) { + if (mFailover || mActiveBufferIndex<0) { res = mFailoverTexture; } else { - static_cast<Image&>(res) = mBufferData[mActiveBuffer].texture; + static_cast<Image&>(res) = mBufferData[mActiveBufferIndex].texture; } return res; } sp<GraphicBuffer> Layer::BufferManager::getActiveBuffer() const { - sp<GraphicBuffer> result; - const ssize_t activeBuffer = mActiveBuffer; - if (activeBuffer >= 0) { - BufferData const * const buffers = mBufferData; - Mutex::Autolock _l(mLock); - result = buffers[activeBuffer].buffer; - } - return result; + return mActiveBuffer; +} + +bool Layer::BufferManager::hasActiveBuffer() const { + return mActiveBufferIndex >= 0; } sp<GraphicBuffer> Layer::BufferManager::detachBuffer(size_t index) @@ -871,7 +922,7 @@ status_t Layer::BufferManager::initEglImage(EGLDisplay dpy, const sp<GraphicBuffer>& buffer) { status_t err = NO_INIT; - ssize_t index = mActiveBuffer; + ssize_t index = mActiveBufferIndex; if (index >= 0) { if (!mFailover) { Image& texture(mBufferData[index].texture); diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 7bb207a..2908119 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -68,6 +68,8 @@ public: bool isFixedSize() const; // LayerBase interface + virtual void setGeometry(hwc_layer_t* hwcl); + virtual void setPerFrameData(hwc_layer_t* hwcl); virtual void drawForSreenShot() const; virtual void onDraw(const Region& clip) const; virtual uint32_t doTransaction(uint32_t transactionFlags); @@ -80,12 +82,6 @@ public: virtual sp<Surface> createSurface() const; virtual status_t ditch(); virtual void onRemoved(); - virtual bool setBypass(bool enable); - - void updateBuffersOrientation(); - - inline sp<GraphicBuffer> getBypassBuffer() const { - return mBufferManager.getActiveBuffer(); } // only for debugging inline sp<GraphicBuffer> getBuffer(int i) const { @@ -167,7 +163,8 @@ private: size_t mNumBuffers; Texture mFailoverTexture; TextureManager& mTextureManager; - ssize_t mActiveBuffer; + ssize_t mActiveBufferIndex; + sp<GraphicBuffer> mActiveBuffer; bool mFailover; static status_t destroyTexture(Image* tex, EGLDisplay dpy); @@ -180,7 +177,8 @@ private: sp<GraphicBuffer> detachBuffer(size_t index); status_t attachBuffer(size_t index, const sp<GraphicBuffer>& buffer); // resize the number of active buffers - status_t resize(size_t size); + status_t resize(size_t size, const sp<SurfaceFlinger>& flinger, + EGLDisplay dpy); // ---------------------------------------------- // must be called from GL thread @@ -190,6 +188,8 @@ private: size_t getActiveBufferIndex() const; // return the active buffer sp<GraphicBuffer> getActiveBuffer() const; + // return wether we have an active buffer + bool hasActiveBuffer() const; // return the active texture (or fail-over) Texture getActiveTexture() const; // frees resources associated with all buffers @@ -237,7 +237,6 @@ private: uint32_t mReqFormat; bool mNeedsScaling; bool mFixedSize; - bool mBypassState; }; // --------------------------------------------------------------------------- diff --git a/services/surfaceflinger/LayerBase.cpp b/services/surfaceflinger/LayerBase.cpp index 916d420..86057f8 100644 --- a/services/surfaceflinger/LayerBase.cpp +++ b/services/surfaceflinger/LayerBase.cpp @@ -301,6 +301,15 @@ void LayerBase::drawRegion(const Region& reg) const } } +void LayerBase::setGeometry(hwc_layer_t* hwcl) { + hwcl->flags |= HWC_SKIP_LAYER; +} + +void LayerBase::setPerFrameData(hwc_layer_t* hwcl) { + hwcl->compositionType = HWC_FRAMEBUFFER; + hwcl->handle = NULL; +} + void LayerBase::draw(const Region& clip) const { // reset GL state @@ -489,13 +498,17 @@ void LayerBase::drawWithOpenGL(const Region& clip, const Texture& texture) const } void LayerBase::setBufferCrop(const Rect& crop) { - if (!crop.isEmpty()) { + if (mBufferCrop != crop) { mBufferCrop = crop; + mFlinger->invalidateHwcGeometry(); } } void LayerBase::setBufferTransform(uint32_t transform) { - mBufferTransform = transform; + if (mBufferTransform != transform) { + mBufferTransform = transform; + mFlinger->invalidateHwcGeometry(); + } } void LayerBase::dump(String8& result, char* buffer, size_t SIZE) const @@ -548,10 +561,15 @@ sp<LayerBaseClient::Surface> LayerBaseClient::getSurface() if (s == 0) { s = createSurface(); mClientSurface = s; + mClientSurfaceBinder = s->asBinder(); } return s; } +wp<IBinder> LayerBaseClient::getSurfaceBinder() const { + return mClientSurfaceBinder; +} + sp<LayerBaseClient::Surface> LayerBaseClient::createSurface() const { return new Surface(mFlinger, mIdentity, @@ -609,21 +627,6 @@ sp<LayerBaseClient> LayerBaseClient::Surface::getOwner() const { status_t LayerBaseClient::Surface::onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { - switch (code) { - case REGISTER_BUFFERS: - case UNREGISTER_BUFFERS: - case CREATE_OVERLAY: - { - if (!mFlinger->mAccessSurfaceFlinger.checkCalling()) { - IPCThreadState* ipc = IPCThreadState::self(); - const int pid = ipc->getCallingPid(); - const int uid = ipc->getCallingUid(); - LOGE("Permission Denial: " - "can't access SurfaceFlinger pid=%d, uid=%d", pid, uid); - return PERMISSION_DENIED; - } - } - } return BnSurface::onTransact(code, data, reply, flags); } @@ -638,26 +641,6 @@ status_t LayerBaseClient::Surface::setBufferCount(int bufferCount) return INVALID_OPERATION; } -status_t LayerBaseClient::Surface::registerBuffers( - const ISurface::BufferHeap& buffers) -{ - return INVALID_OPERATION; -} - -void LayerBaseClient::Surface::postBuffer(ssize_t offset) -{ -} - -void LayerBaseClient::Surface::unregisterBuffers() -{ -} - -sp<OverlayRef> LayerBaseClient::Surface::createOverlay( - uint32_t w, uint32_t h, int32_t format, int32_t orientation) -{ - return NULL; -}; - // --------------------------------------------------------------------------- }; // namespace android diff --git a/services/surfaceflinger/LayerBase.h b/services/surfaceflinger/LayerBase.h index 1470729..8ed4749 100644 --- a/services/surfaceflinger/LayerBase.h +++ b/services/surfaceflinger/LayerBase.h @@ -27,7 +27,6 @@ #include <utils/RefBase.h> #include <ui/Region.h> -#include <ui/Overlay.h> #include <surfaceflinger/ISurfaceComposerClient.h> #include <private/surfaceflinger/SharedBufferStack.h> @@ -35,6 +34,8 @@ #include <pixelflinger/pixelflinger.h> +#include <hardware/hwcomposer.h> + #include "DisplayHardware/DisplayHardware.h" #include "Transform.h" @@ -109,6 +110,10 @@ public: virtual const char* getTypeId() const { return "LayerBase"; } + virtual void setGeometry(hwc_layer_t* hwcl); + + virtual void setPerFrameData(hwc_layer_t* hwcl); + /** * draw - performs some global clipping optimizations * and calls onDraw(). @@ -119,11 +124,6 @@ public: virtual void drawForSreenShot() const; /** - * bypass mode - */ - virtual bool setBypass(bool enable) { return false; } - - /** * onDraw - draws the surface. */ virtual void onDraw(const Region& clip) const = 0; @@ -285,6 +285,7 @@ public: virtual ~LayerBaseClient(); sp<Surface> getSurface(); + wp<IBinder> getSurfaceBinder() const; virtual sp<Surface> createSurface() const; virtual sp<LayerBaseClient> getLayerBaseClient() const { return const_cast<LayerBaseClient*>(this); } @@ -309,12 +310,6 @@ public: uint32_t w, uint32_t h, uint32_t format, uint32_t usage); virtual status_t setBufferCount(int bufferCount); - virtual status_t registerBuffers(const ISurface::BufferHeap& buffers); - virtual void postBuffer(ssize_t offset); - virtual void unregisterBuffers(); - virtual sp<OverlayRef> createOverlay(uint32_t w, uint32_t h, - int32_t format, int32_t orientation); - protected: friend class LayerBaseClient; sp<SurfaceFlinger> mFlinger; @@ -331,6 +326,7 @@ protected: private: mutable Mutex mLock; mutable wp<Surface> mClientSurface; + wp<IBinder> mClientSurfaceBinder; const wp<Client> mClientRef; // only read const uint32_t mIdentity; diff --git a/services/surfaceflinger/LayerBlur.cpp b/services/surfaceflinger/LayerBlur.cpp deleted file mode 100644 index 4cfcfe3..0000000 --- a/services/surfaceflinger/LayerBlur.cpp +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#include <stdlib.h> -#include <stdint.h> -#include <sys/types.h> - -#include <utils/Errors.h> -#include <utils/Log.h> - -#include <GLES/gl.h> -#include <GLES/glext.h> - -#include "clz.h" -#include "BlurFilter.h" -#include "LayerBlur.h" -#include "SurfaceFlinger.h" -#include "DisplayHardware/DisplayHardware.h" - -namespace android { -// --------------------------------------------------------------------------- - -LayerBlur::LayerBlur(SurfaceFlinger* flinger, DisplayID display, - const sp<Client>& client) - : LayerBaseClient(flinger, display, client), mCacheDirty(true), - mRefreshCache(true), mCacheAge(0), mTextureName(-1U), - mWidthScale(1.0f), mHeightScale(1.0f), - mBlurFormat(GGL_PIXEL_FORMAT_RGB_565) -{ -} - -LayerBlur::~LayerBlur() -{ - if (mTextureName != -1U) { - glDeleteTextures(1, &mTextureName); - } -} - -void LayerBlur::setVisibleRegion(const Region& visibleRegion) -{ - LayerBaseClient::setVisibleRegion(visibleRegion); - if (visibleRegionScreen.isEmpty()) { - if (mTextureName != -1U) { - // We're not visible, free the texture up. - glBindTexture(GL_TEXTURE_2D, 0); - glDeleteTextures(1, &mTextureName); - mTextureName = -1U; - } - } -} - -uint32_t LayerBlur::doTransaction(uint32_t flags) -{ - // we're doing a transaction, refresh the cache! - if (!mFlinger->isFrozen()) { - mRefreshCache = true; - mCacheDirty = true; - flags |= eVisibleRegion; - this->contentDirty = true; - } - return LayerBase::doTransaction(flags); -} - -void LayerBlur::unlockPageFlip(const Transform& planeTransform, Region& outDirtyRegion) -{ - // this code-path must be as tight as possible, it's called each time - // the screen is composited. - if (UNLIKELY(!visibleRegionScreen.isEmpty())) { - // if anything visible below us is invalidated, the cache becomes dirty - if (!mCacheDirty && - !visibleRegionScreen.intersect(outDirtyRegion).isEmpty()) { - mCacheDirty = true; - } - if (mCacheDirty) { - if (!mFlinger->isFrozen()) { - // update everything below us that is visible - outDirtyRegion.orSelf(visibleRegionScreen); - nsecs_t now = systemTime(); - if ((now - mCacheAge) >= ms2ns(500)) { - mCacheAge = now; - mRefreshCache = true; - mCacheDirty = false; - } else { - if (!mAutoRefreshPending) { - mFlinger->postMessageAsync( - new MessageBase(MessageQueue::INVALIDATE), - ms2ns(500)); - mAutoRefreshPending = true; - } - } - } - } - } - LayerBase::unlockPageFlip(planeTransform, outDirtyRegion); -} - -void LayerBlur::onDraw(const Region& clip) const -{ - const DisplayHardware& hw(graphicPlane(0).displayHardware()); - const uint32_t fbHeight = hw.getHeight(); - int x = mTransformedBounds.left; - int y = mTransformedBounds.top; - int w = mTransformedBounds.width(); - int h = mTransformedBounds.height(); - GLint X = x; - GLint Y = fbHeight - (y + h); - if (X < 0) { - w += X; - X = 0; - } - if (Y < 0) { - h += Y; - Y = 0; - } - if (w<0 || h<0) { - // we're outside of the framebuffer - return; - } - - if (mTextureName == -1U) { - // create the texture name the first time - // can't do that in the ctor, because it runs in another thread. - glGenTextures(1, &mTextureName); - glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES, &mReadFormat); - glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE_OES, &mReadType); - if (mReadFormat != GL_RGB || mReadType != GL_UNSIGNED_SHORT_5_6_5) { - mReadFormat = GL_RGBA; - mReadType = GL_UNSIGNED_BYTE; - mBlurFormat = GGL_PIXEL_FORMAT_RGBX_8888; - } - } - - Region::const_iterator it = clip.begin(); - Region::const_iterator const end = clip.end(); - if (it != end) { -#if defined(GL_OES_EGL_image_external) - if (GLExtensions::getInstance().haveTextureExternal()) { - glDisable(GL_TEXTURE_EXTERNAL_OES); - } -#endif - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, mTextureName); - - if (mRefreshCache) { - mRefreshCache = false; - mAutoRefreshPending = false; - - int32_t pixelSize = 4; - int32_t s = w; - if (mReadType == GL_UNSIGNED_SHORT_5_6_5) { - // allocate enough memory for 4-bytes (2 pixels) aligned data - s = (w + 1) & ~1; - pixelSize = 2; - } - - uint16_t* const pixels = (uint16_t*)malloc(s*h*pixelSize); - - // This reads the frame-buffer, so a h/w GL would have to - // finish() its rendering first. we don't want to do that - // too often. Read data is 4-bytes aligned. - glReadPixels(X, Y, w, h, mReadFormat, mReadType, pixels); - - // blur that texture. - GGLSurface bl; - bl.version = sizeof(GGLSurface); - bl.width = w; - bl.height = h; - bl.stride = s; - bl.format = mBlurFormat; - bl.data = (GGLubyte*)pixels; - blurFilter(&bl, 8, 2); - - if (GLExtensions::getInstance().haveNpot()) { - glTexImage2D(GL_TEXTURE_2D, 0, mReadFormat, w, h, 0, - mReadFormat, mReadType, pixels); - mWidthScale = 1.0f / w; - mHeightScale =-1.0f / h; - mYOffset = 0; - } else { - GLuint tw = 1 << (31 - clz(w)); - GLuint th = 1 << (31 - clz(h)); - if (tw < GLuint(w)) tw <<= 1; - if (th < GLuint(h)) th <<= 1; - glTexImage2D(GL_TEXTURE_2D, 0, mReadFormat, tw, th, 0, - mReadFormat, mReadType, NULL); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, - mReadFormat, mReadType, pixels); - mWidthScale = 1.0f / tw; - mHeightScale =-1.0f / th; - mYOffset = th-h; - } - - free((void*)pixels); - } - - const State& s = drawingState(); - if (UNLIKELY(s.alpha < 0xFF)) { - const GLfloat alpha = s.alpha * (1.0f/255.0f); - glColor4f(0, 0, 0, alpha); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - } else { - glDisable(GL_BLEND); - } - - if (mFlags & DisplayHardware::SLOW_CONFIG) { - glDisable(GL_DITHER); - } else { - glEnable(GL_DITHER); - } - - glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - - glMatrixMode(GL_TEXTURE); - glLoadIdentity(); - glScalef(mWidthScale, mHeightScale, 1); - glTranslatef(-x, mYOffset - y, 0); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glVertexPointer(2, GL_FLOAT, 0, mVertices); - glTexCoordPointer(2, GL_FLOAT, 0, mVertices); - while (it != end) { - const Rect& r = *it++; - const GLint sy = fbHeight - (r.top + r.height()); - glScissor(r.left, sy, r.width(), r.height()); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - } - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - glLoadIdentity(); - glMatrixMode(GL_MODELVIEW); - } -} - -// --------------------------------------------------------------------------- - -}; // namespace android diff --git a/services/surfaceflinger/LayerBlur.h b/services/surfaceflinger/LayerBlur.h deleted file mode 100644 index 4c9ec64..0000000 --- a/services/surfaceflinger/LayerBlur.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#ifndef ANDROID_LAYER_BLUR_H -#define ANDROID_LAYER_BLUR_H - -#include <stdint.h> -#include <sys/types.h> - -#include <ui/Region.h> - -#include "LayerBase.h" - -namespace android { - -// --------------------------------------------------------------------------- - -class LayerBlur : public LayerBaseClient -{ -public: - LayerBlur(SurfaceFlinger* flinger, DisplayID display, - const sp<Client>& client); - virtual ~LayerBlur(); - - virtual void onDraw(const Region& clip) const; - virtual bool needsBlending() const { return true; } - virtual bool isSecure() const { return false; } - virtual const char* getTypeId() const { return "LayerBlur"; } - - virtual uint32_t doTransaction(uint32_t flags); - virtual void setVisibleRegion(const Region& visibleRegion); - virtual void unlockPageFlip(const Transform& planeTransform, Region& outDirtyRegion); - -private: - bool mCacheDirty; - mutable bool mRefreshCache; - mutable bool mAutoRefreshPending; - nsecs_t mCacheAge; - mutable GLuint mTextureName; - mutable GLfloat mWidthScale; - mutable GLfloat mHeightScale; - mutable GLfloat mYOffset; - mutable GLint mReadFormat; - mutable GLint mReadType; - mutable uint32_t mBlurFormat; -}; - -// --------------------------------------------------------------------------- - -}; // namespace android - -#endif // ANDROID_LAYER_BLUR_H diff --git a/services/surfaceflinger/LayerBuffer.cpp b/services/surfaceflinger/LayerBuffer.cpp deleted file mode 100644 index 23506cf..0000000 --- a/services/surfaceflinger/LayerBuffer.cpp +++ /dev/null @@ -1,701 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#include <stdlib.h> -#include <stdint.h> -#include <math.h> -#include <sys/types.h> - -#include <utils/Errors.h> -#include <utils/Log.h> -#include <utils/StopWatch.h> - -#include <ui/GraphicBuffer.h> -#include <ui/PixelFormat.h> -#include <ui/FramebufferNativeWindow.h> -#include <ui/Rect.h> -#include <ui/Region.h> - -#include <hardware/copybit.h> - -#include "LayerBuffer.h" -#include "SurfaceFlinger.h" -#include "DisplayHardware/DisplayHardware.h" - -namespace android { - -// --------------------------------------------------------------------------- - -gralloc_module_t const* LayerBuffer::sGrallocModule = 0; - -// --------------------------------------------------------------------------- - -LayerBuffer::LayerBuffer(SurfaceFlinger* flinger, DisplayID display, - const sp<Client>& client) - : LayerBaseClient(flinger, display, client), - mNeedsBlending(false), mBlitEngine(0) -{ -} - -LayerBuffer::~LayerBuffer() -{ - if (mBlitEngine) { - copybit_close(mBlitEngine); - } -} - -void LayerBuffer::onFirstRef() -{ - LayerBaseClient::onFirstRef(); - mSurface = new SurfaceLayerBuffer(mFlinger, this); - - hw_module_t const* module = (hw_module_t const*)sGrallocModule; - if (!module) { - // NOTE: technically there is a race here, but it shouldn't - // cause any problem since hw_get_module() always returns - // the same value. - if (hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module) == 0) { - sGrallocModule = (gralloc_module_t const *)module; - } - } - - if (hw_get_module(COPYBIT_HARDWARE_MODULE_ID, &module) == 0) { - copybit_open(module, &mBlitEngine); - } -} - -sp<LayerBaseClient::Surface> LayerBuffer::createSurface() const -{ - return mSurface; -} - -status_t LayerBuffer::ditch() -{ - mSurface.clear(); - return NO_ERROR; -} - -bool LayerBuffer::needsBlending() const { - return mNeedsBlending; -} - -void LayerBuffer::setNeedsBlending(bool blending) { - mNeedsBlending = blending; -} - -void LayerBuffer::postBuffer(ssize_t offset) -{ - sp<Source> source(getSource()); - if (source != 0) - source->postBuffer(offset); -} - -void LayerBuffer::unregisterBuffers() -{ - sp<Source> source(clearSource()); - if (source != 0) - source->unregisterBuffers(); -} - -uint32_t LayerBuffer::doTransaction(uint32_t flags) -{ - sp<Source> source(getSource()); - if (source != 0) - source->onTransaction(flags); - uint32_t res = LayerBase::doTransaction(flags); - // we always want filtering for these surfaces - mNeedsFiltering = !(mFlags & DisplayHardware::SLOW_CONFIG); - return res; -} - -void LayerBuffer::unlockPageFlip(const Transform& planeTransform, - Region& outDirtyRegion) -{ - // this code-path must be as tight as possible, it's called each time - // the screen is composited. - sp<Source> source(getSource()); - if (source != 0) - source->onVisibilityResolved(planeTransform); - LayerBase::unlockPageFlip(planeTransform, outDirtyRegion); -} - -void LayerBuffer::validateVisibility(const Transform& globalTransform) -{ - sp<Source> source(getSource()); - if (source != 0) - source->onvalidateVisibility(globalTransform); - LayerBase::validateVisibility(globalTransform); -} - -void LayerBuffer::drawForSreenShot() const -{ - const DisplayHardware& hw(graphicPlane(0).displayHardware()); - clearWithOpenGL( Region(hw.bounds()) ); -} - -void LayerBuffer::onDraw(const Region& clip) const -{ - sp<Source> source(getSource()); - if (LIKELY(source != 0)) { - source->onDraw(clip); - } else { - clearWithOpenGL(clip); - } -} - -void LayerBuffer::serverDestroy() -{ - sp<Source> source(clearSource()); - if (source != 0) { - source->destroy(); - } -} - -/** - * This creates a "buffer" source for this surface - */ -status_t LayerBuffer::registerBuffers(const ISurface::BufferHeap& buffers) -{ - Mutex::Autolock _l(mLock); - if (mSource != 0) - return INVALID_OPERATION; - - sp<BufferSource> source = new BufferSource(*this, buffers); - - status_t result = source->getStatus(); - if (result == NO_ERROR) { - mSource = source; - } - return result; -} - -/** - * This creates an "overlay" source for this surface - */ -sp<OverlayRef> LayerBuffer::createOverlay(uint32_t w, uint32_t h, int32_t f, - int32_t orientation) -{ - sp<OverlayRef> result; - Mutex::Autolock _l(mLock); - if (mSource != 0) - return result; - - sp<OverlaySource> source = new OverlaySource(*this, &result, w, h, f, orientation); - if (result != 0) { - mSource = source; - } - return result; -} - -sp<LayerBuffer::Source> LayerBuffer::getSource() const { - Mutex::Autolock _l(mLock); - return mSource; -} - -sp<LayerBuffer::Source> LayerBuffer::clearSource() { - sp<Source> source; - Mutex::Autolock _l(mLock); - source = mSource; - mSource.clear(); - return source; -} - -// ============================================================================ -// LayerBuffer::SurfaceLayerBuffer -// ============================================================================ - -LayerBuffer::SurfaceLayerBuffer::SurfaceLayerBuffer( - const sp<SurfaceFlinger>& flinger, const sp<LayerBuffer>& owner) - : LayerBaseClient::Surface(flinger, owner->getIdentity(), owner) -{ -} - -LayerBuffer::SurfaceLayerBuffer::~SurfaceLayerBuffer() -{ - unregisterBuffers(); -} - -status_t LayerBuffer::SurfaceLayerBuffer::registerBuffers( - const ISurface::BufferHeap& buffers) -{ - sp<LayerBuffer> owner(getOwner()); - if (owner != 0) - return owner->registerBuffers(buffers); - return NO_INIT; -} - -void LayerBuffer::SurfaceLayerBuffer::postBuffer(ssize_t offset) -{ - sp<LayerBuffer> owner(getOwner()); - if (owner != 0) - owner->postBuffer(offset); -} - -void LayerBuffer::SurfaceLayerBuffer::unregisterBuffers() -{ - sp<LayerBuffer> owner(getOwner()); - if (owner != 0) - owner->unregisterBuffers(); -} - -sp<OverlayRef> LayerBuffer::SurfaceLayerBuffer::createOverlay( - uint32_t w, uint32_t h, int32_t format, int32_t orientation) { - sp<OverlayRef> result; - sp<LayerBuffer> owner(getOwner()); - if (owner != 0) - result = owner->createOverlay(w, h, format, orientation); - return result; -} - -// ============================================================================ -// LayerBuffer::Buffer -// ============================================================================ - -LayerBuffer::Buffer::Buffer(const ISurface::BufferHeap& buffers, - ssize_t offset, size_t bufferSize) - : mBufferHeap(buffers), mSupportsCopybit(false) -{ - NativeBuffer& src(mNativeBuffer); - src.crop.l = 0; - src.crop.t = 0; - src.crop.r = buffers.w; - src.crop.b = buffers.h; - - src.img.w = buffers.hor_stride ?: buffers.w; - src.img.h = buffers.ver_stride ?: buffers.h; - src.img.format = buffers.format; - src.img.base = (void*)(intptr_t(buffers.heap->base()) + offset); - src.img.handle = 0; - - gralloc_module_t const * module = LayerBuffer::getGrallocModule(); - if (module && module->perform) { - int err = module->perform(module, - GRALLOC_MODULE_PERFORM_CREATE_HANDLE_FROM_BUFFER, - buffers.heap->heapID(), bufferSize, - offset, buffers.heap->base(), - &src.img.handle); - - // we can fail here is the passed buffer is purely software - mSupportsCopybit = (err == NO_ERROR); - } - } - -LayerBuffer::Buffer::~Buffer() -{ - NativeBuffer& src(mNativeBuffer); - if (src.img.handle) { - native_handle_delete(src.img.handle); - } -} - -// ============================================================================ -// LayerBuffer::Source -// LayerBuffer::BufferSource -// LayerBuffer::OverlaySource -// ============================================================================ - -LayerBuffer::Source::Source(LayerBuffer& layer) - : mLayer(layer) -{ -} -LayerBuffer::Source::~Source() { -} -void LayerBuffer::Source::onDraw(const Region& clip) const { -} -void LayerBuffer::Source::onTransaction(uint32_t flags) { -} -void LayerBuffer::Source::onVisibilityResolved( - const Transform& planeTransform) { -} -void LayerBuffer::Source::postBuffer(ssize_t offset) { -} -void LayerBuffer::Source::unregisterBuffers() { -} - -// --------------------------------------------------------------------------- - -LayerBuffer::BufferSource::BufferSource(LayerBuffer& layer, - const ISurface::BufferHeap& buffers) - : Source(layer), mStatus(NO_ERROR), mBufferSize(0) -{ - if (buffers.heap == NULL) { - // this is allowed, but in this case, it is illegal to receive - // postBuffer(). The surface just erases the framebuffer with - // fully transparent pixels. - mBufferHeap = buffers; - mLayer.setNeedsBlending(false); - return; - } - - status_t err = (buffers.heap->heapID() >= 0) ? NO_ERROR : NO_INIT; - if (err != NO_ERROR) { - LOGE("LayerBuffer::BufferSource: invalid heap (%s)", strerror(err)); - mStatus = err; - return; - } - - PixelFormatInfo info; - err = getPixelFormatInfo(buffers.format, &info); - if (err != NO_ERROR) { - LOGE("LayerBuffer::BufferSource: invalid format %d (%s)", - buffers.format, strerror(err)); - mStatus = err; - return; - } - - if (buffers.hor_stride<0 || buffers.ver_stride<0) { - LOGE("LayerBuffer::BufferSource: invalid parameters " - "(w=%d, h=%d, xs=%d, ys=%d)", - buffers.w, buffers.h, buffers.hor_stride, buffers.ver_stride); - mStatus = BAD_VALUE; - return; - } - - mBufferHeap = buffers; - mLayer.setNeedsBlending((info.h_alpha - info.l_alpha) > 0); - mBufferSize = info.getScanlineSize(buffers.hor_stride)*buffers.ver_stride; - mLayer.forceVisibilityTransaction(); -} - -LayerBuffer::BufferSource::~BufferSource() -{ - class MessageDestroyTexture : public MessageBase { - SurfaceFlinger* flinger; - GLuint name; - public: - MessageDestroyTexture( - SurfaceFlinger* flinger, GLuint name) - : flinger(flinger), name(name) { } - virtual bool handler() { - glDeleteTextures(1, &name); - return true; - } - }; - - if (mTexture.name != -1U) { - // GL textures can only be destroyed from the GL thread - getFlinger()->mEventQueue.postMessage( - new MessageDestroyTexture(getFlinger(), mTexture.name) ); - } - if (mTexture.image != EGL_NO_IMAGE_KHR) { - EGLDisplay dpy(getFlinger()->graphicPlane(0).getEGLDisplay()); - eglDestroyImageKHR(dpy, mTexture.image); - } -} - -void LayerBuffer::BufferSource::postBuffer(ssize_t offset) -{ - ISurface::BufferHeap buffers; - { // scope for the lock - Mutex::Autolock _l(mBufferSourceLock); - buffers = mBufferHeap; - if (buffers.heap != 0) { - const size_t memorySize = buffers.heap->getSize(); - if ((size_t(offset) + mBufferSize) > memorySize) { - LOGE("LayerBuffer::BufferSource::postBuffer() " - "invalid buffer (offset=%d, size=%d, heap-size=%d", - int(offset), int(mBufferSize), int(memorySize)); - return; - } - } - } - - sp<Buffer> buffer; - if (buffers.heap != 0) { - buffer = new LayerBuffer::Buffer(buffers, offset, mBufferSize); - if (buffer->getStatus() != NO_ERROR) - buffer.clear(); - setBuffer(buffer); - mLayer.invalidate(); - } -} - -void LayerBuffer::BufferSource::unregisterBuffers() -{ - Mutex::Autolock _l(mBufferSourceLock); - mBufferHeap.heap.clear(); - mBuffer.clear(); - mLayer.invalidate(); -} - -sp<LayerBuffer::Buffer> LayerBuffer::BufferSource::getBuffer() const -{ - Mutex::Autolock _l(mBufferSourceLock); - return mBuffer; -} - -void LayerBuffer::BufferSource::setBuffer(const sp<LayerBuffer::Buffer>& buffer) -{ - Mutex::Autolock _l(mBufferSourceLock); - mBuffer = buffer; -} - -void LayerBuffer::BufferSource::onDraw(const Region& clip) const -{ - sp<Buffer> ourBuffer(getBuffer()); - if (UNLIKELY(ourBuffer == 0)) { - // nothing to do, we don't have a buffer - mLayer.clearWithOpenGL(clip); - return; - } - - status_t err = NO_ERROR; - NativeBuffer src(ourBuffer->getBuffer()); - const Rect transformedBounds(mLayer.getTransformedBounds()); - -#if defined(EGL_ANDROID_image_native_buffer) - if (GLExtensions::getInstance().haveDirectTexture()) { - err = INVALID_OPERATION; - if (ourBuffer->supportsCopybit()) { - copybit_device_t* copybit = mLayer.mBlitEngine; - if (copybit && err != NO_ERROR) { - // create our EGLImageKHR the first time - err = initTempBuffer(); - if (err == NO_ERROR) { - // NOTE: Assume the buffer is allocated with the proper USAGE flags - const NativeBuffer& dst(mTempBuffer); - region_iterator clip(Region(Rect(dst.crop.r, dst.crop.b))); - copybit->set_parameter(copybit, COPYBIT_TRANSFORM, 0); - copybit->set_parameter(copybit, COPYBIT_PLANE_ALPHA, 0xFF); - copybit->set_parameter(copybit, COPYBIT_DITHER, COPYBIT_ENABLE); - err = copybit->stretch(copybit, &dst.img, &src.img, - &dst.crop, &src.crop, &clip); - if (err != NO_ERROR) { - clearTempBufferImage(); - } - } - } - } - } -#endif - else { - err = INVALID_OPERATION; - } - - if (err != NO_ERROR) { - // slower fallback - GGLSurface t; - t.version = sizeof(GGLSurface); - t.width = src.crop.r; - t.height = src.crop.b; - t.stride = src.img.w; - t.vstride= src.img.h; - t.format = src.img.format; - t.data = (GGLubyte*)src.img.base; - const Region dirty(Rect(t.width, t.height)); - mTextureManager.loadTexture(&mTexture, dirty, t); - } - - mLayer.setBufferTransform(mBufferHeap.transform); - mLayer.drawWithOpenGL(clip, mTexture); -} - -status_t LayerBuffer::BufferSource::initTempBuffer() const -{ - // figure out the size we need now - const ISurface::BufferHeap& buffers(mBufferHeap); - uint32_t w = mLayer.mTransformedBounds.width(); - uint32_t h = mLayer.mTransformedBounds.height(); - if (mLayer.getOrientation() & (Transform::ROT_90 | Transform::ROT_270)) { - int t = w; w = h; h = t; - } - - // we're in the copybit case, so make sure we can handle this blit - // we don't have to keep the aspect ratio here - copybit_device_t* copybit = mLayer.mBlitEngine; - const int down = copybit->get(copybit, COPYBIT_MINIFICATION_LIMIT); - const int up = copybit->get(copybit, COPYBIT_MAGNIFICATION_LIMIT); - if (buffers.w > w*down) w = buffers.w / down; - else if (w > buffers.w*up) w = buffers.w*up; - if (buffers.h > h*down) h = buffers.h / down; - else if (h > buffers.h*up) h = buffers.h*up; - - if (mTexture.image != EGL_NO_IMAGE_KHR) { - // we have an EGLImage, make sure the needed size didn't change - if (w!=mTexture.width || h!= mTexture.height) { - // delete the EGLImage and texture - clearTempBufferImage(); - } else { - // we're good, we have an EGLImageKHR and it's (still) the - // right size - return NO_ERROR; - } - } - - // figure out if we need linear filtering - if (buffers.w * h == buffers.h * w) { - // same pixel area, don't use filtering - mLayer.mNeedsFiltering = false; - } - - // Allocate a temporary buffer and create the corresponding EGLImageKHR - // once the EGLImage has been created we don't need the - // graphic buffer reference anymore. - sp<GraphicBuffer> buffer = new GraphicBuffer( - w, h, HAL_PIXEL_FORMAT_RGB_565, - GraphicBuffer::USAGE_HW_TEXTURE | - GraphicBuffer::USAGE_HW_2D); - - status_t err = buffer->initCheck(); - if (err == NO_ERROR) { - NativeBuffer& dst(mTempBuffer); - dst.img.w = buffer->getStride(); - dst.img.h = h; - dst.img.format = buffer->getPixelFormat(); - dst.img.handle = (native_handle_t *)buffer->handle; - dst.img.base = 0; - dst.crop.l = 0; - dst.crop.t = 0; - dst.crop.r = w; - dst.crop.b = h; - - EGLDisplay dpy(getFlinger()->graphicPlane(0).getEGLDisplay()); - err = mTextureManager.initEglImage(&mTexture, dpy, buffer); - } - - return err; -} - -void LayerBuffer::BufferSource::clearTempBufferImage() const -{ - // delete the image - EGLDisplay dpy(getFlinger()->graphicPlane(0).getEGLDisplay()); - eglDestroyImageKHR(dpy, mTexture.image); - - // and the associated texture (recreate a name) - glDeleteTextures(1, &mTexture.name); - Texture defaultTexture; - mTexture = defaultTexture; -} - -// --------------------------------------------------------------------------- - -LayerBuffer::OverlaySource::OverlaySource(LayerBuffer& layer, - sp<OverlayRef>* overlayRef, - uint32_t w, uint32_t h, int32_t format, int32_t orientation) - : Source(layer), mVisibilityChanged(false), - mOverlay(0), mOverlayHandle(0), mOverlayDevice(0), mOrientation(orientation) -{ - overlay_control_device_t* overlay_dev = getFlinger()->getOverlayEngine(); - if (overlay_dev == NULL) { - // overlays not supported - return; - } - - mOverlayDevice = overlay_dev; - overlay_t* overlay = overlay_dev->createOverlay(overlay_dev, w, h, format); - if (overlay == NULL) { - // couldn't create the overlay (no memory? no more overlays?) - return; - } - - // enable dithering... - overlay_dev->setParameter(overlay_dev, overlay, - OVERLAY_DITHER, OVERLAY_ENABLE); - - mOverlay = overlay; - mWidth = overlay->w; - mHeight = overlay->h; - mFormat = overlay->format; - mWidthStride = overlay->w_stride; - mHeightStride = overlay->h_stride; - mInitialized = false; - - mOverlayHandle = overlay->getHandleRef(overlay); - - sp<OverlayChannel> channel = new OverlayChannel( &layer ); - - *overlayRef = new OverlayRef(mOverlayHandle, channel, - mWidth, mHeight, mFormat, mWidthStride, mHeightStride); - getFlinger()->signalEvent(); -} - -LayerBuffer::OverlaySource::~OverlaySource() -{ - if (mOverlay && mOverlayDevice) { - overlay_control_device_t* overlay_dev = mOverlayDevice; - overlay_dev->destroyOverlay(overlay_dev, mOverlay); - } -} - -void LayerBuffer::OverlaySource::onDraw(const Region& clip) const -{ - // this would be where the color-key would be set, should we need it. - GLclampf red = 0; - GLclampf green = 0; - GLclampf blue = 0; - mLayer.clearWithOpenGL(clip, red, green, blue, 0); -} - -void LayerBuffer::OverlaySource::onTransaction(uint32_t flags) -{ - const Layer::State& front(mLayer.drawingState()); - const Layer::State& temp(mLayer.currentState()); - if (temp.sequence != front.sequence) { - mVisibilityChanged = true; - } -} - -void LayerBuffer::OverlaySource::onvalidateVisibility(const Transform&) -{ - mVisibilityChanged = true; -} - -void LayerBuffer::OverlaySource::onVisibilityResolved( - const Transform& planeTransform) -{ - // this code-path must be as tight as possible, it's called each time - // the screen is composited. - if (UNLIKELY(mOverlay != 0)) { - if (mVisibilityChanged || !mInitialized) { - mVisibilityChanged = false; - mInitialized = true; - const Rect bounds(mLayer.getTransformedBounds()); - int x = bounds.left; - int y = bounds.top; - int w = bounds.width(); - int h = bounds.height(); - - // we need a lock here to protect "destroy" - Mutex::Autolock _l(mOverlaySourceLock); - if (mOverlay) { - overlay_control_device_t* overlay_dev = mOverlayDevice; - overlay_dev->setPosition(overlay_dev, mOverlay, x,y,w,h); - // we need to combine the layer orientation and the - // user-requested orientation. - Transform finalTransform(Transform(mLayer.getOrientation()) * - Transform(mOrientation)); - overlay_dev->setParameter(overlay_dev, mOverlay, - OVERLAY_TRANSFORM, finalTransform.getOrientation()); - overlay_dev->commit(overlay_dev, mOverlay); - } - } - } -} - -void LayerBuffer::OverlaySource::destroy() -{ - // we need a lock here to protect "onVisibilityResolved" - Mutex::Autolock _l(mOverlaySourceLock); - if (mOverlay && mOverlayDevice) { - overlay_control_device_t* overlay_dev = mOverlayDevice; - overlay_dev->destroyOverlay(overlay_dev, mOverlay); - mOverlay = 0; - } -} - -// --------------------------------------------------------------------------- -}; // namespace android diff --git a/services/surfaceflinger/LayerBuffer.h b/services/surfaceflinger/LayerBuffer.h deleted file mode 100644 index a89d8fe..0000000 --- a/services/surfaceflinger/LayerBuffer.h +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#ifndef ANDROID_LAYER_BUFFER_H -#define ANDROID_LAYER_BUFFER_H - -#include <stdint.h> -#include <sys/types.h> - -#include "LayerBase.h" -#include "TextureManager.h" - -struct copybit_device_t; - -namespace android { - -// --------------------------------------------------------------------------- - -class Buffer; -class Region; -class OverlayRef; - -// --------------------------------------------------------------------------- - -class LayerBuffer : public LayerBaseClient -{ - class Source : public LightRefBase<Source> { - public: - Source(LayerBuffer& layer); - virtual ~Source(); - virtual void onDraw(const Region& clip) const; - virtual void onTransaction(uint32_t flags); - virtual void onVisibilityResolved(const Transform& planeTransform); - virtual void onvalidateVisibility(const Transform& globalTransform) { } - virtual void postBuffer(ssize_t offset); - virtual void unregisterBuffers(); - virtual void destroy() { } - SurfaceFlinger* getFlinger() const { return mLayer.mFlinger.get(); } - protected: - LayerBuffer& mLayer; - }; - -public: - LayerBuffer(SurfaceFlinger* flinger, DisplayID display, - const sp<Client>& client); - virtual ~LayerBuffer(); - - virtual void onFirstRef(); - virtual bool needsBlending() const; - virtual const char* getTypeId() const { return "LayerBuffer"; } - - virtual sp<LayerBaseClient::Surface> createSurface() const; - virtual status_t ditch(); - virtual void onDraw(const Region& clip) const; - virtual void drawForSreenShot() const; - virtual uint32_t doTransaction(uint32_t flags); - virtual void unlockPageFlip(const Transform& planeTransform, Region& outDirtyRegion); - virtual void validateVisibility(const Transform& globalTransform); - - status_t registerBuffers(const ISurface::BufferHeap& buffers); - void postBuffer(ssize_t offset); - void unregisterBuffers(); - sp<OverlayRef> createOverlay(uint32_t w, uint32_t h, int32_t format, - int32_t orientation); - - sp<Source> getSource() const; - sp<Source> clearSource(); - void setNeedsBlending(bool blending); - Rect getTransformedBounds() const { - return mTransformedBounds; - } - - void serverDestroy(); - -private: - struct NativeBuffer { - copybit_image_t img; - copybit_rect_t crop; - }; - - static gralloc_module_t const* sGrallocModule; - static gralloc_module_t const* getGrallocModule() { - return sGrallocModule; - } - - class Buffer : public LightRefBase<Buffer> { - public: - Buffer(const ISurface::BufferHeap& buffers, - ssize_t offset, size_t bufferSize); - inline bool supportsCopybit() const { - return mSupportsCopybit; - } - inline status_t getStatus() const { - return mBufferHeap.heap!=0 ? NO_ERROR : NO_INIT; - } - inline const NativeBuffer& getBuffer() const { - return mNativeBuffer; - } - protected: - friend class LightRefBase<Buffer>; - Buffer& operator = (const Buffer& rhs); - Buffer(const Buffer& rhs); - ~Buffer(); - private: - ISurface::BufferHeap mBufferHeap; - NativeBuffer mNativeBuffer; - bool mSupportsCopybit; - }; - - class BufferSource : public Source { - public: - BufferSource(LayerBuffer& layer, const ISurface::BufferHeap& buffers); - virtual ~BufferSource(); - - status_t getStatus() const { return mStatus; } - sp<Buffer> getBuffer() const; - void setBuffer(const sp<Buffer>& buffer); - - virtual void onDraw(const Region& clip) const; - virtual void postBuffer(ssize_t offset); - virtual void unregisterBuffers(); - virtual void destroy() { } - private: - status_t initTempBuffer() const; - void clearTempBufferImage() const; - mutable Mutex mBufferSourceLock; - sp<Buffer> mBuffer; - status_t mStatus; - ISurface::BufferHeap mBufferHeap; - size_t mBufferSize; - mutable Texture mTexture; - mutable NativeBuffer mTempBuffer; - mutable TextureManager mTextureManager; - }; - - class OverlaySource : public Source { - public: - OverlaySource(LayerBuffer& layer, - sp<OverlayRef>* overlayRef, - uint32_t w, uint32_t h, int32_t format, int32_t orientation); - virtual ~OverlaySource(); - virtual void onDraw(const Region& clip) const; - virtual void onTransaction(uint32_t flags); - virtual void onVisibilityResolved(const Transform& planeTransform); - virtual void onvalidateVisibility(const Transform& globalTransform); - virtual void destroy(); - private: - - class OverlayChannel : public BnOverlay { - wp<LayerBuffer> mLayer; - virtual void destroy() { - sp<LayerBuffer> layer(mLayer.promote()); - if (layer != 0) { - layer->serverDestroy(); - } - } - public: - OverlayChannel(const sp<LayerBuffer>& layer) - : mLayer(layer) { - } - }; - - friend class OverlayChannel; - bool mVisibilityChanged; - - overlay_t* mOverlay; - overlay_handle_t mOverlayHandle; - overlay_control_device_t* mOverlayDevice; - uint32_t mWidth; - uint32_t mHeight; - int32_t mFormat; - int32_t mWidthStride; - int32_t mHeightStride; - int32_t mOrientation; - mutable Mutex mOverlaySourceLock; - bool mInitialized; - }; - - - class SurfaceLayerBuffer : public LayerBaseClient::Surface - { - public: - SurfaceLayerBuffer(const sp<SurfaceFlinger>& flinger, - const sp<LayerBuffer>& owner); - virtual ~SurfaceLayerBuffer(); - - virtual status_t registerBuffers(const ISurface::BufferHeap& buffers); - virtual void postBuffer(ssize_t offset); - virtual void unregisterBuffers(); - - virtual sp<OverlayRef> createOverlay( - uint32_t w, uint32_t h, int32_t format, int32_t orientation); - private: - sp<LayerBuffer> getOwner() const { - return static_cast<LayerBuffer*>(Surface::getOwner().get()); - } - }; - - mutable Mutex mLock; - sp<Source> mSource; - sp<Surface> mSurface; - bool mInvalidate; - bool mNeedsBlending; - copybit_device_t* mBlitEngine; -}; - -// --------------------------------------------------------------------------- - -}; // namespace android - -#endif // ANDROID_LAYER_BUFFER_H diff --git a/services/surfaceflinger/LayerDim.cpp b/services/surfaceflinger/LayerDim.cpp index 80cc52c..f79166d 100644 --- a/services/surfaceflinger/LayerDim.cpp +++ b/services/surfaceflinger/LayerDim.cpp @@ -30,29 +30,12 @@ namespace android { // --------------------------------------------------------------------------- -bool LayerDim::sUseTexture; -GLuint LayerDim::sTexId; -EGLImageKHR LayerDim::sImage; -int32_t LayerDim::sWidth; -int32_t LayerDim::sHeight; - -// --------------------------------------------------------------------------- - LayerDim::LayerDim(SurfaceFlinger* flinger, DisplayID display, const sp<Client>& client) : LayerBaseClient(flinger, display, client) { } -void LayerDim::initDimmer(SurfaceFlinger* flinger, uint32_t w, uint32_t h) -{ - sTexId = -1; - sImage = EGL_NO_IMAGE_KHR; - sWidth = w; - sHeight = h; - sUseTexture = false; -} - LayerDim::~LayerDim() { } @@ -67,8 +50,14 @@ void LayerDim::onDraw(const Region& clip) const const GLfloat alpha = s.alpha/255.0f; const uint32_t fbHeight = hw.getHeight(); glDisable(GL_DITHER); - glEnable(GL_BLEND); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + if (s.alpha == 0xFF) { + glDisable(GL_BLEND); + } else { + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + } + glColor4f(0, 0, 0, alpha); #if defined(GL_OES_EGL_image_external) @@ -78,15 +67,7 @@ void LayerDim::onDraw(const Region& clip) const #endif glDisable(GL_TEXTURE_2D); - GLshort w = sWidth; - GLshort h = sHeight; - const GLshort vertices[4][2] = { - { 0, 0 }, - { 0, h }, - { w, h }, - { w, 0 } - }; - glVertexPointer(2, GL_SHORT, 0, vertices); + glVertexPointer(2, GL_FLOAT, 0, mVertices); while (it != end) { const Rect& r = *it++; diff --git a/services/surfaceflinger/LayerDim.h b/services/surfaceflinger/LayerDim.h index f032314..5631c0a 100644 --- a/services/surfaceflinger/LayerDim.h +++ b/services/surfaceflinger/LayerDim.h @@ -31,11 +31,6 @@ namespace android { class LayerDim : public LayerBaseClient { - static bool sUseTexture; - static GLuint sTexId; - static EGLImageKHR sImage; - static int32_t sWidth; - static int32_t sHeight; public: LayerDim(SurfaceFlinger* flinger, DisplayID display, const sp<Client>& client); @@ -45,8 +40,6 @@ public: virtual bool needsBlending() const { return true; } virtual bool isSecure() const { return false; } virtual const char* getTypeId() const { return "LayerDim"; } - - static void initDimmer(SurfaceFlinger* flinger, uint32_t w, uint32_t h); }; // --------------------------------------------------------------------------- diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 4b3235d..40882d8 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -47,12 +47,11 @@ #include "clz.h" #include "GLExtensions.h" #include "Layer.h" -#include "LayerBlur.h" -#include "LayerBuffer.h" #include "LayerDim.h" #include "SurfaceFlinger.h" #include "DisplayHardware/DisplayHardware.h" +#include "DisplayHardware/HWComposer.h" /* ideally AID_GRAPHICS would be in a semi-public header * or there would be a way to map a user/group name to its id @@ -61,10 +60,6 @@ #define AID_GRAPHICS 1003 #endif -#ifdef USE_COMPOSITION_BYPASS -#warning "using COMPOSITION_BYPASS" -#endif - #define DISPLAY_COUNT 1 namespace android { @@ -82,6 +77,7 @@ SurfaceFlinger::SurfaceFlinger() mReadFramebuffer("android.permission.READ_FRAME_BUFFER"), mDump("android.permission.DUMP"), mVisibleRegionsDirty(false), + mHwWorkListDirty(false), mDeferReleaseConsole(false), mFreezeDisplay(false), mElectronBeamAnimationMode(0), @@ -89,6 +85,7 @@ SurfaceFlinger::SurfaceFlinger() mFreezeDisplayTime(0), mDebugRegion(0), mDebugBackground(0), + mDebugDisableHWC(0), mDebugInSwapBuffers(0), mLastSwapBufferTime(0), mDebugInTransaction(0), @@ -120,11 +117,6 @@ SurfaceFlinger::~SurfaceFlinger() glDeleteTextures(1, &mWormholeTexName); } -overlay_control_device_t* SurfaceFlinger::getOverlayEngine() const -{ - return graphicPlane(0).displayHardware().getOverlayEngine(); -} - sp<IMemoryHeap> SurfaceFlinger::getCblk() const { return mServerHeap; @@ -152,6 +144,11 @@ sp<ISurfaceComposerClient> SurfaceFlinger::createClientConnection() return bclient; } +sp<IGraphicBufferAlloc> SurfaceFlinger::createGraphicBufferAlloc() +{ + sp<GraphicBufferAlloc> gba(new GraphicBufferAlloc()); + return gba; +} const GraphicPlane& SurfaceFlinger::graphicPlane(int dpy) const { @@ -170,7 +167,7 @@ void SurfaceFlinger::bootFinished() { const nsecs_t now = systemTime(); const nsecs_t duration = now - mBootTime; - LOGI("Boot is finished (%ld ms)", long(ns2ms(duration)) ); + LOGI("Boot is finished (%ld ms)", long(ns2ms(duration)) ); mBootFinished = true; property_set("ctl.stop", "bootanim"); } @@ -206,10 +203,10 @@ status_t SurfaceFlinger::readyToRun() mServerHeap = new MemoryHeapBase(4096, MemoryHeapBase::READ_ONLY, "SurfaceFlinger read-only heap"); LOGE_IF(mServerHeap==0, "can't create shared memory dealer"); - + mServerCblk = static_cast<surface_flinger_cblk_t*>(mServerHeap->getBase()); LOGE_IF(mServerCblk==0, "can't get to shared control block's address"); - + new(mServerCblk) surface_flinger_cblk_t; // initialize primary screen @@ -238,7 +235,7 @@ status_t SurfaceFlinger::readyToRun() // Initialize OpenGL|ES glPixelStorei(GL_UNPACK_ALIGNMENT, 4); - glPixelStorei(GL_PACK_ALIGNMENT, 4); + glPixelStorei(GL_PACK_ALIGNMENT, 4); glEnableClientState(GL_VERTEX_ARRAY); glEnable(GL_SCISSOR_TEST); glShadeModel(GL_FLAT); @@ -262,8 +259,6 @@ status_t SurfaceFlinger::readyToRun() glLoadIdentity(); glOrthof(0, w, h, 0, 0, 1); - LayerDim::initDimmer(this, w, h); - mReadyToRunBarrier.open(); /* @@ -272,7 +267,7 @@ status_t SurfaceFlinger::readyToRun() // start boot animation property_set("ctl.start", "bootanim"); - + return NO_ERROR; } @@ -375,17 +370,15 @@ bool SurfaceFlinger::threadLoop() // post surfaces (if needed) handlePageFlip(); + if (UNLIKELY(mHwWorkListDirty)) { + // build the h/w work list + handleWorkList(); + } + const DisplayHardware& hw(graphicPlane(0).displayHardware()); if (LIKELY(hw.canDraw() && !isFrozen())) { - -#ifdef USE_COMPOSITION_BYPASS - if (handleBypassLayer()) { - unlockClients(); - return true; - } -#endif - // repaint the framebuffer (if needed) + const int index = hw.getCurrentBufferIndex(); GraphicLog& logger(GraphicLog::getInstance()); @@ -408,20 +401,6 @@ bool SurfaceFlinger::threadLoop() return true; } -bool SurfaceFlinger::handleBypassLayer() -{ - sp<Layer> bypassLayer(mBypassLayer.promote()); - if (bypassLayer != 0) { - sp<GraphicBuffer> buffer(bypassLayer->getBypassBuffer()); - if (buffer!=0 && (buffer->usage & GRALLOC_USAGE_HW_FB)) { - const DisplayHardware& hw(graphicPlane(0).displayHardware()); - hw.postBypassBuffer(buffer->handle); - return true; - } - } - return false; -} - void SurfaceFlinger::postFramebuffer() { if (!mInvalidRegion.isEmpty()) { @@ -480,6 +459,7 @@ void SurfaceFlinger::handleTransaction(uint32_t transactionFlags) handleTransactionLocked(transactionFlags, ditchedLayers); mLastTransactionTime = systemTime() - now; mDebugInTransaction = 0; + invalidateHwcGeometry(); // here the transaction has been committed } @@ -487,6 +467,7 @@ void SurfaceFlinger::handleTransaction(uint32_t transactionFlags) * Clean-up all layers that went away * (do this without the lock held) */ + const size_t count = ditchedLayers.size(); for (size_t i=0 ; i<count ; i++) { if (ditchedLayers[i] != 0) { @@ -690,7 +671,7 @@ void SurfaceFlinger::computeVisibleRegions( // Update aboveOpaqueLayers for next (lower) layer aboveOpaqueLayers.orSelf(opaqueRegion); - + // Store the visible region is screen space layer->setVisibleRegion(visibleRegion); layer->setCoveredRegion(coveredRegion); @@ -717,37 +698,11 @@ void SurfaceFlinger::commitTransaction() mTransactionCV.broadcast(); } -void SurfaceFlinger::setBypassLayer(const sp<LayerBase>& layer) -{ - // if this layer is already the bypass layer, do nothing - sp<Layer> cur(mBypassLayer.promote()); - if (mBypassLayer == layer) { - if (cur != NULL) { - cur->updateBuffersOrientation(); - } - return; - } - - // clear the current bypass layer - mBypassLayer.clear(); - if (cur != 0) { - cur->setBypass(false); - cur.clear(); - } - - // set new bypass layer - if (layer != 0) { - if (layer->setBypass(true)) { - mBypassLayer = static_cast<Layer*>(layer.get()); - } - } -} - void SurfaceFlinger::handlePageFlip() { bool visibleRegions = mVisibleRegionsDirty; - LayerVector& currentLayers = const_cast<LayerVector&>( - mDrawingState.layersSortedByZ); + LayerVector& currentLayers( + const_cast<LayerVector&>(mDrawingState.layersSortedByZ)); visibleRegions |= lockPageFlip(currentLayers); const DisplayHardware& hw = graphicPlane(0).displayHardware(); @@ -768,29 +723,20 @@ void SurfaceFlinger::handlePageFlip() mVisibleLayersSortedByZ.add(currentLayers[i]); } -#ifdef USE_COMPOSITION_BYPASS - sp<LayerBase> bypassLayer; - const size_t numVisibleLayers = mVisibleLayersSortedByZ.size(); - if (numVisibleLayers == 1) { - const sp<LayerBase>& candidate(mVisibleLayersSortedByZ[0]); - const Region& visibleRegion(candidate->visibleRegionScreen); - const Region reminder(screenRegion.subtract(visibleRegion)); - if (reminder.isEmpty()) { - // fullscreen candidate! - bypassLayer = candidate; - } - } - setBypassLayer(bypassLayer); -#endif - mWormholeRegion = screenRegion.subtract(opaqueRegion); mVisibleRegionsDirty = false; + invalidateHwcGeometry(); } unlockPageFlip(currentLayers); mDirtyRegion.andSelf(screenRegion); } +void SurfaceFlinger::invalidateHwcGeometry() +{ + mHwWorkListDirty = true; +} + bool SurfaceFlinger::lockPageFlip(const LayerVector& currentLayers) { bool recomputeVisibleRegions = false; @@ -815,15 +761,29 @@ void SurfaceFlinger::unlockPageFlip(const LayerVector& currentLayers) } } +void SurfaceFlinger::handleWorkList() +{ + mHwWorkListDirty = false; + HWComposer& hwc(graphicPlane(0).displayHardware().getHwComposer()); + if (hwc.initCheck() == NO_ERROR) { + const Vector< sp<LayerBase> >& currentLayers(mVisibleLayersSortedByZ); + const size_t count = currentLayers.size(); + hwc.createWorkList(count); + hwc_layer_t* const cur(hwc.getLayers()); + for (size_t i=0 ; cur && i<count ; i++) { + currentLayers[i]->setGeometry(&cur[i]); + if (mDebugDisableHWC) { + cur[i].compositionType = HWC_FRAMEBUFFER; + cur[i].flags |= HWC_SKIP_LAYER; + } + } + } +} void SurfaceFlinger::handleRepaint() { // compute the invalid region mInvalidRegion.orSelf(mDirtyRegion); - if (mInvalidRegion.isEmpty()) { - // nothing to do - return; - } if (UNLIKELY(mDebugRegion)) { debugFlashRegions(); @@ -835,8 +795,8 @@ void SurfaceFlinger::handleRepaint() glLoadIdentity(); uint32_t flags = hw.getFlags(); - if ((flags & DisplayHardware::SWAP_RECTANGLE) || - (flags & DisplayHardware::BUFFER_PRESERVED)) + if ((flags & DisplayHardware::SWAP_RECTANGLE) || + (flags & DisplayHardware::BUFFER_PRESERVED)) { // we can redraw only what's dirty, but since SWAP_RECTANGLE only // takes a rectangle, we must make sure to update that whole @@ -879,9 +839,79 @@ void SurfaceFlinger::composeSurfaces(const Region& dirty) // draw something... drawWormhole(); } + + status_t err = NO_ERROR; const Vector< sp<LayerBase> >& layers(mVisibleLayersSortedByZ); - const size_t count = layers.size(); - for (size_t i=0 ; i<count ; ++i) { + size_t count = layers.size(); + + const DisplayHardware& hw(graphicPlane(0).displayHardware()); + HWComposer& hwc(hw.getHwComposer()); + hwc_layer_t* const cur(hwc.getLayers()); + + LOGE_IF(cur && hwc.getNumLayers() != count, + "HAL number of layers (%d) doesn't match surfaceflinger (%d)", + hwc.getNumLayers(), count); + + // just to be extra-safe, use the smallest count + if (hwc.initCheck() == NO_ERROR) { + count = count < hwc.getNumLayers() ? count : hwc.getNumLayers(); + } + + /* + * update the per-frame h/w composer data for each layer + * and build the transparent region of the FB + */ + Region transparent; + if (cur) { + for (size_t i=0 ; i<count ; i++) { + const sp<LayerBase>& layer(layers[i]); + layer->setPerFrameData(&cur[i]); + } + err = hwc.prepare(); + LOGE_IF(err, "HWComposer::prepare failed (%s)", strerror(-err)); + + if (err == NO_ERROR) { + for (size_t i=0 ; i<count ; i++) { + if (cur[i].hints & HWC_HINT_CLEAR_FB) { + const sp<LayerBase>& layer(layers[i]); + if (!(layer->needsBlending())) { + transparent.orSelf(layer->visibleRegionScreen); + } + } + } + + /* + * clear the area of the FB that need to be transparent + */ + transparent.andSelf(dirty); + if (!transparent.isEmpty()) { + glClearColor(0,0,0,0); + Region::const_iterator it = transparent.begin(); + Region::const_iterator const end = transparent.end(); + const int32_t height = hw.getHeight(); + while (it != end) { + const Rect& r(*it++); + const GLint sy = height - (r.top + r.height()); + glScissor(r.left, sy, r.width(), r.height()); + glClear(GL_COLOR_BUFFER_BIT); + } + } + } + } + + + /* + * and then, render the layers targeted at the framebuffer + */ + for (size_t i=0 ; i<count ; i++) { + if (cur) { + if ((cur[i].compositionType != HWC_FRAMEBUFFER) && + !(cur[i].flags & HWC_SKIP_LAYER)) { + // skip layers handled by the HAL + continue; + } + } + const sp<LayerBase>& layer(layers[i]); const Region clip(dirty.intersect(layer->visibleRegionScreen)); if (!clip.isEmpty()) { @@ -993,6 +1023,8 @@ void SurfaceFlinger::drawWormhole() const glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); } } @@ -1054,7 +1086,7 @@ status_t SurfaceFlinger::removeLayer_l(const sp<LayerBase>& layerBase) { sp<LayerBaseClient> lbc(layerBase->getLayerBaseClient()); if (lbc != 0) { - mLayerMap.removeItem( lbc->getSurface()->asBinder() ); + mLayerMap.removeItem( lbc->getSurfaceBinder() ); } ssize_t index = mCurrentState.layersSortedByZ.remove(layerBase); if (index >= 0) { @@ -1113,7 +1145,7 @@ void SurfaceFlinger::closeGlobalTransaction() if (android_atomic_dec(&mTransactionCount) == 1) { signalEvent(); - // if there is a transaction with a resize, wait for it to + // if there is a transaction with a resize, wait for it to // take effect before returning. Mutex::Autolock _l(mStateLock); while (mResizeTransationPending) { @@ -1157,7 +1189,7 @@ status_t SurfaceFlinger::unfreezeDisplay(DisplayID dpy, uint32_t flags) return NO_ERROR; } -int SurfaceFlinger::setOrientation(DisplayID dpy, +int SurfaceFlinger::setOrientation(DisplayID dpy, int orientation, uint32_t flags) { if (UNLIKELY(uint32_t(dpy) >= DISPLAY_COUNT)) @@ -1190,21 +1222,17 @@ sp<ISurface> SurfaceFlinger::createSurface(const sp<Client>& client, int pid, int(w), int(h)); return surfaceHandle; } - + //LOGD("createSurface for pid %d (%d x %d)", pid, w, h); sp<Layer> normalLayer; switch (flags & eFXSurfaceMask) { case eFXSurfaceNormal: - if (UNLIKELY(flags & ePushBuffers)) { - layer = createPushBuffersSurface(client, d, w, h, flags); - } else { - normalLayer = createNormalSurface(client, d, w, h, flags, format); - layer = normalLayer; - } + normalLayer = createNormalSurface(client, d, w, h, flags, format); + layer = normalLayer; break; case eFXSurfaceBlur: - layer = createBlurSurface(client, d, w, h, flags); - break; + // for now we treat Blur as Dim, until we can implement it + // efficiently. case eFXSurfaceDim: layer = createDimSurface(client, d, w, h, flags); break; @@ -1216,7 +1244,7 @@ sp<ISurface> SurfaceFlinger::createSurface(const sp<Client>& client, int pid, ssize_t token = addClientLayer(client, layer); surfaceHandle = layer->getSurface(); - if (surfaceHandle != 0) { + if (surfaceHandle != 0) { params->token = token; params->identity = surfaceHandle->getIdentity(); params->width = w; @@ -1268,15 +1296,6 @@ sp<Layer> SurfaceFlinger::createNormalSurface( return layer; } -sp<LayerBlur> SurfaceFlinger::createBlurSurface( - const sp<Client>& client, DisplayID display, - uint32_t w, uint32_t h, uint32_t flags) -{ - sp<LayerBlur> layer = new LayerBlur(this, display, client); - layer->initStates(w, h, flags); - return layer; -} - sp<LayerDim> SurfaceFlinger::createDimSurface( const sp<Client>& client, DisplayID display, uint32_t w, uint32_t h, uint32_t flags) @@ -1286,21 +1305,12 @@ sp<LayerDim> SurfaceFlinger::createDimSurface( return layer; } -sp<LayerBuffer> SurfaceFlinger::createPushBuffersSurface( - const sp<Client>& client, DisplayID display, - uint32_t w, uint32_t h, uint32_t flags) -{ - sp<LayerBuffer> layer = new LayerBuffer(this, display, client); - layer->initStates(w, h, flags); - return layer; -} - status_t SurfaceFlinger::removeSurface(const sp<Client>& client, SurfaceID sid) { /* * called by the window manager, when a surface should be marked for * destruction. - * + * * The surface is removed from the current and drawing lists, but placed * in the purgatory queue, so it's not destroyed right-away (we need * to wait for all client's references to go away first). @@ -1321,7 +1331,7 @@ status_t SurfaceFlinger::removeSurface(const sp<Client>& client, SurfaceID sid) status_t SurfaceFlinger::destroySurface(const sp<LayerBaseClient>& layer) { // called by ~ISurface() when all references are gone - + class MessageDestroySurface : public MessageBase { SurfaceFlinger* flinger; sp<LayerBaseClient> layer; @@ -1334,9 +1344,9 @@ status_t SurfaceFlinger::destroySurface(const sp<LayerBaseClient>& layer) layer.clear(); // clear it outside of the lock; Mutex::Autolock _l(flinger->mStateLock); /* - * remove the layer from the current list -- chances are that it's - * not in the list anyway, because it should have been removed - * already upon request of the client (eg: window manager). + * remove the layer from the current list -- chances are that it's + * not in the list anyway, because it should have been removed + * already upon request of the client (eg: window manager). * However, a buggy client could have not done that. * Since we know we don't have any more clients, we don't need * to use the purgatory. @@ -1437,7 +1447,7 @@ void SurfaceFlinger::screenAcquired(int dpy) status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args) { - const size_t SIZE = 1024; + const size_t SIZE = 4096; char buffer[SIZE]; String8 result; if (!mDump.checkCalling()) { @@ -1464,7 +1474,7 @@ status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args) } const bool locked(retry >= 0); if (!locked) { - snprintf(buffer, SIZE, + snprintf(buffer, SIZE, "SurfaceFlinger appears to be unresponsive, " "dumping anyways (no locks held)\n"); result.append(buffer); @@ -1507,9 +1517,9 @@ status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args) mWormholeRegion.dump(result, "WormholeRegion"); const DisplayHardware& hw(graphicPlane(0).displayHardware()); snprintf(buffer, SIZE, - " display frozen: %s, freezeCount=%d, orientation=%d, bypass=%p, canDraw=%d\n", + " display frozen: %s, freezeCount=%d, orientation=%d, canDraw=%d\n", mFreezeDisplay?"yes":"no", mFreezeCount, - mCurrentState.orientation, mBypassLayer.unsafe_get(), hw.canDraw()); + mCurrentState.orientation, hw.canDraw()); result.append(buffer); snprintf(buffer, SIZE, " last eglSwapBuffers() time: %f us\n" @@ -1530,10 +1540,21 @@ status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args) } /* + * Dump HWComposer state + */ + HWComposer& hwc(hw.getHwComposer()); + snprintf(buffer, SIZE, " h/w composer %s and %s\n", + hwc.initCheck()==NO_ERROR ? "present" : "not present", + mDebugDisableHWC ? "disabled" : "enabled"); + result.append(buffer); + hwc.dump(result, buffer, SIZE); + + /* * Dump gralloc state */ const GraphicBufferAllocator& alloc(GraphicBufferAllocator::get()); alloc.dump(result); + hw.dump(result); if (locked) { mStateLock.unlock(); @@ -1607,6 +1628,11 @@ status_t SurfaceFlinger::onTransact( n = data.readInt32(); mDebugBackground = n ? 1 : 0; return NO_ERROR; + case 1008: // toggle use of hw composer + n = data.readInt32(); + mDebugDisableHWC = n ? 1 : 0; + invalidateHwcGeometry(); + // fall-through... case 1004:{ // repaint everything Mutex::Autolock _l(mStateLock); const DisplayHardware& hw(graphicPlane(0).displayHardware()); @@ -2100,7 +2126,8 @@ status_t SurfaceFlinger::turnElectronBeamOn(int32_t mode) status_t SurfaceFlinger::captureScreenImplLocked(DisplayID dpy, sp<IMemoryHeap>* heap, uint32_t* w, uint32_t* h, PixelFormat* f, - uint32_t sw, uint32_t sh) + uint32_t sw, uint32_t sh, + uint32_t minLayerZ, uint32_t maxLayerZ) { status_t result = PERMISSION_DENIED; @@ -2123,6 +2150,9 @@ status_t SurfaceFlinger::captureScreenImplLocked(DisplayID dpy, sh = (!sh) ? hw_h : sh; const size_t size = sw * sh * 4; + LOGD("screenshot: sw=%d, sh=%d, minZ=%d, maxZ=%d", + sw, sh, minLayerZ, maxLayerZ); + // make sure to clear all GL error flags while ( glGetError() != GL_NO_ERROR ) ; @@ -2137,6 +2167,7 @@ status_t SurfaceFlinger::captureScreenImplLocked(DisplayID dpy, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, tname); GLenum status = glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES); + if (status == GL_FRAMEBUFFER_COMPLETE_OES) { // invert everything, b/c glReadPixel() below will invert the FB @@ -2156,7 +2187,10 @@ status_t SurfaceFlinger::captureScreenImplLocked(DisplayID dpy, const size_t count = layers.size(); for (size_t i=0 ; i<count ; ++i) { const sp<LayerBase>& layer(layers[i]); - layer->drawForSreenShot(); + const uint32_t z = layer->drawingState().z; + if (z >= minLayerZ && z <= maxLayerZ) { + layer->drawForSreenShot(); + } } // XXX: this is needed on tegra @@ -2191,8 +2225,6 @@ status_t SurfaceFlinger::captureScreenImplLocked(DisplayID dpy, glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); - - } else { result = BAD_VALUE; } @@ -2204,6 +2236,8 @@ status_t SurfaceFlinger::captureScreenImplLocked(DisplayID dpy, hw.compositionComplete(); + LOGD("screenshot: result = %s", result<0 ? strerror(result) : "OK"); + return result; } @@ -2211,7 +2245,8 @@ status_t SurfaceFlinger::captureScreenImplLocked(DisplayID dpy, status_t SurfaceFlinger::captureScreen(DisplayID dpy, sp<IMemoryHeap>* heap, uint32_t* width, uint32_t* height, PixelFormat* format, - uint32_t sw, uint32_t sh) + uint32_t sw, uint32_t sh, + uint32_t minLayerZ, uint32_t maxLayerZ) { // only one display supported for now if (UNLIKELY(uint32_t(dpy) >= DISPLAY_COUNT)) @@ -2229,13 +2264,18 @@ status_t SurfaceFlinger::captureScreen(DisplayID dpy, PixelFormat* f; uint32_t sw; uint32_t sh; + uint32_t minLayerZ; + uint32_t maxLayerZ; status_t result; public: MessageCaptureScreen(SurfaceFlinger* flinger, DisplayID dpy, sp<IMemoryHeap>* heap, uint32_t* w, uint32_t* h, PixelFormat* f, - uint32_t sw, uint32_t sh) + uint32_t sw, uint32_t sh, + uint32_t minLayerZ, uint32_t maxLayerZ) : flinger(flinger), dpy(dpy), - heap(heap), w(w), h(h), f(f), sw(sw), sh(sh), result(PERMISSION_DENIED) + heap(heap), w(w), h(h), f(f), sw(sw), sh(sh), + minLayerZ(minLayerZ), maxLayerZ(maxLayerZ), + result(PERMISSION_DENIED) { } status_t getResult() const { @@ -2249,14 +2289,14 @@ status_t SurfaceFlinger::captureScreen(DisplayID dpy, return true; result = flinger->captureScreenImplLocked(dpy, - heap, w, h, f, sw, sh); + heap, w, h, f, sw, sh, minLayerZ, maxLayerZ); return true; } }; sp<MessageBase> msg = new MessageCaptureScreen(this, - dpy, heap, width, height, format, sw, sh); + dpy, heap, width, height, format, sw, sh, minLayerZ, maxLayerZ); status_t res = postMessageSync(msg); if (res == NO_ERROR) { res = static_cast<MessageCaptureScreen*>( msg.get() )->getResult(); @@ -2406,12 +2446,15 @@ ssize_t UserClient::getTokenForSurface(const sp<ISurface>& sur) const { int32_t name = NAME_NOT_FOUND; sp<Layer> layer(mFlinger->getLayer(sur)); - if (layer == 0) return name; + if (layer == 0) { + return name; + } // if this layer already has a token, just return it name = layer->getToken(); - if ((name >= 0) && (layer->getClient() == this)) + if ((name >= 0) && (layer->getClient() == this)) { return name; + } name = 0; do { @@ -2452,6 +2495,39 @@ status_t UserClient::setState(int32_t count, const layer_state_t* states) { // --------------------------------------------------------------------------- +GraphicBufferAlloc::GraphicBufferAlloc() {} + +GraphicBufferAlloc::~GraphicBufferAlloc() {} + +sp<GraphicBuffer> GraphicBufferAlloc::createGraphicBuffer(uint32_t w, uint32_t h, + PixelFormat format, uint32_t usage) { + sp<GraphicBuffer> graphicBuffer(new GraphicBuffer(w, h, format, usage)); + status_t err = graphicBuffer->initCheck(); + if (err != 0) { + LOGE("createGraphicBuffer: init check failed: %d", err); + return 0; + } else if (graphicBuffer->handle == 0) { + LOGE("createGraphicBuffer: unable to create GraphicBuffer"); + return 0; + } + Mutex::Autolock _l(mLock); + mBuffers.add(graphicBuffer); + return graphicBuffer; +} + +void GraphicBufferAlloc::freeAllGraphicBuffersExcept(int bufIdx) { + Mutex::Autolock _l(mLock); + if (0 <= bufIdx && bufIdx < mBuffers.size()) { + sp<GraphicBuffer> b(mBuffers[bufIdx]); + mBuffers.clear(); + mBuffers.add(b); + } else { + mBuffers.clear(); + } +} + +// --------------------------------------------------------------------------- + GraphicPlane::GraphicPlane() : mHw(0) { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index a023347..6dd91ac 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -34,6 +34,7 @@ #include <ui/PixelFormat.h> #include <surfaceflinger/ISurfaceComposer.h> #include <surfaceflinger/ISurfaceComposerClient.h> +#include <surfaceflinger/IGraphicBufferAlloc.h> #include "Barrier.h" #include "Layer.h" @@ -48,9 +49,7 @@ class Client; class DisplayHardware; class FreezeLock; class Layer; -class LayerBlur; class LayerDim; -class LayerBuffer; #define LIKELY( exp ) (__builtin_expect( (exp) != 0, true )) #define UNLIKELY( exp ) (__builtin_expect( (exp) != 0, false )) @@ -121,6 +120,21 @@ private: sp<SurfaceFlinger> mFlinger; }; +class GraphicBufferAlloc : public BnGraphicBufferAlloc +{ +public: + GraphicBufferAlloc(); + virtual ~GraphicBufferAlloc(); + + virtual sp<GraphicBuffer> createGraphicBuffer(uint32_t w, uint32_t h, + PixelFormat format, uint32_t usage); + virtual void freeAllGraphicBuffersExcept(int bufIdx); + +private: + Vector<sp<GraphicBuffer> > mBuffers; + Mutex mLock; +}; + // --------------------------------------------------------------------------- class GraphicPlane @@ -186,6 +200,7 @@ public: // ISurfaceComposer interface virtual sp<ISurfaceComposerClient> createConnection(); virtual sp<ISurfaceComposerClient> createClientConnection(); + virtual sp<IGraphicBufferAlloc> createGraphicBufferAlloc(); virtual sp<IMemoryHeap> getCblk() const; virtual void bootFinished(); virtual void openGlobalTransaction(); @@ -194,35 +209,32 @@ public: virtual status_t unfreezeDisplay(DisplayID dpy, uint32_t flags); virtual int setOrientation(DisplayID dpy, int orientation, uint32_t flags); virtual void signal() const; - virtual status_t captureScreen(DisplayID dpy, - sp<IMemoryHeap>* heap, - uint32_t* width, - uint32_t* height, - PixelFormat* format, - uint32_t reqWidth, - uint32_t reqHeight); + + virtual status_t captureScreen(DisplayID dpy, + sp<IMemoryHeap>* heap, + uint32_t* width, uint32_t* height, + PixelFormat* format, uint32_t reqWidth, uint32_t reqHeight, + uint32_t minLayerZ, uint32_t maxLayerZ); + virtual status_t turnElectronBeamOff(int32_t mode); virtual status_t turnElectronBeamOn(int32_t mode); void screenReleased(DisplayID dpy); void screenAcquired(DisplayID dpy); - overlay_control_device_t* getOverlayEngine() const; - status_t removeLayer(const sp<LayerBase>& layer); status_t addLayer(const sp<LayerBase>& layer); status_t invalidateLayerVisibility(const sp<LayerBase>& layer); + void invalidateHwcGeometry(); sp<Layer> getLayer(const sp<ISurface>& sur) const; private: friend class Client; friend class LayerBase; - friend class LayerBuffer; friend class LayerBaseClient; friend class LayerBaseClient::Surface; friend class Layer; - friend class LayerBlur; friend class LayerDim; sp<ISurface> createSurface(const sp<Client>& client, @@ -236,18 +248,10 @@ private: uint32_t w, uint32_t h, uint32_t flags, PixelFormat& format); - sp<LayerBlur> createBlurSurface( - const sp<Client>& client, DisplayID display, - uint32_t w, uint32_t h, uint32_t flags); - sp<LayerDim> createDimSurface( const sp<Client>& client, DisplayID display, uint32_t w, uint32_t h, uint32_t flags); - sp<LayerBuffer> createPushBuffersSurface( - const sp<Client>& client, DisplayID display, - uint32_t w, uint32_t h, uint32_t flags); - status_t removeSurface(const sp<Client>& client, SurfaceID sid); status_t destroySurface(const sp<LayerBaseClient>& layer); status_t setClientState(const sp<Client>& client, @@ -306,8 +310,8 @@ private: void handlePageFlip(); bool lockPageFlip(const LayerVector& currentLayers); void unlockPageFlip(const LayerVector& currentLayers); + void handleWorkList(); void handleRepaint(); - bool handleBypassLayer(); void postFramebuffer(); void composeSurfaces(const Region& dirty); @@ -322,12 +326,12 @@ private: uint32_t setTransactionFlags(uint32_t flags); void commitTransaction(); - void setBypassLayer(const sp<LayerBase>& layer); status_t captureScreenImplLocked(DisplayID dpy, sp<IMemoryHeap>* heap, uint32_t* width, uint32_t* height, PixelFormat* format, - uint32_t reqWidth = 0, uint32_t reqHeight = 0); + uint32_t reqWidth, uint32_t reqHeight, + uint32_t minLayerZ, uint32_t maxLayerZ); status_t turnElectronBeamOffImplLocked(int32_t mode); status_t turnElectronBeamOnImplLocked(int32_t mode); @@ -395,18 +399,19 @@ private: Region mInvalidRegion; Region mWormholeRegion; bool mVisibleRegionsDirty; + bool mHwWorkListDirty; bool mDeferReleaseConsole; bool mFreezeDisplay; int32_t mElectronBeamAnimationMode; int32_t mFreezeCount; nsecs_t mFreezeDisplayTime; Vector< sp<LayerBase> > mVisibleLayersSortedByZ; - wp<Layer> mBypassLayer; // don't use a lock for these, we don't care int mDebugRegion; int mDebugBackground; + int mDebugDisableHWC; volatile nsecs_t mDebugInSwapBuffers; nsecs_t mLastSwapBufferTime; volatile nsecs_t mDebugInTransaction; diff --git a/services/surfaceflinger/tests/overlays/Android.mk b/services/surfaceflinger/tests/overlays/Android.mk deleted file mode 100644 index 592b601..0000000 --- a/services/surfaceflinger/tests/overlays/Android.mk +++ /dev/null @@ -1,17 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES:= \ - overlays.cpp - -LOCAL_SHARED_LIBRARIES := \ - libcutils \ - libutils \ - libui \ - libsurfaceflinger_client - -LOCAL_MODULE:= test-overlays - -LOCAL_MODULE_TAGS := tests - -include $(BUILD_EXECUTABLE) diff --git a/services/surfaceflinger/tests/overlays/overlays.cpp b/services/surfaceflinger/tests/overlays/overlays.cpp deleted file mode 100644 index c248a61..0000000 --- a/services/surfaceflinger/tests/overlays/overlays.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include <binder/IPCThreadState.h> -#include <binder/ProcessState.h> -#include <binder/IServiceManager.h> -#include <utils/Log.h> - -#include <ui/Overlay.h> - -#include <surfaceflinger/Surface.h> -#include <surfaceflinger/ISurface.h> -#include <surfaceflinger/SurfaceComposerClient.h> - -using namespace android; - -namespace android { -class Test { -public: - static const sp<ISurface>& getISurface(const sp<Surface>& s) { - return s->getISurface(); - } -}; -}; - -int main(int argc, char** argv) -{ - // set up the thread-pool - sp<ProcessState> proc(ProcessState::self()); - ProcessState::self()->startThreadPool(); - - // create a client to surfaceflinger - sp<SurfaceComposerClient> client = new SurfaceComposerClient(); - - // create pushbuffer surface - sp<Surface> surface = client->createSurface(getpid(), 0, 320, 240, - PIXEL_FORMAT_UNKNOWN, ISurfaceComposer::ePushBuffers); - - // get to the isurface - sp<ISurface> isurface = Test::getISurface(surface); - printf("isurface = %p\n", isurface.get()); - - // now request an overlay - sp<OverlayRef> ref = isurface->createOverlay(320, 240, PIXEL_FORMAT_RGB_565); - sp<Overlay> overlay = new Overlay(ref); - - - /* - * here we can use the overlay API - */ - - overlay_buffer_t buffer; - overlay->dequeueBuffer(&buffer); - printf("buffer = %p\n", buffer); - - void* address = overlay->getBufferAddress(buffer); - printf("address = %p\n", address); - - overlay->queueBuffer(buffer); - - return 0; -} diff --git a/services/surfaceflinger/tests/resize/resize.cpp b/services/surfaceflinger/tests/resize/resize.cpp index 127cca3..99f4b4f 100644 --- a/services/surfaceflinger/tests/resize/resize.cpp +++ b/services/surfaceflinger/tests/resize/resize.cpp @@ -10,8 +10,6 @@ #include <surfaceflinger/ISurface.h> #include <surfaceflinger/SurfaceComposerClient.h> -#include <ui/Overlay.h> - using namespace android; namespace android { @@ -32,7 +30,6 @@ int main(int argc, char** argv) // create a client to surfaceflinger sp<SurfaceComposerClient> client = new SurfaceComposerClient(); - // create pushbuffer surface sp<Surface> surface = client->createSurface(getpid(), 0, 160, 240, PIXEL_FORMAT_RGB_565); diff --git a/services/surfaceflinger/tests/surface/surface.cpp b/services/surfaceflinger/tests/surface/surface.cpp index b4de4b4..194fbb6 100644 --- a/services/surfaceflinger/tests/surface/surface.cpp +++ b/services/surfaceflinger/tests/surface/surface.cpp @@ -10,8 +10,6 @@ #include <surfaceflinger/ISurface.h> #include <surfaceflinger/SurfaceComposerClient.h> -#include <ui/Overlay.h> - using namespace android; int main(int argc, char** argv) @@ -23,7 +21,6 @@ int main(int argc, char** argv) // create a client to surfaceflinger sp<SurfaceComposerClient> client = new SurfaceComposerClient(); - // create pushbuffer surface sp<SurfaceControl> surfaceControl = client->createSurface( getpid(), 0, 160, 240, PIXEL_FORMAT_RGB_565); client->openTransaction(); diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk index b07a10b..186b349 100644 --- a/services/tests/servicestests/Android.mk +++ b/services/tests/servicestests/Android.mk @@ -7,8 +7,10 @@ LOCAL_MODULE_TAGS := tests # Include all test java files. LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_STATIC_JAVA_LIBRARIES := easymocklib LOCAL_JAVA_LIBRARIES := android.test.runner services + LOCAL_PACKAGE_NAME := FrameworksServicesTests LOCAL_CERTIFICATE := platform diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 5ce109f..f115f42 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -23,6 +23,19 @@ <application> <uses-library android:name="android.test.runner" /> + + <service android:name="com.android.server.AccessibilityManagerServiceTest$MyFirstMockAccessibilityService"> + <intent-filter> + <action android:name="android.accessibilityservice.AccessibilityService"/> + </intent-filter> + </service> + + <service android:name="com.android.server.AccessibilityManagerServiceTest$MySecondMockAccessibilityService"> + <intent-filter> + <action android:name="android.accessibilityservice.AccessibilityService"/> + </intent-filter> + </service> + </application> <instrumentation diff --git a/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java new file mode 100644 index 0000000..2bc6825 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java @@ -0,0 +1,725 @@ +/* + * Copyright (C) 2010 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.server; + +import android.accessibilityservice.AccessibilityService; +import android.accessibilityservice.AccessibilityServiceInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ServiceInfo; +import android.os.IBinder; +import android.os.Message; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.provider.Settings; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.IAccessibilityManager; +import android.view.accessibility.IAccessibilityManagerClient; + +/** + * This test exercises the + * {@link com.android.server.AccessibilityManagerService} by mocking the + * {@link android.view.accessibility.AccessibilityManager} which talks to to the + * service. The service itself is interacting with the platform. Note: Testing + * the service in full isolation would require significant amount of work for + * mocking all system interactions. It would also require a lot of mocking code. + */ +public class AccessibilityManagerServiceTest extends AndroidTestCase { + + /** + * Timeout required for pending Binder calls or event processing to + * complete. + */ + private static final long TIMEOUT_BINDER_CALL = 100; + + /** + * Timeout in which we are waiting for the system to start the mock + * accessibility services. + */ + private static final long TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES = 300; + + /** + * Timeout used for testing that a service is notified only upon a + * notification timeout. + */ + private static final long TIMEOUT_TEST_NOTIFICATION_TIMEOUT = 300; + + /** + * The interface used to talk to the tested service. + */ + private IAccessibilityManager mManagerService; + + @Override + public void setContext(Context context) { + super.setContext(context); + if (MyFirstMockAccessibilityService.sComponentName == null) { + MyFirstMockAccessibilityService.sComponentName = new ComponentName( + context.getPackageName(), MyFirstMockAccessibilityService.class.getName()) + .flattenToShortString(); + } + if (MySecondMockAccessibilityService.sComponentName == null) { + MySecondMockAccessibilityService.sComponentName = new ComponentName( + context.getPackageName(), MySecondMockAccessibilityService.class.getName()) + .flattenToShortString(); + } + } + + /** + * Creates a new instance. + */ + public AccessibilityManagerServiceTest() { + IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); + mManagerService = IAccessibilityManager.Stub.asInterface(iBinder); + } + + @LargeTest + public void testAddClient_AccessibilityDisabledThenEnabled() throws Exception { + // make sure accessibility is disabled + ensureAccessibilityEnabled(mContext, false); + + // create a client mock instance + MyMockAccessibilityManagerClient mockClient = new MyMockAccessibilityManagerClient(); + + // invoke the method under test + boolean enabledAccessibilityDisabled = mManagerService.addClient(mockClient); + + // check expected result + assertFalse("The client must be disabled since accessibility is disabled.", + enabledAccessibilityDisabled); + + // enable accessibility + ensureAccessibilityEnabled(mContext, true); + + // invoke the method under test + boolean enabledAccessibilityEnabled = mManagerService.addClient(mockClient); + + // check expected result + assertTrue("The client must be enabled since accessibility is enabled.", + enabledAccessibilityEnabled); + } + + @LargeTest + public void testAddClient_AccessibilityEnabledThenDisabled() throws Exception { + // enable accessibility before registering the client + ensureAccessibilityEnabled(mContext, true); + + // create a client mock instance + MyMockAccessibilityManagerClient mockClient = new MyMockAccessibilityManagerClient(); + + // invoke the method under test + boolean enabledAccessibilityEnabled = mManagerService.addClient(mockClient); + + // check expected result + assertTrue("The client must be enabled since accessibility is enabled.", + enabledAccessibilityEnabled); + + // disable accessibility + ensureAccessibilityEnabled(mContext, false); + + // invoke the method under test + boolean enabledAccessibilityDisabled = mManagerService.addClient(mockClient); + + // check expected result + assertFalse("The client must be disabled since accessibility is disabled.", + enabledAccessibilityDisabled); + } + + @LargeTest + public void testGetAccessibilityServicesList() throws Exception { + boolean firstMockServiceInstalled = false; + boolean secondMockServiceInstalled = false; + + String packageName = getContext().getPackageName(); + String firstMockServiceClassName = MyFirstMockAccessibilityService.class.getName(); + String secondMockServiceClassName = MySecondMockAccessibilityService.class.getName(); + + // look for the two mock services + for (ServiceInfo serviceInfo : mManagerService.getAccessibilityServiceList()) { + if (packageName.equals(serviceInfo.packageName)) { + if (firstMockServiceClassName.equals(serviceInfo.name)) { + firstMockServiceInstalled = true; + } else if (secondMockServiceClassName.equals(serviceInfo.name)) { + secondMockServiceInstalled = true; + } + } + } + + // check expected result + assertTrue("First mock service must be installed", firstMockServiceInstalled); + assertTrue("Second mock service must be installed", secondMockServiceInstalled); + } + + @LargeTest + public void testSendAccessibilityEvent_OneService_MatchingPackageAndEventType() + throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility service + ensureOnlyMockServicesEnabled(mContext, true, false); + + // configure the mock service + MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance; + service.setServiceInfo(MockAccessibilityService.createDefaultInfo()); + + // wait for the binder call to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate an event to be sent + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(sentEvent); + + // set expectations + service.expectEvent(sentEvent); + service.replay(); + + // send the event + mManagerService.sendAccessibilityEvent(sentEvent); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(service); + } + + @LargeTest + public void testSendAccessibilityEvent_OneService_NotMatchingPackage() throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility service + ensureOnlyMockServicesEnabled(mContext, true, false); + + // configure the mock service + MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance; + service.setServiceInfo(MockAccessibilityService.createDefaultInfo()); + + // wait for the binder call to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate an event to be sent + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(sentEvent); + sentEvent.setPackageName("no.service.registered.for.this.package"); + + // set expectations + service.replay(); + + // send the event + mManagerService.sendAccessibilityEvent(sentEvent); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(service); + } + + @LargeTest + public void testSendAccessibilityEvent_OneService_NotMatchingEventType() throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility service + ensureOnlyMockServicesEnabled(mContext, true, false); + + // configure the mock service + MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance; + service.setServiceInfo(MockAccessibilityService.createDefaultInfo()); + + // wait for the binder call to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate an event to be sent + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(sentEvent); + sentEvent.setEventType(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); + + // set expectations + service.replay(); + + // send the event + mManagerService.sendAccessibilityEvent(sentEvent); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(service); + } + + @LargeTest + public void testSendAccessibilityEvent_OneService_NotifivationAfterTimeout() throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility service + ensureOnlyMockServicesEnabled(mContext, true, false); + + // configure the mock service + MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance; + AccessibilityServiceInfo info = MockAccessibilityService.createDefaultInfo(); + info.notificationTimeout = TIMEOUT_TEST_NOTIFICATION_TIMEOUT; + service.setServiceInfo(info); + + // wait for the binder call to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate the first event to be sent + AccessibilityEvent firstEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(firstEvent); + + // create and populate the second event to be sent + AccessibilityEvent secondEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(secondEvent); + + // set expectations + service.expectEvent(secondEvent); + service.replay(); + + // send the events + mManagerService.sendAccessibilityEvent(firstEvent); + mManagerService.sendAccessibilityEvent(secondEvent); + + // wait for #sendAccessibilityEvent to reach the backing service + Thread.sleep(TIMEOUT_BINDER_CALL); + + try { + service.verify(); + fail("No events must be dispatched before the expiration of the notification timeout."); + } catch (IllegalStateException ise) { + /* expected */ + } + + // wait for the configured notification timeout to expire + Thread.sleep(TIMEOUT_TEST_NOTIFICATION_TIMEOUT); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(service); + } + + @LargeTest + public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_DiffFeedback() + throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility services + ensureOnlyMockServicesEnabled(mContext, true, true); + + // configure the first mock service + MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance; + AccessibilityServiceInfo firstInfo = MockAccessibilityService.createDefaultInfo(); + firstInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_AUDIBLE; + firstService.setServiceInfo(firstInfo); + + // configure the second mock service + MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance; + AccessibilityServiceInfo secondInfo = MockAccessibilityService.createDefaultInfo(); + secondInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_HAPTIC; + secondService.setServiceInfo(secondInfo); + + // wait for the binder calls to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate an event to be sent + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(sentEvent); + + // set expectations for the first mock service + firstService.expectEvent(sentEvent); + firstService.replay(); + + // set expectations for the second mock service + secondService.expectEvent(sentEvent); + secondService.replay(); + + // send the event + mManagerService.sendAccessibilityEvent(sentEvent); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(firstService); + assertMockServiceVerifiedWithinTimeout(secondService); + } + + @LargeTest + public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType() + throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility services + ensureOnlyMockServicesEnabled(mContext, true, true); + + // configure the first mock service + MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance; + firstService.setServiceInfo(MockAccessibilityService.createDefaultInfo()); + + // configure the second mock service + MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance; + secondService.setServiceInfo(MockAccessibilityService.createDefaultInfo()); + + // wait for the binder calls to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate an event to be sent + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(sentEvent); + + // set expectations for the first mock service + firstService.expectEvent(sentEvent); + firstService.replay(); + + // set expectations for the second mock service + secondService.replay(); + + // send the event + mManagerService.sendAccessibilityEvent(sentEvent); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(firstService); + assertMockServiceVerifiedWithinTimeout(secondService); + } + + @LargeTest + public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_OneDefault() + throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility services + ensureOnlyMockServicesEnabled(mContext, true, true); + + // configure the first mock service + MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance; + AccessibilityServiceInfo firstInfo = MyFirstMockAccessibilityService.createDefaultInfo(); + firstInfo.flags = AccessibilityServiceInfo.DEFAULT; + firstService.setServiceInfo(firstInfo); + + // configure the second mock service + MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance; + secondService.setServiceInfo(MySecondMockAccessibilityService.createDefaultInfo()); + + // wait for the binder calls to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate an event to be sent + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(sentEvent); + + // set expectations for the first mock service + firstService.replay(); + + // set expectations for the second mock service + secondService.expectEvent(sentEvent); + secondService.replay(); + + // send the event + mManagerService.sendAccessibilityEvent(sentEvent); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(firstService); + assertMockServiceVerifiedWithinTimeout(secondService); + } + + @LargeTest + public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_TwoDefault() + throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility services + ensureOnlyMockServicesEnabled(mContext, true, true); + + // configure the first mock service + MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance; + AccessibilityServiceInfo firstInfo = MyFirstMockAccessibilityService.createDefaultInfo(); + firstInfo.flags = AccessibilityServiceInfo.DEFAULT; + firstService.setServiceInfo(firstInfo); + + // configure the second mock service + MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance; + AccessibilityServiceInfo secondInfo = MyFirstMockAccessibilityService.createDefaultInfo(); + secondInfo.flags = AccessibilityServiceInfo.DEFAULT; + secondService.setServiceInfo(firstInfo); + + // wait for the binder calls to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate an event to be sent + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(sentEvent); + + // set expectations for the first mock service + firstService.expectEvent(sentEvent); + firstService.replay(); + + // set expectations for the second mock service + secondService.replay(); + + // send the event + mManagerService.sendAccessibilityEvent(sentEvent); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(firstService); + assertMockServiceVerifiedWithinTimeout(secondService); + } + + @LargeTest + public void testInterrupt() throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility services + ensureOnlyMockServicesEnabled(mContext, true, true); + + // configure the first mock service + MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance; + firstService.setServiceInfo(MockAccessibilityService.createDefaultInfo()); + + // configure the second mock service + MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance; + secondService.setServiceInfo(MockAccessibilityService.createDefaultInfo()); + + // wait for the binder calls to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // set expectations for the first mock service + firstService.expectInterrupt(); + firstService.replay(); + + // set expectations for the second mock service + secondService.expectInterrupt(); + secondService.replay(); + + // call the method under test + mManagerService.interrupt(); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(firstService); + assertMockServiceVerifiedWithinTimeout(secondService); + } + + /** + * Fully populates the {@link AccessibilityEvent} to marshal. + * + * @param sentEvent The event to populate. + */ + private void fullyPopulateDefaultAccessibilityEvent(AccessibilityEvent sentEvent) { + sentEvent.setAddedCount(1); + sentEvent.setBeforeText("BeforeText"); + sentEvent.setChecked(true); + sentEvent.setClassName("foo.bar.baz.Class"); + sentEvent.setContentDescription("ContentDescription"); + sentEvent.setCurrentItemIndex(1); + sentEvent.setEnabled(true); + sentEvent.setEventType(AccessibilityEvent.TYPE_VIEW_CLICKED); + sentEvent.setEventTime(1000); + sentEvent.setFromIndex(1); + sentEvent.setFullScreen(true); + sentEvent.setItemCount(1); + sentEvent.setPackageName("foo.bar.baz"); + sentEvent.setParcelableData(Message.obtain(null, 1, null)); + sentEvent.setPassword(true); + sentEvent.setRemovedCount(1); + } + + /** + * This class is a mock {@link IAccessibilityManagerClient}. + */ + public class MyMockAccessibilityManagerClient extends IAccessibilityManagerClient.Stub { + boolean mIsEnabled; + + public void setEnabled(boolean enabled) { + mIsEnabled = enabled; + } + } + + /** + * Ensures accessibility is in a given state by writing the state to the + * settings and waiting until the accessibility manager service pick it up. + * + * @param context A context handle to access the settings. + * @param enabled The accessibility state to write to the settings. + * @throws Exception If any error occurs. + */ + private void ensureAccessibilityEnabled(Context context, boolean enabled) throws Exception { + boolean isEnabled = (Settings.Secure.getInt(context.getContentResolver(), + Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1 ? true : false); + + if (isEnabled == enabled) { + return; + } + + Settings.Secure.putInt(context.getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED, + enabled ? 1 : 0); + + // wait the accessibility manager service to pick the change up + Thread.sleep(TIMEOUT_BINDER_CALL); + } + + /** + * Ensures the only {@link MockAccessibilityService}s with given component + * names are enabled by writing to the system settings and waiting until the + * accessibility manager service picks that up or the + * {@link #TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES} is exceeded. + * + * @param context A context handle to access the settings. + * @param firstMockServiceEnabled If the first mock accessibility service is enabled. + * @param secondMockServiceEnabled If the second mock accessibility service is enabled. + * @throws IllegalStateException If some of the requested for enabling mock services + * is not properly started. + * @throws Exception Exception If any error occurs. + */ + private void ensureOnlyMockServicesEnabled(Context context, boolean firstMockServiceEnabled, + boolean secondMockServiceEnabled) throws Exception { + String enabledServices = Settings.Secure.getString(context.getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); + + StringBuilder servicesToEnable = new StringBuilder(); + if (firstMockServiceEnabled) { + servicesToEnable.append(MyFirstMockAccessibilityService.sComponentName).append(":"); + } + if (secondMockServiceEnabled) { + servicesToEnable.append(MySecondMockAccessibilityService.sComponentName).append(":"); + } + + if (servicesToEnable.equals(enabledServices)) { + return; + } + + Settings.Secure.putString(context.getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, servicesToEnable.toString()); + + // we have enabled the services of interest and need to wait until they + // are instantiated and started (if needed) and the system binds to them + boolean firstMockServiceOK = false; + boolean secondMockServiceOK = false; + long start = SystemClock.uptimeMillis(); + long pollingInterval = TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES / 6; + + while (SystemClock.uptimeMillis() - start < TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES) { + firstMockServiceOK = !firstMockServiceEnabled + || (MyFirstMockAccessibilityService.sInstance != null + && MyFirstMockAccessibilityService.sInstance.isSystemBoundAsClient()); + + secondMockServiceOK = !secondMockServiceEnabled + || (MySecondMockAccessibilityService.sInstance != null + && MySecondMockAccessibilityService.sInstance.isSystemBoundAsClient()); + + if (firstMockServiceOK && secondMockServiceOK) { + return; + } + + Thread.sleep(pollingInterval); + } + + StringBuilder message = new StringBuilder(); + message.append("Mock accessibility services not started or system not bound as a client: "); + if (!firstMockServiceOK) { + message.append(MyFirstMockAccessibilityService.sComponentName); + message.append(" "); + } + if (!secondMockServiceOK) { + message.append(MySecondMockAccessibilityService.sComponentName); + } + throw new IllegalStateException(message.toString()); + } + + /** + * Asserts the the mock accessibility service has been successfully verified + * (which is it has received the expected method calls with expected + * arguments) within the {@link #TIMEOUT_BINDER_CALL}. The verified state is + * checked by polling upon small intervals. + * + * @param service The service to verify. + * @throws Exception If the verification has failed with exception after the + * {@link #TIMEOUT_BINDER_CALL}. + */ + private void assertMockServiceVerifiedWithinTimeout(MockAccessibilityService service) + throws Exception { + Exception lastVerifyException = null; + long beginTime = SystemClock.uptimeMillis(); + long pollTmeout = TIMEOUT_BINDER_CALL / 5; + + // poll until the timeout has elapsed + while (SystemClock.uptimeMillis() - beginTime < TIMEOUT_BINDER_CALL) { + // sleep first since immediate call will always fail + try { + Thread.sleep(pollTmeout); + } catch (InterruptedException ie) { + /* ignore */ + } + // poll for verification and if this fails save the exception and + // keep polling + try { + service.verify(); + // reset so it does not accept more events + service.reset(); + return; + } catch (Exception e) { + lastVerifyException = e; + } + } + + // reset, we have already failed + service.reset(); + + // always not null + throw lastVerifyException; + } + + /** + * This class is the first mock {@link AccessibilityService}. + */ + public static class MyFirstMockAccessibilityService extends MockAccessibilityService { + + /** + * The service {@link ComponentName} flattened as a string. + */ + static String sComponentName; + + /** + * Handle to the service instance. + */ + static MyFirstMockAccessibilityService sInstance; + + /** + * Creates a new instance. + */ + public MyFirstMockAccessibilityService() { + sInstance = this; + } + } + + /** + * This class is the first mock {@link AccessibilityService}. + */ + public static class MySecondMockAccessibilityService extends MockAccessibilityService { + + /** + * The service {@link ComponentName} flattened as a string. + */ + static String sComponentName; + + /** + * Handle to the service instance. + */ + static MySecondMockAccessibilityService sInstance; + + /** + * Creates a new instance. + */ + public MySecondMockAccessibilityService() { + sInstance = this; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java b/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java new file mode 100644 index 0000000..38fed22 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2010 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.server; + +import static org.easymock.EasyMock.createStrictMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.reportMatcher; +import static org.easymock.EasyMock.reset; +import static org.easymock.EasyMock.verify; + +import org.easymock.IArgumentMatcher; + +import android.content.pm.ServiceInfo; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.MediumTest; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.IAccessibilityManager; +import android.view.accessibility.IAccessibilityManagerClient; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tests for the AccessibilityManager which mocking the backing service. + */ +public class AccessibilityManagerTest extends AndroidTestCase { + + /** + * Timeout required for pending Binder calls or event processing to + * complete. + */ + public static final long TIMEOUT_BINDER_CALL = 50; + + /** + * The reusable mock {@link IAccessibilityManager}. + */ + private final IAccessibilityManager mMockServiceInterface = + createStrictMock(IAccessibilityManager.class); + + @Override + public void setUp() throws Exception { + reset(mMockServiceInterface); + } + + @MediumTest + public void testGetAccessibilityServiceList() throws Exception { + // create a list of installed accessibility services the mock service returns + List<ServiceInfo> expectedServices = new ArrayList<ServiceInfo>(); + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.name = "TestServiceInfoName"; + expectedServices.add(serviceInfo); + + // configure the mock service behavior + IAccessibilityManager mockServiceInterface = mMockServiceInterface; + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true); + expect(mockServiceInterface.getAccessibilityServiceList()).andReturn(expectedServices); + replay(mockServiceInterface); + + // invoke the method under test + AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface); + List<ServiceInfo> receivedServices = manager.getAccessibilityServiceList(); + + // check expected result (list equals() compares it contents as well) + assertEquals("All expected services must be returned", receivedServices, expectedServices); + + // verify the mock service was properly called + verify(mockServiceInterface); + } + + @MediumTest + public void testInterrupt() throws Exception { + // configure the mock service behavior + IAccessibilityManager mockServiceInterface = mMockServiceInterface; + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true); + mockServiceInterface.interrupt(); + replay(mockServiceInterface); + + // invoke the method under test + AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface); + manager.interrupt(); + + // verify the mock service was properly called + verify(mockServiceInterface); + } + + @LargeTest + public void testIsEnabled() throws Exception { + // configure the mock service behavior + IAccessibilityManager mockServiceInterface = mMockServiceInterface; + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true); + replay(mockServiceInterface); + + // invoke the method under test + AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface); + boolean isEnabledServiceEnabled = manager.isEnabled(); + + // check expected result + assertTrue("Must be enabled since the mock service is enabled", isEnabledServiceEnabled); + + // disable accessibility + manager.getClient().setEnabled(false); + + // wait for the asynchronous IBinder call to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // invoke the method under test + boolean isEnabledServcieDisabled = manager.isEnabled(); + + // check expected result + assertFalse("Must be disabled since the mock service is disabled", + isEnabledServcieDisabled); + + // verify the mock service was properly called + verify(mockServiceInterface); + } + + @MediumTest + public void testSendAccessibilityEvent_AccessibilityEnabled() throws Exception { + // create an event to be dispatched + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + + // configure the mock service behavior + IAccessibilityManager mockServiceInterface = mMockServiceInterface; + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true); + expect(mockServiceInterface.sendAccessibilityEvent(eqAccessibilityEvent(sentEvent))) + .andReturn(true); + expect(mockServiceInterface.sendAccessibilityEvent(eqAccessibilityEvent(sentEvent))) + .andReturn(false); + replay(mockServiceInterface); + + // invoke the method under test (manager and service in different processes) + AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface); + manager.sendAccessibilityEvent(sentEvent); + + // check expected result + AccessibilityEvent nextEventDifferentProcesses = AccessibilityEvent.obtain(); + assertSame("The manager and the service are in different processes, so the event must be " + + "recycled", sentEvent, nextEventDifferentProcesses); + + // invoke the method under test (manager and service in the same process) + manager.sendAccessibilityEvent(sentEvent); + + // check expected result + AccessibilityEvent nextEventSameProcess = AccessibilityEvent.obtain(); + assertNotSame("The manager and the service are in the same process, so the event must not" + + "be recycled", sentEvent, nextEventSameProcess); + + // verify the mock service was properly called + verify(mockServiceInterface); + } + + @MediumTest + public void testSendAccessibilityEvent_AccessibilityDisabled() throws Exception { + // create an event to be dispatched + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + + // configure the mock service behavior + IAccessibilityManager mockServiceInterface = mMockServiceInterface; + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(false); + replay(mockServiceInterface); + + // invoke the method under test (accessibility disabled) + AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface); + try { + manager.sendAccessibilityEvent(sentEvent); + fail("No accessibility events are sent if accessibility is disabled"); + } catch (IllegalStateException ise) { + // check expected result + assertEquals("Accessibility off. Did you forget to check that?", ise.getMessage()); + } + + // verify the mock service was properly called + verify(mockServiceInterface); + } + + /** + * Determines if an {@link AccessibilityEvent} passed as a method argument + * matches expectations. + * + * @param matched The event to check. + * @return True if expectations are matched. + */ + private static AccessibilityEvent eqAccessibilityEvent(AccessibilityEvent matched) { + reportMatcher(new AccessibilityEventMather(matched)); + return null; + } + + /** + * Determines if an {@link IAccessibilityManagerClient} passed as a method argument + * matches expectations which in this case are that any instance is accepted. + * + * @return <code>null</code>. + */ + private static IAccessibilityManagerClient anyIAccessibilityManagerClient() { + reportMatcher(new AnyIAccessibilityManagerClientMather()); + return null; + } + + /** + * Matcher for {@link AccessibilityEvent}s. + */ + private static class AccessibilityEventMather implements IArgumentMatcher { + private AccessibilityEvent mExpectedEvent; + + public AccessibilityEventMather(AccessibilityEvent expectedEvent) { + mExpectedEvent = expectedEvent; + } + + public boolean matches(Object matched) { + if (!(matched instanceof AccessibilityEvent)) { + return false; + } + AccessibilityEvent receivedEvent = (AccessibilityEvent) matched; + return mExpectedEvent.getEventType() == receivedEvent.getEventType(); + } + + public void appendTo(StringBuffer buffer) { + buffer.append("sendAccessibilityEvent()"); + buffer.append(" with event type \""); + buffer.append(mExpectedEvent.getEventType()); + buffer.append("\""); + } + } + + /** + * Matcher for {@link IAccessibilityManagerClient}s. + */ + private static class AnyIAccessibilityManagerClientMather implements IArgumentMatcher { + public boolean matches(Object matched) { + if (!(matched instanceof IAccessibilityManagerClient)) { + return false; + } + return true; + } + + public void appendTo(StringBuffer buffer) { + buffer.append("addClient() with any IAccessibilityManagerClient"); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java new file mode 100644 index 0000000..17a1585 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2010 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.server; + +import android.content.Context; +import android.location.Country; +import android.location.CountryListener; +import android.location.ICountryListener; +import android.os.RemoteException; +import android.test.AndroidTestCase; + +public class CountryDetectorServiceTest extends AndroidTestCase { + private class CountryListenerTester extends ICountryListener.Stub { + private Country mCountry; + + @Override + public void onCountryDetected(Country country) throws RemoteException { + mCountry = country; + } + + public Country getCountry() { + return mCountry; + } + + public boolean isNotified() { + return mCountry != null; + } + } + + private class CountryDetectorServiceTester extends CountryDetectorService { + + private CountryListener mListener; + + public CountryDetectorServiceTester(Context context) { + super(context); + } + + @Override + public void notifyReceivers(Country country) { + super.notifyReceivers(country); + } + + @Override + protected void setCountryListener(final CountryListener listener) { + mListener = listener; + } + + public boolean isListenerSet() { + return mListener != null; + } + } + + public void testAddRemoveListener() throws RemoteException { + CountryDetectorServiceTester serviceTester = new CountryDetectorServiceTester(getContext()); + serviceTester.systemReady(); + waitForSystemReady(serviceTester); + CountryListenerTester listenerTester = new CountryListenerTester(); + serviceTester.addCountryListener(listenerTester); + assertTrue(serviceTester.isListenerSet()); + serviceTester.removeCountryListener(listenerTester); + assertFalse(serviceTester.isListenerSet()); + } + + public void testNotifyListeners() throws RemoteException { + CountryDetectorServiceTester serviceTester = new CountryDetectorServiceTester(getContext()); + CountryListenerTester listenerTesterA = new CountryListenerTester(); + CountryListenerTester listenerTesterB = new CountryListenerTester(); + Country country = new Country("US", Country.COUNTRY_SOURCE_NETWORK); + serviceTester.systemReady(); + waitForSystemReady(serviceTester); + serviceTester.addCountryListener(listenerTesterA); + serviceTester.addCountryListener(listenerTesterB); + serviceTester.notifyReceivers(country); + assertTrue(serviceTester.isListenerSet()); + assertTrue(listenerTesterA.isNotified()); + assertTrue(listenerTesterB.isNotified()); + serviceTester.removeCountryListener(listenerTesterA); + serviceTester.removeCountryListener(listenerTesterB); + assertFalse(serviceTester.isListenerSet()); + } + + private void waitForSystemReady(CountryDetectorService service) { + int count = 5; + while (count-- > 0) { + try { + Thread.sleep(500); + } catch (Exception e) { + } + if (service.isSystemReady()) { + return; + } + } + throw new RuntimeException("Wait System Ready timeout"); + } +} diff --git a/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java b/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java new file mode 100644 index 0000000..1bc9b86 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2010 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.server; + +import android.accessibilityservice.AccessibilityService; +import android.accessibilityservice.AccessibilityServiceInfo; +import android.content.Intent; +import android.os.Message; +import android.view.accessibility.AccessibilityEvent; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +import junit.framework.TestCase; + +/** + * This is the base class for mock {@link AccessibilityService}s. + */ +public abstract class MockAccessibilityService extends AccessibilityService { + + /** + * The event this service expects to receive. + */ + private final Queue<AccessibilityEvent> mExpectedEvents = new LinkedList<AccessibilityEvent>(); + + /** + * Interruption call this service expects to receive. + */ + private boolean mExpectedInterrupt; + + /** + * Flag if the mock is currently replaying. + */ + private boolean mReplaying; + + /** + * Flag if the system is bound as a client to this service. + */ + private boolean mIsSystemBoundAsClient; + + /** + * Creates an {@link AccessibilityServiceInfo} populated with default + * values. + * + * @return The default info. + */ + public static AccessibilityServiceInfo createDefaultInfo() { + AccessibilityServiceInfo defaultInfo = new AccessibilityServiceInfo(); + defaultInfo.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED; + defaultInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_AUDIBLE; + defaultInfo.flags = 0; + defaultInfo.notificationTimeout = 0; + defaultInfo.packageNames = new String[] { + "foo.bar.baz" + }; + + return defaultInfo; + } + + /** + * Starts replaying the mock. + */ + public void replay() { + mReplaying = true; + } + + /** + * Verifies if all expected service methods have been called. + */ + public void verify() { + if (!mReplaying) { + throw new IllegalStateException("Did you forget to call replay()"); + } + + if (mExpectedInterrupt) { + throw new IllegalStateException("Expected call to #interrupt() not received"); + } + if (!mExpectedEvents.isEmpty()) { + throw new IllegalStateException("Expected a call to onAccessibilityEvent() for " + + "events \"" + mExpectedEvents + "\" not received"); + } + } + + /** + * Resets this instance so it can be reused. + */ + public void reset() { + mExpectedEvents.clear(); + mExpectedInterrupt = false; + mReplaying = false; + } + + /** + * Sets an expected call to + * {@link #onAccessibilityEvent(AccessibilityEvent)} with given event as + * argument. + * + * @param expectedEvent The expected event argument. + */ + public void expectEvent(AccessibilityEvent expectedEvent) { + mExpectedEvents.add(expectedEvent); + } + + /** + * Sets an expected call of {@link #onInterrupt()}. + */ + public void expectInterrupt() { + mExpectedInterrupt = true; + } + + @Override + public void onAccessibilityEvent(AccessibilityEvent receivedEvent) { + if (!mReplaying) { + return; + } + + if (mExpectedEvents.isEmpty()) { + throw new IllegalStateException("Unexpected event: " + receivedEvent); + } + + AccessibilityEvent expectedEvent = mExpectedEvents.poll(); + assertEqualsAccessiblityEvent(expectedEvent, receivedEvent); + } + + @Override + public void onInterrupt() { + if (!mReplaying) { + return; + } + + if (!mExpectedInterrupt) { + throw new IllegalStateException("Unexpected call to onInterrupt()"); + } + + mExpectedInterrupt = false; + } + + @Override + protected void onServiceConnected() { + mIsSystemBoundAsClient = true; + } + + @Override + public boolean onUnbind(Intent intent) { + mIsSystemBoundAsClient = false; + return false; + } + + /** + * Returns if the system is bound as client to this service. + * + * @return True if the system is bound, false otherwise. + */ + public boolean isSystemBoundAsClient() { + return mIsSystemBoundAsClient; + } + + /** + * Compares all properties of the <code>expectedEvent</code> and the + * <code>receviedEvent</code> to verify that the received event is the one + * that is expected. + */ + private void assertEqualsAccessiblityEvent(AccessibilityEvent expectedEvent, + AccessibilityEvent receivedEvent) { + TestCase.assertEquals("addedCount has incorrect value", expectedEvent.getAddedCount(), + receivedEvent.getAddedCount()); + TestCase.assertEquals("beforeText has incorrect value", expectedEvent.getBeforeText(), + receivedEvent.getBeforeText()); + TestCase.assertEquals("checked has incorrect value", expectedEvent.isChecked(), + receivedEvent.isChecked()); + TestCase.assertEquals("className has incorrect value", expectedEvent.getClassName(), + receivedEvent.getClassName()); + TestCase.assertEquals("contentDescription has incorrect value", expectedEvent + .getContentDescription(), receivedEvent.getContentDescription()); + TestCase.assertEquals("currentItemIndex has incorrect value", expectedEvent + .getCurrentItemIndex(), receivedEvent.getCurrentItemIndex()); + TestCase.assertEquals("enabled has incorrect value", expectedEvent.isEnabled(), + receivedEvent.isEnabled()); + TestCase.assertEquals("eventType has incorrect value", expectedEvent.getEventType(), + receivedEvent.getEventType()); + TestCase.assertEquals("fromIndex has incorrect value", expectedEvent.getFromIndex(), + receivedEvent.getFromIndex()); + TestCase.assertEquals("fullScreen has incorrect value", expectedEvent.isFullScreen(), + receivedEvent.isFullScreen()); + TestCase.assertEquals("itemCount has incorrect value", expectedEvent.getItemCount(), + receivedEvent.getItemCount()); + assertEqualsNotificationAsParcelableData(expectedEvent, receivedEvent); + TestCase.assertEquals("password has incorrect value", expectedEvent.isPassword(), + receivedEvent.isPassword()); + TestCase.assertEquals("removedCount has incorrect value", expectedEvent.getRemovedCount(), + receivedEvent.getRemovedCount()); + assertEqualsText(expectedEvent, receivedEvent); + } + + /** + * Compares the {@link android.os.Parcelable} data of the + * <code>expectedEvent</code> and <code>receivedEvent</code> to verify that + * the received event is the one that is expected. + */ + private void assertEqualsNotificationAsParcelableData(AccessibilityEvent expectedEvent, + AccessibilityEvent receivedEvent) { + String message = "parcelableData has incorrect value"; + Message expectedMessage = (Message) expectedEvent.getParcelableData(); + Message receivedMessage = (Message) receivedEvent.getParcelableData(); + + if (expectedMessage == null) { + if (receivedMessage == null) { + return; + } + } + + TestCase.assertNotNull(message, receivedMessage); + + // we do a very simple sanity check since we do not test Message + TestCase.assertEquals(message, expectedMessage.what, receivedMessage.what); + } + + /** + * Compares the text of the <code>expectedEvent</code> and + * <code>receivedEvent</code> by comparing the string representation of the + * corresponding {@link CharSequence}s. + */ + private void assertEqualsText(AccessibilityEvent expectedEvent, + AccessibilityEvent receivedEvent) { + String message = "text has incorrect value"; + List<CharSequence> expectedText = expectedEvent.getText(); + List<CharSequence> receivedText = receivedEvent.getText(); + + TestCase.assertEquals(message, expectedText.size(), receivedText.size()); + + Iterator<CharSequence> expectedTextIterator = expectedText.iterator(); + Iterator<CharSequence> receivedTextIterator = receivedText.iterator(); + + for (int i = 0; i < expectedText.size(); i++) { + // compare the string representation + TestCase.assertEquals(message, expectedTextIterator.next().toString(), + receivedTextIterator.next().toString()); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/location/ComprehensiveCountryDetectorTest.java b/services/tests/servicestests/src/com/android/server/location/ComprehensiveCountryDetectorTest.java new file mode 100644 index 0000000..98966c0 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/location/ComprehensiveCountryDetectorTest.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2010 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.server.location; + +import android.location.Country; +import android.location.CountryListener; +import android.test.AndroidTestCase; + +public class ComprehensiveCountryDetectorTest extends AndroidTestCase { + private class TestCountryDetector extends ComprehensiveCountryDetector { + public final static String COUNTRY_ISO = "us"; + private boolean mLocationBasedDetectorStarted; + private boolean mLocationBasedDetectorStopped; + protected boolean mNotified; + private boolean listenerAdded = false; + + private Country mNotifiedCountry; + public TestCountryDetector() { + super(getContext()); + } + + public void notifyLocationBasedListener(Country country) { + mNotified = true; + mNotifiedCountry = country; + mLocationBasedCountryDetector.notifyListener(country); + } + + public boolean locationBasedDetectorStarted() { + return mLocationBasedCountryDetector != null && mLocationBasedDetectorStarted; + } + + public boolean locationBasedDetectorStopped() { + return mLocationBasedCountryDetector == null && mLocationBasedDetectorStopped; + } + + public boolean locationRefreshStarted() { + return mLocationRefreshTimer != null; + } + + public boolean locationRefreshCancelled() { + return mLocationRefreshTimer == null; + } + + @Override + protected CountryDetectorBase createLocationBasedCountryDetector() { + return new CountryDetectorBase(mContext) { + @Override + public Country detectCountry() { + mLocationBasedDetectorStarted = true; + return null; + } + + @Override + public void stop() { + mLocationBasedDetectorStopped = true; + } + }; + } + + @Override + protected Country getNetworkBasedCountry() { + return null; + } + + @Override + protected Country getLastKnownLocationBasedCountry() { + return mNotifiedCountry; + } + + @Override + protected Country getSimBasedCountry() { + return null; + } + + @Override + protected Country getLocaleCountry() { + return null; + } + + @Override + protected void runAfterDetectionAsync(final Country country, final Country detectedCountry, + final boolean notifyChange, final boolean startLocationBasedDetection) { + runAfterDetection(country, detectedCountry, notifyChange, startLocationBasedDetection); + }; + + @Override + protected boolean isAirplaneModeOff() { + return true; + } + + @Override + protected synchronized void addPhoneStateListener() { + listenerAdded = true; + } + + @Override + protected synchronized void removePhoneStateListener() { + listenerAdded = false; + } + + @Override + protected boolean isGeoCoderImplemented() { + return true; + } + + public boolean isPhoneStateListenerAdded() { + return listenerAdded; + } + } + + private class CountryListenerImpl implements CountryListener { + private boolean mNotified; + private Country mCountry; + + public void onCountryDetected(Country country) { + mNotified = true; + mCountry = country; + } + + public boolean notified() { + return mNotified; + } + + public Country getCountry() { + return mCountry; + } + } + + public void testDetectNetworkBasedCountry() { + final Country resultCountry = new Country( + TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_NETWORK); + TestCountryDetector countryDetector = new TestCountryDetector() { + @Override + protected Country getNetworkBasedCountry() { + return resultCountry; + } + }; + CountryListenerImpl listener = new CountryListenerImpl(); + countryDetector.setCountryListener(listener); + Country country = countryDetector.detectCountry(); + assertTrue(sameCountry(country, resultCountry)); + assertFalse(listener.notified()); + assertFalse(countryDetector.locationBasedDetectorStarted()); + assertFalse(countryDetector.locationRefreshStarted()); + countryDetector.stop(); + } + + public void testDetectLocationBasedCountry() { + final Country resultCountry = new Country( + TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_SIM); + final Country locationBasedCountry = new Country( + TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_LOCATION); + TestCountryDetector countryDetector = new TestCountryDetector() { + @Override + protected Country getSimBasedCountry() { + return resultCountry; + } + }; + CountryListenerImpl listener = new CountryListenerImpl(); + countryDetector.setCountryListener(listener); + Country country = countryDetector.detectCountry(); + assertTrue(sameCountry(country, resultCountry)); + assertTrue(countryDetector.locationBasedDetectorStarted()); + countryDetector.notifyLocationBasedListener(locationBasedCountry); + assertTrue(listener.notified()); + assertTrue(sameCountry(listener.getCountry(), locationBasedCountry)); + assertTrue(countryDetector.locationBasedDetectorStopped()); + assertTrue(countryDetector.locationRefreshStarted()); + countryDetector.stop(); + assertTrue(countryDetector.locationRefreshCancelled()); + } + + public void testLocaleBasedCountry() { + final Country resultCountry = new Country( + TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_LOCALE); + TestCountryDetector countryDetector = new TestCountryDetector() { + @Override + protected Country getLocaleCountry() { + return resultCountry; + } + }; + CountryListenerImpl listener = new CountryListenerImpl(); + countryDetector.setCountryListener(listener); + Country country = countryDetector.detectCountry(); + assertTrue(sameCountry(country, resultCountry)); + assertFalse(listener.notified()); + assertTrue(countryDetector.locationBasedDetectorStarted()); + assertTrue(countryDetector.locationRefreshStarted()); + countryDetector.stop(); + assertTrue(countryDetector.locationRefreshCancelled()); + } + + public void testStoppingDetector() { + // Test stopping detector when LocationBasedCountryDetector was started + final Country resultCountry = new Country( + TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_SIM); + TestCountryDetector countryDetector = new TestCountryDetector() { + @Override + protected Country getSimBasedCountry() { + return resultCountry; + } + }; + CountryListenerImpl listener = new CountryListenerImpl(); + countryDetector.setCountryListener(listener); + Country country = countryDetector.detectCountry(); + assertTrue(sameCountry(country, resultCountry)); + assertTrue(countryDetector.locationBasedDetectorStarted()); + countryDetector.stop(); + // The LocationBasedDetector should be stopped. + assertTrue(countryDetector.locationBasedDetectorStopped()); + // The location refresh should not running. + assertTrue(countryDetector.locationRefreshCancelled()); + } + + public void testLocationBasedCountryNotFound() { + final Country resultCountry = new Country( + TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_SIM); + TestCountryDetector countryDetector = new TestCountryDetector() { + @Override + protected Country getSimBasedCountry() { + return resultCountry; + } + }; + CountryListenerImpl listener = new CountryListenerImpl(); + countryDetector.setCountryListener(listener); + Country country = countryDetector.detectCountry(); + assertTrue(sameCountry(country, resultCountry)); + assertTrue(countryDetector.locationBasedDetectorStarted()); + countryDetector.notifyLocationBasedListener(null); + assertFalse(listener.notified()); + assertTrue(sameCountry(listener.getCountry(), null)); + assertTrue(countryDetector.locationBasedDetectorStopped()); + assertTrue(countryDetector.locationRefreshStarted()); + countryDetector.stop(); + assertTrue(countryDetector.locationRefreshCancelled()); + } + + public void testNoCountryFound() { + TestCountryDetector countryDetector = new TestCountryDetector(); + CountryListenerImpl listener = new CountryListenerImpl(); + countryDetector.setCountryListener(listener); + Country country = countryDetector.detectCountry(); + assertTrue(sameCountry(country, null)); + assertTrue(countryDetector.locationBasedDetectorStarted()); + countryDetector.notifyLocationBasedListener(null); + assertFalse(listener.notified()); + assertTrue(sameCountry(listener.getCountry(), null)); + assertTrue(countryDetector.locationBasedDetectorStopped()); + assertTrue(countryDetector.locationRefreshStarted()); + countryDetector.stop(); + assertTrue(countryDetector.locationRefreshCancelled()); + } + + public void testAddRemoveListener() { + TestCountryDetector countryDetector = new TestCountryDetector(); + CountryListenerImpl listener = new CountryListenerImpl(); + countryDetector.setCountryListener(listener); + assertTrue(countryDetector.isPhoneStateListenerAdded()); + assertTrue(countryDetector.locationBasedDetectorStarted()); + countryDetector.setCountryListener(null); + assertFalse(countryDetector.isPhoneStateListenerAdded()); + assertTrue(countryDetector.locationBasedDetectorStopped()); + } + + public void testGeocoderNotImplemented() { + TestCountryDetector countryDetector = new TestCountryDetector() { + @Override + protected boolean isGeoCoderImplemented() { + return false; + } + }; + CountryListenerImpl listener = new CountryListenerImpl(); + countryDetector.setCountryListener(listener); + assertTrue(countryDetector.isPhoneStateListenerAdded()); + assertFalse(countryDetector.locationBasedDetectorStarted()); + countryDetector.setCountryListener(null); + assertFalse(countryDetector.isPhoneStateListenerAdded()); + } + + private boolean sameCountry(Country country1, Country country2) { + return country1 == null && country2 == null || country1 != null && country2 != null && + country1.getCountryIso().equalsIgnoreCase(country2.getCountryIso()) && + country1.getSource() == country2.getSource(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java b/services/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java new file mode 100755 index 0000000..71e8e2a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2010 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.server.location; + +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; + +import android.location.Country; +import android.location.CountryListener; +import android.location.Location; +import android.location.LocationListener; +import android.test.AndroidTestCase; + +public class LocationBasedCountryDetectorTest extends AndroidTestCase { + private class TestCountryDetector extends LocationBasedCountryDetector { + public static final int TOTAL_PROVIDERS = 2; + protected Object countryFoundLocker = new Object(); + protected boolean notifyCountry = false; + private final Location mLocation; + private final String mCountry; + private final long mQueryLocationTimeout; + private List<LocationListener> mListeners; + + public TestCountryDetector(String country, String provider) { + this(country, provider, 1000 * 60 * 5); + } + + public TestCountryDetector(String country, String provider, long queryLocationTimeout) { + super(getContext()); + mCountry = country; + mLocation = new Location(provider); + mQueryLocationTimeout = queryLocationTimeout; + mListeners = new ArrayList<LocationListener>(); + } + + @Override + protected String getCountryFromLocation(Location location) { + synchronized (countryFoundLocker) { + if (!notifyCountry) { + try { + countryFoundLocker.wait(); + } catch (InterruptedException e) { + } + } + } + if (mLocation.getProvider().endsWith(location.getProvider())) { + return mCountry; + } else { + return null; + } + } + + @Override + protected Location getLastKnownLocation() { + return mLocation; + } + + @Override + protected void registerEnabledProviders(List<LocationListener> listeners) { + mListeners.addAll(listeners); + } + + @Override + protected void unregisterProviders(List<LocationListener> listeners) { + for (LocationListener listener : mLocationListeners) { + assertTrue(mListeners.remove(listener)); + } + } + + @Override + protected long getQueryLocationTimeout() { + return mQueryLocationTimeout; + } + + @Override + protected int getTotalEnabledProviders() { + return TOTAL_PROVIDERS; + } + + public void notifyLocationFound() { + // Listener could be removed in the notification. + LocationListener[] listeners = new LocationListener[mListeners.size()]; + mLocationListeners.toArray(listeners); + for (LocationListener listener :listeners) { + listener.onLocationChanged(mLocation); + } + } + + public int getListenersCount() { + return mListeners.size(); + } + + public void notifyCountryFound() { + synchronized (countryFoundLocker) { + notifyCountry = true; + countryFoundLocker.notify(); + } + } + + public Timer getTimer() { + return mTimer; + } + + public Thread getQueryThread() { + return mQueryThread; + } + } + + private class CountryListenerImpl implements CountryListener { + private boolean mNotified; + private String mCountryCode; + public void onCountryDetected(Country country) { + mNotified = true; + if (country != null) { + mCountryCode = country.getCountryIso(); + } + } + + public boolean notified() { + return mNotified; + } + + public String getCountry() { + return mCountryCode; + } + } + + public void testFindingCountry() { + final String country = "us"; + final String provider = "Good"; + CountryListenerImpl countryListener = new CountryListenerImpl(); + TestCountryDetector detector = new TestCountryDetector(country, provider); + detector.setCountryListener(countryListener); + detector.detectCountry(); + assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS); + detector.notifyLocationFound(); + // All listeners should be unregistered + assertEquals(detector.getListenersCount(), 0); + assertNull(detector.getTimer()); + Thread queryThread = waitForQueryThreadLaunched(detector); + detector.notifyCountryFound(); + // Wait for query thread ending + waitForThreadEnding(queryThread); + // QueryThread should be set to NULL + assertNull(detector.getQueryThread()); + assertTrue(countryListener.notified()); + assertEquals(countryListener.getCountry(), country); + } + + public void testFindingCountryCancelled() { + final String country = "us"; + final String provider = "Good"; + CountryListenerImpl countryListener = new CountryListenerImpl(); + TestCountryDetector detector = new TestCountryDetector(country, provider); + detector.setCountryListener(countryListener); + detector.detectCountry(); + assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS); + detector.notifyLocationFound(); + // All listeners should be unregistered + assertEquals(detector.getListenersCount(), 0); + // The time should be stopped + assertNull(detector.getTimer()); + Thread queryThread = waitForQueryThreadLaunched(detector); + detector.stop(); + // There is no way to stop the thread, let's test it could be stopped, after get country + detector.notifyCountryFound(); + // Wait for query thread ending + waitForThreadEnding(queryThread); + // QueryThread should be set to NULL + assertNull(detector.getQueryThread()); + assertTrue(countryListener.notified()); + assertEquals(countryListener.getCountry(), country); + } + + public void testFindingLocationCancelled() { + final String country = "us"; + final String provider = "Good"; + CountryListenerImpl countryListener = new CountryListenerImpl(); + TestCountryDetector detector = new TestCountryDetector(country, provider); + detector.setCountryListener(countryListener); + detector.detectCountry(); + assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS); + detector.stop(); + // All listeners should be unregistered + assertEquals(detector.getListenersCount(), 0); + // The time should be stopped + assertNull(detector.getTimer()); + // QueryThread should still be NULL + assertNull(detector.getQueryThread()); + assertFalse(countryListener.notified()); + } + + public void testFindingLocationFailed() { + final String country = "us"; + final String provider = "Good"; + long timeout = 1000; + TestCountryDetector detector = new TestCountryDetector(country, provider, timeout) { + @Override + protected Location getLastKnownLocation() { + return null; + } + }; + CountryListenerImpl countryListener = new CountryListenerImpl(); + detector.setCountryListener(countryListener); + detector.detectCountry(); + assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS); + waitForTimerReset(detector); + // All listeners should be unregistered + assertEquals(detector.getListenersCount(), 0); + // QueryThread should still be NULL + assertNull(detector.getQueryThread()); + assertTrue(countryListener.notified()); + assertNull(countryListener.getCountry()); + } + + public void testFindingCountryFailed() { + final String country = "us"; + final String provider = "Good"; + TestCountryDetector detector = new TestCountryDetector(country, provider) { + @Override + protected String getCountryFromLocation(Location location) { + synchronized (countryFoundLocker) { + if (! notifyCountry) { + try { + countryFoundLocker.wait(); + } catch (InterruptedException e) { + } + } + } + // We didn't find country. + return null; + } + }; + CountryListenerImpl countryListener = new CountryListenerImpl(); + detector.setCountryListener(countryListener); + detector.detectCountry(); + assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS); + detector.notifyLocationFound(); + // All listeners should be unregistered + assertEquals(detector.getListenersCount(), 0); + assertNull(detector.getTimer()); + Thread queryThread = waitForQueryThreadLaunched(detector); + detector.notifyCountryFound(); + // Wait for query thread ending + waitForThreadEnding(queryThread); + // QueryThread should be set to NULL + assertNull(detector.getQueryThread()); + // CountryListener should be notified + assertTrue(countryListener.notified()); + assertNull(countryListener.getCountry()); + } + + public void testFindingCountryWithLastKnownLocation() { + final String country = "us"; + final String provider = "Good"; + long timeout = 1000; + TestCountryDetector detector = new TestCountryDetector(country, provider, timeout); + CountryListenerImpl countryListener = new CountryListenerImpl(); + detector.setCountryListener(countryListener); + detector.detectCountry(); + assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS); + waitForTimerReset(detector); + // All listeners should be unregistered + assertEquals(detector.getListenersCount(), 0); + Thread queryThread = waitForQueryThreadLaunched(detector); + detector.notifyCountryFound(); + // Wait for query thread ending + waitForThreadEnding(queryThread); + // QueryThread should be set to NULL + assertNull(detector.getQueryThread()); + // CountryListener should be notified + assertTrue(countryListener.notified()); + assertEquals(countryListener.getCountry(), country); + } + + private void waitForTimerReset(TestCountryDetector detector) { + int count = 5; + long interval = 1000; + try { + while (count-- > 0 && detector.getTimer() != null) { + Thread.sleep(interval); + } + } catch (InterruptedException e) { + } + Timer timer = detector.getTimer(); + assertTrue(timer == null); + } + + private void waitForThreadEnding(Thread thread) { + try { + thread.join(5000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + private Thread waitForQueryThreadLaunched(TestCountryDetector detector) { + int count = 5; + long interval = 1000; + try { + while (count-- > 0 && detector.getQueryThread() == null) { + Thread.sleep(interval); + } + } catch (InterruptedException e) { + } + Thread thread = detector.getQueryThread(); + assertTrue(thread != null); + return thread; + } +} |