diff options
Diffstat (limited to 'services')
160 files changed, 38174 insertions, 10749 deletions
diff --git a/services/audioflinger/A2dpAudioInterface.cpp b/services/audioflinger/A2dpAudioInterface.cpp index 995e31ca0797..d926cb14fc57 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 48154f942878..dbe2c6a48caa 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/Android.mk b/services/audioflinger/Android.mk index 22ecc5444aab..69a4adca68df 100644 --- a/services/audioflinger/Android.mk +++ b/services/audioflinger/Android.mk @@ -120,12 +120,4 @@ ifeq ($(TARGET_SIMULATOR),true) endif endif -ifeq ($(BOARD_USE_LVMX),true) - LOCAL_CFLAGS += -DLVMX - LOCAL_C_INCLUDES += vendor/nxp - LOCAL_STATIC_LIBRARIES += liblifevibes - LOCAL_SHARED_LIBRARIES += liblvmxservice -# LOCAL_SHARED_LIBRARIES += liblvmxipc -endif - include $(BUILD_SHARED_LIBRARY) diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp index 5935bf9e7f14..2b08ab59f87b 100644 --- a/services/audioflinger/AudioFlinger.cpp +++ b/services/audioflinger/AudioFlinger.cpp @@ -24,6 +24,7 @@ #include <sys/time.h> #include <sys/resource.h> +#include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> #include <utils/Log.h> #include <binder/Parcel.h> @@ -35,6 +36,7 @@ #include <media/AudioTrack.h> #include <media/AudioRecord.h> +#include <media/IMediaPlayerService.h> #include <private/media/AudioTrackShared.h> #include <private/media/AudioEffectShared.h> @@ -47,10 +49,6 @@ #include "A2dpAudioInterface.h" #endif -#ifdef LVMX -#include "lifevibes.h" -#endif - #include <media/EffectsFactoryApi.h> #include <media/EffectVisualizerApi.h> @@ -125,31 +123,43 @@ static bool settingsAllowed() { #endif } +// To collect the amplifier usage +static void addBatteryData(uint32_t params) { + sp<IBinder> binder = + defaultServiceManager()->getService(String16("media.player")); + sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder); + if (service.get() == NULL) { + LOGW("Cannot connect to the MediaPlayerService for battery tracking"); + return; + } + + service->addBatteryData(params); +} + // ---------------------------------------------------------------------------- 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!"); } -#ifdef LVMX - LifeVibes::init(); - mLifeVibesClientPid = -1; -#endif } AudioFlinger::~AudioFlinger() @@ -343,7 +353,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 +450,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); @@ -479,9 +492,6 @@ status_t AudioFlinger::setMode(int mode) mMode = mode; for (uint32_t i = 0; i < mPlaybackThreads.size(); i++) mPlaybackThreads.valueAt(i)->setMode(mode); -#ifdef LVMX - LifeVibes::setMode(mode); -#endif } return ret; @@ -517,6 +527,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 +590,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 +628,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; @@ -638,39 +639,11 @@ status_t AudioFlinger::setParameters(int ioHandle, const String8& keyValuePairs) return PERMISSION_DENIED; } -#ifdef LVMX - AudioParameter param = AudioParameter(keyValuePairs); - LifeVibes::setParameters(ioHandle,keyValuePairs); - String8 key = String8(AudioParameter::keyRouting); - int device; - if (NO_ERROR != param.getInt(key, device)) { - device = -1; - } - - key = String8(LifevibesTag); - String8 value; - int musicEnabled = -1; - if (NO_ERROR == param.get(key, value)) { - if (value == LifevibesEnable) { - mLifeVibesClientPid = IPCThreadState::self()->getCallingPid(); - musicEnabled = 1; - } else if (value == LifevibesDisable) { - mLifeVibesClientPid = -1; - musicEnabled = 0; - } - } -#endif - // ioHandle == 0 means the parameters are global to the audio hardware interface if (ioHandle == 0) { AutoMutex lock(mHardwareLock); mHardwareStatus = AUDIO_SET_PARAMETER; result = mAudioHardware->setParameters(keyValuePairs); -#ifdef LVMX - if (musicEnabled != -1) { - LifeVibes::enableMusic((bool) musicEnabled); - } -#endif mHardwareStatus = AUDIO_HW_IDLE; return result; } @@ -687,11 +660,6 @@ status_t AudioFlinger::setParameters(int ioHandle, const String8& keyValuePairs) } if (thread != NULL) { result = thread->setParameters(keyValuePairs); -#ifdef LVMX - if ((NO_ERROR == result) && (device != -1)) { - LifeVibes::setDevice(LifeVibes::threadIdToAudioOutputType(thread->id()), device); - } -#endif return result; } return BAD_VALUE; @@ -805,13 +773,6 @@ void AudioFlinger::removeNotificationClient(pid_t pid) if (index >= 0) { sp <NotificationClient> client = mNotificationClients.valueFor(pid); LOGV("removeNotificationClient() %p, pid %d", client.get(), pid); -#ifdef LVMX - if (pid == mLifeVibesClientPid) { - LOGV("Disabling lifevibes"); - LifeVibes::enableMusic(false); - mLifeVibesClientPid = -1; - } -#endif mNotificationClients.removeItem(pid); } } @@ -1217,24 +1178,12 @@ uint32_t AudioFlinger::PlaybackThread::latency() const status_t AudioFlinger::PlaybackThread::setMasterVolume(float value) { -#ifdef LVMX - int audioOutputType = LifeVibes::getMixerType(mId, mType); - if (LifeVibes::audioOutputTypeIsLifeVibes(audioOutputType)) { - LifeVibes::setMasterVolume(audioOutputType, value); - } -#endif mMasterVolume = value; return NO_ERROR; } status_t AudioFlinger::PlaybackThread::setMasterMute(bool muted) { -#ifdef LVMX - int audioOutputType = LifeVibes::getMixerType(mId, mType); - if (LifeVibes::audioOutputTypeIsLifeVibes(audioOutputType)) { - LifeVibes::setMasterMute(audioOutputType, muted); - } -#endif mMasterMute = muted; return NO_ERROR; } @@ -1251,24 +1200,12 @@ bool AudioFlinger::PlaybackThread::masterMute() const status_t AudioFlinger::PlaybackThread::setStreamVolume(int stream, float value) { -#ifdef LVMX - int audioOutputType = LifeVibes::getMixerType(mId, mType); - if (LifeVibes::audioOutputTypeIsLifeVibes(audioOutputType)) { - LifeVibes::setStreamVolume(audioOutputType, stream, value); - } -#endif mStreamTypes[stream].volume = value; return NO_ERROR; } status_t AudioFlinger::PlaybackThread::setStreamMute(int stream, bool muted) { -#ifdef LVMX - int audioOutputType = LifeVibes::getMixerType(mId, mType); - if (LifeVibes::audioOutputTypeIsLifeVibes(audioOutputType)) { - LifeVibes::setStreamMute(audioOutputType, stream, muted); - } -#endif mStreamTypes[stream].mute = muted; return NO_ERROR; } @@ -1283,20 +1220,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) { @@ -1610,12 +1533,6 @@ bool AudioFlinger::MixerThread::threadLoop() } // enable changes in effect chain unlockEffectChains(effectChains); -#ifdef LVMX - int audioOutputType = LifeVibes::getMixerType(mId, mType); - if (LifeVibes::audioOutputTypeIsLifeVibes(audioOutputType)) { - LifeVibes::process(audioOutputType, mMixBuffer, mixBufferSize); - } -#endif mLastWriteTime = systemTime(); mInWrite = true; mBytesWritten += mixBufferSize; @@ -1678,24 +1595,6 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track if (masterMute) { masterVolume = 0; } -#ifdef LVMX - bool tracksConnectedChanged = false; - bool stateChanged = false; - - int audioOutputType = LifeVibes::getMixerType(mId, mType); - if (LifeVibes::audioOutputTypeIsLifeVibes(audioOutputType)) - { - int activeTypes = 0; - for (size_t i=0 ; i<count ; i++) { - sp<Track> t = activeTracks[i].promote(); - if (t == 0) continue; - Track* const track = t.get(); - int iTracktype=track->type(); - activeTypes |= 1<<track->type(); - } - LifeVibes::computeVolumes(audioOutputType, activeTypes, tracksConnectedChanged, stateChanged, masterVolume, masterMute); - } -#endif // Delegate master volume control to effect in output mix effect chain if needed sp<EffectChain> chain = getEffectChain_l(AudioSystem::SESSION_OUTPUT_MIX); if (chain != 0) { @@ -1745,6 +1644,7 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track track->mState = TrackBase::ACTIVE; param = AudioMixer::RAMP_VOLUME; } + mAudioMixer->setParameter(AudioMixer::RESAMPLE, AudioMixer::RESET, NULL); } else if (cblk->server != 0) { // If the track is stopped before the first frame was mixed, // do not apply ramp @@ -1763,17 +1663,6 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track // read original volumes with volume control float typeVolume = mStreamTypes[track->type()].volume; -#ifdef LVMX - bool streamMute=false; - // read the volume from the LivesVibes audio engine. - if (LifeVibes::audioOutputTypeIsLifeVibes(audioOutputType)) - { - LifeVibes::getStreamVolumes(audioOutputType, track->type(), &typeVolume, &streamMute); - if (streamMute) { - typeVolume = 0; - } - } -#endif float v = masterVolume * typeVolume; vl = (uint32_t)(v * cblk->volume[0]) << 12; vr = (uint32_t)(v * cblk->volume[1]) << 12; @@ -1806,14 +1695,6 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track if (va > MAX_GAIN_INT) va = MAX_GAIN_INT; aux = int16_t(va); -#ifdef LVMX - if ( tracksConnectedChanged || stateChanged ) - { - // only do the ramp when the volume is changed by the user / application - param = AudioMixer::VOLUME; - } -#endif - // XXX: these things DON'T need to be done each time mAudioMixer->setBufferProvider(track); mAudioMixer->enable(AudioMixer::MIXING); @@ -1967,6 +1848,27 @@ bool AudioFlinger::MixerThread::checkForNewParameters_l() } } if (param.getInt(String8(AudioParameter::keyRouting), value) == NO_ERROR) { + // when changing the audio output device, call addBatteryData to notify + // the change + if (mDevice != value) { + uint32_t params = 0; + // check whether speaker is on + if (value & AudioSystem::DEVICE_OUT_SPEAKER) { + params |= IMediaPlayerService::kBatteryDataSpeakerOn; + } + + int deviceWithoutSpeaker + = AudioSystem::DEVICE_OUT_ALL & ~AudioSystem::DEVICE_OUT_SPEAKER; + // check if any other device (except speaker) is on + if (value & deviceWithoutSpeaker ) { + params |= IMediaPlayerService::kBatteryDataOtherAudioDeviceOn; + } + + if (params != 0) { + addBatteryData(params); + } + } + // forward device change to effects that have requested to be // aware of attached audio device. mDevice = (uint32_t)value; @@ -2965,6 +2867,9 @@ void AudioFlinger::PlaybackThread::Track::destroy() AudioSystem::stopOutput(thread->id(), (AudioSystem::stream_type)mStreamType, mSessionId); + + // to track the speaker usage + addBatteryData(IMediaPlayerService::kBatteryDataAudioFlingerStop); } AudioSystem::releaseOutput(thread->id()); } @@ -3075,6 +2980,11 @@ status_t AudioFlinger::PlaybackThread::Track::start() (AudioSystem::stream_type)mStreamType, mSessionId); thread->mLock.lock(); + + // to track the speaker usage + if (status == NO_ERROR) { + addBatteryData(IMediaPlayerService::kBatteryDataAudioFlingerStart); + } } if (status == NO_ERROR) { PlaybackThread *playbackThread = (PlaybackThread *)thread.get(); @@ -3110,6 +3020,9 @@ void AudioFlinger::PlaybackThread::Track::stop() (AudioSystem::stream_type)mStreamType, mSessionId); thread->mLock.lock(); + + // to track the speaker usage + addBatteryData(IMediaPlayerService::kBatteryDataAudioFlingerStop); } } } @@ -3129,6 +3042,9 @@ void AudioFlinger::PlaybackThread::Track::pause() (AudioSystem::stream_type)mStreamType, mSessionId); thread->mLock.lock(); + + // to track the speaker usage + addBatteryData(IMediaPlayerService::kBatteryDataAudioFlingerStop); } } } @@ -3699,7 +3615,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; } @@ -3990,9 +3906,12 @@ status_t AudioFlinger::RecordThread::start(RecordThread::RecordTrack* recordTrac mActiveTrack.clear(); return status; } - mActiveTrack->mState = TrackBase::RESUMING; mRsmpInIndex = mFrameCount; mBytesRead = 0; + if (mResampler != NULL) { + mResampler->reset(); + } + mActiveTrack->mState = TrackBase::RESUMING; // signal thread to start LOGV("Signal record thread"); mWaitWorkCV.signal(); @@ -4300,7 +4219,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)) { @@ -4309,18 +4228,6 @@ int AudioFlinger::openOutput(uint32_t *pDevices, } else { thread = new MixerThread(this, output, id, *pDevices); LOGV("openOutput() created mixer output: ID %d thread %p", id, thread); - -#ifdef LVMX - unsigned bitsPerSample = - (format == AudioSystem::PCM_16_BIT) ? 16 : - ((format == AudioSystem::PCM_8_BIT) ? 8 : 0); - unsigned channelCount = (channels == AudioSystem::CHANNEL_OUT_STEREO) ? 2 : 1; - int audioOutputType = LifeVibes::threadIdToAudioOutputType(thread->id()); - - LifeVibes::init_aot(audioOutputType, samplingRate, bitsPerSample, channelCount); - LifeVibes::setDevice(audioOutputType, *pDevices); -#endif - } mPlaybackThreads.add(id, thread); @@ -4348,7 +4255,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 +4380,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 +4450,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 +4486,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 +4876,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 5917632f00b8..ec3d202cd5c7 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, @@ -1185,9 +1181,6 @@ private: DefaultKeyedVector< pid_t, sp<NotificationClient> > mNotificationClients; volatile int32_t mNextUniqueId; -#ifdef LVMX - int mLifeVibesClientPid; -#endif uint32_t mMode; }; diff --git a/services/audioflinger/AudioMixer.cpp b/services/audioflinger/AudioMixer.cpp index 433f1f7027a1..50dcda7a1aff 100644 --- a/services/audioflinger/AudioMixer.cpp +++ b/services/audioflinger/AudioMixer.cpp @@ -220,6 +220,12 @@ status_t AudioMixer::setParameter(int target, int name, void *value) return NO_ERROR; } } + if (name == RESET) { + track_t& track = mState.tracks[ mActiveTrack ]; + track.resetResampler(); + invalidateState(1<<mActiveTrack); + return NO_ERROR; + } break; case RAMP_VOLUME: case VOLUME: @@ -289,6 +295,13 @@ bool AudioMixer::track_t::doesResample() const return resampler != 0; } +void AudioMixer::track_t::resetResampler() +{ + if (resampler != 0) { + resampler->reset(); + } +} + inline void AudioMixer::track_t::adjustVolumeRamp(bool aux) { diff --git a/services/audioflinger/AudioMixer.h b/services/audioflinger/AudioMixer.h index aee3e17af3ce..88408a7a7789 100644 --- a/services/audioflinger/AudioMixer.h +++ b/services/audioflinger/AudioMixer.h @@ -67,6 +67,7 @@ public: AUX_BUFFER = 0x4003, // for TARGET RESAMPLE SAMPLE_RATE = 0x4100, + RESET = 0x4101, // for TARGET VOLUME (8 channels max) VOLUME0 = 0x4200, VOLUME1 = 0x4201, @@ -163,6 +164,7 @@ private: bool setResampler(uint32_t sampleRate, uint32_t devSampleRate); bool doesResample() const; + void resetResampler(); void adjustVolumeRamp(bool aux); }; diff --git a/services/audioflinger/AudioPolicyManagerBase.cpp b/services/audioflinger/AudioPolicyManagerBase.cpp index 4612af15a878..f653dc50b8b8 100644 --- a/services/audioflinger/AudioPolicyManagerBase.cpp +++ b/services/audioflinger/AudioPolicyManagerBase.cpp @@ -19,6 +19,7 @@ #include <utils/Log.h> #include <hardware_legacy/AudioPolicyManagerBase.h> #include <media/mediarecorder.h> +#include <math.h> namespace android { @@ -121,12 +122,12 @@ status_t AudioPolicyManagerBase::setDeviceConnectionState(AudioSystem::audio_dev // request routing change if necessary uint32_t newDevice = getNewDevice(mHardwareOutput, false); #ifdef WITH_A2DP + checkA2dpSuspend(); checkOutputForAllStrategies(); // A2DP outputs must be closed after checkOutputForAllStrategies() is executed if (state == AudioSystem::DEVICE_STATE_UNAVAILABLE && AudioSystem::isA2dpDevice(device)) { closeA2dpOutputs(); } - checkA2dpSuspend(); #endif updateDeviceForStrategy(); setOutputDevice(mHardwareOutput, newDevice); @@ -268,8 +269,8 @@ void AudioPolicyManagerBase::setPhoneState(int state) // check for device and output changes triggered by new phone state newDevice = getNewDevice(mHardwareOutput, false); #ifdef WITH_A2DP - checkOutputForAllStrategies(); checkA2dpSuspend(); + checkOutputForAllStrategies(); #endif updateDeviceForStrategy(); @@ -312,8 +313,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; @@ -339,11 +339,14 @@ void AudioPolicyManagerBase::setForceUse(AudioSystem::force_use usage, AudioSyst LOGW("setForceUse() invalid config %d for FOR_COMMUNICATION", config); return; } + forceVolumeReeval = true; mForceUse[usage] = config; 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 +362,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; @@ -373,13 +379,13 @@ void AudioPolicyManagerBase::setForceUse(AudioSystem::force_use usage, AudioSyst // check for device and output changes triggered by new phone state uint32_t newDevice = getNewDevice(mHardwareOutput, false); #ifdef WITH_A2DP - checkOutputForAllStrategies(); checkA2dpSuspend(); + checkOutputForAllStrategies(); #endif updateDeviceForStrategy(); setOutputDevice(mHardwareOutput, newDevice); if (forceVolumeReeval) { - applyStreamVolumes(mHardwareOutput, newDevice); + applyStreamVolumes(mHardwareOutput, newDevice, 0, true); } audio_io_handle_t activeInput = getActiveInput(); @@ -473,6 +479,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,12 +608,10 @@ 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)); + setOutputDevice(output, getNewDevice(output), false, outputDesc->mLatency*2); #ifdef WITH_A2DP if (mA2dpOutput != 0 && !a2dpUsedForSonification() && @@ -914,6 +919,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 +1022,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) { @@ -1014,6 +1032,8 @@ AudioPolicyManagerBase::AudioPolicyManagerBase(AudioPolicyClientInterface *clien mForceUse[i] = AudioSystem::FORCE_NONE; } + initializeVolumeCurves(); + // devices available by default are speaker, ear piece and microphone mAvailableOutputDevices = AudioSystem::DEVICE_OUT_EARPIECE | AudioSystem::DEVICE_OUT_SPEAKER; @@ -1047,25 +1067,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 +1108,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 +1374,7 @@ status_t AudioPolicyManagerBase::handleA2dpDisconnection(AudioSystem::audio_devi void AudioPolicyManagerBase::closeA2dpOutputs() { + LOGV("setDeviceConnectionState() closing A2DP and duplicated output!"); if (mDuplicatedOutput != 0) { @@ -1516,6 +1544,20 @@ uint32_t AudioPolicyManagerBase::getStrategyForStream(AudioSystem::stream_type s return (uint32_t)getStrategy(stream); } +uint32_t AudioPolicyManagerBase::getDevicesForStream(AudioSystem::stream_type stream) { + uint32_t devices; + // By checking the range of stream before calling getStrategy, we avoid + // getStrategy's behavior for invalid streams. getStrategy would do a LOGE + // and then return STRATEGY_MEDIA, but we want to return the empty set. + if (stream < (AudioSystem::stream_type) 0 || stream >= AudioSystem::NUM_STREAM_TYPES) { + devices = 0; + } else { + AudioPolicyManagerBase::routing_strategy strategy = getStrategy(stream); + devices = getDeviceForStrategy(strategy, true); + } + return devices; +} + AudioPolicyManagerBase::routing_strategy AudioPolicyManagerBase::getStrategy( AudioSystem::stream_type stream) { // stream to strategy mapping @@ -1583,13 +1625,19 @@ uint32_t AudioPolicyManagerBase::getDeviceForStrategy(routing_strategy strategy, if (device) break; #ifdef WITH_A2DP // when not in a phone call, phone strategy should route STREAM_VOICE_CALL to A2DP - if (!isInCall()) { + if (!isInCall() && !mA2dpSuspended) { device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP; if (device) break; device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES; if (device) break; } #endif + 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; device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_EARPIECE; if (device == 0) { LOGE("getDeviceForStrategy() earpiece device not found"); @@ -1597,18 +1645,20 @@ 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; - } #ifdef WITH_A2DP // when not in a phone call, phone strategy should route STREAM_VOICE_CALL to // A2DP speaker when forcing to speaker output - if (!isInCall()) { + if (!isInCall() && !mA2dpSuspended) { device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER; if (device) break; } #endif + 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; device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_SPEAKER; if (device == 0) { LOGE("getDeviceForStrategy() speaker device not found"); @@ -1633,18 +1683,13 @@ uint32_t AudioPolicyManagerBase::getDeviceForStrategy(routing_strategy strategy, // FALL THROUGH case STRATEGY_MEDIA: { - uint32_t device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_AUX_DIGITAL; - if (device2 == 0) { - device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADPHONE; - } + uint32_t device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADPHONE; if (device2 == 0) { device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADSET; } #ifdef WITH_A2DP - if (mA2dpOutput != 0) { - if (strategy == STRATEGY_SONIFICATION && !a2dpUsedForSonification()) { - break; - } + if ((mA2dpOutput != 0) && !mA2dpSuspended && + (strategy != STRATEGY_SONIFICATION || a2dpUsedForSonification())) { if (device2 == 0) { device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP; } @@ -1657,6 +1702,15 @@ uint32_t AudioPolicyManagerBase::getDeviceForStrategy(routing_strategy strategy, } #endif if (device2 == 0) { + 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; + } + if (device2 == 0) { device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_SPEAKER; } @@ -1783,6 +1837,70 @@ audio_io_handle_t AudioPolicyManagerBase::getActiveInput() return 0; } +float AudioPolicyManagerBase::volIndexToAmpl(uint32_t device, const StreamDescriptor& streamDesc, + int indexInUi) { + // the volume index in the UI is relative to the min and max volume indices for this stream type + int nbSteps = 1 + streamDesc.mVolIndex[StreamDescriptor::VOLMAX] - + streamDesc.mVolIndex[StreamDescriptor::VOLMIN]; + int volIdx = (nbSteps * (indexInUi - streamDesc.mIndexMin)) / + (streamDesc.mIndexMax - streamDesc.mIndexMin); + + // find what part of the curve this index volume belongs to, or if it's out of bounds + int segment = 0; + if (volIdx < streamDesc.mVolIndex[StreamDescriptor::VOLMIN]) { // out of bounds + return 0.0f; + } else if (volIdx < streamDesc.mVolIndex[StreamDescriptor::VOLKNEE1]) { + segment = 0; + } else if (volIdx < streamDesc.mVolIndex[StreamDescriptor::VOLKNEE2]) { + segment = 1; + } else if (volIdx <= streamDesc.mVolIndex[StreamDescriptor::VOLMAX]) { + segment = 2; + } else { // out of bounds + return 1.0f; + } + + // linear interpolation in the attenuation table in dB + float decibels = streamDesc.mVolDbAtt[segment] + + ((float)(volIdx - streamDesc.mVolIndex[segment])) * + ( (streamDesc.mVolDbAtt[segment+1] - streamDesc.mVolDbAtt[segment]) / + ((float)(streamDesc.mVolIndex[segment+1] - streamDesc.mVolIndex[segment])) ); + + float amplification = exp( decibels * 0.115129f); // exp( dB * ln(10) / 20 ) + + LOGV("VOLUME vol index=[%d %d %d], dB=[%.1f %.1f %.1f] ampl=%.5f", + streamDesc.mVolIndex[segment], volIdx, streamDesc.mVolIndex[segment+1], + streamDesc.mVolDbAtt[segment], decibels, streamDesc.mVolDbAtt[segment+1], + amplification); + + return amplification; +} + +void AudioPolicyManagerBase::initializeVolumeCurves() { + // initialize the volume curves to a (-49.5 - 0 dB) attenuation in 0.5dB steps + for (int i=0 ; i< AudioSystem::NUM_STREAM_TYPES ; i++) { + mStreams[i].mVolIndex[StreamDescriptor::VOLMIN] = 1; + mStreams[i].mVolDbAtt[StreamDescriptor::VOLMIN] = -49.5f; + mStreams[i].mVolIndex[StreamDescriptor::VOLKNEE1] = 33; + mStreams[i].mVolDbAtt[StreamDescriptor::VOLKNEE1] = -33.5f; + mStreams[i].mVolIndex[StreamDescriptor::VOLKNEE2] = 66; + mStreams[i].mVolDbAtt[StreamDescriptor::VOLKNEE2] = -17.0f; + // here we use 100 steps to avoid rounding errors + // when computing the volume in volIndexToAmpl() + mStreams[i].mVolIndex[StreamDescriptor::VOLMAX] = 100; + mStreams[i].mVolDbAtt[StreamDescriptor::VOLMAX] = 0.0f; + } + + // Modification for music: more attenuation for lower volumes, finer steps at high volumes + mStreams[AudioSystem::MUSIC].mVolIndex[StreamDescriptor::VOLMIN] = 1; + mStreams[AudioSystem::MUSIC].mVolDbAtt[StreamDescriptor::VOLMIN] = -58.0f; + mStreams[AudioSystem::MUSIC].mVolIndex[StreamDescriptor::VOLKNEE1] = 20; + mStreams[AudioSystem::MUSIC].mVolDbAtt[StreamDescriptor::VOLKNEE1] = -40.0f; + mStreams[AudioSystem::MUSIC].mVolIndex[StreamDescriptor::VOLKNEE2] = 60; + mStreams[AudioSystem::MUSIC].mVolDbAtt[StreamDescriptor::VOLKNEE2] = -17.0f; + mStreams[AudioSystem::MUSIC].mVolIndex[StreamDescriptor::VOLMAX] = 100; + mStreams[AudioSystem::MUSIC].mVolDbAtt[StreamDescriptor::VOLMAX] = 0.0f; +} + float AudioPolicyManagerBase::computeVolume(int stream, int index, audio_io_handle_t output, uint32_t device) { float volume = 1.0; @@ -1793,8 +1911,7 @@ float AudioPolicyManagerBase::computeVolume(int stream, int index, audio_io_hand device = outputDesc->device(); } - int volInt = (100 * (index - streamDesc.mIndexMin)) / (streamDesc.mIndexMax - streamDesc.mIndexMin); - volume = AudioSystem::linearToLog(volInt); + volume = volIndexToAmpl(device, streamDesc, index); // if a headset is connected, apply the following rules to ring tones and notifications // to avoid sound level bursts in user's ears: @@ -1857,7 +1974,13 @@ status_t AudioPolicyManagerBase::checkAndSetVolume(int stream, int index, audio_ // offset value to reflect actual hardware volume that never reaches 0 // 1% corresponds roughly to first step in VOICE_CALL stream volume setting (see AudioService.java) volume = 0.01 + 0.99 * volume; + // Force VOICE_CALL to track BLUETOOTH_SCO stream volume when bluetooth audio is + // enabled + if (stream == AudioSystem::BLUETOOTH_SCO) { + mpClientInterface->setStreamVolume(AudioSystem::VOICE_CALL, volume, output, delayMs); + } } + mpClientInterface->setStreamVolume((AudioSystem::stream_type)stream, volume, output, delayMs); } @@ -1870,6 +1993,7 @@ status_t AudioPolicyManagerBase::checkAndSetVolume(int stream, int index, audio_ } else { voiceVolume = 1.0; } + if (voiceVolume != mLastVoiceVolume && output == mHardwareOutput) { mpClientInterface->setVoiceVolume(voiceVolume, delayMs); mLastVoiceVolume = voiceVolume; @@ -1879,12 +2003,12 @@ status_t AudioPolicyManagerBase::checkAndSetVolume(int stream, int index, audio_ return NO_ERROR; } -void AudioPolicyManagerBase::applyStreamVolumes(audio_io_handle_t output, uint32_t device, int delayMs) +void AudioPolicyManagerBase::applyStreamVolumes(audio_io_handle_t output, uint32_t device, int delayMs, bool force) { LOGV("applyStreamVolumes() for output %d and device %x", output, device); for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) { - checkAndSetVolume(stream, mStreams[stream].mIndexCur, output, device, delayMs); + checkAndSetVolume(stream, mStreams[stream].mIndexCur, output, device, delayMs, force); } } @@ -2007,6 +2131,7 @@ AudioPolicyManagerBase::AudioOutputDescriptor::AudioOutputDescriptor() mRefCount[i] = 0; mCurVolume[i] = -1.0; mMuteCount[i] = 0; + mStopTime[i] = 0; } } @@ -2057,7 +2182,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 f24e08e4dc88..b614c483632f 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() @@ -354,6 +365,14 @@ uint32_t AudioPolicyService::getStrategyForStream(AudioSystem::stream_type strea return mpPolicyManager->getStrategyForStream(stream); } +uint32_t AudioPolicyService::getDevicesForStream(AudioSystem::stream_type stream) +{ + if (mpPolicyManager == NULL) { + return 0; + } + return mpPolicyManager->getDevicesForStream(stream); +} + audio_io_handle_t AudioPolicyService::getOutputForEffect(effect_descriptor_t *desc) { if (mpPolicyManager == NULL) { @@ -383,6 +402,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()); @@ -468,13 +496,6 @@ status_t AudioPolicyService::onTransact( // ---------------------------------------------------------------------------- -void AudioPolicyService::instantiate() { - defaultServiceManager()->addService( - String16("media.audio_policy"), new AudioPolicyService()); -} - - -// ---------------------------------------------------------------------------- // AudioPolicyClientInterface implementation // ---------------------------------------------------------------------------- diff --git a/services/audioflinger/AudioPolicyService.h b/services/audioflinger/AudioPolicyService.h index 558f455e50f1..faad893ea968 100644 --- a/services/audioflinger/AudioPolicyService.h +++ b/services/audioflinger/AudioPolicyService.h @@ -21,6 +21,7 @@ #include <hardware_legacy/AudioPolicyInterface.h> #include <media/ToneGenerator.h> #include <utils/Vector.h> +#include <binder/BinderService.h> namespace android { @@ -28,12 +29,17 @@ class String8; // ---------------------------------------------------------------------------- -class AudioPolicyService: public BnAudioPolicyService, public AudioPolicyClientInterface, +class AudioPolicyService : + public BinderService<AudioPolicyService>, + public BnAudioPolicyService, + public AudioPolicyClientInterface, public IBinder::DeathRecipient { + friend class BinderService<AudioPolicyService>; public: - static void instantiate(); + // for BinderService + static const char *getServiceName() { return "media.audio_policy"; } virtual status_t dump(int fd, const Vector<String16>& args); @@ -80,6 +86,7 @@ public: virtual status_t getStreamVolumeIndex(AudioSystem::stream_type stream, int *index); virtual uint32_t getStrategyForStream(AudioSystem::stream_type stream); + virtual uint32_t getDevicesForStream(AudioSystem::stream_type stream); virtual audio_io_handle_t getOutputForEffect(effect_descriptor_t *desc); virtual status_t registerEffect(effect_descriptor_t *desc, @@ -88,6 +95,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 +238,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 @@ -240,11 +248,3 @@ private: }; // namespace android #endif // ANDROID_AUDIOPOLICYSERVICE_H - - - - - - - - diff --git a/services/audioflinger/AudioResampler.cpp b/services/audioflinger/AudioResampler.cpp index 5dabacbb70a0..5c3b43fcbce4 100644 --- a/services/audioflinger/AudioResampler.cpp +++ b/services/audioflinger/AudioResampler.cpp @@ -148,6 +148,12 @@ void AudioResampler::setVolume(int16_t left, int16_t right) { mVolume[1] = right; } +void AudioResampler::reset() { + mInputIndex = 0; + mPhaseFraction = 0; + mBuffer.frameCount = 0; +} + // ---------------------------------------------------------------------------- void AudioResamplerOrder1::resample(int32_t* out, size_t outFrameCount, diff --git a/services/audioflinger/AudioResampler.h b/services/audioflinger/AudioResampler.h index 2dfac7651591..9f06c1cf1d2f 100644 --- a/services/audioflinger/AudioResampler.h +++ b/services/audioflinger/AudioResampler.h @@ -53,6 +53,8 @@ public: virtual void resample(int32_t* out, size_t outFrameCount, AudioBufferProvider* provider) = 0; + virtual void reset(); + protected: // number of bits for phase fraction - 30 bits allows nearly 2x downsampling static const int kNumPhaseBits = 30; diff --git a/services/camera/libcameraservice/Android.mk b/services/camera/libcameraservice/Android.mk index 87975af9eabf..b52fc694e781 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 b3e0ee6ebdef..07b5a37e4af8 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 d3427ba4b7e1..9b66a76ba831 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 a64ddcfc118a..a09e16b3b412 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()); @@ -778,17 +727,30 @@ status_t CameraService::Client::cancelAutoFocus() { } // take a picture - image is returned in callback -status_t CameraService::Client::takePicture() { - LOG1("takePicture (pid %d)", getCallingPid()); +status_t CameraService::Client::takePicture(int msgType) { + LOG1("takePicture (pid %d): 0x%x", getCallingPid(), msgType); Mutex::Autolock lock(mLock); status_t result = checkPidAndHardware(); if (result != NO_ERROR) return result; - enableMsgType(CAMERA_MSG_SHUTTER | - CAMERA_MSG_POSTVIEW_FRAME | - CAMERA_MSG_RAW_IMAGE | - CAMERA_MSG_COMPRESSED_IMAGE); + if ((msgType & CAMERA_MSG_RAW_IMAGE) && + (msgType & CAMERA_MSG_RAW_IMAGE_NOTIFY)) { + LOGE("CAMERA_MSG_RAW_IMAGE and CAMERA_MSG_RAW_IMAGE_NOTIFY" + " cannot be both enabled"); + return BAD_VALUE; + } + + // We only accept picture related message types + // and ignore other types of messages for takePicture(). + int picMsgType = msgType + & (CAMERA_MSG_SHUTTER | + CAMERA_MSG_POSTVIEW_FRAME | + CAMERA_MSG_RAW_IMAGE | + CAMERA_MSG_RAW_IMAGE_NOTIFY | + CAMERA_MSG_COMPRESSED_IMAGE); + + enableMsgType(picMsgType); return mHardware->takePicture(); } @@ -815,6 +777,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 +824,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 +1000,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 +1012,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 +1021,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 +1041,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 +1078,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 +1257,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 f09773d11a6c..1c43b00f7ff1 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,18 +93,22 @@ 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(); virtual void releaseRecordingFrame(const sp<IMemory>& mem); virtual status_t autoFocus(); virtual status_t cancelAutoFocus(); - virtual status_t takePicture(); + virtual status_t takePicture(int msgType); virtual status_t setParameters(const String8& params); virtual String8 getParameters() const; virtual status_t sendCommand(int32_t cmd, int32_t arg1, int32_t arg2); @@ -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/camera/libcameraservice/CannedJpeg.h b/services/camera/libcameraservice/CannedJpeg.h index b6266fbd71d4..6dd99c1e4db7 100644 --- a/services/camera/libcameraservice/CannedJpeg.h +++ b/services/camera/libcameraservice/CannedJpeg.h @@ -1,3 +1,19 @@ +/* + * 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. + */ + const int kCannedJpegWidth = 320; const int kCannedJpegHeight = 240; const int kCannedJpegSize = 8733; diff --git a/services/camera/tests/CameraServiceTest/CameraServiceTest.cpp b/services/camera/tests/CameraServiceTest/CameraServiceTest.cpp index 3c8d55397a95..8a228fd7b6c4 100644 --- a/services/camera/tests/CameraServiceTest/CameraServiceTest.cpp +++ b/services/camera/tests/CameraServiceTest/CameraServiceTest.cpp @@ -1,3 +1,19 @@ +/* + * 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 "CameraServiceTest" #include <stdio.h> diff --git a/services/input/Android.mk b/services/input/Android.mk new file mode 100644 index 000000000000..d7b61fc9592e --- /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 000000000000..853dda42e030 --- /dev/null +++ b/services/input/EventHub.cpp @@ -0,0 +1,1215 @@ +/* + * 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), relBitmask(NULL), + configuration(NULL), virtualKeyMap(NULL) { +} + +EventHub::Device::~Device() { + close(); + delete[] keyBitmask; + delete[] relBitmask; + 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); + memset(mSwitches, 0, sizeof(mSwitches)); +} + +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; +} + +bool EventHub::hasRelativeAxis(int32_t deviceId, int axis) const { + if (axis >= 0 && axis <= REL_MAX) { + AutoMutex _l(mLock); + + Device* device = getDeviceLocked(deviceId); + if (device && device->relBitmask) { + return test_bit(axis, device->relBitmask); + } + } + return false; +} + +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->findScanCodesForKey(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 { + if (sw >= 0 && sw <= SW_MAX) { + AutoMutex _l(mLock); + + Device* device = getDeviceLocked(deviceId); + if (device != NULL) { + return getSwitchStateLocked(device, sw); + } + } + 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->findScanCodesForKey( + 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::mapKey(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->mapKey(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->mapKey(scancode, outKeycode, outFlags); + if (err == NO_ERROR) { + return NO_ERROR; + } + } + } + + *outKeycode = 0; + *outFlags = 0; + return NAME_NOT_FOUND; +} + +status_t EventHub::mapAxis(int32_t deviceId, int scancode, AxisInfo* outAxisInfo) const +{ + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + + if (device && device->keyMap.haveKeyLayout()) { + status_t err = device->keyMap.keyLayoutMap->mapAxis(scancode, outAxisInfo); + if (err == NO_ERROR) { + return NO_ERROR; + } + } + + if (mBuiltInKeyboardId != -1) { + device = getDeviceLocked(mBuiltInKeyboardId); + + if (device && device->keyMap.haveKeyLayout()) { + status_t err = device->keyMap.keyLayoutMap->mapAxis(scancode, outAxisInfo); + if (err == NO_ERROR) { + return NO_ERROR; + } + } + } + + 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. + bool deviceWasRemoved = false; + 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->mapKey(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 == ENODEV) { + deviceWasRemoved = true; + break; + } + 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; + } + } + } + + // Handle the case where a device has been removed but INotify has not yet noticed. + if (deviceWasRemoved) { + AutoMutex _l(mLock); + closeDeviceAtIndexLocked(mInputFdIndex); + continue; // report added or removed devices immediately + } + +#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 + + // 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); + } + } + + // Prepare to process all of the FDs we just polled. + mInputFdIndex = 0; + } +} + +/* + * 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, + AKEYCODE_BUTTON_1, AKEYCODE_BUTTON_2, AKEYCODE_BUTTON_3, AKEYCODE_BUTTON_4, + AKEYCODE_BUTTON_5, AKEYCODE_BUTTON_6, AKEYCODE_BUTTON_7, AKEYCODE_BUTTON_8, + AKEYCODE_BUTTON_9, AKEYCODE_BUTTON_10, AKEYCODE_BUTTON_11, AKEYCODE_BUTTON_12, + AKEYCODE_BUTTON_13, AKEYCODE_BUTTON_14, AKEYCODE_BUTTON_15, AKEYCODE_BUTTON_16, +}; + +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)); + ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bitmask)), key_bitmask); + + uint8_t abs_bitmask[sizeof_bit_array(ABS_MAX + 1)]; + memset(abs_bitmask, 0, sizeof(abs_bitmask)); + ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(abs_bitmask)), abs_bitmask); + + uint8_t rel_bitmask[sizeof_bit_array(REL_MAX + 1)]; + memset(rel_bitmask, 0, sizeof(rel_bitmask)); + ioctl(fd, EVIOCGBIT(EV_REL, sizeof(rel_bitmask)), rel_bitmask); + + uint8_t sw_bitmask[sizeof_bit_array(SW_MAX + 1)]; + memset(sw_bitmask, 0, sizeof(sw_bitmask)); + ioctl(fd, EVIOCGBIT(EV_SW, sizeof(sw_bitmask)), sw_bitmask); + + 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; + } + + device->relBitmask = new uint8_t[sizeof(rel_bitmask)]; + if (device->relBitmask != NULL) { + memcpy(device->relBitmask, rel_bitmask, sizeof(rel_bitmask)); + } else { + delete device; + LOGE("out of memory allocating rel bitmask"); + return -1; + } + + // See if this is a keyboard. Ignore everything in the button range except for + // joystick and gamepad buttons which are handled like keyboards for the most part. + bool haveKeyboardKeys = containsNonZeroByte(key_bitmask, 0, sizeof_bit_array(BTN_MISC)) + || containsNonZeroByte(key_bitmask, sizeof_bit_array(KEY_OK), + sizeof_bit_array(KEY_MAX + 1)); + bool haveGamepadButtons = containsNonZeroByte(key_bitmask, sizeof_bit_array(BTN_MISC), + sizeof_bit_array(BTN_MOUSE)) + || containsNonZeroByte(key_bitmask, sizeof_bit_array(BTN_JOYSTICK), + sizeof_bit_array(BTN_DIGI)); + if (haveKeyboardKeys || haveGamepadButtons) { + device->classes |= INPUT_DEVICE_CLASS_KEYBOARD; + } + + // See if this is a cursor device such as a trackball or mouse. + if (test_bit(BTN_MOUSE, key_bitmask) + && 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. + // 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)) { + // Some joysticks such as the PS3 controller report axes that conflict + // with the ABS_MT range. Try to confirm that the device really is + // a touch screen. + if (test_bit(BTN_TOUCH, key_bitmask) || !haveGamepadButtons) { + 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; + } + + // See if this device is a joystick. + // Ignore touchscreens because they use the same absolute axes for other purposes. + // Assumes that joysticks always have gamepad buttons in order to distinguish them + // from other devices such as accelerometers that also have absolute axes. + if (haveGamepadButtons + && !(device->classes & INPUT_DEVICE_CLASS_TOUCH) + && containsNonZeroByte(abs_bitmask, 0, sizeof_bit_array(ABS_MAX + 1))) { + device->classes |= INPUT_DEVICE_CLASS_JOYSTICK; + } + + // figure out the switches this device reports + bool haveSwitches = false; + 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)) { + haveSwitches = true; + if (mSwitches[i] == 0) { + mSwitches[i] = device->id; + } + } + } + if (haveSwitches) { + device->classes |= INPUT_DEVICE_CLASS_SWITCH; + } + + 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; + } + } + + // Load the key map. + // We need to do this for joysticks too because the key layout may specify axes. + status_t keyMapStatus = NAME_NOT_FOUND; + if (device->classes & (INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_JOYSTICK)) { + // Load the keymap for the device. + keyMapStatus = loadKeyMap(device); + } + + // Configure the keyboard, gamepad or virtual keyboard. + if (device->classes & INPUT_DEVICE_CLASS_KEYBOARD) { + // Set system properties for the keyboard. + setKeyboardProperties(device, false); + + // Register the keyboard as a built-in keyboard if it is eligible. + if (!keyMapStatus + && 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; + } + + // Determine whether the device is external or internal. + if (isExternalDevice(device)) { + device->classes |= INPUT_DEVICE_CLASS_EXTERNAL; + } + + 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::isExternalDevice(Device* device) { + if (device->configuration) { + bool value; + if (device->configuration->tryGetProperty(String8("device.internal"), value) + && value) { + return false; + } + } + return device->identifier.bus == BUS_USB || device->identifier.bus == BUS_BLUETOOTH; +} + +bool EventHub::hasKeycodeLocked(Device* device, int keycode) const { + if (!device->keyMap.haveKeyLayout() || !device->keyBitmask) { + return false; + } + + Vector<int32_t> scanCodes; + device->keyMap.keyLayoutMap->findScanCodesForKey(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) { + return closeDeviceAtIndexLocked(i); + } + } + LOGV("Remove device: %s not found, device may already have been removed.", devicePath); + return -1; +} + +int EventHub::closeDeviceAtIndexLocked(int index) { + Device* device = mDevices[index]; + 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); + + for (int j=0; j<EV_SW; j++) { + if (mSwitches[j] == device->id) { + mSwitches[j] = 0; + } + } + + 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(index); + mDevices.removeAt(index); + device->close(); + + // Unlink for opening devices list if it is present. + Device* pred = NULL; + bool found = false; + for (Device* entry = mOpeningDevices; entry != NULL; ) { + if (entry == device) { + found = true; + break; + } + pred = entry; + entry = entry->next; + } + if (found) { + // Unlink the device from the opening devices list then delete it. + // We don't need to tell the client that the device was closed because + // it does not even know it was opened in the first place. + LOGI("Device %s was immediately closed after opening.", device->path.string()); + if (pred) { + pred->next = device->next; + } else { + mOpeningDevices = device->next; + } + delete device; + } else { + // Link into closing devices list. + // The device will be deleted later after we have informed the client. + device->next = mClosingDevices; + mClosingDevices = device; + } + return 0; +} + +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 000000000000..7053a948049c --- /dev/null +++ b/services/input/EventHub.h @@ -0,0 +1,343 @@ +/* + * 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 void clear() { + valid = false; + minValue = 0; + maxValue = 0; + flat = 0; + fuzz = 0; + } +}; + +/* + * Input device classes. + */ +enum { + /* The input device is a keyboard or has buttons. */ + 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, + + /* The input device is a joystick (implies gamepad, has joystick absolute axes). */ + INPUT_DEVICE_CLASS_JOYSTICK = 0x00000100, + + /* The input device is external (not built-in). */ + INPUT_DEVICE_CLASS_EXTERNAL = 0x80000000, +}; + +/* + * 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 bool hasRelativeAxis(int32_t deviceId, int axis) const = 0; + + virtual status_t mapKey(int32_t deviceId, int scancode, + int32_t* outKeycode, uint32_t* outFlags) const = 0; + + virtual status_t mapAxis(int32_t deviceId, int scancode, + AxisInfo* outAxisInfo) 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 bool hasRelativeAxis(int32_t deviceId, int axis) const; + + virtual status_t mapKey(int32_t deviceId, int scancode, + int32_t* outKeycode, uint32_t* outFlags) const; + + virtual status_t mapAxis(int32_t deviceId, int scancode, + AxisInfo* outAxisInfo) 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 closeDeviceAtIndexLocked(int index); + 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; + uint8_t* relBitmask; + 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); + + bool isExternalDevice(Device* device); + + // 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. + int32_t mSwitches[SW_MAX + 1]; + + 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 000000000000..cc8006217c11 --- /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 000000000000..19295e6d77ca --- /dev/null +++ b/services/input/InputDispatcher.cpp @@ -0,0 +1,3921 @@ +/* + * 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: + case AMOTION_EVENT_ACTION_HOVER_MOVE: + case AMOTION_EVENT_ACTION_SCROLL: + 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 + || motionEntry->action == AMOTION_EVENT_ACTION_HOVER_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 = 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]. + getAxisValue(AMOTION_EVENT_AXIS_X)); + int32_t y = int32_t(motionEntry->firstSample.pointerCoords[0]. + getAxisValue(AMOTION_EVENT_AXIS_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; + + 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(); + } + + if (entry->repeatCount == 1) { + entry->flags |= AKEY_EVENT_FLAG_LONG_PRESS; + } else { + entry->flags &= ~AKEY_EVENT_FLAG_LONG_PRESS; + } + + 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. + bool conflictingPointerActions = false; + if (! mCurrentInputTargetsValid) { + int32_t injectionResult; + if (isPointerEvent) { + // Pointer event. (eg. touchscreen) + injectionResult = findTouchedWindowTargetsLocked(currentTime, + entry, nextWakeupTime, &conflictingPointerActions); + } 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. + if (conflictingPointerActions) { + synthesizeCancelationEventsForAllConnectionsLocked( + InputState::CANCEL_POINTER_EVENTS, "Conflicting pointer actions."); + } + 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].getAxisValue(AMOTION_EVENT_AXIS_X), + sample->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y), + sample->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), + sample->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE), + sample->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), + sample->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), + sample->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), + sample->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), + sample->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_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, bool* outConflictingPointerActions) { + 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 = mTouchState.split; + bool wrongDevice = mTouchState.down + && (mTouchState.deviceId != entry->deviceId + || mTouchState.source != entry->source); + if (maskedAction == AMOTION_EVENT_ACTION_DOWN + || maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE + || maskedAction == AMOTION_EVENT_ACTION_SCROLL) { + bool down = maskedAction == AMOTION_EVENT_ACTION_DOWN; + if (wrongDevice && !down) { + mTempTouchState.copyFrom(mTouchState); + } else { + mTempTouchState.reset(); + mTempTouchState.down = down; + mTempTouchState.deviceId = entry->deviceId; + mTempTouchState.source = entry->source; + isSplit = false; + wrongDevice = false; + } + } else { + mTempTouchState.copyFrom(mTouchState); + } + 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) + || maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE + || maskedAction == AMOTION_EVENT_ACTION_SCROLL) { + /* Case 1: New splittable pointer going down, or need target for hover or scroll. */ + + int32_t pointerIndex = getMotionEventActionPointerIndex(action); + int32_t x = int32_t(entry->firstSample.pointerCoords[pointerIndex]. + getAxisValue(AMOTION_EVENT_AXIS_X)); + int32_t y = int32_t(entry->firstSample.pointerCoords[pointerIndex]. + getAxisValue(AMOTION_EVENT_AXIS_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. + // We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper + // engine only supports touch events. We would need to add a mechanism similar + // to View.onGenericMotionEvent to enable wallpapers to handle these events. + 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 + || maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) { + // All pointers up or canceled. + mTouchState.reset(); + } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { + // First pointer went down. + if (mTouchState.down) { + *outConflictingPointerActions = true; +#if DEBUG_FOCUS + LOGD("Pointer down received while already down."); +#endif + } + mTouchState.copyFrom(mTempTouchState); + } 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; + } + } + mTouchState.copyFrom(mTempTouchState); + } else if (maskedAction == AMOTION_EVENT_ACTION_SCROLL) { + // Discard temporary touch state since it was only valid for this action. + } else { + // Save changes to touch state as-is for all other actions. + 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_OTHER_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 (MotionEvent::isTouchEvent(motionEntry->source, motionEntry->action)) { + 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; + } + eventType = POWER_MANAGER_BUTTON_EVENT; + 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; + connection->inputState.trackEvent(eventEntry); + + // 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; + } + if (policyFlags & POLICY_FLAG_ALT) { + metaState |= AMETA_ALT_ON | AMETA_ALT_LEFT_ON; + } + if (policyFlags & POLICY_FLAG_ALT_GR) { + metaState |= AMETA_ALT_ON | AMETA_ALT_RIGHT_ON; + } + if (policyFlags & POLICY_FLAG_SHIFT) { + metaState |= AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON; + } + if (policyFlags & POLICY_FLAG_CAPS_LOCK) { + metaState |= AMETA_CAPS_LOCK_ON; + } + if (policyFlags & POLICY_FLAG_FUNCTION) { + metaState |= AMETA_FUNCTION_ON; + } + + 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].getAxisValue(AMOTION_EVENT_AXIS_X), + pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y), + pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), + pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE), + pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), + pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), + pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), + pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), + pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION)); + } +#endif + if (! validateMotionEvent(action, pointerCount, pointerIds)) { + return; + } + + policyFlags |= POLICY_FLAG_TRUSTED; + mPolicy->interceptMotionBeforeQueueing(eventTime, /*byref*/ policyFlags); + + bool needWake; + { // acquire lock + AutoMutex _l(mLock); + + // Attempt batching and streaming of move events. + if (action == AMOTION_EVENT_ACTION_MOVE + || action == AMOTION_EVENT_ACTION_HOVER_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 + || motionEntry->source != source) { + // Keep looking for this device and source. + continue; + } + + if (motionEntry->action != action + || motionEntry->pointerCount != pointerCount + || motionEntry->isInjected()) { + // Last motion event in the queue for this device and source 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 != action + || 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->interceptMotionBeforeQueueing(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 want to send a fallback CANCEL. + synthesizeCancelationEventsForConnectionLocked(connection, + InputState::CANCEL_FALLBACK_EVENTS, + "application handled a non-fallback event, " + "canceling all fallback events"); + connection->originalKeyCodeForFallback = -1; + } else { + // If the application did not handle a non-fallback key, first check + // that we are in a good state to handle the fallback key. Then ask + // the policy what to do with it. + if (connection->originalKeyCodeForFallback < 0) { + if (keyEntry->action != AKEY_EVENT_ACTION_DOWN + || keyEntry->repeatCount != 0) { +#if DEBUG_OUTBOUND_EVENT_DETAILS + LOGD("Unhandled key event: Skipping fallback since this " + "is not an initial down. " + "keyCode=%d, action=%d, repeatCount=%d", + keyEntry->keyCode, keyEntry->action, keyEntry->repeatCount); +#endif + goto SkipFallback; + } + + // Start handling the fallback key on DOWN. + connection->originalKeyCodeForFallback = keyEntry->keyCode; + } else { + if (keyEntry->keyCode != connection->originalKeyCodeForFallback) { +#if DEBUG_OUTBOUND_EVENT_DETAILS + LOGD("Unhandled key event: Skipping fallback since there is " + "already a different fallback in progress. " + "keyCode=%d, originalKeyCodeForFallback=%d", + keyEntry->keyCode, connection->originalKeyCodeForFallback); +#endif + goto SkipFallback; + } + + // Finish handling the fallback key on UP. + if (keyEntry->action == AKEY_EVENT_ACTION_UP) { + connection->originalKeyCodeForFallback = -1; + } + } + +#if DEBUG_OUTBOUND_EVENT_DETAILS + LOGD("Unhandled key event: Asking policy to perform fallback action. " + "keyCode=%d, action=%d, repeatCount=%d", + keyEntry->keyCode, keyEntry->action, keyEntry->repeatCount); +#endif + 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; + +#if DEBUG_OUTBOUND_EVENT_DETAILS + LOGD("Unhandled key event: Dispatching fallback key. " + "fallbackKeyCode=%d, fallbackMetaState=%08x", + keyEntry->keyCode, keyEntry->metaState); +#endif + + dispatchEntry->inProgress = false; + startDispatchCycleLocked(now(), connection); + return; + } + } + } + } + } + +SkipFallback: + 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(); +} + +void InputDispatcher::InputState::trackEvent( + const EventEntry* entry) { + switch (entry->type) { + case EventEntry::TYPE_KEY: + trackKey(static_cast<const KeyEntry*>(entry)); + break; + + case EventEntry::TYPE_MOTION: + trackMotion(static_cast<const MotionEntry*>(entry)); + break; + } +} + +void 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; + + case AKEY_EVENT_ACTION_DOWN: + mKeyMementos.removeAt(i); + goto Found; + + default: + return; + } + } + } + +Found: + if (action == 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; + } +} + +void 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: + case AMOTION_EVENT_ACTION_HOVER_MOVE: + mMotionMementos.removeAt(i); + return; + + case AMOTION_EVENT_ACTION_DOWN: + mMotionMementos.removeAt(i); + goto Found; + + case AMOTION_EVENT_ACTION_POINTER_UP: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + case AMOTION_EVENT_ACTION_MOVE: + memento.setPointers(entry); + return; + + default: + return; + } + } + } + +Found: + if (action == 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); + } +} + +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), + originalKeyCodeForFallback(-1) { +} + +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 000000000000..1e118c4d2891 --- /dev/null +++ b/services/input/InputDispatcher.h @@ -0,0 +1,986 @@ +/* + * 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 touch, trackball or other motion 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 interceptMotionBeforeQueueing(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 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. + void trackEvent(const EventEntry* entry); + + // Records tracking information for a key event that has just been published. + void trackKey(const KeyEntry* entry); + + // Records tracking information for a motion event that has just been published. + void 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 + int32_t originalKeyCodeForFallback; // original keycode for fallback in progress, -1 if none + + 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, bool* outConflictingPointerActions); + + 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 000000000000..5dfa5d5930d8 --- /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 000000000000..df4d299ed3b3 --- /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 000000000000..3029028e4e76 --- /dev/null +++ b/services/input/InputReader.cpp @@ -0,0 +1,4258 @@ +/* + * 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 int32_t rotateValueUsingRotationMap(int32_t value, int32_t orientation, + const int32_t map[][4], size_t mapSize) { + if (orientation != DISPLAY_ORIENTATION_0) { + for (size_t i = 0; i < mapSize; i++) { + if (value == map[i][0]) { + return map[i][orientation]; + } + } + } + return value; +} + +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 size_t keyCodeRotationMapSize = + sizeof(keyCodeRotationMap) / sizeof(keyCodeRotationMap[0]); + +int32_t rotateKeyCode(int32_t keyCode, int32_t orientation) { + return rotateValueUsingRotationMap(keyCode, orientation, + keyCodeRotationMap, keyCodeRotationMapSize); +} + +static const int32_t edgeFlagRotationMap[][4] = { + // edge flags enumerated counter-clockwise with the original (unrotated) edge flag first + // no rotation, 90 degree rotation, 180 degree rotation, 270 degree rotation + { AMOTION_EVENT_EDGE_FLAG_BOTTOM, AMOTION_EVENT_EDGE_FLAG_RIGHT, + AMOTION_EVENT_EDGE_FLAG_TOP, AMOTION_EVENT_EDGE_FLAG_LEFT }, + { AMOTION_EVENT_EDGE_FLAG_RIGHT, AMOTION_EVENT_EDGE_FLAG_TOP, + AMOTION_EVENT_EDGE_FLAG_LEFT, AMOTION_EVENT_EDGE_FLAG_BOTTOM }, + { AMOTION_EVENT_EDGE_FLAG_TOP, AMOTION_EVENT_EDGE_FLAG_LEFT, + AMOTION_EVENT_EDGE_FLAG_BOTTOM, AMOTION_EVENT_EDGE_FLAG_RIGHT }, + { AMOTION_EVENT_EDGE_FLAG_LEFT, AMOTION_EVENT_EDGE_FLAG_BOTTOM, + AMOTION_EVENT_EDGE_FLAG_RIGHT, AMOTION_EVENT_EDGE_FLAG_TOP }, +}; +static const size_t edgeFlagRotationMapSize = + sizeof(edgeFlagRotationMap) / sizeof(edgeFlagRotationMap[0]); + +static int32_t rotateEdgeFlag(int32_t edgeFlag, int32_t orientation) { + return rotateValueUsingRotationMap(edgeFlag, orientation, + edgeFlagRotationMap, edgeFlagRotationMapSize); +} + +static inline bool sourcesMatchMask(uint32_t sources, uint32_t sourceMask) { + return (sources & sourceMask & ~ AINPUT_SOURCE_CLASS_MASK) != 0; +} + +static uint32_t getButtonStateForScanCode(int32_t scanCode) { + // Currently all buttons are mapped to the primary button. + switch (scanCode) { + case BTN_LEFT: + case BTN_RIGHT: + case BTN_MIDDLE: + case BTN_SIDE: + case BTN_EXTRA: + case BTN_FORWARD: + case BTN_BACK: + case BTN_TASK: + return BUTTON_STATE_PRIMARY; + default: + return 0; + } +} + +// Returns true if the pointer should be reported as being down given the specified +// button states. +static bool isPointerDown(uint32_t buttonState) { + return buttonState & BUTTON_STATE_PRIMARY; +} + +static int32_t calculateEdgeFlagsUsingPointerBounds( + const sp<PointerControllerInterface>& pointerController, float x, float y) { + int32_t edgeFlags = 0; + float minX, minY, maxX, maxY; + if (pointerController->getBounds(&minX, &minY, &maxX, &maxY)) { + if (x <= minX) { + edgeFlags |= AMOTION_EVENT_EDGE_FLAG_LEFT; + } else if (x >= maxX) { + edgeFlags |= AMOTION_EVENT_EDGE_FLAG_RIGHT; + } + if (y <= minY) { + edgeFlags |= AMOTION_EVENT_EDGE_FLAG_TOP; + } else if (y >= maxY) { + edgeFlags |= AMOTION_EVENT_EDGE_FLAG_BOTTOM; + } + } + return edgeFlags; +} + + +// --- 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); + + // External devices. + if (classes & INPUT_DEVICE_CLASS_EXTERNAL) { + device->setExternal(true); + } + + // Switch-like devices. + if (classes & INPUT_DEVICE_CLASS_SWITCH) { + device->addMapper(new SwitchInputMapper(device)); + } + + // Keyboard-like devices. + uint32_t keyboardSource = 0; + int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC; + if (classes & INPUT_DEVICE_CLASS_KEYBOARD) { + keyboardSource |= AINPUT_SOURCE_KEYBOARD; + } + if (classes & INPUT_DEVICE_CLASS_ALPHAKEY) { + keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC; + } + if (classes & INPUT_DEVICE_CLASS_DPAD) { + keyboardSource |= AINPUT_SOURCE_DPAD; + } + if (classes & INPUT_DEVICE_CLASS_GAMEPAD) { + keyboardSource |= AINPUT_SOURCE_GAMEPAD; + } + + if (keyboardSource != 0) { + device->addMapper(new KeyboardInputMapper(device, keyboardSource, 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)); + } + + // Joystick-like devices. + if (classes & INPUT_DEVICE_CLASS_JOYSTICK) { + device->addMapper(new JoystickInputMapper(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::fadePointer() { + { // acquire device registry reader lock + RWLock::AutoRLock _rl(mDeviceRegistryLock); + + for (size_t i = 0; i < mDevices.size(); i++) { + InputDevice* device = mDevices.valueAt(i); + device->fadePointer(); + } + } // release device registry reader lock +} + +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), mIsExternal(false) { +} + +InputDevice::~InputDevice() { + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + delete mMappers[i]; + } + mMappers.clear(); +} + +void InputDevice::dump(String8& dump) { + InputDeviceInfo deviceInfo; + getDeviceInfo(& deviceInfo); + + dump.appendFormat(INDENT "Device %d: %s\n", deviceInfo.getId(), + deviceInfo.getName().string()); + dump.appendFormat(INDENT2 "IsExternal: %s\n", toString(mIsExternal)); + dump.appendFormat(INDENT2 "Sources: 0x%08x\n", deviceInfo.getSources()); + dump.appendFormat(INDENT2 "KeyboardType: %d\n", deviceInfo.getKeyboardType()); + + const Vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges(); + if (!ranges.isEmpty()) { + dump.append(INDENT2 "Motion Ranges:\n"); + for (size_t i = 0; i < ranges.size(); i++) { + const InputDeviceInfo::MotionRange& range = ranges.itemAt(i); + const char* label = getAxisLabel(range.axis); + char name[32]; + if (label) { + strncpy(name, label, sizeof(name)); + name[sizeof(name) - 1] = '\0'; + } else { + snprintf(name, sizeof(name), "%d", range.axis); + } + dump.appendFormat(INDENT3 "%s: source=0x%08x, " + "min=%0.3f, max=%0.3f, flat=%0.3f, fuzz=%0.3f\n", + name, range.source, range.min, range.max, range.flat, range.fuzz); + } + } + + 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; + + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + mapper->configure(); + mSources |= mapper->getSources(); + } +} + +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; +} + +void InputDevice::fadePointer() { + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + mapper->fadePointer(); + } +} + + +// --- 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; +} + +void InputMapper::fadePointer() { +} + +void InputMapper::dumpRawAbsoluteAxisInfo(String8& dump, + const 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); + } +} + + +// --- 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 source, int32_t keyboardType) : + InputMapper(device), mSource(source), + mKeyboardType(keyboardType) { + initializeLocked(); +} + +KeyboardInputMapper::~KeyboardInputMapper() { +} + +void KeyboardInputMapper::initializeLocked() { + mLocked.metaState = AMETA_NONE; + mLocked.downTime = 0; +} + +uint32_t KeyboardInputMapper::getSources() { + return mSource; +} + +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_MISC && scanCode < BTN_MOUSE) + || (scanCode >= BTN_JOYSTICK && 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 + + // Key down on external an keyboard should wake the device. + // We don't do this for internal keyboards to prevent them from waking up in your pocket. + // For internal keyboards, the key layout file should specify the policy flags for + // each wake key individually. + // TODO: Use the input device configuration to control this behavior more finely. + if (down && getDevice()->isExternal() + && !(policyFlags & (POLICY_FLAG_WAKE | POLICY_FLAG_WAKE_DROPPED))) { + policyFlags |= POLICY_FLAG_WAKE_DROPPED; + } + + if (metaStateChanged) { + getContext()->updateGlobalMetaState(); + } + + if (down && !isMetaKey(keyCode)) { + getContext()->fadePointer(); + } + + getDispatcher()->notifyKey(when, getDeviceId(), mSource, 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 mSource; +} + +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(AMOTION_EVENT_AXIS_X, mSource, minX, maxX, 0.0f, 0.0f); + info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, minY, maxY, 0.0f, 0.0f); + } + } else { + info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, -1.0f, 1.0f, 0.0f, mXScale); + info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale); + } + info->addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, mSource, 0.0f, 1.0f, 0.0f, 0.0f); + + if (mHaveVWheel) { + info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f); + } + if (mHaveHWheel) { + info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.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 "XScale: %0.3f\n", mXScale); + dump.appendFormat(INDENT3 "YScale: %0.3f\n", mYScale); + dump.appendFormat(INDENT3 "XPrecision: %0.3f\n", mXPrecision); + dump.appendFormat(INDENT3 "YPrecision: %0.3f\n", mYPrecision); + dump.appendFormat(INDENT3 "HaveVWheel: %s\n", toString(mHaveVWheel)); + dump.appendFormat(INDENT3 "HaveHWheel: %s\n", toString(mHaveHWheel)); + dump.appendFormat(INDENT3 "VWheelScale: %0.3f\n", mVWheelScale); + dump.appendFormat(INDENT3 "HWheelScale: %0.3f\n", mHWheelScale); + dump.appendFormat(INDENT3 "ButtonState: 0x%08x\n", mLocked.buttonState); + dump.appendFormat(INDENT3 "Down: %s\n", toString(isPointerDown(mLocked.buttonState))); + 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: + mSource = AINPUT_SOURCE_MOUSE; + mXPrecision = 1.0f; + mYPrecision = 1.0f; + mXScale = 1.0f; + mYScale = 1.0f; + mPointerController = getPolicy()->obtainPointerController(getDeviceId()); + break; + case Parameters::MODE_NAVIGATION: + mSource = AINPUT_SOURCE_TRACKBALL; + mXPrecision = TRACKBALL_MOVEMENT_THRESHOLD; + mYPrecision = TRACKBALL_MOVEMENT_THRESHOLD; + mXScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD; + mYScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD; + break; + } + + mVWheelScale = 1.0f; + mHWheelScale = 1.0f; + + mHaveVWheel = getEventHub()->hasRelativeAxis(getDeviceId(), REL_WHEEL); + mHaveHWheel = getEventHub()->hasRelativeAxis(getDeviceId(), REL_HWHEEL); +} + +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.buttonState = 0; + mLocked.downTime = 0; +} + +void CursorInputMapper::reset() { + for (;;) { + uint32_t buttonState; + { // acquire lock + AutoMutex _l(mLock); + + buttonState = mLocked.buttonState; + if (!buttonState) { + initializeLocked(); + break; // done + } + } // release lock + + // Synthesize button up event on reset. + nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC); + mAccumulator.clear(); + mAccumulator.buttonDown = 0; + mAccumulator.buttonUp = buttonState; + mAccumulator.fields = Accumulator::FIELD_BUTTONS; + sync(when); + } + + InputMapper::reset(); +} + +void CursorInputMapper::process(const RawEvent* rawEvent) { + switch (rawEvent->type) { + case EV_KEY: { + uint32_t buttonState = getButtonStateForScanCode(rawEvent->scanCode); + if (buttonState) { + if (rawEvent->value) { + mAccumulator.buttonDown = buttonState; + mAccumulator.buttonUp = 0; + } else { + mAccumulator.buttonDown = 0; + mAccumulator.buttonUp = buttonState; + } + mAccumulator.fields |= Accumulator::FIELD_BUTTONS; + + // 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; + case REL_WHEEL: + mAccumulator.fields |= Accumulator::FIELD_REL_WHEEL; + mAccumulator.relWheel = rawEvent->value; + break; + case REL_HWHEEL: + mAccumulator.fields |= Accumulator::FIELD_REL_HWHEEL; + mAccumulator.relHWheel = 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 + } + + int32_t motionEventAction; + int32_t motionEventEdgeFlags; + PointerCoords pointerCoords; + nsecs_t downTime; + float vscroll, hscroll; + { // acquire lock + AutoMutex _l(mLock); + + bool down, downChanged; + bool wasDown = isPointerDown(mLocked.buttonState); + bool buttonsChanged = fields & Accumulator::FIELD_BUTTONS; + if (buttonsChanged) { + mLocked.buttonState = (mLocked.buttonState | mAccumulator.buttonDown) + & ~mAccumulator.buttonUp; + + down = isPointerDown(mLocked.buttonState); + + if (!wasDown && down) { + mLocked.downTime = when; + downChanged = true; + } else if (wasDown && !down) { + downChanged = true; + } else { + downChanged = false; + } + } else { + down = wasDown; + 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 = down ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP; + } else if (down || mPointerController == NULL) { + motionEventAction = AMOTION_EVENT_ACTION_MOVE; + } else { + motionEventAction = AMOTION_EVENT_ACTION_HOVER_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; + } + } + + pointerCoords.clear(); + + motionEventEdgeFlags = AMOTION_EVENT_EDGE_FLAG_NONE; + + if (mPointerController != NULL) { + mPointerController->move(deltaX, deltaY); + if (buttonsChanged) { + mPointerController->setButtonState(mLocked.buttonState); + } + + float x, y; + mPointerController->getPosition(&x, &y); + pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x); + pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y); + + if (motionEventAction == AMOTION_EVENT_ACTION_DOWN) { + motionEventEdgeFlags = calculateEdgeFlagsUsingPointerBounds( + mPointerController, x, y); + } + } else { + pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, deltaX); + pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, deltaY); + } + + pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f); + + if (mHaveVWheel && (fields & Accumulator::FIELD_REL_WHEEL)) { + vscroll = mAccumulator.relWheel; + } else { + vscroll = 0; + } + if (mHaveHWheel && (fields & Accumulator::FIELD_REL_HWHEEL)) { + hscroll = mAccumulator.relHWheel; + } else { + hscroll = 0; + } + if (hscroll != 0 || vscroll != 0) { + mPointerController->unfade(); + } + } // release lock + + // Moving an external trackball or mouse should wake the device. + // We don't do this for internal cursor devices to prevent them from waking up + // the device in your pocket. + // TODO: Use the input device configuration to control this behavior more finely. + uint32_t policyFlags = 0; + if (getDevice()->isExternal()) { + policyFlags |= POLICY_FLAG_WAKE_DROPPED; + } + + int32_t metaState = mContext->getGlobalMetaState(); + int32_t pointerId = 0; + getDispatcher()->notifyMotion(when, getDeviceId(), mSource, policyFlags, + motionEventAction, 0, metaState, motionEventEdgeFlags, + 1, &pointerId, &pointerCoords, mXPrecision, mYPrecision, downTime); + + mAccumulator.clear(); + + if (vscroll != 0 || hscroll != 0) { + pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll); + pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll); + + getDispatcher()->notifyMotion(when, getDeviceId(), mSource, policyFlags, + AMOTION_EVENT_ACTION_SCROLL, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE, + 1, &pointerId, &pointerCoords, mXPrecision, mYPrecision, downTime); + } +} + +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; + } +} + +void CursorInputMapper::fadePointer() { + { // acquire lock + AutoMutex _l(mLock); + if (mPointerController != NULL) { + mPointerController->fade(); + } + } // release lock +} + + +// --- TouchInputMapper --- + +TouchInputMapper::TouchInputMapper(InputDevice* device) : + InputMapper(device) { + mLocked.surfaceOrientation = -1; + mLocked.surfaceWidth = -1; + mLocked.surfaceHeight = -1; + + initializeLocked(); +} + +TouchInputMapper::~TouchInputMapper() { +} + +uint32_t TouchInputMapper::getSources() { + return mTouchSource; +} + +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. + if (!configureSurfaceLocked()) { + return; + } + + info->addMotionRange(mLocked.orientedRanges.x); + info->addMotionRange(mLocked.orientedRanges.y); + + if (mLocked.orientedRanges.havePressure) { + info->addMotionRange(mLocked.orientedRanges.pressure); + } + + if (mLocked.orientedRanges.haveSize) { + info->addMotionRange(mLocked.orientedRanges.size); + } + + if (mLocked.orientedRanges.haveTouchSize) { + info->addMotionRange(mLocked.orientedRanges.touchMajor); + info->addMotionRange(mLocked.orientedRanges.touchMinor); + } + + if (mLocked.orientedRanges.haveToolSize) { + info->addMotionRange(mLocked.orientedRanges.toolMajor); + info->addMotionRange(mLocked.orientedRanges.toolMinor); + } + + if (mLocked.orientedRanges.haveOrientation) { + info->addMotionRange(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 "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); + + dump.appendFormat(INDENT3 "Last Touch:\n"); + dump.appendFormat(INDENT4 "Pointer Count: %d\n", mLastTouch.pointerCount); + } // 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: + mTouchSource = AINPUT_SOURCE_TOUCHSCREEN; + break; + case Parameters::DEVICE_TYPE_TOUCH_PAD: + mTouchSource = 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") { + mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD; + } else { + LOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.string()); + } + } + + mParameters.orientationAware = mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN; + getDevice()->getConfiguration().tryGetProperty(String8("touch.orientationAware"), + mParameters.orientationAware); + + mParameters.associatedDisplayId = mParameters.orientationAware + || mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN + ? 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(); +} + +void TouchInputMapper::dumpRawAxes(String8& dump) { + dump.append(INDENT3 "Raw Axes:\n"); + dumpRawAbsoluteAxisInfo(dump, mRawAxes.x, "X"); + dumpRawAbsoluteAxisInfo(dump, mRawAxes.y, "Y"); + dumpRawAbsoluteAxisInfo(dump, mRawAxes.pressure, "Pressure"); + dumpRawAbsoluteAxisInfo(dump, mRawAxes.touchMajor, "TouchMajor"); + dumpRawAbsoluteAxisInfo(dump, mRawAxes.touchMinor, "TouchMinor"); + dumpRawAbsoluteAxisInfo(dump, mRawAxes.toolMajor, "ToolMajor"); + dumpRawAbsoluteAxisInfo(dump, mRawAxes.toolMinor, "ToolMinor"); + dumpRawAbsoluteAxisInfo(dump, mRawAxes.orientation, "Orientation"); +} + +bool TouchInputMapper::configureSurfaceLocked() { + // Ensure we have valid X and Y axes. + if (!mRawAxes.x.valid || !mRawAxes.y.valid) { + LOGW(INDENT "Touch device '%s' did not report support for X or Y axis! " + "The device will be inoperable.", getDeviceName().string()); + return false; + } + + // Update orientation and dimensions if needed. + int32_t orientation = DISPLAY_ORIENTATION_0; + int32_t width = mRawAxes.x.maxValue - mRawAxes.x.minValue + 1; + int32_t height = mRawAxes.y.maxValue - mRawAxes.y.minValue + 1; + + if (mParameters.associatedDisplayId >= 0) { + // Note: getDisplayInfo is non-reentrant so we can continue holding the lock. + if (! getPolicy()->getDisplayInfo(mParameters.associatedDisplayId, + &mLocked.associatedDisplayWidth, &mLocked.associatedDisplayHeight, + &mLocked.associatedDisplayOrientation)) { + return false; + } + + // A touch screen inherits the dimensions of the display. + if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN) { + width = mLocked.associatedDisplayWidth; + height = mLocked.associatedDisplayHeight; + } + + // The device inherits the orientation of the display if it is orientation aware. + if (mParameters.orientationAware) { + orientation = mLocked.associatedDisplayOrientation; + } + } + + 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', surface size is now %dx%d", + getDeviceId(), getDeviceName().string(), width, height); + + mLocked.surfaceWidth = width; + mLocked.surfaceHeight = height; + + // Configure X and Y factors. + mLocked.xScale = float(width) / (mRawAxes.x.maxValue - mRawAxes.x.minValue + 1); + mLocked.yScale = float(height) / (mRawAxes.y.maxValue - mRawAxes.y.minValue + 1); + mLocked.xPrecision = 1.0f / mLocked.xScale; + mLocked.yPrecision = 1.0f / mLocked.yScale; + + mLocked.orientedRanges.x.axis = AMOTION_EVENT_AXIS_X; + mLocked.orientedRanges.x.source = mTouchSource; + mLocked.orientedRanges.y.axis = AMOTION_EVENT_AXIS_Y; + mLocked.orientedRanges.y.source = mTouchSource; + + configureVirtualKeysLocked(); + + // 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.axis = AMOTION_EVENT_AXIS_TOUCH_MAJOR; + mLocked.orientedRanges.touchMajor.source = mTouchSource; + 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; + mLocked.orientedRanges.touchMinor.axis = AMOTION_EVENT_AXIS_TOUCH_MINOR; + } + + // 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.axis = AMOTION_EVENT_AXIS_TOOL_MAJOR; + mLocked.orientedRanges.toolMajor.source = mTouchSource; + 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; + mLocked.orientedRanges.toolMinor.axis = AMOTION_EVENT_AXIS_TOOL_MINOR; + } + + // 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.axis = AMOTION_EVENT_AXIS_PRESSURE; + mLocked.orientedRanges.pressure.source = mTouchSource; + 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.axis = AMOTION_EVENT_AXIS_SIZE; + mLocked.orientedRanges.size.source = mTouchSource; + 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.axis = AMOTION_EVENT_AXIS_ORIENTATION; + mLocked.orientedRanges.orientation.source = mTouchSource; + 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, scales and ranges. + // Note that the maximum value reported is an inclusive maximum value so it is one + // unit less than the total width or height of surface. + 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; + + mLocked.orientedRanges.x.min = 0; + mLocked.orientedRanges.x.max = (mRawAxes.y.maxValue - mRawAxes.y.minValue) + * mLocked.yScale; + mLocked.orientedRanges.x.flat = 0; + mLocked.orientedRanges.x.fuzz = mLocked.yScale; + + mLocked.orientedRanges.y.min = 0; + mLocked.orientedRanges.y.max = (mRawAxes.x.maxValue - mRawAxes.x.minValue) + * mLocked.xScale; + mLocked.orientedRanges.y.flat = 0; + mLocked.orientedRanges.y.fuzz = mLocked.xScale; + break; + + default: + mLocked.orientedSurfaceWidth = mLocked.surfaceWidth; + mLocked.orientedSurfaceHeight = mLocked.surfaceHeight; + + mLocked.orientedXPrecision = mLocked.xPrecision; + mLocked.orientedYPrecision = mLocked.yPrecision; + + mLocked.orientedRanges.x.min = 0; + mLocked.orientedRanges.x.max = (mRawAxes.x.maxValue - mRawAxes.x.minValue) + * mLocked.xScale; + mLocked.orientedRanges.x.flat = 0; + mLocked.orientedRanges.x.fuzz = mLocked.xScale; + + mLocked.orientedRanges.y.min = 0; + mLocked.orientedRanges.y.max = (mRawAxes.y.maxValue - mRawAxes.y.minValue) + * mLocked.yScale; + mLocked.orientedRanges.y.flat = 0; + mLocked.orientedRanges.y.fuzz = mLocked.yScale; + break; + } + } + + 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() { + 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.maxValue - mRawAxes.x.minValue + 1; + int32_t touchScreenHeight = mRawAxes.y.maxValue - mRawAxes.y.minValue + 1; + + 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()->mapKey(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; + + // 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"); + + // 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) { + // 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; + } + + uint32_t policyFlags = 0; + if (mLastTouch.pointerCount == 0 && mCurrentTouch.pointerCount != 0) { + if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN) { + // If this is a touch screen, hide the pointer on an initial down. + getContext()->fadePointer(); + } + + // Initial downs on external touch devices should wake the device. + // We don't do this for internal touch screens to prevent them from waking + // up in your pocket. + // TODO: Use the input device configuration to control this behavior more finely. + if (getDevice()->isExternal()) { + policyFlags |= POLICY_FLAG_WAKE_DROPPED; + } + } + + // Process touches and virtual keys. + TouchResult touchResult = consumeOffScreenTouches(when, policyFlags); + if (touchResult == DISPATCH_TOUCH) { + suppressSwipeOntoVirtualKeys(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::suppressSwipeOntoVirtualKeys(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 = AMOTION_EVENT_EDGE_FLAG_NONE; + float xPrecision, yPrecision; + + { // acquire lock + AutoMutex _l(mLock); + + // Walk through the the active pointers and map touch screen coordinates (TouchData) into + // display or surface 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]; + + // 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 scale = 1.0f + pythag(c1, c2) / 16.0f; + touchMajor *= scale; + touchMinor /= scale; + toolMajor *= scale; + toolMinor /= scale; + } else { + orientation = 0; + } + break; + } + default: + orientation = 0; + } + + // X and Y + // Adjust coords for surface orientation. + float x, y; + switch (mLocked.surfaceOrientation) { + case DISPLAY_ORIENTATION_90: + x = float(in.y - mRawAxes.y.minValue) * mLocked.yScale; + y = float(mRawAxes.x.maxValue - in.x) * mLocked.xScale; + orientation -= M_PI_2; + if (orientation < - M_PI_2) { + orientation += M_PI; + } + break; + case DISPLAY_ORIENTATION_180: + x = float(mRawAxes.x.maxValue - in.x) * mLocked.xScale; + y = float(mRawAxes.y.maxValue - in.y) * mLocked.yScale; + break; + case DISPLAY_ORIENTATION_270: + x = float(mRawAxes.y.maxValue - in.y) * mLocked.yScale; + y = float(in.x - mRawAxes.x.minValue) * mLocked.xScale; + orientation += M_PI_2; + if (orientation > M_PI_2) { + orientation -= M_PI; + } + break; + default: + x = float(in.x - mRawAxes.x.minValue) * mLocked.xScale; + y = float(in.y - mRawAxes.y.minValue) * mLocked.yScale; + break; + } + + // Write output coords. + PointerCoords& out = pointerCoords[outIndex]; + out.clear(); + out.setAxisValue(AMOTION_EVENT_AXIS_X, x); + out.setAxisValue(AMOTION_EVENT_AXIS_Y, y); + out.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure); + out.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size); + out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, touchMajor); + out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, touchMinor); + out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor); + out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor); + out.setAxisValue(AMOTION_EVENT_AXIS_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) { + uint32_t inIndex = touch->idToIndex[pointerIds[0]]; + const PointerData& in = touch->pointers[inIndex]; + + if (in.x <= mRawAxes.x.minValue) { + motionEventEdgeFlags |= rotateEdgeFlag(AMOTION_EVENT_EDGE_FLAG_LEFT, + mLocked.surfaceOrientation); + } else if (in.x >= mRawAxes.x.maxValue) { + motionEventEdgeFlags |= rotateEdgeFlag(AMOTION_EVENT_EDGE_FLAG_RIGHT, + mLocked.surfaceOrientation); + } + if (in.y <= mRawAxes.y.minValue) { + motionEventEdgeFlags |= rotateEdgeFlag(AMOTION_EVENT_EDGE_FLAG_TOP, + mLocked.surfaceOrientation); + } else if (in.y >= mRawAxes.y.maxValue) { + motionEventEdgeFlags |= rotateEdgeFlag(AMOTION_EVENT_EDGE_FLAG_BOTTOM, + mLocked.surfaceOrientation); + } + } + + xPrecision = mLocked.orientedXPrecision; + yPrecision = mLocked.orientedYPrecision; + } // release lock + + getDispatcher()->notifyMotion(when, getDeviceId(), mTouchSource, policyFlags, + motionEventAction, 0, getContext()->getGlobalMetaState(), motionEventEdgeFlags, + pointerCount, pointerIds, pointerCoords, + xPrecision, yPrecision, mDownTime); +} + +bool TouchInputMapper::isPointInsideSurfaceLocked(int32_t x, int32_t y) { + return x >= mRawAxes.x.minValue && x <= mRawAxes.x.maxValue + && y >= mRawAxes.y.minValue && y <= mRawAxes.y.maxValue; +} + +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() { + 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.maxValue - mRawAxes.y.minValue + 1) * 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() { + 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.maxValue - mRawAxes.y.minValue + 1) / 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); +} + + +// --- JoystickInputMapper --- + +JoystickInputMapper::JoystickInputMapper(InputDevice* device) : + InputMapper(device) { +} + +JoystickInputMapper::~JoystickInputMapper() { +} + +uint32_t JoystickInputMapper::getSources() { + return AINPUT_SOURCE_JOYSTICK; +} + +void JoystickInputMapper::populateDeviceInfo(InputDeviceInfo* info) { + InputMapper::populateDeviceInfo(info); + + for (size_t i = 0; i < mAxes.size(); i++) { + const Axis& axis = mAxes.valueAt(i); + info->addMotionRange(axis.axisInfo.axis, AINPUT_SOURCE_JOYSTICK, + axis.min, axis.max, axis.flat, axis.fuzz); + if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) { + info->addMotionRange(axis.axisInfo.highAxis, AINPUT_SOURCE_JOYSTICK, + axis.min, axis.max, axis.flat, axis.fuzz); + } + } +} + +void JoystickInputMapper::dump(String8& dump) { + dump.append(INDENT2 "Joystick Input Mapper:\n"); + + dump.append(INDENT3 "Axes:\n"); + size_t numAxes = mAxes.size(); + for (size_t i = 0; i < numAxes; i++) { + const Axis& axis = mAxes.valueAt(i); + const char* label = getAxisLabel(axis.axisInfo.axis); + if (label) { + dump.appendFormat(INDENT4 "%s", label); + } else { + dump.appendFormat(INDENT4 "%d", axis.axisInfo.axis); + } + if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) { + label = getAxisLabel(axis.axisInfo.highAxis); + if (label) { + dump.appendFormat(" / %s (split at %d)", label, axis.axisInfo.splitValue); + } else { + dump.appendFormat(" / %d (split at %d)", axis.axisInfo.highAxis, + axis.axisInfo.splitValue); + } + } else if (axis.axisInfo.mode == AxisInfo::MODE_INVERT) { + dump.append(" (invert)"); + } + + dump.appendFormat(": min=%0.5f, max=%0.5f, flat=%0.5f, fuzz=%0.5f\n", + axis.min, axis.max, axis.flat, axis.fuzz); + dump.appendFormat(INDENT4 " scale=%0.5f, offset=%0.5f, " + "highScale=%0.5f, highOffset=%0.5f\n", + axis.scale, axis.offset, axis.highScale, axis.highOffset); + dump.appendFormat(INDENT4 " rawAxis=%d, rawMin=%d, rawMax=%d, rawFlat=%d, rawFuzz=%d\n", + mAxes.keyAt(i), axis.rawAxisInfo.minValue, axis.rawAxisInfo.maxValue, + axis.rawAxisInfo.flat, axis.rawAxisInfo.fuzz); + } +} + +void JoystickInputMapper::configure() { + InputMapper::configure(); + + // Collect all axes. + for (int32_t abs = 0; abs <= ABS_MAX; abs++) { + RawAbsoluteAxisInfo rawAxisInfo; + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), abs, &rawAxisInfo); + if (rawAxisInfo.valid) { + // Map axis. + AxisInfo axisInfo; + bool explicitlyMapped = !getEventHub()->mapAxis(getDeviceId(), abs, &axisInfo); + if (!explicitlyMapped) { + // Axis is not explicitly mapped, will choose a generic axis later. + axisInfo.mode = AxisInfo::MODE_NORMAL; + axisInfo.axis = -1; + } + + // Apply flat override. + int32_t rawFlat = axisInfo.flatOverride < 0 + ? rawAxisInfo.flat : axisInfo.flatOverride; + + // Calculate scaling factors and limits. + Axis axis; + if (axisInfo.mode == AxisInfo::MODE_SPLIT) { + float scale = 1.0f / (axisInfo.splitValue - rawAxisInfo.minValue); + float highScale = 1.0f / (rawAxisInfo.maxValue - axisInfo.splitValue); + axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped, + scale, 0.0f, highScale, 0.0f, + 0.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale); + } else if (isCenteredAxis(axisInfo.axis)) { + float scale = 2.0f / (rawAxisInfo.maxValue - rawAxisInfo.minValue); + float offset = avg(rawAxisInfo.minValue, rawAxisInfo.maxValue) * -scale; + axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped, + scale, offset, scale, offset, + -1.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale); + } else { + float scale = 1.0f / (rawAxisInfo.maxValue - rawAxisInfo.minValue); + axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped, + scale, 0.0f, scale, 0.0f, + 0.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale); + } + + // To eliminate noise while the joystick is at rest, filter out small variations + // in axis values up front. + axis.filter = axis.flat * 0.25f; + + mAxes.add(abs, axis); + } + } + + // If there are too many axes, start dropping them. + // Prefer to keep explicitly mapped axes. + if (mAxes.size() > PointerCoords::MAX_AXES) { + LOGI("Joystick '%s' has %d axes but the framework only supports a maximum of %d.", + getDeviceName().string(), mAxes.size(), PointerCoords::MAX_AXES); + pruneAxes(true); + pruneAxes(false); + } + + // Assign generic axis ids to remaining axes. + int32_t nextGenericAxisId = AMOTION_EVENT_AXIS_GENERIC_1; + size_t numAxes = mAxes.size(); + for (size_t i = 0; i < numAxes; i++) { + Axis& axis = mAxes.editValueAt(i); + if (axis.axisInfo.axis < 0) { + while (nextGenericAxisId <= AMOTION_EVENT_AXIS_GENERIC_16 + && haveAxis(nextGenericAxisId)) { + nextGenericAxisId += 1; + } + + if (nextGenericAxisId <= AMOTION_EVENT_AXIS_GENERIC_16) { + axis.axisInfo.axis = nextGenericAxisId; + nextGenericAxisId += 1; + } else { + LOGI("Ignoring joystick '%s' axis %d because all of the generic axis ids " + "have already been assigned to other axes.", + getDeviceName().string(), mAxes.keyAt(i)); + mAxes.removeItemsAt(i--); + numAxes -= 1; + } + } + } +} + +bool JoystickInputMapper::haveAxis(int32_t axisId) { + size_t numAxes = mAxes.size(); + for (size_t i = 0; i < numAxes; i++) { + const Axis& axis = mAxes.valueAt(i); + if (axis.axisInfo.axis == axisId + || (axis.axisInfo.mode == AxisInfo::MODE_SPLIT + && axis.axisInfo.highAxis == axisId)) { + return true; + } + } + return false; +} + +void JoystickInputMapper::pruneAxes(bool ignoreExplicitlyMappedAxes) { + size_t i = mAxes.size(); + while (mAxes.size() > PointerCoords::MAX_AXES && i-- > 0) { + if (ignoreExplicitlyMappedAxes && mAxes.valueAt(i).explicitlyMapped) { + continue; + } + LOGI("Discarding joystick '%s' axis %d because there are too many axes.", + getDeviceName().string(), mAxes.keyAt(i)); + mAxes.removeItemsAt(i); + } +} + +bool JoystickInputMapper::isCenteredAxis(int32_t axis) { + switch (axis) { + case AMOTION_EVENT_AXIS_X: + case AMOTION_EVENT_AXIS_Y: + case AMOTION_EVENT_AXIS_Z: + case AMOTION_EVENT_AXIS_RX: + case AMOTION_EVENT_AXIS_RY: + case AMOTION_EVENT_AXIS_RZ: + case AMOTION_EVENT_AXIS_HAT_X: + case AMOTION_EVENT_AXIS_HAT_Y: + case AMOTION_EVENT_AXIS_ORIENTATION: + case AMOTION_EVENT_AXIS_RUDDER: + case AMOTION_EVENT_AXIS_WHEEL: + return true; + default: + return false; + } +} + +void JoystickInputMapper::reset() { + // Recenter all axes. + nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC); + + size_t numAxes = mAxes.size(); + for (size_t i = 0; i < numAxes; i++) { + Axis& axis = mAxes.editValueAt(i); + axis.resetValue(); + } + + sync(when, true /*force*/); + + InputMapper::reset(); +} + +void JoystickInputMapper::process(const RawEvent* rawEvent) { + switch (rawEvent->type) { + case EV_ABS: { + ssize_t index = mAxes.indexOfKey(rawEvent->scanCode); + if (index >= 0) { + Axis& axis = mAxes.editValueAt(index); + float newValue, highNewValue; + switch (axis.axisInfo.mode) { + case AxisInfo::MODE_INVERT: + newValue = (axis.rawAxisInfo.maxValue - rawEvent->value) + * axis.scale + axis.offset; + highNewValue = 0.0f; + break; + case AxisInfo::MODE_SPLIT: + if (rawEvent->value < axis.axisInfo.splitValue) { + newValue = (axis.axisInfo.splitValue - rawEvent->value) + * axis.scale + axis.offset; + highNewValue = 0.0f; + } else if (rawEvent->value > axis.axisInfo.splitValue) { + newValue = 0.0f; + highNewValue = (rawEvent->value - axis.axisInfo.splitValue) + * axis.highScale + axis.highOffset; + } else { + newValue = 0.0f; + highNewValue = 0.0f; + } + break; + default: + newValue = rawEvent->value * axis.scale + axis.offset; + highNewValue = 0.0f; + break; + } + axis.newValue = newValue; + axis.highNewValue = highNewValue; + } + break; + } + + case EV_SYN: + switch (rawEvent->scanCode) { + case SYN_REPORT: + sync(rawEvent->when, false /*force*/); + break; + } + break; + } +} + +void JoystickInputMapper::sync(nsecs_t when, bool force) { + if (!filterAxes(force)) { + return; + } + + int32_t metaState = mContext->getGlobalMetaState(); + + PointerCoords pointerCoords; + pointerCoords.clear(); + + size_t numAxes = mAxes.size(); + for (size_t i = 0; i < numAxes; i++) { + const Axis& axis = mAxes.valueAt(i); + pointerCoords.setAxisValue(axis.axisInfo.axis, axis.currentValue); + if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) { + pointerCoords.setAxisValue(axis.axisInfo.highAxis, axis.highCurrentValue); + } + } + + // Moving a joystick axis should not wake the devide because joysticks can + // be fairly noisy even when not in use. On the other hand, pushing a gamepad + // button will likely wake the device. + // TODO: Use the input device configuration to control this behavior more finely. + uint32_t policyFlags = 0; + + int32_t pointerId = 0; + getDispatcher()->notifyMotion(when, getDeviceId(), AINPUT_SOURCE_JOYSTICK, policyFlags, + AMOTION_EVENT_ACTION_MOVE, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE, + 1, &pointerId, &pointerCoords, 0, 0, 0); +} + +bool JoystickInputMapper::filterAxes(bool force) { + bool atLeastOneSignificantChange = force; + size_t numAxes = mAxes.size(); + for (size_t i = 0; i < numAxes; i++) { + Axis& axis = mAxes.editValueAt(i); + if (force || hasValueChangedSignificantly(axis.filter, + axis.newValue, axis.currentValue, axis.min, axis.max)) { + axis.currentValue = axis.newValue; + atLeastOneSignificantChange = true; + } + if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) { + if (force || hasValueChangedSignificantly(axis.filter, + axis.highNewValue, axis.highCurrentValue, axis.min, axis.max)) { + axis.highCurrentValue = axis.highNewValue; + atLeastOneSignificantChange = true; + } + } + } + return atLeastOneSignificantChange; +} + +bool JoystickInputMapper::hasValueChangedSignificantly( + float filter, float newValue, float currentValue, float min, float max) { + if (newValue != currentValue) { + // Filter out small changes in value unless the value is converging on the axis + // bounds or center point. This is intended to reduce the amount of information + // sent to applications by particularly noisy joysticks (such as PS3). + if (fabs(newValue - currentValue) > filter + || hasMovedNearerToValueWithinFilteredRange(filter, newValue, currentValue, min) + || hasMovedNearerToValueWithinFilteredRange(filter, newValue, currentValue, max) + || hasMovedNearerToValueWithinFilteredRange(filter, newValue, currentValue, 0)) { + return true; + } + } + return false; +} + +bool JoystickInputMapper::hasMovedNearerToValueWithinFilteredRange( + float filter, float newValue, float currentValue, float thresholdValue) { + float newDistance = fabs(newValue - thresholdValue); + if (newDistance < filter) { + float oldDistance = fabs(currentValue - thresholdValue); + if (newDistance < oldDistance) { + return true; + } + } + return false; +} + +} // namespace android diff --git a/services/input/InputReader.h b/services/input/InputReader.h new file mode 100644 index 000000000000..68002ca1ab02 --- /dev/null +++ b/services/input/InputReader.h @@ -0,0 +1,1074 @@ +/* + * 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 void fadePointer() = 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(); + + virtual void fadePointer(); + + 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 isExternal() { return mIsExternal; } + inline void setExternal(bool external) { mIsExternal = external; } + + 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(); + + void fadePointer(); + + inline const PropertyMap& getConfiguration() { + return mConfiguration; + } + +private: + InputReaderContext* mContext; + int32_t mId; + + Vector<InputMapper*> mMappers; + + String8 mName; + uint32_t mSources; + bool mIsExternal; + + 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(); + + virtual void fadePointer(); + +protected: + InputDevice* mDevice; + InputReaderContext* mContext; + + static void dumpRawAbsoluteAxisInfo(String8& dump, + const RawAbsoluteAxisInfo& axis, const char* name); +}; + + +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 source, 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 mSource; + 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); + + virtual void fadePointer(); + +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_BUTTONS = 1, + FIELD_REL_X = 2, + FIELD_REL_Y = 4, + FIELD_REL_WHEEL = 8, + FIELD_REL_HWHEEL = 16, + }; + + uint32_t fields; + + uint32_t buttonDown; + uint32_t buttonUp; + + int32_t relX; + int32_t relY; + int32_t relWheel; + int32_t relHWheel; + + inline void clear() { + fields = 0; + } + } mAccumulator; + + int32_t mSource; + float mXScale; + float mYScale; + float mXPrecision; + float mYPrecision; + + bool mHaveVWheel; + bool mHaveHWheel; + float mVWheelScale; + float mHWheelScale; + + sp<PointerControllerInterface> mPointerController; + + struct LockedState { + uint32_t buttonState; + 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. + uint32_t mTouchSource; // sources when reporting touch data + + // 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 { + // 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; + + // The associated display orientation and width and height set by configureSurfaceLocked(). + int32_t associatedDisplayOrientation; + int32_t associatedDisplayWidth, associatedDisplayHeight; + + // Translation and scaling factors, orientation-independent. + float xScale; + float xPrecision; + + 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 suppressSwipeOntoVirtualKeys(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); +}; + + +class JoystickInputMapper : public InputMapper { +public: + JoystickInputMapper(InputDevice* device); + virtual ~JoystickInputMapper(); + + 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); + +private: + struct Axis { + RawAbsoluteAxisInfo rawAxisInfo; + AxisInfo axisInfo; + + bool explicitlyMapped; // true if the axis was explicitly assigned an axis id + + float scale; // scale factor from raw to normalized values + float offset; // offset to add after scaling for normalization + float highScale; // scale factor from raw to normalized values of high split + float highOffset; // offset to add after scaling for normalization of high split + + float min; // normalized inclusive minimum + float max; // normalized inclusive maximum + float flat; // normalized flat region size + float fuzz; // normalized error tolerance + + float filter; // filter out small variations of this size + float currentValue; // current value + float newValue; // most recent value + float highCurrentValue; // current value of high split + float highNewValue; // most recent value of high split + + void initialize(const RawAbsoluteAxisInfo& rawAxisInfo, const AxisInfo& axisInfo, + bool explicitlyMapped, float scale, float offset, + float highScale, float highOffset, + float min, float max, float flat, float fuzz) { + this->rawAxisInfo = rawAxisInfo; + this->axisInfo = axisInfo; + this->explicitlyMapped = explicitlyMapped; + this->scale = scale; + this->offset = offset; + this->highScale = highScale; + this->highOffset = highOffset; + this->min = min; + this->max = max; + this->flat = flat; + this->fuzz = fuzz; + this->filter = 0; + resetValue(); + } + + void resetValue() { + this->currentValue = 0; + this->newValue = 0; + this->highCurrentValue = 0; + this->highNewValue = 0; + } + }; + + // Axes indexed by raw ABS_* axis index. + KeyedVector<int32_t, Axis> mAxes; + + void sync(nsecs_t when, bool force); + + bool haveAxis(int32_t axisId); + void pruneAxes(bool ignoreExplicitlyMappedAxes); + bool filterAxes(bool force); + + static bool hasValueChangedSignificantly(float filter, + float newValue, float currentValue, float min, float max); + static bool hasMovedNearerToValueWithinFilteredRange(float filter, + float newValue, float currentValue, float thresholdValue); + + static bool isCenteredAxis(int32_t axis); +}; + +} // namespace android + +#endif // _UI_INPUT_READER_H diff --git a/services/input/InputWindow.cpp b/services/input/InputWindow.cpp new file mode 100644 index 000000000000..b552f6d5b43d --- /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 000000000000..9c43067363a9 --- /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 000000000000..a4ee295a73b1 --- /dev/null +++ b/services/input/PointerController.cpp @@ -0,0 +1,530 @@ +/* + * 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 --- + +// Time to wait before starting the fade when the pointer is inactive. +static const nsecs_t INACTIVITY_FADE_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds +static const nsecs_t INACTIVITY_FADE_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds + +// Time to spend fading out the pointer completely. +static const nsecs_t FADE_DURATION = 500 * 1000000LL; // 500 ms + +// Time to wait between frames. +static const nsecs_t FADE_FRAME_INTERVAL = 1000000000LL / 60; + +// Amount to subtract from alpha per frame. +static const float FADE_DECAY_PER_FRAME = float(FADE_FRAME_INTERVAL) / FADE_DURATION; + + +PointerController::PointerController(const sp<Looper>& looper, int32_t pointerLayer) : + mLooper(looper), 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.fadeAlpha = 1; + mLocked.inactivityFadeDelay = INACTIVITY_FADE_DELAY_NORMAL; + + mLocked.wantVisible = false; + mLocked.visible = false; + mLocked.drawn = false; + + mHandler = new WeakMessageHandler(this); +} + +PointerController::~PointerController() { + mLooper->removeMessages(mHandler); + + 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 - 1; + *outMaxY = mLocked.displayWidth - 1; + break; + default: + *outMaxX = mLocked.displayWidth - 1; + *outMaxY = mLocked.displayHeight - 1; + 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; + unfadeBeforeUpdateLocked(); + 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; + } + unfadeBeforeUpdateLocked(); + updateLocked(); + } +} + +void PointerController::getPosition(float* outX, float* outY) const { + AutoMutex _l(mLock); + + *outX = mLocked.pointerX; + *outY = mLocked.pointerY; +} + +void PointerController::fade() { + AutoMutex _l(mLock); + + startFadeLocked(); +} + +void PointerController::unfade() { + AutoMutex _l(mLock); + + if (unfadeBeforeUpdateLocked()) { + updateLocked(); + } +} + +void PointerController::setInactivityFadeDelay(InactivityFadeDelay inactivityFadeDelay) { + AutoMutex _l(mLock); + + if (mLocked.inactivityFadeDelay != inactivityFadeDelay) { + mLocked.inactivityFadeDelay = inactivityFadeDelay; + startInactivityFadeDelayLocked(); + } +} + +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; + } + + status = mSurfaceControl->setAlpha(mLocked.fadeAlpha); + if (status) { + LOGE("Error %d setting pointer surface alpha.", 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) { + // Apply offsets to convert from the pixel top-left corner position to the pixel center. + // This creates an invariant frame of reference that we can easily rotate when + // taking into account that the pointer may be located at fractional pixel offsets. + float x = mLocked.pointerX + 0.5f; + float y = mLocked.pointerY + 0.5f; + float temp; + + // Undo the previous rotation. + switch (mLocked.displayOrientation) { + case DISPLAY_ORIENTATION_90: + temp = x; + x = mLocked.displayWidth - y; + y = temp; + break; + case DISPLAY_ORIENTATION_180: + x = mLocked.displayWidth - x; + y = mLocked.displayHeight - y; + break; + case DISPLAY_ORIENTATION_270: + temp = x; + x = y; + y = mLocked.displayHeight - temp; + break; + } + + // Perform the new rotation. + switch (orientation) { + case DISPLAY_ORIENTATION_90: + temp = x; + x = y; + y = mLocked.displayWidth - x; + break; + case DISPLAY_ORIENTATION_180: + x = mLocked.displayWidth - x; + y = mLocked.displayHeight - y; + break; + case DISPLAY_ORIENTATION_270: + temp = x; + x = mLocked.displayHeight - y; + y = temp; + break; + } + + // Apply offsets to convert from the pixel center to the pixel top-left corner position + // and save the results. + mLocked.pointerX = x - 0.5f; + mLocked.pointerY = y - 0.5f; + 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; +} + +void PointerController::handleMessage(const Message& message) { + switch (message.what) { + case MSG_FADE_STEP: { + AutoMutex _l(mLock); + fadeStepLocked(); + break; + } + } +} + +bool PointerController::unfadeBeforeUpdateLocked() { + sendFadeStepMessageDelayedLocked(getInactivityFadeDelayTimeLocked()); + + if (isFadingLocked()) { + mLocked.wantVisible = true; + mLocked.fadeAlpha = 1; + return true; // update required to effect the unfade + } + return false; // update not required +} + +void PointerController::startFadeLocked() { + if (!isFadingLocked()) { + sendFadeStepMessageDelayedLocked(0); + } +} + +void PointerController::startInactivityFadeDelayLocked() { + if (!isFadingLocked()) { + sendFadeStepMessageDelayedLocked(getInactivityFadeDelayTimeLocked()); + } +} + +void PointerController::fadeStepLocked() { + if (mLocked.wantVisible) { + mLocked.fadeAlpha -= FADE_DECAY_PER_FRAME; + if (mLocked.fadeAlpha < 0) { + mLocked.fadeAlpha = 0; + mLocked.wantVisible = false; + } else { + sendFadeStepMessageDelayedLocked(FADE_FRAME_INTERVAL); + } + updateLocked(); + } +} + +bool PointerController::isFadingLocked() { + return !mLocked.wantVisible || mLocked.fadeAlpha != 1; +} + +nsecs_t PointerController::getInactivityFadeDelayTimeLocked() { + return mLocked.inactivityFadeDelay == INACTIVITY_FADE_DELAY_SHORT + ? INACTIVITY_FADE_DELAY_TIME_SHORT : INACTIVITY_FADE_DELAY_TIME_NORMAL; +} + +void PointerController::sendFadeStepMessageDelayedLocked(nsecs_t delayTime) { + mLooper->removeMessages(mHandler, MSG_FADE_STEP); + mLooper->sendMessageDelayed(delayTime, mHandler, Message(MSG_FADE_STEP)); +} + +} // namespace android diff --git a/services/input/PointerController.h b/services/input/PointerController.h new file mode 100644 index 000000000000..e1dab5c78313 --- /dev/null +++ b/services/input/PointerController.h @@ -0,0 +1,160 @@ +/* + * 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 <ui/Input.h> +#include <utils/RefBase.h> +#include <utils/Looper.h> +#include <utils/String8.h> + +#include <surfaceflinger/Surface.h> +#include <surfaceflinger/SurfaceComposerClient.h> +#include <surfaceflinger/ISurfaceComposer.h> + +#include <SkBitmap.h> + +namespace android { + +/** + * 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; + + /* Fades the pointer out now. */ + virtual void fade() = 0; + + /* Makes the pointer visible if it has faded out. */ + virtual void unfade() = 0; +}; + + +/* + * Tracks pointer movements and draws the pointer sprite to a surface. + * + * Handles pointer acceleration and animation. + */ +class PointerController : public PointerControllerInterface, public MessageHandler { +protected: + virtual ~PointerController(); + +public: + enum InactivityFadeDelay { + INACTIVITY_FADE_DELAY_NORMAL = 0, + INACTIVITY_FADE_DELAY_SHORT = 1, + }; + + PointerController(const sp<Looper>& looper, 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; + virtual void fade(); + virtual void unfade(); + + void setDisplaySize(int32_t width, int32_t height); + void setDisplayOrientation(int32_t orientation); + void setPointerIcon(const SkBitmap* bitmap, float hotSpotX, float hotSpotY); + void setInactivityFadeDelay(InactivityFadeDelay inactivityFadeDelay); + +private: + enum { + MSG_FADE_STEP = 0, + }; + + mutable Mutex mLock; + + sp<Looper> mLooper; + 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; + + float fadeAlpha; + InactivityFadeDelay inactivityFadeDelay; + + bool wantVisible; + bool visible; + bool drawn; + } mLocked; + + sp<WeakMessageHandler> mHandler; + + 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); + + void handleMessage(const Message& message); + bool unfadeBeforeUpdateLocked(); + void startFadeLocked(); + void startInactivityFadeDelayLocked(); + void fadeStepLocked(); + bool isFadingLocked(); + nsecs_t getInactivityFadeDelayTimeLocked(); + void sendFadeStepMessageDelayedLocked(nsecs_t delayTime); +}; + +} // 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 000000000000..799eb7689060 --- /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 000000000000..2f846c47ba3c --- /dev/null +++ b/services/input/tests/InputDispatcher_test.cpp @@ -0,0 +1,242 @@ +/* + * 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 "../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 interceptMotionBeforeQueueing(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 000000000000..32982c4f6569 --- /dev/null +++ b/services/input/tests/InputReader_test.cpp @@ -0,0 +1,3761 @@ +/* + * 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 "../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; + } + + virtual void fade() { + } + + virtual void unfade() { + } +}; + + +// --- 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 { + NotifyConfigurationChangedArgs() : eventTime(0) { } + + 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> absoluteAxes; + KeyedVector<int, bool> relativeAxes; + 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 addAbsoluteAxis(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->absoluteAxes.add(axis, info); + } + + void addRelativeAxis(int32_t deviceId, int32_t axis) { + Device* device = getDevice(deviceId); + device->relativeAxes.add(axis, true); + } + + 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->absoluteAxes.indexOfKey(axis); + if (index >= 0) { + *outAxisInfo = device->absoluteAxes.valueAt(index); + return OK; + } + } + return -1; + } + + virtual bool hasRelativeAxis(int32_t deviceId, int axis) const { + Device* device = getDevice(deviceId); + if (device) { + return device->relativeAxes.indexOfKey(axis) >= 0; + } + return false; + } + + virtual status_t mapKey(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 status_t mapAxis(int32_t deviceId, int scancode, + AxisInfo* outAxisInfo) const { + 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 bool isExternal(int32_t deviceId) const { + return false; + } + + 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; + } + + virtual void fadePointer() { + } +}; + + +// --- 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; + } + + virtual void fadePointer() { + } +}; + + +// --- 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), + mNextDevice(NULL) { + } + + 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 axis, uint32_t source, float min, float max, float flat, float fuzz) { + const InputDeviceInfo::MotionRange* range = info.getMotionRange(axis, source); + ASSERT_TRUE(range != NULL) << "Axis: " << axis << " Source: " << source; + ASSERT_EQ(axis, range->axis) << "Axis: " << axis << " Source: " << source; + ASSERT_EQ(source, range->source) << "Axis: " << axis << " Source: " << source; + ASSERT_NEAR(min, range->min, EPSILON) << "Axis: " << axis << " Source: " << source; + ASSERT_NEAR(max, range->max, EPSILON) << "Axis: " << axis << " Source: " << source; + ASSERT_NEAR(flat, range->flat, EPSILON) << "Axis: " << axis << " Source: " << source; + ASSERT_NEAR(fuzz, range->fuzz, EPSILON) << "Axis: " << axis << " Source: " << source; + } + + 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.getAxisValue(AMOTION_EVENT_AXIS_X), 1); + ASSERT_NEAR(y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + ASSERT_NEAR(pressure, coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), EPSILON); + ASSERT_NEAR(size, coords.getAxisValue(AMOTION_EVENT_AXIS_SIZE), EPSILON); + ASSERT_NEAR(touchMajor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), 1); + ASSERT_NEAR(touchMinor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), 1); + ASSERT_NEAR(toolMajor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), 1); + ASSERT_NEAR(toolMinor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), 1); + ASSERT_NEAR(orientation, coords.getAxisValue(AMOTION_EVENT_AXIS_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, AINPUT_SOURCE_MOUSE)); + ASSERT_EQ(NULL, info.getMotionRange(AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, + AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_MOUSE, 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 - 1, 480 - 1); + + InputDeviceInfo info2; + mapper->populateDeviceInfo(&info2); + + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, + AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE, + 1, 800 - 1, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, + AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE, + 2, 480 - 1, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, + AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_MOUSE, + 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, AINPUT_SOURCE_TRACKBALL, + -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, + AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_TRACKBALL, + -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, + AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_TRACKBALL, + 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_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 = 1019; +const int32_t TouchInputMapperTest::RAW_Y_MIN = 30; +const int32_t TouchInputMapperTest::RAW_Y_MAX = 1009; +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 + 1) / DISPLAY_WIDTH; +const float TouchInputMapperTest::Y_PRECISION = float(RAW_Y_MAX - RAW_Y_MIN + 1) / 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 + 1) / DISPLAY_WIDTH + RAW_X_MIN); +} + +int32_t TouchInputMapperTest::toRawY(float displayY) { + return int32_t(displayY * (RAW_Y_MAX - RAW_Y_MIN + 1) / 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 + 1); +} + +float TouchInputMapperTest::toDisplayY(int32_t rawY) { + return float(rawY - RAW_Y_MIN) * DISPLAY_HEIGHT / (RAW_Y_MAX - RAW_Y_MIN + 1); +} + + +// --- 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->addAbsoluteAxis(DEVICE_ID, ABS_X, + RAW_X_MIN, RAW_X_MAX, 0, 0); + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_Y, + RAW_Y_MIN, RAW_Y_MAX, 0, 0); + } + if (axes & PRESSURE) { + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_PRESSURE, + RAW_PRESSURE_MIN, RAW_PRESSURE_MAX, 0, 0); + } + if (axes & TOOL) { + mFakeEventHub->addAbsoluteAxis(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].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_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].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled()); + + // Rotation 90. + prepareDisplay(DISPLAY_ORIENTATION_90); + processDown(mapper, RAW_X_MAX - toRawX(75) + RAW_X_MIN, toRawY(50)); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled()); + + // Rotation 180. + prepareDisplay(DISPLAY_ORIENTATION_180); + processDown(mapper, RAW_X_MAX - toRawX(50) + RAW_X_MIN, RAW_Y_MAX - toRawY(75) + RAW_Y_MIN); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled()); + + // Rotation 270. + prepareDisplay(DISPLAY_ORIENTATION_270); + processDown(mapper, toRawX(75), RAW_Y_MAX - toRawY(50) + RAW_Y_MIN); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); + ASSERT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_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->addAbsoluteAxis(DEVICE_ID, ABS_MT_POSITION_X, + RAW_X_MIN, RAW_X_MAX, 0, 0); + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_POSITION_Y, + RAW_Y_MIN, RAW_Y_MAX, 0, 0); + } + if (axes & TOUCH) { + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_TOUCH_MAJOR, + RAW_TOUCH_MIN, RAW_TOUCH_MAX, 0, 0); + if (axes & MINOR) { + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_TOUCH_MINOR, + RAW_TOUCH_MIN, RAW_TOUCH_MAX, 0, 0); + } + } + if (axes & TOOL) { + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_WIDTH_MAJOR, + RAW_TOOL_MIN, RAW_TOOL_MAX, 0, 0); + if (axes & MINOR) { + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_WIDTH_MINOR, + RAW_TOOL_MAX, RAW_TOOL_MAX, 0, 0); + } + } + if (axes & ORIENTATION) { + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_ORIENTATION, + RAW_ORIENTATION_MIN, RAW_ORIENTATION_MAX, 0, 0); + } + if (axes & PRESSURE) { + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_PRESSURE, + RAW_PRESSURE_MIN, RAW_PRESSURE_MAX, 0, 0); + } + if (axes & ID) { + mFakeEventHub->addAbsoluteAxis(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 + 1), + float(DISPLAY_HEIGHT) / (RAW_Y_MAX - RAW_Y_MIN + 1)); + 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 87de79affe3f..04ae4901d74b 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 4931cc7bd98d..8c07e155847a 100644 --- a/services/java/com/android/server/AlarmManagerService.java +++ b/services/java/com/android/server/AlarmManagerService.java @@ -63,7 +63,10 @@ class AlarmManagerService extends IAlarmManager.Stub { private static final int ELAPSED_REALTIME_WAKEUP_MASK = 1 << AlarmManager.ELAPSED_REALTIME_WAKEUP; private static final int ELAPSED_REALTIME_MASK = 1 << AlarmManager.ELAPSED_REALTIME; private static final int TIME_CHANGED_MASK = 1 << 16; - + + // Alignment quantum for inexact repeating alarms + private static final long QUANTUM = AlarmManager.INTERVAL_FIFTEEN_MINUTES; + private static final String TAG = "AlarmManager"; private static final String ClockReceiver_TAG = "ClockReceiver"; private static final boolean localLOGV = false; @@ -83,17 +86,6 @@ class AlarmManagerService extends IAlarmManager.Stub { private final ArrayList<Alarm> mElapsedRealtimeAlarms = new ArrayList<Alarm>(); private final IncreasingTimeOrder mIncreasingTimeOrder = new IncreasingTimeOrder(); - // slots corresponding with the inexact-repeat interval buckets, - // ordered from shortest to longest - private static final long sInexactSlotIntervals[] = { - AlarmManager.INTERVAL_FIFTEEN_MINUTES, - AlarmManager.INTERVAL_HALF_HOUR, - AlarmManager.INTERVAL_HOUR, - AlarmManager.INTERVAL_HALF_DAY, - AlarmManager.INTERVAL_DAY - }; - private long mInexactDeliveryTimes[] = { 0, 0, 0, 0, 0}; - private int mDescriptor; private int mBroadcastRefCount = 0; private PowerManager.WakeLock mWakeLock; @@ -199,58 +191,40 @@ class AlarmManagerService extends IAlarmManager.Stub { return; } - // find the slot in the delivery-times array that we will use - int intervalSlot; - for (intervalSlot = 0; intervalSlot < sInexactSlotIntervals.length; intervalSlot++) { - if (sInexactSlotIntervals[intervalSlot] == interval) { - break; - } + if (interval <= 0) { + Slog.w(TAG, "setInexactRepeating ignored because interval " + interval + + " is invalid"); + return; } - - // Non-bucket intervals just fall back to the less-efficient - // unbucketed recurring alarm implementation - if (intervalSlot >= sInexactSlotIntervals.length) { + + // If the requested interval isn't a multiple of 15 minutes, just treat it as exact + if (interval % QUANTUM != 0) { + if (localLOGV) Slog.v(TAG, "Interval " + interval + " not a quantum multiple"); setRepeating(type, triggerAtTime, interval, operation); return; } - // Align bucketed alarm deliveries by trying to match - // the shortest-interval bucket already scheduled - long bucketTime = 0; - for (int slot = 0; slot < mInexactDeliveryTimes.length; slot++) { - if (mInexactDeliveryTimes[slot] > 0) { - bucketTime = mInexactDeliveryTimes[slot]; - break; - } - } - - if (bucketTime == 0) { - // If nothing is scheduled yet, just start at the requested time - bucketTime = triggerAtTime; - } else { - // Align the new alarm with the existing bucketed sequence. To achieve - // alignment, we slide the start time around by min{interval, slot interval} - long adjustment = (interval <= sInexactSlotIntervals[intervalSlot]) - ? interval : sInexactSlotIntervals[intervalSlot]; - - // The bucket may have started in the past; adjust - while (bucketTime < triggerAtTime) { - bucketTime += adjustment; - } + // Translate times into the ELAPSED timebase for alignment purposes so that + // alignment never tries to match against wall clock times. + final boolean isRtc = (type == AlarmManager.RTC || type == AlarmManager.RTC_WAKEUP); + final long skew = (isRtc) + ? System.currentTimeMillis() - SystemClock.elapsedRealtime() + : 0; - // Or the bucket may be set to start more than an interval beyond - // our requested trigger time; pull it back to meet our needs - while (bucketTime > triggerAtTime + adjustment) { - bucketTime -= adjustment; - } + // Slip forward to the next ELAPSED-timebase quantum after the stated time. If + // we're *at* a quantum point, leave it alone. + final long adjustedTriggerTime; + long offset = (triggerAtTime - skew) % QUANTUM; + if (offset != 0) { + adjustedTriggerTime = triggerAtTime - offset + QUANTUM; + } else { + adjustedTriggerTime = triggerAtTime; } - // Remember where this bucket started (reducing the amount of later - // fixup required) and set the alarm with the new, bucketed start time. - if (localLOGV) Slog.v(TAG, "setInexactRepeating: interval=" + interval - + " bucketTime=" + bucketTime); - mInexactDeliveryTimes[intervalSlot] = bucketTime; - setRepeating(type, bucketTime, interval, operation); + // Set the alarm based on the quantum-aligned start time + if (localLOGV) Slog.v(TAG, "setInexactRepeating: type=" + type + " interval=" + interval + + " trigger=" + adjustedTriggerTime + " orig=" + triggerAtTime); + setRepeating(type, adjustedTriggerTime, interval, operation); } public void setTime(long millis) { @@ -661,7 +635,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 731fb22e2685..a4a95a0ace90 100644 --- a/services/java/com/android/server/AppWidgetService.java +++ b/services/java/com/android/server/AppWidgetService.java @@ -16,6 +16,23 @@ 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.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 +41,39 @@ 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.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 android.widget.RemoteViewsService; -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; +import com.android.internal.widget.IRemoteViewsFactory; class AppWidgetService extends IAppWidgetService.Stub { @@ -107,6 +117,49 @@ 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 (Exception e) { + e.printStackTrace(); + } + } + public void onServiceDisconnected(ComponentName name) { + disconnect(); + } + public void disconnect() { + final IRemoteViewsAdapterConnection cb = + IRemoteViewsAdapterConnection.Stub.asInterface(mConnectionCb); + try { + cb.onServiceDisconnected(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + // Manages active connections to RemoteViewsServices + private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection> + mBoundRemoteViewsServices = new HashMap<Pair<Integer,FilterComparison>,ServiceConnection>(); + // Manages persistent references to RemoteViewsServices from different App Widgets + private final HashMap<FilterComparison, HashSet<Integer>> + mRemoteViewsServicesAppWidgets = new HashMap<FilterComparison, HashSet<Integer>>(); + Context mContext; Locale mLocale; PackageManager mPackageManager; @@ -144,6 +197,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 +347,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 +432,167 @@ class AppWidgetService extends IAppWidgetService.Stub } } + // Binds to a specific RemoteViewsService + 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; + FilterComparison fc = new FilterComparison(intent); + Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, fc); + 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); + } + + // Add it to the mapping of RemoteViewsService to appWidgetIds so that we can determine + // when we can call back to the RemoteViewsService later to destroy associated + // factories. + incrementAppWidgetServiceRefCount(appWidgetId, fc); + } + } + + // Unbinds from a specific RemoteViewsService + 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"); + } + } + } + + // Unbinds from a RemoteViewsService when we delete an app widget + 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(); + } + } + + // Check if we need to destroy any services (if no other app widgets are + // referencing the same service) + decrementAppWidgetServiceRefCount(appWidgetId); + } + + // Destroys the cached factory on the RemoteViewsService's side related to the specified intent + private void destroyRemoteViewsService(final Intent intent) { + final ServiceConnection conn = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + final IRemoteViewsFactory cb = + IRemoteViewsFactory.Stub.asInterface(service); + try { + cb.onDestroy(intent); + } catch (Exception e) { + e.printStackTrace(); + } + mContext.unbindService(this); + } + @Override + public void onServiceDisconnected(android.content.ComponentName name) { + // Do nothing + } + }; + + // Bind to the service and remove the static intent->factory mapping in the + // RemoteViewsService. + final long token = Binder.clearCallingIdentity(); + try { + mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // Adds to the ref-count for a given RemoteViewsService intent + private void incrementAppWidgetServiceRefCount(int appWidgetId, FilterComparison fc) { + HashSet<Integer> appWidgetIds = null; + if (mRemoteViewsServicesAppWidgets.containsKey(fc)) { + appWidgetIds = mRemoteViewsServicesAppWidgets.get(fc); + } else { + appWidgetIds = new HashSet<Integer>(); + mRemoteViewsServicesAppWidgets.put(fc, appWidgetIds); + } + appWidgetIds.add(appWidgetId); + } + + // Subtracts from the ref-count for a given RemoteViewsService intent, prompting a delete if + // the ref-count reaches zero. + private void decrementAppWidgetServiceRefCount(int appWidgetId) { + Iterator<FilterComparison> it = + mRemoteViewsServicesAppWidgets.keySet().iterator(); + while (it.hasNext()) { + final FilterComparison key = it.next(); + final HashSet<Integer> ids = mRemoteViewsServicesAppWidgets.get(key); + if (ids.remove(appWidgetId)) { + // If we have removed the last app widget referencing this service, then we + // should destroy it and remove it from this set + if (ids.isEmpty()) { + destroyRemoteViewsService(key.getIntent()); + it.remove(); + } + } + } + } + public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) { synchronized (mAppWidgetIds) { AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); @@ -426,6 +644,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 +695,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 +721,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 +861,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) { @@ -690,15 +973,15 @@ class AppWidgetService extends IAppWidgetService.Stub + "AppWidget provider '" + component + '\''); return null; } - + AttributeSet attrs = Xml.asAttributeSet(parser); - + int type; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { // drain whitespace, comments, etc. } - + String nodeName = parser.getName(); if (!"appwidget-provider".equals(nodeName)) { Slog.w(TAG, "Meta-data does not start with appwidget-provider tag for" @@ -718,10 +1001,10 @@ class AppWidgetService extends IAppWidgetService.Stub Resources res = mPackageManager.getResourcesForApplication( activityInfo.applicationInfo); - + TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AppWidgetProviderInfo); - + // These dimensions has to be resolved in the application's context. // We simply send back the raw complex data, which will be // converted to dp in {@link AppWidgetManager#getAppWidgetInfo}. @@ -730,7 +1013,7 @@ class AppWidgetService extends IAppWidgetService.Stub info.minWidth = value != null ? value.data : 0; value = sa.peekValue(com.android.internal.R.styleable.AppWidgetProviderInfo_minHeight); info.minHeight = value != null ? value.data : 0; - + info.updatePeriodMillis = sa.getInt( com.android.internal.R.styleable.AppWidgetProviderInfo_updatePeriodMillis, 0); info.initialLayout = sa.getResourceId( @@ -742,6 +1025,14 @@ 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); + info.resizeMode = sa.getInt( + com.android.internal.R.styleable.AppWidgetProviderInfo_resizeMode, + AppWidgetProviderInfo.RESIZE_NONE); + sa.recycle(); } catch (Exception e) { // Ok to catch Exception here, because anything going wrong because @@ -1096,6 +1387,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 +1406,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 f1e226eed8ad..ea38fbbeea41 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@"; @@ -378,6 +379,10 @@ class BackupManagerService extends IBackupManager.Stub { } } + // Done: reset the session timeout clock + removeMessages(MSG_RESTORE_TIMEOUT); + sendEmptyMessageDelayed(MSG_RESTORE_TIMEOUT, TIMEOUT_RESTORE_INTERVAL); + mWakelock.release(); } break; @@ -396,6 +401,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 +610,7 @@ class BackupManagerService extends IBackupManager.Stub { } } } + tf.close(); } catch (FileNotFoundException fnf) { // Probably innocuous Slog.v(TAG, "No ancestral data"); @@ -912,42 +933,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 +1024,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 +1395,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 +1867,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 +2093,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 +2390,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 +2552,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 +2587,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 +2602,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 +2633,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 +2668,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 +2728,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 +2794,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 fc4e06f3b4e1..47599c832262 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 aa8cdedcc66b..062ab74ff406 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 97fd8046f1e4..f170cb7358e2 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -16,28 +16,31 @@ package com.android.server; -import android.app.Notification; -import android.app.NotificationManager; -import android.content.Context; +import android.bluetooth.BluetoothTetheringDataTracker; import android.content.ContentResolver; -import android.content.ContextWrapper; +import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.content.res.Resources.NotFoundException; +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.net.wimax.WimaxManagerConstants; 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; @@ -45,29 +48,28 @@ import android.provider.Settings; import android.text.TextUtils; import android.util.EventLog; import android.util.Slog; + import com.android.internal.telephony.Phone; import com.android.server.connectivity.Tethering; -import dalvik.system.DexClassLoader; + import java.io.FileDescriptor; +import java.io.FileWriter; +import java.io.IOException; import java.io.PrintWriter; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.InvocationTargetException; +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 @@ -76,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; @@ -93,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 @@ -111,6 +114,9 @@ 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; @@ -169,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 @@ -178,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. @@ -217,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; - } - - @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 synchronized ConnectivityService getInstance(Context context) { + if (sServiceInstance == null) { + sServiceInstance = new ConnectivityService(context); } - } - - 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 = NetworkUtils.numericToInetAddress(dns); + } catch (IllegalArgumentException 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(); @@ -287,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; } @@ -304,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; } @@ -366,37 +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_WIMAX: - NetworkStateTracker nst = makeWimaxStateTracker(); - if (nst != null) { - nst.startMonitoring(); - } - mNetTrackers[netType] = nst; + 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; } @@ -406,103 +431,23 @@ 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(); } - } - - private NetworkStateTracker makeWimaxStateTracker() { - //Initialize Wimax - DexClassLoader wimaxClassLoader; - Class wimaxStateTrackerClass = null; - Class wimaxServiceClass = null; - Class wimaxManagerClass; - String wimaxJarLocation; - String wimaxLibLocation; - String wimaxManagerClassName; - String wimaxServiceClassName; - String wimaxStateTrackerClassName; - - NetworkStateTracker wimaxStateTracker = null; - boolean isWimaxEnabled = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_wimaxEnabled); - - if (isWimaxEnabled) { - try { - wimaxJarLocation = mContext.getResources().getString( - com.android.internal.R.string.config_wimaxServiceJarLocation); - wimaxLibLocation = mContext.getResources().getString( - com.android.internal.R.string.config_wimaxNativeLibLocation); - wimaxManagerClassName = mContext.getResources().getString( - com.android.internal.R.string.config_wimaxManagerClassname); - wimaxServiceClassName = mContext.getResources().getString( - com.android.internal.R.string.config_wimaxServiceClassname); - wimaxStateTrackerClassName = mContext.getResources().getString( - com.android.internal.R.string.config_wimaxStateTrackerClassname); - - wimaxClassLoader = new DexClassLoader(wimaxJarLocation, - new ContextWrapper(mContext).getCacheDir().getAbsolutePath(), - wimaxLibLocation,ClassLoader.getSystemClassLoader()); - - try { - wimaxManagerClass = wimaxClassLoader.loadClass(wimaxManagerClassName); - wimaxStateTrackerClass = wimaxClassLoader.loadClass(wimaxStateTrackerClassName); - wimaxServiceClass = wimaxClassLoader.loadClass(wimaxServiceClassName); - } catch (ClassNotFoundException ex) { - ex.printStackTrace(); - return null; - } - } catch(Resources.NotFoundException ex) { - Slog.e(TAG, "Wimax Resources does not exist!!! "); - return null; - } + mSettingsObserver = new SettingsObserver(mHandler, EVENT_APPLY_GLOBAL_HTTP_PROXY); + mSettingsObserver.observe(mContext); - try { - Slog.v(TAG, "Starting Wimax Service... "); - - Constructor wmxStTrkrConst = wimaxStateTrackerClass.getConstructor - (new Class[] {Context.class,Handler.class}); - wimaxStateTracker = (NetworkStateTracker)wmxStTrkrConst.newInstance(mContext,mHandler); - - Constructor wmxSrvConst = wimaxServiceClass.getDeclaredConstructor - (new Class[] {Context.class,wimaxStateTrackerClass}); - wmxSrvConst.setAccessible(true); - IBinder svcInvoker = (IBinder) wmxSrvConst.newInstance(mContext,wimaxStateTracker); - wmxSrvConst.setAccessible(false); - - ServiceManager.addService(WimaxManagerConstants.WIMAX_SERVICE, svcInvoker); - - } catch(ClassCastException ex) { - ex.printStackTrace(); - return null; - } catch (NoSuchMethodException ex) { - ex.printStackTrace(); - return null; - } catch (InstantiationException ex) { - ex.printStackTrace(); - return null; - } catch(IllegalAccessException ex) { - ex.printStackTrace(); - return null; - } catch(InvocationTargetException ex) { - ex.printStackTrace(); - return null; - } catch(Exception ex) { - ex.printStackTrace(); - return null; - } - } else { - Slog.e(TAG, "Wimax is not enabled or not added to the network attributes!!! "); - return null; - } + loadGlobalProxy(); - return wimaxStateTracker; + VpnManager.startVpnService(context); } + /** * Sets the preferred network. * @param preference the new preference @@ -567,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]); @@ -601,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; } } @@ -630,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(); @@ -684,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); @@ -707,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) || @@ -721,31 +696,26 @@ 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)) { - usedNetworkType = ConnectivityManager.TYPE_MOBILE_DUN; - } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) { - usedNetworkType = ConnectivityManager.TYPE_MOBILE_HIPRI; + usedNetworkType = convertFeatureToNetworkType(feature); + if (usedNetworkType < 0) { + Slog.e(TAG, "Can't match any netTracker!"); + usedNetworkType = networkType; } } 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) { @@ -764,28 +734,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; @@ -817,7 +788,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; } } @@ -832,7 +803,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); } @@ -845,7 +816,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(); @@ -863,7 +834,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; } } @@ -872,19 +843,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { // TODO - move to MobileDataStateTracker int usedNetworkType = networkType; if (networkType == ConnectivityManager.TYPE_MOBILE) { - 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)) { - usedNetworkType = ConnectivityManager.TYPE_MOBILE_DUN; - } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) { - usedNetworkType = ConnectivityManager.TYPE_MOBILE_HIPRI; + usedNetworkType = convertFeatureToNetworkType(feature); + if (usedNetworkType < 0) { + usedNetworkType = networkType; } } 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) { @@ -892,20 +858,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; } } @@ -949,16 +916,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; } } @@ -967,8 +960,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(); } /** @@ -979,28 +971,31 @@ 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)); } private void handleSetBackgroundData(boolean enabled) { - if (enabled != getBackgroundDataSetting()) { - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.BACKGROUND_DATA, enabled ? 1 : 0); - Intent broadcast = new Intent( - ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED); - mContext.sendBroadcast(broadcast); - } + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.BACKGROUND_DATA, enabled ? 1 : 0); + Intent broadcast = new Intent( + ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED); + mContext.sendBroadcast(broadcast); } /** * @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; } @@ -1009,47 +1004,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() { @@ -1077,6 +1044,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 @@ -1146,32 +1119,46 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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. */ if (mNetAttributes[prevNetType].isDefault()) { if (mActiveDefaultNetwork == prevNetType) { mActiveDefaultNetwork = -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; - } + 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) Slog.d(TAG, "Attempting to switch to " + checkInfo.getTypeName()); + if (DBG) log("Attempting to switch to " + checkInfo.getTypeName()); } } } @@ -1218,7 +1205,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); @@ -1276,6 +1263,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) { @@ -1294,24 +1283,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; @@ -1325,36 +1325,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. */ @@ -1367,19 +1345,164 @@ 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(); + if (TextUtils.isEmpty(interfaceName)) return; + for (InetAddress gateway : p.getGateways()) { + + if (NetworkUtils.addHostRoute(interfaceName, gateway, null) && + NetworkUtils.addDefaultRoute(interfaceName, gateway)) { + if (DBG) { + NetworkInfo networkInfo = nt.getNetworkInfo(); + log("addDefaultRoute for " + networkInfo.getTypeName() + + " (" + interfaceName + "), GatewayAddr=" + gateway.getHostAddress()); + } + } + } + } + + + 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) { + if (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. @@ -1387,7 +1510,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; @@ -1395,12 +1518,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(); } @@ -1422,13 +1547,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() { @@ -1444,27 +1574,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) { - Slog.d(TAG, "adding dns " + dns + " for " + + 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) { + 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 { @@ -1472,11 +1634,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() { @@ -1531,6 +1693,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) { @@ -1544,6 +1713,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; @@ -1562,7 +1735,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()); } @@ -1570,7 +1743,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()); @@ -1605,29 +1778,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; @@ -1665,6 +1832,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { handleSetMobileData(enabled); break; } + case EVENT_APPLY_GLOBAL_HTTP_PROXY: + { + handleDeprecatedGlobalHttpProxy(); + } } } } @@ -1721,6 +1892,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() { @@ -1749,9 +1929,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"); @@ -1773,22 +1972,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(), @@ -1803,37 +2002,190 @@ 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.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + 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); + } + int convertFeatureToNetworkType(String feature){ + int networkType = -1; + if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) { + networkType = ConnectivityManager.TYPE_MOBILE_MMS; + } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_SUPL)) { + networkType = ConnectivityManager.TYPE_MOBILE_SUPL; + } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN) || + TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN_ALWAYS)) { + networkType = ConnectivityManager.TYPE_MOBILE_DUN; + } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) { + networkType = ConnectivityManager.TYPE_MOBILE_HIPRI; + } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_FOTA)) { + networkType = ConnectivityManager.TYPE_MOBILE_FOTA; + } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_IMS)) { + networkType = ConnectivityManager.TYPE_MOBILE_IMS; + } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_CBS)) { + networkType = ConnectivityManager.TYPE_MOBILE_CBS; + } + return networkType; + } } diff --git a/services/java/com/android/server/CountryDetectorService.java b/services/java/com/android/server/CountryDetectorService.java new file mode 100644 index 000000000000..3081ebefd05a --- /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 277985f1456b..000000000000 --- 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 1538003450bc..df2cd1b9572a 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 { - static final String TAG = "DevicePolicyManagerService"; - + private static final String TAG = "DevicePolicyManagerService"; + + private static final int REQUEST_EXPIRE_PASSWORD = 5571; + + 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 0b1a4a3ee314..0fba7c3603af 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 bee8872ede0a..dea900786874 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 3981525bbff2..5c878c946231 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 6f0a91d8a30a..000000000000 --- 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/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index ecad3cc0d025..1455764dced8 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -26,7 +26,7 @@ import com.android.internal.view.IInputMethodManager; import com.android.internal.view.IInputMethodSession; import com.android.internal.view.InputBindResult; -import com.android.server.StatusBarManagerService; +import com.android.server.EventLogTags; import org.xmlpull.v1.XmlPullParserException; @@ -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; @@ -61,25 +62,29 @@ import android.os.ServiceManager; import android.os.SystemClock; 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; import java.io.PrintWriter; -import java.text.Collator; import java.util.ArrayList; +import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -93,6 +98,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 +117,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 +139,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 +238,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 +312,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub */ boolean mScreenOn = true; + int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT; + int mImeWindowVis; + AlertDialog.Builder mDialogBuilder; AlertDialog mSwitchingDialog; InputMethodInfo[] mIms; CharSequence[] mItems; + int[] mSubtypeIds; class SettingsObserver extends ContentObserver { SettingsObserver(Handler handler) { @@ -299,6 +327,10 @@ 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.ENABLED_INPUT_METHODS), 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)); @@ -469,27 +500,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 +523,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 +572,39 @@ 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); } + List<InputMethodSubtype> enabledSubtypes = + mSettings.getEnabledInputMethodSubtypeListLocked(imi); + if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) { + enabledSubtypes = getApplicableSubtypesLocked(mRes, getSubtypes(imi)); + } + return InputMethodSubtype.sort(mContext, 0, imi, enabledSubtypes); + } - return res; + public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi, + boolean allowsImplicitlySelectedSubtypes) { + synchronized (mMethodMap) { + return getEnabledInputMethodSubtypeListLocked(imi, allowsImplicitlySelectedSubtypes); + } } public void addClient(IInputMethodClient client, @@ -959,21 +989,46 @@ 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); + } + } + void updateFromSettingsLocked() { // 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 +1036,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 +1182,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 +1262,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 +1285,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 +1334,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 +1417,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub long ident = Binder.clearCallingIdentity(); try { - setInputMethodLocked(id); + setInputMethodLocked(id, subtypeId); } finally { Binder.restoreCallingIdentity(ident); } @@ -1315,6 +1483,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 +1594,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 +1615,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 +1632,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 +1690,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 +1726,104 @@ 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) { + final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis = + getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(); + if (immis == null || immis.size() == 0) { + return; + } + hideInputMethodMenuLocked(); - int N = immis.size(); + final TreeMap<InputMethodInfo, List<InputMethodSubtype>> sortedImmis = + new TreeMap<InputMethodInfo, List<InputMethodSubtype>>( + new Comparator<InputMethodInfo>() { + @Override + public int compare(InputMethodInfo imi1, InputMethodInfo imi2) { + if (imi2 == null) return 0; + if (imi1 == null) return 1; + if (pm == null) { + return imi1.getId().compareTo(imi2.getId()); + } + CharSequence imiId1 = imi1.loadLabel(pm) + "/" + imi1.getId(); + CharSequence imiId2 = imi2.loadLabel(pm) + "/" + imi2.getId(); + return imiId1.toString().compareTo(imiId2.toString()); + } + }); - final Map<CharSequence, InputMethodInfo> imMap = - new TreeMap<CharSequence, InputMethodInfo>(Collator.getInstance()); + sortedImmis.putAll(immis); - for (int i = 0; i < N; ++i) { - InputMethodInfo property = immis.get(i); - if (property == null) { - continue; + final ArrayList<Pair<CharSequence, Pair<InputMethodInfo, Integer>>> imList = + new ArrayList<Pair<CharSequence, Pair<InputMethodInfo, Integer>>>(); + + for (InputMethodInfo imi : sortedImmis.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); + final 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()))) { + final CharSequence title; + int nameResId = subtype.getNameResId(); + String mode = subtype.getMode(); + if (nameResId != 0) { + title = TextUtils.concat(pm.getText(imi.getPackageName(), + nameResId, imi.getServiceInfo().applicationInfo), + (TextUtils.isEmpty(label) ? "" : " (" + label + ")")); + } else { + CharSequence language = subtype.getLocale(); + // TODO: Use more friendly Title and UI + title = label + "," + (mode == null ? "" : mode) + "," + + (language == null ? "" : language); + } + imList.add(new Pair<CharSequence, Pair<InputMethodInfo, Integer>>( + title, new Pair<InputMethodInfo, Integer>(imi, j))); + } + } + } else { + imList.add(new Pair<CharSequence, Pair<InputMethodInfo, Integer>>( + label, new Pair<InputMethodInfo, Integer>(imi, NOT_A_SUBTYPE_ID))); } - imMap.put(property.loadLabel(pm), property); } - N = imMap.size(); - mItems = imMap.keySet().toArray(new CharSequence[N]); - mIms = imMap.values().toArray(new InputMethodInfo[N]); - + final int N = imList.size(); + mItems = new CharSequence[N]; + for (int i = 0; i < N; ++i) { + mItems[i] = imList.get(i).first; + } + mIms = new InputMethodInfo[N]; + mSubtypeIds = new int[N]; int checkedItem = 0; for (int i = 0; i < N; ++i) { + Pair<InputMethodInfo, Integer> value = imList.get(i).second; + 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,26 +1837,43 @@ 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.setCanceledOnTouchOutside(true); mSwitchingDialog.getWindow().setType( WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG); + mSwitchingDialog.getWindow().getAttributes().setTitle("Select input method"); mSwitchingDialog.show(); } } @@ -1630,80 +1922,788 @@ 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; } } + } - StringBuilder builder = new StringBuilder(256); + private boolean canAddToLastInputMethod(InputMethodSubtype subtype) { + if (subtype == null) return true; + return !subtype.containsExtraValueKey(SUBTYPE_EXTRAVALUE_EXCLUDE_FROM_LAST_IME); + } - boolean removed = false; - String firstId = null; + 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); + } + } - // 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; + private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId, + boolean setSubtypeOnly) { + // Update the history of InputMethod and Subtype + saveCurrentInputMethodAndSubtypeToHistory(); + + // 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; + } + } + + /** + * @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/Installer.java b/services/java/com/android/server/Installer.java index 85eca6062a41..08d1b82f2520 100644 --- a/services/java/com/android/server/Installer.java +++ b/services/java/com/android/server/Installer.java @@ -166,17 +166,11 @@ class Installer { } } - public int install(String name, boolean useEncryptedFilesystem, int uid, int gid) { + public int install(String name, int uid, int gid) { StringBuilder builder = new StringBuilder("install"); builder.append(' '); builder.append(name); builder.append(' '); - if (useEncryptedFilesystem) { - builder.append('1'); - } else { - builder.append('0'); - } - builder.append(' '); builder.append(uid); builder.append(' '); builder.append(gid); @@ -209,57 +203,33 @@ class Installer { return execute(builder.toString()); } - public int remove(String name, boolean useEncryptedFilesystem) { + public int remove(String name) { StringBuilder builder = new StringBuilder("remove"); builder.append(' '); builder.append(name); - builder.append(' '); - if (useEncryptedFilesystem) { - builder.append('1'); - } else { - builder.append('0'); - } return execute(builder.toString()); } - public int rename(String oldname, String newname, boolean useEncryptedFilesystem) { + public int rename(String oldname, String newname) { StringBuilder builder = new StringBuilder("rename"); builder.append(' '); builder.append(oldname); builder.append(' '); builder.append(newname); - builder.append(' '); - if (useEncryptedFilesystem) { - builder.append('1'); - } else { - builder.append('0'); - } return execute(builder.toString()); } - public int deleteCacheFiles(String name, boolean useEncryptedFilesystem) { + public int deleteCacheFiles(String name) { StringBuilder builder = new StringBuilder("rmcache"); builder.append(' '); builder.append(name); - builder.append(' '); - if (useEncryptedFilesystem) { - builder.append('1'); - } else { - builder.append('0'); - } return execute(builder.toString()); } - public int clearUserData(String name, boolean useEncryptedFilesystem) { + public int clearUserData(String name) { StringBuilder builder = new StringBuilder("rmuserdata"); builder.append(' '); builder.append(name); - builder.append(' '); - if (useEncryptedFilesystem) { - builder.append('1'); - } else { - builder.append('0'); - } return execute(builder.toString()); } @@ -292,8 +262,8 @@ class Installer { return execute(builder.toString()); } - public int getSizeInfo(String pkgName, String apkPath, - String fwdLockApkPath, PackageStats pStats, boolean useEncryptedFilesystem) { + public int getSizeInfo(String pkgName, String apkPath, String fwdLockApkPath, + PackageStats pStats) { StringBuilder builder = new StringBuilder("getsize"); builder.append(' '); builder.append(pkgName); @@ -301,12 +271,6 @@ class Installer { builder.append(apkPath); builder.append(' '); builder.append(fwdLockApkPath != null ? fwdLockApkPath : "!"); - builder.append(' '); - if (useEncryptedFilesystem) { - builder.append('1'); - } else { - builder.append('0'); - } String s = transaction(builder.toString()); String res[] = s.split(" "); diff --git a/services/java/com/android/server/IntentResolver.java b/services/java/com/android/server/IntentResolver.java index a8b28403ee1a..b78389bbe8da 100644 --- a/services/java/com/android/server/IntentResolver.java +++ b/services/java/com/android/server/IntentResolver.java @@ -27,6 +27,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import android.net.Uri; +import android.util.FastImmutableArraySet; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.Slog; @@ -34,7 +36,6 @@ import android.util.LogPrinter; import android.util.Printer; import android.util.Config; -import android.content.ContentResolver; import android.content.Intent; import android.content.IntentFilter; @@ -207,10 +208,11 @@ public class IntentResolver<F extends IntentFilter, R extends Object> { final boolean debug = localLOGV || ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0); + FastImmutableArraySet<String> categories = getFastIntentCategories(intent); final String scheme = intent.getScheme(); int N = listCut.size(); for (int i = 0; i < N; ++i) { - buildResolveList(intent, debug, defaultOnly, + buildResolveList(intent, categories, debug, defaultOnly, resolvedType, scheme, listCut.get(i), resultList); } sortResults(resultList); @@ -286,20 +288,21 @@ public class IntentResolver<F extends IntentFilter, R extends Object> { if (debug) Slog.v(TAG, "Action list: " + firstTypeCut); } + FastImmutableArraySet<String> categories = getFastIntentCategories(intent); if (firstTypeCut != null) { - buildResolveList(intent, debug, defaultOnly, + buildResolveList(intent, categories, debug, defaultOnly, resolvedType, scheme, firstTypeCut, finalList); } if (secondTypeCut != null) { - buildResolveList(intent, debug, defaultOnly, + buildResolveList(intent, categories, debug, defaultOnly, resolvedType, scheme, secondTypeCut, finalList); } if (thirdTypeCut != null) { - buildResolveList(intent, debug, defaultOnly, + buildResolveList(intent, categories, debug, defaultOnly, resolvedType, scheme, thirdTypeCut, finalList); } if (schemeCut != null) { - buildResolveList(intent, debug, defaultOnly, + buildResolveList(intent, categories, debug, defaultOnly, resolvedType, scheme, schemeCut, finalList); } sortResults(finalList); @@ -322,6 +325,15 @@ public class IntentResolver<F extends IntentFilter, R extends Object> { return true; } + /** + * Returns whether the object associated with the given filter is + * "stopped," that is whether it should not be included in the result + * if the intent requests to excluded stopped objects. + */ + protected boolean isFilterStopped(F filter) { + return false; + } + protected String packageForFilter(F filter) { return null; } @@ -478,9 +490,21 @@ public class IntentResolver<F extends IntentFilter, R extends Object> { return false; } - private void buildResolveList(Intent intent, boolean debug, boolean defaultOnly, + private static FastImmutableArraySet<String> getFastIntentCategories(Intent intent) { + final Set<String> categories = intent.getCategories(); + if (categories == null) { + return null; + } + return new FastImmutableArraySet<String>(categories.toArray(new String[categories.size()])); + } + + private void buildResolveList(Intent intent, FastImmutableArraySet<String> categories, + boolean debug, boolean defaultOnly, String resolvedType, String scheme, List<F> src, List<R> dest) { - Set<String> categories = intent.getCategories(); + final String action = intent.getAction(); + final Uri data = intent.getData(); + + final boolean excludingStopped = intent.isExcludingStopped(); final int N = src != null ? src.size() : 0; boolean hasNonDefaults = false; @@ -490,6 +514,13 @@ public class IntentResolver<F extends IntentFilter, R extends Object> { int match; if (debug) Slog.v(TAG, "Matching against filter " + filter); + if (excludingStopped && isFilterStopped(filter)) { + if (debug) { + Slog.v(TAG, " Filter's target is stopped; skipping"); + } + continue; + } + // Do we already have this one? if (!allowFilterResult(filter, dest)) { if (debug) { @@ -498,8 +529,7 @@ public class IntentResolver<F extends IntentFilter, R extends Object> { continue; } - match = filter.match( - intent.getAction(), resolvedType, scheme, intent.getData(), categories, TAG); + match = filter.match(action, resolvedType, scheme, data, categories, TAG); if (match >= 0) { if (debug) Slog.v(TAG, " Filter matched! match=0x" + Integer.toHexString(match)); diff --git a/services/java/com/android/server/LightsService.java b/services/java/com/android/server/LightsService.java index 9b57735b87cf..21f2bcf00be1 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 10107c6788f6..656ec4d266dd 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 e6e3e5fbfe9a..91ada6bf52b1 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: { @@ -369,7 +374,7 @@ class MountService extends IMountService.Stub done = true; } else { // Eliminate system process here? - ams.killPids(pids, "unmount media"); + ams.killPids(pids, "unmount media", true); // Confirm if file references have been freed. pids = getStorageUsers(path); if (pids == null || pids.length == 0) { @@ -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 f0acdc0d51b8..44f5df2cf1bc 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -26,6 +26,8 @@ import android.content.pm.PackageManager; import android.net.Uri; import android.net.InterfaceConfiguration; import android.net.INetworkManagementEventObserver; +import android.net.LinkAddress; +import android.net.NetworkUtils; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration.KeyMgmt; import android.os.INetworkManagementService; @@ -219,28 +221,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 @@ -268,7 +248,7 @@ class NetworkManagementService extends INetworkManagementService.Stub { } Slog.d(TAG, String.format("rsp <%s>", rsp)); - // Rsp: 213 xx:xx:xx:xx:xx:xx yyy.yyy.yyy.yyy zzz.zzz.zzz.zzz [flag1 flag2 flag3] + // Rsp: 213 xx:xx:xx:xx:xx:xx yyy.yyy.yyy.yyy zzz [flag1 flag2 flag3] StringTokenizer st = new StringTokenizer(rsp); InterfaceConfiguration cfg; @@ -287,19 +267,21 @@ class NetworkManagementService extends INetworkManagementService.Stub { cfg = new InterfaceConfiguration(); cfg.hwAddr = st.nextToken(" "); + InetAddress addr = null; + int prefixLength = 0; try { - cfg.ipAddr = stringToIpAddr(st.nextToken(" ")); - } catch (UnknownHostException uhe) { - Slog.e(TAG, "Failed to parse ipaddr", uhe); - cfg.ipAddr = 0; + addr = NetworkUtils.numericToInetAddress(st.nextToken(" ")); + } catch (IllegalArgumentException iae) { + Slog.e(TAG, "Failed to parse ipaddr", iae); } try { - cfg.netmask = stringToIpAddr(st.nextToken(" ")); - } catch (UnknownHostException uhe) { - Slog.e(TAG, "Failed to parse netmask", uhe); - cfg.netmask = 0; + prefixLength = Integer.parseInt(st.nextToken(" ")); + } catch (NumberFormatException nfe) { + Slog.e(TAG, "Failed to parse prefixLength", nfe); } + + cfg.addr = new LinkAddress(addr, prefixLength); cfg.interfaceFlags = st.nextToken("]").trim() +"]"; } catch (NoSuchElementException nsee) { throw new IllegalStateException( @@ -311,13 +293,19 @@ 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); + LinkAddress linkAddr = cfg.addr; + if (linkAddr == null || linkAddr.getAddress() == null) { + throw new IllegalStateException("Null LinkAddress given"); + } + String cmd = String.format("interface setcfg %s %s %d %s", iface, + linkAddr.getAddress().getHostAddress(), + linkAddr.getNetworkPrefixLength(), + 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); } } @@ -463,7 +451,7 @@ class NetworkManagementService extends INetworkManagementService.Stub { try { String cmd = "tether dns set"; for (String s : dns) { - cmd += " " + InetAddress.getByName(s).getHostAddress(); + cmd += " " + NetworkUtils.numericToInetAddress(s).getHostAddress(); } try { mConnector.doCommand(cmd); @@ -471,7 +459,7 @@ class NetworkManagementService extends INetworkManagementService.Stub { throw new IllegalStateException( "Unable to communicate to native daemon for setting tether dns"); } - } catch (UnknownHostException e) { + } catch (IllegalArgumentException e) { throw new IllegalStateException("Error resolving dns name", e); } } @@ -531,11 +519,11 @@ class NetworkManagementService extends INetworkManagementService.Stub { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); mConnector.doCommand(String.format("pppd attach %s %s %s %s %s", tty, - InetAddress.getByName(localAddr).getHostAddress(), - InetAddress.getByName(remoteAddr).getHostAddress(), - InetAddress.getByName(dns1Addr).getHostAddress(), - InetAddress.getByName(dns2Addr).getHostAddress())); - } catch (UnknownHostException e) { + NetworkUtils.numericToInetAddress(localAddr).getHostAddress(), + NetworkUtils.numericToInetAddress(remoteAddr).getHostAddress(), + NetworkUtils.numericToInetAddress(dns1Addr).getHostAddress(), + NetworkUtils.numericToInetAddress(dns2Addr).getHostAddress())); + } catch (IllegalArgumentException e) { throw new IllegalStateException("Error resolving addr", e); } catch (NativeDaemonConnectorException e) { throw new IllegalStateException("Error communicating to native daemon to attach pppd", e); @@ -622,11 +610,10 @@ class NetworkManagementService extends INetworkManagementService.Stub { * argv7 - Preamble * argv8 - Max SCB */ - String str = String.format("softap set " + wlanIface + " " + softapIface + - " %s %s %s", convertQuotedString(wifiConfig.SSID), - wifiConfig.allowedKeyManagement.get(KeyMgmt.WPA_PSK) ? - "wpa2-psk" : "open", - convertQuotedString(wifiConfig.preSharedKey)); + String str = String.format("softap set " + wlanIface + " " + softapIface + + " %s %s %s", convertQuotedString(wifiConfig.SSID), + getSecurityType(wifiConfig), + convertQuotedString(wifiConfig.preSharedKey)); mConnector.doCommand(str); } mConnector.doCommand(String.format("softap startap")); @@ -643,6 +630,17 @@ class NetworkManagementService extends INetworkManagementService.Stub { return '"' + s.replaceAll("\\\\","\\\\\\\\").replaceAll("\"","\\\\\"") + '"'; } + private String getSecurityType(WifiConfiguration wifiConfig) { + switch (wifiConfig.getAuthType()) { + case KeyMgmt.WPA_PSK: + return "wpa-psk"; + case KeyMgmt.WPA2_PSK: + return "wpa2-psk"; + default: + return "open"; + } + } + public void stopAccessPoint() throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); @@ -668,7 +666,7 @@ class NetworkManagementService extends INetworkManagementService.Stub { } else { String str = String.format("softap set " + wlanIface + " " + softapIface + " %s %s %s", convertQuotedString(wifiConfig.SSID), - wifiConfig.allowedKeyManagement.get(KeyMgmt.WPA_PSK) ? "wpa2-psk" : "open", + getSecurityType(wifiConfig), convertQuotedString(wifiConfig.preSharedKey)); mConnector.doCommand(str); } diff --git a/services/java/com/android/server/NetworkTimeUpdateService.java b/services/java/com/android/server/NetworkTimeUpdateService.java new file mode 100644 index 000000000000..15f22c0b2ad5 --- /dev/null +++ b/services/java/com/android/server/NetworkTimeUpdateService.java @@ -0,0 +1,329 @@ +/* + * 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.ConnectivityManager; +import android.net.NetworkInfo; +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; + private static final int EVENT_WIFI_CONNECTED = 3; + + /** 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(); + registerForConnectivityIntents(); + + 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 registerForConnectivityIntents() { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + mContext.registerReceiver(mConnectivityReceiver, intentFilter); + } + + 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(); + } + } + }; + + /** Receiver for ConnectivityManager events */ + private BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) { + // There is connectivity + NetworkInfo netInfo = (NetworkInfo)intent.getParcelableExtra( + ConnectivityManager.EXTRA_NETWORK_INFO); + if (netInfo != null) { + // Verify that it's a WIFI connection + if (netInfo.getState() == NetworkInfo.State.CONNECTED && + netInfo.getType() == ConnectivityManager.TYPE_WIFI ) { + mHandler.obtainMessage(EVENT_WIFI_CONNECTED).sendToTarget(); + } + } + } + } + }; + + /** 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: + case EVENT_WIFI_CONNECTED: + 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 540389e21d84..e73814511224 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; @@ -41,13 +40,11 @@ import android.database.ContentObserver; import android.hardware.usb.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,22 +330,7 @@ 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(UsbManager.ACTION_USB_STATE)) { + if (action.equals(UsbManager.ACTION_USB_STATE)) { Bundle extras = intent.getExtras(); boolean usbConnected = extras.getBoolean(UsbManager.USB_CONNECTED); boolean adbEnabled = (UsbManager.USB_FUNCTION_ENABLED.equals( @@ -428,7 +406,6 @@ public class NotificationManagerService extends INotificationManager.Stub { super(); mContext = context; - mLightsService = lights; mAm = ActivityManagerNative.getDefault(); mSound = new NotificationPlayer(TAG); mSound.setUsesWakeLock(context); @@ -438,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); @@ -450,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 @@ -470,9 +435,8 @@ 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(UsbManager.ACTION_USB_STATE); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); @@ -676,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); @@ -688,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 @@ -731,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); @@ -765,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(); @@ -786,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 { @@ -892,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(); @@ -941,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) { @@ -958,7 +953,7 @@ public class NotificationManagerService extends INotificationManager.Stub mNotificationList.remove(index); - cancelNotificationLocked(r); + cancelNotificationLocked(r, sendDelete); updateLightsLocked(); } } @@ -991,7 +986,7 @@ public class NotificationManagerService extends INotificationManager.Stub return true; } mNotificationList.remove(i); - cancelNotificationLocked(r); + cancelNotificationLocked(r, false); } if (canceledSomething) { updateLightsLocked(); @@ -1000,7 +995,7 @@ public class NotificationManagerService extends INotificationManager.Stub } } - + @Deprecated public void cancelNotification(String pkg, int id) { cancelNotificationWithTag(pkg, null /* tag */, id); } @@ -1010,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) { @@ -1046,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); } } @@ -1064,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 8c74566c231b..d54267347e36 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -85,7 +85,6 @@ import android.os.Process; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; -import android.provider.Settings; import android.security.SystemKeyStore; import android.util.*; import android.view.Display; @@ -114,12 +113,10 @@ import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.zip.ZipEntry; -import java.util.zip.ZipException; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; @@ -140,6 +137,7 @@ class PackageManagerService extends IPackageManager.Stub { private static final boolean DEBUG_PREFERRED = false; private static final boolean DEBUG_UPGRADE = false; private static final boolean DEBUG_INSTALL = false; + private static final boolean DEBUG_STOPPED = false; private static final boolean MULTIPLE_APPLICATION_UIDS = true; private static final int RADIO_UID = Process.PHONE_UID; @@ -153,8 +151,6 @@ class PackageManagerService extends IPackageManager.Stub { private static final boolean GET_CERTIFICATES = true; - private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled"; - private static final int REMOVE_EVENTS = FileObserver.CLOSE_WRITE | FileObserver.DELETE | FileObserver.MOVED_FROM; private static final int ADD_EVENTS = @@ -216,10 +212,6 @@ class PackageManagerService extends IPackageManager.Stub { // This is where all application persistent data goes. final File mAppDataDir; - // If Encrypted File System feature is enabled, all application persistent data - // should go here instead. - final File mSecureAppDataDir; - // This is the object monitoring the framework dir. final FileObserver mFrameworkInstallObserver; @@ -364,6 +356,7 @@ class PackageManagerService extends IPackageManager.Stub { static final int MCS_GIVE_UP = 11; static final int UPDATED_MEDIA_STATUS = 12; static final int WRITE_SETTINGS = 13; + static final int WRITE_STOPPED_PACKAGES = 14; static final int WRITE_SETTINGS_DELAY = 10*1000; // 10 seconds @@ -607,11 +600,14 @@ class PackageManagerService extends IPackageManager.Stub { } sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, res.pkg.applicationInfo.packageName, - extras, null); + extras, null, null); if (update) { sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, res.pkg.applicationInfo.packageName, - extras, null); + extras, null, null); + sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, + null, null, + res.pkg.applicationInfo.packageName, null); } if (res.removedInfo.args != null) { // Remove the replaced package's older resources safely now @@ -665,10 +661,19 @@ class PackageManagerService extends IPackageManager.Stub { Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); synchronized (mPackages) { removeMessages(WRITE_SETTINGS); + removeMessages(WRITE_STOPPED_PACKAGES); mSettings.writeLP(); } Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } break; + case WRITE_STOPPED_PACKAGES: { + Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); + synchronized (mPackages) { + removeMessages(WRITE_STOPPED_PACKAGES); + mSettings.writeStoppedLP(); + } + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + } break; } } } @@ -679,6 +684,12 @@ class PackageManagerService extends IPackageManager.Stub { } } + void scheduleWriteStoppedPackagesLocked() { + if (!mHandler.hasMessages(WRITE_STOPPED_PACKAGES)) { + mHandler.sendEmptyMessageDelayed(WRITE_STOPPED_PACKAGES, WRITE_SETTINGS_DELAY); + } + } + static boolean installOnSd(int flags) { if (((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) || ((flags & PackageManager.INSTALL_INTERNAL) != 0)) { @@ -785,7 +796,6 @@ class PackageManagerService extends IPackageManager.Stub { File dataDir = Environment.getDataDirectory(); mAppDataDir = new File(dataDir, "data"); - mSecureAppDataDir = new File(dataDir, "secure/data"); mDrmAppPrivateInstallDir = new File(dataDir, "app-private"); if (mInstaller == null) { @@ -795,7 +805,6 @@ class PackageManagerService extends IPackageManager.Stub { File miscDir = new File(dataDir, "misc"); miscDir.mkdirs(); mAppDataDir.mkdirs(); - mSecureAppDataDir.mkdirs(); mDrmAppPrivateInstallDir.mkdirs(); } @@ -964,9 +973,7 @@ class PackageManagerService extends IPackageManager.Stub { + " no longer exists; wiping its data"; reportSettingsProblem(Log.WARN, msg); if (mInstaller != null) { - // XXX how to set useEncryptedFSDir for packages that - // are not encrypted? - mInstaller.remove(ps.name, true); + mInstaller.remove(ps.name); } } } @@ -1050,8 +1057,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); - int retCode = mInstaller.remove(ps.name, useSecureFS); + int retCode = mInstaller.remove(ps.name); if (retCode < 0) { Slog.w(TAG, "Couldn't remove app data directory for package: " + ps.name + ", retcode=" + retCode); @@ -1225,6 +1231,7 @@ class PackageManagerService extends IPackageManager.Stub { } } + permReader.close(); } catch (XmlPullParserException e) { Slog.w(TAG, "Got execption parsing permissions.", e); } catch (IOException e) { @@ -1497,6 +1504,7 @@ class PackageManagerService extends IPackageManager.Stub { ps.pkg.applicationInfo.dataDir = getDataPathForPackage(ps.pkg).getPath(); ps.pkg.applicationInfo.nativeLibraryDir = ps.nativeLibraryPathString; ps.pkg.mSetEnabled = ps.enabled; + ps.pkg.mSetStopped = ps.stopped; } return generatePackageInfo(ps.pkg, flags); } @@ -2017,10 +2025,10 @@ class PackageManagerService extends IPackageManager.Stub { final int M = prefs.size(); for (int i=0; i<M; i++) { PreferredActivity pa = prefs.get(i); - if (pa.mMatch != match) { + if (pa.mPref.mMatch != match) { continue; } - ActivityInfo ai = getActivityInfo(pa.mActivity, flags); + ActivityInfo ai = getActivityInfo(pa.mPref.mComponent, flags); if (DEBUG_PREFERRED) { Log.v(TAG, "Got preferred activity:"); if (ai != null) { @@ -2044,7 +2052,7 @@ class PackageManagerService extends IPackageManager.Stub { // If the result set is different from when this // was created, we need to clear it and re-ask the // user their preference. - if (!pa.sameSet(query, priority)) { + if (!pa.mPref.sameSet(query, priority)) { Slog.i(TAG, "Result set changed, dropping preferred activity for " + intent + " type " + resolvedType); mSettings.mPreferredActivities.removeFilter(pa); @@ -2086,7 +2094,7 @@ class PackageManagerService extends IPackageManager.Stub { return (List<ResolveInfo>) mActivities.queryIntentForPackage(intent, resolvedType, flags, pkg.activities); } - return null; + return new ArrayList<ResolveInfo>(); } } @@ -2779,11 +2787,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,13 +2803,7 @@ class PackageManagerService extends IPackageManager.Stub { } private File getDataPathForPackage(PackageParser.Package pkg) { - boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(pkg); - File dataPath; - if (useEncryptedFSDir) { - dataPath = new File(mSecureAppDataDir, pkg.packageName); - } else { - dataPath = new File(mAppDataDir, pkg.packageName); - } + final File dataPath = new File(mAppDataDir, pkg.packageName); return dataPath; } @@ -2852,7 +2849,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 +3133,6 @@ 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); dataPath = getDataPathForPackage(pkg); boolean uidError = false; @@ -3153,7 +3149,7 @@ class PackageManagerService extends IPackageManager.Stub { // If this is a system app, we can at least delete its // current data so the application will still work. if (mInstaller != null) { - int ret = mInstaller.remove(pkgName, useEncryptedFSDir); + int ret = mInstaller.remove(pkgName); if (ret >= 0) { // Old data gone! String msg = "System package " + pkg.packageName @@ -3164,7 +3160,7 @@ class PackageManagerService extends IPackageManager.Stub { recovered = true; // And now re-install the app. - ret = mInstaller.install(pkgName, useEncryptedFSDir, pkg.applicationInfo.uid, + ret = mInstaller.install(pkgName, pkg.applicationInfo.uid, pkg.applicationInfo.uid); if (ret == -1) { // Ack should not happen! @@ -3205,7 +3201,7 @@ class PackageManagerService extends IPackageManager.Stub { Log.v(TAG, "Want this data dir: " + dataPath); //invoke installer to do the actual installation if (mInstaller != null) { - int ret = mInstaller.install(pkgName, useEncryptedFSDir, pkg.applicationInfo.uid, + int ret = mInstaller.install(pkgName, pkg.applicationInfo.uid, pkg.applicationInfo.uid); if(ret < 0) { // Error from installer @@ -4116,6 +4112,18 @@ class PackageManagerService extends IPackageManager.Stub { } @Override + protected boolean isFilterStopped(PackageParser.ActivityIntentInfo filter) { + PackageParser.Package p = filter.activity.owner; + if (p != null) { + PackageSetting ps = (PackageSetting)p.mExtras; + if (ps != null) { + return ps.stopped; + } + } + return false; + } + + @Override protected String packageForFilter(PackageParser.ActivityIntentInfo info) { return info.activity.owner.packageName; } @@ -4273,6 +4281,18 @@ class PackageManagerService extends IPackageManager.Stub { } @Override + protected boolean isFilterStopped(PackageParser.ServiceIntentInfo filter) { + PackageParser.Package p = filter.service.owner; + if (p != null) { + PackageSetting ps = (PackageSetting)p.mExtras; + if (ps != null) { + return ps.stopped; + } + } + return false; + } + + @Override protected String packageForFilter(PackageParser.ServiceIntentInfo info) { return info.service.owner.packageName; } @@ -4375,7 +4395,7 @@ class PackageManagerService extends IPackageManager.Stub { }; private static final void sendPackageBroadcast(String action, String pkg, - Bundle extras, IIntentReceiver finishedReceiver) { + Bundle extras, String targetPkg, IIntentReceiver finishedReceiver) { IActivityManager am = ActivityManagerNative.getDefault(); if (am != null) { try { @@ -4384,6 +4404,9 @@ class PackageManagerService extends IPackageManager.Stub { if (extras != null) { intent.putExtras(extras); } + if (targetPkg != null) { + intent.setPackage(targetPkg); + } intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); am.broadcastIntent(null, intent, null, finishedReceiver, 0, null, null, null, finishedReceiver != null, false); @@ -4391,10 +4414,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 +4446,7 @@ class PackageManagerService extends IPackageManager.Stub { void startCleaningPackages() { synchronized (mPackages) { - if (!mMediaMounted) { + if (!isExternalMediaAvailable()) { return; } if (mSettings.mPackagesToBeCleaned.size() <= 0) { @@ -4510,13 +4542,13 @@ class PackageManagerService extends IPackageManager.Stub { extras.putInt(Intent.EXTRA_UID, removedUid); extras.putBoolean(Intent.EXTRA_DATA_REMOVED, false); sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, - extras, null); + extras, null, null); } if (addedPackage != null) { Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, addedUid); sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, addedPackage, - extras, null); + extras, null, null); } } @@ -4549,6 +4581,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 +4762,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(externalMediaDir.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; @@ -6025,10 +6204,10 @@ class PackageManagerService extends IPackageManager.Stub { mHandler.post(new Runnable() { public void run() { mHandler.removeCallbacks(this); - final boolean succeded = deletePackageX(packageName, true, true, flags); + final int returnCode = deletePackageX(packageName, true, true, flags); if (observer != null) { try { - observer.packageDeleted(succeded); + observer.packageDeleted(packageName, returnCode); } catch (RemoteException e) { Log.i(TAG, "Observer no longer exists."); } //end catch @@ -6051,17 +6230,17 @@ class PackageManagerService extends IPackageManager.Stub { * persisting settings for later use * sending a broadcast if necessary */ - private boolean deletePackageX(String packageName, boolean sendBroadCast, + private int deletePackageX(String packageName, boolean sendBroadCast, boolean deleteCodeAndResources, int flags) { - PackageRemovedInfo info = new PackageRemovedInfo(); - boolean res; + final PackageRemovedInfo info = new PackageRemovedInfo(); + final boolean res; IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface( ServiceManager.getService(Context.DEVICE_POLICY_SERVICE)); try { if (dpm != null && dpm.packageHasActiveAdmins(packageName)) { Slog.w(TAG, "Not removing package " + packageName + ": has active device admin"); - return false; + return PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER; } } catch (RemoteException e) { } @@ -6071,7 +6250,7 @@ class PackageManagerService extends IPackageManager.Stub { flags | REMOVE_CHATTY, info, true); } - if(res && sendBroadCast) { + if (res && sendBroadCast) { boolean systemUpdate = info.isRemovedPackageSystemUpdate; info.sendBroadcast(deleteCodeAndResources, systemUpdate); @@ -6082,8 +6261,12 @@ class PackageManagerService extends IPackageManager.Stub { extras.putInt(Intent.EXTRA_UID, info.removedUid >= 0 ? info.removedUid : info.uid); extras.putBoolean(Intent.EXTRA_REPLACING, true); - sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, extras, null); - sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, extras, null); + sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, + extras, null, null); + sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, + extras, null, null); + sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, + null, packageName, null); } } // Force a gc here. @@ -6095,7 +6278,8 @@ class PackageManagerService extends IPackageManager.Stub { info.args.doPostDeleteLI(deleteCodeAndResources); } } - return res; + + return res ? PackageManager.DELETE_SUCCEEDED : PackageManager.DELETE_FAILED_INTERNAL_ERROR; } static class PackageRemovedInfo { @@ -6114,10 +6298,11 @@ class PackageManagerService extends IPackageManager.Stub { extras.putBoolean(Intent.EXTRA_REPLACING, true); } if (removedPackage != null) { - sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, extras, null); + sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, + extras, null, null); } if (removedUid >= 0) { - sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras, null); + sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras, null, null); } } } @@ -6141,9 +6326,8 @@ class PackageManagerService extends IPackageManager.Stub { deletedPs = mSettings.mPackages.get(packageName); } if ((flags&PackageManager.DONT_DELETE_DATA) == 0) { - boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(p); if (mInstaller != null) { - int retCode = mInstaller.remove(packageName, useEncryptedFSDir); + int retCode = mInstaller.remove(packageName); if (retCode < 0) { Slog.w(TAG, "Couldn't remove app data or cache directory for package: " + packageName + ", retcode=" + retCode); @@ -6174,7 +6358,7 @@ class PackageManagerService extends IPackageManager.Stub { // remove from preferred activities. ArrayList<PreferredActivity> removed = new ArrayList<PreferredActivity>(); for (PreferredActivity pa : mSettings.mPreferredActivities.filterSet()) { - if (pa.mActivity.getPackageName().equals(deletedPs.name)) { + if (pa.mPref.mComponent.getPackageName().equals(deletedPs.name)) { removed.add(pa); } } @@ -6383,9 +6567,8 @@ class PackageManagerService extends IPackageManager.Stub { p = ps.pkg; } } - 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,10 +6579,9 @@ 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); + int retCode = mInstaller.clearUserData(packageName); if (retCode < 0) { Slog.w(TAG, "Couldn't remove cache files for package: " + packageName); @@ -6450,9 +6632,8 @@ class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Package " + packageName + " has no applicationInfo."); return false; } - boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(p); if (mInstaller != null) { - int retCode = mInstaller.deleteCacheFiles(packageName, useEncryptedFSDir); + int retCode = mInstaller.deleteCacheFiles(packageName); if (retCode < 0) { Slog.w(TAG, "Couldn't remove cache files for package: " + packageName); @@ -6470,18 +6651,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,10 +6693,8 @@ class PackageManagerService extends IPackageManager.Stub { } publicSrcDir = isForwardLocked(p) ? applicationInfo.publicSourceDir : null; } - boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(p); if (mInstaller != null) { - int res = mInstaller.getSizeInfo(packageName, p.mPath, - publicSrcDir, pStats, useEncryptedFSDir); + int res = mInstaller.getSizeInfo(packageName, p.mPath, publicSrcDir, pStats); if (res < 0) { return false; } else { @@ -6628,7 +6805,7 @@ class PackageManagerService extends IPackageManager.Stub { PreferredActivity pa = it.next(); if (pa.getAction(0).equals(action) && pa.getCategory(0).equals(category)) { it.remove(); - Log.i(TAG, "Removed preferred activity " + pa.mActivity + ":"); + Log.i(TAG, "Removed preferred activity " + pa.mPref.mComponent + ":"); filter.dump(new LogPrinter(Log.INFO, TAG), " "); } } @@ -6666,7 +6843,7 @@ class PackageManagerService extends IPackageManager.Stub { Iterator<PreferredActivity> it = mSettings.mPreferredActivities.filterIterator(); while (it.hasNext()) { PreferredActivity pa = it.next(); - if (pa.mActivity.getPackageName().equals(packageName)) { + if (pa.mPref.mComponent.getPackageName().equals(packageName)) { it.remove(); changed = true; } @@ -6683,12 +6860,12 @@ class PackageManagerService extends IPackageManager.Stub { while (it.hasNext()) { PreferredActivity pa = it.next(); if (packageName == null - || pa.mActivity.getPackageName().equals(packageName)) { + || pa.mPref.mComponent.getPackageName().equals(packageName)) { if (outFilters != null) { outFilters.add(new IntentFilter(pa)); } if (outActivities != null) { - outActivities.add(pa.mActivity); + outActivities.add(pa.mPref.mComponent); } } } @@ -6822,7 +6999,45 @@ class PackageManagerService extends IPackageManager.Stub { extras.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, nameList); extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, killFlag); extras.putInt(Intent.EXTRA_UID, packageUid); - sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, null); + sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, null, null); + } + + public void setPackageStoppedState(String packageName, boolean stopped) { + PackageSetting pkgSetting; + final int uid = Binder.getCallingUid(); + final int permission = mContext.checkCallingOrSelfPermission( + android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE); + final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED); + synchronized (mPackages) { + pkgSetting = mSettings.mPackages.get(packageName); + if (pkgSetting == null) { + throw new IllegalArgumentException("Unknown package: " + packageName); + } + if (!allowedByPermission && (uid != pkgSetting.userId)) { + throw new SecurityException( + "Permission Denial: attempt to change stopped state from pid=" + + Binder.getCallingPid() + + ", uid=" + uid + ", package uid=" + pkgSetting.userId); + } + if (DEBUG_STOPPED && stopped) { + RuntimeException e = new RuntimeException("here"); + e.fillInStackTrace(); + Slog.i(TAG, "Stopping package " + packageName, e); + } + if (pkgSetting.stopped != stopped) { + pkgSetting.stopped = stopped; + pkgSetting.pkg.mSetStopped = stopped; + if (pkgSetting.notLaunched) { + if (pkgSetting.installerPackageName != null) { + sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, + pkgSetting.name, null, + pkgSetting.installerPackageName, null); + } + pkgSetting.notLaunched = false; + } + scheduleWriteStoppedPackagesLocked(); + } + } } public String getInstallerPackageName(String packageName) { @@ -7172,11 +7387,15 @@ class PackageManagerService extends IPackageManager.Stub { date.setTime(ps.firstInstallTime); pw.println(sdf.format(date)); pw.print(" lastUpdateTime="); date.setTime(ps.lastUpdateTime); pw.println(sdf.format(date)); + if (ps.installerPackageName != null) { + pw.print(" installerPackageName="); pw.println(ps.installerPackageName); + } pw.print(" signatures="); pw.println(ps.signatures); pw.print(" permissionsFixed="); pw.print(ps.permissionsFixed); pw.print(" haveGids="); pw.println(ps.haveGids); pw.print(" pkgFlags=0x"); pw.print(Integer.toHexString(ps.pkgFlags)); pw.print(" installStatus="); pw.print(ps.installStatus); + pw.print(" stopped="); pw.print(ps.stopped); pw.print(" enabled="); pw.println(ps.enabled); if (ps.disabledComponents.size() > 0) { pw.println(" disabledComponents:"); @@ -7278,7 +7497,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 +7506,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) { + } + } } } } @@ -7491,168 +7717,41 @@ class PackageManagerService extends IPackageManager.Stub { } } - static class PreferredActivity extends IntentFilter { - final int mMatch; - final String[] mSetPackages; - final String[] mSetClasses; - final String[] mSetComponents; - final ComponentName mActivity; - final String mShortActivity; - String mParseError; + static class PreferredActivity extends IntentFilter implements PreferredComponent.Callbacks { + final PreferredComponent mPref; PreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity) { super(filter); - mMatch = match&IntentFilter.MATCH_CATEGORY_MASK; - mActivity = activity; - mShortActivity = activity.flattenToShortString(); - mParseError = null; - if (set != null) { - final int N = set.length; - String[] myPackages = new String[N]; - String[] myClasses = new String[N]; - String[] myComponents = new String[N]; - for (int i=0; i<N; i++) { - ComponentName cn = set[i]; - if (cn == null) { - mSetPackages = null; - mSetClasses = null; - mSetComponents = null; - return; - } - myPackages[i] = cn.getPackageName().intern(); - myClasses[i] = cn.getClassName().intern(); - myComponents[i] = cn.flattenToShortString().intern(); - } - mSetPackages = myPackages; - mSetClasses = myClasses; - mSetComponents = myComponents; - } else { - mSetPackages = null; - mSetClasses = null; - mSetComponents = null; - } + mPref = new PreferredComponent(this, match, set, activity); } PreferredActivity(XmlPullParser parser) throws XmlPullParserException, IOException { - mShortActivity = parser.getAttributeValue(null, "name"); - mActivity = ComponentName.unflattenFromString(mShortActivity); - if (mActivity == null) { - mParseError = "Bad activity name " + mShortActivity; - } - String matchStr = parser.getAttributeValue(null, "match"); - mMatch = matchStr != null ? Integer.parseInt(matchStr, 16) : 0; - String setCountStr = parser.getAttributeValue(null, "set"); - int setCount = setCountStr != null ? Integer.parseInt(setCountStr) : 0; - - String[] myPackages = setCount > 0 ? new String[setCount] : null; - String[] myClasses = setCount > 0 ? new String[setCount] : null; - String[] myComponents = setCount > 0 ? new String[setCount] : null; - - int setPos = 0; - - int outerDepth = parser.getDepth(); - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { - continue; - } - - String tagName = parser.getName(); - //Log.i(TAG, "Parse outerDepth=" + outerDepth + " depth=" - // + parser.getDepth() + " tag=" + tagName); - if (tagName.equals("set")) { - String name = parser.getAttributeValue(null, "name"); - if (name == null) { - if (mParseError == null) { - mParseError = "No name in set tag in preferred activity " - + mShortActivity; - } - } else if (setPos >= setCount) { - if (mParseError == null) { - mParseError = "Too many set tags in preferred activity " - + mShortActivity; - } - } else { - ComponentName cn = ComponentName.unflattenFromString(name); - if (cn == null) { - if (mParseError == null) { - mParseError = "Bad set name " + name + " in preferred activity " - + mShortActivity; - } - } else { - myPackages[setPos] = cn.getPackageName(); - myClasses[setPos] = cn.getClassName(); - myComponents[setPos] = name; - setPos++; - } - } - XmlUtils.skipCurrentTag(parser); - } else if (tagName.equals("filter")) { - //Log.i(TAG, "Starting to parse filter..."); - readFromXml(parser); - //Log.i(TAG, "Finished filter: outerDepth=" + outerDepth + " depth=" - // + parser.getDepth() + " tag=" + parser.getName()); - } else { - reportSettingsProblem(Log.WARN, - "Unknown element under <preferred-activities>: " - + parser.getName()); - XmlUtils.skipCurrentTag(parser); - } - } - - if (setPos != setCount) { - if (mParseError == null) { - mParseError = "Not enough set tags (expected " + setCount - + " but found " + setPos + ") in " + mShortActivity; - } - } - - mSetPackages = myPackages; - mSetClasses = myClasses; - mSetComponents = myComponents; + mPref = new PreferredComponent(this, parser); } public void writeToXml(XmlSerializer serializer) throws IOException { - final int NS = mSetClasses != null ? mSetClasses.length : 0; - serializer.attribute(null, "name", mShortActivity); - serializer.attribute(null, "match", Integer.toHexString(mMatch)); - serializer.attribute(null, "set", Integer.toString(NS)); - for (int s=0; s<NS; s++) { - serializer.startTag(null, "set"); - serializer.attribute(null, "name", mSetComponents[s]); - serializer.endTag(null, "set"); - } + mPref.writeToXml(serializer); serializer.startTag(null, "filter"); super.writeToXml(serializer); serializer.endTag(null, "filter"); } - boolean sameSet(List<ResolveInfo> query, int priority) { - if (mSetPackages == null) return false; - final int NQ = query.size(); - final int NS = mSetPackages.length; - int numMatch = 0; - for (int i=0; i<NQ; i++) { - ResolveInfo ri = query.get(i); - if (ri.priority != priority) continue; - ActivityInfo ai = ri.activityInfo; - boolean good = false; - for (int j=0; j<NS; j++) { - if (mSetPackages[j].equals(ai.packageName) - && mSetClasses[j].equals(ai.name)) { - numMatch++; - good = true; - break; - } - } - if (!good) return false; + public boolean onReadTag(String tagName, XmlPullParser parser) + throws XmlPullParserException, IOException { + if (tagName.equals("filter")) { + //Log.i(TAG, "Starting to parse filter..."); + readFromXml(parser); + //Log.i(TAG, "Finished filter: outerDepth=" + outerDepth + " depth=" + // + parser.getDepth() + " tag=" + parser.getName()); + } else { + reportSettingsProblem(Log.WARN, + "Unknown element under <preferred-activities>: " + + parser.getName()); + XmlUtils.skipCurrentTag(parser); } - return numMatch == NS; + return true; } } @@ -7679,8 +7778,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); } } @@ -7707,6 +7805,13 @@ class PackageManagerService extends IPackageManager.Stub { boolean permissionsFixed; boolean haveGids; + // Whether this package is currently stopped, thus can not be + // started until explicitly launched by the user. + public boolean stopped; + + // Set to true if we have never launched this app. + public boolean notLaunched; + /* Explicitly disabled components */ HashSet<String> disabledComponents = new HashSet<String>(0); /* Explicitly enabled components */ @@ -7751,6 +7856,8 @@ class PackageManagerService extends IPackageManager.Stub { permissionsFixed = base.permissionsFixed; haveGids = base.haveGids; + stopped = base.stopped; + notLaunched = base.notLaunched; disabledComponents = (HashSet<String>) base.disabledComponents.clone(); @@ -7807,6 +7914,8 @@ class PackageManagerService extends IPackageManager.Stub { signatures = base.signatures; permissionsFixed = base.permissionsFixed; haveGids = base.haveGids; + stopped = base.stopped; + notLaunched = base.notLaunched; disabledComponents = base.disabledComponents; enabledComponents = base.enabledComponents; enabled = base.enabled; @@ -7905,6 +8014,8 @@ class PackageManagerService extends IPackageManager.Stub { private final File mSettingsFilename; private final File mBackupSettingsFilename; private final File mPackageListFilename; + private final File mStoppedPackagesFilename; + private final File mBackupStoppedPackagesFilename; private final HashMap<String, PackageSetting> mPackages = new HashMap<String, PackageSetting>(); // List of replaced system applications @@ -7923,24 +8034,12 @@ class PackageManagerService extends IPackageManager.Stub { new IntentResolver<PreferredActivity, PreferredActivity>() { @Override protected String packageForFilter(PreferredActivity filter) { - return filter.mActivity.getPackageName(); + return filter.mPref.mComponent.getPackageName(); } @Override protected void dumpFilter(PrintWriter out, String prefix, PreferredActivity filter) { - out.print(prefix); out.print( - Integer.toHexString(System.identityHashCode(filter))); - out.print(' '); - out.print(filter.mActivity.flattenToShortString()); - out.print(" match=0x"); - out.println( Integer.toHexString(filter.mMatch)); - if (filter.mSetComponents != null) { - out.print(prefix); out.println(" Selected from:"); - for (int i=0; i<filter.mSetComponents.length; i++) { - out.print(prefix); out.print(" "); - out.println(filter.mSetComponents[i]); - } - } + filter.mPref.dump(out, prefix, filter); } }; private final HashMap<String, SharedUserSetting> mSharedUsers = @@ -8004,6 +8103,8 @@ class PackageManagerService extends IPackageManager.Stub { mSettingsFilename = new File(systemDir, "packages.xml"); mBackupSettingsFilename = new File(systemDir, "packages-backup.xml"); mPackageListFilename = new File(systemDir, "packages.list"); + mStoppedPackagesFilename = new File(systemDir, "packages-stopped.xml"); + mBackupStoppedPackagesFilename = new File(systemDir, "packages-stopped-backup.xml"); } PackageSetting getPackageLP(PackageParser.Package pkg, PackageSetting origPackage, @@ -8259,6 +8360,16 @@ class PackageManagerService extends IPackageManager.Stub { nativeLibraryPathString, vc, pkgFlags); p.setTimeStamp(codePath.lastModified()); p.sharedUser = sharedUser; + // If this is not a system app, it starts out stopped. + if ((pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) { + if (DEBUG_STOPPED) { + RuntimeException e = new RuntimeException("here"); + e.fillInStackTrace(); + Slog.i(TAG, "Stopping package " + name, e); + } + p.stopped = true; + p.notLaunched = true; + } if (sharedUser != null) { p.userId = sharedUser.userId; } else if (MULTIPLE_APPLICATION_UIDS) { @@ -8305,6 +8416,7 @@ class PackageManagerService extends IPackageManager.Stub { private void insertPackageSettingLP(PackageSetting p, PackageParser.Package pkg) { p.pkg = pkg; pkg.mSetEnabled = p.enabled; + pkg.mSetStopped = p.stopped; final String codePath = pkg.applicationInfo.sourceDir; final String resourcePath = pkg.applicationInfo.publicSourceDir; // Update code path if needed @@ -8521,6 +8633,180 @@ class PackageManagerService extends IPackageManager.Stub { } } + void writeStoppedLP() { + // Keep the old stopped packages around until we know the new ones have + // been successfully written. + if (mStoppedPackagesFilename.exists()) { + // Presence of backup settings file indicates that we failed + // to persist packages earlier. So preserve the older + // backup for future reference since the current packages + // might have been corrupted. + if (!mBackupStoppedPackagesFilename.exists()) { + if (!mStoppedPackagesFilename.renameTo(mBackupStoppedPackagesFilename)) { + Log.wtf(TAG, "Unable to backup package manager stopped packages, " + + "current changes will be lost at reboot"); + return; + } + } else { + mStoppedPackagesFilename.delete(); + Slog.w(TAG, "Preserving older stopped packages backup"); + } + } + + try { + FileOutputStream fstr = new FileOutputStream(mStoppedPackagesFilename); + BufferedOutputStream str = new BufferedOutputStream(fstr); + + //XmlSerializer serializer = XmlUtils.serializerInstance(); + XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(str, "utf-8"); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + + serializer.startTag(null, "stopped-packages"); + + for (PackageSetting pkg : mPackages.values()) { + if (pkg.stopped) { + serializer.startTag(null, "pkg"); + serializer.attribute(null, "name", pkg.name); + if (pkg.notLaunched) { + serializer.attribute(null, "nl", "1"); + } + serializer.endTag(null, "pkg"); + } + } + + serializer.endTag(null, "stopped-packages"); + + serializer.endDocument(); + + str.flush(); + FileUtils.sync(fstr); + str.close(); + + // New settings successfully written, old ones are no longer + // needed. + mBackupStoppedPackagesFilename.delete(); + FileUtils.setPermissions(mStoppedPackagesFilename.toString(), + FileUtils.S_IRUSR|FileUtils.S_IWUSR + |FileUtils.S_IRGRP|FileUtils.S_IWGRP + |FileUtils.S_IROTH, + -1, -1); + + // Done, all is good! + return; + + } catch(java.io.IOException e) { + Log.wtf(TAG, "Unable to write package manager stopped packages, " + + " current changes will be lost at reboot", e); + } + + // Clean up partially written files + if (mStoppedPackagesFilename.exists()) { + if (!mStoppedPackagesFilename.delete()) { + Log.i(TAG, "Failed to clean up mangled file: " + mStoppedPackagesFilename); + } + } + } + + // Note: assumed "stopped" field is already cleared in all packages. + void readStoppedLP() { + FileInputStream str = null; + if (mBackupStoppedPackagesFilename.exists()) { + try { + str = new FileInputStream(mBackupStoppedPackagesFilename); + mReadMessages.append("Reading from backup stopped packages file\n"); + reportSettingsProblem(Log.INFO, "Need to read from backup stopped packages file"); + if (mSettingsFilename.exists()) { + // If both the backup and normal file exist, we + // ignore the normal one since it might have been + // corrupted. + Slog.w(TAG, "Cleaning up stopped packages file " + + mStoppedPackagesFilename); + mStoppedPackagesFilename.delete(); + } + } catch (java.io.IOException e) { + // We'll try for the normal settings file. + } + } + + try { + if (str == null) { + if (!mStoppedPackagesFilename.exists()) { + mReadMessages.append("No stopped packages file found\n"); + reportSettingsProblem(Log.INFO, "No stopped packages file file; " + + "assuming all started"); + // At first boot, make sure no packages are stopped. + // We usually want to have third party apps initialize + // in the stopped state, but not at first boot. + for (PackageSetting pkg : mPackages.values()) { + pkg.stopped = false; + pkg.notLaunched = false; + } + return; + } + str = new FileInputStream(mStoppedPackagesFilename); + } + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(str, null); + + int type; + while ((type=parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + ; + } + + if (type != XmlPullParser.START_TAG) { + mReadMessages.append("No start tag found in stopped packages file\n"); + reportSettingsProblem(Log.WARN, + "No start tag found in package manager stopped packages"); + return; + } + + int outerDepth = parser.getDepth(); + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG + || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("pkg")) { + String name = parser.getAttributeValue(null, "name"); + PackageSetting ps = mPackages.get(name); + if (ps != null) { + ps.stopped = true; + if ("1".equals(parser.getAttributeValue(null, "nl"))) { + ps.notLaunched = true; + } + } else { + Slog.w(TAG, "No package known for stopped package: " + name); + } + XmlUtils.skipCurrentTag(parser); + } else { + Slog.w(TAG, "Unknown element under <stopped-packages>: " + + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + + str.close(); + + } catch(XmlPullParserException e) { + mReadMessages.append("Error reading: " + e.toString()); + reportSettingsProblem(Log.ERROR, "Error reading stopped packages: " + e); + Log.wtf(TAG, "Error reading package manager stopped packages", e); + + } catch(java.io.IOException e) { + mReadMessages.append("Error reading: " + e.toString()); + reportSettingsProblem(Log.ERROR, "Error reading settings: " + e); + Log.wtf(TAG, "Error reading package manager stopped packages", e); + + } + } + void writeLP() { //Debug.startMethodTracing("/data/system/packageprof", 8 * 1024 * 1024); @@ -8533,7 +8819,8 @@ class PackageManagerService extends IPackageManager.Stub { // might have been corrupted. if (!mBackupSettingsFilename.exists()) { if (!mSettingsFilename.renameTo(mBackupSettingsFilename)) { - Slog.w(TAG, "Unable to backup package manager settings, current changes will be lost at reboot"); + Log.wtf(TAG, "Unable to backup package manager settings, " + + " current changes will be lost at reboot"); return; } } else { @@ -8695,17 +8982,21 @@ class PackageManagerService extends IPackageManager.Stub { |FileUtils.S_IROTH, -1, -1); + writeStoppedLP(); + return; } catch(XmlPullParserException e) { - Slog.w(TAG, "Unable to write package manager settings, current changes will be lost at reboot", e); + Log.wtf(TAG, "Unable to write package manager settings, " + + "current changes will be lost at reboot", e); } catch(java.io.IOException e) { - Slog.w(TAG, "Unable to write package manager settings, current changes will be lost at reboot", e); + Log.wtf(TAG, "Unable to write package manager settings, " + + "current changes will be lost at reboot", e); } // Clean up partially written files if (mSettingsFilename.exists()) { if (!mSettingsFilename.delete()) { - Log.i(TAG, "Failed to clean up mangled file: " + mSettingsFilename); + Log.wtf(TAG, "Failed to clean up mangled file: " + mSettingsFilename); } } //Debug.stopMethodTracing(); @@ -8929,6 +9220,7 @@ class PackageManagerService extends IPackageManager.Stub { if (type != XmlPullParser.START_TAG) { mReadMessages.append("No start tag found in settings file\n"); reportSettingsProblem(Log.WARN, "No start tag found in package manager settings"); + Log.wtf(TAG, "No start tag found in package manager settings"); return false; } @@ -8992,12 +9284,12 @@ class PackageManagerService extends IPackageManager.Stub { } catch(XmlPullParserException e) { mReadMessages.append("Error reading: " + e.toString()); reportSettingsProblem(Log.ERROR, "Error reading settings: " + e); - Slog.e(TAG, "Error reading package manager settings", e); + Log.wtf(TAG, "Error reading package manager settings", e); } catch(java.io.IOException e) { mReadMessages.append("Error reading: " + e.toString()); reportSettingsProblem(Log.ERROR, "Error reading settings: " + e); - Slog.e(TAG, "Error reading package manager settings", e); + Log.wtf(TAG, "Error reading package manager settings", e); } @@ -9031,6 +9323,8 @@ class PackageManagerService extends IPackageManager.Stub { } mPendingPackages.clear(); + readStoppedLP(); + mReadMessages.append("Read completed successfully: " + mPackages.size() + " packages, " + mSharedUsers.size() + " shared uids\n"); @@ -9577,12 +9871,12 @@ class PackageManagerService extends IPackageManager.Stub { String tagName = parser.getName(); if (tagName.equals("item")) { PreferredActivity pa = new PreferredActivity(parser); - if (pa.mParseError == null) { + if (pa.mPref.getParseError() == null) { mPreferredActivities.addFilter(pa); } else { reportSettingsProblem(Log.WARN, "Error in package manager settings: <preferred-activity> " - + pa.mParseError + " at " + + pa.mPref.getParseError() + " at " + parser.getPositionDescription()); } } else { @@ -9715,7 +10009,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) { @@ -9835,7 +10130,7 @@ class PackageManagerService extends IPackageManager.Stub { } String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE; - sendPackageBroadcast(action, null, extras, finishedReceiver); + sendPackageBroadcast(action, null, extras, null, finishedReceiver); } } diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java index a6daaefe7102..d80a2cd41c2d 100644 --- a/services/java/com/android/server/PowerManagerService.java +++ b/services/java/com/android/server/PowerManagerService.java @@ -40,7 +40,6 @@ import android.hardware.SensorManager; import android.os.BatteryManager; import android.os.BatteryStats; import android.os.Binder; -import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; @@ -50,7 +49,6 @@ import android.os.Power; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.SystemClock; import android.os.WorkSource; import android.provider.Settings.SettingNotFoundException; @@ -69,14 +67,13 @@ import static android.provider.Settings.System.WINDOW_ANIMATION_SCALE; import static android.provider.Settings.System.TRANSITION_ANIMATION_SCALE; import java.io.FileDescriptor; -import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.Observable; import java.util.Observer; -class PowerManagerService extends IPowerManager.Stub +public class PowerManagerService extends IPowerManager.Stub implements LocalPowerManager, Watchdog.Monitor { private static final String TAG = "PowerManagerService"; @@ -219,6 +216,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 +1140,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 +1172,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 +1608,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 +1741,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 +2213,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 +2308,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 +2553,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 +2584,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 @@ -2705,7 +2686,7 @@ class PowerManagerService extends IPowerManager.Stub } } - void setPolicy(WindowManagerPolicy p) { + public void setPolicy(WindowManagerPolicy p) { synchronized (mLocks) { mPolicy = p; mLocks.notifyAll(); @@ -2729,7 +2710,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 +2727,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 +2883,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 +2966,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/PreferredComponent.java b/services/java/com/android/server/PreferredComponent.java new file mode 100644 index 000000000000..718b05d0227e --- /dev/null +++ b/services/java/com/android/server/PreferredComponent.java @@ -0,0 +1,219 @@ +/* + * 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 com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import android.content.ComponentName; +import android.content.IntentFilter; +import android.content.pm.ActivityInfo; +import android.content.pm.ResolveInfo; +import android.util.Slog; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.List; + +public class PreferredComponent { + public final int mMatch; + public final ComponentName mComponent; + + private final String[] mSetPackages; + private final String[] mSetClasses; + private final String[] mSetComponents; + private final String mShortComponent; + private String mParseError; + + private final Callbacks mCallbacks; + + public interface Callbacks { + public boolean onReadTag(String tagName, XmlPullParser parser) + throws XmlPullParserException, IOException; + } + + public PreferredComponent(Callbacks callbacks, int match, ComponentName[] set, + ComponentName component) { + mCallbacks = callbacks; + mMatch = match&IntentFilter.MATCH_CATEGORY_MASK; + mComponent = component; + mShortComponent = component.flattenToShortString(); + mParseError = null; + if (set != null) { + final int N = set.length; + String[] myPackages = new String[N]; + String[] myClasses = new String[N]; + String[] myComponents = new String[N]; + for (int i=0; i<N; i++) { + ComponentName cn = set[i]; + if (cn == null) { + mSetPackages = null; + mSetClasses = null; + mSetComponents = null; + return; + } + myPackages[i] = cn.getPackageName().intern(); + myClasses[i] = cn.getClassName().intern(); + myComponents[i] = cn.flattenToShortString().intern(); + } + mSetPackages = myPackages; + mSetClasses = myClasses; + mSetComponents = myComponents; + } else { + mSetPackages = null; + mSetClasses = null; + mSetComponents = null; + } + } + + public PreferredComponent(Callbacks callbacks, XmlPullParser parser) + throws XmlPullParserException, IOException { + mCallbacks = callbacks; + mShortComponent = parser.getAttributeValue(null, "name"); + mComponent = ComponentName.unflattenFromString(mShortComponent); + if (mComponent == null) { + mParseError = "Bad activity name " + mShortComponent; + } + String matchStr = parser.getAttributeValue(null, "match"); + mMatch = matchStr != null ? Integer.parseInt(matchStr, 16) : 0; + String setCountStr = parser.getAttributeValue(null, "set"); + int setCount = setCountStr != null ? Integer.parseInt(setCountStr) : 0; + + String[] myPackages = setCount > 0 ? new String[setCount] : null; + String[] myClasses = setCount > 0 ? new String[setCount] : null; + String[] myComponents = setCount > 0 ? new String[setCount] : null; + + int setPos = 0; + + int outerDepth = parser.getDepth(); + int type; + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG + || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + //Log.i(TAG, "Parse outerDepth=" + outerDepth + " depth=" + // + parser.getDepth() + " tag=" + tagName); + if (tagName.equals("set")) { + String name = parser.getAttributeValue(null, "name"); + if (name == null) { + if (mParseError == null) { + mParseError = "No name in set tag in preferred activity " + + mShortComponent; + } + } else if (setPos >= setCount) { + if (mParseError == null) { + mParseError = "Too many set tags in preferred activity " + + mShortComponent; + } + } else { + ComponentName cn = ComponentName.unflattenFromString(name); + if (cn == null) { + if (mParseError == null) { + mParseError = "Bad set name " + name + " in preferred activity " + + mShortComponent; + } + } else { + myPackages[setPos] = cn.getPackageName(); + myClasses[setPos] = cn.getClassName(); + myComponents[setPos] = name; + setPos++; + } + } + XmlUtils.skipCurrentTag(parser); + } else if (!mCallbacks.onReadTag(tagName, parser)) { + Slog.w("PreferredComponent", "Unknown element: " + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + + if (setPos != setCount) { + if (mParseError == null) { + mParseError = "Not enough set tags (expected " + setCount + + " but found " + setPos + ") in " + mShortComponent; + } + } + + mSetPackages = myPackages; + mSetClasses = myClasses; + mSetComponents = myComponents; + } + + public String getParseError() { + return mParseError; + } + + public void writeToXml(XmlSerializer serializer) throws IOException { + final int NS = mSetClasses != null ? mSetClasses.length : 0; + serializer.attribute(null, "name", mShortComponent); + if (mMatch != 0) { + serializer.attribute(null, "match", Integer.toHexString(mMatch)); + } + serializer.attribute(null, "set", Integer.toString(NS)); + for (int s=0; s<NS; s++) { + serializer.startTag(null, "set"); + serializer.attribute(null, "name", mSetComponents[s]); + serializer.endTag(null, "set"); + } + } + + public boolean sameSet(List<ResolveInfo> query, int priority) { + if (mSetPackages == null) return false; + final int NQ = query.size(); + final int NS = mSetPackages.length; + int numMatch = 0; + for (int i=0; i<NQ; i++) { + ResolveInfo ri = query.get(i); + if (ri.priority != priority) continue; + ActivityInfo ai = ri.activityInfo; + boolean good = false; + for (int j=0; j<NS; j++) { + if (mSetPackages[j].equals(ai.packageName) + && mSetClasses[j].equals(ai.name)) { + numMatch++; + good = true; + break; + } + } + if (!good) return false; + } + return numMatch == NS; + } + + public void dump(PrintWriter out, String prefix, Object ident) { + out.print(prefix); out.print( + Integer.toHexString(System.identityHashCode(ident))); + out.print(' '); + out.print(mComponent.flattenToShortString()); + out.print(" match=0x"); + out.println( Integer.toHexString(mMatch)); + if (mSetComponents != null) { + out.print(prefix); out.println(" Selected from:"); + for (int i=0; i<mSetComponents.length; i++) { + out.print(prefix); out.print(" "); + out.println(mSetComponents[i]); + } + } + } +} diff --git a/services/java/com/android/server/ProcessStats.java b/services/java/com/android/server/ProcessStats.java index 43dbcc012cf3..1a12a84a0a63 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 000000000000..61267d07b18c --- /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/ShutdownActivity.java b/services/java/com/android/server/ShutdownActivity.java index 64b9c5d6db8e..c9d4d0117c56 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 e5dfb1892223..8df817789af2 100644 --- a/services/java/com/android/server/StatusBarManagerService.java +++ b/services/java/com/android/server/StatusBarManagerService.java @@ -32,12 +32,14 @@ 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; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.statusbar.StatusBarIconList; import com.android.internal.statusbar.StatusBarNotification; +import com.android.server.wm.WindowManagerService; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -52,11 +54,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 +70,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 +97,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 +106,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 +120,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 +147,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 +244,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 +362,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 +381,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 +418,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 +487,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 +556,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/SystemBackupAgent.java b/services/java/com/android/server/SystemBackupAgent.java index fff1874096d5..80b0174b22c3 100644 --- a/services/java/com/android/server/SystemBackupAgent.java +++ b/services/java/com/android/server/SystemBackupAgent.java @@ -16,18 +16,17 @@ 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 +53,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 +61,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 +73,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 0f03f757017f..d1609630646d 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -18,6 +18,7 @@ package com.android.server; import com.android.server.am.ActivityManagerService; import com.android.server.usb.UsbService; +import com.android.server.wm.WindowManagerService; import com.android.internal.app.ShutdownThread; import com.android.internal.os.BinderInternal; import com.android.internal.os.SamplingProfilerIntegration; @@ -25,6 +26,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; @@ -33,9 +35,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; @@ -47,10 +50,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; @@ -58,11 +63,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() { @@ -121,12 +123,13 @@ class ServerThread extends Thread { WindowManagerService wm = null; BluetoothService bluetooth = null; BluetoothA2dpService bluetoothA2dp = null; - HeadsetObserver headset = null; + WiredAccessoryObserver wiredAccessory = null; DockObserver dock = null; UsbService usb = null; UiModeManagerService uiMode = null; RecognitionManagerService recognition = null; ThrottleService throttle = null; + NetworkTimeUpdateService networkTimeUpdater = null; // Critical services... try { @@ -169,13 +172,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)); @@ -203,11 +206,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); @@ -216,6 +217,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); @@ -235,6 +237,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 { @@ -247,7 +250,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); @@ -346,6 +349,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)); @@ -353,11 +364,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, @@ -382,14 +388,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); @@ -398,7 +396,15 @@ class ServerThread extends Thread { } try { - Slog.i(TAG, "USB Service"); + 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 UsbService(context); ServiceManager.addService(Context.USB_SERVICE, usb); @@ -436,13 +442,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 @@ -457,14 +482,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(); @@ -480,10 +502,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(); @@ -491,7 +524,7 @@ 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; @@ -503,6 +536,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 @@ -514,7 +549,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(); @@ -530,7 +565,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(); } }); @@ -542,39 +579,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; @@ -612,15 +627,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 33702793994a..eb14180766b2 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 a93a6ee0bca8..d841cb3a7766 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/VibratorService.java b/services/java/com/android/server/VibratorService.java index f0b59555b2b9..2fcdb5d5502f 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/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java index 997e75039072..b1a6a9a5a42c 100644 --- a/services/java/com/android/server/WallpaperManagerService.java +++ b/services/java/com/android/server/WallpaperManagerService.java @@ -70,8 +70,6 @@ import com.android.internal.content.PackageMonitor; import com.android.internal.service.wallpaper.ImageWallpaper; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.JournaledFile; -import com.android.server.DevicePolicyManagerService.ActiveAdmin; -import com.android.server.DevicePolicyManagerService.MyPackageMonitor; class WallpaperManagerService extends IWallpaperManager.Stub { static final String TAG = "WallpaperService"; diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index 5aa01119ac71..000023787548 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,79 @@ 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.ScanResult; +import android.net.wifi.SupplicantState; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.net.wifi.WifiNative; -import android.net.wifi.WifiStateTracker; -import android.net.wifi.ScanResult; +import android.net.wifi.WifiStateMachine; 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.net.NetworkInfo.DetailedState; +import android.net.TrafficStats; import android.os.Binder; import android.os.Handler; +import android.os.Messenger; 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.SystemProperties; import android.os.WorkSource; import android.provider.Settings; -import android.util.Slog; import android.text.TextUtils; +import android.util.Slog; 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.AtomicInteger; +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 +97,8 @@ public class WifiService extends IWifiManager.Stub { private boolean mDeviceIdle; private int mPluggedType; - private enum DriverAction {DRIVER_UNLOAD, NO_DRIVER_UNLOAD}; - - // true if the user enabled Wifi while in airplane mode - private boolean mAirplaneModeOverwridden; + /* Chipset supports background scan */ + private final boolean mBackgroundScanSupported; private final LockList mLocks = new LockList(); // some wifi lock statistics @@ -131,10 +116,19 @@ public class WifiService extends IWifiManager.Stub { private final IBatteryStats mBatteryStats; - private INetworkManagementService nwService; - ConnectivityManager mCm; - private WifiWatchdogService mWifiWatchdogService = null; - private String[] mWifiRegexs; + private boolean mEnableTrafficStatsPoll = false; + private int mTrafficStatsPollToken = 0; + private long mTxPkts; + private long mRxPkts; + /* Tracks last reported data activity */ + private int mDataActivity; + private String mInterfaceName; + + /** + * Interval in milliseconds between polling for traffic + * statistics + */ + private static final int POLL_TRAFFIC_STATS_INTERVAL_MSECS = 1000; /** * See {@link Settings.Secure#WIFI_IDLE_MS}. This is the default value if a @@ -143,144 +137,268 @@ 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 ACTION_DEVICE_IDLE = + "com.android.server.WifiManager.action.DEVICE_IDLE"; + + private static final int WIFI_DISABLED = 0; + private static final int WIFI_ENABLED = 1; + /* Wifi enabled while in airplane mode */ + private static final int WIFI_ENABLED_AIRPLANE_OVERRIDE = 2; + + private AtomicInteger mWifiState = new AtomicInteger(WIFI_DISABLED); + private AtomicBoolean mAirplaneModeOn = new AtomicBoolean(false); - private static final String WAKELOCK_TAG = "*wifi*"; + 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; + /** + * Observes the user setting to keep {@link #mNotificationEnabled} in sync. + */ + private NotificationEnabledSettingObserver mNotificationEnabledSettingObserver; + /** + * The {@link System#currentTimeMillis()} must be at least this value for us + * to show the notification again. + */ + private long mNotificationRepeatTime; + /** + * The Notification object given to the NotificationManager. + */ + 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; + /** + * 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 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; /** - * Temporary for computing UIDS that are responsible for starting WIFI. - * Protected by mWifiStateTracker lock. + * Asynchronous channel to WifiStateMachine */ - private final WorkSource mTmpWorkSource = new WorkSource(); + private AsyncChannel mWifiStateMachineChannel; - /* - * Last UID that asked to enable WIFI. + /** + * Clients receiving asynchronous messages */ - private int mLastEnableUid = Process.myUid(); + private List<AsyncChannel> mClients = new ArrayList<AsyncChannel>(); - /* - * Last UID that asked to enable WIFI AP. + /** + * Handles client connections */ - private int mLastApEnableUid = Process.myUid(); + private class AsyncServiceHandler extends Handler { + + AsyncServiceHandler(android.os.Looper looper) { + super(looper); + } + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: { + if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { + Slog.d(TAG, "New client listening to asynchronous messages"); + mClients.add((AsyncChannel) msg.obj); + } else { + Slog.e(TAG, "Client connection failure, error=" + msg.arg1); + } + break; + } + case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: { + AsyncChannel ac = new AsyncChannel(); + ac.connect(mContext, this, msg.replyTo); + break; + } + case WifiManager.CMD_ENABLE_TRAFFIC_STATS_POLL: { + mEnableTrafficStatsPoll = (msg.arg1 == 1); + mTrafficStatsPollToken++; + if (mEnableTrafficStatsPoll) { + notifyOnDataActivity(); + sendMessageDelayed(Message.obtain(this, WifiManager.CMD_TRAFFIC_STATS_POLL, + mTrafficStatsPollToken, 0), POLL_TRAFFIC_STATS_INTERVAL_MSECS); + } + break; + } + case WifiManager.CMD_TRAFFIC_STATS_POLL: { + if (msg.arg1 == mTrafficStatsPollToken) { + notifyOnDataActivity(); + sendMessageDelayed(Message.obtain(this, WifiManager.CMD_TRAFFIC_STATS_POLL, + mTrafficStatsPollToken, 0), POLL_TRAFFIC_STATS_INTERVAL_MSECS); + } + break; + } + case WifiManager.CMD_CONNECT_NETWORK: { + if (msg.obj != null) { + mWifiStateMachine.connectNetwork((WifiConfiguration)msg.obj); + } else { + mWifiStateMachine.connectNetwork(msg.arg1); + } + break; + } + case WifiManager.CMD_SAVE_NETWORK: { + mWifiStateMachine.saveNetwork((WifiConfiguration)msg.obj); + break; + } + case WifiManager.CMD_FORGET_NETWORK: { + mWifiStateMachine.forgetNetwork(msg.arg1); + break; + } + case WifiManager.CMD_START_WPS: { + //replyTo has the original source + mWifiStateMachine.startWps(msg.replyTo, (WpsConfiguration)msg.obj); + break; + } + default: { + Slog.d(TAG, "WifiServicehandler.handleMessage ignoring msg=" + msg); + break; + } + } + } + } + private AsyncServiceHandler mAsyncServiceHandler; /** - * Number of allowed radio frequency channels in various regulatory domains. - * This list is sufficient for 802.11b/g networks (2.4GHz range). + * Handles interaction with WifiStateMachine */ - private static int[] sValidRegulatoryChannelCounts = new int[] {11, 13, 14}; + private class WifiStateMachineHandler extends Handler { + private AsyncChannel mWsmChannel; - private static final String ACTION_DEVICE_IDLE = - "com.android.server.WifiManager.action.DEVICE_IDLE"; + WifiStateMachineHandler(android.os.Looper looper) { + super(looper); + mWsmChannel = new AsyncChannel(); + mWsmChannel.connect(mContext, this, mWifiStateMachine.getHandler()); + } - WifiService(Context context, WifiStateTracker tracker) { - mContext = context; - mWifiStateTracker = tracker; - mWifiStateTracker.enableRssiPolling(true); - mBatteryStats = BatteryStatsService.getService(); + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: { + if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { + mWifiStateMachineChannel = mWsmChannel; + } else { + Slog.e(TAG, "WifiStateMachine connection failure, error=" + msg.arg1); + mWifiStateMachineChannel = null; + } + break; + } + default: { + Slog.d(TAG, "WifiStateMachineHandler.handleMessage ignoring msg=" + msg); + break; + } + } + } + } + WifiStateMachineHandler mWifiStateMachineHandler; - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - nwService = INetworkManagementService.Stub.asInterface(b); + /** + * Temporary for computing UIDS that are responsible for starting WIFI. + * Protected by mWifiStateTracker lock. + */ + private final WorkSource mTmpWorkSource = new WorkSource(); - 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(); - } - }; + WifiService(Context context) { + mContext = context; - HandlerThread wifiThread = new HandlerThread("WifiService"); - wifiThread.start(); - mWifiHandler = new WifiHandler(wifiThread.getLooper()); + mInterfaceName = SystemProperties.get("wifi.interface", "wlan0"); - mWifiStateTracker.setWifiState(WIFI_STATE_DISABLED); - mWifiApState = WIFI_AP_STATE_DISABLED; + mWifiStateMachine = new WifiStateMachine(mContext, mInterfaceName); + 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); - mContext.registerReceiver( new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - // clear our flag indicating the user has overwridden airplane mode - mAirplaneModeOverwridden = false; - // on airplane disable, restore Wifi if the saved state indicates so - if (!isAirplaneModeOn() && testAndClearWifiSavedState()) { - persistWifiEnabled(true); + mAirplaneModeOn.set(isAirplaneModeOn()); + /* On airplane mode disable, restore wifi state if necessary */ + if (!mAirplaneModeOn.get() && (testAndClearWifiSavedState() || + mWifiState.get() == WIFI_ENABLED_AIRPLANE_OVERRIDE)) { + persistWifiEnabled(true); } updateWifiState(); } }, new IntentFilter(Intent.ACTION_AIRPLANE_MODE_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) { + 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: + evaluateTrafficStatsPolling(); + resetNotification(); + break; + } + } else if (intent.getAction().equals( + WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { + checkAndSetNotification(); + } + } + }, filter); + + HandlerThread wifiThread = new HandlerThread("WifiService"); + wifiThread.start(); + mAsyncServiceHandler = new AsyncServiceHandler(wifiThread.getLooper()); + mWifiStateMachineHandler = new WifiStateMachineHandler(wifiThread.getLooper()); - ArrayList<String> available = intent.getStringArrayListExtra( - ConnectivityManager.EXTRA_AVAILABLE_TETHER); - ArrayList<String> active = intent.getStringArrayListExtra( - ConnectivityManager.EXTRA_ACTIVE_TETHER); - updateTetherState(available, active); + // 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(); - } - },new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED)); + mBackgroundScanSupported = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_wifi_background_scan_support); } /** @@ -289,61 +407,16 @@ public class WifiService extends IWifiManager.Stub { * * This function is used only at boot time */ - public void startWifi() { - /* Start if Wi-Fi is enabled or the saved state indicates Wi-Fi was on */ - boolean wifiEnabled = !isAirplaneModeOn() - && (getPersistedWifiEnabled() || testAndClearWifiSavedState()); + public void checkAndStartWifi() { + mAirplaneModeOn.set(isAirplaneModeOn()); + mWifiState.set(getPersistedWifiState()); + /* Start if Wi-Fi should be enabled or the saved state indicates Wi-Fi was on */ + boolean wifiEnabled = shouldWifiBeEnabled() || testAndClearWifiSavedState(); Slog.i(TAG, "WifiService starting up with Wi-Fi " + (wifiEnabled ? "enabled" : "disabled")); setWifiEnabled(wifiEnabled); } - private void updateTetherState(ArrayList<String> available, ArrayList<String> tethered) { - - boolean wifiTethered = false; - boolean wifiAvailable = false; - - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); - - mCm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - mWifiRegexs = mCm.getTetherableWifiRegexs(); - - for (String intf : available) { - for (String regex : mWifiRegexs) { - if (intf.matches(regex)) { - - InterfaceConfiguration ifcg = null; - try { - 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"; - - 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); - return; - } - - if(mCm.tether(intf) != ConnectivityManager.TETHER_ERROR_NO_ERROR) { - Slog.e(TAG, "Error tethering "+intf); - } - break; - } - } - } - } - private boolean testAndClearWifiSavedState() { final ContentResolver cr = mContext.getContentResolver(); int wifiSavedState = 0; @@ -357,33 +430,51 @@ public class WifiService extends IWifiManager.Stub { return (wifiSavedState == 1); } - private boolean getPersistedWifiEnabled() { + private int getPersistedWifiState() { final ContentResolver cr = mContext.getContentResolver(); try { - return Settings.Secure.getInt(cr, Settings.Secure.WIFI_ON) == 1; + return Settings.Secure.getInt(cr, Settings.Secure.WIFI_ON); } catch (Settings.SettingNotFoundException e) { - Settings.Secure.putInt(cr, Settings.Secure.WIFI_ON, 0); - return false; + Settings.Secure.putInt(cr, Settings.Secure.WIFI_ON, WIFI_DISABLED); + return WIFI_DISABLED; + } + } + + private boolean shouldWifiBeEnabled() { + if (mAirplaneModeOn.get()) { + return mWifiState.get() == WIFI_ENABLED_AIRPLANE_OVERRIDE; + } else { + return mWifiState.get() != WIFI_DISABLED; } } private void persistWifiEnabled(boolean enabled) { final ContentResolver cr = mContext.getContentResolver(); - Settings.Secure.putInt(cr, Settings.Secure.WIFI_ON, enabled ? 1 : 0); + if (enabled) { + if (isAirplaneModeOn() && isAirplaneToggleable()) { + mWifiState.set(WIFI_ENABLED_AIRPLANE_OVERRIDE); + } else { + mWifiState.set(WIFI_ENABLED); + } + } else { + mWifiState.set(WIFI_DISABLED); + } + Settings.Secure.putInt(cr, Settings.Secure.WIFI_ON, mWifiState.get()); } - 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 (mWifiStateMachineChannel != null) { + return mWifiStateMachine.syncPingSupplicant(mWifiStateMachineChannel); + } else { + Slog.e(TAG, "mWifiStateMachineChannel is not initialized"); + return false; + } } /** @@ -391,9 +482,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 +508,39 @@ 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()); - } - - 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; + if (DBG) { + Slog.e(TAG, "Invoking mWifiStateMachine.setWifiEnabled\n"); } - /** - * 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 (!mIsReceiverRegistered) { + registerForBroadcasts(); + mIsReceiverRegistered = true; } - if (!mWifiStateTracker.startSupplicant()) { - mWifiStateTracker.unloadDriver(); - Slog.e(TAG, "Failed to start supplicant daemon."); - setWifiEnabledState(WIFI_STATE_UNKNOWN, uid); - return false; - } - - 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 +551,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,159 +621,44 @@ 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; if (wifiConfig == null) return; + int authType = wifiConfig.getAuthType(); Settings.Secure.putString(cr, Settings.Secure.WIFI_AP_SSID, wifiConfig.SSID); - isWpa = wifiConfig.allowedKeyManagement.get(KeyMgmt.WPA_PSK); - Settings.Secure.putInt(cr, - Settings.Secure.WIFI_AP_SECURITY, - isWpa ? KeyMgmt.WPA_PSK : KeyMgmt.NONE); - if (isWpa) + Settings.Secure.putInt(cr, Settings.Secure.WIFI_AP_SECURITY, authType); + if (authType != KeyMgmt.NONE) Settings.Secure.putString(cr, Settings.Secure.WIFI_AP_PASSWD, wifiConfig.preSharedKey); } /** - * 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 +667,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 +677,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 (mWifiStateMachineChannel != null) { + return mWifiStateMachine.syncAddOrUpdateNetwork(mWifiStateMachineChannel, config); + } else { + Slog.e(TAG, "mWifiStateMachineChannel 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 +693,12 @@ public class WifiService extends IWifiManager.Stub { */ public boolean removeNetwork(int netId) { enforceChangePermission(); - - return mWifiStateTracker.removeNetwork(netId); + if (mWifiStateMachineChannel != null) { + return mWifiStateMachine.syncRemoveNetwork(mWifiStateMachineChannel, netId); + } else { + Slog.e(TAG, "mWifiStateMachineChannel is not initialized"); + return false; + } } /** @@ -1326,14 +710,13 @@ 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 (mWifiStateMachineChannel != null) { + return mWifiStateMachine.syncEnableNetwork(mWifiStateMachineChannel, netId, + disableOthers); + } else { + Slog.e(TAG, "mWifiStateMachineChannel is not initialized"); + return false; } - return result; } /** @@ -1344,8 +727,12 @@ public class WifiService extends IWifiManager.Stub { */ public boolean disableNetwork(int netId) { enforceChangePermission(); - - return mWifiStateTracker.disableNetwork(netId); + if (mWifiStateMachineChannel != null) { + return mWifiStateMachine.syncDisableNetwork(mWifiStateMachineChannel, netId); + } else { + Slog.e(TAG, "mWifiStateMachineChannel is not initialized"); + return false; + } } /** @@ -1358,7 +745,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 +755,153 @@ 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 (mWifiStateMachineChannel != null) { + return mWifiStateMachine.syncSaveConfig(mWifiStateMachineChannel); + } else { + Slog.e(TAG, "mWifiStateMachineChannel 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; - } - - if (mWifiHandler == null) return false; - - Message.obtain(mWifiHandler, - MESSAGE_SET_CHANNELS, numChannels, (persist ? 1 : 0)).sendToTarget(); - - return true; + mWifiStateMachine.setDriverStart(false); } + /** - * 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#addToBlacklist} + * */ - 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 addToBlacklist(String bssid) { + enforceChangePermission(); + + mWifiStateMachine.addToBlacklist(bssid); } /** - * 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 + * see {@link android.net.wifi.WifiManager#clearBlacklist} + * */ - public int getNumAllowedChannels() { - int numChannels; - - enforceAccessPermission(); + public void clearBlacklist() { + enforceChangePermission(); - /* - * 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; + mWifiStateMachine.clearBlacklist(); } /** - * Return the list of valid values for the number of allowed radio channels - * for various regulatory domains. - * @return the list of channel counts + * Get a reference to handler. This is used by a client to establish + * an AsyncChannel communication with WifiService */ - public int[] getValidChannelCounts() { + public Messenger getMessenger() { + /* Enforce the highest permissions + TODO: when we consider exposing the asynchronous API, think about + how to provide both access and change permissions seperately + */ enforceAccessPermission(); - return sValidRegulatoryChannelCounts; + enforceChangePermission(); + return new Messenger(mAsyncServiceHandler); } /** - * Return the DHCP-assigned addresses from the last successful DHCP request, - * if any. - * @return the DHCP information + * Get the IP and proxy configuration file */ - public DhcpInfo getDhcpInfo() { + public String getConfigFile() { enforceAccessPermission(); - return mWifiStateTracker.getDhcpInfo(); + return mWifiStateMachine.getConfigFile(); } private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @@ -1653,7 +911,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 +924,24 @@ 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(); + evaluateTrafficStatsPolling(); + 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); + evaluateTrafficStatsPolling(); + 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 +949,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 +972,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 +998,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 +1015,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 +1039,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,65 +1052,19 @@ 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 lockHeld = mLocks.hasLocks(); int strongestLockMode = WifiManager.WIFI_MODE_FULL; - boolean wifiShouldBeEnabled = wifiEnabled && !airplaneMode; boolean wifiShouldBeStarted = !mDeviceIdle || lockHeld; if (lockHeld) { @@ -1871,43 +1075,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 (mAirplaneModeOn.get()) { + 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 (shouldWifiBeEnabled()) { + 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 +1104,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 +1132,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 +1141,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 +1175,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 +1252,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 +1286,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 +1298,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 +1322,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 +1330,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 +1469,7 @@ public class WifiService extends IWifiManager.Stub { if (mMulticasters.size() != 0) { return; } else { - mWifiStateTracker.startPacketFiltering(); + mWifiStateMachine.startPacketFiltering(); } } } @@ -2416,7 +1484,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 +1521,7 @@ public class WifiService extends IWifiManager.Stub { removed.unlinkDeathRecipient(); } if (mMulticasters.size() == 0) { - mWifiStateTracker.startPacketFiltering(); + mWifiStateMachine.startPacketFiltering(); } Long ident = Binder.clearCallingIdentity(); @@ -2472,4 +1540,188 @@ public class WifiService extends IWifiManager.Stub { return (mMulticasters.size() > 0); } } + + /** + * Evaluate if traffic stats polling is needed based on + * connection and screen on status + */ + private void evaluateTrafficStatsPolling() { + Message msg; + if (mNetworkInfo.getDetailedState() == DetailedState.CONNECTED && !mScreenOff) { + msg = Message.obtain(mAsyncServiceHandler, + WifiManager.CMD_ENABLE_TRAFFIC_STATS_POLL, 1, 0); + } else { + msg = Message.obtain(mAsyncServiceHandler, + WifiManager.CMD_ENABLE_TRAFFIC_STATS_POLL, 0, 0); + } + msg.sendToTarget(); + } + + private void notifyOnDataActivity() { + long sent, received; + long preTxPkts = mTxPkts, preRxPkts = mRxPkts; + int dataActivity = WifiManager.DATA_ACTIVITY_NONE; + + mTxPkts = TrafficStats.getTxPackets(mInterfaceName); + mRxPkts = TrafficStats.getRxPackets(mInterfaceName); + + if (preTxPkts > 0 || preRxPkts > 0) { + sent = mTxPkts - preTxPkts; + received = mRxPkts - preRxPkts; + if (sent > 0) { + dataActivity |= WifiManager.DATA_ACTIVITY_OUT; + } + if (received > 0) { + dataActivity |= WifiManager.DATA_ACTIVITY_IN; + } + + if (dataActivity != mDataActivity && !mScreenOff) { + mDataActivity = dataActivity; + for (AsyncChannel client : mClients) { + client.sendMessage(WifiManager.DATA_ACTIVITY_NOTIFICATION, mDataActivity); + } + } + } + } + + + 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 000000000000..e69de29bb2d1 --- /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 445dd0366d57..94531bbb8e2c 100644 --- a/services/java/com/android/server/WifiWatchdogService.java +++ b/services/java/com/android/server/WifiWatchdogService.java @@ -22,12 +22,12 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObserver; +import android.net.ConnectivityManager; +import android.net.LinkProperties; import android.net.NetworkInfo; -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; @@ -43,6 +43,7 @@ import java.net.InetAddress; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; +import java.util.Collection; import java.util.List; import java.util.Random; @@ -77,9 +78,9 @@ public class WifiWatchdogService { private Context mContext; private ContentResolver mContentResolver; - private WifiStateTracker mWifiStateTracker; private WifiManager mWifiManager; - + private ConnectivityManager mConnectivityManager; + /** * The main watchdog thread. */ @@ -108,10 +109,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 +275,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. @@ -312,19 +313,26 @@ public class WifiWatchdogService { } /** - * Gets the DNS of the current AP. + * Gets the first DNS of the current AP. * - * @return The DNS of the current AP. + * @return The first DNS of the current AP. */ - private int getDns() { - DhcpInfo addressInfo = mWifiManager.getDhcpInfo(); - if (addressInfo != null) { - return addressInfo.dns1; - } else { - return -1; + private InetAddress getDns() { + if (mConnectivityManager == null) { + mConnectivityManager = (ConnectivityManager)mContext.getSystemService( + Context.CONNECTIVITY_SERVICE); } + + LinkProperties linkProperties = mConnectivityManager.getLinkProperties( + ConnectivityManager.TYPE_WIFI); + if (linkProperties == null) return null; + + Collection<InetAddress> dnses = linkProperties.getDnses(); + if (dnses == null || dnses.size() == 0) return null; + + return dnses.iterator().next(); } - + /** * Checks whether the DNS can be reached using multiple attempts according * to the current setting values. @@ -332,29 +340,28 @@ public class WifiWatchdogService { * @return Whether the DNS is reachable */ private boolean checkDnsConnectivity() { - int dns = getDns(); - if (dns == -1) { + InetAddress dns = getDns(); + if (dns == null) { if (V) { myLogV("checkDnsConnectivity: Invalid DNS, returning false"); } return false; } - + if (V) { - myLogV("checkDnsConnectivity: Checking 0x" + - Integer.toHexString(Integer.reverseBytes(dns)) + " for connectivity"); + myLogV("checkDnsConnectivity: Checking " + dns.getHostAddress() + " for connectivity"); } int numInitialIgnoredPings = getInitialIgnoredPingCount(); int numPings = getPingCount(); int pingDelay = getPingDelayMs(); int acceptableLoss = getAcceptablePacketLossPercentage(); - + /** See {@link Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT} */ int ignoredPingCounter = 0; int pingCounter = 0; int successCounter = 0; - + // No connectivity check needed if (numPings == 0) { return true; @@ -373,20 +380,20 @@ public class WifiWatchdogService { pingCounter++; successCounter++; } - + if (V) { Slog.v(TAG, (dnsAlive ? " +" : " Ignored: -")); } if (shouldCancel()) return false; - + try { Thread.sleep(pingDelay); } catch (InterruptedException e) { Slog.w(TAG, "Interrupted while pausing between pings", e); } } - + // Do the pings that we use to measure packet loss for (; pingCounter < numPings; pingCounter++) { if (shouldCancel()) return false; @@ -403,40 +410,41 @@ public class WifiWatchdogService { } if (shouldCancel()) return false; - + try { Thread.sleep(pingDelay); } catch (InterruptedException e) { Slog.w(TAG, "Interrupted while pausing between pings", e); } } - + int packetLossPercentage = 100 * (numPings - successCounter) / numPings; if (D) { Slog.d(TAG, packetLossPercentage + "% packet loss (acceptable is " + acceptableLoss + "%)"); } - + return !shouldCancel() && (packetLossPercentage <= acceptableLoss); } private boolean backgroundCheckDnsConnectivity() { - int dns = getDns(); - if (false && V) { - myLogV("backgroundCheckDnsConnectivity: Background checking " + dns + - " for connectivity"); - } - - if (dns == -1) { + InetAddress dns = getDns(); + + if (dns == null) { if (V) { myLogV("backgroundCheckDnsConnectivity: DNS is empty, returning false"); } return false; } - + + if (false && V) { + myLogV("backgroundCheckDnsConnectivity: Background checking " + + dns.getHostAddress() + " for connectivity"); + } + return DnsPinger.isDnsReachable(dns, getBackgroundCheckTimeoutMs()); } - + /** * Signals the current action to cancel. */ @@ -751,7 +759,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 +770,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 +865,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 +936,7 @@ public class WifiWatchdogService { * should revert anything done by the watchdog monitoring. */ private void handleReset() { - mWifiStateTracker.clearBlacklist(); + mWifiManager.clearBlacklist(); setIdleState(true); } @@ -1151,7 +1153,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(); } @@ -1215,43 +1217,37 @@ public class WifiWatchdogService { /** Used to generate IDs */ private static Random sRandom = new Random(); - - static boolean isDnsReachable(int dns, int timeout) { + + static boolean isDnsReachable(InetAddress dnsAddress, int timeout) { DatagramSocket socket = null; try { socket = new DatagramSocket(); - + // Set some socket properties socket.setSoTimeout(timeout); - + byte[] buf = new byte[DNS_QUERY_BASE_SIZE]; fillQuery(buf); - + // Send the DNS query - byte parts[] = new byte[4]; - parts[0] = (byte)(dns & 0xff); - parts[1] = (byte)((dns >> 8) & 0xff); - parts[2] = (byte)((dns >> 16) & 0xff); - parts[3] = (byte)((dns >> 24) & 0xff); - InetAddress dnsAddress = InetAddress.getByAddress(parts); DatagramPacket packet = new DatagramPacket(buf, buf.length, dnsAddress, DNS_PORT); socket.send(packet); - + // Wait for reply (blocks for the above timeout) DatagramPacket replyPacket = new DatagramPacket(buf, buf.length); socket.receive(replyPacket); // If a timeout occurred, an exception would have been thrown. We got a reply! return true; - + } catch (SocketException e) { if (V) { Slog.v(TAG, "DnsPinger.isReachable received SocketException", e); } return false; - + } catch (UnknownHostException e) { if (V) { Slog.v(TAG, "DnsPinger.isReachable is unable to resolve the DNS host", e); diff --git a/services/java/com/android/server/WiredAccessoryObserver.java b/services/java/com/android/server/WiredAccessoryObserver.java new file mode 100644 index 000000000000..e45c3685b1c4 --- /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 883fdda5347b..50fffd004723 100755..100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -24,8 +24,8 @@ import com.android.server.ProcessMap; import com.android.server.ProcessStats; import com.android.server.SystemServer; import com.android.server.Watchdog; -import com.android.server.WindowManagerService; import com.android.server.am.ActivityStack.ActivityState; +import com.android.server.wm.WindowManagerService; import dalvik.system.Zygote; @@ -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; @@ -178,6 +180,7 @@ public final class ActivityManagerService extends ActivityManagerNative static final int STOCK_PM_FLAGS = PackageManager.GET_SHARED_LIBRARY_FILES; private static final String SYSTEM_SECURE = "ro.secure"; + private static final String SYSTEM_DEBUGGABLE = "ro.debuggable"; // This is the maximum number of application processes we would like // to have running. Due to the asynchronous nature of things, we can @@ -554,7 +557,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. @@ -714,6 +717,8 @@ public final class ActivityManagerService extends ActivityManagerNative final private SparseArray<HashMap<Uri, UriPermission>> mGrantedUriPermissions = new SparseArray<HashMap<Uri, UriPermission>>(); + CoreSettingsObserver mCoreSettingsObserver; + /** * Thread-local storage used to carry caller permissions over through * indirect content-provider access. @@ -959,6 +964,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 +1117,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 +1327,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 +1380,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 +2029,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 +2085,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 +2124,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 +2141,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 +2152,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 +2270,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 +2292,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 +2496,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 +2615,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 +3063,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 { @@ -3098,6 +3148,13 @@ public final class ActivityManagerService extends ActivityManagerNative return; } forceStopPackageLocked(packageName, pkgUid); + try { + pm.setPackageStoppedState(packageName, true); + } catch (RemoteException e) { + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Failed trying to unstop package " + + packageName + ": " + e); + } } } finally { Binder.restoreCallingIdentity(callingId); @@ -3537,7 +3594,8 @@ public final class ActivityManagerService extends ActivityManagerNative app.instrumentationClass, app.instrumentationProfileFile, app.instrumentationArguments, app.instrumentationWatcher, testMode, isRestrictedBackupMode || !normalMode, - mConfiguration, getCommonServicesLocked()); + mConfiguration, getCommonServicesLocked(), + mCoreSettingsObserver.getCoreSettingsLocked()); updateLruProcessLocked(app, false, true); app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis(); } catch (Exception e) { @@ -3738,22 +3796,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 +3820,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 +3906,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 +3952,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 +3962,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 +3983,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 +4162,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 +4179,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 +4215,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 +4365,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 +4397,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 +4465,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 +4529,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 +4551,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 +4856,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 +4905,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; @@ -4833,6 +4982,8 @@ public final class ActivityManagerService extends ActivityManagerNative IPackageManager pm = AppGlobals.getPackageManager(); + ActivityRecord resumed = mMainStack.mResumedActivity; + final int N = mRecentTasks.size(); ArrayList<ActivityManager.RecentTaskInfo> res = new ArrayList<ActivityManager.RecentTaskInfo>( @@ -4846,9 +4997,11 @@ public final class ActivityManagerService extends ActivityManagerNative ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo(); rti.id = tr.numActivities > 0 ? tr.taskId : -1; + rti.persistentId = tr.taskId; rti.baseIntent = new Intent( tr.intent != null ? tr.intent : tr.affinityIntent); rti.origActivity = tr.origActivity; + rti.description = tr.lastDescription; if ((flags&ActivityManager.RECENT_IGNORE_UNAVAILABLE) != 0) { // Check whether this activity is currently available. @@ -4876,6 +5029,26 @@ public final class ActivityManagerService extends ActivityManagerNative } } + public Bitmap getTaskThumbnail(int id) { + synchronized (this) { + enforceCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER, + "getTaskThumbnail()"); + ActivityRecord resumed = mMainStack.mResumedActivity; + final int N = mRecentTasks.size(); + for (int i=0; i<N; i++) { + TaskRecord tr = mRecentTasks.get(i); + if (tr.taskId == id) { + if (resumed != null && resumed.task == tr) { + return resumed.stack.screenshotActivities(resumed); + } else { + return tr.lastThumbnail; + } + } + } + } + return null; + } + private final int findAffinityTaskTopLocked(int startIndex, String affinity) { int j; TaskRecord startTask = ((ActivityRecord)mMainStack.mHistory.get(startIndex)).task; @@ -4916,7 +5089,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 +5104,14 @@ 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_NO_USER_ACTION) == 0) { + mMainStack.mUserLeaving = true; + } + 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 +5119,14 @@ 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_NO_USER_ACTION) == 0) { + mMainStack.mUserLeaving = true; + } + 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 +5355,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 +5372,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 +5393,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; } @@ -5358,20 +5555,34 @@ public final class ActivityManagerService extends ActivityManagerNative // started. if (i >= N) { final long origId = Binder.clearCallingIdentity(); - ProcessRecord proc = startProcessLocked(cpi.processName, - cpr.appInfo, false, 0, "content provider", - new ComponentName(cpi.applicationInfo.packageName, - cpi.name), false); - if (proc == null) { - Slog.w(TAG, "Unable to launch app " - + cpi.applicationInfo.packageName + "/" - + cpi.applicationInfo.uid + " for provider " - + name + ": process is bad"); - return null; - } - cpr.launchingApp = proc; - mLaunchingProviders.add(cpr); - Binder.restoreCallingIdentity(origId); + + try { + // Content provider is now in use, its package can't be stopped. + try { + AppGlobals.getPackageManager().setPackageStoppedState( + cpr.appInfo.packageName, false); + } catch (RemoteException e) { + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Failed trying to unstop package " + + cpr.appInfo.packageName + ": " + e); + } + + ProcessRecord proc = startProcessLocked(cpi.processName, + cpr.appInfo, false, 0, "content provider", + new ComponentName(cpi.applicationInfo.packageName, + cpi.name), false); + if (proc == null) { + Slog.w(TAG, "Unable to launch app " + + cpi.applicationInfo.packageName + "/" + + cpi.applicationInfo.uid + " for provider " + + name + ": process is bad"); + return null; + } + cpr.launchingApp = proc; + mLaunchingProviders.add(cpr); + } finally { + Binder.restoreCallingIdentity(origId); + } } // Make sure the provider is published (the same provider class @@ -5570,6 +5781,8 @@ public final class ActivityManagerService extends ActivityManagerNative if (providers != null) { mSystemThread.installSystemProviders(providers); } + + mSelf.mCoreSettingsObserver = new CoreSettingsObserver(mSelf); } /** @@ -5628,6 +5841,16 @@ public final class ActivityManagerService extends ActivityManagerNative updateLruProcessLocked(app, true, true); } + // This package really, really can not be stopped. + try { + AppGlobals.getPackageManager().setPackageStoppedState( + info.packageName, false); + } catch (RemoteException e) { + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Failed trying to unstop package " + + info.packageName + ": " + e); + } + if ((info.flags&(ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PERSISTENT)) == (ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PERSISTENT)) { app.persistent = true; @@ -5689,16 +5912,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 +5947,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 +5971,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 +6039,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 +6123,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 +6161,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; @@ -5925,7 +6198,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } - public boolean killPids(int[] pids, String pReason) { + public boolean killPids(int[] pids, String pReason, boolean secure) { if (Binder.getCallingUid() != Process.SYSTEM_UID) { throw new SecurityException("killPids only available to the system"); } @@ -5948,11 +6221,18 @@ public final class ActivityManagerService extends ActivityManagerNative } } - // If the worse oom_adj is somewhere in the hidden proc LRU range, + // If the worst oom_adj is somewhere in the hidden proc LRU range, // then constrain it so we will kill all hidden procs. if (worstType < EMPTY_APP_ADJ && worstType > HIDDEN_APP_MIN_ADJ) { worstType = HIDDEN_APP_MIN_ADJ; } + + // If this is not a secure call, don't let it kill processes that + // are important. + if (!secure && worstType < SECONDARY_SERVER_ADJ) { + worstType = SECONDARY_SERVER_ADJ; + } + Slog.w(TAG, "Killing processes " + reason + " at adjustment " + worstType); for (int i=0; i<pids.length; i++) { ProcessRecord proc = mPidsSelfLocked.get(pids[i]); @@ -6378,7 +6658,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 +6667,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 @@ -6399,8 +6682,10 @@ public final class ActivityManagerService extends ActivityManagerNative mProcessCrashTimes.remove(app.info.processName, app.info.uid); app.removed = true; removeProcessLocked(app, false); + mMainStack.resumeTopActivityLocked(null); return false; } + mMainStack.resumeTopActivityLocked(null); } else { ActivityRecord r = mMainStack.topRunningActivityLocked(null); if (r.app == app) { @@ -6411,7 +6696,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 +6705,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, @@ -6506,7 +6791,7 @@ public final class ActivityManagerService extends ActivityManagerNative * @param crashInfo describing the exception */ public void handleApplicationCrash(IBinder app, ApplicationErrorReport.CrashInfo crashInfo) { - ProcessRecord r = findAppProcess(app); + ProcessRecord r = findAppProcess(app, "Crash"); EventLog.writeEvent(EventLogTags.AM_CRASH, Binder.getCallingPid(), app == null ? "system" : (r == null ? "unknown" : r.processName), @@ -6525,10 +6810,13 @@ public final class ActivityManagerService extends ActivityManagerNative IBinder app, int violationMask, StrictMode.ViolationInfo info) { - ProcessRecord r = findAppProcess(app); + ProcessRecord r = findAppProcess(app, "StrictMode"); + if (r == null) { + return; + } 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 +6893,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); @@ -6685,7 +6987,7 @@ public final class ActivityManagerService extends ActivityManagerNative */ public boolean handleApplicationWtf(IBinder app, String tag, ApplicationErrorReport.CrashInfo crashInfo) { - ProcessRecord r = findAppProcess(app); + ProcessRecord r = findAppProcess(app, "WTF"); EventLog.writeEvent(EventLogTags.AM_WTF, Binder.getCallingPid(), app == null ? "system" : (r == null ? "unknown" : r.processName), @@ -6708,7 +7010,7 @@ public final class ActivityManagerService extends ActivityManagerNative * @param app object of some object (as stored in {@link com.android.internal.os.RuntimeInit}) * @return the corresponding {@link ProcessRecord} object, or null if none could be found */ - private ProcessRecord findAppProcess(IBinder app) { + private ProcessRecord findAppProcess(IBinder app, String reason) { if (app == null) { return null; } @@ -6724,7 +7026,9 @@ public final class ActivityManagerService extends ActivityManagerNative } } - Slog.w(TAG, "Can't find mystery application: " + app); + Slog.w(TAG, "Can't find mystery application for " + reason + + " from pid=" + Binder.getCallingPid() + + " uid=" + Binder.getCallingUid() + ": " + app); return null; } } @@ -7173,6 +7477,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"); @@ -7214,13 +7521,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; } } @@ -7293,6 +7608,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:"); @@ -7304,6 +7624,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(" "); @@ -7567,8 +7888,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; @@ -7588,7 +7908,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) { @@ -7600,7 +7920,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); } } } @@ -7609,16 +7929,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 @@ -7633,6 +7947,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; @@ -8189,7 +8593,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, @@ -8597,8 +9002,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 @@ -8680,11 +9093,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); } @@ -8723,15 +9144,17 @@ public final class ActivityManagerService extends ActivityManagerNative ServiceRecord.StartItem si = r.pendingStarts.remove(0); if (DEBUG_SERVICE) Slog.v(TAG, "Sending arguments to: " + r + " " + r.intent + " args=" + si.intent); - if (si.intent == null) { - // If somehow we got a dummy start at the front, then - // just drop it here. + if (si.intent == null && N > 1) { + // If somehow we got a dummy null intent in the middle, + // then skip it. DO NOT skip a null intent when it is + // the only one in the list -- this is to support the + // onStartCommand(null) case. continue; } si.deliveredTime = SystemClock.uptimeMillis(); r.deliveredStarts.add(si); si.deliveryCount++; - if (si.targetPermissionUid >= 0) { + if (si.targetPermissionUid >= 0 && si.intent != null) { grantUriPermissionUncheckedFromIntentLocked(si.targetPermissionUid, r.packageName, si.intent, si.getUriPermissionsLocked()); } @@ -8851,6 +9274,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(); @@ -8890,9 +9318,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; + } } } } @@ -8973,6 +9408,16 @@ public final class ActivityManagerService extends ActivityManagerNative // restarting state. mRestartingServices.remove(r); + // Service is now being launched, its package can't be stopped. + try { + AppGlobals.getPackageManager().setPackageStoppedState( + r.packageName, false); + } catch (RemoteException e) { + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Failed trying to unstop package " + + r.packageName + ": " + e); + } + final String appName = r.processName; ProcessRecord app = getProcessRecordLocked(appName, r.appInfo.uid); if (app != null && app.thread != null) { @@ -9270,7 +9715,7 @@ public final class ActivityManagerService extends ActivityManagerNative // r.record is null if findServiceLocked() failed the caller permission check if (r.record == null) { throw new SecurityException( - "Permission Denial: Accessing service " + "Permission Denial: Accessing service " + r.record.name + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + " requires " + r.permission); @@ -9867,6 +10312,16 @@ public final class ActivityManagerService extends ActivityManagerNative ss = stats.getServiceStatsLocked(app.uid, app.packageName, app.name); } + // Backup agent is now in use, its package can't be stopped. + try { + AppGlobals.getPackageManager().setPackageStoppedState( + app.packageName, false); + } catch (RemoteException e) { + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Failed trying to unstop package " + + app.packageName + ": " + e); + } + BackupRecord r = new BackupRecord(ss, app, backupMode); ComponentName hostingName = new ComponentName(app.packageName, app.backupAgentName); // startProcessLocked() returns existing proc's record if it's already running @@ -10154,6 +10609,9 @@ public final class ActivityManagerService extends ActivityManagerNative boolean ordered, boolean sticky, int callingPid, int callingUid) { intent = new Intent(intent); + // By default broadcasts do not go to stopped apps. + intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES); + if (DEBUG_BROADCAST_LIGHT) Slog.v( TAG, (sticky ? "Broadcast sticky: ": "Broadcast: ") + intent + " ordered=" + ordered); @@ -10172,7 +10630,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(); @@ -10231,6 +10689,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. @@ -10831,7 +11298,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() @@ -10844,7 +11311,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() @@ -11110,17 +11577,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 && @@ -11167,6 +11643,16 @@ public final class ActivityManagerService extends ActivityManagerNative info.activityInfo.name); r.curReceiver = info.activityInfo; + // Broadcast is being executed, its package can't be stopped. + try { + AppGlobals.getPackageManager().setPackageStoppedState( + r.curComponent.getPackageName(), false); + } catch (RemoteException e) { + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Failed trying to unstop package " + + r.curComponent.getPackageName() + ": " + e); + } + // Is this receiver's application already running? ProcessRecord app = getProcessRecordLocked(targetProcess, info.activityInfo.applicationInfo.uid); @@ -11465,14 +11951,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) { @@ -11567,28 +12048,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; @@ -11597,13 +12056,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 { @@ -11615,7 +12080,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 @@ -12484,8 +12979,8 @@ public final class ActivityManagerService extends ActivityManagerNative throw new IllegalArgumentException("Unknown process: " + process); } - boolean isSecure = "1".equals(SystemProperties.get(SYSTEM_SECURE, "0")); - if (isSecure) { + boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0")); + if (!isDebuggable) { if ((proc.info.flags&ApplicationInfo.FLAG_DEBUGGABLE) == 0) { throw new SecurityException("Process not debuggable: " + proc); } @@ -12506,9 +13001,84 @@ 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 isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0")); + if (!isDebuggable) { + 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) { } } + + public void onCoreSettingsChange(Bundle settings) { + for (int i = mLruProcesses.size() - 1; i >= 0; i--) { + ProcessRecord processRecord = mLruProcesses.get(i); + try { + if (processRecord.thread != null) { + processRecord.thread.setCoreSettings(settings); + } + } catch (RemoteException re) { + /* ignore */ + } + } + } } diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java index 4d773e498c02..0fb30ff4b172 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 463493bd99c7..c087aecfb594 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); @@ -1168,6 +1293,17 @@ public class ActivityStack { } } + // Launching this app's activity, make sure the app is no longer + // considered stopped. + try { + AppGlobals.getPackageManager().setPackageStoppedState( + next.packageName, false); + } catch (RemoteException e1) { + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Failed trying to unstop package " + + next.packageName + ": " + e); + } + // We are starting up the next activity, so tell the window manager // that the previous one will be hidden soon. This way it can know // to ignore it when computing the desired screen orientation. @@ -1176,11 +1312,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 +1325,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 +1341,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 +1363,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 +1427,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 +1445,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 +1481,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 +1493,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 +1564,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 +1607,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 +1632,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 +1719,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 +1756,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 +1918,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 +1948,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 +1986,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 +2070,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 +2148,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 +2200,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 +2237,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 +2320,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 +2358,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 +2377,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 +2394,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 +2455,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 +2514,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 +2541,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 +2584,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 +2604,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 +2637,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 +2701,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 +2747,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 +2798,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 +2944,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 +2990,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 +3194,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 +3260,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 +3313,8 @@ public class ActivityStack { Message msg = Message.obtain(); msg.what = IDLE_NOW_MSG; mHandler.sendMessage(msg); + } else { + checkReadyForSleepLocked(); } } r.state = ActivityState.STOPPING; @@ -3020,6 +3324,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 +3401,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 +3552,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 +3600,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 +3614,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 +3701,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 03e32725aa8c..d1e89bcd58fb 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 367c4cfff89b..963a691bb727 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/CoreSettingsObserver.java b/services/java/com/android/server/am/CoreSettingsObserver.java new file mode 100644 index 000000000000..585cf2b3f25f --- /dev/null +++ b/services/java/com/android/server/am/CoreSettingsObserver.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2006-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.am; + +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; +import android.provider.Settings.SettingNotFoundException; +import android.util.Log; + +import java.util.HashMap; +import java.util.Map; + +/** + * Helper class for watching a set of core settings which the framework + * propagates to application processes to avoid multiple lookups and potentially + * disk I/O operations. Note: This class assumes that all core settings reside + * in {@link Settings.Secure}. + */ +class CoreSettingsObserver extends ContentObserver { + private static final String LOG_TAG = CoreSettingsObserver.class.getSimpleName(); + + // mapping form property name to its type + private static final Map<String, Class<?>> sCoreSettingToTypeMap = new HashMap< + String, Class<?>>(); + static { + sCoreSettingToTypeMap.put(Settings.Secure.LONG_PRESS_TIMEOUT, int.class); + // add other core settings here... + } + + private final Bundle mCoreSettings = new Bundle(); + + private final ActivityManagerService mActivityManagerService; + + public CoreSettingsObserver(ActivityManagerService activityManagerService) { + super(activityManagerService.mHandler); + mActivityManagerService = activityManagerService; + beginObserveCoreSettings(); + sendCoreSettings(); + } + + public Bundle getCoreSettingsLocked() { + return (Bundle) mCoreSettings.clone(); + } + + @Override + public void onChange(boolean selfChange) { + synchronized (mActivityManagerService) { + sendCoreSettings(); + } + } + + private void sendCoreSettings() { + populateCoreSettings(mCoreSettings); + mActivityManagerService.onCoreSettingsChange(mCoreSettings); + } + + private void beginObserveCoreSettings() { + for (String setting : sCoreSettingToTypeMap.keySet()) { + Uri uri = Settings.Secure.getUriFor(setting); + mActivityManagerService.mContext.getContentResolver().registerContentObserver( + uri, false, this); + } + } + + private void populateCoreSettings(Bundle snapshot) { + Context context = mActivityManagerService.mContext; + for (Map.Entry<String, Class<?>> entry : sCoreSettingToTypeMap.entrySet()) { + String setting = entry.getKey(); + Class<?> type = entry.getValue(); + try { + if (type == String.class) { + String value = Settings.Secure.getString(context.getContentResolver(), + setting); + snapshot.putString(setting, value); + } else if (type == int.class) { + int value = Settings.Secure.getInt(context.getContentResolver(), + setting); + snapshot.putInt(setting, value); + } else if (type == float.class) { + float value = Settings.Secure.getFloat(context.getContentResolver(), + setting); + snapshot.putFloat(setting, value); + } else if (type == long.class) { + long value = Settings.Secure.getLong(context.getContentResolver(), + setting); + snapshot.putLong(setting, value); + } + } catch (SettingNotFoundException snfe) { + Log.w(LOG_TAG, "Cannot find setting \"" + setting + "\"", snfe); + } + } + } +} diff --git a/services/java/com/android/server/am/LaunchWarningWindow.java b/services/java/com/android/server/am/LaunchWarningWindow.java index 4130e33fdcb2..cb2b7bbeaff2 100644 --- a/services/java/com/android/server/am/LaunchWarningWindow.java +++ b/services/java/com/android/server/am/LaunchWarningWindow.java @@ -1,9 +1,26 @@ +/* + * 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.am; 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 +37,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 7a85eb880536..ee6e4204af39 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 bcb8f5406e65..86cec427166e 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 99c82e6c7ff0..68a2e0fdc426 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 f774b299e3de..585369632a09 100644 --- a/services/java/com/android/server/connectivity/Tethering.java +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -19,8 +19,8 @@ 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; @@ -31,6 +31,8 @@ import android.net.ConnectivityManager; import android.net.InterfaceConfiguration; import android.net.IConnectivityManager; import android.net.INetworkManagementEventObserver; +import android.net.LinkAddress; +import android.net.LinkProperties; import android.net.NetworkInfo; import android.net.NetworkUtils; import android.os.Binder; @@ -52,8 +54,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 +70,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 +80,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; @@ -85,19 +91,33 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private BroadcastReceiver mStateReceiver; private static final String USB_NEAR_IFACE_ADDR = "192.168.42.129"; - private static final String USB_NETMASK = "255.255.255.0"; + private static final int USB_PREFIX_LENGTH = 24; - // 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 @@ -152,11 +172,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 @@ -164,6 +194,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); @@ -174,7 +206,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)) { @@ -182,6 +214,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; @@ -216,6 +250,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); @@ -228,29 +268,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); @@ -321,13 +366,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); @@ -336,6 +382,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); } @@ -350,17 +398,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(); } @@ -391,7 +447,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; } @@ -435,6 +491,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; @@ -465,9 +522,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); @@ -492,7 +549,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); @@ -511,16 +568,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])); + InetAddress addr = NetworkUtils.numericToInetAddress(USB_NEAR_IFACE_ADDR); + ifcg.addr = new LinkAddress(addr, USB_PREFIX_LENGTH); if (enabled) { ifcg.interfaceFlags = ifcg.interfaceFlags.replace("down", "up"); } else { @@ -548,6 +597,10 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return mTetherableWifiRegexs; } + public String[] getTetherableBluetoothRegexs() { + return mTetherableBluetoothRegexs; + } + public String[] getUpstreamIfaceRegexs() { return mUpstreamIfaceRegexs; } @@ -739,7 +792,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: @@ -780,7 +833,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? @@ -832,7 +885,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(); @@ -843,7 +896,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) { @@ -886,13 +939,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); @@ -959,7 +1017,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)) { @@ -1035,7 +1093,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; @@ -1074,43 +1133,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; } @@ -1127,8 +1190,13 @@ public class Tethering extends INetworkManagementEventObserver.Stub { try { service.startTethering(mDhcpRange); } catch (Exception e) { - transitionTo(mStartTetheringErrorState); - return false; + try { + service.stopTethering(); + service.startTethering(mDhcpRange); + } catch (Exception ee) { + transitionTo(mStartTetheringErrorState); + return false; + } } try { service.setDnsForwarders(mDnsServers); @@ -1159,7 +1227,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]; @@ -1173,32 +1254,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) { @@ -1206,7 +1290,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(); @@ -1218,11 +1302,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(); @@ -1235,16 +1319,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; @@ -1257,23 +1344,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); @@ -1288,10 +1375,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 @@ -1303,7 +1391,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: @@ -1323,16 +1411,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 000000000000..e9ce3ce3ccf7 --- /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 000000000000..8326ef949858 --- /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 000000000000..139f05d211fd --- /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/java/com/android/server/usb/UsbDeviceSettingsManager.java b/services/java/com/android/server/usb/UsbDeviceSettingsManager.java index 616bdca7a855..de0b114ea0c5 100644 --- a/services/java/com/android/server/usb/UsbDeviceSettingsManager.java +++ b/services/java/com/android/server/usb/UsbDeviceSettingsManager.java @@ -29,6 +29,8 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.res.XmlResourceParser; import android.hardware.usb.UsbAccessory; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbManager; import android.os.Binder; import android.os.FileUtils; @@ -65,15 +67,176 @@ class UsbDeviceSettingsManager { private final Context mContext; private final PackageManager mPackageManager; + // Temporary mapping USB device name to list of UIDs with permissions for the device + private final HashMap<String, SparseBooleanArray> mDevicePermissionMap = + new HashMap<String, SparseBooleanArray>(); // Temporary mapping UsbAccessory to list of UIDs with permissions for the accessory private final HashMap<UsbAccessory, SparseBooleanArray> mAccessoryPermissionMap = new HashMap<UsbAccessory, SparseBooleanArray>(); + // Maps DeviceFilter to user preferred application package + private final HashMap<DeviceFilter, String> mDevicePreferenceMap = + new HashMap<DeviceFilter, String>(); // Maps AccessoryFilter to user preferred application package private final HashMap<AccessoryFilter, String> mAccessoryPreferenceMap = new HashMap<AccessoryFilter, String>(); private final Object mLock = new Object(); + // This class is used to describe a USB device. + // When used in HashMaps all values must be specified, + // but wildcards can be used for any of the fields in + // the package meta-data. + private static class DeviceFilter { + // USB Vendor ID (or -1 for unspecified) + public final int mVendorId; + // USB Product ID (or -1 for unspecified) + public final int mProductId; + // USB device or interface class (or -1 for unspecified) + public final int mClass; + // USB device subclass (or -1 for unspecified) + public final int mSubclass; + // USB device protocol (or -1 for unspecified) + public final int mProtocol; + + public DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol) { + mVendorId = vid; + mProductId = pid; + mClass = clasz; + mSubclass = subclass; + mProtocol = protocol; + } + + public DeviceFilter(UsbDevice device) { + mVendorId = device.getVendorId(); + mProductId = device.getProductId(); + mClass = device.getDeviceClass(); + mSubclass = device.getDeviceSubclass(); + mProtocol = device.getDeviceProtocol(); + } + + public static DeviceFilter read(XmlPullParser parser) + throws XmlPullParserException, IOException { + int vendorId = -1; + int productId = -1; + int deviceClass = -1; + int deviceSubclass = -1; + int deviceProtocol = -1; + + int count = parser.getAttributeCount(); + for (int i = 0; i < count; i++) { + String name = parser.getAttributeName(i); + // All attribute values are ints + int value = Integer.parseInt(parser.getAttributeValue(i)); + + if ("vendor-id".equals(name)) { + vendorId = value; + } else if ("product-id".equals(name)) { + productId = value; + } else if ("class".equals(name)) { + deviceClass = value; + } else if ("subclass".equals(name)) { + deviceSubclass = value; + } else if ("protocol".equals(name)) { + deviceProtocol = value; + } + } + return new DeviceFilter(vendorId, productId, + deviceClass, deviceSubclass, deviceProtocol); + } + + public void write(XmlSerializer serializer) throws IOException { + serializer.startTag(null, "usb-device"); + if (mVendorId != -1) { + serializer.attribute(null, "vendor-id", Integer.toString(mVendorId)); + } + if (mProductId != -1) { + serializer.attribute(null, "product-id", Integer.toString(mProductId)); + } + if (mClass != -1) { + serializer.attribute(null, "class", Integer.toString(mClass)); + } + if (mSubclass != -1) { + serializer.attribute(null, "subclass", Integer.toString(mSubclass)); + } + if (mProtocol != -1) { + serializer.attribute(null, "protocol", Integer.toString(mProtocol)); + } + serializer.endTag(null, "usb-device"); + } + + private boolean matches(int clasz, int subclass, int protocol) { + return ((mClass == -1 || clasz == mClass) && + (mSubclass == -1 || subclass == mSubclass) && + (mProtocol == -1 || protocol == mProtocol)); + } + + public boolean matches(UsbDevice device) { + if (mVendorId != -1 && device.getVendorId() != mVendorId) return false; + if (mProductId != -1 && device.getProductId() != mProductId) return false; + + // check device class/subclass/protocol + if (matches(device.getDeviceClass(), device.getDeviceSubclass(), + device.getDeviceProtocol())) return true; + + // if device doesn't match, check the interfaces + int count = device.getInterfaceCount(); + for (int i = 0; i < count; i++) { + UsbInterface intf = device.getInterface(i); + if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(), + intf.getInterfaceProtocol())) return true; + } + + return false; + } + + public boolean matches(DeviceFilter f) { + if (mVendorId != -1 && f.mVendorId != mVendorId) return false; + if (mProductId != -1 && f.mProductId != mProductId) return false; + + // check device class/subclass/protocol + return matches(f.mClass, f.mSubclass, f.mProtocol); + } + + @Override + public boolean equals(Object obj) { + // can't compare if we have wildcard strings + if (mVendorId == -1 || mProductId == -1 || + mClass == -1 || mSubclass == -1 || mProtocol == -1) { + return false; + } + if (obj instanceof DeviceFilter) { + DeviceFilter filter = (DeviceFilter)obj; + return (filter.mVendorId == mVendorId && + filter.mProductId == mProductId && + filter.mClass == mClass && + filter.mSubclass == mSubclass && + filter.mProtocol == mProtocol); + } + if (obj instanceof UsbDevice) { + UsbDevice device = (UsbDevice)obj; + return (device.getVendorId() == mVendorId && + device.getProductId() == mProductId && + device.getDeviceClass() == mClass && + device.getDeviceSubclass() == mSubclass && + device.getDeviceProtocol() == mProtocol); + } + return false; + } + + @Override + public int hashCode() { + return (((mVendorId << 16) | mProductId) ^ + ((mClass << 16) | (mSubclass << 8) | mProtocol)); + } + + @Override + public String toString() { + return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId + + ",mClass=" + mClass + ",mSubclass=" + mSubclass + + ",mProtocol=" + mProtocol + "]"; + } + } + // This class is used to describe a USB accessory. // When used in HashMaps all values must be specified, // but wildcards can be used for any of the fields in @@ -220,7 +383,10 @@ class UsbDeviceSettingsManager { } } XmlUtils.nextElement(parser); - if ("usb-accessory".equals(parser.getName())) { + if ("usb-device".equals(parser.getName())) { + DeviceFilter filter = DeviceFilter.read(parser); + mDevicePreferenceMap.put(filter, packageName); + } else if ("usb-accessory".equals(parser.getName())) { AccessoryFilter filter = AccessoryFilter.read(parser); mAccessoryPreferenceMap.put(filter, packageName); } @@ -270,6 +436,13 @@ class UsbDeviceSettingsManager { serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); serializer.startTag(null, "settings"); + for (DeviceFilter filter : mDevicePreferenceMap.keySet()) { + serializer.startTag(null, "preference"); + serializer.attribute(null, "package", mDevicePreferenceMap.get(filter)); + filter.write(serializer); + serializer.endTag(null, "preference"); + } + for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) { serializer.startTag(null, "preference"); serializer.attribute(null, "package", mAccessoryPreferenceMap.get(filter)); @@ -289,9 +462,10 @@ class UsbDeviceSettingsManager { } } - // Checks to see if a package matches an accessory. + // Checks to see if a package matches a device or accessory. + // Only one of device and accessory should be non-null. private boolean packageMatchesLocked(ResolveInfo info, String metaDataName, - UsbAccessory accessory) { + UsbDevice device, UsbAccessory accessory) { ActivityInfo ai = info.activityInfo; XmlResourceParser parser = null; @@ -305,7 +479,13 @@ class UsbDeviceSettingsManager { XmlUtils.nextElement(parser); while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { String tagName = parser.getName(); - if (accessory != null && "usb-accessory".equals(tagName)) { + if (device != null && "usb-device".equals(tagName)) { + DeviceFilter filter = DeviceFilter.read(parser); + if (filter.matches(device)) { + return true; + } + } + else if (accessory != null && "usb-accessory".equals(tagName)) { AccessoryFilter filter = AccessoryFilter.read(parser); if (filter.matches(accessory)) { return true; @@ -321,6 +501,20 @@ class UsbDeviceSettingsManager { return false; } + private final ArrayList<ResolveInfo> getDeviceMatchesLocked(UsbDevice device, Intent intent) { + ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>(); + List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(intent, + PackageManager.GET_META_DATA); + int count = resolveInfos.size(); + for (int i = 0; i < count; i++) { + ResolveInfo resolveInfo = resolveInfos.get(i); + if (packageMatchesLocked(resolveInfo, intent.getAction(), device, null)) { + matches.add(resolveInfo); + } + } + return matches; + } + private final ArrayList<ResolveInfo> getAccessoryMatchesLocked( UsbAccessory accessory, Intent intent) { ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>(); @@ -329,13 +523,40 @@ class UsbDeviceSettingsManager { int count = resolveInfos.size(); for (int i = 0; i < count; i++) { ResolveInfo resolveInfo = resolveInfos.get(i); - if (packageMatchesLocked(resolveInfo, intent.getAction(), accessory)) { + if (packageMatchesLocked(resolveInfo, intent.getAction(), null, accessory)) { matches.add(resolveInfo); } } return matches; } + public void deviceAttached(UsbDevice device) { + Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED); + intent.putExtra(UsbManager.EXTRA_DEVICE, device); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + ArrayList<ResolveInfo> matches; + String defaultPackage; + synchronized (mLock) { + matches = getDeviceMatchesLocked(device, intent); + // Launch our default activity directly, if we have one. + // Otherwise we will start the UsbResolverActivity to allow the user to choose. + defaultPackage = mDevicePreferenceMap.get(new DeviceFilter(device)); + } + + resolveActivity(intent, matches, defaultPackage, device, null); + } + + public void deviceDetached(UsbDevice device) { + // clear temporary permissions for the device + mDevicePermissionMap.remove(device.getDeviceName()); + + Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_DETACHED); + intent.putExtra(UsbManager.EXTRA_DEVICE, device); + Log.d(TAG, "usbDeviceRemoved, sending " + intent); + mContext.sendBroadcast(intent); + } + public void accessoryAttached(UsbAccessory accessory) { Intent intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED); intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); @@ -350,7 +571,7 @@ class UsbDeviceSettingsManager { defaultPackage = mAccessoryPreferenceMap.get(new AccessoryFilter(accessory)); } - resolveActivity(intent, matches, defaultPackage, accessory); + resolveActivity(intent, matches, defaultPackage, null, accessory); } public void accessoryDetached(UsbAccessory accessory) { @@ -364,7 +585,7 @@ class UsbDeviceSettingsManager { } private void resolveActivity(Intent intent, ArrayList<ResolveInfo> matches, - String defaultPackage, UsbAccessory accessory) { + String defaultPackage, UsbDevice device, UsbAccessory accessory) { int count = matches.size(); // don't show the resolver activity if there are no choices available @@ -418,7 +639,11 @@ class UsbDeviceSettingsManager { if (defaultRI != null) { // grant permission for default activity - grantAccessoryPermission(accessory, defaultRI.activityInfo.applicationInfo.uid); + if (device != null) { + grantDevicePermission(device, defaultRI.activityInfo.applicationInfo.uid); + } else if (accessory != null) { + grantAccessoryPermission(accessory, defaultRI.activityInfo.applicationInfo.uid); + } // start default activity directly try { @@ -438,7 +663,12 @@ class UsbDeviceSettingsManager { resolverIntent.setClassName("com.android.systemui", "com.android.systemui.usb.UsbConfirmActivity"); resolverIntent.putExtra("rinfo", matches.get(0)); - resolverIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); + + if (device != null) { + resolverIntent.putExtra(UsbManager.EXTRA_DEVICE, device); + } else { + resolverIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); + } } else { // start UsbResolverActivity so user can choose an activity resolverIntent.setClassName("com.android.systemui", @@ -454,6 +684,17 @@ class UsbDeviceSettingsManager { } } + private boolean clearCompatibleMatchesLocked(String packageName, DeviceFilter filter) { + boolean changed = false; + for (DeviceFilter test : mDevicePreferenceMap.keySet()) { + if (filter.matches(test)) { + mDevicePreferenceMap.remove(test); + changed = true; + } + } + return changed; + } + private boolean clearCompatibleMatchesLocked(String packageName, AccessoryFilter filter) { boolean changed = false; for (AccessoryFilter test : mAccessoryPreferenceMap.keySet()) { @@ -477,7 +718,13 @@ class UsbDeviceSettingsManager { XmlUtils.nextElement(parser); while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { String tagName = parser.getName(); - if ("usb-accessory".equals(tagName)) { + if ("usb-device".equals(tagName)) { + DeviceFilter filter = DeviceFilter.read(parser); + if (clearCompatibleMatchesLocked(packageName, filter)) { + changed = true; + } + } + else if ("usb-accessory".equals(tagName)) { AccessoryFilter filter = AccessoryFilter.read(parser); if (clearCompatibleMatchesLocked(packageName, filter)) { changed = true; @@ -513,6 +760,10 @@ class UsbDeviceSettingsManager { for (int i = 0; i < activities.length; i++) { // check for meta-data, both for devices and accessories if (handlePackageUpdateLocked(packageName, activities[i], + UsbManager.ACTION_USB_DEVICE_ATTACHED)) { + changed = true; + } + if (handlePackageUpdateLocked(packageName, activities[i], UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) { changed = true; } @@ -524,6 +775,16 @@ class UsbDeviceSettingsManager { } } + public boolean hasPermission(UsbDevice device) { + synchronized (mLock) { + SparseBooleanArray uidList = mDevicePermissionMap.get(device.getDeviceName()); + if (uidList == null) { + return false; + } + return uidList.get(Binder.getCallingUid()); + } + } + public boolean hasPermission(UsbAccessory accessory) { synchronized (mLock) { SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory); @@ -534,6 +795,12 @@ class UsbDeviceSettingsManager { } } + public void checkPermission(UsbDevice device) { + if (!hasPermission(device)) { + throw new SecurityException("User has not given permission to device " + device); + } + } + public void checkPermission(UsbAccessory accessory) { if (!hasPermission(accessory)) { throw new SecurityException("User has not given permission to accessory " + accessory); @@ -570,6 +837,26 @@ class UsbDeviceSettingsManager { } } + public void requestPermission(UsbDevice device, String packageName, PendingIntent pi) { + Intent intent = new Intent(); + + // respond immediately if permission has already been granted + if (hasPermission(device)) { + intent.putExtra(UsbManager.EXTRA_DEVICE, device); + intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true); + try { + pi.send(mContext, 0, intent); + } catch (PendingIntent.CanceledException e) { + Log.w(TAG, "requestPermission PendingIntent was cancelled"); + } + return; + } + + // start UsbPermissionActivity so user can choose an activity + intent.putExtra(UsbManager.EXTRA_DEVICE, device); + requestPermissionDialog(intent, packageName, pi); + } + public void requestPermission(UsbAccessory accessory, String packageName, PendingIntent pi) { Intent intent = new Intent(); @@ -589,6 +876,24 @@ class UsbDeviceSettingsManager { requestPermissionDialog(intent, packageName, pi); } + public void setDevicePackage(UsbDevice device, String packageName) { + DeviceFilter filter = new DeviceFilter(device); + boolean changed = false; + synchronized (mLock) { + if (packageName == null) { + changed = (mDevicePreferenceMap.remove(filter) != null); + } else { + changed = !packageName.equals(mDevicePreferenceMap.get(filter)); + if (changed) { + mDevicePreferenceMap.put(filter, packageName); + } + } + if (changed) { + writeSettingsLocked(); + } + } + } + public void setAccessoryPackage(UsbAccessory accessory, String packageName) { AccessoryFilter filter = new AccessoryFilter(accessory); boolean changed = false; @@ -607,6 +912,18 @@ class UsbDeviceSettingsManager { } } + public void grantDevicePermission(UsbDevice device, int uid) { + synchronized (mLock) { + String deviceName = device.getDeviceName(); + SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName); + if (uidList == null) { + uidList = new SparseBooleanArray(1); + mDevicePermissionMap.put(deviceName, uidList); + } + uidList.put(uid, true); + } + } + public void grantAccessoryPermission(UsbAccessory accessory, int uid) { synchronized (mLock) { SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory); @@ -620,7 +937,9 @@ class UsbDeviceSettingsManager { public boolean hasDefaults(String packageName) { synchronized (mLock) { - return mAccessoryPreferenceMap.values().contains(packageName); + if (mDevicePreferenceMap.values().contains(packageName)) return true; + if (mAccessoryPreferenceMap.values().contains(packageName)) return true; + return false; } } @@ -635,6 +954,17 @@ class UsbDeviceSettingsManager { private boolean clearPackageDefaultsLocked(String packageName) { boolean cleared = false; synchronized (mLock) { + if (mDevicePreferenceMap.containsValue(packageName)) { + // make a copy of the key set to avoid ConcurrentModificationException + Object[] keys = mDevicePreferenceMap.keySet().toArray(); + for (int i = 0; i < keys.length; i++) { + Object key = keys[i]; + if (packageName.equals(mDevicePreferenceMap.get(key))) { + mDevicePreferenceMap.remove(key); + cleared = true; + } + } + } if (mAccessoryPreferenceMap.containsValue(packageName)) { // make a copy of the key set to avoid ConcurrentModificationException Object[] keys = mAccessoryPreferenceMap.keySet().toArray(); @@ -652,6 +982,16 @@ class UsbDeviceSettingsManager { public void dump(FileDescriptor fd, PrintWriter pw) { synchronized (mLock) { + pw.println(" Device permissions:"); + for (String deviceName : mDevicePermissionMap.keySet()) { + pw.print(" " + deviceName + ": "); + SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName); + int count = uidList.size(); + for (int i = 0; i < count; i++) { + pw.print(Integer.toString(uidList.keyAt(i)) + " "); + } + pw.println(""); + } pw.println(" Accessory permissions:"); for (UsbAccessory accessory : mAccessoryPermissionMap.keySet()) { pw.print(" " + accessory + ": "); @@ -662,6 +1002,10 @@ class UsbDeviceSettingsManager { } pw.println(""); } + pw.println(" Device preferences:"); + for (DeviceFilter filter : mDevicePreferenceMap.keySet()) { + pw.println(" " + filter + ": " + mDevicePreferenceMap.get(filter)); + } pw.println(" Accessory preferences:"); for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) { pw.println(" " + filter + ": " + mAccessoryPreferenceMap.get(filter)); diff --git a/services/java/com/android/server/usb/UsbService.java b/services/java/com/android/server/usb/UsbService.java index f366e1095098..b7f63463ed4b 100644 --- a/services/java/com/android/server/usb/UsbService.java +++ b/services/java/com/android/server/usb/UsbService.java @@ -25,6 +25,10 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.hardware.usb.IUsbManager; import android.hardware.usb.UsbAccessory; +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbManager; import android.net.Uri; import android.os.Binder; @@ -92,6 +96,12 @@ public class UsbService extends IUsbManager.Stub { private final ArrayList<String> mEnabledFunctions = new ArrayList<String>(); private final ArrayList<String> mDisabledFunctions = new ArrayList<String>(); + // contains all connected USB devices (for USB host mode) + private final HashMap<String,UsbDevice> mDevices = new HashMap<String,UsbDevice>(); + + // USB busses to exclude from USB host support + private final String[] mHostBlacklist; + private boolean mSystemReady; private UsbAccessory mCurrentAccessory; @@ -101,6 +111,7 @@ public class UsbService extends IUsbManager.Stub { private final Context mContext; private final Object mLock = new Object(); private final UsbDeviceSettingsManager mDeviceManager; + private final boolean mHasUsbHost; private final boolean mHasUsbAccessory; private final void readCurrentAccessoryLocked() { @@ -204,8 +215,12 @@ public class UsbService extends IUsbManager.Stub { mContext = context; mDeviceManager = new UsbDeviceSettingsManager(context); PackageManager pm = mContext.getPackageManager(); + mHasUsbHost = pm.hasSystemFeature(PackageManager.FEATURE_USB_HOST); mHasUsbAccessory = pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY); + mHostBlacklist = context.getResources().getStringArray( + com.android.internal.R.array.config_usbHostBlacklist); + synchronized (mLock) { init(); // set initial status @@ -292,8 +307,122 @@ public class UsbService extends IUsbManager.Stub { } } + private boolean isBlackListed(String deviceName) { + int count = mHostBlacklist.length; + for (int i = 0; i < count; i++) { + if (deviceName.startsWith(mHostBlacklist[i])) { + return true; + } + } + return false; + } + + /* returns true if the USB device should not be accessible by applications (host mode) */ + private boolean isBlackListed(int clazz, int subClass, int protocol) { + // blacklist hubs + if (clazz == UsbConstants.USB_CLASS_HUB) return true; + + // blacklist HID boot devices (mouse and keyboard) + if (clazz == UsbConstants.USB_CLASS_HID && + subClass == UsbConstants.USB_INTERFACE_SUBCLASS_BOOT) { + return true; + } + + return false; + } + + /* Called from JNI in monitorUsbHostBus() to report new USB devices (host mode) */ + private void usbDeviceAdded(String deviceName, int vendorID, int productID, + int deviceClass, int deviceSubclass, int deviceProtocol, + /* array of quintuples containing id, class, subclass, protocol + and number of endpoints for each interface */ + int[] interfaceValues, + /* array of quadruples containing address, attributes, max packet size + and interval for each endpoint */ + int[] endpointValues) { + + if (isBlackListed(deviceName) || + isBlackListed(deviceClass, deviceSubclass, deviceProtocol)) { + return; + } + + synchronized (mLock) { + if (mDevices.get(deviceName) != null) { + Log.w(TAG, "device already on mDevices list: " + deviceName); + return; + } + + int numInterfaces = interfaceValues.length / 5; + Parcelable[] interfaces = new UsbInterface[numInterfaces]; + try { + // repackage interfaceValues as an array of UsbInterface + int intf, endp, ival = 0, eval = 0; + for (intf = 0; intf < numInterfaces; intf++) { + int interfaceId = interfaceValues[ival++]; + int interfaceClass = interfaceValues[ival++]; + int interfaceSubclass = interfaceValues[ival++]; + int interfaceProtocol = interfaceValues[ival++]; + int numEndpoints = interfaceValues[ival++]; + + Parcelable[] endpoints = new UsbEndpoint[numEndpoints]; + for (endp = 0; endp < numEndpoints; endp++) { + int address = endpointValues[eval++]; + int attributes = endpointValues[eval++]; + int maxPacketSize = endpointValues[eval++]; + int interval = endpointValues[eval++]; + endpoints[endp] = new UsbEndpoint(address, attributes, + maxPacketSize, interval); + } + + // don't allow if any interfaces are blacklisted + if (isBlackListed(interfaceClass, interfaceSubclass, interfaceProtocol)) { + return; + } + interfaces[intf] = new UsbInterface(interfaceId, interfaceClass, + interfaceSubclass, interfaceProtocol, endpoints); + } + } catch (Exception e) { + // beware of index out of bound exceptions, which might happen if + // a device does not set bNumEndpoints correctly + Log.e(TAG, "error parsing USB descriptors", e); + return; + } + + UsbDevice device = new UsbDevice(deviceName, vendorID, productID, + deviceClass, deviceSubclass, deviceProtocol, interfaces); + mDevices.put(deviceName, device); + mDeviceManager.deviceAttached(device); + } + } + + /* Called from JNI in monitorUsbHostBus to report USB device removal (host mode) */ + private void usbDeviceRemoved(String deviceName) { + synchronized (mLock) { + UsbDevice device = mDevices.remove(deviceName); + if (device != null) { + mDeviceManager.deviceDetached(device); + } + } + } + + private void initHostSupport() { + // Create a thread to call into native code to wait for USB host events. + // This thread will call us back on usbDeviceAdded and usbDeviceRemoved. + Runnable runnable = new Runnable() { + public void run() { + monitorUsbHostBus(); + } + }; + new Thread(null, runnable, "UsbService host thread").start(); + } + public void systemReady() { synchronized (mLock) { + if (mHasUsbHost) { + // start monitoring for connected USB devices + initHostSupport(); + } + update(false); if (mCurrentAccessory != null) { Log.d(TAG, "accessoryAttached at systemReady"); @@ -316,6 +445,32 @@ public class UsbService extends IUsbManager.Stub { mHandler.sendEmptyMessageDelayed(MSG_UPDATE_STATE, delayed ? UPDATE_DELAY : 0); } + /* Returns a list of all currently attached USB devices (host mdoe) */ + public void getDeviceList(Bundle devices) { + synchronized (mLock) { + for (String name : mDevices.keySet()) { + devices.putParcelable(name, mDevices.get(name)); + } + } + } + + /* Opens the specified USB device (host mode) */ + public ParcelFileDescriptor openDevice(String deviceName) { + synchronized (mLock) { + if (isBlackListed(deviceName)) { + throw new SecurityException("USB device is on a restricted bus"); + } + UsbDevice device = mDevices.get(deviceName); + if (device == null) { + // if it is not in mDevices, it either does not exist or is blacklisted + throw new IllegalArgumentException( + "device " + deviceName + " does not exist or is restricted"); + } + mDeviceManager.checkPermission(device); + return nativeOpenDevice(deviceName); + } + } + /* returns the currently attached USB accessory (device mode) */ public UsbAccessory getCurrentAccessory() { return mCurrentAccessory; @@ -337,20 +492,39 @@ public class UsbService extends IUsbManager.Stub { } } + public void setDevicePackage(UsbDevice device, String packageName) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + mDeviceManager.setDevicePackage(device, packageName); + } + public void setAccessoryPackage(UsbAccessory accessory, String packageName) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); mDeviceManager.setAccessoryPackage(accessory, packageName); } + public boolean hasDevicePermission(UsbDevice device) { + return mDeviceManager.hasPermission(device); + } + public boolean hasAccessoryPermission(UsbAccessory accessory) { return mDeviceManager.hasPermission(accessory); } + public void requestDevicePermission(UsbDevice device, String packageName, + PendingIntent pi) { + mDeviceManager.requestPermission(device, packageName, pi); + } + public void requestAccessoryPermission(UsbAccessory accessory, String packageName, PendingIntent pi) { mDeviceManager.requestPermission(accessory, packageName, pi); } + public void grantDevicePermission(UsbDevice device, int uid) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + mDeviceManager.grantDevicePermission(device, uid); + } + public void grantAccessoryPermission(UsbAccessory accessory, int uid) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); mDeviceManager.grantAccessoryPermission(accessory, uid); @@ -465,10 +639,17 @@ public class UsbService extends IUsbManager.Stub { pw.println(" mConnected: " + mConnected + ", mConfiguration: " + mConfiguration); pw.println(" mCurrentAccessory: " + mCurrentAccessory); + pw.println(" USB Host State:"); + for (String name : mDevices.keySet()) { + pw.println(" " + name + ": " + mDevices.get(name)); + } mDeviceManager.dump(fd, pw); } } + // host support + private native void monitorUsbHostBus(); + private native ParcelFileDescriptor nativeOpenDevice(String deviceName); // accessory support private native String[] nativeGetAccessoryStrings(); private native ParcelFileDescriptor nativeOpenAccessory(); diff --git a/services/java/com/android/server/wm/AppWindowToken.java b/services/java/com/android/server/wm/AppWindowToken.java new file mode 100644 index 000000000000..d3d9df4359d5 --- /dev/null +++ b/services/java/com/android/server/wm/AppWindowToken.java @@ -0,0 +1,413 @@ +/* + * 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.wm; + +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; + +import com.android.server.wm.WindowManagerService.H; + +import android.content.pm.ActivityInfo; +import android.os.Message; +import android.os.RemoteException; +import android.util.Slog; +import android.view.IApplicationToken; +import android.view.View; +import android.view.WindowManager; +import android.view.animation.Animation; +import android.view.animation.Transformation; + +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Version of WindowToken that is specifically for a particular application (or + * really activity) that is displaying windows. + */ +class AppWindowToken extends WindowToken { + // Non-null only for application tokens. + final IApplicationToken appToken; + + // All of the windows and child windows that are included in this + // application token. Note this list is NOT sorted! + final ArrayList<WindowState> allAppWindows = new ArrayList<WindowState>(); + + int groupId = -1; + boolean appFullscreen; + int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + + // The input dispatching timeout for this application token in nanoseconds. + long inputDispatchingTimeoutNanos; + + // These are used for determining when all windows associated with + // an activity have been drawn, so they can be made visible together + // at the same time. + int lastTransactionSequence; + int numInterestingWindows; + int numDrawnWindows; + boolean inPendingTransaction; + boolean allDrawn; + + // Is this token going to be hidden in a little while? If so, it + // won't be taken into account for setting the screen orientation. + boolean willBeHidden; + + // Is this window's surface needed? This is almost like hidden, except + // it will sometimes be true a little earlier: when the token has + // been shown, but is still waiting for its app transition to execute + // before making its windows shown. + boolean hiddenRequested; + + // Have we told the window clients to hide themselves? + boolean clientHidden; + + // Last visibility state we reported to the app token. + boolean reportedVisible; + + // Set to true when the token has been removed from the window mgr. + boolean removed; + + // Have we been asked to have this token keep the screen frozen? + boolean freezingScreen; + + boolean animating; + Animation animation; + boolean hasTransformation; + final Transformation transformation = new Transformation(); + + // Offset to the window of all layers in the token, for use by + // AppWindowToken animations. + int animLayerAdjustment; + + // Information about an application starting window if displayed. + StartingData startingData; + WindowState startingWindow; + View startingView; + boolean startingDisplayed; + boolean startingMoved; + boolean firstWindowDrawn; + + // Input application handle used by the input dispatcher. + InputApplicationHandle mInputApplicationHandle; + + AppWindowToken(WindowManagerService _service, IApplicationToken _token) { + super(_service, _token.asBinder(), + WindowManager.LayoutParams.TYPE_APPLICATION, true); + appWindowToken = this; + appToken = _token; + mInputApplicationHandle = new InputApplicationHandle(this); + lastTransactionSequence = service.mTransactionSequence-1; + } + + public void setAnimation(Animation anim) { + if (WindowManagerService.localLOGV) Slog.v( + WindowManagerService.TAG, "Setting animation in " + this + ": " + anim); + animation = anim; + animating = false; + anim.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION); + anim.scaleCurrentDuration(service.mTransitionAnimationScale); + int zorder = anim.getZAdjustment(); + int adj = 0; + if (zorder == Animation.ZORDER_TOP) { + adj = WindowManagerService.TYPE_LAYER_OFFSET; + } else if (zorder == Animation.ZORDER_BOTTOM) { + adj = -WindowManagerService.TYPE_LAYER_OFFSET; + } + + if (animLayerAdjustment != adj) { + animLayerAdjustment = adj; + updateLayers(); + } + } + + public void setDummyAnimation() { + if (animation == null) { + if (WindowManagerService.localLOGV) Slog.v( + WindowManagerService.TAG, "Setting dummy animation in " + this); + animation = WindowManagerService.sDummyAnimation; + } + } + + public void clearAnimation() { + if (animation != null) { + animation = null; + animating = true; + } + } + + void updateLayers() { + final int N = allAppWindows.size(); + final int adj = animLayerAdjustment; + for (int i=0; i<N; i++) { + WindowState w = allAppWindows.get(i); + w.mAnimLayer = w.mLayer + adj; + if (WindowManagerService.DEBUG_LAYERS) Slog.v(WindowManagerService.TAG, "Updating layer " + w + ": " + + w.mAnimLayer); + if (w == service.mInputMethodTarget && !service.mInputMethodTargetWaitingAnim) { + service.setInputMethodAnimLayerAdjustment(adj); + } + if (w == service.mWallpaperTarget && service.mLowerWallpaperTarget == null) { + service.setWallpaperAnimLayerAdjustmentLocked(adj); + } + } + } + + void sendAppVisibilityToClients() { + final int N = allAppWindows.size(); + for (int i=0; i<N; i++) { + WindowState win = allAppWindows.get(i); + if (win == startingWindow && clientHidden) { + // Don't hide the starting window. + continue; + } + try { + if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, + "Setting visibility of " + win + ": " + (!clientHidden)); + win.mClient.dispatchAppVisibility(!clientHidden); + } catch (RemoteException e) { + } + } + } + + void showAllWindowsLocked() { + final int NW = allAppWindows.size(); + for (int i=0; i<NW; i++) { + WindowState w = allAppWindows.get(i); + if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, + "performing show on: " + w); + w.performShowLocked(); + } + } + + // This must be called while inside a transaction. + boolean stepAnimationLocked(long currentTime, int dw, int dh) { + if (!service.mDisplayFrozen && service.mPolicy.isScreenOn()) { + // We will run animations as long as the display isn't frozen. + + if (animation == WindowManagerService.sDummyAnimation) { + // This guy is going to animate, but not yet. For now count + // it as not animating for purposes of scheduling transactions; + // when it is really time to animate, this will be set to + // a real animation and the next call will execute normally. + return false; + } + + if ((allDrawn || animating || startingDisplayed) && animation != null) { + if (!animating) { + if (WindowManagerService.DEBUG_ANIM) Slog.v( + WindowManagerService.TAG, "Starting animation in " + this + + " @ " + currentTime + ": dw=" + dw + " dh=" + dh + + " scale=" + service.mTransitionAnimationScale + + " allDrawn=" + allDrawn + " animating=" + animating); + animation.initialize(dw, dh, dw, dh); + animation.setStartTime(currentTime); + animating = true; + } + transformation.clear(); + final boolean more = animation.getTransformation( + currentTime, transformation); + if (WindowManagerService.DEBUG_ANIM) Slog.v( + WindowManagerService.TAG, "Stepped animation in " + this + + ": more=" + more + ", xform=" + transformation); + if (more) { + // we're done! + hasTransformation = true; + return true; + } + if (WindowManagerService.DEBUG_ANIM) Slog.v( + WindowManagerService.TAG, "Finished animation in " + this + + " @ " + currentTime); + animation = null; + } + } else if (animation != null) { + // If the display is frozen, and there is a pending animation, + // clear it and make sure we run the cleanup code. + animating = true; + animation = null; + } + + hasTransformation = false; + + if (!animating) { + return false; + } + + clearAnimation(); + animating = false; + if (animLayerAdjustment != 0) { + animLayerAdjustment = 0; + updateLayers(); + } + if (service.mInputMethodTarget != null && service.mInputMethodTarget.mAppToken == this) { + service.moveInputMethodWindowsIfNeededLocked(true); + } + + if (WindowManagerService.DEBUG_ANIM) Slog.v( + WindowManagerService.TAG, "Animation done in " + this + + ": reportedVisible=" + reportedVisible); + + transformation.clear(); + + final int N = windows.size(); + for (int i=0; i<N; i++) { + windows.get(i).finishExit(); + } + updateReportedVisibilityLocked(); + + return false; + } + + void updateReportedVisibilityLocked() { + if (appToken == null) { + return; + } + + int numInteresting = 0; + int numVisible = 0; + boolean nowGone = true; + + if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, "Update reported visibility: " + this); + final int N = allAppWindows.size(); + for (int i=0; i<N; i++) { + WindowState win = allAppWindows.get(i); + if (win == startingWindow || win.mAppFreezing + || win.mViewVisibility != View.VISIBLE + || win.mAttrs.type == TYPE_APPLICATION_STARTING + || win.mDestroying) { + continue; + } + if (WindowManagerService.DEBUG_VISIBILITY) { + Slog.v(WindowManagerService.TAG, "Win " + win + ": isDrawn=" + + win.isDrawnLw() + + ", isAnimating=" + win.isAnimating()); + if (!win.isDrawnLw()) { + Slog.v(WindowManagerService.TAG, "Not displayed: s=" + win.mSurface + + " pv=" + win.mPolicyVisibility + + " dp=" + win.mDrawPending + + " cdp=" + win.mCommitDrawPending + + " ah=" + win.mAttachedHidden + + " th=" + + (win.mAppToken != null + ? win.mAppToken.hiddenRequested : false) + + " a=" + win.mAnimating); + } + } + numInteresting++; + if (win.isDrawnLw()) { + if (!win.isAnimating()) { + numVisible++; + } + nowGone = false; + } else if (win.isAnimating()) { + nowGone = false; + } + } + + boolean nowVisible = numInteresting > 0 && numVisible >= numInteresting; + if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, "VIS " + this + ": interesting=" + + numInteresting + " visible=" + numVisible); + if (nowVisible != reportedVisible) { + if (WindowManagerService.DEBUG_VISIBILITY) Slog.v( + WindowManagerService.TAG, "Visibility changed in " + this + + ": vis=" + nowVisible); + reportedVisible = nowVisible; + Message m = service.mH.obtainMessage( + H.REPORT_APPLICATION_TOKEN_WINDOWS, + nowVisible ? 1 : 0, + nowGone ? 1 : 0, + this); + service.mH.sendMessage(m); + } + } + + WindowState findMainWindow() { + int j = windows.size(); + while (j > 0) { + j--; + WindowState win = windows.get(j); + if (win.mAttrs.type == WindowManager.LayoutParams.TYPE_BASE_APPLICATION + || win.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) { + return win; + } + } + return null; + } + + void dump(PrintWriter pw, String prefix) { + super.dump(pw, prefix); + if (appToken != null) { + pw.print(prefix); pw.println("app=true"); + } + if (allAppWindows.size() > 0) { + pw.print(prefix); pw.print("allAppWindows="); pw.println(allAppWindows); + } + pw.print(prefix); pw.print("groupId="); pw.print(groupId); + pw.print(" appFullscreen="); pw.print(appFullscreen); + pw.print(" requestedOrientation="); pw.println(requestedOrientation); + pw.print(prefix); pw.print("hiddenRequested="); pw.print(hiddenRequested); + pw.print(" clientHidden="); pw.print(clientHidden); + pw.print(" willBeHidden="); pw.print(willBeHidden); + pw.print(" reportedVisible="); pw.println(reportedVisible); + if (paused || freezingScreen) { + pw.print(prefix); pw.print("paused="); pw.print(paused); + pw.print(" freezingScreen="); pw.println(freezingScreen); + } + if (numInterestingWindows != 0 || numDrawnWindows != 0 + || inPendingTransaction || allDrawn) { + pw.print(prefix); pw.print("numInterestingWindows="); + pw.print(numInterestingWindows); + pw.print(" numDrawnWindows="); pw.print(numDrawnWindows); + pw.print(" inPendingTransaction="); pw.print(inPendingTransaction); + pw.print(" allDrawn="); pw.println(allDrawn); + } + if (animating || animation != null) { + pw.print(prefix); pw.print("animating="); pw.print(animating); + pw.print(" animation="); pw.println(animation); + } + if (hasTransformation) { + 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); + pw.print(" firstWindowDrawn="); pw.println(firstWindowDrawn); + } + if (startingWindow != null || startingView != null + || startingDisplayed || startingMoved) { + pw.print(prefix); pw.print("startingWindow="); pw.print(startingWindow); + pw.print(" startingView="); pw.print(startingView); + pw.print(" startingDisplayed="); pw.print(startingDisplayed); + pw.print(" startingMoved"); pw.println(startingMoved); + } + } + + @Override + public String toString() { + if (stringName == null) { + StringBuilder sb = new StringBuilder(); + sb.append("AppWindowToken{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(" token="); sb.append(token); sb.append('}'); + stringName = sb.toString(); + } + return stringName; + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/wm/DimAnimator.java b/services/java/com/android/server/wm/DimAnimator.java new file mode 100644 index 000000000000..a266d70de7a9 --- /dev/null +++ b/services/java/com/android/server/wm/DimAnimator.java @@ -0,0 +1,190 @@ +/* + * 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.wm; + +import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.util.Slog; +import android.util.TypedValue; +import android.view.Surface; +import android.view.SurfaceSession; + +import java.io.PrintWriter; + +/** + * DimAnimator class that controls the dim animation. This holds the surface and + * all state used for dim animation. + */ +class DimAnimator { + Surface mDimSurface; + boolean mDimShown = false; + float mDimCurrentAlpha; + float mDimTargetAlpha; + float mDimDeltaPerMs; + long mLastDimAnimTime; + + int mLastDimWidth, mLastDimHeight; + + DimAnimator (SurfaceSession session) { + if (mDimSurface == null) { + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DIM " + + mDimSurface + ": CREATE"); + try { + mDimSurface = new Surface(session, 0, + "DimAnimator", + -1, 16, 16, PixelFormat.OPAQUE, + Surface.FX_SURFACE_DIM); + mDimSurface.setAlpha(0.0f); + } catch (Exception e) { + Slog.e(WindowManagerService.TAG, "Exception creating Dim surface", e); + } + } + } + + /** + * Show the dim surface. + */ + void show(int dw, int dh) { + if (!mDimShown) { + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DIM " + mDimSurface + ": SHOW pos=(0,0) (" + + dw + "x" + dh + ")"); + mDimShown = true; + try { + mLastDimWidth = dw; + mLastDimHeight = dh; + mDimSurface.setPosition(0, 0); + mDimSurface.setSize(dw, dh); + mDimSurface.show(); + } catch (RuntimeException e) { + Slog.w(WindowManagerService.TAG, "Failure showing dim surface", e); + } + } else if (mLastDimWidth != dw || mLastDimHeight != dh) { + mLastDimWidth = dw; + mLastDimHeight = dh; + mDimSurface.setSize(dw, dh); + } + } + + /** + * 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(Resources res, WindowState w, long currentTime) { + mDimSurface.setLayer(w.mAnimLayer - WindowManagerService.LAYER_OFFSET_DIM); + + final float target = w.mExiting ? 0 : w.mAttrs.dimAmount; + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DIM " + mDimSurface + + ": layer=" + (w.mAnimLayer-1) + " target=" + target); + if (mDimTargetAlpha != target) { + // If the desired dim level has changed, then + // start an animation to it. + mLastDimAnimTime = currentTime; + long duration = (w.mAnimating && w.mAnimation != null) + ? w.mAnimation.computeDurationHint() + : WindowManagerService.DEFAULT_DIM_DURATION; + if (target > mDimTargetAlpha) { + 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 + duration = 1; + } + mDimTargetAlpha = target; + mDimDeltaPerMs = (mDimTargetAlpha-mDimCurrentAlpha) / duration; + } + } + + /** + * Updating the surface's alpha. Returns true if the animation continues, or returns + * false when the animation is finished and the dim surface is hidden. + */ + boolean updateSurface(boolean dimming, long currentTime, boolean displayFrozen) { + if (!dimming) { + if (mDimTargetAlpha != 0) { + mLastDimAnimTime = currentTime; + mDimTargetAlpha = 0; + mDimDeltaPerMs = (-mDimCurrentAlpha) / WindowManagerService.DEFAULT_DIM_DURATION; + } + } + + boolean animating = false; + if (mLastDimAnimTime != 0) { + mDimCurrentAlpha += mDimDeltaPerMs + * (currentTime-mLastDimAnimTime); + boolean more = true; + if (displayFrozen) { + // If the display is frozen, there is no reason to animate. + more = false; + } else if (mDimDeltaPerMs > 0) { + if (mDimCurrentAlpha > mDimTargetAlpha) { + more = false; + } + } else if (mDimDeltaPerMs < 0) { + if (mDimCurrentAlpha < mDimTargetAlpha) { + more = false; + } + } else { + more = false; + } + + // Do we need to continue animating? + if (more) { + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DIM " + + mDimSurface + ": alpha=" + mDimCurrentAlpha); + mLastDimAnimTime = currentTime; + mDimSurface.setAlpha(mDimCurrentAlpha); + animating = true; + } else { + mDimCurrentAlpha = mDimTargetAlpha; + mLastDimAnimTime = 0; + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DIM " + + mDimSurface + ": final alpha=" + mDimCurrentAlpha); + mDimSurface.setAlpha(mDimCurrentAlpha); + if (!dimming) { + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DIM " + mDimSurface + + ": HIDE"); + try { + mDimSurface.hide(); + } catch (RuntimeException e) { + Slog.w(WindowManagerService.TAG, "Illegal argument exception hiding dim surface"); + } + mDimShown = false; + } + } + } + return animating; + } + + public void printTo(String prefix, PrintWriter pw) { + pw.print(prefix); + pw.print("mDimSurface="); pw.println(mDimSurface); + pw.print(prefix); + pw.print("mDimShown="); pw.print(mDimShown); + pw.print(" current="); pw.print(mDimCurrentAlpha); + pw.print(" target="); pw.print(mDimTargetAlpha); + pw.print(" delta="); pw.print(mDimDeltaPerMs); + pw.print(" lastAnimTime="); pw.println(mLastDimAnimTime); + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/wm/DimSurface.java b/services/java/com/android/server/wm/DimSurface.java new file mode 100644 index 000000000000..084ac6f18881 --- /dev/null +++ b/services/java/com/android/server/wm/DimSurface.java @@ -0,0 +1,97 @@ +/* + * 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.wm; + +import android.graphics.PixelFormat; +import android.util.Slog; +import android.view.Surface; +import android.view.SurfaceSession; + +import java.io.PrintWriter; + +class DimSurface { + Surface mDimSurface; + boolean mDimShown = false; + int mDimColor = 0; + int mLayer = -1; + int mLastDimWidth, mLastDimHeight; + + DimSurface(SurfaceSession session) { + if (mDimSurface == null) { + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DIM " + + mDimSurface + ": CREATE"); + try { + mDimSurface = new Surface(session, 0, + "DimSurface", + -1, 16, 16, PixelFormat.OPAQUE, + Surface.FX_SURFACE_DIM); + mDimSurface.setAlpha(0.0f); + } catch (Exception e) { + Slog.e(WindowManagerService.TAG, "Exception creating Dim surface", e); + } + } + } + + /** + * Show the dim surface. + */ + void show(int dw, int dh, int layer, int color) { + if (!mDimShown) { + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DIM " + mDimSurface + ": SHOW pos=(0,0) (" + + dw + "x" + dh + ")"); + mDimShown = true; + try { + mLastDimWidth = dw; + mLastDimHeight = dh; + mDimSurface.setPosition(0, 0); + mDimSurface.setSize(dw, dh); + mDimSurface.show(); + } catch (RuntimeException e) { + Slog.w(WindowManagerService.TAG, "Failure showing dim surface", e); + } + } else if (mLastDimWidth != dw || mLastDimHeight != dh || mDimColor != color + || mLayer != layer) { + mLastDimWidth = dw; + mLastDimHeight = dh; + mLayer = layer; + mDimColor = color; + mDimSurface.setSize(dw, dh); + mDimSurface.setLayer(layer); + mDimSurface.setAlpha(((color>>24)&0xff)/255.0f); + } + } + + void hide() { + if (mDimShown) { + mDimShown = false; + try { + mDimSurface.hide(); + } catch (RuntimeException e) { + Slog.w(WindowManagerService.TAG, "Illegal argument exception hiding dim surface"); + } + } + } + + public void printTo(String prefix, PrintWriter pw) { + pw.print(prefix); pw.print("mDimSurface="); pw.println(mDimSurface); + pw.print(prefix); pw.print("mDimShown="); pw.print(mDimShown); + pw.print(" mLayer="); pw.println(mLayer); + pw.print(" mDimColor=0x"); pw.println(Integer.toHexString(mDimColor)); + pw.print(prefix); pw.print("mLastDimWidth="); pw.print(mLastDimWidth); + pw.print(" mLastDimWidth="); pw.println(mLastDimWidth); + } +} diff --git a/services/java/com/android/server/wm/DragState.java b/services/java/com/android/server/wm/DragState.java new file mode 100644 index 000000000000..c8f8ff3ca03e --- /dev/null +++ b/services/java/com/android/server/wm/DragState.java @@ -0,0 +1,368 @@ +/* + * 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.wm; + +import com.android.server.wm.WindowManagerService.H; + +import android.content.ClipData; +import android.content.ClipDescription; +import android.graphics.Region; +import android.os.IBinder; +import android.os.Message; +import android.os.Process; +import android.os.RemoteException; +import android.util.Slog; +import android.view.DragEvent; +import android.view.InputChannel; +import android.view.InputQueue; +import android.view.Surface; +import android.view.View; +import android.view.WindowManager; +import android.view.WindowManagerPolicy; + +import java.util.ArrayList; + +/** + * Drag/drop state + */ +class DragState { + final WindowManagerService mService; + 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(WindowManagerService service, IBinder token, Surface surface, + int flags, IBinder localWin) { + mService = service; + 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 (WindowManagerService.DEBUG_DRAG) Slog.d(WindowManagerService.TAG, "registering drag input channel"); + if (mClientChannel != null) { + Slog.e(WindowManagerService.TAG, "Duplicate register of drag input channel"); + } else { + InputChannel[] channels = InputChannel.openInputChannelPair("drag"); + mServerChannel = channels[0]; + mClientChannel = channels[1]; + mService.mInputManager.registerInputChannel(mServerChannel, null); + InputQueue.registerInputChannel(mClientChannel, mService.mDragInputHandler, + mService.mH.getLooper().getQueue()); + } + } + + void unregister() { + if (WindowManagerService.DEBUG_DRAG) Slog.d(WindowManagerService.TAG, "unregistering drag input channel"); + if (mClientChannel == null) { + Slog.e(WindowManagerService.TAG, "Unregister of nonexistent drag input channel"); + } else { + mService.mInputManager.unregisterInputChannel(mServerChannel); + InputQueue.unregisterInputChannel(mClientChannel); + mClientChannel.dispose(); + mServerChannel.dispose(); + mClientChannel = null; + mServerChannel = null; + } + } + + int getDragLayerLw() { + return mService.mPolicy.windowTypeToLayerLw(WindowManager.LayoutParams.TYPE_DRAG) + * WindowManagerService.TYPE_LAYER_MULTIPLIER + + WindowManagerService.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 (WindowManagerService.DEBUG_DRAG) { + Slog.d(WindowManagerService.TAG, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")"); + } + + final int N = mService.mWindows.size(); + for (int i = 0; i < N; i++) { + sendDragStartedLw(mService.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 (WindowManagerService.DEBUG_DRAG) { + Slog.d(WindowManagerService.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(WindowManagerService.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. + */ + 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 (WindowManagerService.DEBUG_DRAG) { + Slog.d(WindowManagerService.TAG, "need to send DRAG_STARTED to new window " + newWin); + } + sendDragStartedLw(newWin, mCurrentX, mCurrentY, mDataDescription); + } + } + + void broadcastDragEndedLw() { + if (WindowManagerService.DEBUG_DRAG) { + Slog.d(WindowManagerService.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(WindowManagerService.TAG, "Unable to drag-end window " + ws); + } + } + mNotifiedWindows.clear(); + mDragInProgress = false; + evt.recycle(); + } + + void endDragLw() { + mService.mDragState.broadcastDragEndedLw(); + + // stop intercepting input + mService.mDragState.unregister(); + mService.mInputMonitor.updateInputWindowsLw(true /*force*/); + + // free our resources and drop all the object references + mService.mDragState.reset(); + mService.mDragState = null; + + if (WindowManagerService.DEBUG_ORIENTATION) Slog.d(WindowManagerService.TAG, "Performing post-drag rotation"); + boolean changed = mService.setRotationUncheckedLocked( + WindowManagerPolicy.USE_LAST_ROTATION, 0, false); + if (changed) { + mService.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 (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, ">>> OPEN TRANSACTION notifyMoveLw"); + Surface.openTransaction(); + try { + mSurface.setPosition((int)(x - mThumbOffsetX), (int)(y - mThumbOffsetY)); + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DRAG " + + mSurface + ": pos=(" + + (int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")"); + } finally { + Surface.closeTransaction(); + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, "<<< CLOSE TRANSACTION notifyMoveLw"); + } + + // Tell the affected window + WindowState touchedWin = getTouchedWinAtPointLw(x, y); + if (touchedWin == null) { + if (WindowManagerService.DEBUG_DRAG) Slog.d(WindowManagerService.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 (WindowManagerService.DEBUG_DRAG) { + Slog.d(WindowManagerService.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 && WindowManagerService.DEBUG_DRAG) { + Slog.d(WindowManagerService.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(WindowManagerService.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 (WindowManagerService.DEBUG_DRAG) { + Slog.d(WindowManagerService.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 + mService.mH.removeMessages(H.DRAG_END_TIMEOUT, token); + Message msg = mService.mH.obtainMessage(H.DRAG_END_TIMEOUT, token); + mService.mH.sendMessageDelayed(msg, 5000); + } catch (RemoteException e) { + Slog.w(WindowManagerService.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 = mService.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; + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/wm/FadeInOutAnimation.java b/services/java/com/android/server/wm/FadeInOutAnimation.java new file mode 100644 index 000000000000..06f76571b016 --- /dev/null +++ b/services/java/com/android/server/wm/FadeInOutAnimation.java @@ -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. + */ + +package com.android.server.wm; + +import android.view.animation.AccelerateInterpolator; +import android.view.animation.Animation; +import android.view.animation.Transformation; + +/** + * Animation that fade in after 0.5 interpolate time, or fade out in reverse order. + * This is used for opening/closing transition for apps in compatible mode. + */ +class FadeInOutAnimation extends Animation { + boolean mFadeIn; + + public FadeInOutAnimation(boolean fadeIn) { + setInterpolator(new AccelerateInterpolator()); + setDuration(WindowManagerService.DEFAULT_FADE_IN_OUT_DURATION); + mFadeIn = fadeIn; + } + + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + float x = interpolatedTime; + if (!mFadeIn) { + x = 1.0f - x; // reverse the interpolation for fade out + } + t.setAlpha(x); + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/InputApplication.java b/services/java/com/android/server/wm/InputApplication.java index 38420d4f899f..e04fd31a3ffc 100644 --- a/services/java/com/android/server/InputApplication.java +++ b/services/java/com/android/server/wm/InputApplication.java @@ -14,20 +14,24 @@ * limitations under the License. */ -package com.android.server; +package com.android.server.wm; + /** * 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/wm/InputApplicationHandle.java b/services/java/com/android/server/wm/InputApplicationHandle.java new file mode 100644 index 000000000000..64c8e7ed73db --- /dev/null +++ b/services/java/com/android/server/wm/InputApplicationHandle.java @@ -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. + */ + +package com.android.server.wm; + + +/** + * 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 AppWindowToken appWindowToken; + + private native void nativeDispose(); + + public InputApplicationHandle(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/wm/InputManager.java index ba39c577d610..ca1da95f34eb 100644 --- a/services/java/com/android/server/InputManager.java +++ b/services/java/com/android/server/wm/InputManager.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server; +package com.android.server.wm; import com.android.internal.util.XmlUtils; @@ -23,25 +23,32 @@ import org.xmlpull.v1.XmlPullParser; 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.Looper; +import android.os.MessageQueue; import android.os.SystemProperties; import android.util.Slog; 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.ViewConfiguration; +import android.view.WindowManager; -import java.io.BufferedReader; import java.io.File; -import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; 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. @@ -55,7 +62,7 @@ public class InputManager { private final Context mContext; private final WindowManagerService mWindowManagerService; - private static native void nativeInit(Callbacks callbacks); + private static native void nativeInit(Callbacks callbacks, MessageQueue messageQueue); private static native void nativeStart(); private static native void nativeSetDisplaySize(int displayId, int width, int height); private static native void nativeSetDisplayOrientation(int displayId, int rotation); @@ -69,16 +76,19 @@ 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); private static native void nativeSetInputWindows(InputWindow[] windows); private static native void nativeSetInputDispatchMode(boolean enabled, boolean frozen); + private static native void nativeSetSystemUiVisibility(int visibility); private static native void nativeSetFocusedApplication(InputApplication application); 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. @@ -110,15 +120,12 @@ public class InputManager { public InputManager(Context context, WindowManagerService windowManagerService) { this.mContext = context; this.mWindowManagerService = windowManagerService; - this.mCallbacks = new Callbacks(); - - init(); - } - - private void init() { + + Looper looper = windowManagerService.mH.getLooper(); + Slog.i(TAG, "Initializing input manager"); - nativeInit(mCallbacks); + nativeInit(mCallbacks, looper.getQueue()); } public void start() { @@ -230,7 +237,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 +245,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); } /** @@ -325,29 +335,83 @@ public class InputManager { public void setInputDispatchMode(boolean enabled, boolean frozen) { nativeSetInputDispatchMode(enabled, frozen); } - + + public void setSystemUiVisibility(int visibility) { + nativeSetSystemUiVisibility(visibility); + } + + /** + * 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 +424,7 @@ public class InputManager { @SuppressWarnings("unused") public void notifyConfigurationChanged(long whenNanos) { - mWindowManagerService.sendNewConfiguration(); + mWindowManagerService.mInputMonitor.notifyConfigurationChanged(); } @SuppressWarnings("unused") @@ -369,28 +433,41 @@ 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 int interceptMotionBeforeQueueingWhenScreenOff(int policyFlags) { + return mWindowManagerService.mInputMonitor.interceptMotionBeforeQueueingWhenScreenOff( + policyFlags); + } + + @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 +496,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>(); @@ -526,7 +530,17 @@ public class InputManager { return names.toArray(new String[names.size()]); } - + + @SuppressWarnings("unused") + public int getKeyRepeatTimeout() { + return ViewConfiguration.getKeyRepeatTimeout(); + } + + @SuppressWarnings("unused") + public int getKeyRepeatDelay() { + return ViewConfiguration.getKeyRepeatDelay(); + } + @SuppressWarnings("unused") public int getMaxEventsPerSecond() { int result = 0; @@ -535,9 +549,23 @@ public class InputManager { } catch (NumberFormatException e) { } if (result < 1) { - result = 60; + result = 55; } 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/wm/InputMonitor.java b/services/java/com/android/server/wm/InputMonitor.java new file mode 100644 index 000000000000..45a78af37af3 --- /dev/null +++ b/services/java/com/android/server/wm/InputMonitor.java @@ -0,0 +1,384 @@ +/* + * 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.wm; + +import android.graphics.Rect; +import android.os.Process; +import android.os.RemoteException; +import android.util.Log; +import android.util.Slog; +import android.view.KeyEvent; +import android.view.WindowManager; + +import java.util.ArrayList; + +final class InputMonitor { + private final WindowManagerService mService; + + // Current window with input focus for keys and other non-touch events. May be null. + private WindowState mInputFocus; + + // When true, prevents input dispatch from proceeding until set to false again. + private boolean mInputDispatchFrozen; + + // 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; + + public InputMonitor(WindowManagerService service) { + mService = service; + } + + /* Notifies the window manager about a broken input channel. + * + * Called by the InputManager. + */ + public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle) { + if (inputWindowHandle == null) { + return; + } + + synchronized (mService.mWindowMap) { + WindowState windowState = (WindowState) inputWindowHandle.windowState; + Slog.i(WindowManagerService.TAG, "WINDOW DIED " + windowState); + mService.removeWindowLocked(windowState.mSession, windowState); + } + } + + /* Notifies the window manager about an application that is not responding. + * Returns a new timeout to continue waiting in nanoseconds, or 0 to abort dispatch. + * + * Called by the InputManager. + */ + public long notifyANR(InputApplicationHandle inputApplicationHandle, + InputWindowHandle inputWindowHandle) { + AppWindowToken appWindowToken = null; + if (inputWindowHandle != null) { + synchronized (mService.mWindowMap) { + WindowState windowState = (WindowState) inputWindowHandle.windowState; + if (windowState != null) { + Slog.i(WindowManagerService.TAG, "Input event dispatching timed out sending to " + + windowState.mAttrs.getTitle()); + appWindowToken = windowState.mAppToken; + } + } + } + + if (appWindowToken == null && inputApplicationHandle != null) { + appWindowToken = inputApplicationHandle.appWindowToken; + Slog.i(WindowManagerService.TAG, "Input event dispatching timed out sending to application " + + appWindowToken.stringName); + } + + if (appWindowToken != null && appWindowToken.appToken != null) { + try { + // Notify the activity manager about the timeout and let it decide whether + // to abort dispatching or keep waiting. + boolean abort = appWindowToken.appToken.keyDispatchingTimedOut(); + if (! abort) { + // The activity manager declined to abort dispatching. + // Wait a bit longer and timeout again later. + return appWindowToken.inputDispatchingTimeoutNanos; + } + } catch (RemoteException ex) { + } + } + return 0; // abort dispatching + } + + private void addDragInputWindowLw(InputWindowList windowList) { + final InputWindow inputWindow = windowList.add(); + inputWindow.inputChannel = mService.mDragState.mServerChannel; + inputWindow.name = "drag"; + inputWindow.layoutParamsFlags = 0; + inputWindow.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG; + inputWindow.dispatchingTimeoutNanos = WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; + inputWindow.visible = true; + inputWindow.canReceiveKeys = false; + inputWindow.hasFocus = true; + inputWindow.hasWallpaper = false; + inputWindow.paused = false; + inputWindow.layer = mService.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 = mService.mDisplay.getWidth(); + inputWindow.frameBottom = mService.mDisplay.getHeight(); + + // The drag window cannot receive new touches. + inputWindow.touchableRegion.setEmpty(); + } + + public void setUpdateInputWindowsNeededLw() { + mUpdateInputWindowsNeeded = true; + } + + /* Updates the cached window information provided to the input dispatcher. */ + 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 = mService.mWindows; + + // If there's a drag in flight, provide a pseudowindow to catch drag input + final boolean inDrag = (mService.mDragState != null); + if (inDrag) { + if (WindowManagerService.DEBUG_DRAG) { + Log.d(WindowManagerService.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); + if (child.mInputChannel == null || child.mRemoved) { + // Skip this window because it cannot possibly receive input. + continue; + } + + final int flags = child.mAttrs.flags; + final int type = child.mAttrs.type; + + final boolean hasFocus = (child == mInputFocus); + final boolean isVisible = child.isVisibleLw(); + final boolean hasWallpaper = (child == mService.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) { + mService.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; + inputWindow.layoutParamsType = type; + inputWindow.dispatchingTimeoutNanos = child.getInputDispatchingTimeoutNanos(); + inputWindow.visible = isVisible; + inputWindow.canReceiveKeys = child.canReceiveKeys(); + inputWindow.hasFocus = hasFocus; + inputWindow.hasWallpaper = hasWallpaper; + inputWindow.paused = child.mAppToken != null ? child.mAppToken.paused : false; + inputWindow.layer = child.mLayer; + inputWindow.ownerPid = child.mSession.mPid; + inputWindow.ownerUid = child.mSession.mUid; + + final Rect frame = child.mFrame; + inputWindow.frameLeft = frame.left; + inputWindow.frameTop = frame.top; + inputWindow.frameRight = frame.right; + inputWindow.frameBottom = frame.bottom; + + child.getTouchableRegion(inputWindow.touchableRegion); + } + + // Send windows to native code. + mService.mInputManager.setInputWindows(mTempInputWindows.toNullTerminatedArray()); + + // Clear the list in preparation for the next round. + // Also avoids keeping InputChannel objects referenced unnecessarily. + mTempInputWindows.clear(); + } + + /* Notifies that the input device configuration has changed. */ + public void notifyConfigurationChanged() { + mService.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) { + mService.mPolicy.notifyLidSwitchChanged(whenNanos, lidOpen); + } + + /* 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( + KeyEvent event, int policyFlags, boolean isScreenOn) { + return mService.mPolicy.interceptKeyBeforeQueueing(event, policyFlags, isScreenOn); + } + + /* Provides an opportunity for the window manager policy to intercept early + * motion event processing when the screen is off since these events are normally + * dropped. */ + public int interceptMotionBeforeQueueingWhenScreenOff(int policyFlags) { + return mService.mPolicy.interceptMotionBeforeQueueingWhenScreenOff(policyFlags); + } + + /* Provides an opportunity for the window manager policy to process a key before + * ordinary dispatch. */ + public boolean interceptKeyBeforeDispatching( + InputWindowHandle focus, KeyEvent event, int policyFlags) { + WindowState windowState = focus != null ? (WindowState) focus.windowState : null; + return mService.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 mService.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, boolean updateInputWindows) { + if (WindowManagerService.DEBUG_INPUT) { + Slog.d(WindowManagerService.TAG, "Input focus has changed to " + newWindow); + } + + if (newWindow != mInputFocus) { + if (newWindow != null && newWindow.canReceiveKeys()) { + // Displaying a window implicitly causes dispatching to be unpaused. + // This is to protect against bugs if someone pauses dispatching but + // forgets to resume. + newWindow.mToken.paused = false; + } + + mInputFocus = newWindow; + setUpdateInputWindowsNeededLw(); + + if (updateInputWindows) { + updateInputWindowsLw(false /*force*/); + } + } + } + + public void setFocusedAppLw(AppWindowToken newApp) { + // Focused app has changed. + if (newApp == null) { + mService.mInputManager.setFocusedApplication(null); + } else { + mTempInputApplication.inputApplicationHandle = newApp.mInputApplicationHandle; + mTempInputApplication.name = newApp.toString(); + mTempInputApplication.dispatchingTimeoutNanos = + newApp.inputDispatchingTimeoutNanos; + + mService.mInputManager.setFocusedApplication(mTempInputApplication); + + mTempInputApplication.recycle(); + } + } + + public void pauseDispatchingLw(WindowToken window) { + if (! window.paused) { + if (WindowManagerService.DEBUG_INPUT) { + Slog.v(WindowManagerService.TAG, "Pausing WindowToken " + window); + } + + window.paused = true; + updateInputWindowsLw(true /*force*/); + } + } + + public void resumeDispatchingLw(WindowToken window) { + if (window.paused) { + if (WindowManagerService.DEBUG_INPUT) { + Slog.v(WindowManagerService.TAG, "Resuming WindowToken " + window); + } + + window.paused = false; + updateInputWindowsLw(true /*force*/); + } + } + + public void freezeInputDispatchingLw() { + if (! mInputDispatchFrozen) { + if (WindowManagerService.DEBUG_INPUT) { + Slog.v(WindowManagerService.TAG, "Freezing input dispatching"); + } + + mInputDispatchFrozen = true; + updateInputDispatchModeLw(); + } + } + + public void thawInputDispatchingLw() { + if (mInputDispatchFrozen) { + if (WindowManagerService.DEBUG_INPUT) { + Slog.v(WindowManagerService.TAG, "Thawing input dispatching"); + } + + mInputDispatchFrozen = false; + updateInputDispatchModeLw(); + } + } + + public void setEventDispatchingLw(boolean enabled) { + if (mInputDispatchEnabled != enabled) { + if (WindowManagerService.DEBUG_INPUT) { + Slog.v(WindowManagerService.TAG, "Setting event dispatching to " + enabled); + } + + mInputDispatchEnabled = enabled; + updateInputDispatchModeLw(); + } + } + + private void updateInputDispatchModeLw() { + mService.mInputManager.setInputDispatchMode(mInputDispatchEnabled, mInputDispatchFrozen); + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/InputWindow.java b/services/java/com/android/server/wm/InputWindow.java index befc770b4136..e3eb4732dc81 100644 --- a/services/java/com/android/server/InputWindow.java +++ b/services/java/com/android/server/wm/InputWindow.java @@ -14,70 +14,65 @@ * limitations under the License. */ -package com.android.server; +package com.android.server.wm; +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/wm/InputWindowHandle.java b/services/java/com/android/server/wm/InputWindowHandle.java new file mode 100644 index 000000000000..cc508c6f7013 --- /dev/null +++ b/services/java/com/android/server/wm/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.wm; + +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/InputWindowList.java b/services/java/com/android/server/wm/InputWindowList.java index 1cbb2cc6f9c0..6077337a8aae 100644 --- a/services/java/com/android/server/InputWindowList.java +++ b/services/java/com/android/server/wm/InputWindowList.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server; +package com.android.server.wm; /** diff --git a/services/java/com/android/server/wm/ScreenRotationAnimation.java b/services/java/com/android/server/wm/ScreenRotationAnimation.java new file mode 100644 index 000000000000..fbf1ec3b095f --- /dev/null +++ b/services/java/com/android/server/wm/ScreenRotationAnimation.java @@ -0,0 +1,413 @@ +/* + * 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.wm; + +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) { + // Device is not capable of screenshots... we can't do an animation. + return; + } + + // Screenshot does NOT include rotation! + mSnapshotRotation = 0; + mWidth = screenshot.getWidth(); + mHeight = screenshot.getHeight(); + + 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); + } catch (Surface.OutOfResourcesException e) { + Slog.w(TAG, "Unable to lock surface", e); + } + if (c == null) { + Slog.w(TAG, "Null surface canvas"); + mSurface.destroy(); + mSurface = null; + return; + } + + Paint paint = new Paint(0); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); + c.drawBitmap(screenshot, 0, 0, paint); + + mSurface.unlockCanvasAndPost(c); + } + } finally { + if (!inTransaction) { + Surface.closeTransaction(); + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, + "<<< CLOSE TRANSACTION ScreenRotationAnimation"); + } + + screenshot.recycle(); + } + } + + boolean hasScreenshot() { + return mSurface != null; + } + + 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) { + if (mSurface == null) { + // Can't do animation. + return false; + } + + // Figure out how the screen has moved from the original rotation. + int delta = deltaRotation(mCurRotation, mOriginalRotation); + + 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/wm/Session.java b/services/java/com/android/server/wm/Session.java new file mode 100644 index 000000000000..0f09356f95e5 --- /dev/null +++ b/services/java/com/android/server/wm/Session.java @@ -0,0 +1,423 @@ +/* + * 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.wm; + +import com.android.internal.view.IInputContext; +import com.android.internal.view.IInputMethodClient; +import com.android.internal.view.IInputMethodManager; +import com.android.server.wm.WindowManagerService.H; + +import android.content.ClipData; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.graphics.Region; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcel; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Slog; +import android.view.IWindow; +import android.view.IWindowSession; +import android.view.InputChannel; +import android.view.Surface; +import android.view.SurfaceSession; +import android.view.WindowManager; + +import java.io.PrintWriter; + +/** + * This class represents an active client session. There is generally one + * Session object per process that is interacting with the window manager. + */ +final class Session extends IWindowSession.Stub + implements IBinder.DeathRecipient { + final WindowManagerService mService; + final IInputMethodClient mClient; + final IInputContext mInputContext; + final int mUid; + final int mPid; + final String mStringName; + SurfaceSession mSurfaceSession; + int mNumWindow = 0; + boolean mClientDead = false; + + public Session(WindowManagerService service, IInputMethodClient client, + IInputContext inputContext) { + mService = service; + mClient = client; + mInputContext = inputContext; + mUid = Binder.getCallingUid(); + mPid = Binder.getCallingPid(); + StringBuilder sb = new StringBuilder(); + sb.append("Session{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(" uid "); + sb.append(mUid); + sb.append("}"); + mStringName = sb.toString(); + + synchronized (mService.mWindowMap) { + if (mService.mInputMethodManager == null && mService.mHaveInputMethods) { + IBinder b = ServiceManager.getService( + Context.INPUT_METHOD_SERVICE); + mService.mInputMethodManager = IInputMethodManager.Stub.asInterface(b); + } + } + long ident = Binder.clearCallingIdentity(); + try { + // Note: it is safe to call in to the input method manager + // here because we are not holding our lock. + if (mService.mInputMethodManager != null) { + mService.mInputMethodManager.addClient(client, inputContext, + mUid, mPid); + } else { + client.setUsingInputMethod(false); + } + client.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + // The caller has died, so we can just forget about this. + try { + if (mService.mInputMethodManager != null) { + mService.mInputMethodManager.removeClient(client); + } + } catch (RemoteException ee) { + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @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) { + // Log all 'real' exceptions thrown to the caller + if (!(e instanceof SecurityException)) { + Slog.e(WindowManagerService.TAG, "Window Session Crash", e); + } + throw e; + } + } + + public void binderDied() { + // Note: it is safe to call in to the input method manager + // here because we are not holding our lock. + try { + if (mService.mInputMethodManager != null) { + mService.mInputMethodManager.removeClient(mClient); + } + } catch (RemoteException e) { + } + synchronized(mService.mWindowMap) { + mClient.asBinder().unlinkToDeath(this, 0); + mClientDead = true; + killSessionLocked(); + } + } + + public int add(IWindow window, WindowManager.LayoutParams attrs, + int viewVisibility, Rect outContentInsets, InputChannel outInputChannel) { + return mService.addWindow(this, window, attrs, viewVisibility, outContentInsets, + outInputChannel); + } + + public int addWithoutInputChannel(IWindow window, WindowManager.LayoutParams attrs, + int viewVisibility, Rect outContentInsets) { + return mService.addWindow(this, window, attrs, viewVisibility, outContentInsets, null); + } + + public void remove(IWindow window) { + mService.removeWindow(this, window); + } + + public int relayout(IWindow window, WindowManager.LayoutParams attrs, + int requestedWidth, int requestedHeight, int viewFlags, + boolean insetsPending, Rect outFrame, Rect outContentInsets, + Rect outVisibleInsets, Configuration outConfig, Surface outSurface) { + //Log.d(TAG, ">>>>>> ENTERED relayout from " + Binder.getCallingPid()); + int res = mService.relayoutWindow(this, window, attrs, + requestedWidth, requestedHeight, viewFlags, insetsPending, + outFrame, outContentInsets, outVisibleInsets, outConfig, outSurface); + //Log.d(TAG, "<<<<<< EXITING relayout to " + Binder.getCallingPid()); + return res; + } + + public boolean outOfMemory(IWindow window) { + return mService.outOfMemoryWindow(this, window); + } + + public void setTransparentRegion(IWindow window, Region region) { + mService.setTransparentRegionWindow(this, window, region); + } + + public void setInsets(IWindow window, int touchableInsets, + Rect contentInsets, Rect visibleInsets, Region touchableArea) { + mService.setInsetsWindow(this, window, touchableInsets, contentInsets, + visibleInsets, touchableArea); + } + + public void getDisplayFrame(IWindow window, Rect outDisplayFrame) { + mService.getWindowDisplayFrame(this, window, outDisplayFrame); + } + + public void finishDrawing(IWindow window) { + if (WindowManagerService.localLOGV) Slog.v( + WindowManagerService.TAG, "IWindow finishDrawing called for " + window); + mService.finishDrawingWindow(this, window); + } + + public void setInTouchMode(boolean mode) { + synchronized(mService.mWindowMap) { + mService.mInTouchMode = mode; + } + } + + public boolean getInTouchMode() { + synchronized(mService.mWindowMap) { + return mService.mInTouchMode; + } + } + + public boolean performHapticFeedback(IWindow window, int effectId, + boolean always) { + synchronized(mService.mWindowMap) { + long ident = Binder.clearCallingIdentity(); + try { + return mService.mPolicy.performHapticFeedbackLw( + mService.windowForClientLocked(this, window, true), + effectId, always); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + /* Drag/drop */ + public IBinder prepareDrag(IWindow window, int flags, + int width, int height, Surface outSurface) { + return mService.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 (WindowManagerService.DEBUG_DRAG) { + Slog.d(WindowManagerService.TAG, "perform drag: win=" + window + " data=" + data); + } + + synchronized (mService.mWindowMap) { + if (mService.mDragState == null) { + Slog.w(WindowManagerService.TAG, "No drag prepared"); + throw new IllegalStateException("performDrag() without prepareDrag()"); + } + + if (dragToken != mService.mDragState.mToken) { + Slog.w(WindowManagerService.TAG, "Performing mismatched drag"); + throw new IllegalStateException("performDrag() does not match prepareDrag()"); + } + + WindowState callingWin = mService.windowForClientLocked(null, window, false); + if (callingWin == null) { + Slog.w(WindowManagerService.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() + + mService.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 + + mService.mDragState.register(); + mService.mInputMonitor.updateInputWindowsLw(true /*force*/); + if (!mService.mInputManager.transferTouchFocus(callingWin.mInputChannel, + mService.mDragState.mServerChannel)) { + Slog.e(WindowManagerService.TAG, "Unable to transfer touch focus"); + mService.mDragState.unregister(); + mService.mDragState = null; + mService.mInputMonitor.updateInputWindowsLw(true /*force*/); + return false; + } + + mService.mDragState.mData = data; + mService.mDragState.mCurrentX = touchX; + mService.mDragState.mCurrentY = touchY; + mService.mDragState.broadcastDragStartedLw(touchX, touchY); + + // remember the thumb offsets for later + mService.mDragState.mThumbOffsetX = thumbCenterX; + mService.mDragState.mThumbOffsetY = thumbCenterY; + + // Make the surface visible at the proper location + final Surface surface = mService.mDragState.mSurface; + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, ">>> OPEN TRANSACTION performDrag"); + Surface.openTransaction(); + try { + surface.setPosition((int)(touchX - thumbCenterX), + (int)(touchY - thumbCenterY)); + surface.setAlpha(.7071f); + surface.setLayer(mService.mDragState.getDragLayerLw()); + surface.show(); + } finally { + Surface.closeTransaction(); + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, "<<< CLOSE TRANSACTION performDrag"); + } + } + + return true; // success! + } + + public void reportDropResult(IWindow window, boolean consumed) { + IBinder token = window.asBinder(); + if (WindowManagerService.DEBUG_DRAG) { + Slog.d(WindowManagerService.TAG, "Drop result=" + consumed + " reported by " + token); + } + + synchronized (mService.mWindowMap) { + long ident = Binder.clearCallingIdentity(); + try { + if (mService.mDragState == null || mService.mDragState.mToken != token) { + Slog.w(WindowManagerService.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. + mService.mH.removeMessages(H.DRAG_END_TIMEOUT, window.asBinder()); + WindowState callingWin = mService.windowForClientLocked(null, window, false); + if (callingWin == null) { + Slog.w(WindowManagerService.TAG, "Bad result-reporting window " + window); + return; // !!! TODO: throw here? + } + + mService.mDragState.mDragResult = consumed; + mService.mDragState.endDragLw(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + public void dragRecipientEntered(IWindow window) { + if (WindowManagerService.DEBUG_DRAG) { + Slog.d(WindowManagerService.TAG, "Drag into new candidate view @ " + window.asBinder()); + } + } + + public void dragRecipientExited(IWindow window) { + if (WindowManagerService.DEBUG_DRAG) { + Slog.d(WindowManagerService.TAG, "Drag from old candidate view @ " + window.asBinder()); + } + } + + public void setWallpaperPosition(IBinder window, float x, float y, float xStep, float yStep) { + synchronized(mService.mWindowMap) { + long ident = Binder.clearCallingIdentity(); + try { + mService.setWindowWallpaperPositionLocked( + mService.windowForClientLocked(this, window, true), + x, y, xStep, yStep); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + public void wallpaperOffsetsComplete(IBinder window) { + mService.wallpaperOffsetsComplete(window); + } + + public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y, + int z, Bundle extras, boolean sync) { + synchronized(mService.mWindowMap) { + long ident = Binder.clearCallingIdentity(); + try { + return mService.sendWindowWallpaperCommandLocked( + mService.windowForClientLocked(this, window, true), + action, x, y, z, extras, sync); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + public void wallpaperCommandComplete(IBinder window, Bundle result) { + mService.wallpaperCommandComplete(window, result); + } + + void windowAddedLocked() { + if (mSurfaceSession == null) { + if (WindowManagerService.localLOGV) Slog.v( + WindowManagerService.TAG, "First window added to " + this + ", creating SurfaceSession"); + mSurfaceSession = new SurfaceSession(); + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i( + WindowManagerService.TAG, " NEW SURFACE SESSION " + mSurfaceSession); + mService.mSessions.add(this); + } + mNumWindow++; + } + + void windowRemovedLocked() { + mNumWindow--; + killSessionLocked(); + } + + void killSessionLocked() { + if (mNumWindow <= 0 && mClientDead) { + mService.mSessions.remove(this); + if (mSurfaceSession != null) { + if (WindowManagerService.localLOGV) Slog.v( + WindowManagerService.TAG, "Last window removed from " + this + + ", destroying " + mSurfaceSession); + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i( + WindowManagerService.TAG, " KILL SURFACE SESSION " + mSurfaceSession); + try { + mSurfaceSession.kill(); + } catch (Exception e) { + Slog.w(WindowManagerService.TAG, "Exception thrown when killing surface session " + + mSurfaceSession + " in session " + this + + ": " + e.toString()); + } + mSurfaceSession = null; + } + } + } + + void dump(PrintWriter pw, String prefix) { + pw.print(prefix); pw.print("mNumWindow="); pw.print(mNumWindow); + pw.print(" mClientDead="); pw.print(mClientDead); + pw.print(" mSurfaceSession="); pw.println(mSurfaceSession); + } + + @Override + public String toString() { + return mStringName; + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/wm/StartingData.java b/services/java/com/android/server/wm/StartingData.java new file mode 100644 index 000000000000..625fcfeb27d6 --- /dev/null +++ b/services/java/com/android/server/wm/StartingData.java @@ -0,0 +1,36 @@ +/* + * 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.wm; + +final class StartingData { + final String pkg; + final int theme; + final CharSequence nonLocalizedLabel; + final int labelRes; + final int icon; + final int windowFlags; + + StartingData(String _pkg, int _theme, CharSequence _nonLocalizedLabel, + int _labelRes, int _icon, int _windowFlags) { + pkg = _pkg; + theme = _theme; + nonLocalizedLabel = _nonLocalizedLabel; + labelRes = _labelRes; + icon = _icon; + windowFlags = _windowFlags; + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/wm/StrictModeFlash.java b/services/java/com/android/server/wm/StrictModeFlash.java new file mode 100644 index 000000000000..2c62080ea7d2 --- /dev/null +++ b/services/java/com/android/server/wm/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.wm; + + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.PixelFormat; +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/ViewServer.java b/services/java/com/android/server/wm/ViewServer.java index 7b5d18ac8b2c..cebd5e70e8a8 100644 --- a/services/java/com/android/server/ViewServer.java +++ b/services/java/com/android/server/wm/ViewServer.java @@ -14,7 +14,8 @@ * limitations under the License. */ -package com.android.server; +package com.android.server.wm; + import android.util.Slog; @@ -33,7 +34,7 @@ import java.io.OutputStreamWriter; /** * The ViewServer is local socket server that can be used to communicate with the * views of the opened windows. Communication with the views is ensured by the - * {@link com.android.server.WindowManagerService} and is a cross-process operation. + * {@link com.android.server.wm.WindowManagerService} and is a cross-process operation. * * {@hide} */ diff --git a/services/java/com/android/server/wm/Watermark.java b/services/java/com/android/server/wm/Watermark.java new file mode 100644 index 000000000000..22126f3052ed --- /dev/null +++ b/services/java/com/android/server/wm/Watermark.java @@ -0,0 +1,177 @@ +/* + * 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.wm; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.graphics.Paint.FontMetricsInt; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.TypedValue; +import android.view.Display; +import android.view.Surface; +import android.view.SurfaceSession; +import android.view.Surface.OutOfResourcesException; + +/** + * Displays a watermark on top of the window manager's windows. + */ +class Watermark { + final String[] mTokens; + final String mText; + final Paint mTextPaint; + final int mTextWidth; + final int mTextHeight; + final int mTextAscent; + final int mTextDescent; + final int mDeltaX; + final int mDeltaY; + + Surface mSurface; + int mLastDW; + int mLastDH; + boolean mDrawNeeded; + + Watermark(Display display, SurfaceSession session, String[] tokens) { + final DisplayMetrics dm = new DisplayMetrics(); + display.getMetrics(dm); + + if (false) { + Log.i(WindowManagerService.TAG, "*********************** WATERMARK"); + for (int i=0; i<tokens.length; i++) { + Log.i(WindowManagerService.TAG, " TOKEN #" + i + ": " + tokens[i]); + } + } + + mTokens = tokens; + + StringBuilder builder = new StringBuilder(32); + int len = mTokens[0].length(); + len = len & ~1; + for (int i=0; i<len; i+=2) { + int c1 = mTokens[0].charAt(i); + int c2 = mTokens[0].charAt(i+1); + if (c1 >= 'a' && c1 <= 'f') c1 = c1 - 'a' + 10; + else if (c1 >= 'A' && c1 <= 'F') c1 = c1 - 'A' + 10; + else c1 -= '0'; + if (c2 >= 'a' && c2 <= 'f') c2 = c2 - 'a' + 10; + else if (c2 >= 'A' && c2 <= 'F') c2 = c2 - 'A' + 10; + else c2 -= '0'; + builder.append((char)(255-((c1*16)+c2))); + } + mText = builder.toString(); + if (false) { + Log.i(WindowManagerService.TAG, "Final text: " + mText); + } + + int fontSize = WindowManagerService.getPropertyInt(tokens, 1, + TypedValue.COMPLEX_UNIT_DIP, 20, dm); + + mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mTextPaint.setTextSize(fontSize); + mTextPaint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD)); + + FontMetricsInt fm = mTextPaint.getFontMetricsInt(); + mTextWidth = (int)mTextPaint.measureText(mText); + mTextAscent = fm.ascent; + mTextDescent = fm.descent; + mTextHeight = fm.descent - fm.ascent; + + mDeltaX = WindowManagerService.getPropertyInt(tokens, 2, + TypedValue.COMPLEX_UNIT_PX, mTextWidth*2, dm); + mDeltaY = WindowManagerService.getPropertyInt(tokens, 3, + TypedValue.COMPLEX_UNIT_PX, mTextHeight*3, dm); + int shadowColor = WindowManagerService.getPropertyInt(tokens, 4, + TypedValue.COMPLEX_UNIT_PX, 0xb0000000, dm); + int color = WindowManagerService.getPropertyInt(tokens, 5, + TypedValue.COMPLEX_UNIT_PX, 0x60ffffff, dm); + int shadowRadius = WindowManagerService.getPropertyInt(tokens, 6, + TypedValue.COMPLEX_UNIT_PX, 7, dm); + int shadowDx = WindowManagerService.getPropertyInt(tokens, 8, + TypedValue.COMPLEX_UNIT_PX, 0, dm); + int shadowDy = WindowManagerService.getPropertyInt(tokens, 9, + TypedValue.COMPLEX_UNIT_PX, 0, dm); + + mTextPaint.setColor(color); + mTextPaint.setShadowLayer(shadowRadius, shadowDx, shadowDy, shadowColor); + + try { + mSurface = new Surface(session, 0, + "WatermarkSurface", -1, 1, 1, PixelFormat.TRANSLUCENT, 0); + mSurface.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER*100); + mSurface.setPosition(0, 0); + mSurface.show(); + } catch (OutOfResourcesException e) { + } + } + + void positionSurface(int dw, int dh) { + if (mLastDW != dw || mLastDH != dh) { + mLastDW = dw; + mLastDH = dh; + mSurface.setSize(dw, dh); + mDrawNeeded = true; + } + } + + void drawIfNeeded() { + if (mDrawNeeded) { + final int dw = mLastDW; + final int dh = mLastDH; + + mDrawNeeded = false; + Rect dirty = new Rect(0, 0, dw, dh); + Canvas c = null; + try { + c = mSurface.lockCanvas(dirty); + } catch (IllegalArgumentException e) { + } catch (OutOfResourcesException e) { + } + if (c != null) { + c.drawColor(0, PorterDuff.Mode.CLEAR); + + int deltaX = mDeltaX; + int deltaY = mDeltaY; + + // deltaX shouldn't be close to a round fraction of our + // x step, or else things will line up too much. + int div = (dw+mTextWidth)/deltaX; + int rem = (dw+mTextWidth) - (div*deltaX); + int qdelta = deltaX/4; + if (rem < qdelta || rem > (deltaX-qdelta)) { + deltaX += deltaX/3; + } + + int y = -mTextHeight; + int x = -mTextWidth; + while (y < (dh+mTextHeight)) { + c.drawText(mText, x, y, mTextPaint); + x += deltaX; + if (x >= dw) { + x -= (dw+mTextWidth); + y += deltaY; + } + } + mSurface.unlockCanvasAndPost(c); + } + } + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 667b5449439b..33e6a36d73b8 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server; +package com.android.server.wm; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; @@ -22,14 +22,11 @@ import static android.view.WindowManager.LayoutParams.FLAG_BLUR_BEHIND; 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,15 +36,21 @@ 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; import com.android.internal.view.WindowManagerPolicyThread; +import com.android.server.AttributeCache; +import com.android.server.EventLogTags; +import com.android.server.PowerManagerService; +import com.android.server.Watchdog; 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.Context; @@ -57,14 +60,12 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; -import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Region; -import android.graphics.Typeface; -import android.graphics.Paint.FontMetricsInt; import android.os.BatteryStats; import android.os.Binder; import android.os.Bundle; @@ -81,6 +82,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 +94,6 @@ import android.util.Slog; import android.util.SparseIntArray; import android.util.TypedValue; import android.view.Display; -import android.view.Gravity; -import android.view.HapticFeedbackConstants; import android.view.IApplicationToken; import android.view.IOnKeyguardExitResult; import android.view.IRotationWatcher; @@ -103,23 +103,21 @@ 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; import android.view.SurfaceSession; import android.view.View; -import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.WindowManagerImpl; import android.view.WindowManagerPolicy; -import android.view.Surface.OutOfResourcesException; import android.view.WindowManager.LayoutParams; -import android.view.animation.AccelerateInterpolator; 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 +141,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 +151,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; @@ -180,6 +180,16 @@ public class WindowManagerService extends IWindowManager.Stub */ static final int WINDOW_LAYER_MULTIPLIER = 5; + /** + * Dim surface layer is immediately below target window. + */ + static final int LAYER_OFFSET_DIM = 1; + + /** + * Blur surface layer is immediately below dim layer. + */ + static final int LAYER_OFFSET_BLUR = 2; + /** The maximum length we will accept for a loaded animation duration: * this is 10 seconds. */ @@ -195,16 +205,22 @@ 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; + static final long DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS = 5000 * 1000000L; static final int UPDATE_FOCUS_NORMAL = 0; static final int UPDATE_FOCUS_WILL_ASSIGN_LAYERS = 1; @@ -288,12 +304,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 +312,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 +329,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 +346,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 +367,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 +380,8 @@ public class WindowManagerService extends IWindowManager.Stub Surface mBlurSurface; boolean mBlurShown; Watermark mWatermark; + StrictModeFlash mStrictModeFlash; + ScreenRotationAnimation mScreenRotationAnimation; int mTransactionSequence = 0; @@ -387,6 +398,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 +450,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 +471,12 @@ 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; + DimSurface mWindowAnimationBackgroundSurface = null; int mWallpaperAnimLayerAdjustment; float mLastWallpaperX = -1; float mLastWallpaperY = -1; @@ -485,6 +507,64 @@ public class WindowManagerService extends IWindowManager.Stub boolean mTurnOnScreen; + DragState mDragState = null; + 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 +588,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 +634,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 +676,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 +754,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 +763,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 +821,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 +903,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 +922,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 +952,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 +964,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 +974,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 +996,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 +1024,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 +1047,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 +1117,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 +1138,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 +1167,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 +1354,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 +1431,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 +1444,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 +1474,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 +1617,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 +1718,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 +1926,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())) { @@ -1841,7 +1972,7 @@ public class WindowManagerService extends IWindowManager.Stub + attrs.token + ". Aborting."); return WindowManagerImpl.ADD_BAD_APP_TOKEN; } - token = new WindowToken(attrs.token, -1, false); + token = new WindowToken(this, attrs.token, -1, false); addToken = true; } else if (attrs.type >= FIRST_APPLICATION_WINDOW && attrs.type <= LAST_APPLICATION_WINDOW) { @@ -1875,7 +2006,7 @@ public class WindowManagerService extends IWindowManager.Stub } } - win = new WindowState(session, client, token, + win = new WindowState(this, session, client, token, attachedWindow, attrs, viewVisibility); if (win.mDeathRecipient == null) { // Client has apparently died, so there is no reason to @@ -1898,18 +2029,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 +2081,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 +2103,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 +2182,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 +2200,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 +2235,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 +2250,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 +2261,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,10 +2301,10 @@ public class WindowManagerService extends IWindowManager.Stub } } - mInputMonitor.updateInputWindowsLw(); + mInputMonitor.updateInputWindowsLw(true /*force*/); } - private static void logSurface(WindowState w, String msg, RuntimeException where) { + static void logSurface(WindowState w, String msg, RuntimeException where) { String str = " SURFACE " + Integer.toHexString(w.hashCode()) + ": " + msg + " / " + w.mAttrs.getTitle(); if (where != null) { @@ -2168,21 +2314,23 @@ public class WindowManagerService extends IWindowManager.Stub } } - private void setTransparentRegionWindow(Session session, IWindow client, Region region) { + void setTransparentRegionWindow(Session session, IWindow client, Region region) { long origId = Binder.clearCallingIdentity(); try { 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 +2341,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 +2350,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 +2435,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 +2504,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 +2560,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 +2647,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 +2679,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 +2704,7 @@ public class WindowManagerService extends IWindowManager.Stub inTouchMode = mInTouchMode; - mInputMonitor.updateInputWindowsLw(); + mInputMonitor.updateInputWindowsLw(true /*force*/); } if (configChanged) { @@ -2555,6 +2717,22 @@ public class WindowManagerService extends IWindowManager.Stub | (displayed ? WindowManagerImpl.RELAYOUT_FIRST_TIME : 0); } + public boolean outOfMemoryWindow(Session session, IWindow client) { + long origId = Binder.clearCallingIdentity(); + + try { + synchronized(mWindowMap) { + WindowState win = windowForClientLocked(session, client, false); + if (win == null) { + return false; + } + return reclaimSomeSurfaceMemoryLocked(win, "from-client", false); + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + public void finishDrawingWindow(Session session, IWindow client) { final long origId = Binder.clearCallingIdentity(); synchronized(mWindowMap) { @@ -2571,7 +2749,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 +2770,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) { @@ -2606,7 +2784,7 @@ public class WindowManagerService extends IWindowManager.Stub return null; } - private void applyEnterAnimationLocked(WindowState win) { + void applyEnterAnimationLocked(WindowState win) { int transit = WindowManagerPolicy.TRANSIT_SHOW; if (win.mEnterAnimationPending) { win.mEnterAnimationPending = false; @@ -2616,7 +2794,7 @@ public class WindowManagerService extends IWindowManager.Stub applyAnimationLocked(win, transit, true); } - private boolean applyAnimationLocked(WindowState win, + boolean applyAnimationLocked(WindowState win, int transit, boolean isEntrance) { if (win.mLocalAnimating && win.mAnimationIsEntrance == isEntrance) { // If we are trying to apply an animation, but already running @@ -2872,9 +3050,8 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG, "Attempted to add existing input method token: " + token); return; } - wtoken = new WindowToken(token, type, true); + wtoken = new WindowToken(this, token, type, true); mTokenMap.put(token, wtoken); - mTokenList.add(wtoken); if (type == TYPE_WALLPAPER) { mWallpaperTokens.add(wtoken); } @@ -2890,7 +3067,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 +3092,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 +3103,7 @@ public class WindowManagerService extends IWindowManager.Stub } } - mInputMonitor.updateInputWindowsLw(); + mInputMonitor.updateInputWindowsLw(true /*force*/); } else { Slog.w(TAG, "Attempted to remove non-existing token: " + token); } @@ -2961,15 +3138,14 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG, "Attempted to add existing app token: " + token); return; } - wtoken = new AppWindowToken(token); + wtoken = new AppWindowToken(this, token); wtoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos; 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 +3262,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 +3284,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 +3309,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 +3330,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 +3422,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 +3442,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 +3501,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 +3557,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 +3597,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 +3652,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 +3758,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 +3910,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 +3979,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 +3998,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 +4008,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 +4019,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 +4136,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 +4176,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 +4186,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 +4198,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 +4217,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 +4240,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 +4273,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 +4298,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 +4332,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 +4435,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 +4647,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 +4688,199 @@ 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); + } + + /** + * Takes a snapshot of the screen. In landscape mode this grabs the whole screen. + * In portrait mode, it grabs the upper region of the screen based on the vertical dimension + * of the target image. + * + * @param width the width of the target bitmap + * @param height the height of the target bitmap + */ + public Bitmap screenshotApplications(IBinder appToken, int width, int height) { + 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 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; + } + + // Don't include wallpaper in bounds calculation + if (!ws.mIsWallpaper) { + 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 = width/(float)fw; + + // 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(width, height, 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, + "freezeRotation()")) { + throw new SecurityException("Requires SET_ORIENTATION permission"); + } + + if (DEBUG_ORIENTATION) Slog.v(TAG, "freezeRotation: mRotation=" + mRotation); + + mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_LOCKED, mRotation); + setRotationUnchecked(WindowManagerPolicy.USE_LAST_ROTATION, false, 0); + } + + public void thawRotation() { + if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION, + "thawRotation()")) { + throw new SecurityException("Requires SET_ORIENTATION permission"); + } + + if (DEBUG_ORIENTATION) Slog.v(TAG, "thawRotation: mRotation=" + mRotation); + + mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_FREE, 777); // rot not used + setRotationUnchecked(WindowManagerPolicy.USE_LAST_ROTATION, false, 0); + } + public void setRotation(int rotation, boolean alwaysSendConfiguration, int animFlags) { if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION, @@ -4490,12 +4894,14 @@ public class WindowManagerService extends IWindowManager.Stub public void setRotationUnchecked(int rotation, boolean alwaysSendConfiguration, int animFlags) { if(DEBUG_ORIENTATION) Slog.v(TAG, - "alwaysSendConfiguration set to "+alwaysSendConfiguration); + "setRotationUnchecked(rotation=" + rotation + + " alwaysSendConfiguration=" + alwaysSendConfiguration + + " animFlags=" + animFlags); long origId = Binder.clearCallingIdentity(); boolean changed; synchronized(mWindowMap) { - changed = setRotationUncheckedLocked(rotation, animFlags); + changed = setRotationUncheckedLocked(rotation, animFlags, false); } if (changed || alwaysSendConfiguration) { @@ -4513,14 +4919,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 +4963,36 @@ 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); + // NOTE: We disable the rotation in the emulator because + // it doesn't support hardware OpenGL emulation yet. + if (CUSTOM_SCREEN_ROTATION && mScreenRotationAnimation != null + && mScreenRotationAnimation.hasScreenshot()) { + 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); @@ -4604,8 +5052,8 @@ public class WindowManagerService extends IWindowManager.Stub * * @return True if the server was successfully started, false otherwise. * - * @see com.android.server.ViewServer - * @see com.android.server.ViewServer#VIEW_SERVER_DEFAULT_PORT + * @see com.android.server.wm.ViewServer + * @see com.android.server.wm.ViewServer#VIEW_SERVER_DEFAULT_PORT */ public boolean startViewServer(int port) { if (isSystemSecure()) { @@ -4651,7 +5099,7 @@ public class WindowManagerService extends IWindowManager.Stub * @return True if the server stopped, false if it wasn't started or * couldn't be stopped. * - * @see com.android.server.ViewServer + * @see com.android.server.wm.ViewServer */ public boolean stopViewServer() { if (isSystemSecure()) { @@ -4673,7 +5121,7 @@ public class WindowManagerService extends IWindowManager.Stub * * @return True if the server is running, false otherwise. * - * @see com.android.server.ViewServer + * @see com.android.server.wm.ViewServer */ public boolean isViewServerRunning() { if (isSystemSecure()) { @@ -4835,7 +5283,7 @@ public class WindowManagerService extends IWindowManager.Stub parameters = ""; } - final WindowManagerService.WindowState window = findWindow(hashCode); + final WindowState window = findWindow(hashCode); if (window == null) { return false; } @@ -4958,7 +5406,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; } } @@ -5010,8 +5464,9 @@ public class WindowManagerService extends IWindowManager.Stub shortSize = (int)(shortSize/dm.density); // These semi-magic numbers define our compatibility modes for - // applications with different screens. Don't change unless you - // make sure to test lots and lots of apps! + // applications with different screens. These are guarantees to + // app developers about the space they can expect for a particular + // configuration. DO NOT CHANGE! if (longSize < 470) { // This is shorter than an HVGA normal density screen (which // is 480 pixels on its long side). @@ -5019,12 +5474,12 @@ public class WindowManagerService extends IWindowManager.Stub | Configuration.SCREENLAYOUT_LONG_NO; } else { // What size is this screen screen? - if (longSize >= 800 && shortSize >= 600) { - // SVGA or larger screens at medium density are the point + if (longSize >= 960 && shortSize >= 720) { + // 1.5xVGA or larger screens at medium density are the point // at which we consider it to be an extra large screen. mScreenLayout = Configuration.SCREENLAYOUT_SIZE_XLARGE; - } else if (longSize >= 530 && shortSize >= 400) { - // SVGA or larger screens at high density are the point + } else if (longSize >= 640 && shortSize >= 480) { + // VGA or larger screens at medium density are the point // at which we consider it to be a large screen. mScreenLayout = Configuration.SCREENLAYOUT_SIZE_LARGE; } else { @@ -5049,318 +5504,129 @@ 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; } - - // ------------------------------------------------------------- - // Input Events and Focus Management - // ------------------------------------------------------------- - - InputMonitor mInputMonitor = new InputMonitor(); - - /* Tracks the progress of input dispatch and ensures that input dispatch state - * is kept in sync with changes in window focus, visibility, registration, and - * other relevant Window Manager state transitions. */ - final class InputMonitor { - // Current window with input focus for keys and other non-touch events. May be null. - private WindowState mInputFocus; - - // When true, prevents input dispatch from proceeding until set to false again. - private boolean mInputDispatchFrozen; - - // When true, input dispatch proceeds normally. Otherwise all events are dropped. - private boolean mInputDispatchEnabled = 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(); - - /* Notifies the window manager about a broken input channel. - * - * Called by the InputManager. - */ - public void notifyInputChannelBroken(InputChannel inputChannel) { - synchronized (mWindowMap) { - WindowState windowState = getWindowStateForInputChannelLocked(inputChannel); - if (windowState == null) { - return; // irrelevant - } - - Slog.i(TAG, "WINDOW DIED " + windowState); - removeWindowLocked(windowState.mSession, windowState); - } + public boolean isHardKeyboardAvailable() { + synchronized (mWindowMap) { + return mHardKeyboardAvailable; } - - /* Notifies the window manager about an application that is not responding. - * Returns a new timeout to continue waiting in nanoseconds, or 0 to abort dispatch. - * - * Called by the InputManager. - */ - public long notifyANR(Object token, InputChannel inputChannel) { - AppWindowToken appWindowToken = null; - if (inputChannel != null) { - synchronized (mWindowMap) { - WindowState windowState = getWindowStateForInputChannelLocked(inputChannel); - if (windowState != null) { - Slog.i(TAG, "Input event dispatching timed out sending to " - + windowState.mAttrs.getTitle()); - appWindowToken = windowState.mAppToken; - } - } - } - - if (appWindowToken == null && token != null) { - appWindowToken = (AppWindowToken) token; - Slog.i(TAG, "Input event dispatching timed out sending to application " - + appWindowToken.stringName); - } + } - if (appWindowToken != null && appWindowToken.appToken != null) { - try { - // Notify the activity manager about the timeout and let it decide whether - // to abort dispatching or keep waiting. - boolean abort = appWindowToken.appToken.keyDispatchingTimedOut(); - if (! abort) { - // The activity manager declined to abort dispatching. - // Wait a bit longer and timeout again later. - return appWindowToken.inputDispatchingTimeoutNanos; - } - } catch (RemoteException ex) { - } - } - return 0; // abort dispatching - } - - private WindowState getWindowStateForInputChannel(InputChannel inputChannel) { - synchronized (mWindowMap) { - return getWindowStateForInputChannelLocked(inputChannel); - } + public boolean isHardKeyboardEnabled() { + synchronized (mWindowMap) { + return mHardKeyboardEnabled; } - - 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; - } + } + + public void setHardKeyboardEnabled(boolean enabled) { + synchronized (mWindowMap) { + if (mHardKeyboardEnabled != enabled) { + mHardKeyboardEnabled = enabled; + mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION); } - - return null; } - - /* Updates the cached window information provided to the input dispatcher. */ - public void updateInputWindowsLw() { - // 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; - final int N = windows.size(); - for (int i = N - 1; i >= 0; i--) { - final WindowState child = windows.get(i); - if (child.mInputChannel == null || child.mRemoved) { - // Skip this window because it cannot possibly receive input. - continue; - } - - final int flags = child.mAttrs.flags; - final int type = child.mAttrs.type; - - final boolean hasFocus = (child == mInputFocus); - final boolean isVisible = child.isVisibleLw(); - final boolean hasWallpaper = (child == mWallpaperTarget) - && (type != WindowManager.LayoutParams.TYPE_KEYGUARD); - - // Add a window to our list of input windows. - final InputWindow inputWindow = mTempInputWindows.add(); - inputWindow.inputChannel = child.mInputChannel; - inputWindow.name = child.toString(); - inputWindow.layoutParamsFlags = flags; - inputWindow.layoutParamsType = type; - inputWindow.dispatchingTimeoutNanos = child.getInputDispatchingTimeoutNanos(); - inputWindow.visible = isVisible; - inputWindow.canReceiveKeys = child.canReceiveKeys(); - inputWindow.hasFocus = hasFocus; - inputWindow.hasWallpaper = hasWallpaper; - inputWindow.paused = child.mAppToken != null ? child.mAppToken.paused : false; - inputWindow.layer = child.mLayer; - inputWindow.ownerPid = child.mSession.mPid; - inputWindow.ownerUid = child.mSession.mUid; - - final Rect frame = child.mFrame; - inputWindow.frameLeft = frame.left; - 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; - } - } - } + } - // Send windows to native code. - mInputManager.setInputWindows(mTempInputWindows.toNullTerminatedArray()); - - // Clear the list in preparation for the next round. - // Also avoids keeping InputChannel objects referenced unnecessarily. - mTempInputWindows.clear(); - } - - /* Notifies that the lid switch changed state. */ - public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) { - mPolicy.notifyLidSwitchChanged(whenNanos, lidOpen); - } - - /* 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); - } - - /* 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 void setOnHardKeyboardStatusChangeListener( + OnHardKeyboardStatusChangeListener listener) { + synchronized (mWindowMap) { + mHardKeyboardStatusChangeListener = listener; } - - /* 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) { - if (DEBUG_INPUT) { - Slog.d(TAG, "Input focus has changed to " + newWindow); - } + } - if (newWindow != mInputFocus) { - if (newWindow != null && newWindow.canReceiveKeys()) { - // Displaying a window implicitly causes dispatching to be unpaused. - // This is to protect against bugs if someone pauses dispatching but - // forgets to resume. - newWindow.mToken.paused = false; - } - - mInputFocus = newWindow; - updateInputWindowsLw(); - } - } - - public void setFocusedAppLw(AppWindowToken newApp) { - // Focused app has changed. - if (newApp == null) { - mInputManager.setFocusedApplication(null); - } else { - mTempInputApplication.name = newApp.toString(); - mTempInputApplication.dispatchingTimeoutNanos = - newApp.inputDispatchingTimeoutNanos; - mTempInputApplication.token = newApp; - - mInputManager.setFocusedApplication(mTempInputApplication); - } - } - - public void pauseDispatchingLw(WindowToken window) { - if (! window.paused) { - if (DEBUG_INPUT) { - Slog.v(TAG, "Pausing WindowToken " + window); - } - - window.paused = true; - updateInputWindowsLw(); - } + void notifyHardKeyboardStatusChange() { + final boolean available, enabled; + final OnHardKeyboardStatusChangeListener listener; + synchronized (mWindowMap) { + listener = mHardKeyboardStatusChangeListener; + available = mHardKeyboardAvailable; + enabled = mHardKeyboardEnabled; } - - public void resumeDispatchingLw(WindowToken window) { - if (window.paused) { - if (DEBUG_INPUT) { - Slog.v(TAG, "Resuming WindowToken " + window); - } - - window.paused = false; - updateInputWindowsLw(); - } + if (listener != null) { + listener.onHardKeyboardStatusChange(available, enabled); } - - public void freezeInputDispatchingLw() { - if (! mInputDispatchFrozen) { - if (DEBUG_INPUT) { - Slog.v(TAG, "Freezing input dispatching"); - } - - mInputDispatchFrozen = true; - updateInputDispatchModeLw(); - } - } - - public void thawInputDispatchingLw() { - if (mInputDispatchFrozen) { - if (DEBUG_INPUT) { - Slog.v(TAG, "Thawing input dispatching"); - } - - mInputDispatchFrozen = false; - updateInputDispatchModeLw(); - } + } + + // ------------------------------------------------------------- + // 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()); } - - public void setEventDispatchingLw(boolean enabled) { - if (mInputDispatchEnabled != enabled) { - if (DEBUG_INPUT) { - Slog.v(TAG, "Setting event dispatching to " + enabled); + + 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(); + mDragState = new DragState(this, token, surface, /*flags*/ 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; + } } - - mInputDispatchEnabled = enabled; - updateInputDispatchModeLw(); } + } finally { + Binder.restoreCallingIdentity(origId); } - - private void updateInputDispatchModeLw() { - mInputManager.setInputDispatchMode(mInputDispatchEnabled, mInputDispatchFrozen); - } + + return token; } + // ------------------------------------------------------------- + // Input Events and Focus Management + // ------------------------------------------------------------- + + final InputMonitor mInputMonitor = new InputMonitor(this); + public void pauseKeyDispatching(IBinder _token) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "pauseKeyDispatching()")) { @@ -5557,2182 +5823,37 @@ 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() { - mPolicy.systemReady(); - } - - // ------------------------------------------------------------- - // Client Session State - // ------------------------------------------------------------- - - private final class Session extends IWindowSession.Stub - implements IBinder.DeathRecipient { - final IInputMethodClient mClient; - final IInputContext mInputContext; - final int mUid; - final int mPid; - final String mStringName; - SurfaceSession mSurfaceSession; - int mNumWindow = 0; - boolean mClientDead = false; - - public Session(IInputMethodClient client, IInputContext inputContext) { - mClient = client; - mInputContext = inputContext; - mUid = Binder.getCallingUid(); - mPid = Binder.getCallingPid(); - StringBuilder sb = new StringBuilder(); - sb.append("Session{"); - sb.append(Integer.toHexString(System.identityHashCode(this))); - sb.append(" uid "); - sb.append(mUid); - sb.append("}"); - mStringName = sb.toString(); - - synchronized (mWindowMap) { - if (mInputMethodManager == null && mHaveInputMethods) { - IBinder b = ServiceManager.getService( - Context.INPUT_METHOD_SERVICE); - mInputMethodManager = IInputMethodManager.Stub.asInterface(b); - } - } - long ident = Binder.clearCallingIdentity(); - try { - // Note: it is safe to call in to the input method manager - // here because we are not holding our lock. - if (mInputMethodManager != null) { - mInputMethodManager.addClient(client, inputContext, - mUid, mPid); - } else { - client.setUsingInputMethod(false); - } - client.asBinder().linkToDeath(this, 0); - } catch (RemoteException e) { - // The caller has died, so we can just forget about this. - try { - if (mInputMethodManager != null) { - mInputMethodManager.removeClient(client); - } - } catch (RemoteException ee) { - } - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - @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) { - // Log all 'real' exceptions thrown to the caller - if (!(e instanceof SecurityException)) { - Slog.e(TAG, "Window Session Crash", e); - } - throw e; - } - } - - public void binderDied() { - // Note: it is safe to call in to the input method manager - // here because we are not holding our lock. - try { - if (mInputMethodManager != null) { - mInputMethodManager.removeClient(mClient); - } - } catch (RemoteException e) { - } - synchronized(mWindowMap) { - mClient.asBinder().unlinkToDeath(this, 0); - mClientDead = true; - killSessionLocked(); - } - } - - public int add(IWindow window, WindowManager.LayoutParams attrs, - int viewVisibility, Rect outContentInsets, InputChannel outInputChannel) { - return addWindow(this, window, attrs, viewVisibility, outContentInsets, - outInputChannel); - } - - public int addWithoutInputChannel(IWindow window, WindowManager.LayoutParams attrs, - int viewVisibility, Rect outContentInsets) { - return addWindow(this, window, attrs, viewVisibility, outContentInsets, null); - } - - public void remove(IWindow window) { - removeWindow(this, window); - } - - public int relayout(IWindow window, WindowManager.LayoutParams attrs, - int requestedWidth, int requestedHeight, int viewFlags, - boolean insetsPending, Rect outFrame, Rect outContentInsets, - Rect outVisibleInsets, Configuration outConfig, Surface outSurface) { - //Log.d(TAG, ">>>>>> ENTERED relayout from " + Binder.getCallingPid()); - int res = relayoutWindow(this, window, attrs, - requestedWidth, requestedHeight, viewFlags, insetsPending, - outFrame, outContentInsets, outVisibleInsets, outConfig, outSurface); - //Log.d(TAG, "<<<<<< EXITING relayout to " + Binder.getCallingPid()); - return res; - } - - public void setTransparentRegion(IWindow window, Region region) { - setTransparentRegionWindow(this, window, region); - } - - public void setInsets(IWindow window, int touchableInsets, - Rect contentInsets, Rect visibleInsets) { - setInsetsWindow(this, window, touchableInsets, contentInsets, - visibleInsets); - } - - public void getDisplayFrame(IWindow window, Rect outDisplayFrame) { - getWindowDisplayFrame(this, window, outDisplayFrame); - } - - public void finishDrawing(IWindow window) { - if (localLOGV) Slog.v( - TAG, "IWindow finishDrawing called for " + window); - finishDrawingWindow(this, window); - } - - public void setInTouchMode(boolean mode) { - synchronized(mWindowMap) { - mInTouchMode = mode; - } - } - - public boolean getInTouchMode() { - synchronized(mWindowMap) { - return mInTouchMode; - } - } - - public boolean performHapticFeedback(IWindow window, int effectId, - boolean always) { - synchronized(mWindowMap) { - long ident = Binder.clearCallingIdentity(); - try { - return mPolicy.performHapticFeedbackLw( - windowForClientLocked(this, window, true), - effectId, always); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - } - - public void setWallpaperPosition(IBinder window, float x, float y, float xStep, float yStep) { - synchronized(mWindowMap) { - long ident = Binder.clearCallingIdentity(); - try { - setWindowWallpaperPositionLocked( - windowForClientLocked(this, window, true), - x, y, xStep, yStep); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - } - - public void wallpaperOffsetsComplete(IBinder window) { - WindowManagerService.this.wallpaperOffsetsComplete(window); - } - - public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y, - int z, Bundle extras, boolean sync) { - synchronized(mWindowMap) { - long ident = Binder.clearCallingIdentity(); - try { - return sendWindowWallpaperCommandLocked( - windowForClientLocked(this, window, true), - action, x, y, z, extras, sync); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - } - - public void wallpaperCommandComplete(IBinder window, Bundle result) { - WindowManagerService.this.wallpaperCommandComplete(window, result); - } - - void windowAddedLocked() { - if (mSurfaceSession == null) { - if (localLOGV) Slog.v( - TAG, "First window added to " + this + ", creating SurfaceSession"); - mSurfaceSession = new SurfaceSession(); - if (SHOW_TRANSACTIONS) Slog.i( - TAG, " NEW SURFACE SESSION " + mSurfaceSession); - mSessions.add(this); - } - mNumWindow++; - } - - void windowRemovedLocked() { - mNumWindow--; - killSessionLocked(); - } - - void killSessionLocked() { - if (mNumWindow <= 0 && mClientDead) { - mSessions.remove(this); - if (mSurfaceSession != null) { - if (localLOGV) Slog.v( - TAG, "Last window removed from " + this - + ", destroying " + mSurfaceSession); - if (SHOW_TRANSACTIONS) Slog.i( - TAG, " KILL SURFACE SESSION " + mSurfaceSession); - try { - mSurfaceSession.kill(); - } catch (Exception e) { - Slog.w(TAG, "Exception thrown when killing surface session " - + mSurfaceSession + " in session " + this - + ": " + e.toString()); - } - mSurfaceSession = null; - } - } - } - - void dump(PrintWriter pw, String prefix) { - pw.print(prefix); pw.print("mNumWindow="); pw.print(mNumWindow); - pw.print(" mClientDead="); pw.print(mClientDead); - pw.print(" mSurfaceSession="); pw.println(mSurfaceSession); - } - - @Override - public String toString() { - return mStringName; - } - } - - // ------------------------------------------------------------- - // Client Window State - // ------------------------------------------------------------- - - private final class WindowState implements WindowManagerPolicy.WindowState { - final Session mSession; - final IWindow mClient; - WindowToken mToken; - WindowToken mRootToken; - AppWindowToken mAppToken; - AppWindowToken mTargetAppToken; - final WindowManager.LayoutParams mAttrs = new WindowManager.LayoutParams(); - final DeathRecipient mDeathRecipient; - final WindowState mAttachedWindow; - final ArrayList<WindowState> mChildWindows = new ArrayList<WindowState>(); - final int mBaseLayer; - final int mSubLayer; - final boolean mLayoutAttached; - final boolean mIsImWindow; - final boolean mIsWallpaper; - final boolean mIsFloatingLayer; - int mViewVisibility; - boolean mPolicyVisibility = true; - boolean mPolicyVisibilityAfterAnim = true; - boolean mAppFreezing; - Surface mSurface; - boolean mReportDestroySurface; - boolean mSurfacePendingDestroy; - boolean mAttachedHidden; // is our parent window hidden? - boolean mLastHidden; // was this window last hidden? - boolean mWallpaperVisible; // for wallpaper, what was last vis report? - int mRequestedWidth; - int mRequestedHeight; - int mLastRequestedWidth; - int mLastRequestedHeight; - int mLayer; - int mAnimLayer; - int mLastLayer; - boolean mHaveFrame; - boolean mObscured; - boolean mTurnOnScreen; - - int mLayoutSeq = -1; - - Configuration mConfiguration = null; - - // Actual frame shown on-screen (may be modified by animation) - final Rect mShownFrame = new Rect(); - final Rect mLastShownFrame = new Rect(); - - /** - * Set when we have changed the size of the surface, to know that - * we must tell them application to resize (and thus redraw itself). - */ - boolean mSurfaceResized; - - /** - * Insets that determine the actually visible area - */ - final Rect mVisibleInsets = new Rect(); - final Rect mLastVisibleInsets = new Rect(); - boolean mVisibleInsetsChanged; - - /** - * Insets that are covered by system windows - */ - final Rect mContentInsets = new Rect(); - final Rect mLastContentInsets = new Rect(); - boolean mContentInsetsChanged; - - /** - * Set to true if we are waiting for this window to receive its - * given internal insets before laying out other windows based on it. - */ - boolean mGivenInsetsPending; - - /** - * These are the content insets that were given during layout for - * this window, to be applied to windows behind it. - */ - final Rect mGivenContentInsets = new Rect(); - - /** - * These are the visible insets that were given during layout for - * this window, to be applied to windows behind it. - */ - final Rect mGivenVisibleInsets = new Rect(); - - /** - * 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 - * tests. - */ - int mTouchableInsets = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME; - - // Current transformation being applied. - float mDsDx=1, mDtDx=0, mDsDy=0, mDtDy=1; - float mLastDsDx=1, mLastDtDx=0, mLastDsDy=0, mLastDtDy=1; - float mHScale=1, mVScale=1; - float mLastHScale=1, mLastVScale=1; - final Matrix mTmpMatrix = new Matrix(); - - // "Real" frame that the application sees. - final Rect mFrame = new Rect(); - final Rect mLastFrame = new Rect(); - - final Rect mContainingFrame = new Rect(); - final Rect mDisplayFrame = new Rect(); - final Rect mContentFrame = new Rect(); - final Rect mVisibleFrame = new Rect(); - - float mShownAlpha = 1; - float mAlpha = 1; - float mLastAlpha = 1; - - // Set to true if, when the window gets displayed, it should perform - // an enter animation. - boolean mEnterAnimationPending; - - // Currently running animation. - boolean mAnimating; - boolean mLocalAnimating; - Animation mAnimation; - boolean mAnimationIsEntrance; - boolean mHasTransformation; - boolean mHasLocalTransformation; - final Transformation mTransformation = new Transformation(); - - // If a window showing a wallpaper: the requested offset for the - // wallpaper; if a wallpaper window: the currently applied offset. - float mWallpaperX = -1; - float mWallpaperY = -1; - - // If a window showing a wallpaper: what fraction of the offset - // range corresponds to a full virtual screen. - float mWallpaperXStep = -1; - float mWallpaperYStep = -1; - - // Wallpaper windows: pixels offset based on above variables. - int mXOffset; - int mYOffset; - - // This is set after IWindowSession.relayout() has been called at - // least once for the window. It allows us to detect the situation - // where we don't yet have a surface, but should have one soon, so - // we can give the window focus before waiting for the relayout. - boolean mRelayoutCalled; - - // This is set after the Surface has been created but before the - // window has been drawn. During this time the surface is hidden. - boolean mDrawPending; - - // This is set after the window has finished drawing for the first - // time but before its surface is shown. The surface will be - // displayed when the next layout is run. - boolean mCommitDrawPending; - - // This is set during the time after the window's drawing has been - // committed, and before its surface is actually shown. It is used - // to delay showing the surface until all windows in a token are ready - // to be shown. - boolean mReadyToShow; - - // Set when the window has been shown in the screen the first time. - boolean mHasDrawn; - - // Currently running an exit animation? - boolean mExiting; - - // Currently on the mDestroySurface list? - boolean mDestroying; - - // Completely remove from window manager after exit animation? - boolean mRemoveOnExit; - - // Set when the orientation is changing and this window has not yet - // been updated for the new orientation. - boolean mOrientationChanging; - - // Is this window now (or just being) removed? - boolean mRemoved; - - // 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 - InputChannel mInputChannel; - - // Used to improve performance of toString() - String mStringNameCache; - CharSequence mLastTitle; - boolean mWasPaused; - - WindowState(Session s, IWindow c, WindowToken token, - WindowState attachedWindow, WindowManager.LayoutParams a, - int viewVisibility) { - mSession = s; - mClient = c; - mToken = token; - mAttrs.copyFrom(a); - mViewVisibility = viewVisibility; - DeathRecipient deathRecipient = new DeathRecipient(); - mAlpha = a.alpha; - if (localLOGV) Slog.v( - TAG, "Window " + this + " client=" + c.asBinder() - + " token=" + token + " (" + mAttrs.token + ")"); - try { - c.asBinder().linkToDeath(deathRecipient, 0); - } catch (RemoteException e) { - mDeathRecipient = null; - mAttachedWindow = null; - mLayoutAttached = false; - mIsImWindow = false; - mIsWallpaper = false; - mIsFloatingLayer = false; - mBaseLayer = 0; - mSubLayer = 0; - return; - } - mDeathRecipient = deathRecipient; - - if ((mAttrs.type >= FIRST_SUB_WINDOW && - mAttrs.type <= LAST_SUB_WINDOW)) { - // The multiplier here is to reserve space for multiple - // windows in the same type layer. - mBaseLayer = mPolicy.windowTypeToLayerLw( - attachedWindow.mAttrs.type) * TYPE_LAYER_MULTIPLIER - + TYPE_LAYER_OFFSET; - mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type); - mAttachedWindow = attachedWindow; - mAttachedWindow.mChildWindows.add(this); - mLayoutAttached = mAttrs.type != - WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; - mIsImWindow = attachedWindow.mAttrs.type == TYPE_INPUT_METHOD - || attachedWindow.mAttrs.type == TYPE_INPUT_METHOD_DIALOG; - mIsWallpaper = attachedWindow.mAttrs.type == TYPE_WALLPAPER; - mIsFloatingLayer = mIsImWindow || mIsWallpaper; - } else { - // The multiplier here is to reserve space for multiple - // windows in the same type layer. - mBaseLayer = mPolicy.windowTypeToLayerLw(a.type) - * TYPE_LAYER_MULTIPLIER - + TYPE_LAYER_OFFSET; - mSubLayer = 0; - mAttachedWindow = null; - mLayoutAttached = false; - mIsImWindow = mAttrs.type == TYPE_INPUT_METHOD - || mAttrs.type == TYPE_INPUT_METHOD_DIALOG; - mIsWallpaper = mAttrs.type == TYPE_WALLPAPER; - mIsFloatingLayer = mIsImWindow || mIsWallpaper; - } - - WindowState appWin = this; - while (appWin.mAttachedWindow != null) { - appWin = mAttachedWindow; - } - WindowToken appToken = appWin.mToken; - while (appToken.appWindowToken == null) { - WindowToken parent = mTokenMap.get(appToken.token); - if (parent == null || appToken == parent) { - break; - } - appToken = parent; - } - mRootToken = appToken; - mAppToken = appToken.appWindowToken; - - mSurface = null; - mRequestedWidth = 0; - mRequestedHeight = 0; - mLastRequestedWidth = 0; - mLastRequestedHeight = 0; - mXOffset = 0; - mYOffset = 0; - mLayer = 0; - mAnimLayer = 0; - mLastLayer = 0; - } - - void attach() { - if (localLOGV) Slog.v( - TAG, "Attaching " + this + " token=" + mToken - + ", list=" + mToken.windows); - mSession.windowAddedLocked(); - } - - public void computeFrameLw(Rect pf, Rect df, Rect cf, Rect vf) { - mHaveFrame = true; - - final Rect container = mContainingFrame; - container.set(pf); - - final Rect display = mDisplayFrame; - display.set(df); - - if ((mAttrs.flags & FLAG_COMPATIBLE_WINDOW) != 0) { - container.intersect(mCompatibleScreenFrame); - if ((mAttrs.flags & FLAG_LAYOUT_NO_LIMITS) == 0) { - display.intersect(mCompatibleScreenFrame); - } - } - - final int pw = container.right - container.left; - final int ph = container.bottom - container.top; - - int w,h; - if ((mAttrs.flags & mAttrs.FLAG_SCALED) != 0) { - w = mAttrs.width < 0 ? pw : mAttrs.width; - h = mAttrs.height< 0 ? ph : mAttrs.height; - } else { - w = mAttrs.width == mAttrs.MATCH_PARENT ? pw : mRequestedWidth; - h = mAttrs.height== mAttrs.MATCH_PARENT ? ph : mRequestedHeight; - } - - final Rect content = mContentFrame; - content.set(cf); - - final Rect visible = mVisibleFrame; - visible.set(vf); - - final Rect frame = mFrame; - final int fw = frame.width(); - final int fh = frame.height(); - - //System.out.println("In: w=" + w + " h=" + h + " container=" + - // container + " x=" + mAttrs.x + " y=" + mAttrs.y); - - Gravity.apply(mAttrs.gravity, w, h, container, - (int) (mAttrs.x + mAttrs.horizontalMargin * pw), - (int) (mAttrs.y + mAttrs.verticalMargin * ph), frame); - - //System.out.println("Out: " + mFrame); - - // Now make sure the window fits in the overall display. - Gravity.applyDisplay(mAttrs.gravity, df, frame); - - // Make sure the content and visible frames are inside of the - // final window frame. - if (content.left < frame.left) content.left = frame.left; - if (content.top < frame.top) content.top = frame.top; - if (content.right > frame.right) content.right = frame.right; - if (content.bottom > frame.bottom) content.bottom = frame.bottom; - if (visible.left < frame.left) visible.left = frame.left; - if (visible.top < frame.top) visible.top = frame.top; - if (visible.right > frame.right) visible.right = frame.right; - if (visible.bottom > frame.bottom) visible.bottom = frame.bottom; - - final Rect contentInsets = mContentInsets; - contentInsets.left = content.left-frame.left; - contentInsets.top = content.top-frame.top; - contentInsets.right = frame.right-content.right; - contentInsets.bottom = frame.bottom-content.bottom; - - final Rect visibleInsets = mVisibleInsets; - visibleInsets.left = visible.left-frame.left; - visibleInsets.top = visible.top-frame.top; - visibleInsets.right = frame.right-visible.right; - visibleInsets.bottom = frame.bottom-visible.bottom; - - if (mIsWallpaper && (fw != frame.width() || fh != frame.height())) { - updateWallpaperOffsetLocked(this, mDisplay.getWidth(), - mDisplay.getHeight(), false); - } - - if (localLOGV) { - //if ("com.google.android.youtube".equals(mAttrs.packageName) - // && mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) { - Slog.v(TAG, "Resolving (mRequestedWidth=" - + mRequestedWidth + ", mRequestedheight=" - + mRequestedHeight + ") to" + " (pw=" + pw + ", ph=" + ph - + "): frame=" + mFrame.toShortString() - + " ci=" + contentInsets.toShortString() - + " vi=" + visibleInsets.toShortString()); - //} - } - } - - public Rect getFrameLw() { - return mFrame; - } - - public Rect getShownFrameLw() { - return mShownFrame; - } - - public Rect getDisplayFrameLw() { - return mDisplayFrame; - } - - public Rect getContentFrameLw() { - return mContentFrame; - } - - public Rect getVisibleFrameLw() { - return mVisibleFrame; - } - - public boolean getGivenInsetsPendingLw() { - return mGivenInsetsPending; - } - - public Rect getGivenContentInsetsLw() { - return mGivenContentInsets; - } - - public Rect getGivenVisibleInsetsLw() { - return mGivenVisibleInsets; - } - - public WindowManager.LayoutParams getAttrs() { - return mAttrs; - } - - public int getSurfaceLayer() { - return mLayer; - } - - public IApplicationToken getAppToken() { - return mAppToken != null ? mAppToken.appToken : null; - } - - public long getInputDispatchingTimeoutNanos() { - return mAppToken != null - ? mAppToken.inputDispatchingTimeoutNanos - : DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; - } - - public boolean hasAppShownWindows() { - return mAppToken != null ? mAppToken.firstWindowDrawn : false; - } - - public void setAnimation(Animation anim) { - if (localLOGV) Slog.v( - TAG, "Setting animation in " + this + ": " + anim); - mAnimating = false; - mLocalAnimating = false; - mAnimation = anim; - mAnimation.restrictDuration(MAX_ANIMATION_DURATION); - mAnimation.scaleCurrentDuration(mWindowAnimationScale); - } - - public void clearAnimation() { - if (mAnimation != null) { - mAnimating = true; - mLocalAnimating = false; - mAnimation = null; - } - } - - Surface createSurfaceLocked() { - if (mSurface == null) { - mReportDestroySurface = false; - mSurfacePendingDestroy = false; - mDrawPending = true; - mCommitDrawPending = false; - mReadyToShow = false; - if (mAppToken != null) { - mAppToken.allDrawn = false; - } - - 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; - } - if (DEBUG_VISIBILITY) Slog.v( - TAG, "Creating surface in session " - + mSession.mSurfaceSession + " window " + this - + " w=" + mFrame.width() - + " h=" + mFrame.height() + " format=" - + mAttrs.format + " flags=" + flags); - - int w = mFrame.width(); - int h = mFrame.height(); - if ((mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) { - // for a scaled surface, we always want the requested - // size. - w = mRequestedWidth; - h = mRequestedHeight; - } - - // Something is wrong and SurfaceFlinger will not like this, - // try to revert to sane values - if (w <= 0) w = 1; - if (h <= 0) h = 1; - - mSurfaceShown = false; - mSurfaceLayer = 0; - mSurfaceAlpha = 1; - mSurfaceX = 0; - mSurfaceY = 0; - mSurfaceW = w; - mSurfaceH = h; - try { - mSurface = new Surface( - mSession.mSurfaceSession, mSession.mPid, - mAttrs.getTitle().toString(), - 0, w, h, mAttrs.format, flags); - if (SHOW_TRANSACTIONS) Slog.i(TAG, " CREATE SURFACE " - + mSurface + " IN SESSION " - + mSession.mSurfaceSession - + ": pid=" + mSession.mPid + " format=" - + mAttrs.format + " flags=0x" - + Integer.toHexString(flags) - + " / " + this); - } catch (Surface.OutOfResourcesException e) { - Slog.w(TAG, "OutOfResourcesException creating surface"); - reclaimSomeSurfaceMemoryLocked(this, "create"); - return null; - } catch (Exception e) { - Slog.e(TAG, "Exception creating surface", e); - return null; - } - - if (localLOGV) Slog.v( - TAG, "Got surface: " + mSurface - + ", 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 + ") (" + - mFrame.width() + "x" + mFrame.height() + "), layer=" + - mAnimLayer + " HIDE", null); - } - Surface.openTransaction(); - try { - try { - mSurfaceX = mFrame.left + mXOffset; - mSurfaceY = mFrame.top + mYOffset; - mSurface.setPosition(mSurfaceX, mSurfaceY); - mSurfaceLayer = mAnimLayer; - mSurface.setLayer(mAnimLayer); - mSurfaceShown = false; - mSurface.hide(); - if ((mAttrs.flags&WindowManager.LayoutParams.FLAG_DITHER) != 0) { - if (SHOW_TRANSACTIONS) logSurface(this, "DITHER", null); - mSurface.setFlags(Surface.SURFACE_DITHER, - Surface.SURFACE_DITHER); - } - } catch (RuntimeException e) { - Slog.w(TAG, "Error creating surface in " + w, e); - reclaimSomeSurfaceMemoryLocked(this, "create-init"); - } - mLastHidden = true; - } finally { - if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION"); - Surface.closeTransaction(); - } - if (localLOGV) Slog.v( - TAG, "Created surface " + this); - } - return mSurface; - } - - void destroySurfaceLocked() { - if (mAppToken != null && this == mAppToken.startingWindow) { - mAppToken.startingDisplayed = false; - } - - if (mSurface != null) { - mDrawPending = false; - mCommitDrawPending = false; - mReadyToShow = false; - - int i = mChildWindows.size(); - while (i > 0) { - i--; - WindowState c = mChildWindows.get(i); - c.mAttachedHidden = true; - } - - if (mReportDestroySurface) { - mReportDestroySurface = false; - mSurfacePendingDestroy = true; - try { - mClient.dispatchGetNewSurface(); - // We'll really destroy on the next time around. - return; - } catch (RemoteException e) { - } - } - - try { - if (DEBUG_VISIBILITY) { - RuntimeException e = null; - if (!HIDE_STACK_CRAWLS) { - e = new RuntimeException(); - e.fillInStackTrace(); - } - Slog.w(TAG, "Window " + this + " destroying surface " - + mSurface + ", session " + mSession, e); - } - if (SHOW_TRANSACTIONS) { - RuntimeException e = null; - if (!HIDE_STACK_CRAWLS) { - e = new RuntimeException(); - e.fillInStackTrace(); - } - if (SHOW_TRANSACTIONS) logSurface(this, "DESTROY", e); - } - mSurface.destroy(); - } catch (RuntimeException e) { - Slog.w(TAG, "Exception thrown when destroying Window " + this - + " surface " + mSurface + " session " + mSession - + ": " + e.toString()); - } - - mSurfaceShown = false; - mSurface = null; - } - } - - boolean finishDrawingLocked() { - if (mDrawPending) { - if (SHOW_TRANSACTIONS || DEBUG_ORIENTATION) Slog.v( - TAG, "finishDrawingLocked: " + mSurface); - mCommitDrawPending = true; - mDrawPending = false; - return true; - } - return false; - } - - // This must be called while inside a transaction. - boolean commitFinishDrawingLocked(long currentTime) { - //Slog.i(TAG, "commitFinishDrawingLocked: " + mSurface); - if (!mCommitDrawPending) { - return false; - } - mCommitDrawPending = false; - mReadyToShow = true; - final boolean starting = mAttrs.type == TYPE_APPLICATION_STARTING; - final AppWindowToken atoken = mAppToken; - if (atoken == null || atoken.allDrawn || starting) { - performShowLocked(); - } - return true; - } - - // This must be called while inside a transaction. - boolean performShowLocked() { - if (DEBUG_VISIBILITY) { - RuntimeException e = null; - if (!HIDE_STACK_CRAWLS) { - e = new RuntimeException(); - e.fillInStackTrace(); - } - Slog.v(TAG, "performShow on " + this - + ": readyToShow=" + mReadyToShow + " readyForDisplay=" + isReadyForDisplay() - + " starting=" + (mAttrs.type == TYPE_APPLICATION_STARTING), e); - } - if (mReadyToShow && isReadyForDisplay()) { - if (SHOW_TRANSACTIONS || DEBUG_ORIENTATION) logSurface(this, - "SHOW (performShowLocked)", null); - if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this - + " during animation: policyVis=" + mPolicyVisibility - + " attHidden=" + mAttachedHidden - + " tok.hiddenRequested=" - + (mAppToken != null ? mAppToken.hiddenRequested : false) - + " tok.hidden=" - + (mAppToken != null ? mAppToken.hidden : false) - + " animating=" + mAnimating - + " tok animating=" - + (mAppToken != null ? mAppToken.animating : false)); - if (!showSurfaceRobustlyLocked(this)) { - return false; - } - mLastAlpha = -1; - mHasDrawn = true; - mLastHidden = false; - mReadyToShow = false; - enableScreenIfNeededLocked(); - - applyEnterAnimationLocked(this); - - int i = mChildWindows.size(); - while (i > 0) { - i--; - WindowState c = mChildWindows.get(i); - if (c.mAttachedHidden) { - c.mAttachedHidden = false; - if (c.mSurface != null) { - c.performShowLocked(); - // It hadn't been shown, which means layout not - // performed on it, so now we want to make sure to - // do a layout. If called from within the transaction - // loop, this will cause it to restart with a new - // layout. - mLayoutNeeded = true; - } - } - } - - if (mAttrs.type != TYPE_APPLICATION_STARTING - && mAppToken != null) { - mAppToken.firstWindowDrawn = true; - - if (mAppToken.startingData != null) { - if (DEBUG_STARTING_WINDOW || DEBUG_ANIM) Slog.v(TAG, - "Finish starting " + mToken - + ": first real window is shown, no animation"); - // If this initial window is animating, stop it -- we - // will do an animation to reveal it from behind the - // starting window, so there is no need for it to also - // be doing its own stuff. - if (mAnimation != null) { - mAnimation = null; - // Make sure we clean up the animation. - mAnimating = true; - } - mFinishedStarting.add(mAppToken); - mH.sendEmptyMessage(H.FINISHED_STARTING); - } - mAppToken.updateReportedVisibilityLocked(); - } - } - return true; - } - - // This must be called while inside a transaction. Returns true if - // there is more animation to run. - boolean stepAnimationLocked(long currentTime, int dw, int dh) { - if (!mDisplayFrozen && mPolicy.isScreenOn()) { - // We will run animations as long as the display isn't frozen. - - if (!mDrawPending && !mCommitDrawPending && mAnimation != null) { - mHasTransformation = true; - mHasLocalTransformation = true; - if (!mLocalAnimating) { - if (DEBUG_ANIM) Slog.v( - TAG, "Starting animation in " + this + - " @ " + currentTime + ": ww=" + mFrame.width() + " wh=" + mFrame.height() + - " dw=" + dw + " dh=" + dh + " scale=" + mWindowAnimationScale); - mAnimation.initialize(mFrame.width(), mFrame.height(), dw, dh); - mAnimation.setStartTime(currentTime); - mLocalAnimating = true; - mAnimating = true; - } - mTransformation.clear(); - final boolean more = mAnimation.getTransformation( - currentTime, mTransformation); - if (DEBUG_ANIM) Slog.v( - TAG, "Stepped animation in " + this + - ": more=" + more + ", xform=" + mTransformation); - if (more) { - // we're not done! - return true; - } - if (DEBUG_ANIM) Slog.v( - TAG, "Finished animation in " + this + - " @ " + currentTime); - mAnimation = null; - //WindowManagerService.this.dump(); - } - mHasLocalTransformation = false; - if ((!mLocalAnimating || mAnimationIsEntrance) && mAppToken != null - && mAppToken.animation != null) { - // When our app token is animating, we kind-of pretend like - // we are as well. Note the mLocalAnimating mAnimationIsEntrance - // part of this check means that we will only do this if - // our window is not currently exiting, or it is not - // locally animating itself. The idea being that one that - // is exiting and doing a local animation should be removed - // once that animation is done. - mAnimating = true; - mHasTransformation = true; - mTransformation.clear(); - return false; - } else if (mHasTransformation) { - // Little trick to get through the path below to act like - // we have finished an animation. - mAnimating = true; - } else if (isAnimating()) { - mAnimating = true; - } - } else if (mAnimation != null) { - // If the display is frozen, and there is a pending animation, - // clear it and make sure we run the cleanup code. - mAnimating = true; - mLocalAnimating = true; - mAnimation = null; - } - - if (!mAnimating && !mLocalAnimating) { - return false; - } - - if (DEBUG_ANIM) Slog.v( - TAG, "Animation done in " + this + ": exiting=" + mExiting - + ", reportedVisible=" - + (mAppToken != null ? mAppToken.reportedVisible : false)); - - mAnimating = false; - mLocalAnimating = false; - mAnimation = null; - mAnimLayer = mLayer; - if (mIsImWindow) { - mAnimLayer += mInputMethodAnimLayerAdjustment; - } else if (mIsWallpaper) { - mAnimLayer += mWallpaperAnimLayerAdjustment; - } - if (DEBUG_LAYERS) Slog.v(TAG, "Stepping win " + this - + " anim layer: " + mAnimLayer); - mHasTransformation = false; - mHasLocalTransformation = false; - if (mPolicyVisibility != mPolicyVisibilityAfterAnim) { - if (DEBUG_VISIBILITY) { - Slog.v(TAG, "Policy visibility changing after anim in " + this + ": " - + mPolicyVisibilityAfterAnim); - } - mPolicyVisibility = mPolicyVisibilityAfterAnim; - if (!mPolicyVisibility) { - if (mCurrentFocus == this) { - mFocusMayChange = true; - } - // Window is no longer visible -- make sure if we were waiting - // for it to be displayed before enabling the display, that - // we allow the display to be enabled now. - enableScreenIfNeededLocked(); - } - } - mTransformation.clear(); - if (mHasDrawn - && mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING - && mAppToken != null - && mAppToken.firstWindowDrawn - && mAppToken.startingData != null) { - if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Finish starting " - + mToken + ": first real window done animating"); - mFinishedStarting.add(mAppToken); - mH.sendEmptyMessage(H.FINISHED_STARTING); - } - - finishExit(); - - if (mAppToken != null) { - mAppToken.updateReportedVisibilityLocked(); - } - - return false; - } - - void finishExit() { - if (DEBUG_ANIM) Slog.v( - TAG, "finishExit in " + this - + ": exiting=" + mExiting - + " remove=" + mRemoveOnExit - + " windowAnimating=" + isWindowAnimating()); - - final int N = mChildWindows.size(); - for (int i=0; i<N; i++) { - mChildWindows.get(i).finishExit(); - } - - if (!mExiting) { - return; - } - - if (isWindowAnimating()) { - return; - } - - if (localLOGV) Slog.v( - TAG, "Exit animation finished in " + this - + ": remove=" + mRemoveOnExit); - if (mSurface != null) { - mDestroySurface.add(this); - mDestroying = true; - if (SHOW_TRANSACTIONS) logSurface(this, "HIDE (finishExit)", null); - mSurfaceShown = false; - try { - mSurface.hide(); - } catch (RuntimeException e) { - Slog.w(TAG, "Error hiding surface in " + this, e); - } - mLastHidden = true; - } - mExiting = false; - if (mRemoveOnExit) { - mPendingRemove.add(this); - mRemoveOnExit = false; - } - } - - boolean isIdentityMatrix(float dsdx, float dtdx, float dsdy, float dtdy) { - if (dsdx < .99999f || dsdx > 1.00001f) return false; - if (dtdy < .99999f || dtdy > 1.00001f) return false; - if (dtdx < -.000001f || dtdx > .000001f) return false; - if (dsdy < -.000001f || dsdy > .000001f) return false; - return true; - } - - void computeShownFrameLocked() { - final boolean selfTransformation = mHasLocalTransformation; - Transformation attachedTransformation = - (mAttachedWindow != null && mAttachedWindow.mHasLocalTransformation) - ? mAttachedWindow.mTransformation : null; - Transformation appTransformation = - (mAppToken != null && mAppToken.hasTransformation) - ? mAppToken.transformation : null; - - // Wallpapers are animated based on the "real" window they - // are currently targeting. - if (mAttrs.type == TYPE_WALLPAPER && mLowerWallpaperTarget == null - && mWallpaperTarget != null) { - if (mWallpaperTarget.mHasLocalTransformation && - mWallpaperTarget.mAnimation != null && - !mWallpaperTarget.mAnimation.getDetachWallpaper()) { - attachedTransformation = mWallpaperTarget.mTransformation; - if (DEBUG_WALLPAPER && attachedTransformation != null) { - Slog.v(TAG, "WP target attached xform: " + attachedTransformation); - } - } - if (mWallpaperTarget.mAppToken != null && - mWallpaperTarget.mAppToken.hasTransformation && - mWallpaperTarget.mAppToken.animation != null && - !mWallpaperTarget.mAppToken.animation.getDetachWallpaper()) { - appTransformation = mWallpaperTarget.mAppToken.transformation; - if (DEBUG_WALLPAPER && appTransformation != null) { - Slog.v(TAG, "WP target app xform: " + appTransformation); - } - } - } - - if (selfTransformation || attachedTransformation != null - || appTransformation != null) { - // cache often used attributes locally - final Rect frame = mFrame; - final float tmpFloats[] = mTmpFloats; - final Matrix tmpMatrix = mTmpMatrix; - - // Compute the desired transformation. - tmpMatrix.setTranslate(0, 0); - if (selfTransformation) { - tmpMatrix.postConcat(mTransformation.getMatrix()); - } - tmpMatrix.postTranslate(frame.left, frame.top); - if (attachedTransformation != null) { - tmpMatrix.postConcat(attachedTransformation.getMatrix()); - } - if (appTransformation != null) { - tmpMatrix.postConcat(appTransformation.getMatrix()); - } - - // "convert" it into SurfaceFlinger's format - // (a 2x2 matrix + an offset) - // Here we must not transform the position of the surface - // since it is already included in the transformation. - //Slog.i(TAG, "Transform: " + matrix); - - tmpMatrix.getValues(tmpFloats); - mDsDx = tmpFloats[Matrix.MSCALE_X]; - mDtDx = tmpFloats[Matrix.MSKEW_X]; - mDsDy = tmpFloats[Matrix.MSKEW_Y]; - mDtDy = tmpFloats[Matrix.MSCALE_Y]; - int x = (int)tmpFloats[Matrix.MTRANS_X] + mXOffset; - int y = (int)tmpFloats[Matrix.MTRANS_Y] + mYOffset; - int w = frame.width(); - int h = frame.height(); - mShownFrame.set(x, y, x+w, y+h); - - // Now set the alpha... but because our current hardware - // can't do alpha transformation on a non-opaque surface, - // turn it off if we are running an animation that is also - // transforming since it is more important to have that - // animation be smooth. - mShownAlpha = mAlpha; - if (!mLimitedAlphaCompositing - || (!PixelFormat.formatHasAlpha(mAttrs.format) - || (isIdentityMatrix(mDsDx, mDtDx, mDsDy, mDtDy) - && x == frame.left && y == frame.top))) { - //Slog.i(TAG, "Applying alpha transform"); - if (selfTransformation) { - mShownAlpha *= mTransformation.getAlpha(); - } - if (attachedTransformation != null) { - mShownAlpha *= attachedTransformation.getAlpha(); - } - if (appTransformation != null) { - mShownAlpha *= appTransformation.getAlpha(); - } - } else { - //Slog.i(TAG, "Not applying alpha transform"); - } - - if (localLOGV) Slog.v( - TAG, "Continuing animation in " + this + - ": " + mShownFrame + - ", alpha=" + mTransformation.getAlpha()); - return; - } - - mShownFrame.set(mFrame); - if (mXOffset != 0 || mYOffset != 0) { - mShownFrame.offset(mXOffset, mYOffset); - } - mShownAlpha = mAlpha; - mDsDx = 1; - mDtDx = 0; - mDsDy = 0; - mDtDy = 1; - } - - /** - * Is this window visible? It is not visible if there is no - * surface, or we are in the process of running an exit animation - * that will remove the surface, or its app token has been hidden. - */ - public boolean isVisibleLw() { - final AppWindowToken atoken = mAppToken; - return mSurface != null && mPolicyVisibility && !mAttachedHidden - && (atoken == null || !atoken.hiddenRequested) - && !mExiting && !mDestroying; - } - - /** - * Like {@link #isVisibleLw}, but also counts a window that is currently - * "hidden" behind the keyguard as visible. This allows us to apply - * things like window flags that impact the keyguard. - * XXX I am starting to think we need to have ANOTHER visibility flag - * for this "hidden behind keyguard" state rather than overloading - * mPolicyVisibility. Ungh. - */ - public boolean isVisibleOrBehindKeyguardLw() { - final AppWindowToken atoken = mAppToken; - return mSurface != null && !mAttachedHidden - && (atoken == null ? mPolicyVisibility : !atoken.hiddenRequested) - && (mOrientationChanging || (!mDrawPending && !mCommitDrawPending)) - && !mExiting && !mDestroying; - } - - /** - * Is this window visible, ignoring its app token? It is not visible - * if there is no surface, or we are in the process of running an exit animation - * that will remove the surface. - */ - public boolean isWinVisibleLw() { - final AppWindowToken atoken = mAppToken; - return mSurface != null && mPolicyVisibility && !mAttachedHidden - && (atoken == null || !atoken.hiddenRequested || atoken.animating) - && !mExiting && !mDestroying; - } - - /** - * The same as isVisible(), but follows the current hidden state of - * the associated app token, not the pending requested hidden state. - */ - boolean isVisibleNow() { - return mSurface != null && mPolicyVisibility && !mAttachedHidden - && !mRootToken.hidden && !mExiting && !mDestroying; - } - - /** - * Same as isVisible(), but we also count it as visible between the - * call to IWindowSession.add() and the first relayout(). - */ - boolean isVisibleOrAdding() { - final AppWindowToken atoken = mAppToken; - return ((mSurface != null && !mReportDestroySurface) - || (!mRelayoutCalled && mViewVisibility == View.VISIBLE)) - && mPolicyVisibility && !mAttachedHidden - && (atoken == null || !atoken.hiddenRequested) - && !mExiting && !mDestroying; - } - - /** - * Is this window currently on-screen? It is on-screen either if it - * is visible or it is currently running an animation before no longer - * being visible. - */ - boolean isOnScreen() { - final AppWindowToken atoken = mAppToken; - if (atoken != null) { - return mSurface != null && mPolicyVisibility && !mDestroying - && ((!mAttachedHidden && !atoken.hiddenRequested) - || mAnimation != null || atoken.animation != null); - } else { - return mSurface != null && mPolicyVisibility && !mDestroying - && (!mAttachedHidden || mAnimation != null); - } - } - - /** - * Like isOnScreen(), but we don't return true if the window is part - * of a transition that has not yet been started. - */ - boolean isReadyForDisplay() { - if (mRootToken.waitingToShow && - mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { - return false; - } - final AppWindowToken atoken = mAppToken; - final boolean animating = atoken != null - ? (atoken.animation != null) : false; - return mSurface != null && mPolicyVisibility && !mDestroying - && ((!mAttachedHidden && mViewVisibility == View.VISIBLE - && !mRootToken.hidden) - || mAnimation != null || animating); - } - - /** Is the window or its container currently animating? */ - boolean isAnimating() { - final WindowState attached = mAttachedWindow; - final AppWindowToken atoken = mAppToken; - return mAnimation != null - || (attached != null && attached.mAnimation != null) - || (atoken != null && - (atoken.animation != null - || atoken.inPendingTransaction)); - } - - /** Is this window currently animating? */ - boolean isWindowAnimating() { - return mAnimation != null; - } - - /** - * Like isOnScreen, but returns false if the surface hasn't yet - * been drawn. - */ - public boolean isDisplayedLw() { - final AppWindowToken atoken = mAppToken; - return mSurface != null && mPolicyVisibility && !mDestroying - && !mDrawPending && !mCommitDrawPending - && ((!mAttachedHidden && - (atoken == null || !atoken.hiddenRequested)) - || mAnimating); - } - - /** - * 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. - */ - public boolean isDrawnLw() { - final AppWindowToken atoken = mAppToken; - return mSurface != null && !mDestroying - && (mOrientationChanging || (!mDrawPending && !mCommitDrawPending)); - } - - /** - * Return true if the window is opaque and fully drawn. This indicates - * it may obscure windows behind it. - */ - boolean isOpaqueDrawn() { - return (mAttrs.format == PixelFormat.OPAQUE - || mAttrs.type == TYPE_WALLPAPER) - && mSurface != null && mAnimation == null - && (mAppToken == null || mAppToken.animation == null) - && !mDrawPending && !mCommitDrawPending; - } - - boolean needsBackgroundFiller(int screenWidth, int screenHeight) { - return - // only if the application is requesting compatible window - (mAttrs.flags & FLAG_COMPATIBLE_WINDOW) != 0 && - // only if it's visible - mHasDrawn && mViewVisibility == View.VISIBLE && - // and only if the application fills the compatible screen - 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; - } - - boolean isFullscreen(int screenWidth, int screenHeight) { - return mFrame.left <= 0 && mFrame.top <= 0 && - mFrame.right >= screenWidth && mFrame.bottom >= screenHeight; - } - - void removeLocked() { - disposeInputChannel(); - - if (mAttachedWindow != null) { - mAttachedWindow.mChildWindows.remove(this); - } - destroySurfaceLocked(); - mSession.windowRemovedLocked(); - try { - mClient.asBinder().unlinkToDeath(mDeathRecipient, 0); - } catch (RuntimeException e) { - // Ignore if it has already been removed (usually because - // we are doing this as part of processing a death note.) - } - } - - void disposeInputChannel() { - if (mInputChannel != null) { - mInputManager.unregisterInputChannel(mInputChannel); - - mInputChannel.dispose(); - mInputChannel = null; - } - } - - private class DeathRecipient implements IBinder.DeathRecipient { - public void binderDied() { - try { - synchronized(mWindowMap) { - WindowState win = windowForClientLocked(mSession, mClient, false); - Slog.i(TAG, "WIN DEATH: " + win); - if (win != null) { - removeWindowLocked(mSession, win); - } - } - } catch (IllegalArgumentException ex) { - // This will happen if the window has already been - // removed. - } - } - } - - /** Returns true if this window desires key events. */ - public final boolean canReceiveKeys() { - return isVisibleOrAdding() - && (mViewVisibility == View.VISIBLE) - && ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0); - } - - public boolean hasDrawnLw() { - return mHasDrawn; - } - - public boolean showLw(boolean doAnimation) { - return showLw(doAnimation, true); - } - - boolean showLw(boolean doAnimation, boolean requestAnim) { - if (mPolicyVisibility && mPolicyVisibilityAfterAnim) { - return false; - } - if (DEBUG_VISIBILITY) Slog.v(TAG, "Policy visibility true: " + this); - if (doAnimation) { - if (DEBUG_VISIBILITY) Slog.v(TAG, "doAnimation: mPolicyVisibility=" - + mPolicyVisibility + " mAnimation=" + mAnimation); - if (mDisplayFrozen || !mPolicy.isScreenOn()) { - doAnimation = false; - } else if (mPolicyVisibility && mAnimation == null) { - // Check for the case where we are currently visible and - // not animating; we do not want to do animation at such a - // point to become visible when we already are. - doAnimation = false; - } - } - mPolicyVisibility = true; - mPolicyVisibilityAfterAnim = true; - if (doAnimation) { - applyAnimationLocked(this, WindowManagerPolicy.TRANSIT_ENTER, true); - } - if (requestAnim) { - requestAnimationLocked(0); - } - return true; - } - - public boolean hideLw(boolean doAnimation) { - return hideLw(doAnimation, true); - } - - boolean hideLw(boolean doAnimation, boolean requestAnim) { - if (doAnimation) { - if (mDisplayFrozen || !mPolicy.isScreenOn()) { - doAnimation = false; - } - } - boolean current = doAnimation ? mPolicyVisibilityAfterAnim - : mPolicyVisibility; - if (!current) { - return false; - } - if (doAnimation) { - applyAnimationLocked(this, WindowManagerPolicy.TRANSIT_EXIT, false); - if (mAnimation == null) { - doAnimation = false; - } - } - if (doAnimation) { - mPolicyVisibilityAfterAnim = false; - } else { - if (DEBUG_VISIBILITY) Slog.v(TAG, "Policy visibility false: " + this); - mPolicyVisibilityAfterAnim = false; - mPolicyVisibility = false; - // Window is no longer visible -- make sure if we were waiting - // for it to be displayed before enabling the display, that - // we allow the display to be enabled now. - enableScreenIfNeededLocked(); - if (mCurrentFocus == this) { - mFocusMayChange = true; - } - } - if (requestAnim) { - requestAnimationLocked(0); - } - return true; - } - - void dump(PrintWriter pw, String prefix) { - pw.print(prefix); pw.print("mSession="); pw.print(mSession); - pw.print(" mClient="); pw.println(mClient.asBinder()); - pw.print(prefix); pw.print("mAttrs="); pw.println(mAttrs); - if (mAttachedWindow != null || mLayoutAttached) { - pw.print(prefix); pw.print("mAttachedWindow="); pw.print(mAttachedWindow); - pw.print(" mLayoutAttached="); pw.println(mLayoutAttached); - } - if (mIsImWindow || mIsWallpaper || mIsFloatingLayer) { - pw.print(prefix); pw.print("mIsImWindow="); pw.print(mIsImWindow); - pw.print(" mIsWallpaper="); pw.print(mIsWallpaper); - pw.print(" mIsFloatingLayer="); pw.print(mIsFloatingLayer); - pw.print(" mWallpaperVisible="); pw.println(mWallpaperVisible); - } - pw.print(prefix); pw.print("mBaseLayer="); pw.print(mBaseLayer); - pw.print(" mSubLayer="); pw.print(mSubLayer); - pw.print(" mAnimLayer="); pw.print(mLayer); pw.print("+"); - pw.print((mTargetAppToken != null ? mTargetAppToken.animLayerAdjustment - : (mAppToken != null ? mAppToken.animLayerAdjustment : 0))); - pw.print("="); pw.print(mAnimLayer); - pw.print(" mLastLayer="); pw.println(mLastLayer); - if (mSurface != null) { - pw.print(prefix); pw.print("mSurface="); pw.println(mSurface); - pw.print(prefix); pw.print("Surface: shown="); pw.print(mSurfaceShown); - pw.print(" layer="); pw.print(mSurfaceLayer); - pw.print(" alpha="); pw.print(mSurfaceAlpha); - pw.print(" rect=("); pw.print(mSurfaceX); - pw.print(","); pw.print(mSurfaceY); - pw.print(") "); pw.print(mSurfaceW); - pw.print(" x "); pw.println(mSurfaceH); - } - pw.print(prefix); pw.print("mToken="); pw.println(mToken); - pw.print(prefix); pw.print("mRootToken="); pw.println(mRootToken); - if (mAppToken != null) { - pw.print(prefix); pw.print("mAppToken="); pw.println(mAppToken); - } - if (mTargetAppToken != null) { - pw.print(prefix); pw.print("mTargetAppToken="); pw.println(mTargetAppToken); - } - pw.print(prefix); pw.print("mViewVisibility=0x"); - pw.print(Integer.toHexString(mViewVisibility)); - pw.print(" mLastHidden="); pw.print(mLastHidden); - pw.print(" mHaveFrame="); pw.print(mHaveFrame); - pw.print(" mObscured="); pw.println(mObscured); - if (!mPolicyVisibility || !mPolicyVisibilityAfterAnim || mAttachedHidden) { - pw.print(prefix); pw.print("mPolicyVisibility="); - pw.print(mPolicyVisibility); - pw.print(" mPolicyVisibilityAfterAnim="); - pw.print(mPolicyVisibilityAfterAnim); - pw.print(" mAttachedHidden="); pw.println(mAttachedHidden); - } - if (!mRelayoutCalled) { - pw.print(prefix); pw.print("mRelayoutCalled="); pw.println(mRelayoutCalled); - } - pw.print(prefix); pw.print("Requested w="); pw.print(mRequestedWidth); - pw.print(" h="); pw.print(mRequestedHeight); - pw.print(" mLayoutSeq="); pw.println(mLayoutSeq); - if (mXOffset != 0 || mYOffset != 0) { - pw.print(prefix); pw.print("Offsets x="); pw.print(mXOffset); - pw.print(" y="); pw.println(mYOffset); - } - pw.print(prefix); pw.print("mGivenContentInsets="); - mGivenContentInsets.printShortString(pw); - pw.print(" mGivenVisibleInsets="); - mGivenVisibleInsets.printShortString(pw); - pw.println(); - if (mTouchableInsets != 0 || mGivenInsetsPending) { - pw.print(prefix); pw.print("mTouchableInsets="); pw.print(mTouchableInsets); - pw.print(" mGivenInsetsPending="); pw.println(mGivenInsetsPending); - } - pw.print(prefix); pw.print("mConfiguration="); pw.println(mConfiguration); - pw.print(prefix); pw.print("mShownFrame="); - mShownFrame.printShortString(pw); - pw.print(" last="); mLastShownFrame.printShortString(pw); - pw.println(); - pw.print(prefix); pw.print("mFrame="); mFrame.printShortString(pw); - pw.print(" last="); mLastFrame.printShortString(pw); - pw.println(); - pw.print(prefix); pw.print("mContainingFrame="); - mContainingFrame.printShortString(pw); - pw.print(" mDisplayFrame="); - mDisplayFrame.printShortString(pw); - pw.println(); - pw.print(prefix); pw.print("mContentFrame="); mContentFrame.printShortString(pw); - pw.print(" mVisibleFrame="); mVisibleFrame.printShortString(pw); - pw.println(); - pw.print(prefix); pw.print("mContentInsets="); mContentInsets.printShortString(pw); - pw.print(" last="); mLastContentInsets.printShortString(pw); - 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); - pw.print(" mLocalAnimating="); pw.print(mLocalAnimating); - pw.print(" mAnimationIsEntrance="); pw.print(mAnimationIsEntrance); - pw.print(" mAnimation="); pw.println(mAnimation); - } - if (mHasTransformation || mHasLocalTransformation) { - pw.print(prefix); pw.print("XForm: has="); - pw.print(mHasTransformation); - pw.print(" hasLocal="); pw.print(mHasLocalTransformation); - pw.print(" "); mTransformation.printShortString(pw); - pw.println(); - } - pw.print(prefix); pw.print("mDrawPending="); pw.print(mDrawPending); - pw.print(" mCommitDrawPending="); pw.print(mCommitDrawPending); - pw.print(" mReadyToShow="); pw.print(mReadyToShow); - pw.print(" mHasDrawn="); pw.println(mHasDrawn); - if (mExiting || mRemoveOnExit || mDestroying || mRemoved) { - pw.print(prefix); pw.print("mExiting="); pw.print(mExiting); - pw.print(" mRemoveOnExit="); pw.print(mRemoveOnExit); - pw.print(" mDestroying="); pw.print(mDestroying); - pw.print(" mRemoved="); pw.println(mRemoved); - } - if (mOrientationChanging || mAppFreezing || mTurnOnScreen) { - pw.print(prefix); pw.print("mOrientationChanging="); - pw.print(mOrientationChanging); - pw.print(" mAppFreezing="); pw.print(mAppFreezing); - pw.print(" mTurnOnScreen="); pw.println(mTurnOnScreen); - } - if (mHScale != 1 || mVScale != 1) { - pw.print(prefix); pw.print("mHScale="); pw.print(mHScale); - pw.print(" mVScale="); pw.println(mVScale); - } - if (mWallpaperX != -1 || mWallpaperY != -1) { - pw.print(prefix); pw.print("mWallpaperX="); pw.print(mWallpaperX); - pw.print(" mWallpaperY="); pw.println(mWallpaperY); - } - if (mWallpaperXStep != -1 || mWallpaperYStep != -1) { - pw.print(prefix); pw.print("mWallpaperXStep="); pw.print(mWallpaperXStep); - pw.print(" mWallpaperYStep="); pw.println(mWallpaperYStep); - } - } - - String makeInputChannelName() { - return Integer.toHexString(System.identityHashCode(this)) - + " " + mAttrs.getTitle(); - } - - @Override - public String toString() { - if (mStringNameCache == null || mLastTitle != mAttrs.getTitle() - || mWasPaused != mToken.paused) { - mLastTitle = mAttrs.getTitle(); - mWasPaused = mToken.paused; - mStringNameCache = "Window{" + Integer.toHexString(System.identityHashCode(this)) - + " " + mLastTitle + " paused=" + mWasPaused + "}"; - } - return mStringNameCache; - } - } - - // ------------------------------------------------------------- - // Window Token State - // ------------------------------------------------------------- - - class WindowToken { - // The actual token. - final IBinder token; - - // The type of window this token is for, as per WindowManager.LayoutParams. - final int windowType; - - // Set if this token was explicitly added by a client, so should - // not be removed when all windows are removed. - final boolean explicit; - - // For printing. - String stringName; - - // If this is an AppWindowToken, this is non-null. - AppWindowToken appWindowToken; - - // All of the windows associated with this token. - final ArrayList<WindowState> windows = new ArrayList<WindowState>(); - - // Is key dispatching paused for this token? - boolean paused = false; - - // Should this token's windows be hidden? - boolean hidden; - - // Temporary for finding which tokens no longer have visible windows. - boolean hasVisible; - - // Set to true when this token is in a pending transaction where it - // will be shown. - boolean waitingToShow; - - // Set to true when this token is in a pending transaction where it - // will be hidden. - boolean waitingToHide; - - // Set to true when this token is in a pending transaction where its - // windows will be put to the bottom of the list. - boolean sendingToBottom; - - // Set to true when this token is in a pending transaction where its - // windows will be put to the top of the list. - boolean sendingToTop; - - WindowToken(IBinder _token, int type, boolean _explicit) { - token = _token; - windowType = type; - explicit = _explicit; - } - - void dump(PrintWriter pw, String prefix) { - pw.print(prefix); pw.print("token="); pw.println(token); - pw.print(prefix); pw.print("windows="); pw.println(windows); - pw.print(prefix); pw.print("windowType="); pw.print(windowType); - pw.print(" hidden="); pw.print(hidden); - pw.print(" hasVisible="); pw.println(hasVisible); - if (waitingToShow || waitingToHide || sendingToBottom || sendingToTop) { - pw.print(prefix); pw.print("waitingToShow="); pw.print(waitingToShow); - pw.print(" waitingToHide="); pw.print(waitingToHide); - pw.print(" sendingToBottom="); pw.print(sendingToBottom); - pw.print(" sendingToTop="); pw.println(sendingToTop); + 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, mDisplay.getRealWidth(), mDisplay.getRealHeight()); } - @Override - public String toString() { - if (stringName == null) { - StringBuilder sb = new StringBuilder(); - sb.append("WindowToken{"); - sb.append(Integer.toHexString(System.identityHashCode(this))); - sb.append(" token="); sb.append(token); sb.append('}'); - stringName = sb.toString(); - } - return stringName; + try { + mActivityManager.updateConfiguration(null); + } catch (RemoteException e) { } - }; - - class AppWindowToken extends WindowToken { - // Non-null only for application tokens. - final IApplicationToken appToken; - - // All of the windows and child windows that are included in this - // application token. Note this list is NOT sorted! - final ArrayList<WindowState> allAppWindows = new ArrayList<WindowState>(); - - int groupId = -1; - boolean appFullscreen; - int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; - // The input dispatching timeout for this application token in nanoseconds. - long inputDispatchingTimeoutNanos; - - // These are used for determining when all windows associated with - // an activity have been drawn, so they can be made visible together - // at the same time. - int lastTransactionSequence = mTransactionSequence-1; - int numInterestingWindows; - int numDrawnWindows; - boolean inPendingTransaction; - boolean allDrawn; - - // Is this token going to be hidden in a little while? If so, it - // won't be taken into account for setting the screen orientation. - boolean willBeHidden; - - // Is this window's surface needed? This is almost like hidden, except - // it will sometimes be true a little earlier: when the token has - // been shown, but is still waiting for its app transition to execute - // before making its windows shown. - boolean hiddenRequested; - - // Have we told the window clients to hide themselves? - boolean clientHidden; - - // Last visibility state we reported to the app token. - boolean reportedVisible; - - // Set to true when the token has been removed from the window mgr. - boolean removed; - - // Have we been asked to have this token keep the screen frozen? - boolean freezingScreen; - - boolean animating; - Animation animation; - boolean hasTransformation; - final Transformation transformation = new Transformation(); - - // Offset to the window of all layers in the token, for use by - // AppWindowToken animations. - int animLayerAdjustment; - - // Information about an application starting window if displayed. - StartingData startingData; - WindowState startingWindow; - View startingView; - boolean startingDisplayed; - boolean startingMoved; - boolean firstWindowDrawn; - - AppWindowToken(IApplicationToken _token) { - super(_token.asBinder(), - WindowManager.LayoutParams.TYPE_APPLICATION, true); - appWindowToken = this; - appToken = _token; - } - - public void setAnimation(Animation anim) { - if (localLOGV) Slog.v( - TAG, "Setting animation in " + this + ": " + anim); - animation = anim; - animating = false; - anim.restrictDuration(MAX_ANIMATION_DURATION); - anim.scaleCurrentDuration(mTransitionAnimationScale); - int zorder = anim.getZAdjustment(); - int adj = 0; - if (zorder == Animation.ZORDER_TOP) { - adj = TYPE_LAYER_OFFSET; - } else if (zorder == Animation.ZORDER_BOTTOM) { - adj = -TYPE_LAYER_OFFSET; - } - - if (animLayerAdjustment != adj) { - animLayerAdjustment = adj; - updateLayers(); - } - } - - public void setDummyAnimation() { - if (animation == null) { - if (localLOGV) Slog.v( - TAG, "Setting dummy animation in " + this); - animation = sDummyAnimation; - } - } - - public void clearAnimation() { - if (animation != null) { - animation = null; - animating = true; - } - } - - void updateLayers() { - final int N = allAppWindows.size(); - final int adj = animLayerAdjustment; - for (int i=0; i<N; i++) { - WindowState w = allAppWindows.get(i); - w.mAnimLayer = w.mLayer + adj; - if (DEBUG_LAYERS) Slog.v(TAG, "Updating layer " + w + ": " - + w.mAnimLayer); - if (w == mInputMethodTarget) { - setInputMethodAnimLayerAdjustment(adj); - } - if (w == mWallpaperTarget && mLowerWallpaperTarget == null) { - setWallpaperAnimLayerAdjustmentLocked(adj); - } - } - } - - void sendAppVisibilityToClients() { - final int N = allAppWindows.size(); - for (int i=0; i<N; i++) { - WindowState win = allAppWindows.get(i); - if (win == startingWindow && clientHidden) { - // Don't hide the starting window. - continue; - } - try { - if (DEBUG_VISIBILITY) Slog.v(TAG, - "Setting visibility of " + win + ": " + (!clientHidden)); - win.mClient.dispatchAppVisibility(!clientHidden); - } catch (RemoteException e) { - } - } - } - - void showAllWindowsLocked() { - final int NW = allAppWindows.size(); - for (int i=0; i<NW; i++) { - WindowState w = allAppWindows.get(i); - if (DEBUG_VISIBILITY) Slog.v(TAG, - "performing show on: " + w); - w.performShowLocked(); - } - } - - // This must be called while inside a transaction. - boolean stepAnimationLocked(long currentTime, int dw, int dh) { - if (!mDisplayFrozen && mPolicy.isScreenOn()) { - // We will run animations as long as the display isn't frozen. - - if (animation == sDummyAnimation) { - // This guy is going to animate, but not yet. For now count - // it as not animating for purposes of scheduling transactions; - // when it is really time to animate, this will be set to - // a real animation and the next call will execute normally. - return false; - } - - if ((allDrawn || animating || startingDisplayed) && animation != null) { - if (!animating) { - if (DEBUG_ANIM) Slog.v( - TAG, "Starting animation in " + this + - " @ " + currentTime + ": dw=" + dw + " dh=" + dh - + " scale=" + mTransitionAnimationScale - + " allDrawn=" + allDrawn + " animating=" + animating); - animation.initialize(dw, dh, dw, dh); - animation.setStartTime(currentTime); - animating = true; - } - transformation.clear(); - final boolean more = animation.getTransformation( - currentTime, transformation); - if (DEBUG_ANIM) Slog.v( - TAG, "Stepped animation in " + this + - ": more=" + more + ", xform=" + transformation); - if (more) { - // we're done! - hasTransformation = true; - return true; - } - if (DEBUG_ANIM) Slog.v( - TAG, "Finished animation in " + this + - " @ " + currentTime); - animation = null; - } - } else if (animation != null) { - // If the display is frozen, and there is a pending animation, - // clear it and make sure we run the cleanup code. - animating = true; - animation = null; - } - - hasTransformation = false; - - if (!animating) { - return false; - } - - clearAnimation(); - animating = false; - if (mInputMethodTarget != null && mInputMethodTarget.mAppToken == this) { - moveInputMethodWindowsIfNeededLocked(true); - } - - if (DEBUG_ANIM) Slog.v( - TAG, "Animation done in " + this - + ": reportedVisible=" + reportedVisible); - - transformation.clear(); - if (animLayerAdjustment != 0) { - animLayerAdjustment = 0; - updateLayers(); - } - - final int N = windows.size(); - for (int i=0; i<N; i++) { - windows.get(i).finishExit(); - } - updateReportedVisibilityLocked(); - - return false; - } - - void updateReportedVisibilityLocked() { - if (appToken == null) { - return; - } - - int numInteresting = 0; - int numVisible = 0; - boolean nowGone = true; - - if (DEBUG_VISIBILITY) Slog.v(TAG, "Update reported visibility: " + this); - final int N = allAppWindows.size(); - for (int i=0; i<N; i++) { - WindowState win = allAppWindows.get(i); - if (win == startingWindow || win.mAppFreezing - || win.mViewVisibility != View.VISIBLE - || win.mAttrs.type == TYPE_APPLICATION_STARTING - || win.mDestroying) { - continue; - } - if (DEBUG_VISIBILITY) { - Slog.v(TAG, "Win " + win + ": isDrawn=" - + win.isDrawnLw() - + ", isAnimating=" + win.isAnimating()); - if (!win.isDrawnLw()) { - Slog.v(TAG, "Not displayed: s=" + win.mSurface - + " pv=" + win.mPolicyVisibility - + " dp=" + win.mDrawPending - + " cdp=" + win.mCommitDrawPending - + " ah=" + win.mAttachedHidden - + " th=" - + (win.mAppToken != null - ? win.mAppToken.hiddenRequested : false) - + " a=" + win.mAnimating); - } - } - numInteresting++; - if (win.isDrawnLw()) { - if (!win.isAnimating()) { - numVisible++; - } - nowGone = false; - } else if (win.isAnimating()) { - nowGone = false; - } - } - - boolean nowVisible = numInteresting > 0 && numVisible >= numInteresting; - if (DEBUG_VISIBILITY) Slog.v(TAG, "VIS " + this + ": interesting=" - + numInteresting + " visible=" + numVisible); - if (nowVisible != reportedVisible) { - if (DEBUG_VISIBILITY) Slog.v( - TAG, "Visibility changed in " + this - + ": vis=" + nowVisible); - reportedVisible = nowVisible; - Message m = mH.obtainMessage( - H.REPORT_APPLICATION_TOKEN_WINDOWS, - nowVisible ? 1 : 0, - nowGone ? 1 : 0, - this); - mH.sendMessage(m); - } - } - - WindowState findMainWindow() { - int j = windows.size(); - while (j > 0) { - j--; - WindowState win = windows.get(j); - if (win.mAttrs.type == WindowManager.LayoutParams.TYPE_BASE_APPLICATION - || win.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) { - return win; - } - } - return null; - } - - void dump(PrintWriter pw, String prefix) { - super.dump(pw, prefix); - if (appToken != null) { - pw.print(prefix); pw.println("app=true"); - } - if (allAppWindows.size() > 0) { - pw.print(prefix); pw.print("allAppWindows="); pw.println(allAppWindows); - } - pw.print(prefix); pw.print("groupId="); pw.print(groupId); - pw.print(" appFullscreen="); pw.print(appFullscreen); - pw.print(" requestedOrientation="); pw.println(requestedOrientation); - pw.print(prefix); pw.print("hiddenRequested="); pw.print(hiddenRequested); - pw.print(" clientHidden="); pw.print(clientHidden); - pw.print(" willBeHidden="); pw.print(willBeHidden); - pw.print(" reportedVisible="); pw.println(reportedVisible); - if (paused || freezingScreen) { - pw.print(prefix); pw.print("paused="); pw.print(paused); - pw.print(" freezingScreen="); pw.println(freezingScreen); - } - if (numInterestingWindows != 0 || numDrawnWindows != 0 - || inPendingTransaction || allDrawn) { - pw.print(prefix); pw.print("numInterestingWindows="); - pw.print(numInterestingWindows); - pw.print(" numDrawnWindows="); pw.print(numDrawnWindows); - pw.print(" inPendingTransaction="); pw.print(inPendingTransaction); - pw.print(" allDrawn="); pw.println(allDrawn); - } - if (animating || animation != null) { - 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.println(); - } - if (startingData != null || removed || firstWindowDrawn) { - pw.print(prefix); pw.print("startingData="); pw.print(startingData); - pw.print(" removed="); pw.print(removed); - pw.print(" firstWindowDrawn="); pw.println(firstWindowDrawn); - } - if (startingWindow != null || startingView != null - || startingDisplayed || startingMoved) { - pw.print(prefix); pw.print("startingWindow="); pw.print(startingWindow); - pw.print(" startingView="); pw.print(startingView); - pw.print(" startingDisplayed="); pw.print(startingDisplayed); - pw.print(" startingMoved"); pw.println(startingMoved); - } - } - - @Override - public String toString() { - if (stringName == null) { - StringBuilder sb = new StringBuilder(); - sb.append("AppWindowToken{"); - sb.append(Integer.toHexString(System.identityHashCode(this))); - sb.append(" token="); sb.append(token); sb.append('}'); - stringName = sb.toString(); - } - return stringName; - } + mPolicy.systemReady(); } - // ------------------------------------------------------------- - // DummyAnimation - // ------------------------------------------------------------- - // This is an animation that does nothing: it just immediately finishes // itself every time it is called. It is used as a stub animation in cases // where we want to synchronize multiple things that may be animating. @@ -7747,24 +5868,7 @@ public class WindowManagerService extends IWindowManager.Stub // Async Handler // ------------------------------------------------------------- - static final class StartingData { - final String pkg; - final int theme; - final CharSequence nonLocalizedLabel; - final int labelRes; - final int icon; - - StartingData(String _pkg, int _theme, CharSequence _nonLocalizedLabel, - int _labelRes, int _icon) { - pkg = _pkg; - theme = _theme; - nonLocalizedLabel = _nonLocalizedLabel; - labelRes = _labelRes; - icon = _icon; - } - } - - private final class H extends Handler { + final class H extends Handler { public static final int REPORT_FOCUS_CHANGE = 2; public static final int REPORT_LOSING_FOCUS = 3; public static final int ANIMATE = 4; @@ -7781,6 +5885,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 +5940,8 @@ public class WindowManagerService extends IWindowManager.Stub // Ignore if process has died. } } + + mPolicy.focusChanged(lastFocus, newFocus); } } break; @@ -7879,7 +5988,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 +6232,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; + } } } } @@ -8135,7 +6278,7 @@ public class WindowManagerService extends IWindowManager.Stub IInputContext inputContext) { if (client == null) throw new IllegalArgumentException("null client"); if (inputContext == null) throw new IllegalArgumentException("null inputContext"); - Session session = new Session(client, inputContext); + Session session = new Session(this, client, inputContext); return session; } @@ -8147,11 +6290,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 +6380,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 +6429,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 +6453,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 +6510,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 +6572,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 +6606,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 +6630,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 +6653,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 +6680,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 +6704,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 +6725,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 +6743,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 +6771,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 +6783,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 +6818,23 @@ 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; + WindowState windowAnimationBackground = null; + int windowAnimationBackgroundColor = 0; mPolicy.beginAnimationLw(dw, dh); @@ -8595,7 +6846,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 +6856,69 @@ 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) { + if (w.mAnimation.getDetachWallpaper()) { + windowDetachedWallpaper = w; + } + if (w.mAnimation.getBackgroundColor() != 0) { + windowAnimationBackground = w; + windowAnimationBackgroundColor = + w.mAnimation.getBackgroundColor(); + } + } 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) { + if (w.mAppToken.animation.getDetachWallpaper()) { + windowDetachedWallpaper = w; + } + if (w.mAppToken.animation.getBackgroundColor() != 0) { + windowAnimationBackground = w; + windowAnimationBackgroundColor = + w.mAppToken.animation.getBackgroundColor(); + } + } + 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 +7015,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 +7114,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 +7157,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 +7206,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 +7250,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 +7339,25 @@ 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 (windowAnimationBackgroundColor != 0) { + if (mWindowAnimationBackgroundSurface == null) { + mWindowAnimationBackgroundSurface = new DimSurface(mFxSession); + } + mWindowAnimationBackgroundSurface.show(dw, dh, + windowAnimationBackground.mAnimLayer - LAYER_OFFSET_DIM, + windowAnimationBackgroundColor); + } else if (mWindowAnimationBackgroundSurface != null) { + mWindowAnimationBackgroundSurface.hide(); + } + if (wallpaperMayChange) { if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper may change! Adjusting"); @@ -9060,7 +7377,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 +7390,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 +7401,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 +7434,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 +7443,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", true); + } + } + } + + 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"); + reclaimSomeSurfaceMemoryLocked(w, "size", true); } } } } + if (!w.mAppFreezing && w.mLayoutSeq == mLayoutSeq) { w.mContentInsetsChanged = !w.mLastContentInsets.equals(w.mContentInsets); @@ -9197,11 +7514,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 +7580,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); @@ -9318,7 +7639,7 @@ public class WindowManagerService extends IWindowManager.Stub } catch (RuntimeException e) { Slog.w(TAG, "Error updating surface in " + w, e); if (!recoveringMemory) { - reclaimSomeSurfaceMemoryLocked(w, "update"); + reclaimSomeSurfaceMemoryLocked(w, "update", true); } } } @@ -9370,6 +7691,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 +7704,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 +7742,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 +7760,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 +7769,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 +7778,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 " @@ -9473,7 +7787,7 @@ public class WindowManagerService extends IWindowManager.Stub dw + "x" + dh + "), layer=" + (w.mAnimLayer-1)); mBlurSurface.setPosition(0, 0); mBlurSurface.setSize(dw, dh); - mBlurSurface.setLayer(w.mAnimLayer-2); + mBlurSurface.setLayer(w.mAnimLayer-LAYER_OFFSET_BLUR); if (!mBlurShown) { try { if (SHOW_TRANSACTIONS) Slog.i(TAG, " BLUR " @@ -9498,9 +7812,40 @@ 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 + - LAYER_OFFSET_DIM); + 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 +7868,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 +7965,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 +8000,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 +8018,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 +8032,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,16 +8095,18 @@ 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"); + reclaimSomeSurfaceMemoryLocked(win, "show", true); return false; } - void reclaimSomeSurfaceMemoryLocked(WindowState win, String operation) { + boolean reclaimSomeSurfaceMemoryLocked(WindowState win, String operation, boolean secure) { final Surface surface = win.mSurface; + boolean leakedSurface = false; + boolean killedApps = false; EventLog.writeEvent(EventLogTags.WM_NO_SURFACE_MEMORY, win.toString(), win.mSession.mPid, operation); @@ -9765,7 +8121,6 @@ public class WindowManagerService extends IWindowManager.Stub // window list to make sure we haven't left any dangling surfaces // around. int N = mWindows.size(); - boolean leakedSurface = false; Slog.i(TAG, "Out of memory for surface! Looking for leaks..."); for (int i=0; i<N; i++) { WindowState ws = mWindows.get(i); @@ -9776,6 +8131,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 +8139,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; @@ -9795,7 +8152,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - boolean killedApps = false; if (!leakedSurface) { Slog.w(TAG, "No leaked surfaces; killing applicatons!"); SparseIntArray pidCandidates = new SparseIntArray(); @@ -9811,7 +8167,7 @@ public class WindowManagerService extends IWindowManager.Stub pids[i] = pidCandidates.keyAt(i); } try { - if (mActivityManager.killPids(pids, "Free memory")) { + if (mActivityManager.killPids(pids, "Free memory", secure)) { killedApps = true; } } catch (RemoteException e) { @@ -9824,6 +8180,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; @@ -9837,9 +8194,11 @@ public class WindowManagerService extends IWindowManager.Stub } finally { Binder.restoreCallingIdentity(callingIdentity); } + + return leakedSurface || killedApps; } - 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 +8219,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 +8230,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 +8310,7 @@ public class WindowManagerService extends IWindowManager.Stub return result; } - private void startFreezingDisplayLocked() { + private void startFreezingDisplayLocked(boolean inTransaction) { if (mDisplayFrozen) { return; } @@ -9971,8 +8330,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 +8344,22 @@ 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); + } + if (!mScreenRotationAnimation.hasScreenshot()) { + Surface.freezeDisplay(0); + } + } else { + Surface.freezeDisplay(0); + } } private void stopFreezingDisplayLocked() { @@ -9999,24 +8371,41 @@ 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 && mScreenRotationAnimation != null + && mScreenRotationAnimation.hasScreenshot()) { + if (mScreenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION, + mTransitionAnimationScale)) { + requestAnimationLocked(0); + } else { + mScreenRotationAnimation = null; + updateRotation = true; + } + } else { + if (mScreenRotationAnimation != null) { + mScreenRotationAnimation.kill(); + mScreenRotationAnimation = null; + } + updateRotation = true; + 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 +8417,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,146 +8448,6 @@ public class WindowManagerService extends IWindowManager.Stub return val; } - class Watermark { - final String[] mTokens; - final String mText; - final Paint mTextPaint; - final int mTextWidth; - final int mTextHeight; - final int mTextAscent; - final int mTextDescent; - final int mDeltaX; - final int mDeltaY; - - Surface mSurface; - int mLastDW; - int mLastDH; - boolean mDrawNeeded; - - Watermark(SurfaceSession session, String[] tokens) { - final DisplayMetrics dm = new DisplayMetrics(); - mDisplay.getMetrics(dm); - - if (false) { - Log.i(TAG, "*********************** WATERMARK"); - for (int i=0; i<tokens.length; i++) { - Log.i(TAG, " TOKEN #" + i + ": " + tokens[i]); - } - } - - mTokens = tokens; - - StringBuilder builder = new StringBuilder(32); - int len = mTokens[0].length(); - len = len & ~1; - for (int i=0; i<len; i+=2) { - int c1 = mTokens[0].charAt(i); - int c2 = mTokens[0].charAt(i+1); - if (c1 >= 'a' && c1 <= 'f') c1 = c1 - 'a' + 10; - else if (c1 >= 'A' && c1 <= 'F') c1 = c1 - 'A' + 10; - else c1 -= '0'; - if (c2 >= 'a' && c2 <= 'f') c2 = c2 - 'a' + 10; - else if (c2 >= 'A' && c2 <= 'F') c2 = c2 - 'A' + 10; - else c2 -= '0'; - builder.append((char)(255-((c1*16)+c2))); - } - mText = builder.toString(); - if (false) { - Log.i(TAG, "Final text: " + mText); - } - - int fontSize = getPropertyInt(tokens, 1, - TypedValue.COMPLEX_UNIT_DIP, 20, dm); - - mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mTextPaint.setTextSize(fontSize); - mTextPaint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD)); - - FontMetricsInt fm = mTextPaint.getFontMetricsInt(); - mTextWidth = (int)mTextPaint.measureText(mText); - mTextAscent = fm.ascent; - mTextDescent = fm.descent; - mTextHeight = fm.descent - fm.ascent; - - mDeltaX = getPropertyInt(tokens, 2, - TypedValue.COMPLEX_UNIT_PX, mTextWidth*2, dm); - mDeltaY = getPropertyInt(tokens, 3, - TypedValue.COMPLEX_UNIT_PX, mTextHeight*3, dm); - int shadowColor = getPropertyInt(tokens, 4, - TypedValue.COMPLEX_UNIT_PX, 0xb0000000, dm); - int color = getPropertyInt(tokens, 5, - TypedValue.COMPLEX_UNIT_PX, 0x60ffffff, dm); - int shadowRadius = getPropertyInt(tokens, 6, - TypedValue.COMPLEX_UNIT_PX, 7, dm); - int shadowDx = getPropertyInt(tokens, 8, - TypedValue.COMPLEX_UNIT_PX, 0, dm); - int shadowDy = getPropertyInt(tokens, 9, - TypedValue.COMPLEX_UNIT_PX, 0, dm); - - mTextPaint.setColor(color); - mTextPaint.setShadowLayer(shadowRadius, shadowDx, shadowDy, shadowColor); - - try { - mSurface = new Surface(session, 0, - "WatermarkSurface", -1, 1, 1, PixelFormat.TRANSLUCENT, 0); - mSurface.setLayer(TYPE_LAYER_MULTIPLIER*100); - mSurface.setPosition(0, 0); - mSurface.show(); - } catch (OutOfResourcesException e) { - } - } - - void positionSurface(int dw, int dh) { - if (mLastDW != dw || mLastDH != dh) { - mLastDW = dw; - mLastDH = dh; - mSurface.setSize(dw, dh); - mDrawNeeded = true; - } - } - - void drawIfNeeded() { - if (mDrawNeeded) { - final int dw = mLastDW; - final int dh = mLastDH; - - mDrawNeeded = false; - Rect dirty = new Rect(0, 0, dw, dh); - Canvas c = null; - try { - c = mSurface.lockCanvas(dirty); - } catch (IllegalArgumentException e) { - } catch (OutOfResourcesException e) { - } - if (c != null) { - int deltaX = mDeltaX; - int deltaY = mDeltaY; - - // deltaX shouldn't be close to a round fraction of our - // x step, or else things will line up too much. - int div = (dw+mTextWidth)/deltaX; - int rem = (dw+mTextWidth) - (div*deltaX); - int qdelta = deltaX/4; - if (rem < qdelta || rem > (deltaX-qdelta)) { - deltaX += deltaX/3; - } - - int y = -mTextHeight; - int x = -mTextWidth; - while (y < (dh+mTextHeight)) { - c.drawText(mText, x, y, mTextPaint); - x += deltaX; - if (x >= dw) { - x -= (dw+mTextWidth); - y += deltaY; - } - } - mSurface.unlockCanvasAndPost(c); - } - } - } - } - void createWatermark() { if (mWatermark != null) { return; @@ -10203,7 +8462,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 +8478,24 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public void statusBarVisibilityChanged(int visibility) { + mInputManager.setSystemUiVisibility(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 +8594,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 +8653,13 @@ 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); + } + if (mWindowAnimationBackgroundSurface != null) { + pw.println(" mWindowAnimationBackgroundSurface:"); + mWindowAnimationBackgroundSurface.printTo(" ", pw); + } pw.print(" mCurConfiguration="); pw.println(this.mCurConfiguration); pw.print(" mInTouchMode="); pw.print(mInTouchMode); pw.print(" mLayoutSeq="); pw.println(mLayoutSeq); @@ -10392,7 +8668,8 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mLayoutNeeded="); pw.print(mLayoutNeeded); pw.print(" mBlurShown="); pw.println(mBlurShown); if (mDimAnimator != null) { - mDimAnimator.printTo(pw); + pw.println(" mDimAnimator:"); + mDimAnimator.printTo(" ", pw); } else { pw.println( " no DimAnimator "); } @@ -10409,6 +8686,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 +8706,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 +8718,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); } } @@ -10454,201 +8735,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mKeyguardTokenWatcher) { } } - /** - * DimAnimator class that controls the dim animation. This holds the surface and - * all state used for dim animation. - */ - private static class DimAnimator { - Surface mDimSurface; - boolean mDimShown = false; - float mDimCurrentAlpha; - float mDimTargetAlpha; - float mDimDeltaPerMs; - long mLastDimAnimTime; - - int mLastDimWidth, mLastDimHeight; - - DimAnimator (SurfaceSession session) { - if (mDimSurface == null) { - if (SHOW_TRANSACTIONS) Slog.i(TAG, " DIM " - + mDimSurface + ": CREATE"); - try { - mDimSurface = new Surface(session, 0, - "DimSurface", - -1, 16, 16, PixelFormat.OPAQUE, - Surface.FX_SURFACE_DIM); - mDimSurface.setAlpha(0.0f); - } catch (Exception e) { - Slog.e(TAG, "Exception creating Dim surface", e); - } - } - } - - /** - * Show the dim surface. - */ - void show(int dw, int dh) { - if (!mDimShown) { - if (SHOW_TRANSACTIONS) Slog.i(TAG, " DIM " + mDimSurface + ": SHOW pos=(0,0) (" + - dw + "x" + dh + ")"); - mDimShown = true; - try { - mLastDimWidth = dw; - mLastDimHeight = dh; - mDimSurface.setPosition(0, 0); - mDimSurface.setSize(dw, dh); - mDimSurface.show(); - } catch (RuntimeException e) { - Slog.w(TAG, "Failure showing dim surface", e); - } - } else if (mLastDimWidth != dw || mLastDimHeight != dh) { - mLastDimWidth = dw; - mLastDimHeight = dh; - mDimSurface.setSize(dw, dh); - } - } - - /** - * 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) { - mDimSurface.setLayer(w.mAnimLayer-1); - - final float target = w.mExiting ? 0 : w.mAttrs.dimAmount; - if (SHOW_TRANSACTIONS) Slog.i(TAG, " DIM " + mDimSurface - + ": layer=" + (w.mAnimLayer-1) + " target=" + target); - if (mDimTargetAlpha != target) { - // If the desired dim level has changed, then - // start an animation to it. - mLastDimAnimTime = currentTime; - long duration = (w.mAnimating && w.mAnimation != null) - ? 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; - } - if (duration < 1) { - // Don't divide by zero - duration = 1; - } - mDimTargetAlpha = target; - mDimDeltaPerMs = (mDimTargetAlpha-mDimCurrentAlpha) / duration; - } - } - - /** - * Updating the surface's alpha. Returns true if the animation continues, or returns - * false when the animation is finished and the dim surface is hidden. - */ - boolean updateSurface(boolean dimming, long currentTime, boolean displayFrozen) { - if (!dimming) { - if (mDimTargetAlpha != 0) { - mLastDimAnimTime = currentTime; - mDimTargetAlpha = 0; - mDimDeltaPerMs = (-mDimCurrentAlpha) / DEFAULT_DIM_DURATION; - } - } - - boolean animating = false; - if (mLastDimAnimTime != 0) { - mDimCurrentAlpha += mDimDeltaPerMs - * (currentTime-mLastDimAnimTime); - boolean more = true; - if (displayFrozen) { - // If the display is frozen, there is no reason to animate. - more = false; - } else if (mDimDeltaPerMs > 0) { - if (mDimCurrentAlpha > mDimTargetAlpha) { - more = false; - } - } else if (mDimDeltaPerMs < 0) { - if (mDimCurrentAlpha < mDimTargetAlpha) { - more = false; - } - } else { - more = false; - } - - // Do we need to continue animating? - if (more) { - if (SHOW_TRANSACTIONS) Slog.i(TAG, " DIM " - + mDimSurface + ": alpha=" + mDimCurrentAlpha); - mLastDimAnimTime = currentTime; - mDimSurface.setAlpha(mDimCurrentAlpha); - animating = true; - } else { - mDimCurrentAlpha = mDimTargetAlpha; - mLastDimAnimTime = 0; - if (SHOW_TRANSACTIONS) Slog.i(TAG, " DIM " - + mDimSurface + ": final alpha=" + mDimCurrentAlpha); - mDimSurface.setAlpha(mDimCurrentAlpha); - if (!dimming) { - if (SHOW_TRANSACTIONS) Slog.i(TAG, " DIM " + mDimSurface - + ": HIDE"); - try { - mDimSurface.hide(); - } catch (RuntimeException e) { - Slog.w(TAG, "Illegal argument exception hiding dim surface"); - } - mDimShown = false; - } - } - } - return animating; - } - - public void printTo(PrintWriter pw) { - pw.print(" mDimShown="); pw.print(mDimShown); - pw.print(" current="); pw.print(mDimCurrentAlpha); - pw.print(" target="); pw.print(mDimTargetAlpha); - pw.print(" delta="); pw.print(mDimDeltaPerMs); - pw.print(" lastAnimTime="); pw.println(mLastDimAnimTime); - } - } - - /** - * Animation that fade in after 0.5 interpolate time, or fade out in reverse order. - * 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) { - setInterpolator(new AccelerateInterpolator()); - setDuration(DEFAULT_FADE_IN_OUT_DURATION); - mFadeIn = fadeIn; - } - - @Override - protected void applyTransformation(float interpolatedTime, Transformation t) { - float x = interpolatedTime; - 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; - } - - @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/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java new file mode 100644 index 000000000000..f8ff5f8133b6 --- /dev/null +++ b/services/java/com/android/server/wm/WindowState.java @@ -0,0 +1,1623 @@ +/* + * 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.wm; + +import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; +import static android.view.WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; +import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; + +import com.android.server.wm.WindowManagerService.H; + +import android.content.res.Configuration; +import android.graphics.Matrix; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.Region; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; +import android.view.Gravity; +import android.view.IApplicationToken; +import android.view.IWindow; +import android.view.InputChannel; +import android.view.Surface; +import android.view.View; +import android.view.ViewTreeObserver; +import android.view.WindowManager; +import android.view.WindowManagerPolicy; +import android.view.WindowManager.LayoutParams; +import android.view.animation.Animation; +import android.view.animation.Transformation; + +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * A window in the window manager. + */ +final class WindowState implements WindowManagerPolicy.WindowState { + final WindowManagerService mService; + final Session mSession; + final IWindow mClient; + WindowToken mToken; + WindowToken mRootToken; + AppWindowToken mAppToken; + AppWindowToken mTargetAppToken; + final WindowManager.LayoutParams mAttrs = new WindowManager.LayoutParams(); + final DeathRecipient mDeathRecipient; + final WindowState mAttachedWindow; + final ArrayList<WindowState> mChildWindows = new ArrayList<WindowState>(); + final int mBaseLayer; + final int mSubLayer; + final boolean mLayoutAttached; + final boolean mIsImWindow; + final boolean mIsWallpaper; + final boolean mIsFloatingLayer; + int mViewVisibility; + boolean mPolicyVisibility = true; + boolean mPolicyVisibilityAfterAnim = true; + boolean mAppFreezing; + Surface mSurface; + boolean mReportDestroySurface; + boolean mSurfacePendingDestroy; + boolean mAttachedHidden; // is our parent window hidden? + boolean mLastHidden; // was this window last hidden? + boolean mWallpaperVisible; // for wallpaper, what was last vis report? + int mRequestedWidth; + int mRequestedHeight; + int mLastRequestedWidth; + int mLastRequestedHeight; + int mLayer; + int mAnimLayer; + int mLastLayer; + boolean mHaveFrame; + boolean mObscured; + boolean mTurnOnScreen; + + int mLayoutSeq = -1; + + Configuration mConfiguration = null; + + // Actual frame shown on-screen (may be modified by animation) + final Rect mShownFrame = new Rect(); + final Rect mLastShownFrame = new Rect(); + + /** + * Set when we have changed the size of the surface, to know that + * we must tell them application to resize (and thus redraw itself). + */ + boolean mSurfaceResized; + + /** + * Insets that determine the actually visible area + */ + final Rect mVisibleInsets = new Rect(); + final Rect mLastVisibleInsets = new Rect(); + boolean mVisibleInsetsChanged; + + /** + * Insets that are covered by system windows + */ + final Rect mContentInsets = new Rect(); + final Rect mLastContentInsets = new Rect(); + boolean mContentInsetsChanged; + + /** + * Set to true if we are waiting for this window to receive its + * given internal insets before laying out other windows based on it. + */ + boolean mGivenInsetsPending; + + /** + * These are the content insets that were given during layout for + * this window, to be applied to windows behind it. + */ + final Rect mGivenContentInsets = new Rect(); + + /** + * These are the visible insets that were given during layout for + * this window, to be applied to windows behind it. + */ + 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 + * tests. + */ + 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; + float mLastHScale=1, mLastVScale=1; + final Matrix mTmpMatrix = new Matrix(); + + // "Real" frame that the application sees. + final Rect mFrame = new Rect(); + final Rect mLastFrame = new Rect(); + + 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; + + // Set to true if, when the window gets displayed, it should perform + // an enter animation. + boolean mEnterAnimationPending; + + // Currently running animation. + boolean mAnimating; + boolean mLocalAnimating; + Animation mAnimation; + boolean mAnimationIsEntrance; + boolean mHasTransformation; + boolean mHasLocalTransformation; + final Transformation mTransformation = new Transformation(); + + // If a window showing a wallpaper: the requested offset for the + // wallpaper; if a wallpaper window: the currently applied offset. + float mWallpaperX = -1; + float mWallpaperY = -1; + + // If a window showing a wallpaper: what fraction of the offset + // range corresponds to a full virtual screen. + float mWallpaperXStep = -1; + float mWallpaperYStep = -1; + + // Wallpaper windows: pixels offset based on above variables. + int mXOffset; + int mYOffset; + + // This is set after IWindowSession.relayout() has been called at + // least once for the window. It allows us to detect the situation + // where we don't yet have a surface, but should have one soon, so + // we can give the window focus before waiting for the relayout. + boolean mRelayoutCalled; + + // This is set after the Surface has been created but before the + // window has been drawn. During this time the surface is hidden. + boolean mDrawPending; + + // This is set after the window has finished drawing for the first + // time but before its surface is shown. The surface will be + // displayed when the next layout is run. + boolean mCommitDrawPending; + + // This is set during the time after the window's drawing has been + // committed, and before its surface is actually shown. It is used + // to delay showing the surface until all windows in a token are ready + // to be shown. + boolean mReadyToShow; + + // Set when the window has been shown in the screen the first time. + boolean mHasDrawn; + + // Currently running an exit animation? + boolean mExiting; + + // Currently on the mDestroySurface list? + boolean mDestroying; + + // Completely remove from window manager after exit animation? + boolean mRemoveOnExit; + + // Set when the orientation is changing and this window has not yet + // been updated for the new orientation. + boolean mOrientationChanging; + + // 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 and input window handle used by the input dispatcher. + InputWindowHandle mInputWindowHandle; + InputChannel mInputChannel; + + // Used to improve performance of toString() + String mStringNameCache; + CharSequence mLastTitle; + boolean mWasPaused; + + WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token, + WindowState attachedWindow, WindowManager.LayoutParams a, + int viewVisibility) { + mService = service; + mSession = s; + mClient = c; + mToken = token; + mAttrs.copyFrom(a); + mViewVisibility = viewVisibility; + DeathRecipient deathRecipient = new DeathRecipient(); + mAlpha = a.alpha; + if (WindowManagerService.localLOGV) Slog.v( + WindowManagerService.TAG, "Window " + this + " client=" + c.asBinder() + + " token=" + token + " (" + mAttrs.token + ")"); + try { + c.asBinder().linkToDeath(deathRecipient, 0); + } catch (RemoteException e) { + mDeathRecipient = null; + mAttachedWindow = null; + mLayoutAttached = false; + mIsImWindow = false; + mIsWallpaper = false; + mIsFloatingLayer = false; + mBaseLayer = 0; + mSubLayer = 0; + return; + } + mDeathRecipient = deathRecipient; + + if ((mAttrs.type >= FIRST_SUB_WINDOW && + mAttrs.type <= LAST_SUB_WINDOW)) { + // The multiplier here is to reserve space for multiple + // windows in the same type layer. + mBaseLayer = mService.mPolicy.windowTypeToLayerLw( + attachedWindow.mAttrs.type) * WindowManagerService.TYPE_LAYER_MULTIPLIER + + WindowManagerService.TYPE_LAYER_OFFSET; + mSubLayer = mService.mPolicy.subWindowTypeToLayerLw(a.type); + mAttachedWindow = attachedWindow; + if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(WindowManagerService.TAG, "Adding " + this + " to " + mAttachedWindow); + mAttachedWindow.mChildWindows.add(this); + mLayoutAttached = mAttrs.type != + WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; + mIsImWindow = attachedWindow.mAttrs.type == TYPE_INPUT_METHOD + || attachedWindow.mAttrs.type == TYPE_INPUT_METHOD_DIALOG; + mIsWallpaper = attachedWindow.mAttrs.type == TYPE_WALLPAPER; + mIsFloatingLayer = mIsImWindow || mIsWallpaper; + } else { + // The multiplier here is to reserve space for multiple + // windows in the same type layer. + mBaseLayer = mService.mPolicy.windowTypeToLayerLw(a.type) + * WindowManagerService.TYPE_LAYER_MULTIPLIER + + WindowManagerService.TYPE_LAYER_OFFSET; + mSubLayer = 0; + mAttachedWindow = null; + mLayoutAttached = false; + mIsImWindow = mAttrs.type == TYPE_INPUT_METHOD + || mAttrs.type == TYPE_INPUT_METHOD_DIALOG; + mIsWallpaper = mAttrs.type == TYPE_WALLPAPER; + mIsFloatingLayer = mIsImWindow || mIsWallpaper; + } + + WindowState appWin = this; + while (appWin.mAttachedWindow != null) { + appWin = mAttachedWindow; + } + WindowToken appToken = appWin.mToken; + while (appToken.appWindowToken == null) { + WindowToken parent = mService.mTokenMap.get(appToken.token); + if (parent == null || appToken == parent) { + break; + } + appToken = parent; + } + mRootToken = appToken; + mAppToken = appToken.appWindowToken; + + mSurface = null; + mRequestedWidth = 0; + mRequestedHeight = 0; + mLastRequestedWidth = 0; + mLastRequestedHeight = 0; + mXOffset = 0; + mYOffset = 0; + mLayer = 0; + mAnimLayer = 0; + mLastLayer = 0; + mInputWindowHandle = new InputWindowHandle( + mAppToken != null ? mAppToken.mInputApplicationHandle : null, this); + } + + void attach() { + if (WindowManagerService.localLOGV) Slog.v( + WindowManagerService.TAG, "Attaching " + this + " token=" + mToken + + ", list=" + mToken.windows); + mSession.windowAddedLocked(); + } + + public void computeFrameLw(Rect pf, Rect df, Rect cf, Rect vf) { + mHaveFrame = true; + + final Rect container = mContainingFrame; + container.set(pf); + + final Rect display = mDisplayFrame; + display.set(df); + + if ((mAttrs.flags & FLAG_COMPATIBLE_WINDOW) != 0) { + container.intersect(mService.mCompatibleScreenFrame); + if ((mAttrs.flags & FLAG_LAYOUT_NO_LIMITS) == 0) { + display.intersect(mService.mCompatibleScreenFrame); + } + } + + final int pw = container.right - container.left; + final int ph = container.bottom - container.top; + + int w,h; + if ((mAttrs.flags & mAttrs.FLAG_SCALED) != 0) { + w = mAttrs.width < 0 ? pw : mAttrs.width; + h = mAttrs.height< 0 ? ph : mAttrs.height; + } else { + w = mAttrs.width == mAttrs.MATCH_PARENT ? pw : mRequestedWidth; + 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); + + final Rect visible = mVisibleFrame; + visible.set(vf); + + final Rect frame = mFrame; + final int fw = frame.width(); + final int fh = frame.height(); + + //System.out.println("In: w=" + w + " h=" + h + " container=" + + // container + " x=" + mAttrs.x + " y=" + mAttrs.y); + + Gravity.apply(mAttrs.gravity, w, h, container, + (int) (mAttrs.x + mAttrs.horizontalMargin * pw), + (int) (mAttrs.y + mAttrs.verticalMargin * ph), frame); + + //System.out.println("Out: " + mFrame); + + // Now make sure the window fits in the overall display. + Gravity.applyDisplay(mAttrs.gravity, df, frame); + + // Make sure the content and visible frames are inside of the + // final window frame. + if (content.left < frame.left) content.left = frame.left; + if (content.top < frame.top) content.top = frame.top; + if (content.right > frame.right) content.right = frame.right; + if (content.bottom > frame.bottom) content.bottom = frame.bottom; + if (visible.left < frame.left) visible.left = frame.left; + if (visible.top < frame.top) visible.top = frame.top; + if (visible.right > frame.right) visible.right = frame.right; + if (visible.bottom > frame.bottom) visible.bottom = frame.bottom; + + final Rect contentInsets = mContentInsets; + contentInsets.left = content.left-frame.left; + contentInsets.top = content.top-frame.top; + contentInsets.right = frame.right-content.right; + contentInsets.bottom = frame.bottom-content.bottom; + + final Rect visibleInsets = mVisibleInsets; + visibleInsets.left = visible.left-frame.left; + visibleInsets.top = visible.top-frame.top; + visibleInsets.right = frame.right-visible.right; + visibleInsets.bottom = frame.bottom-visible.bottom; + + if (mIsWallpaper && (fw != frame.width() || fh != frame.height())) { + mService.updateWallpaperOffsetLocked(this, mService.mDisplay.getWidth(), + mService.mDisplay.getHeight(), false); + } + + if (WindowManagerService.localLOGV) { + //if ("com.google.android.youtube".equals(mAttrs.packageName) + // && mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) { + Slog.v(WindowManagerService.TAG, "Resolving (mRequestedWidth=" + + mRequestedWidth + ", mRequestedheight=" + + mRequestedHeight + ") to" + " (pw=" + pw + ", ph=" + ph + + "): frame=" + mFrame.toShortString() + + " ci=" + contentInsets.toShortString() + + " vi=" + visibleInsets.toShortString()); + //} + } + } + + public Rect getFrameLw() { + return mFrame; + } + + public Rect getShownFrameLw() { + return mShownFrame; + } + + public Rect getDisplayFrameLw() { + return mDisplayFrame; + } + + public Rect getContentFrameLw() { + return mContentFrame; + } + + public Rect getVisibleFrameLw() { + return mVisibleFrame; + } + + public boolean getGivenInsetsPendingLw() { + return mGivenInsetsPending; + } + + public Rect getGivenContentInsetsLw() { + return mGivenContentInsets; + } + + public Rect getGivenVisibleInsetsLw() { + return mGivenVisibleInsets; + } + + public WindowManager.LayoutParams getAttrs() { + return mAttrs; + } + + public int getSurfaceLayer() { + return mLayer; + } + + public IApplicationToken getAppToken() { + return mAppToken != null ? mAppToken.appToken : null; + } + + public long getInputDispatchingTimeoutNanos() { + return mAppToken != null + ? mAppToken.inputDispatchingTimeoutNanos + : WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; + } + + public boolean hasAppShownWindows() { + return mAppToken != null ? mAppToken.firstWindowDrawn : false; + } + + public void setAnimation(Animation anim) { + if (WindowManagerService.localLOGV) Slog.v( + WindowManagerService.TAG, "Setting animation in " + this + ": " + anim); + mAnimating = false; + mLocalAnimating = false; + mAnimation = anim; + mAnimation.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION); + mAnimation.scaleCurrentDuration(mService.mWindowAnimationScale); + } + + public void clearAnimation() { + if (mAnimation != null) { + mAnimating = true; + mLocalAnimating = false; + mAnimation.cancel(); + mAnimation = null; + } + } + + Surface createSurfaceLocked() { + if (mSurface == null) { + mReportDestroySurface = false; + mSurfacePendingDestroy = false; + mDrawPending = true; + mCommitDrawPending = false; + mReadyToShow = false; + if (mAppToken != null) { + mAppToken.allDrawn = false; + } + + int flags = 0; + + if ((mAttrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) { + flags |= Surface.SECURE; + } + if (WindowManagerService.DEBUG_VISIBILITY) Slog.v( + WindowManagerService.TAG, "Creating surface in session " + + mSession.mSurfaceSession + " window " + this + + " w=" + mFrame.width() + + " h=" + mFrame.height() + " format=" + + mAttrs.format + " flags=" + flags); + + int w = mFrame.width(); + int h = mFrame.height(); + if ((mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) { + // for a scaled surface, we always want the requested + // size. + w = mRequestedWidth; + h = mRequestedHeight; + } + + // Something is wrong and SurfaceFlinger will not like this, + // try to revert to sane values + if (w <= 0) w = 1; + if (h <= 0) h = 1; + + mSurfaceShown = false; + mSurfaceLayer = 0; + mSurfaceAlpha = 1; + mSurfaceX = 0; + mSurfaceY = 0; + 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, format, flags); + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " CREATE SURFACE " + + mSurface + " IN SESSION " + + mSession.mSurfaceSession + + ": pid=" + mSession.mPid + " format=" + + mAttrs.format + " flags=0x" + + Integer.toHexString(flags) + + " / " + this); + } catch (Surface.OutOfResourcesException e) { + Slog.w(WindowManagerService.TAG, "OutOfResourcesException creating surface"); + mService.reclaimSomeSurfaceMemoryLocked(this, "create", true); + return null; + } catch (Exception e) { + Slog.e(WindowManagerService.TAG, "Exception creating surface", e); + return null; + } + + if (WindowManagerService.localLOGV) Slog.v( + WindowManagerService.TAG, "Got surface: " + mSurface + + ", set left=" + mFrame.left + " top=" + mFrame.top + + ", animLayer=" + mAnimLayer); + if (WindowManagerService.SHOW_TRANSACTIONS) { + Slog.i(WindowManagerService.TAG, ">>> OPEN TRANSACTION createSurfaceLocked"); + WindowManagerService.logSurface(this, "CREATE pos=(" + mFrame.left + "," + mFrame.top + ") (" + + mFrame.width() + "x" + mFrame.height() + "), layer=" + + mAnimLayer + " HIDE", null); + } + Surface.openTransaction(); + try { + try { + mSurfaceX = mFrame.left + mXOffset; + mSurfaceY = mFrame.top + mYOffset; + mSurface.setPosition(mSurfaceX, mSurfaceY); + mSurfaceLayer = mAnimLayer; + mSurface.setLayer(mAnimLayer); + mSurfaceShown = false; + mSurface.hide(); + if ((mAttrs.flags&WindowManager.LayoutParams.FLAG_DITHER) != 0) { + if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(this, "DITHER", null); + mSurface.setFlags(Surface.SURFACE_DITHER, + Surface.SURFACE_DITHER); + } + } catch (RuntimeException e) { + Slog.w(WindowManagerService.TAG, "Error creating surface in " + w, e); + mService.reclaimSomeSurfaceMemoryLocked(this, "create-init", true); + } + mLastHidden = true; + } finally { + Surface.closeTransaction(); + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, "<<< CLOSE TRANSACTION createSurfaceLocked"); + } + if (WindowManagerService.localLOGV) Slog.v( + WindowManagerService.TAG, "Created surface " + this); + } + return mSurface; + } + + void destroySurfaceLocked() { + if (mAppToken != null && this == mAppToken.startingWindow) { + mAppToken.startingDisplayed = false; + } + + if (mSurface != null) { + mDrawPending = false; + mCommitDrawPending = false; + mReadyToShow = false; + + int i = mChildWindows.size(); + while (i > 0) { + i--; + WindowState c = mChildWindows.get(i); + c.mAttachedHidden = true; + } + + if (mReportDestroySurface) { + mReportDestroySurface = false; + mSurfacePendingDestroy = true; + try { + mClient.dispatchGetNewSurface(); + // We'll really destroy on the next time around. + return; + } catch (RemoteException e) { + } + } + + try { + if (WindowManagerService.DEBUG_VISIBILITY) { + RuntimeException e = null; + if (!WindowManagerService.HIDE_STACK_CRAWLS) { + e = new RuntimeException(); + e.fillInStackTrace(); + } + Slog.w(WindowManagerService.TAG, "Window " + this + " destroying surface " + + mSurface + ", session " + mSession, e); + } + if (WindowManagerService.SHOW_TRANSACTIONS) { + RuntimeException e = null; + if (!WindowManagerService.HIDE_STACK_CRAWLS) { + e = new RuntimeException(); + e.fillInStackTrace(); + } + if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(this, "DESTROY", e); + } + mSurface.destroy(); + } catch (RuntimeException e) { + Slog.w(WindowManagerService.TAG, "Exception thrown when destroying Window " + this + + " surface " + mSurface + " session " + mSession + + ": " + e.toString()); + } + + mSurfaceShown = false; + mSurface = null; + } + } + + boolean finishDrawingLocked() { + if (mDrawPending) { + if (WindowManagerService.SHOW_TRANSACTIONS || WindowManagerService.DEBUG_ORIENTATION) Slog.v( + WindowManagerService.TAG, "finishDrawingLocked: " + mSurface); + mCommitDrawPending = true; + mDrawPending = false; + return true; + } + return false; + } + + // This must be called while inside a transaction. + boolean commitFinishDrawingLocked(long currentTime) { + //Slog.i(TAG, "commitFinishDrawingLocked: " + mSurface); + if (!mCommitDrawPending) { + return false; + } + mCommitDrawPending = false; + mReadyToShow = true; + final boolean starting = mAttrs.type == TYPE_APPLICATION_STARTING; + final AppWindowToken atoken = mAppToken; + if (atoken == null || atoken.allDrawn || starting) { + performShowLocked(); + } + return true; + } + + // This must be called while inside a transaction. + boolean performShowLocked() { + if (WindowManagerService.DEBUG_VISIBILITY) { + RuntimeException e = null; + if (!WindowManagerService.HIDE_STACK_CRAWLS) { + e = new RuntimeException(); + e.fillInStackTrace(); + } + Slog.v(WindowManagerService.TAG, "performShow on " + this + + ": readyToShow=" + mReadyToShow + " readyForDisplay=" + isReadyForDisplay() + + " starting=" + (mAttrs.type == TYPE_APPLICATION_STARTING), e); + } + if (mReadyToShow && isReadyForDisplay()) { + if (WindowManagerService.SHOW_TRANSACTIONS || WindowManagerService.DEBUG_ORIENTATION) WindowManagerService.logSurface(this, + "SHOW (performShowLocked)", null); + if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, "Showing " + this + + " during animation: policyVis=" + mPolicyVisibility + + " attHidden=" + mAttachedHidden + + " tok.hiddenRequested=" + + (mAppToken != null ? mAppToken.hiddenRequested : false) + + " tok.hidden=" + + (mAppToken != null ? mAppToken.hidden : false) + + " animating=" + mAnimating + + " tok animating=" + + (mAppToken != null ? mAppToken.animating : false)); + if (!mService.showSurfaceRobustlyLocked(this)) { + return false; + } + mLastAlpha = -1; + mHasDrawn = true; + mLastHidden = false; + mReadyToShow = false; + mService.enableScreenIfNeededLocked(); + + mService.applyEnterAnimationLocked(this); + + int i = mChildWindows.size(); + while (i > 0) { + i--; + WindowState c = mChildWindows.get(i); + if (c.mAttachedHidden) { + c.mAttachedHidden = false; + if (c.mSurface != null) { + c.performShowLocked(); + // It hadn't been shown, which means layout not + // performed on it, so now we want to make sure to + // do a layout. If called from within the transaction + // loop, this will cause it to restart with a new + // layout. + mService.mLayoutNeeded = true; + } + } + } + + if (mAttrs.type != TYPE_APPLICATION_STARTING + && mAppToken != null) { + mAppToken.firstWindowDrawn = true; + + if (mAppToken.startingData != null) { + if (WindowManagerService.DEBUG_STARTING_WINDOW || WindowManagerService.DEBUG_ANIM) Slog.v(WindowManagerService.TAG, + "Finish starting " + mToken + + ": first real window is shown, no animation"); + // If this initial window is animating, stop it -- we + // will do an animation to reveal it from behind the + // 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; + } + mService.mFinishedStarting.add(mAppToken); + mService.mH.sendEmptyMessage(H.FINISHED_STARTING); + } + mAppToken.updateReportedVisibilityLocked(); + } + } + return true; + } + + // This must be called while inside a transaction. Returns true if + // there is more animation to run. + boolean stepAnimationLocked(long currentTime, int dw, int dh) { + if (!mService.mDisplayFrozen && mService.mPolicy.isScreenOn()) { + // We will run animations as long as the display isn't frozen. + + if (!mDrawPending && !mCommitDrawPending && mAnimation != null) { + mHasTransformation = true; + mHasLocalTransformation = true; + if (!mLocalAnimating) { + if (WindowManagerService.DEBUG_ANIM) Slog.v( + WindowManagerService.TAG, "Starting animation in " + this + + " @ " + currentTime + ": ww=" + mFrame.width() + " wh=" + mFrame.height() + + " dw=" + dw + " dh=" + dh + " scale=" + mService.mWindowAnimationScale); + mAnimation.initialize(mFrame.width(), mFrame.height(), dw, dh); + mAnimation.setStartTime(currentTime); + mLocalAnimating = true; + mAnimating = true; + } + mTransformation.clear(); + final boolean more = mAnimation.getTransformation( + currentTime, mTransformation); + if (WindowManagerService.DEBUG_ANIM) Slog.v( + WindowManagerService.TAG, "Stepped animation in " + this + + ": more=" + more + ", xform=" + mTransformation); + if (more) { + // we're not done! + return true; + } + if (WindowManagerService.DEBUG_ANIM) Slog.v( + WindowManagerService.TAG, "Finished animation in " + this + + " @ " + currentTime); + + if (mAnimation != null) { + mAnimation.cancel(); + mAnimation = null; + } + //WindowManagerService.this.dump(); + } + mHasLocalTransformation = false; + if ((!mLocalAnimating || mAnimationIsEntrance) && mAppToken != null + && mAppToken.animation != null) { + // When our app token is animating, we kind-of pretend like + // we are as well. Note the mLocalAnimating mAnimationIsEntrance + // part of this check means that we will only do this if + // our window is not currently exiting, or it is not + // locally animating itself. The idea being that one that + // is exiting and doing a local animation should be removed + // once that animation is done. + mAnimating = true; + mHasTransformation = true; + mTransformation.clear(); + return false; + } else if (mHasTransformation) { + // Little trick to get through the path below to act like + // we have finished an animation. + mAnimating = true; + } else if (isAnimating()) { + mAnimating = true; + } + } else if (mAnimation != null) { + // If the display is frozen, and there is a pending animation, + // clear it and make sure we run the cleanup code. + mAnimating = true; + mLocalAnimating = true; + mAnimation.cancel(); + mAnimation = null; + } + + if (!mAnimating && !mLocalAnimating) { + return false; + } + + if (WindowManagerService.DEBUG_ANIM) Slog.v( + WindowManagerService.TAG, "Animation done in " + this + ": exiting=" + mExiting + + ", reportedVisible=" + + (mAppToken != null ? mAppToken.reportedVisible : false)); + + mAnimating = false; + mLocalAnimating = false; + if (mAnimation != null) { + mAnimation.cancel(); + mAnimation = null; + } + mAnimLayer = mLayer; + if (mIsImWindow) { + mAnimLayer += mService.mInputMethodAnimLayerAdjustment; + } else if (mIsWallpaper) { + mAnimLayer += mService.mWallpaperAnimLayerAdjustment; + } + if (WindowManagerService.DEBUG_LAYERS) Slog.v(WindowManagerService.TAG, "Stepping win " + this + + " anim layer: " + mAnimLayer); + mHasTransformation = false; + mHasLocalTransformation = false; + if (mPolicyVisibility != mPolicyVisibilityAfterAnim) { + if (WindowManagerService.DEBUG_VISIBILITY) { + Slog.v(WindowManagerService.TAG, "Policy visibility changing after anim in " + this + ": " + + mPolicyVisibilityAfterAnim); + } + mPolicyVisibility = mPolicyVisibilityAfterAnim; + if (!mPolicyVisibility) { + if (mService.mCurrentFocus == this) { + mService.mFocusMayChange = true; + } + // Window is no longer visible -- make sure if we were waiting + // for it to be displayed before enabling the display, that + // we allow the display to be enabled now. + mService.enableScreenIfNeededLocked(); + } + } + mTransformation.clear(); + if (mHasDrawn + && mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING + && mAppToken != null + && mAppToken.firstWindowDrawn + && mAppToken.startingData != null) { + if (WindowManagerService.DEBUG_STARTING_WINDOW) Slog.v(WindowManagerService.TAG, "Finish starting " + + mToken + ": first real window done animating"); + mService.mFinishedStarting.add(mAppToken); + mService.mH.sendEmptyMessage(H.FINISHED_STARTING); + } + + finishExit(); + + if (mAppToken != null) { + mAppToken.updateReportedVisibilityLocked(); + } + + return false; + } + + void finishExit() { + if (WindowManagerService.DEBUG_ANIM) Slog.v( + WindowManagerService.TAG, "finishExit in " + this + + ": exiting=" + mExiting + + " remove=" + mRemoveOnExit + + " windowAnimating=" + isWindowAnimating()); + + final int N = mChildWindows.size(); + for (int i=0; i<N; i++) { + mChildWindows.get(i).finishExit(); + } + + if (!mExiting) { + return; + } + + if (isWindowAnimating()) { + return; + } + + if (WindowManagerService.localLOGV) Slog.v( + WindowManagerService.TAG, "Exit animation finished in " + this + + ": remove=" + mRemoveOnExit); + if (mSurface != null) { + mService.mDestroySurface.add(this); + mDestroying = true; + if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(this, "HIDE (finishExit)", null); + mSurfaceShown = false; + try { + mSurface.hide(); + } catch (RuntimeException e) { + Slog.w(WindowManagerService.TAG, "Error hiding surface in " + this, e); + } + mLastHidden = true; + } + mExiting = false; + if (mRemoveOnExit) { + mService.mPendingRemove.add(this); + mRemoveOnExit = false; + } + } + + boolean isIdentityMatrix(float dsdx, float dtdx, float dsdy, float dtdy) { + if (dsdx < .99999f || dsdx > 1.00001f) return false; + if (dtdy < .99999f || dtdy > 1.00001f) return false; + if (dtdx < -.000001f || dtdx > .000001f) return false; + if (dsdy < -.000001f || dsdy > .000001f) return false; + return true; + } + + void computeShownFrameLocked() { + final boolean selfTransformation = mHasLocalTransformation; + Transformation attachedTransformation = + (mAttachedWindow != null && mAttachedWindow.mHasLocalTransformation) + ? mAttachedWindow.mTransformation : null; + Transformation appTransformation = + (mAppToken != null && mAppToken.hasTransformation) + ? mAppToken.transformation : null; + + // Wallpapers are animated based on the "real" window they + // are currently targeting. + if (mAttrs.type == TYPE_WALLPAPER && mService.mLowerWallpaperTarget == null + && mService.mWallpaperTarget != null) { + if (mService.mWallpaperTarget.mHasLocalTransformation && + mService.mWallpaperTarget.mAnimation != null && + !mService.mWallpaperTarget.mAnimation.getDetachWallpaper()) { + attachedTransformation = mService.mWallpaperTarget.mTransformation; + if (WindowManagerService.DEBUG_WALLPAPER && attachedTransformation != null) { + Slog.v(WindowManagerService.TAG, "WP target attached xform: " + attachedTransformation); + } + } + if (mService.mWallpaperTarget.mAppToken != null && + mService.mWallpaperTarget.mAppToken.hasTransformation && + mService.mWallpaperTarget.mAppToken.animation != null && + !mService.mWallpaperTarget.mAppToken.animation.getDetachWallpaper()) { + appTransformation = mService.mWallpaperTarget.mAppToken.transformation; + if (WindowManagerService.DEBUG_WALLPAPER && appTransformation != null) { + Slog.v(WindowManagerService.TAG, "WP target app xform: " + appTransformation); + } + } + } + + final boolean screenAnimation = mService.mScreenRotationAnimation != null + && mService.mScreenRotationAnimation.isAnimating(); + if (selfTransformation || attachedTransformation != null + || appTransformation != null || screenAnimation) { + // cache often used attributes locally + final Rect frame = mFrame; + final float tmpFloats[] = mService.mTmpFloats; + final Matrix tmpMatrix = mTmpMatrix; + + // Compute the desired transformation. + tmpMatrix.setTranslate(0, 0); + if (selfTransformation) { + tmpMatrix.postConcat(mTransformation.getMatrix()); + } + 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( + mService.mScreenRotationAnimation.getEnterTransformation().getMatrix()); + } + + // "convert" it into SurfaceFlinger's format + // (a 2x2 matrix + an offset) + // Here we must not transform the position of the surface + // 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_Y]; + mDsDy = tmpFloats[Matrix.MSKEW_X]; + mDtDy = tmpFloats[Matrix.MSCALE_Y]; + 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); + + // Now set the alpha... but because our current hardware + // can't do alpha transformation on a non-opaque surface, + // turn it off if we are running an animation that is also + // transforming since it is more important to have that + // animation be smooth. + mShownAlpha = mAlpha; + if (!mService.mLimitedAlphaCompositing + || (!PixelFormat.formatHasAlpha(mAttrs.format) + || (isIdentityMatrix(mDsDx, mDtDx, mDsDy, mDtDy) + && x == frame.left && y == frame.top))) { + //Slog.i(TAG, "Applying alpha transform"); + if (selfTransformation) { + mShownAlpha *= mTransformation.getAlpha(); + } + if (attachedTransformation != null) { + mShownAlpha *= attachedTransformation.getAlpha(); + } + if (appTransformation != null) { + mShownAlpha *= appTransformation.getAlpha(); + } + if (screenAnimation) { + mShownAlpha *= + mService.mScreenRotationAnimation.getEnterTransformation().getAlpha(); + } + } else { + //Slog.i(TAG, "Not applying alpha transform"); + } + + if (WindowManagerService.localLOGV) Slog.v( + WindowManagerService.TAG, "Continuing animation in " + this + + ": " + mShownFrame + + ", alpha=" + mTransformation.getAlpha()); + return; + } + + mShownFrame.set(mFrame); + if (mXOffset != 0 || mYOffset != 0) { + mShownFrame.offset(mXOffset, mYOffset); + } + mShownAlpha = mAlpha; + mHaveMatrix = false; + mDsDx = 1; + mDtDx = 0; + mDsDy = 0; + mDtDy = 1; + } + + /** + * Is this window visible? It is not visible if there is no + * surface, or we are in the process of running an exit animation + * that will remove the surface, or its app token has been hidden. + */ + public boolean isVisibleLw() { + final AppWindowToken atoken = mAppToken; + return mSurface != null && mPolicyVisibility && !mAttachedHidden + && (atoken == null || !atoken.hiddenRequested) + && !mExiting && !mDestroying; + } + + /** + * Like {@link #isVisibleLw}, but also counts a window that is currently + * "hidden" behind the keyguard as visible. This allows us to apply + * things like window flags that impact the keyguard. + * XXX I am starting to think we need to have ANOTHER visibility flag + * for this "hidden behind keyguard" state rather than overloading + * mPolicyVisibility. Ungh. + */ + public boolean isVisibleOrBehindKeyguardLw() { + final AppWindowToken atoken = mAppToken; + return mSurface != null && !mAttachedHidden + && (atoken == null ? mPolicyVisibility : !atoken.hiddenRequested) + && !mDrawPending && !mCommitDrawPending + && !mExiting && !mDestroying; + } + + /** + * Is this window visible, ignoring its app token? It is not visible + * if there is no surface, or we are in the process of running an exit animation + * that will remove the surface. + */ + public boolean isWinVisibleLw() { + final AppWindowToken atoken = mAppToken; + return mSurface != null && mPolicyVisibility && !mAttachedHidden + && (atoken == null || !atoken.hiddenRequested || atoken.animating) + && !mExiting && !mDestroying; + } + + /** + * The same as isVisible(), but follows the current hidden state of + * the associated app token, not the pending requested hidden state. + */ + boolean isVisibleNow() { + return mSurface != null && mPolicyVisibility && !mAttachedHidden + && !mRootToken.hidden && !mExiting && !mDestroying; + } + + /** + * 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(). + */ + boolean isVisibleOrAdding() { + final AppWindowToken atoken = mAppToken; + return ((mSurface != null && !mReportDestroySurface) + || (!mRelayoutCalled && mViewVisibility == View.VISIBLE)) + && mPolicyVisibility && !mAttachedHidden + && (atoken == null || !atoken.hiddenRequested) + && !mExiting && !mDestroying; + } + + /** + * Is this window currently on-screen? It is on-screen either if it + * is visible or it is currently running an animation before no longer + * being visible. + */ + boolean isOnScreen() { + final AppWindowToken atoken = mAppToken; + if (atoken != null) { + return mSurface != null && mPolicyVisibility && !mDestroying + && ((!mAttachedHidden && !atoken.hiddenRequested) + || mAnimation != null || atoken.animation != null); + } else { + return mSurface != null && mPolicyVisibility && !mDestroying + && (!mAttachedHidden || mAnimation != null); + } + } + + /** + * Like isOnScreen(), but we don't return true if the window is part + * of a transition that has not yet been started. + */ + boolean isReadyForDisplay() { + if (mRootToken.waitingToShow && + mService.mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { + return false; + } + final AppWindowToken atoken = mAppToken; + final boolean animating = atoken != null + ? (atoken.animation != null) : false; + return mSurface != null && mPolicyVisibility && !mDestroying + && ((!mAttachedHidden && mViewVisibility == View.VISIBLE + && !mRootToken.hidden) + || mAnimation != null || animating); + } + + /** Is the window or its container currently animating? */ + boolean isAnimating() { + final WindowState attached = mAttachedWindow; + final AppWindowToken atoken = mAppToken; + return mAnimation != null + || (attached != null && attached.mAnimation != null) + || (atoken != null && + (atoken.animation != null + || atoken.inPendingTransaction)); + } + + /** Is this window currently animating? */ + boolean isWindowAnimating() { + return mAnimation != null; + } + + /** + * Like isOnScreen, but returns false if the surface hasn't yet + * been drawn. + */ + public boolean isDisplayedLw() { + final AppWindowToken atoken = mAppToken; + return mSurface != null && mPolicyVisibility && !mDestroying + && !mDrawPending && !mCommitDrawPending + && ((!mAttachedHidden && + (atoken == null || !atoken.hiddenRequested)) + || mAnimating); + } + + /** + * Returns true if the window has a surface that it has drawn a + * complete UI in to. + */ + public boolean isDrawnLw() { + final AppWindowToken atoken = mAppToken; + return mSurface != null && !mDestroying + && !mDrawPending && !mCommitDrawPending; + } + + /** + * Return true if the window is opaque and fully drawn. This indicates + * it may obscure windows behind it. + */ + boolean isOpaqueDrawn() { + return (mAttrs.format == PixelFormat.OPAQUE + || mAttrs.type == TYPE_WALLPAPER) + && mSurface != null && mAnimation == null + && (mAppToken == null || mAppToken.animation == null) + && !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 && !mService.mDisplayFrozen + && (mFrame.top != mLastFrame.top + || mFrame.left != mLastFrame.left) + && (mAttachedWindow == null || !mAttachedWindow.shouldAnimateMove()) + && mService.mPolicy.isScreenOn(); + } + + boolean needsBackgroundFiller(int screenWidth, int screenHeight) { + return + // only if the application is requesting compatible window + (mAttrs.flags & FLAG_COMPATIBLE_WINDOW) != 0 && + // only if it's visible + mHasDrawn && mViewVisibility == View.VISIBLE && + // and only if the application fills the compatible screen + mFrame.left <= mService.mCompatibleScreenFrame.left && + mFrame.top <= mService.mCompatibleScreenFrame.top && + mFrame.right >= mService.mCompatibleScreenFrame.right && + mFrame.bottom >= mService.mCompatibleScreenFrame.bottom; + } + + boolean isFullscreen(int screenWidth, int screenHeight) { + return mFrame.left <= 0 && mFrame.top <= 0 && + mFrame.right >= screenWidth && mFrame.bottom >= screenHeight; + } + + void removeLocked() { + disposeInputChannel(); + + if (mAttachedWindow != null) { + if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(WindowManagerService.TAG, "Removing " + this + " from " + mAttachedWindow); + mAttachedWindow.mChildWindows.remove(this); + } + destroySurfaceLocked(); + mSession.windowRemovedLocked(); + try { + mClient.asBinder().unlinkToDeath(mDeathRecipient, 0); + } catch (RuntimeException e) { + // Ignore if it has already been removed (usually because + // we are doing this as part of processing a death note.) + } + } + + void disposeInputChannel() { + if (mInputChannel != null) { + mService.mInputManager.unregisterInputChannel(mInputChannel); + + mInputChannel.dispose(); + mInputChannel = null; + } + } + + private class DeathRecipient implements IBinder.DeathRecipient { + public void binderDied() { + try { + synchronized(mService.mWindowMap) { + WindowState win = mService.windowForClientLocked(mSession, mClient, false); + Slog.i(WindowManagerService.TAG, "WIN DEATH: " + win); + if (win != null) { + mService.removeWindowLocked(mSession, win); + } + } + } catch (IllegalArgumentException ex) { + // This will happen if the window has already been + // removed. + } + } + } + + /** Returns true if this window desires key events. */ + public final boolean canReceiveKeys() { + return isVisibleOrAdding() + && (mViewVisibility == View.VISIBLE) + && ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0); + } + + public boolean hasDrawnLw() { + return mHasDrawn; + } + + public boolean showLw(boolean doAnimation) { + return showLw(doAnimation, true); + } + + boolean showLw(boolean doAnimation, boolean requestAnim) { + if (mPolicyVisibility && mPolicyVisibilityAfterAnim) { + return false; + } + if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, "Policy visibility true: " + this); + if (doAnimation) { + if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, "doAnimation: mPolicyVisibility=" + + mPolicyVisibility + " mAnimation=" + mAnimation); + if (mService.mDisplayFrozen || !mService.mPolicy.isScreenOn()) { + doAnimation = false; + } else if (mPolicyVisibility && mAnimation == null) { + // Check for the case where we are currently visible and + // not animating; we do not want to do animation at such a + // point to become visible when we already are. + doAnimation = false; + } + } + mPolicyVisibility = true; + mPolicyVisibilityAfterAnim = true; + if (doAnimation) { + mService.applyAnimationLocked(this, WindowManagerPolicy.TRANSIT_ENTER, true); + } + if (requestAnim) { + mService.requestAnimationLocked(0); + } + return true; + } + + public boolean hideLw(boolean doAnimation) { + return hideLw(doAnimation, true); + } + + boolean hideLw(boolean doAnimation, boolean requestAnim) { + if (doAnimation) { + if (mService.mDisplayFrozen || !mService.mPolicy.isScreenOn()) { + doAnimation = false; + } + } + boolean current = doAnimation ? mPolicyVisibilityAfterAnim + : mPolicyVisibility; + if (!current) { + return false; + } + if (doAnimation) { + mService.applyAnimationLocked(this, WindowManagerPolicy.TRANSIT_EXIT, false); + if (mAnimation == null) { + doAnimation = false; + } + } + if (doAnimation) { + mPolicyVisibilityAfterAnim = false; + } else { + if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, "Policy visibility false: " + this); + mPolicyVisibilityAfterAnim = false; + mPolicyVisibility = false; + // Window is no longer visible -- make sure if we were waiting + // for it to be displayed before enabling the display, that + // we allow the display to be enabled now. + mService.enableScreenIfNeededLocked(); + if (mService.mCurrentFocus == this) { + mService.mFocusMayChange = true; + } + } + if (requestAnim) { + mService.requestAnimationLocked(0); + } + 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()); + pw.print(prefix); pw.print("mAttrs="); pw.println(mAttrs); + if (mAttachedWindow != null || mLayoutAttached) { + pw.print(prefix); pw.print("mAttachedWindow="); pw.print(mAttachedWindow); + pw.print(" mLayoutAttached="); pw.println(mLayoutAttached); + } + if (mIsImWindow || mIsWallpaper || mIsFloatingLayer) { + pw.print(prefix); pw.print("mIsImWindow="); pw.print(mIsImWindow); + pw.print(" mIsWallpaper="); pw.print(mIsWallpaper); + pw.print(" mIsFloatingLayer="); pw.print(mIsFloatingLayer); + pw.print(" mWallpaperVisible="); pw.println(mWallpaperVisible); + } + pw.print(prefix); pw.print("mBaseLayer="); pw.print(mBaseLayer); + pw.print(" mSubLayer="); pw.print(mSubLayer); + pw.print(" mAnimLayer="); pw.print(mLayer); pw.print("+"); + pw.print((mTargetAppToken != null ? mTargetAppToken.animLayerAdjustment + : (mAppToken != null ? mAppToken.animLayerAdjustment : 0))); + pw.print("="); pw.print(mAnimLayer); + pw.print(" mLastLayer="); pw.println(mLastLayer); + if (mSurface != null) { + pw.print(prefix); pw.print("mSurface="); pw.println(mSurface); + pw.print(prefix); pw.print("Surface: shown="); pw.print(mSurfaceShown); + pw.print(" layer="); pw.print(mSurfaceLayer); + pw.print(" alpha="); pw.print(mSurfaceAlpha); + pw.print(" rect=("); pw.print(mSurfaceX); + pw.print(","); pw.print(mSurfaceY); + pw.print(") "); pw.print(mSurfaceW); + pw.print(" x "); pw.println(mSurfaceH); + } + pw.print(prefix); pw.print("mToken="); pw.println(mToken); + pw.print(prefix); pw.print("mRootToken="); pw.println(mRootToken); + if (mAppToken != null) { + pw.print(prefix); pw.print("mAppToken="); pw.println(mAppToken); + } + if (mTargetAppToken != null) { + pw.print(prefix); pw.print("mTargetAppToken="); pw.println(mTargetAppToken); + } + pw.print(prefix); pw.print("mViewVisibility=0x"); + pw.print(Integer.toHexString(mViewVisibility)); + pw.print(" mLastHidden="); pw.print(mLastHidden); + pw.print(" mHaveFrame="); pw.print(mHaveFrame); + pw.print(" mObscured="); pw.println(mObscured); + if (!mPolicyVisibility || !mPolicyVisibilityAfterAnim || mAttachedHidden) { + pw.print(prefix); pw.print("mPolicyVisibility="); + pw.print(mPolicyVisibility); + pw.print(" mPolicyVisibilityAfterAnim="); + pw.print(mPolicyVisibilityAfterAnim); + pw.print(" mAttachedHidden="); pw.println(mAttachedHidden); + } + if (!mRelayoutCalled) { + pw.print(prefix); pw.print("mRelayoutCalled="); pw.println(mRelayoutCalled); + } + pw.print(prefix); pw.print("Requested w="); pw.print(mRequestedWidth); + pw.print(" h="); pw.print(mRequestedHeight); + pw.print(" mLayoutSeq="); pw.println(mLayoutSeq); + if (mXOffset != 0 || mYOffset != 0) { + pw.print(prefix); pw.print("Offsets x="); pw.print(mXOffset); + pw.print(" y="); pw.println(mYOffset); + } + pw.print(prefix); pw.print("mGivenContentInsets="); + mGivenContentInsets.printShortString(pw); + pw.print(" mGivenVisibleInsets="); + mGivenVisibleInsets.printShortString(pw); + pw.println(); + if (mTouchableInsets != 0 || mGivenInsetsPending) { + pw.print(prefix); pw.print("mTouchableInsets="); pw.print(mTouchableInsets); + pw.print(" mGivenInsetsPending="); pw.println(mGivenInsetsPending); + } + pw.print(prefix); pw.print("mConfiguration="); pw.println(mConfiguration); + pw.print(prefix); pw.print("mShownFrame="); + mShownFrame.printShortString(pw); + pw.print(" last="); mLastShownFrame.printShortString(pw); + pw.println(); + pw.print(prefix); pw.print("mFrame="); mFrame.printShortString(pw); + pw.print(" last="); mLastFrame.printShortString(pw); + 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(); + pw.print(prefix); pw.print("mContentFrame="); mContentFrame.printShortString(pw); + pw.print(" mVisibleFrame="); mVisibleFrame.printShortString(pw); + pw.println(); + pw.print(prefix); pw.print("mContentInsets="); mContentInsets.printShortString(pw); + pw.print(" last="); mLastContentInsets.printShortString(pw); + pw.print(" mVisibleInsets="); mVisibleInsets.printShortString(pw); + pw.print(" last="); mLastVisibleInsets.printShortString(pw); + pw.println(); + if (mAnimating || mLocalAnimating || mAnimationIsEntrance + || mAnimation != null) { + pw.print(prefix); pw.print("mAnimating="); pw.print(mAnimating); + pw.print(" mLocalAnimating="); pw.print(mLocalAnimating); + pw.print(" mAnimationIsEntrance="); pw.print(mAnimationIsEntrance); + pw.print(" mAnimation="); pw.println(mAnimation); + } + if (mHasTransformation || mHasLocalTransformation) { + pw.print(prefix); pw.print("XForm: has="); + pw.print(mHasTransformation); + pw.print(" hasLocal="); pw.print(mHasLocalTransformation); + 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); + pw.print(" mHasDrawn="); pw.println(mHasDrawn); + if (mExiting || mRemoveOnExit || mDestroying || mRemoved) { + pw.print(prefix); pw.print("mExiting="); pw.print(mExiting); + pw.print(" mRemoveOnExit="); pw.print(mRemoveOnExit); + pw.print(" mDestroying="); pw.print(mDestroying); + pw.print(" mRemoved="); pw.println(mRemoved); + } + if (mOrientationChanging || mAppFreezing || mTurnOnScreen) { + pw.print(prefix); pw.print("mOrientationChanging="); + pw.print(mOrientationChanging); + pw.print(" mAppFreezing="); pw.print(mAppFreezing); + pw.print(" mTurnOnScreen="); pw.println(mTurnOnScreen); + } + if (mHScale != 1 || mVScale != 1) { + pw.print(prefix); pw.print("mHScale="); pw.print(mHScale); + pw.print(" mVScale="); pw.println(mVScale); + } + if (mWallpaperX != -1 || mWallpaperY != -1) { + pw.print(prefix); pw.print("mWallpaperX="); pw.print(mWallpaperX); + pw.print(" mWallpaperY="); pw.println(mWallpaperY); + } + if (mWallpaperXStep != -1 || mWallpaperYStep != -1) { + pw.print(prefix); pw.print("mWallpaperXStep="); pw.print(mWallpaperXStep); + pw.print(" mWallpaperYStep="); pw.println(mWallpaperYStep); + } + } + + String makeInputChannelName() { + return Integer.toHexString(System.identityHashCode(this)) + + " " + mAttrs.getTitle(); + } + + @Override + public String toString() { + if (mStringNameCache == null || mLastTitle != mAttrs.getTitle() + || mWasPaused != mToken.paused) { + mLastTitle = mAttrs.getTitle(); + mWasPaused = mToken.paused; + mStringNameCache = "Window{" + Integer.toHexString(System.identityHashCode(this)) + + " " + mLastTitle + " paused=" + mWasPaused + "}"; + } + return mStringNameCache; + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/wm/WindowToken.java b/services/java/com/android/server/wm/WindowToken.java new file mode 100644 index 000000000000..3cd256e85054 --- /dev/null +++ b/services/java/com/android/server/wm/WindowToken.java @@ -0,0 +1,110 @@ +/* + * 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.wm; + +import android.os.IBinder; + +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Container of a set of related windows in the window manager. Often this + * is an AppWindowToken, which is the handle for an Activity that it uses + * to display windows. For nested windows, there is a WindowToken created for + * the parent window to manage its children. + */ +class WindowToken { + // The window manager! + final WindowManagerService service; + + // The actual token. + final IBinder token; + + // The type of window this token is for, as per WindowManager.LayoutParams. + final int windowType; + + // Set if this token was explicitly added by a client, so should + // not be removed when all windows are removed. + final boolean explicit; + + // For printing. + String stringName; + + // If this is an AppWindowToken, this is non-null. + AppWindowToken appWindowToken; + + // All of the windows associated with this token. + final ArrayList<WindowState> windows = new ArrayList<WindowState>(); + + // Is key dispatching paused for this token? + boolean paused = false; + + // Should this token's windows be hidden? + boolean hidden; + + // Temporary for finding which tokens no longer have visible windows. + boolean hasVisible; + + // Set to true when this token is in a pending transaction where it + // will be shown. + boolean waitingToShow; + + // Set to true when this token is in a pending transaction where it + // will be hidden. + boolean waitingToHide; + + // Set to true when this token is in a pending transaction where its + // windows will be put to the bottom of the list. + boolean sendingToBottom; + + // Set to true when this token is in a pending transaction where its + // windows will be put to the top of the list. + boolean sendingToTop; + + WindowToken(WindowManagerService _service, IBinder _token, int type, boolean _explicit) { + service = _service; + token = _token; + windowType = type; + explicit = _explicit; + } + + void dump(PrintWriter pw, String prefix) { + pw.print(prefix); pw.print("token="); pw.println(token); + pw.print(prefix); pw.print("windows="); pw.println(windows); + pw.print(prefix); pw.print("windowType="); pw.print(windowType); + pw.print(" hidden="); pw.print(hidden); + pw.print(" hasVisible="); pw.println(hasVisible); + if (waitingToShow || waitingToHide || sendingToBottom || sendingToTop) { + pw.print(prefix); pw.print("waitingToShow="); pw.print(waitingToShow); + pw.print(" waitingToHide="); pw.print(waitingToHide); + pw.print(" sendingToBottom="); pw.print(sendingToBottom); + pw.print(" sendingToTop="); pw.println(sendingToTop); + } + } + + @Override + public String toString() { + if (stringName == null) { + StringBuilder sb = new StringBuilder(); + sb.append("WindowToken{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(" token="); sb.append(token); sb.append('}'); + stringName = sb.toString(); + } + return stringName; + } +}
\ No newline at end of file diff --git a/services/jni/Android.mk b/services/jni/Android.mk index de8f15891feb..be37d5d56d9b 100644 --- a/services/jni/Android.mk +++ b/services/jni/Android.mk @@ -4,28 +4,38 @@ 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_UsbService.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) @@ -36,10 +46,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_AlarmManagerService.cpp b/services/jni/com_android_server_AlarmManagerService.cpp index 0e162bda1c12..aa8c9b32c5bc 100644 --- a/services/jni/com_android_server_AlarmManagerService.cpp +++ b/services/jni/com_android_server_AlarmManagerService.cpp @@ -33,7 +33,7 @@ #include <errno.h> #include <unistd.h> -#if HAVE_ANDROID_OS +#ifdef HAVE_ANDROID_OS #include <linux/ioctl.h> #include <linux/android_alarm.h> #endif @@ -42,7 +42,7 @@ namespace android { static jint android_server_AlarmManagerService_setKernelTimezone(JNIEnv* env, jobject obj, jint fd, jint minswest) { -#if HAVE_ANDROID_OS +#ifdef HAVE_ANDROID_OS struct timezone tz; tz.tz_minuteswest = minswest; @@ -64,7 +64,7 @@ static jint android_server_AlarmManagerService_setKernelTimezone(JNIEnv* env, jo static jint android_server_AlarmManagerService_init(JNIEnv* env, jobject obj) { -#if HAVE_ANDROID_OS +#ifdef HAVE_ANDROID_OS return open("/dev/alarm", O_RDWR); #else return -1; @@ -73,14 +73,14 @@ static jint android_server_AlarmManagerService_init(JNIEnv* env, jobject obj) static void android_server_AlarmManagerService_close(JNIEnv* env, jobject obj, jint fd) { -#if HAVE_ANDROID_OS +#ifdef HAVE_ANDROID_OS close(fd); #endif } static void android_server_AlarmManagerService_set(JNIEnv* env, jobject obj, jint fd, jint type, jlong seconds, jlong nanoseconds) { -#if HAVE_ANDROID_OS +#ifdef HAVE_ANDROID_OS struct timespec ts; ts.tv_sec = seconds; ts.tv_nsec = nanoseconds; @@ -95,7 +95,7 @@ static void android_server_AlarmManagerService_set(JNIEnv* env, jobject obj, jin static jint android_server_AlarmManagerService_waitForAlarm(JNIEnv* env, jobject obj, jint fd) { -#if HAVE_ANDROID_OS +#ifdef HAVE_ANDROID_OS int result = 0; do diff --git a/services/jni/com_android_server_BatteryService.cpp b/services/jni/com_android_server_BatteryService.cpp index d4513e934331..98d0d92b69e4 100644 --- a/services/jni/com_android_server_BatteryService.cpp +++ b/services/jni/com_android_server_BatteryService.cpp @@ -33,7 +33,7 @@ #include <unistd.h> #include <dirent.h> -#if HAVE_ANDROID_OS +#ifdef HAVE_ANDROID_OS #include <linux/ioctl.h> #endif diff --git a/services/jni/com_android_server_InputApplication.cpp b/services/jni/com_android_server_InputApplication.cpp new file mode 100644 index 000000000000..e64ec4ee4c8c --- /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/wm/InputApplication"); + + GET_FIELD_ID(gInputApplicationClassInfo.inputApplicationHandle, + gInputApplicationClassInfo.clazz, + "inputApplicationHandle", "Lcom/android/server/wm/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 000000000000..85fb891cfd2b --- /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 000000000000..3a1214ff9655 --- /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/wm/InputApplicationHandle", + gInputApplicationHandleMethods, NELEM(gInputApplicationHandleMethods)); + LOG_FATAL_IF(res < 0, "Unable to register native methods."); + + FIND_CLASS(gInputApplicationHandleClassInfo.clazz, "com/android/server/wm/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 000000000000..9d187219b2ec --- /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 171471e264af..80dddc2869d1 100644 --- a/services/jni/com_android_server_InputManager.cpp +++ b/services/jni/com_android_server_InputManager.cpp @@ -24,25 +24,33 @@ // 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/Looper.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_os_MessageQueue.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; @@ -51,74 +59,23 @@ static struct { jmethodID notifyInputChannelBroken; jmethodID notifyANR; jmethodID interceptKeyBeforeQueueing; + jmethodID interceptMotionBeforeQueueingWhenScreenOff; jmethodID interceptKeyBeforeDispatching; + jmethodID dispatchUnhandledKey; jmethodID checkInjectEventsPermission; jmethodID filterTouchEvents; jmethodID filterJumpyTouchEvents; jmethodID getVirtualKeyQuietTimeMillis; - jmethodID getVirtualKeyDefinitions; - jmethodID getInputDeviceCalibration; jmethodID getExcludedDeviceNames; + jmethodID getKeyRepeatTimeout; + jmethodID getKeyRepeatDelay; 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 { @@ -135,7 +92,6 @@ static struct { jfieldID mName; jfieldID mSources; jfieldID mKeyboardType; - jfieldID mMotionRanges; } gInputDeviceClassInfo; static struct { @@ -146,13 +102,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, @@ -161,7 +141,7 @@ protected: virtual ~NativeInputManager(); public: - NativeInputManager(jobject callbacksObj); + NativeInputManager(jobject callbacksObj, const sp<Looper>& looper); inline sp<InputManager> getInputManager() const { return mInputManager; } @@ -171,12 +151,13 @@ 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); void setFocusedApplication(JNIEnv* env, jobject applicationObj); void setInputDispatchMode(bool enabled, bool frozen); + void setSystemUiVisibility(int32_t visibility); /* --- InputReaderPolicyInterface implementation --- */ @@ -185,11 +166,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,65 +175,58 @@ 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 interceptGenericBeforeQueueing(nsecs_t when, uint32_t& policyFlags); - virtual bool interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel, + virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags); + virtual void interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags); + 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; + sp<Looper> mLooper; // Cached filtering policies. int32_t mFilterTouchEvents; int32_t mFilterJumpyTouchEvents; nsecs_t mVirtualKeyQuietTime; + // Cached key repeat policy. + nsecs_t mKeyRepeatTimeout; + nsecs_t mKeyRepeatDelay; + // 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; - // Power manager interactions. - bool isScreenOn(); - bool isScreenBright(); + // System UI visibility. + int32_t systemUiVisibility; - // Weak references to all currently registered input channels by connection pointer. - Mutex mInputChannelRegistryLock; - KeyedVector<InputChannel*, jweak> mInputChannelObjWeakTable; + // Pointer controller singleton, created and destroyed as needed. + wp<PointerController> pointerController; + } mLocked; - jobject getInputChannelObjLocal(JNIEnv* env, const sp<InputChannel>& inputChannel); + void updateInactivityFadeDelayLocked(const sp<PointerController>& controller); + void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags); - static bool populateWindow(JNIEnv* env, jobject windowObj, InputWindow& outWindow); + // Power manager interactions. + bool isScreenOn(); + bool isScreenBright(); static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName); @@ -264,16 +235,26 @@ private: } }; -// ---------------------------------------------------------------------------- -NativeInputManager::NativeInputManager(jobject callbacksObj) : - mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1), mVirtualKeyQuietTime(-1), - mMaxEventsPerSecond(-1), - mDisplayWidth(-1), mDisplayHeight(-1), mDisplayOrientation(ROTATION_0) { + +NativeInputManager::NativeInputManager(jobject callbacksObj, const sp<Looper>& looper) : + mLooper(looper), + mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1), mVirtualKeyQuietTime(-1), + mKeyRepeatTimeout(-1), mKeyRepeatDelay(-1), + mMaxEventsPerSecond(-1) { JNIEnv* env = jniEnv(); mCallbacksObj = env->NewGlobalRef(callbacksObj); + { + AutoMutex _l(mLock); + mLocked.displayWidth = -1; + mLocked.displayHeight = -1; + mLocked.displayOrientation = ROTATION_0; + + mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE; + } + sp<EventHub> eventHub = new EventHub(); mInputManager = new InputManager(eventHub, this, this); } @@ -304,119 +285,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); + + if (mLocked.displayWidth != width || mLocked.displayHeight != height) { + mLocked.displayWidth = width; + mLocked.displayHeight = height; - mDisplayWidth = width; - mDisplayHeight = 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 +396,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 +418,43 @@ 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(mLooper, 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); + } + + updateInactivityFadeDelayLocked(controller); + } + return controller; +} + void NativeInputManager::notifySwitch(nsecs_t when, int32_t switchCode, int32_t switchValue, uint32_t policyFlags) { #if DEBUG_INPUT_DISPATCHER_POLICY @@ -601,50 +485,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); } } @@ -653,13 +533,34 @@ nsecs_t NativeInputManager::getKeyRepeatTimeout() { // Disable key repeat when the screen is off. return -1; } else { - // TODO use ViewConfiguration.getLongPressTimeout() - return milliseconds_to_nanoseconds(500); + if (mKeyRepeatTimeout < 0) { + JNIEnv* env = jniEnv(); + + jint result = env->CallIntMethod(mCallbacksObj, + gCallbacksClassInfo.getKeyRepeatTimeout); + if (checkAndClearExceptionFromCallback(env, "getKeyRepeatTimeout")) { + result = 500; + } + + mKeyRepeatTimeout = milliseconds_to_nanoseconds(result); + } + return mKeyRepeatTimeout; } } nsecs_t NativeInputManager::getKeyRepeatDelay() { - return milliseconds_to_nanoseconds(50); + if (mKeyRepeatDelay < 0) { + JNIEnv* env = jniEnv(); + + jint result = env->CallIntMethod(mCallbacksObj, + gCallbacksClassInfo.getKeyRepeatDelay); + if (checkAndClearExceptionFromCallback(env, "getKeyRepeatDelay")) { + result = 50; + } + + mKeyRepeatDelay = milliseconds_to_nanoseconds(result); + } + return mKeyRepeatDelay; } int32_t NativeInputManager::getMaxEventsPerSecond() { @@ -682,165 +583,58 @@ 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) { mInputManager->getDispatcher()->setInputDispatchMode(enabled, frozen); } +void NativeInputManager::setSystemUiVisibility(int32_t visibility) { + AutoMutex _l(mLock); + + if (mLocked.systemUiVisibility != visibility) { + mLocked.systemUiVisibility = visibility; + + sp<PointerController> controller = mLocked.pointerController.promote(); + if (controller != NULL) { + updateInactivityFadeDelayLocked(controller); + } + } +} + +void NativeInputManager::updateInactivityFadeDelayLocked(const sp<PointerController>& controller) { + bool lightsOut = mLocked.systemUiVisibility & ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN; + controller->setInactivityFadeDelay(lightsOut + ? PointerController::INACTIVITY_FADE_DELAY_SHORT + : PointerController::INACTIVITY_FADE_DELAY_NORMAL); +} + bool NativeInputManager::isScreenOn() { return android_server_PowerManagerService_isScreenOn(); } @@ -849,44 +643,37 @@ 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. // - For normal events wake and brighten the screen if currently off or dim. if ((policyFlags & POLICY_FLAG_TRUSTED)) { - const int32_t WM_ACTION_PASS_TO_USER = 1; - 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) { @@ -894,27 +681,13 @@ void NativeInputManager::interceptKeyBeforeQueueing(nsecs_t when, } } - if (wmActions & WM_ACTION_GO_TO_SLEEP) { - android_server_PowerManagerService_goToSleep(when); - } - - if (wmActions & WM_ACTION_POKE_USER_ACTIVITY) { - android_server_PowerManagerService_userActivity(when, POWER_MANAGER_BUTTON_EVENT); - } - - if (wmActions & WM_ACTION_PASS_TO_USER) { - policyFlags |= POLICY_FLAG_PASS_TO_USER; - } + handleInterceptActions(wmActions, when, /*byref*/ policyFlags); } else { policyFlags |= POLICY_FLAG_PASS_TO_USER; } } -void NativeInputManager::interceptGenericBeforeQueueing(nsecs_t when, uint32_t& policyFlags) { -#if DEBUG_INPUT_DISPATCHER_POLICY - LOGD("interceptGenericBeforeQueueing - when=%lld, policyFlags=0x%x", when, policyFlags); -#endif - +void NativeInputManager::interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags) { // Policy: // - Ignore untrusted events and pass them along. // - No special filtering for injected events required at this time. @@ -927,35 +700,119 @@ void NativeInputManager::interceptGenericBeforeQueueing(nsecs_t when, uint32_t& if (!isScreenBright()) { policyFlags |= POLICY_FLAG_BRIGHT_HERE; } + } else { + JNIEnv* env = jniEnv(); + jint wmActions = env->CallIntMethod(mCallbacksObj, + gCallbacksClassInfo.interceptMotionBeforeQueueingWhenScreenOff, + policyFlags); + if (checkAndClearExceptionFromCallback(env, + "interceptMotionBeforeQueueingWhenScreenOff")) { + wmActions = 0; + } + + policyFlags |= POLICY_FLAG_WOKE_HERE | POLICY_FLAG_BRIGHT_HERE; + handleInterceptActions(wmActions, when, /*byref*/ policyFlags); } } else { policyFlags |= POLICY_FLAG_PASS_TO_USER; } } -bool NativeInputManager::interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel, +void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when, + uint32_t& policyFlags) { + enum { + WM_ACTION_PASS_TO_USER = 1, + WM_ACTION_POKE_USER_ACTIVITY = 2, + WM_ACTION_GO_TO_SLEEP = 4, + }; + + if (wmActions & WM_ACTION_GO_TO_SLEEP) { +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("handleInterceptActions: Going to sleep."); +#endif + android_server_PowerManagerService_goToSleep(when); + } + + if (wmActions & WM_ACTION_POKE_USER_ACTIVITY) { +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("handleInterceptActions: Poking user activity."); +#endif + android_server_PowerManagerService_userActivity(when, POWER_MANAGER_BUTTON_EVENT); + } + + if (wmActions & WM_ACTION_PASS_TO_USER) { + policyFlags |= POLICY_FLAG_PASS_TO_USER; + } else { +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("handleInterceptActions: Not passing key to user."); +#endif + } +} + +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 +829,7 @@ bool NativeInputManager::checkInjectEventsPermissionNonReentrant( return result; } + // ---------------------------------------------------------------------------- static sp<NativeInputManager> gNativeInputManager; @@ -986,9 +844,10 @@ static bool checkInputManagerUnitialized(JNIEnv* env) { } static void android_server_InputManager_nativeInit(JNIEnv* env, jclass clazz, - jobject callbacks) { + jobject callbacks, jobject messageQueueObj) { if (gNativeInputManager == NULL) { - gNativeInputManager = new NativeInputManager(callbacks); + sp<Looper> looper = android_os_MessageQueue_getLooper(env, messageQueueObj); + gNativeInputManager = new NativeInputManager(callbacks, looper); } else { LOGE("Input manager already initialized."); jniThrowRuntimeException(env, "Input manager already initialized."); @@ -1096,7 +955,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 +967,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 +1015,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); @@ -1197,6 +1066,15 @@ static void android_server_InputManager_nativeSetInputDispatchMode(JNIEnv* env, gNativeInputManager->setInputDispatchMode(enabled, frozen); } +static void android_server_InputManager_nativeSetSystemUiVisibility(JNIEnv* env, + jclass clazz, jint visibility) { + if (checkInputManagerUnitialized(env)) { + return; + } + + gNativeInputManager->setSystemUiVisibility(visibility); +} + static jobject android_server_InputManager_nativeGetInputDevice(JNIEnv* env, jclass clazz, jint deviceId) { if (checkInputManagerUnitialized(env)) { @@ -1225,12 +1103,11 @@ static jobject android_server_InputManager_nativeGetInputDevice(JNIEnv* env, env->SetIntField(deviceObj, gInputDeviceClassInfo.mSources, deviceInfo.getSources()); env->SetIntField(deviceObj, gInputDeviceClassInfo.mKeyboardType, deviceInfo.getKeyboardType()); - const KeyedVector<int, InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges(); + const Vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges(); for (size_t i = 0; i < ranges.size(); i++) { - int rangeType = ranges.keyAt(i); - const InputDeviceInfo::MotionRange& range = ranges.valueAt(i); + const InputDeviceInfo::MotionRange& range = ranges.itemAt(i); env->CallVoidMethod(deviceObj, gInputDeviceClassInfo.addMotionRange, - rangeType, range.min, range.max, range.flat, range.fuzz); + range.axis, range.source, range.min, range.max, range.flat, range.fuzz); if (env->ExceptionCheck()) { return NULL; } @@ -1271,6 +1148,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; @@ -1285,7 +1181,7 @@ static jstring android_server_InputManager_nativeDump(JNIEnv* env, jclass clazz) static JNINativeMethod gInputManagerMethods[] = { /* name, signature, funcPtr */ - { "nativeInit", "(Lcom/android/server/InputManager$Callbacks;)V", + { "nativeInit", "(Lcom/android/server/wm/InputManager$Callbacks;Landroid/os/MessageQueue;)V", (void*) android_server_InputManager_nativeInit }, { "nativeStart", "()V", (void*) android_server_InputManager_nativeStart }, @@ -1301,24 +1197,29 @@ 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/wm/InputWindowHandle;Z)V", (void*) android_server_InputManager_nativeRegisterInputChannel }, { "nativeUnregisterInputChannel", "(Landroid/view/InputChannel;)V", (void*) android_server_InputManager_nativeUnregisterInputChannel }, { "nativeInjectInputEvent", "(Landroid/view/InputEvent;IIII)I", (void*) android_server_InputManager_nativeInjectInputEvent }, - { "nativeSetInputWindows", "([Lcom/android/server/InputWindow;)V", + { "nativeSetInputWindows", "([Lcom/android/server/wm/InputWindow;)V", (void*) android_server_InputManager_nativeSetInputWindows }, - { "nativeSetFocusedApplication", "(Lcom/android/server/InputApplication;)V", + { "nativeSetFocusedApplication", "(Lcom/android/server/wm/InputApplication;)V", (void*) android_server_InputManager_nativeSetFocusedApplication }, { "nativeSetInputDispatchMode", "(ZZ)V", (void*) android_server_InputManager_nativeSetInputDispatchMode }, + { "nativeSetSystemUiVisibility", "(I)V", + (void*) android_server_InputManager_nativeSetSystemUiVisibility }, { "nativeGetInputDevice", "(I)Landroid/view/InputDevice;", (void*) android_server_InputManager_nativeGetInputDevice }, { "nativeGetInputDeviceIds", "()[I", (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 }, }; @@ -1337,13 +1238,13 @@ static JNINativeMethod gInputManagerMethods[] = { LOG_FATAL_IF(! var, "Unable to find field " fieldName); int register_android_server_InputManager(JNIEnv* env) { - int res = jniRegisterNativeMethods(env, "com/android/server/InputManager", + int res = jniRegisterNativeMethods(env, "com/android/server/wm/InputManager", gInputManagerMethods, NELEM(gInputManagerMethods)); LOG_FATAL_IF(res < 0, "Unable to register native methods."); // Callbacks - FIND_CLASS(gCallbacksClassInfo.clazz, "com/android/server/InputManager$Callbacks"); + FIND_CLASS(gCallbacksClassInfo.clazz, "com/android/server/wm/InputManager$Callbacks"); GET_METHOD_ID(gCallbacksClassInfo.notifyConfigurationChanged, gCallbacksClassInfo.clazz, "notifyConfigurationChanged", "(J)V"); @@ -1352,16 +1253,26 @@ 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/wm/InputWindowHandle;)V"); GET_METHOD_ID(gCallbacksClassInfo.notifyANR, gCallbacksClassInfo.clazz, - "notifyANR", "(Ljava/lang/Object;Landroid/view/InputChannel;)J"); + "notifyANR", + "(Lcom/android/server/wm/InputApplicationHandle;Lcom/android/server/wm/InputWindowHandle;)J"); GET_METHOD_ID(gCallbacksClassInfo.interceptKeyBeforeQueueing, gCallbacksClassInfo.clazz, - "interceptKeyBeforeQueueing", "(JIIIIIZ)I"); + "interceptKeyBeforeQueueing", "(Landroid/view/KeyEvent;IZ)I"); + + GET_METHOD_ID(gCallbacksClassInfo.interceptMotionBeforeQueueingWhenScreenOff, + gCallbacksClassInfo.clazz, + "interceptMotionBeforeQueueingWhenScreenOff", "(I)I"); GET_METHOD_ID(gCallbacksClassInfo.interceptKeyBeforeDispatching, gCallbacksClassInfo.clazz, - "interceptKeyBeforeDispatching", "(Landroid/view/InputChannel;IIIIIII)Z"); + "interceptKeyBeforeDispatching", + "(Lcom/android/server/wm/InputWindowHandle;Landroid/view/KeyEvent;I)Z"); + + GET_METHOD_ID(gCallbacksClassInfo.dispatchUnhandledKey, gCallbacksClassInfo.clazz, + "dispatchUnhandledKey", + "(Lcom/android/server/wm/InputWindowHandle;Landroid/view/KeyEvent;I)Landroid/view/KeyEvent;"); GET_METHOD_ID(gCallbacksClassInfo.checkInjectEventsPermission, gCallbacksClassInfo.clazz, "checkInjectEventsPermission", "(II)Z"); @@ -1375,143 +1286,23 @@ 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 + GET_METHOD_ID(gCallbacksClassInfo.getKeyRepeatTimeout, gCallbacksClassInfo.clazz, + "getKeyRepeatTimeout", "()I"); - FIND_CLASS(gInputWindowClassInfo.clazz, "com/android/server/InputWindow"); + GET_METHOD_ID(gCallbacksClassInfo.getKeyRepeatDelay, gCallbacksClassInfo.clazz, + "getKeyRepeatDelay", "()I"); - 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.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_METHOD_ID(gCallbacksClassInfo.getMaxEventsPerSecond, gCallbacksClassInfo.clazz, + "getMaxEventsPerSecond", "()I"); - GET_FIELD_ID(gInputApplicationClassInfo.dispatchingTimeoutNanos, - gInputApplicationClassInfo.clazz, - "dispatchingTimeoutNanos", "J"); + GET_METHOD_ID(gCallbacksClassInfo.getPointerLayer, gCallbacksClassInfo.clazz, + "getPointerLayer", "()I"); - GET_FIELD_ID(gInputApplicationClassInfo.token, gInputApplicationClassInfo.clazz, - "token", "Ljava/lang/Object;"); + GET_METHOD_ID(gCallbacksClassInfo.getPointerIcon, gCallbacksClassInfo.clazz, + "getPointerIcon", "()Lcom/android/server/wm/InputManager$PointerIcon;"); // KeyEvent @@ -1529,7 +1320,7 @@ int register_android_server_InputManager(JNIEnv* env) { "<init>", "()V"); GET_METHOD_ID(gInputDeviceClassInfo.addMotionRange, gInputDeviceClassInfo.clazz, - "addMotionRange", "(IFFFF)V"); + "addMotionRange", "(IIFFFF)V"); GET_FIELD_ID(gInputDeviceClassInfo.mId, gInputDeviceClassInfo.clazz, "mId", "I"); @@ -1543,9 +1334,6 @@ int register_android_server_InputManager(JNIEnv* env) { GET_FIELD_ID(gInputDeviceClassInfo.mKeyboardType, gInputDeviceClassInfo.clazz, "mKeyboardType", "I"); - GET_FIELD_ID(gInputDeviceClassInfo.mMotionRanges, gInputDeviceClassInfo.clazz, - "mMotionRanges", "[Landroid/view/InputDevice$MotionRange;"); - // Configuration FIND_CLASS(gConfigurationClassInfo.clazz, "android/content/res/Configuration"); @@ -1559,6 +1347,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/wm/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 000000000000..8548b47de606 --- /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/wm/InputWindow"); + + GET_FIELD_ID(gInputWindowClassInfo.inputWindowHandle, gInputWindowClassInfo.clazz, + "inputWindowHandle", "Lcom/android/server/wm/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 000000000000..eaf7bdedbd7c --- /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 000000000000..5b74e435c7ee --- /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/wm/InputWindowHandle", + gInputWindowHandleMethods, NELEM(gInputWindowHandleMethods)); + LOG_FATAL_IF(res < 0, "Unable to register native methods."); + + FIND_CLASS(gInputWindowHandleClassInfo.clazz, "com/android/server/wm/InputWindowHandle"); + + GET_FIELD_ID(gInputWindowHandleClassInfo.ptr, gInputWindowHandleClassInfo.clazz, + "ptr", "I"); + + GET_FIELD_ID(gInputWindowHandleClassInfo.inputApplicationHandle, + gInputWindowHandleClassInfo.clazz, + "inputApplicationHandle", "Lcom/android/server/wm/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 000000000000..43f2a6ba9536 --- /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_UsbService.cpp b/services/jni/com_android_server_UsbService.cpp index 92c50085927e..6aeede2eddde 100644 --- a/services/jni/com_android_server_UsbService.cpp +++ b/services/jni/com_android_server_UsbService.cpp @@ -22,6 +22,8 @@ #include "android_runtime/AndroidRuntime.h" #include "utils/Vector.h" +#include <usbhost/usbhost.h> + #include <stdio.h> #include <asm/byteorder.h> #include <sys/types.h> @@ -48,6 +50,9 @@ static struct parcel_file_descriptor_offsets_t jmethodID mConstructor; } gParcelFileDescriptorOffsets; +static jmethodID method_usbDeviceAdded; +static jmethodID method_usbDeviceRemoved; + static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) { if (env->ExceptionCheck()) { LOGE("An exception was thrown by callback '%s'.", methodName); @@ -56,6 +61,123 @@ static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodNa } } +static int usb_device_added(const char *devname, void* client_data) { + struct usb_descriptor_header* desc; + struct usb_descriptor_iter iter; + + struct usb_device *device = usb_device_open(devname); + if (!device) { + LOGE("usb_device_open failed\n"); + return 0; + } + + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jobject thiz = (jobject)client_data; + Vector<int> interfaceValues; + Vector<int> endpointValues; + const usb_device_descriptor* deviceDesc = usb_device_get_device_descriptor(device); + + uint16_t vendorId = usb_device_get_vendor_id(device); + uint16_t productId = usb_device_get_product_id(device); + uint8_t deviceClass = deviceDesc->bDeviceClass; + uint8_t deviceSubClass = deviceDesc->bDeviceSubClass; + uint8_t protocol = deviceDesc->bDeviceProtocol; + + usb_descriptor_iter_init(device, &iter); + + while ((desc = usb_descriptor_iter_next(&iter)) != NULL) { + if (desc->bDescriptorType == USB_DT_INTERFACE) { + struct usb_interface_descriptor *interface = (struct usb_interface_descriptor *)desc; + + // push class, subclass, protocol and number of endpoints into interfaceValues vector + interfaceValues.add(interface->bInterfaceNumber); + interfaceValues.add(interface->bInterfaceClass); + interfaceValues.add(interface->bInterfaceSubClass); + interfaceValues.add(interface->bInterfaceProtocol); + interfaceValues.add(interface->bNumEndpoints); + } else if (desc->bDescriptorType == USB_DT_ENDPOINT) { + struct usb_endpoint_descriptor *endpoint = (struct usb_endpoint_descriptor *)desc; + + // push address, attributes, max packet size and interval into endpointValues vector + endpointValues.add(endpoint->bEndpointAddress); + endpointValues.add(endpoint->bmAttributes); + endpointValues.add(__le16_to_cpu(endpoint->wMaxPacketSize)); + endpointValues.add(endpoint->bInterval); + } + } + + usb_device_close(device); + + // handle generic device notification + int length = interfaceValues.size(); + jintArray interfaceArray = env->NewIntArray(length); + env->SetIntArrayRegion(interfaceArray, 0, length, interfaceValues.array()); + + length = endpointValues.size(); + jintArray endpointArray = env->NewIntArray(length); + env->SetIntArrayRegion(endpointArray, 0, length, endpointValues.array()); + + jstring deviceName = env->NewStringUTF(devname); + env->CallVoidMethod(thiz, method_usbDeviceAdded, + deviceName, vendorId, productId, deviceClass, + deviceSubClass, protocol, interfaceArray, endpointArray); + + env->DeleteLocalRef(interfaceArray); + env->DeleteLocalRef(endpointArray); + env->DeleteLocalRef(deviceName); + checkAndClearExceptionFromCallback(env, __FUNCTION__); + + return 0; +} + +static int usb_device_removed(const char *devname, void* client_data) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jobject thiz = (jobject)client_data; + + jstring deviceName = env->NewStringUTF(devname); + env->CallVoidMethod(thiz, method_usbDeviceRemoved, env->NewStringUTF(devname)); + env->DeleteLocalRef(deviceName); + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return 0; +} + +static void android_server_UsbService_monitorUsbHostBus(JNIEnv *env, jobject thiz) +{ + struct usb_host_context* context = usb_host_init(); + if (!context) { + LOGE("usb_host_init failed"); + return; + } + // this will never return so it is safe to pass thiz directly + usb_host_run(context, usb_device_added, usb_device_removed, NULL, (void *)thiz); +} + +static jobject android_server_UsbService_openDevice(JNIEnv *env, jobject thiz, jstring deviceName) +{ + const char *deviceNameStr = env->GetStringUTFChars(deviceName, NULL); + struct usb_device* device = usb_device_open(deviceNameStr); + env->ReleaseStringUTFChars(deviceName, deviceNameStr); + + if (!device) + return NULL; + + int fd = usb_device_get_fd(device); + if (fd < 0) + return NULL; + int newFD = dup(fd); + usb_device_close(device); + + jobject fileDescriptor = env->NewObject(gFileDescriptorOffsets.mClass, + gFileDescriptorOffsets.mConstructor); + if (fileDescriptor != NULL) { + env->SetIntField(fileDescriptor, gFileDescriptorOffsets.mDescriptor, newFD); + } else { + return NULL; + } + return env->NewObject(gParcelFileDescriptorOffsets.mClass, + gParcelFileDescriptorOffsets.mConstructor, fileDescriptor); +} + static void set_accessory_string(JNIEnv *env, int fd, int cmd, jobjectArray strArray, int index) { char buffer[256]; @@ -111,6 +233,9 @@ static jobject android_server_UsbService_openAccessory(JNIEnv *env, jobject thiz } static JNINativeMethod method_table[] = { + { "monitorUsbHostBus", "()V", (void*)android_server_UsbService_monitorUsbHostBus }, + { "nativeOpenDevice", "(Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;", + (void*)android_server_UsbService_openDevice }, { "nativeGetAccessoryStrings", "()[Ljava/lang/String;", (void*)android_server_UsbService_getAccessoryStrings }, { "nativeOpenAccessory","()Landroid/os/ParcelFileDescriptor;", @@ -119,7 +244,23 @@ static JNINativeMethod method_table[] = { int register_android_server_UsbService(JNIEnv *env) { - jclass clazz = env->FindClass("java/io/FileDescriptor"); + jclass clazz = env->FindClass("com/android/server/usb/UsbService"); + if (clazz == NULL) { + LOGE("Can't find com/android/server/usb/UsbService"); + return -1; + } + method_usbDeviceAdded = env->GetMethodID(clazz, "usbDeviceAdded", "(Ljava/lang/String;IIIII[I[I)V"); + if (method_usbDeviceAdded == NULL) { + LOGE("Can't find usbDeviceAdded"); + return -1; + } + method_usbDeviceRemoved = env->GetMethodID(clazz, "usbDeviceRemoved", "(Ljava/lang/String;)V"); + if (method_usbDeviceRemoved == NULL) { + LOGE("Can't find usbDeviceRemoved"); + return -1; + } + + clazz = env->FindClass("java/io/FileDescriptor"); LOG_FATAL_IF(clazz == NULL, "Unable to find class java.io.FileDescriptor"); gFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz); gFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "()V"); diff --git a/services/jni/com_android_server_VibratorService.cpp b/services/jni/com_android_server_VibratorService.cpp index 6ec5c07e89ef..0912d437241c 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 613683bf96b6..0c46eee471b9 100644 --- a/services/jni/onload.cpp +++ b/services/jni/onload.cpp @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2009 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 "JNIHelp.h" #include "jni.h" #include "utils/Log.h" @@ -6,6 +22,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); @@ -29,6 +49,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/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp index 697e879207ec..ce1ab3df23d9 100644 --- a/services/sensorservice/SensorService.cpp +++ b/services/sensorservice/SensorService.cpp @@ -340,11 +340,14 @@ status_t SensorService::enable(const sp<SensorEventConnection>& connection, if (rec->addConnection(connection)) { // this sensor is already activated, but we are adding a // connection that uses it. Immediately send down the last - // known value of the requested sensor. - sensors_event_t scratch; - sensors_event_t& event(mLastEventSeen.editValueFor(handle)); - if (event.version == sizeof(sensors_event_t)) { - connection->sendEvents(&event, 1); + // known value of the requested sensor if it's not a + // "continuous" sensor. + if (sensor->getSensor().getMinDelay() == 0) { + sensors_event_t scratch; + sensors_event_t& event(mLastEventSeen.editValueFor(handle)); + if (event.version == sizeof(sensors_event_t)) { + connection->sendEvents(&event, 1); + } } } } diff --git a/services/surfaceflinger/Android.mk b/services/surfaceflinger/Android.mk index c5bdaa11f22a..8a00a2ed57e7 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 1ffbd5b6c8d7..000000000000 --- 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 294db43cbe05..000000000000 --- 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 818774d98ae8..64cff965b12f 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 79ef2a7fe79d..ee7a2af80e34 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 000000000000..4a3b20ded9dc --- /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 000000000000..5a9e9ebb1934 --- /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 c9dcef370a4d..517c335925a5 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 @@ -55,10 +56,10 @@ Layer::Layer(SurfaceFlinger* flinger, mNeedsBlending(true), mNeedsDithering(false), mSecure(false), + mProtectedByApp(false), mTextureManager(), mBufferManager(mTextureManager), - mWidth(0), mHeight(0), mNeedsScaling(false), mFixedSize(false), - mBypassState(false) + mWidth(0), mHeight(0), mNeedsScaling(false), mFixedSize(false) { } @@ -83,8 +84,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); @@ -120,7 +141,8 @@ void Layer::onRemoved() sp<LayerBaseClient::Surface> Layer::createSurface() const { - return mSurface; + sp<Surface> sur(new SurfaceLayer(mFlinger, const_cast<Layer *>(this))); + return sur; } status_t Layer::ditch() @@ -130,10 +152,6 @@ 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); - mSurface.clear(); - Mutex::Autolock _l(mLock); mWidth = mHeight = 0; return NO_ERROR; @@ -171,17 +189,83 @@ 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; + mProtectedByApp = (flags & ISurfaceComposer::eProtectedByApp) ? true : false; + mNeedsBlending = (info.h_alpha - info.l_alpha) > 0 && + (flags & ISurfaceComposer::eOpaque) == 0; // we use the red index int displayRedSize = displayInfo.getSize(PixelFormatInfo::INDEX_RED); int layerRedsize = info.getSize(PixelFormatInfo::INDEX_RED); mNeedsDithering = layerRedsize > displayRedSize; - mSurface = new SurfaceLayer(mFlinger, this); 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,30 +336,46 @@ void Layer::onDraw(const Region& clip) const } return; } + drawWithOpenGL(clip, tex); +} -#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; +// As documented in libhardware header, formats in the range +// 0x100 - 0x1FF are specific to the HAL implementation, and +// are known to have no alpha channel +// TODO: move definition for device-specific range into +// hardware.h, instead of using hard-coded values here. +#define HARDWARE_IS_DEVICE_FORMAT(f) ((f) >= 0x100 && (f) <= 0x1FF) + +bool Layer::needsBlending(const sp<GraphicBuffer>& buffer) const +{ + // If buffers where set with eOpaque flag, all buffers are known to + // be opaque without having to check their actual format + if (mNeedsBlending && buffer != NULL) { + PixelFormat format = buffer->getPixelFormat(); + + if (HARDWARE_IS_DEVICE_FORMAT(format)) { + return false; + } + + PixelFormatInfo info; + status_t err = getPixelFormatInfo(format, &info); + if (!err && info.h_alpha <= info.l_alpha) { + return false; + } } -#endif - drawWithOpenGL(clip, tex); + // Return opacity as determined from flags and format options + // passed to setBuffers() + return mNeedsBlending; +} + +bool Layer::needsBlending() const +{ + if (mBufferManager.hasActiveBuffer()) { + return needsBlending(mBufferManager.getActiveBuffer()); + } + + return mNeedsBlending; } bool Layer::needsFiltering() const @@ -290,6 +390,12 @@ bool Layer::needsFiltering() const return LayerBase::needsFiltering(); } +bool Layer::isProtected() const +{ + sp<GraphicBuffer> activeBuffer(mBufferManager.getActiveBuffer()); + return (activeBuffer != 0) && + (activeBuffer->getUsage() & GRALLOC_USAGE_PROTECTED); +} status_t Layer::setBufferCount(int bufferCount) { @@ -302,8 +408,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 +443,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 +463,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 +473,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(); @@ -442,40 +519,11 @@ uint32_t Layer::getEffectiveUsage(uint32_t usage) const // request EGLImage for all buffers usage |= GraphicBuffer::USAGE_HW_TEXTURE; } - 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(); - } + if (mProtectedByApp) { + // need a hardware-protected path to external video sink + usage |= GraphicBuffer::USAGE_PROTECTED; } + return usage; } uint32_t Layer::doTransaction(uint32_t flags) @@ -581,14 +629,31 @@ void Layer::lockPageFlip(bool& recomputeVisibleRegions) } // we retired a buffer, which becomes the new front buffer + + const bool noActiveBuffer = !mBufferManager.hasActiveBuffer(); + const bool activeBlending = + noActiveBuffer ? true : needsBlending(mBufferManager.getActiveBuffer()); + 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) { + if (!noActiveBuffer && activeBlending != needsBlending(newFrontBuffer)) { + // new buffer has different opacity than previous active buffer, need + // to recompute visible regions accordingly + recomputeVisibleRegions = true; + } + // get the dirty region // compute the posted region const Region dirty(lcblk->getDirtyRegion(buf)); @@ -712,9 +777,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); } @@ -744,7 +809,7 @@ status_t Layer::ClientRef::setToken(const sp<UserClient>& uc, { // scope for strong mUserClient reference sp<UserClient> userClient(mUserClient.promote()); - if (mUserClient != 0 && mControlBlock != 0) { + if (userClient != 0 && mControlBlock != 0) { mControlBlock->setStatus(NO_INIT); } } @@ -779,7 +844,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 +852,54 @@ 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) { + // If there is an active texture, move it into slot 0 if needed + if (mActiveBufferIndex > 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 +910,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 +981,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 7bb207a1fcdd..128f93d848ce 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -68,24 +68,22 @@ 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); virtual void lockPageFlip(bool& recomputeVisibleRegions); virtual void unlockPageFlip(const Transform& planeTransform, Region& outDirtyRegion); - virtual bool needsBlending() const { return mNeedsBlending; } + virtual bool needsBlending(const sp<GraphicBuffer>& buffer) const; + virtual bool needsBlending() const; virtual bool needsDithering() const { return mNeedsDithering; } virtual bool needsFiltering() const; virtual bool isSecure() const { return mSecure; } + virtual bool isProtected() const; 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 +165,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 +179,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 +190,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 @@ -211,14 +213,14 @@ private: ClientRef mUserClientRef; // constants - sp<Surface> mSurface; PixelFormat mFormat; const GLExtensions& mGLExtensions; bool mNeedsBlending; bool mNeedsDithering; // page-flip thread (currently main thread) - bool mSecure; + bool mSecure; // no screenshots + bool mProtectedByApp; // application requires protected path to external sink Region mPostedDirtyRegion; // page-flip thread and transaction thread (currently main thread) @@ -237,7 +239,6 @@ private: uint32_t mReqFormat; bool mNeedsScaling; bool mFixedSize; - bool mBypassState; }; // --------------------------------------------------------------------------- diff --git a/services/surfaceflinger/LayerBase.cpp b/services/surfaceflinger/LayerBase.cpp index 21c36e18485e..6025ed4921cb 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 @@ -515,13 +528,21 @@ void LayerBase::dump(String8& result, char* buffer, size_t SIZE) const result.append(buffer); } +void LayerBase::shortDump(String8& result, char* scratch, size_t size) const +{ + LayerBase::dump(result, scratch, size); +} + + // --------------------------------------------------------------------------- int32_t LayerBaseClient::sIdentity = 1; LayerBaseClient::LayerBaseClient(SurfaceFlinger* flinger, DisplayID display, const sp<Client>& client) - : LayerBase(flinger, display), mClientRef(client), + : LayerBase(flinger, display), + mHasSurface(false), + mClientRef(client), mIdentity(uint32_t(android_atomic_inc(&sIdentity))) { } @@ -538,14 +559,20 @@ sp<LayerBaseClient::Surface> LayerBaseClient::getSurface() { sp<Surface> s; Mutex::Autolock _l(mLock); - s = mClientSurface.promote(); - if (s == 0) { - s = createSurface(); - mClientSurface = s; - } + + LOG_ALWAYS_FATAL_IF(mHasSurface, + "LayerBaseClient::getSurface() has already been called"); + + mHasSurface = true; + s = createSurface(); + mClientSurfaceBinder = s->asBinder(); return s; } +wp<IBinder> LayerBaseClient::getSurfaceBinder() const { + return mClientSurfaceBinder; +} + sp<LayerBaseClient::Surface> LayerBaseClient::createSurface() const { return new Surface(mFlinger, mIdentity, @@ -566,6 +593,12 @@ void LayerBaseClient::dump(String8& result, char* buffer, size_t SIZE) const result.append(buffer); } + +void LayerBaseClient::shortDump(String8& result, char* scratch, size_t size) const +{ + LayerBaseClient::dump(result, scratch, size); +} + // --------------------------------------------------------------------------- LayerBaseClient::Surface::Surface( @@ -597,21 +630,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); } @@ -626,26 +644,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 afc5ec8d5073..7162e47275c1 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; @@ -196,6 +196,12 @@ public: */ virtual bool isSecure() const { return false; } + /** + * isProtected - true if the layer may contain protected content in the + * GRALLOC_USAGE_PROTECTED sense. + */ + virtual bool isProtected() const { return false; } + /** Called from the main thread, when the surface is removed from the * draw list */ virtual status_t ditch() { return NO_ERROR; } @@ -206,6 +212,7 @@ public: /** always call base class first */ virtual void dump(String8& result, char* scratch, size_t size) const; + virtual void shortDump(String8& result, char* scratch, size_t size) const; enum { // flags for doTransaction() @@ -284,6 +291,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); } @@ -308,12 +316,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; @@ -325,10 +327,12 @@ public: protected: virtual void dump(String8& result, char* scratch, size_t size) const; + virtual void shortDump(String8& result, char* scratch, size_t size) const; private: mutable Mutex mLock; - mutable wp<Surface> mClientSurface; + mutable bool mHasSurface; + 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 4cfcfe3b430a..000000000000 --- 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 4c9ec647a0c5..000000000000 --- 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 23506cfbcb5d..000000000000 --- 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 a89d8fe98143..000000000000 --- 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 80cc52c42deb..f79166d8acca 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 f0323149ac25..75f9a8928287 100644 --- a/services/surfaceflinger/LayerDim.h +++ b/services/surfaceflinger/LayerDim.h @@ -31,22 +31,17 @@ 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); virtual ~LayerDim(); virtual void onDraw(const Region& clip) const; - virtual bool needsBlending() const { return true; } - virtual bool isSecure() const { return false; } + virtual bool needsBlending() const { return true; } + virtual bool isSecure() const { return false; } + virtual bool isProtectedByApp() const { return false; } + virtual bool isProtectedByDRM() 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 c08e2c941235..a9fa1ef42443 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; } @@ -332,6 +327,40 @@ void SurfaceFlinger::signal() const { const_cast<SurfaceFlinger*>(this)->signalEvent(); } +bool SurfaceFlinger::authenticateSurface(const sp<ISurface>& surface) const { + Mutex::Autolock _l(mStateLock); + sp<IBinder> surfBinder(surface->asBinder()); + + // Check the visible layer list for the ISurface + const LayerVector& currentLayers = mCurrentState.layersSortedByZ; + size_t count = currentLayers.size(); + for (size_t i=0 ; i<count ; i++) { + const sp<LayerBase>& layer(currentLayers[i]); + sp<LayerBaseClient> lbc(layer->getLayerBaseClient()); + if (lbc != NULL && lbc->getSurfaceBinder() == surfBinder) { + return true; + } + } + + // Check the layers in the purgatory. This check is here so that if a + // Surface gets destroyed before all the clients are done using it, the + // error will not be reported as "surface XYZ is not authenticated", but + // will instead fail later on when the client tries to use the surface, + // which should be reported as "surface XYZ returned an -ENODEV". The + // purgatorized layers are no less authentic than the visible ones, so this + // should not cause any harm. + size_t purgatorySize = mLayerPurgatory.size(); + for (size_t i=0 ; i<purgatorySize ; i++) { + const sp<LayerBase>& layer(mLayerPurgatory.itemAt(i)); + sp<LayerBaseClient> lbc(layer->getLayerBaseClient()); + if (lbc != NULL && lbc->getSurfaceBinder() == surfBinder) { + return true; + } + } + + return false; +} + status_t SurfaceFlinger::postMessageAsync(const sp<MessageBase>& msg, nsecs_t reltime, uint32_t flags) { @@ -375,17 +404,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 +435,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 +493,7 @@ void SurfaceFlinger::handleTransaction(uint32_t transactionFlags) handleTransactionLocked(transactionFlags, ditchedLayers); mLastTransactionTime = systemTime() - now; mDebugInTransaction = 0; + invalidateHwcGeometry(); // here the transaction has been committed } @@ -487,6 +501,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 +705,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 +732,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 +757,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 +795,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 +829,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 +873,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 +1057,8 @@ void SurfaceFlinger::drawWormhole() const glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); } } @@ -1054,7 +1120,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) { @@ -1066,8 +1132,12 @@ status_t SurfaceFlinger::removeLayer_l(const sp<LayerBase>& layerBase) status_t SurfaceFlinger::purgatorizeLayer_l(const sp<LayerBase>& layerBase) { - // remove the layer from the main list (through a transaction). + // First add the layer to the purgatory list, which makes sure it won't + // go away, then remove it from the main list (through a transaction). ssize_t err = removeLayer_l(layerBase); + if (err >= 0) { + mLayerPurgatory.add(layerBase); + } layerBase->onRemoved(); @@ -1109,7 +1179,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) { @@ -1153,7 +1223,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)) @@ -1186,21 +1256,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; @@ -1212,7 +1278,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; @@ -1264,15 +1330,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) @@ -1282,21 +1339,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). @@ -1317,7 +1365,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; @@ -1330,14 +1378,27 @@ 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. */ status_t err = flinger->removeLayer_l(l); + if (err == NAME_NOT_FOUND) { + // The surface wasn't in the current list, which means it was + // removed already, which means it is in the purgatory, + // and need to be removed from there. + // This needs to happen from the main thread since its dtor + // must run from there (b/c of OpenGL ES). Additionally, we + // can't really acquire our internal lock from + // destroySurface() -- see postMessage() below. + ssize_t idx = flinger->mLayerPurgatory.remove(l); + LOGE_IF(idx < 0, + "layer=%p is not in the purgatory list", l.get()); + } + LOGE_IF(err<0 && err != NAME_NOT_FOUND, "error removing layer=%p (%s)", l.get(), strerror(-err)); return true; @@ -1420,7 +1481,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()) { @@ -1447,14 +1508,19 @@ 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); } + /* + * Dump the visible layer list + */ const LayerVector& currentLayers = mCurrentState.layersSortedByZ; const size_t count = currentLayers.size(); + snprintf(buffer, SIZE, "Visible layers (count = %d)\n", count); + result.append(buffer); for (size_t i=0 ; i<count ; i++) { const sp<LayerBase>& layer(currentLayers[i]); layer->dump(result, buffer, SIZE); @@ -1464,12 +1530,30 @@ status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args) layer->visibleRegionScreen.dump(result, "visibleRegionScreen"); } + /* + * Dump the layers in the purgatory + */ + + const size_t purgatorySize = mLayerPurgatory.size(); + snprintf(buffer, SIZE, "Purgatory state (%d entries)\n", purgatorySize); + result.append(buffer); + for (size_t i=0 ; i<purgatorySize ; i++) { + const sp<LayerBase>& layer(mLayerPurgatory.itemAt(i)); + layer->shortDump(result, buffer, SIZE); + } + + /* + * Dump SurfaceFlinger global state + */ + + snprintf(buffer, SIZE, "SurfaceFlinger global state\n"); + result.append(buffer); 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" @@ -1489,8 +1573,22 @@ status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args) result.append(buffer); } + /* + * 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(); @@ -1564,6 +1662,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()); @@ -2057,7 +2160,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; @@ -2065,6 +2169,19 @@ status_t SurfaceFlinger::captureScreenImplLocked(DisplayID dpy, if (UNLIKELY(uint32_t(dpy) >= DISPLAY_COUNT)) return BAD_VALUE; + // make sure none of the layers are protected + const Vector< sp<LayerBase> >& layers(mVisibleLayersSortedByZ); + const size_t count = layers.size(); + for (size_t i=0 ; i<count ; ++i) { + const sp<LayerBase>& layer(layers[i]); + const uint32_t z = layer->drawingState().z; + if (z >= minLayerZ && z <= maxLayerZ) { + if (layer->isProtected()) { + return INVALID_OPERATION; + } + } + } + if (!GLExtensions::getInstance().haveFramebufferObject()) return INVALID_OPERATION; @@ -2080,6 +2197,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 ) ; @@ -2094,6 +2214,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 @@ -2109,11 +2230,12 @@ status_t SurfaceFlinger::captureScreenImplLocked(DisplayID dpy, glClearColor(0,0,0,1); glClear(GL_COLOR_BUFFER_BIT); - const Vector< sp<LayerBase> >& layers(mVisibleLayersSortedByZ); - 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 @@ -2148,8 +2270,6 @@ status_t SurfaceFlinger::captureScreenImplLocked(DisplayID dpy, glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); - - } else { result = BAD_VALUE; } @@ -2161,6 +2281,8 @@ status_t SurfaceFlinger::captureScreenImplLocked(DisplayID dpy, hw.compositionComplete(); + // LOGD("screenshot: result = %s", result<0 ? strerror(result) : "OK"); + return result; } @@ -2168,7 +2290,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)) @@ -2186,13 +2309,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 { @@ -2206,14 +2334,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(); @@ -2363,12 +2491,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 { @@ -2409,6 +2540,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 df1ca4892c9d..95668190f456 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,33 @@ 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 bool authenticateSurface(const sp<ISurface>& surface) const; + + 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 +249,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 +311,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 +327,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); @@ -370,6 +375,7 @@ private: volatile int32_t mTransactionFlags; volatile int32_t mTransactionCount; Condition mTransactionCV; + SortedVector< sp<LayerBase> > mLayerPurgatory; bool mResizeTransationPending; // protected by mStateLock (but we could use another lock) @@ -394,18 +400,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 592b60141ef1..000000000000 --- 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 c248a615a919..000000000000 --- 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 127cca32579c..0ccca77dd636 100644 --- a/services/surfaceflinger/tests/resize/resize.cpp +++ b/services/surfaceflinger/tests/resize/resize.cpp @@ -1,3 +1,19 @@ +/* + * 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 <cutils/memory.h> #include <utils/Log.h> @@ -10,8 +26,6 @@ #include <surfaceflinger/ISurface.h> #include <surfaceflinger/SurfaceComposerClient.h> -#include <ui/Overlay.h> - using namespace android; namespace android { @@ -32,7 +46,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 b4de4b459c38..67ecf7e1000d 100644 --- a/services/surfaceflinger/tests/surface/surface.cpp +++ b/services/surfaceflinger/tests/surface/surface.cpp @@ -1,3 +1,19 @@ +/* + * 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 <cutils/memory.h> #include <utils/Log.h> @@ -10,8 +26,6 @@ #include <surfaceflinger/ISurface.h> #include <surfaceflinger/SurfaceComposerClient.h> -#include <ui/Overlay.h> - using namespace android; int main(int argc, char** argv) @@ -23,7 +37,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 b07a10b57df9..186b3490e502 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 5ce109f46e8b..f115f4245c28 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 000000000000..2bc68250a9a7 --- /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 000000000000..38fed225e306 --- /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 000000000000..17a1585614b1 --- /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 000000000000..1bc9b86a622f --- /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 000000000000..98966c032d14 --- /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 000000000000..71e8e2a62623 --- /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; + } +} |