diff options
Diffstat (limited to 'services')
170 files changed, 29423 insertions, 21714 deletions
diff --git a/services/audioflinger/Android.mk b/services/audioflinger/Android.mk index 870c0b8..22ecc54 100644 --- a/services/audioflinger/Android.mk +++ b/services/audioflinger/Android.mk @@ -87,7 +87,8 @@ LOCAL_SHARED_LIBRARIES := \ libutils \ libbinder \ libmedia \ - libhardware_legacy + libhardware_legacy \ + libeffects ifeq ($(strip $(BOARD_USES_GENERIC_AUDIO)),true) LOCAL_STATIC_LIBRARIES += libaudiointerface libaudiopolicybase diff --git a/services/audioflinger/AudioDumpInterface.cpp b/services/audioflinger/AudioDumpInterface.cpp index a018b4c..6c11114 100644 --- a/services/audioflinger/AudioDumpInterface.cpp +++ b/services/audioflinger/AudioDumpInterface.cpp @@ -32,7 +32,7 @@ namespace android { // ---------------------------------------------------------------------------- AudioDumpInterface::AudioDumpInterface(AudioHardwareInterface* hw) - : mFirstHwOutput(true), mPolicyCommands(String8("")), mFileName(String8("")) + : mPolicyCommands(String8("")), mFileName(String8("")) { if(hw == 0) { LOGE("Dump construct hw = 0"); @@ -47,6 +47,11 @@ AudioDumpInterface::~AudioDumpInterface() for (size_t i = 0; i < mOutputs.size(); i++) { closeOutputStream((AudioStreamOut *)mOutputs[i]); } + + for (size_t i = 0; i < mInputs.size(); i++) { + closeInputStream((AudioStreamIn *)mInputs[i]); + } + if(mFinalInterface) delete mFinalInterface; } @@ -60,31 +65,32 @@ AudioStreamOut* AudioDumpInterface::openOutputStream( uint32_t lRate = 44100; - if (AudioSystem::isA2dpDevice((AudioSystem::audio_devices)devices) || mFirstHwOutput) { - outFinal = mFinalInterface->openOutputStream(devices, format, channels, sampleRate, status); - if (outFinal != 0) { - lFormat = outFinal->format(); - lChannels = outFinal->channels(); - lRate = outFinal->sampleRate(); - if (!AudioSystem::isA2dpDevice((AudioSystem::audio_devices)devices)) { - mFirstHwOutput = false; - } - } + outFinal = mFinalInterface->openOutputStream(devices, format, channels, sampleRate, status); + if (outFinal != 0) { + lFormat = outFinal->format(); + lChannels = outFinal->channels(); + lRate = outFinal->sampleRate(); } else { - if (format != 0 && *format != 0) { - lFormat = *format; - } else { - lFormat = AudioSystem::PCM_16_BIT; + if (format != 0) { + if (*format != 0) { + lFormat = *format; + } else { + *format = lFormat; + } } - if (channels != 0 && *channels != 0) { - lChannels = *channels; - } else { - lChannels = AudioSystem::CHANNEL_OUT_STEREO; + if (channels != 0) { + if (*channels != 0) { + lChannels = *channels; + } else { + *channels = lChannels; + } } - if (sampleRate != 0 && *sampleRate != 0) { - lRate = *sampleRate; - } else { - lRate = 44100; + if (sampleRate != 0) { + if (*sampleRate != 0) { + lRate = *sampleRate; + } else { + *sampleRate = lRate; + } } if (status) *status = NO_ERROR; } @@ -111,7 +117,6 @@ void AudioDumpInterface::closeOutputStream(AudioStreamOut* out) dumpOut->standby(); if (dumpOut->finalStream() != NULL) { mFinalInterface->closeOutputStream(dumpOut->finalStream()); - mFirstHwOutput = true; } mOutputs.remove(dumpOut); @@ -126,18 +131,33 @@ AudioStreamIn* AudioDumpInterface::openInputStream(uint32_t devices, int *format uint32_t lChannels = AudioSystem::CHANNEL_IN_MONO; uint32_t lRate = 8000; - - if (mInputs.size() == 0) { - inFinal = mFinalInterface->openInputStream(devices, format, channels, sampleRate, status, acoustics); - if (inFinal == 0) return 0; - + inFinal = mFinalInterface->openInputStream(devices, format, channels, sampleRate, status, acoustics); + if (inFinal != 0) { lFormat = inFinal->format(); lChannels = inFinal->channels(); lRate = inFinal->sampleRate(); } else { - if (format != 0 && *format != 0) lFormat = *format; - if (channels != 0 && *channels != 0) lChannels = *channels; - if (sampleRate != 0 && *sampleRate != 0) lRate = *sampleRate; + if (format != 0) { + if (*format != 0) { + lFormat = *format; + } else { + *format = lFormat; + } + } + if (channels != 0) { + if (*channels != 0) { + lChannels = *channels; + } else { + *channels = lChannels; + } + } + if (sampleRate != 0) { + if (*sampleRate != 0) { + lRate = *sampleRate; + } else { + *sampleRate = lRate; + } + } if (status) *status = NO_ERROR; } LOGV("openInputStream(), inFinal %p", inFinal); @@ -223,6 +243,15 @@ String8 AudioDumpInterface::getParameters(const String8& keys) return keyValuePairs; } +status_t AudioDumpInterface::setMode(int mode) +{ + return mFinalInterface->setMode(mode); +} + +size_t AudioDumpInterface::getInputBufferSize(uint32_t sampleRate, int format, int channelCount) +{ + return mFinalInterface->getInputBufferSize(sampleRate, format, channelCount); +} // ---------------------------------------------------------------------------- @@ -235,7 +264,7 @@ AudioStreamOutDump::AudioStreamOutDump(AudioDumpInterface *interface, uint32_t sampleRate) : mInterface(interface), mId(id), mSampleRate(sampleRate), mFormat(format), mChannels(channels), mLatency(0), mDevice(devices), - mBufferSize(1024), mFinalStream(finalStream), mOutFile(0), mFileCount(0) + mBufferSize(1024), mFinalStream(finalStream), mFile(0), mFileCount(0) { LOGV("AudioStreamOutDump Constructor %p, mInterface %p, mFinalStream %p", this, mInterface, mFinalStream); } @@ -254,26 +283,26 @@ ssize_t AudioStreamOutDump::write(const void* buffer, size_t bytes) if (mFinalStream) { ret = mFinalStream->write(buffer, bytes); } else { - usleep((bytes * 1000000) / frameSize() / sampleRate()); + usleep((((bytes * 1000) / frameSize()) / sampleRate()) * 1000); ret = bytes; } - if(!mOutFile) { + if(!mFile) { if (mInterface->fileName() != "") { char name[255]; - sprintf(name, "%s_%d_%d.pcm", mInterface->fileName().string(), mId, ++mFileCount); - mOutFile = fopen(name, "wb"); - LOGV("Opening dump file %s, fh %p", name, mOutFile); + sprintf(name, "%s_out_%d_%d.pcm", mInterface->fileName().string(), mId, ++mFileCount); + mFile = fopen(name, "wb"); + LOGV("Opening dump file %s, fh %p", name, mFile); } } - if (mOutFile) { - fwrite(buffer, bytes, 1, mOutFile); + if (mFile) { + fwrite(buffer, bytes, 1, mFile); } return ret; } status_t AudioStreamOutDump::standby() { - LOGV("AudioStreamOutDump standby(), mOutFile %p, mFinalStream %p", mOutFile, mFinalStream); + LOGV("AudioStreamOutDump standby(), mFile %p, mFinalStream %p", mFile, mFinalStream); Close(); if (mFinalStream != 0 ) return mFinalStream->standby(); @@ -330,7 +359,7 @@ status_t AudioStreamOutDump::setParameters(const String8& keyValuePairs) } if (param.getInt(String8("format"), valueInt) == NO_ERROR) { - if (mOutFile == 0) { + if (mFile == 0) { mFormat = valueInt; } else { status = INVALID_OPERATION; @@ -345,7 +374,7 @@ status_t AudioStreamOutDump::setParameters(const String8& keyValuePairs) } if (param.getInt(String8("sampling_rate"), valueInt) == NO_ERROR) { if (valueInt > 0 && valueInt <= 48000) { - if (mOutFile == 0) { + if (mFile == 0) { mSampleRate = valueInt; } else { status = INVALID_OPERATION; @@ -373,9 +402,9 @@ status_t AudioStreamOutDump::dump(int fd, const Vector<String16>& args) void AudioStreamOutDump::Close() { - if(mOutFile) { - fclose(mOutFile); - mOutFile = 0; + if(mFile) { + fclose(mFile); + mFile = 0; } } @@ -396,7 +425,7 @@ AudioStreamInDump::AudioStreamInDump(AudioDumpInterface *interface, uint32_t sampleRate) : mInterface(interface), mId(id), mSampleRate(sampleRate), mFormat(format), mChannels(channels), mDevice(devices), - mBufferSize(1024), mFinalStream(finalStream), mInFile(0) + mBufferSize(1024), mFinalStream(finalStream), mFile(0), mFileCount(0) { LOGV("AudioStreamInDump Constructor %p, mInterface %p, mFinalStream %p", this, mInterface, mFinalStream); } @@ -409,55 +438,68 @@ AudioStreamInDump::~AudioStreamInDump() ssize_t AudioStreamInDump::read(void* buffer, ssize_t bytes) { - if (mFinalStream) { - return mFinalStream->read(buffer, bytes); - } - - usleep((bytes * 1000000) / frameSize() / sampleRate()); + ssize_t ret; - if(!mInFile) { - char name[255]; - strcpy(name, "/sdcard/music/sine440"); - if (channels() == AudioSystem::CHANNEL_IN_MONO) { - strcat(name, "_mo"); - } else { - strcat(name, "_st"); + if (mFinalStream) { + ret = mFinalStream->read(buffer, bytes); + if(!mFile) { + if (mInterface->fileName() != "") { + char name[255]; + sprintf(name, "%s_in_%d_%d.pcm", mInterface->fileName().string(), mId, ++mFileCount); + mFile = fopen(name, "wb"); + LOGV("Opening input dump file %s, fh %p", name, mFile); + } } - if (format() == AudioSystem::PCM_16_BIT) { - strcat(name, "_16b"); - } else { - strcat(name, "_8b"); + if (mFile) { + fwrite(buffer, bytes, 1, mFile); } - if (sampleRate() < 16000) { - strcat(name, "_8k"); - } else if (sampleRate() < 32000) { - strcat(name, "_22k"); - } else if (sampleRate() < 48000) { - strcat(name, "_44k"); - } else { - strcat(name, "_48k"); - } - strcat(name, ".wav"); - mInFile = fopen(name, "rb"); - LOGV("Opening dump file %s, fh %p", name, mInFile); - if (mInFile) { - fseek(mInFile, AUDIO_DUMP_WAVE_HDR_SIZE, SEEK_SET); + } else { + usleep((((bytes * 1000) / frameSize()) / sampleRate()) * 1000); + ret = bytes; + if(!mFile) { + char name[255]; + strcpy(name, "/sdcard/music/sine440"); + if (channels() == AudioSystem::CHANNEL_IN_MONO) { + strcat(name, "_mo"); + } else { + strcat(name, "_st"); + } + if (format() == AudioSystem::PCM_16_BIT) { + strcat(name, "_16b"); + } else { + strcat(name, "_8b"); + } + if (sampleRate() < 16000) { + strcat(name, "_8k"); + } else if (sampleRate() < 32000) { + strcat(name, "_22k"); + } else if (sampleRate() < 48000) { + strcat(name, "_44k"); + } else { + strcat(name, "_48k"); + } + strcat(name, ".wav"); + mFile = fopen(name, "rb"); + LOGV("Opening input read file %s, fh %p", name, mFile); + if (mFile) { + fseek(mFile, AUDIO_DUMP_WAVE_HDR_SIZE, SEEK_SET); + } } - - } - if (mInFile) { - ssize_t bytesRead = fread(buffer, bytes, 1, mInFile); - if (bytesRead != bytes) { - fseek(mInFile, AUDIO_DUMP_WAVE_HDR_SIZE, SEEK_SET); - fread((uint8_t *)buffer+bytesRead, bytes-bytesRead, 1, mInFile); + if (mFile) { + ssize_t bytesRead = fread(buffer, bytes, 1, mFile); + if (bytesRead >=0 && bytesRead < bytes) { + fseek(mFile, AUDIO_DUMP_WAVE_HDR_SIZE, SEEK_SET); + fread((uint8_t *)buffer+bytesRead, bytes-bytesRead, 1, mFile); + } } } - return bytes; + + return ret; } status_t AudioStreamInDump::standby() { - LOGV("AudioStreamInDump standby(), mInFile %p, mFinalStream %p", mInFile, mFinalStream); + LOGV("AudioStreamInDump standby(), mFile %p, mFinalStream %p", mFile, mFinalStream); Close(); if (mFinalStream != 0 ) return mFinalStream->standby(); @@ -523,9 +565,9 @@ status_t AudioStreamInDump::dump(int fd, const Vector<String16>& args) void AudioStreamInDump::Close() { - if(mInFile) { - fclose(mInFile); - mInFile = 0; + if(mFile) { + fclose(mFile); + mFile = 0; } } }; // namespace android diff --git a/services/audioflinger/AudioDumpInterface.h b/services/audioflinger/AudioDumpInterface.h index 4c62b3e..814ce5f 100644 --- a/services/audioflinger/AudioDumpInterface.h +++ b/services/audioflinger/AudioDumpInterface.h @@ -69,7 +69,7 @@ private: uint32_t mDevice; // current device this output is routed to size_t mBufferSize; AudioStreamOut *mFinalStream; - FILE *mOutFile; // output file + FILE *mFile; // output file int mFileCount; }; @@ -109,7 +109,8 @@ private: uint32_t mDevice; // current device this output is routed to size_t mBufferSize; AudioStreamIn *mFinalStream; - FILE *mInFile; // output file + FILE *mFile; // output file + int mFileCount; }; class AudioDumpInterface : public AudioHardwareBase @@ -134,6 +135,8 @@ public: virtual status_t setMasterVolume(float volume) {return mFinalInterface->setMasterVolume(volume);} + virtual status_t setMode(int mode); + // mic mute virtual status_t setMicMute(bool state) {return mFinalInterface->setMicMute(state);} @@ -143,6 +146,8 @@ public: virtual status_t setParameters(const String8& keyValuePairs); virtual String8 getParameters(const String8& keys); + virtual size_t getInputBufferSize(uint32_t sampleRate, int format, int channelCount); + virtual AudioStreamIn* openInputStream(uint32_t devices, int *format, uint32_t *channels, uint32_t *sampleRate, status_t *status, AudioSystem::audio_in_acoustics acoustics); virtual void closeInputStream(AudioStreamIn* in); @@ -153,8 +158,7 @@ public: protected: AudioHardwareInterface *mFinalInterface; - SortedVector<AudioStreamOutDump *> mOutputs; - bool mFirstHwOutput; + SortedVector<AudioStreamOutDump *> mOutputs; SortedVector<AudioStreamInDump *> mInputs; Mutex mLock; String8 mPolicyCommands; diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp index 2414e8d..cd9b07e 100644 --- a/services/audioflinger/AudioFlinger.cpp +++ b/services/audioflinger/AudioFlinger.cpp @@ -37,7 +37,7 @@ #include <media/AudioRecord.h> #include <private/media/AudioTrackShared.h> - +#include <private/media/AudioEffectShared.h> #include <hardware_legacy/AudioHardwareInterface.h> #include "AudioMixer.h" @@ -51,6 +51,9 @@ #include "lifevibes.h" #endif +#include <media/EffectsFactoryApi.h> +#include <media/EffectVisualizerApi.h> + // ---------------------------------------------------------------------------- // the sim build doesn't have gettid @@ -60,6 +63,8 @@ // ---------------------------------------------------------------------------- +extern const char * const gEffectLibPath; + namespace android { static const char* kDeadlockedString = "AudioFlinger may be deadlocked\n"; @@ -67,6 +72,7 @@ static const char* kHardwareLockedString = "Hardware lock is taken\n"; //static const nsecs_t kStandbyTimeInNsecs = seconds(3); static const float MAX_GAIN = 4096.0f; +static const float MAX_GAIN_INT = 0x1000; // retry counts for buffer fill timeout // 50 * ~20msecs = 1 second @@ -123,7 +129,7 @@ static bool settingsAllowed() { AudioFlinger::AudioFlinger() : BnAudioFlinger(), - mAudioHardware(0), mMasterVolume(1.0f), mMasterMute(false), mNextThreadId(0) + mAudioHardware(0), mMasterVolume(1.0f), mMasterMute(false), mNextUniqueId(1) { mHardwareStatus = AUDIO_HW_IDLE; @@ -132,8 +138,8 @@ AudioFlinger::AudioFlinger() mHardwareStatus = AUDIO_HW_INIT; if (mAudioHardware->initCheck() == NO_ERROR) { // open 16-bit output stream for s/w mixer - - setMode(AudioSystem::MODE_NORMAL); + mMode = AudioSystem::MODE_NORMAL; + setMode(mMode); setMasterVolume(1.0f); setMasterMute(false); @@ -142,6 +148,7 @@ AudioFlinger::AudioFlinger() } #ifdef LVMX LifeVibes::init(); + mLifeVibesClientPid = -1; #endif } @@ -281,6 +288,7 @@ sp<IAudioTrack> AudioFlinger::createTrack( uint32_t flags, const sp<IMemory>& sharedBuffer, int output, + int *sessionId, status_t *status) { sp<PlaybackThread::Track> track; @@ -288,6 +296,7 @@ sp<IAudioTrack> AudioFlinger::createTrack( sp<Client> client; wp<Client> wclient; status_t lStatus; + int lSessionId; if (streamType >= AudioSystem::NUM_STREAM_TYPES) { LOGE("invalid stream type"); @@ -298,6 +307,7 @@ sp<IAudioTrack> AudioFlinger::createTrack( { Mutex::Autolock _l(mLock); PlaybackThread *thread = checkPlaybackThread_l(output); + PlaybackThread *effectThread = NULL; if (thread == NULL) { LOGE("unknown output thread"); lStatus = BAD_VALUE; @@ -312,8 +322,44 @@ sp<IAudioTrack> AudioFlinger::createTrack( client = new Client(this, pid); mClients.add(pid, client); } + + LOGV("createTrack() sessionId: %d", (sessionId == NULL) ? -2 : *sessionId); + if (sessionId != NULL && *sessionId != AudioSystem::SESSION_OUTPUT_MIX) { + for (size_t i = 0; i < mPlaybackThreads.size(); i++) { + sp<PlaybackThread> t = mPlaybackThreads.valueAt(i); + if (mPlaybackThreads.keyAt(i) != output) { + // prevent same audio session on different output threads + uint32_t sessions = t->hasAudioSession(*sessionId); + if (sessions & PlaybackThread::TRACK_SESSION) { + lStatus = BAD_VALUE; + goto Exit; + } + // check if an effect with same session ID is waiting for a track to be created + if (sessions & PlaybackThread::EFFECT_SESSION) { + effectThread = t.get(); + } + } + } + lSessionId = *sessionId; + } else { + // if no audio session id is provided, create one here + lSessionId = nextUniqueId(); + if (sessionId != NULL) { + *sessionId = lSessionId; + } + } + LOGV("createTrack() lSessionId: %d", lSessionId); + track = thread->createTrack_l(client, streamType, sampleRate, format, - channelCount, frameCount, sharedBuffer, &lStatus); + channelCount, frameCount, sharedBuffer, lSessionId, &lStatus); + + // move effect chain to this output thread if an effect on same session was waiting + // for a track to be created + if (lStatus == NO_ERROR && effectThread != NULL) { + Mutex::Autolock _dl(thread->mLock); + Mutex::Autolock _sl(effectThread->mLock); + moveEffectChain_l(lSessionId, effectThread, thread, true); + } } if (lStatus == NO_ERROR) { trackHandle = new TrackHandle(track); @@ -410,6 +456,8 @@ status_t AudioFlinger::setMasterVolume(float value) status_t AudioFlinger::setMode(int mode) { + status_t ret; + // check calling permissions if (!settingsAllowed()) { return PERMISSION_DENIED; @@ -419,15 +467,23 @@ status_t AudioFlinger::setMode(int mode) return BAD_VALUE; } - AutoMutex lock(mHardwareLock); - mHardwareStatus = AUDIO_HW_SET_MODE; - status_t ret = mAudioHardware->setMode(mode); -#ifdef LVMX + { // scope for the lock + AutoMutex lock(mHardwareLock); + mHardwareStatus = AUDIO_HW_SET_MODE; + ret = mAudioHardware->setMode(mode); + mHardwareStatus = AUDIO_HW_IDLE; + } + if (NO_ERROR == ret) { + Mutex::Autolock _l(mLock); + mMode = mode; + for (uint32_t i = 0; i < mPlaybackThreads.size(); i++) + mPlaybackThreads.valueAt(i)->setMode(mode); +#ifdef LVMX LifeVibes::setMode(mode); - } #endif - mHardwareStatus = AUDIO_HW_IDLE; + } + return ret; } @@ -596,8 +652,10 @@ status_t AudioFlinger::setParameters(int ioHandle, const String8& keyValuePairs) 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; } } @@ -609,7 +667,7 @@ status_t AudioFlinger::setParameters(int ioHandle, const String8& keyValuePairs) mHardwareStatus = AUDIO_SET_PARAMETER; result = mAudioHardware->setParameters(keyValuePairs); #ifdef LVMX - if ((NO_ERROR == result) && (musicEnabled != -1)) { + if (musicEnabled != -1) { LifeVibes::enableMusic((bool) musicEnabled); } #endif @@ -713,51 +771,57 @@ status_t AudioFlinger::getRenderPosition(uint32_t *halFrames, uint32_t *dspFrame void AudioFlinger::registerClient(const sp<IAudioFlingerClient>& client) { - LOGV("registerClient() %p, tid %d, calling tid %d", client.get(), gettid(), IPCThreadState::self()->getCallingPid()); Mutex::Autolock _l(mLock); - sp<IBinder> binder = client->asBinder(); - if (mNotificationClients.indexOf(binder) < 0) { - LOGV("Adding notification client %p", binder.get()); - binder->linkToDeath(this); - mNotificationClients.add(binder); - } + int pid = IPCThreadState::self()->getCallingPid(); + if (mNotificationClients.indexOfKey(pid) < 0) { + sp<NotificationClient> notificationClient = new NotificationClient(this, + client, + pid); + LOGV("registerClient() client %p, pid %d", notificationClient.get(), pid); - // the config change is always sent from playback or record threads to avoid deadlock - // with AudioSystem::gLock - for (size_t i = 0; i < mPlaybackThreads.size(); i++) { - mPlaybackThreads.valueAt(i)->sendConfigEvent(AudioSystem::OUTPUT_OPENED); - } + mNotificationClients.add(pid, notificationClient); - for (size_t i = 0; i < mRecordThreads.size(); i++) { - mRecordThreads.valueAt(i)->sendConfigEvent(AudioSystem::INPUT_OPENED); + sp<IBinder> binder = client->asBinder(); + binder->linkToDeath(notificationClient); + + // the config change is always sent from playback or record threads to avoid deadlock + // with AudioSystem::gLock + for (size_t i = 0; i < mPlaybackThreads.size(); i++) { + mPlaybackThreads.valueAt(i)->sendConfigEvent(AudioSystem::OUTPUT_OPENED); + } + + for (size_t i = 0; i < mRecordThreads.size(); i++) { + mRecordThreads.valueAt(i)->sendConfigEvent(AudioSystem::INPUT_OPENED); + } } } -void AudioFlinger::binderDied(const wp<IBinder>& who) { - - LOGV("binderDied() %p, tid %d, calling tid %d", who.unsafe_get(), gettid(), IPCThreadState::self()->getCallingPid()); +void AudioFlinger::removeNotificationClient(pid_t pid) +{ Mutex::Autolock _l(mLock); - IBinder *binder = who.unsafe_get(); - - if (binder != NULL) { - int index = mNotificationClients.indexOf(binder); - if (index >= 0) { - LOGV("Removing notification client %p", binder); - mNotificationClients.removeAt(index); + int index = mNotificationClients.indexOfKey(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); } } // audioConfigChanged_l() must be called with AudioFlinger::mLock held -void AudioFlinger::audioConfigChanged_l(int event, int ioHandle, void *param2) { +void AudioFlinger::audioConfigChanged_l(int event, int ioHandle, void *param2) +{ size_t size = mNotificationClients.size(); for (size_t i = 0; i < size; i++) { - sp<IBinder> binder = mNotificationClients.itemAt(i); - LOGV("audioConfigChanged_l() Notifying change to client %p", binder.get()); - sp<IAudioFlingerClient> client = interface_cast<IAudioFlingerClient> (binder); - client->ioConfigChanged(event, ioHandle, param2); + mNotificationClients.valueAt(i)->client()->ioConfigChanged(event, ioHandle, param2); } } @@ -768,12 +832,13 @@ void AudioFlinger::removeClient_l(pid_t pid) mClients.removeItem(pid); } + // ---------------------------------------------------------------------------- AudioFlinger::ThreadBase::ThreadBase(const sp<AudioFlinger>& audioFlinger, int id) : Thread(false), mAudioFlinger(audioFlinger), mSampleRate(0), mFrameCount(0), mChannelCount(0), - mFormat(0), mFrameSize(1), mStandby(false), mId(id), mExiting(false) + mFrameSize(1), mFormat(0), mStandby(false), mId(id), mExiting(false) { } @@ -806,7 +871,7 @@ uint32_t AudioFlinger::ThreadBase::sampleRate() const int AudioFlinger::ThreadBase::channelCount() const { - return mChannelCount; + return (int)mChannelCount; } int AudioFlinger::ThreadBase::format() const @@ -863,11 +928,12 @@ void AudioFlinger::ThreadBase::processConfigEvents() LOGV("processConfigEvents() remaining events %d", mConfigEvents.size()); ConfigEvent *configEvent = mConfigEvents[0]; mConfigEvents.removeAt(0); - // release mLock because audioConfigChanged() will lock AudioFlinger mLock - // before calling Audioflinger::audioConfigChanged_l() thus creating - // potential cross deadlock between AudioFlinger::mLock and mLock + // release mLock before locking AudioFlinger mLock: lock order is always + // AudioFlinger then ThreadBase to avoid cross deadlock mLock.unlock(); - audioConfigChanged(configEvent->mEvent, configEvent->mParam); + mAudioFlinger->mLock.lock(); + audioConfigChanged_l(configEvent->mEvent, configEvent->mParam); + mAudioFlinger->mLock.unlock(); delete configEvent; mLock.lock(); } @@ -929,10 +995,11 @@ status_t AudioFlinger::ThreadBase::dumpBase(int fd, const Vector<String16>& args // ---------------------------------------------------------------------------- -AudioFlinger::PlaybackThread::PlaybackThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id) +AudioFlinger::PlaybackThread::PlaybackThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id, uint32_t device) : ThreadBase(audioFlinger, id), mMixBuffer(0), mSuspended(0), mBytesWritten(0), mOutput(output), - mLastWriteTime(0), mNumWrites(0), mNumDelayedWrites(0), mInWrite(false) + mLastWriteTime(0), mNumWrites(0), mNumDelayedWrites(0), mInWrite(false), + mDevice(device) { readOutputParameters(); @@ -943,8 +1010,6 @@ AudioFlinger::PlaybackThread::PlaybackThread(const sp<AudioFlinger>& audioFlinge mStreamTypes[stream].volume = mAudioFlinger->streamVolumeInternal(stream); mStreamTypes[stream].mute = mAudioFlinger->streamMute(stream); } - // notify client processes that a new input has been opened - sendConfigEvent(AudioSystem::OUTPUT_OPENED); } AudioFlinger::PlaybackThread::~PlaybackThread() @@ -956,6 +1021,7 @@ status_t AudioFlinger::PlaybackThread::dump(int fd, const Vector<String16>& args { dumpInternals(fd, args); dumpTracks(fd, args); + dumpEffectChains(fd, args); return NO_ERROR; } @@ -967,7 +1033,7 @@ status_t AudioFlinger::PlaybackThread::dumpTracks(int fd, const Vector<String16> snprintf(buffer, SIZE, "Output thread %p tracks\n", this); result.append(buffer); - result.append(" Name Clien Typ Fmt Chn Buf S M F SRate LeftV RighV Serv User\n"); + result.append(" Name Clien Typ Fmt Chn Session Buf S M F SRate LeftV RighV Serv User Main buf Aux Buf\n"); for (size_t i = 0; i < mTracks.size(); ++i) { sp<Track> track = mTracks[i]; if (track != 0) { @@ -978,7 +1044,7 @@ status_t AudioFlinger::PlaybackThread::dumpTracks(int fd, const Vector<String16> snprintf(buffer, SIZE, "Output thread %p active tracks\n", this); result.append(buffer); - result.append(" Name Clien Typ Fmt Chn Buf S M F SRate LeftV RighV Serv User\n"); + result.append(" Name Clien Typ Fmt Chn Session Buf S M F SRate LeftV RighV Serv User Main buf Aux Buf\n"); for (size_t i = 0; i < mActiveTracks.size(); ++i) { wp<Track> wTrack = mActiveTracks[i]; if (wTrack != 0) { @@ -993,6 +1059,24 @@ status_t AudioFlinger::PlaybackThread::dumpTracks(int fd, const Vector<String16> return NO_ERROR; } +status_t AudioFlinger::PlaybackThread::dumpEffectChains(int fd, const Vector<String16>& args) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + snprintf(buffer, SIZE, "\n- %d Effect Chains:\n", mEffectChains.size()); + write(fd, buffer, strlen(buffer)); + + for (size_t i = 0; i < mEffectChains.size(); ++i) { + sp<EffectChain> chain = mEffectChains[i]; + if (chain != 0) { + chain->dump(fd, args); + } + } + return NO_ERROR; +} + status_t AudioFlinger::PlaybackThread::dumpInternals(int fd, const Vector<String16>& args) { const size_t SIZE = 256; @@ -1011,6 +1095,8 @@ status_t AudioFlinger::PlaybackThread::dumpInternals(int fd, const Vector<String result.append(buffer); snprintf(buffer, SIZE, "suspend count: %d\n", mSuspended); result.append(buffer); + snprintf(buffer, SIZE, "mix buffer : %p\n", mMixBuffer); + result.append(buffer); write(fd, result.string(), result.size()); dumpBase(fd, args); @@ -1048,13 +1134,14 @@ sp<AudioFlinger::PlaybackThread::Track> AudioFlinger::PlaybackThread::createTra int channelCount, int frameCount, const sp<IMemory>& sharedBuffer, + int sessionId, status_t *status) { sp<Track> track; status_t lStatus; if (mType == DIRECT) { - if (sampleRate != mSampleRate || format != mFormat || channelCount != mChannelCount) { + if (sampleRate != mSampleRate || format != mFormat || channelCount != (int)mChannelCount) { LOGE("createTrack_l() Bad parameter: sampleRate %d format %d, channelCount %d for output %p", sampleRate, format, channelCount, mOutput); lStatus = BAD_VALUE; @@ -1077,13 +1164,37 @@ sp<AudioFlinger::PlaybackThread::Track> AudioFlinger::PlaybackThread::createTra { // scope for mLock Mutex::Autolock _l(mLock); + + // all tracks in same audio session must share the same routing strategy otherwise + // conflicts will happen when tracks are moved from one output to another by audio policy + // manager + uint32_t strategy = + AudioSystem::getStrategyForStream((AudioSystem::stream_type)streamType); + for (size_t i = 0; i < mTracks.size(); ++i) { + sp<Track> t = mTracks[i]; + if (t != 0) { + if (sessionId == t->sessionId() && + strategy != AudioSystem::getStrategyForStream((AudioSystem::stream_type)t->type())) { + lStatus = BAD_VALUE; + goto Exit; + } + } + } + track = new Track(this, client, streamType, sampleRate, format, - channelCount, frameCount, sharedBuffer); + channelCount, frameCount, sharedBuffer, sessionId); if (track->getCblk() == NULL || track->name() < 0) { lStatus = NO_MEMORY; goto Exit; } mTracks.add(track); + + sp<EffectChain> chain = getEffectChain_l(sessionId); + if (chain != 0) { + LOGV("createTrack_l() setting main buffer %p", chain->inBuffer()); + track->setMainBuffer(chain->inBuffer()); + chain->setStrategy(AudioSystem::getStrategyForStream((AudioSystem::stream_type)track->type())); + } } lStatus = NO_ERROR; @@ -1200,6 +1311,14 @@ status_t AudioFlinger::PlaybackThread::addTrack_l(const sp<Track>& track) track->mFillingUpStatus = Track::FS_FILLING; track->mResetDone = false; mActiveTracks.add(track); + if (track->mainBuffer() != mMixBuffer) { + sp<EffectChain> chain = getEffectChain_l(track->sessionId()); + if (chain != 0) { + LOGV("addTrack_l() starting track on chain %p for session %d", chain.get(), track->sessionId()); + chain->startTrack(); + } + } + status = NO_ERROR; } @@ -1224,16 +1343,17 @@ String8 AudioFlinger::PlaybackThread::getParameters(const String8& keys) return mOutput->getParameters(keys); } -void AudioFlinger::PlaybackThread::audioConfigChanged(int event, int param) { +// destroyTrack_l() must be called with AudioFlinger::mLock held +void AudioFlinger::PlaybackThread::audioConfigChanged_l(int event, int param) { AudioSystem::OutputDescriptor desc; void *param2 = 0; - LOGV("PlaybackThread::audioConfigChanged, thread %p, event %d, param %d", this, event, param); + LOGV("PlaybackThread::audioConfigChanged_l, thread %p, event %d, param %d", this, event, param); switch (event) { case AudioSystem::OUTPUT_OPENED: case AudioSystem::OUTPUT_CONFIG_CHANGED: - desc.channels = mChannelCount; + desc.channels = mChannels; desc.samplingRate = mSampleRate; desc.format = mFormat; desc.frameCount = mFrameCount; @@ -1247,24 +1367,34 @@ void AudioFlinger::PlaybackThread::audioConfigChanged(int event, int param) { default: break; } - Mutex::Autolock _l(mAudioFlinger->mLock); mAudioFlinger->audioConfigChanged_l(event, mId, param2); } void AudioFlinger::PlaybackThread::readOutputParameters() { mSampleRate = mOutput->sampleRate(); - mChannelCount = AudioSystem::popCount(mOutput->channels()); - + mChannels = mOutput->channels(); + mChannelCount = (uint16_t)AudioSystem::popCount(mChannels); mFormat = mOutput->format(); - mFrameSize = mOutput->frameSize(); + mFrameSize = (uint16_t)mOutput->frameSize(); mFrameCount = mOutput->bufferSize() / mFrameSize; // FIXME - Current mixer implementation only supports stereo output: Always // Allocate a stereo buffer even if HW output is mono. - if (mMixBuffer != NULL) delete mMixBuffer; + if (mMixBuffer != NULL) delete[] mMixBuffer; mMixBuffer = new int16_t[mFrameCount * 2]; memset(mMixBuffer, 0, mFrameCount * 2 * sizeof(int16_t)); + + // force reconfiguration of effect chains and engines to take new buffer size and audio + // parameters into account + // Note that mLock is not held when readOutputParameters() is called from the constructor + // but in this case nothing is done below as no audio sessions have effect yet so it doesn't + // matter. + // create a copy of mEffectChains as calling moveEffectChain_l() can reorder some effect chains + Vector< sp<EffectChain> > effectChains = mEffectChains; + for (size_t i = 0; i < effectChains.size(); i ++) { + mAudioFlinger->moveEffectChain_l(effectChains[i]->sessionId(), this, this, false); + } } status_t AudioFlinger::PlaybackThread::getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames) @@ -1280,10 +1410,76 @@ status_t AudioFlinger::PlaybackThread::getRenderPosition(uint32_t *halFrames, ui return mOutput->getRenderPosition(dspFrames); } +uint32_t AudioFlinger::PlaybackThread::hasAudioSession(int sessionId) +{ + Mutex::Autolock _l(mLock); + uint32_t result = 0; + if (getEffectChain_l(sessionId) != 0) { + result = EFFECT_SESSION; + } + + for (size_t i = 0; i < mTracks.size(); ++i) { + sp<Track> track = mTracks[i]; + if (sessionId == track->sessionId() && + !(track->mCblk->flags & CBLK_INVALID_MSK)) { + result |= TRACK_SESSION; + break; + } + } + + return result; +} + +uint32_t AudioFlinger::PlaybackThread::getStrategyForSession_l(int sessionId) +{ + // session AudioSystem::SESSION_OUTPUT_MIX is placed in same strategy as MUSIC stream so that + // it is moved to correct output by audio policy manager when A2DP is connected or disconnected + if (sessionId == AudioSystem::SESSION_OUTPUT_MIX) { + return AudioSystem::getStrategyForStream(AudioSystem::MUSIC); + } + for (size_t i = 0; i < mTracks.size(); i++) { + sp<Track> track = mTracks[i]; + if (sessionId == track->sessionId() && + !(track->mCblk->flags & CBLK_INVALID_MSK)) { + return AudioSystem::getStrategyForStream((AudioSystem::stream_type) track->type()); + } + } + return AudioSystem::getStrategyForStream(AudioSystem::MUSIC); +} + +sp<AudioFlinger::EffectChain> AudioFlinger::PlaybackThread::getEffectChain(int sessionId) +{ + Mutex::Autolock _l(mLock); + return getEffectChain_l(sessionId); +} + +sp<AudioFlinger::EffectChain> AudioFlinger::PlaybackThread::getEffectChain_l(int sessionId) +{ + sp<EffectChain> chain; + + size_t size = mEffectChains.size(); + for (size_t i = 0; i < size; i++) { + if (mEffectChains[i]->sessionId() == sessionId) { + chain = mEffectChains[i]; + break; + } + } + return chain; +} + +void AudioFlinger::PlaybackThread::setMode(uint32_t mode) +{ + Mutex::Autolock _l(mLock); + size_t size = mEffectChains.size(); + for (size_t i = 0; i < size; i++) { + mEffectChains[i]->setMode_l(mode); + } +} + // ---------------------------------------------------------------------------- -AudioFlinger::MixerThread::MixerThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id) - : PlaybackThread(audioFlinger, output, id), +AudioFlinger::MixerThread::MixerThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id, uint32_t device) + : PlaybackThread(audioFlinger, output, id, device), mAudioMixer(0) { mType = PlaybackThread::MIXER; @@ -1302,7 +1498,6 @@ AudioFlinger::MixerThread::~MixerThread() bool AudioFlinger::MixerThread::threadLoop() { - int16_t* curBuf = mMixBuffer; Vector< sp<Track> > tracksToRemove; uint32_t mixerStatus = MIXER_IDLE; nsecs_t standbyTime = systemTime(); @@ -1315,6 +1510,7 @@ bool AudioFlinger::MixerThread::threadLoop() uint32_t activeSleepTime = activeSleepTimeUs(); uint32_t idleSleepTime = idleSleepTimeUs(); uint32_t sleepTime = idleSleepTime; + Vector< sp<EffectChain> > effectChains; while (!exitPending()) { @@ -1373,13 +1569,19 @@ bool AudioFlinger::MixerThread::threadLoop() } mixerStatus = prepareTracks_l(activeTracks, &tracksToRemove); + + // prevent any changes in effect chain list and in each effect chain + // during mixing and effect process as the audio buffers could be deleted + // or modified if an effect is created or deleted + lockEffectChains_l(effectChains); } if (LIKELY(mixerStatus == MIXER_TRACKS_READY)) { // mix buffers... - mAudioMixer->process(curBuf); + mAudioMixer->process(); sleepTime = 0; standbyTime = systemTime() + kStandbyTimeInNsecs; + //TODO: delay standby when effects have a tail } else { // If no tracks are ready, sleep once for the duration of an output // buffer size, then write 0s to the output @@ -1391,27 +1593,34 @@ bool AudioFlinger::MixerThread::threadLoop() } } else if (mBytesWritten != 0 || (mixerStatus == MIXER_TRACKS_ENABLED && longStandbyExit)) { - memset (curBuf, 0, mixBufferSize); + memset (mMixBuffer, 0, mixBufferSize); sleepTime = 0; LOGV_IF((mBytesWritten == 0 && (mixerStatus == MIXER_TRACKS_ENABLED && longStandbyExit)), "anticipated start"); } + // TODO add standby time extension fct of effect tail } if (mSuspended) { - sleepTime = idleSleepTime; + sleepTime = suspendSleepTimeUs(); } // sleepTime == 0 means we must write to audio hardware if (sleepTime == 0) { - mLastWriteTime = systemTime(); - mInWrite = true; - mBytesWritten += mixBufferSize; + for (size_t i = 0; i < effectChains.size(); i ++) { + effectChains[i]->process_l(); + } + // enable changes in effect chain + unlockEffectChains(effectChains); #ifdef LVMX int audioOutputType = LifeVibes::getMixerType(mId, mType); if (LifeVibes::audioOutputTypeIsLifeVibes(audioOutputType)) { - LifeVibes::process(audioOutputType, curBuf, mixBufferSize); + LifeVibes::process(audioOutputType, mMixBuffer, mixBufferSize); } #endif - int bytesWritten = (int)mOutput->write(curBuf, mixBufferSize); + mLastWriteTime = systemTime(); + mInWrite = true; + mBytesWritten += mixBufferSize; + + int bytesWritten = (int)mOutput->write(mMixBuffer, mixBufferSize); if (bytesWritten < 0) mBytesWritten -= mixBufferSize; mNumWrites++; mInWrite = false; @@ -1430,6 +1639,8 @@ bool AudioFlinger::MixerThread::threadLoop() } mStandby = false; } else { + // enable changes in effect chain + unlockEffectChains(effectChains); usleep(sleepTime); } @@ -1437,6 +1648,10 @@ bool AudioFlinger::MixerThread::threadLoop() // since we can't guarantee the destructors won't acquire that // same lock. tracksToRemove.clear(); + + // Effect chains will be actually deleted here if they were removed from + // mEffectChains list during mixing or effects processing + effectChains.clear(); } if (!mStandby) { @@ -1454,10 +1669,15 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track uint32_t mixerStatus = MIXER_IDLE; // find out which tracks need to be processed size_t count = activeTracks.size(); + size_t mixedTracks = 0; + size_t tracksWithEffect = 0; float masterVolume = mMasterVolume; bool masterMute = mMasterMute; + if (masterMute) { + masterVolume = 0; + } #ifdef LVMX bool tracksConnectedChanged = false; bool stateChanged = false; @@ -1476,6 +1696,14 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track 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) { + uint32_t v = (uint32_t)(masterVolume * (1 << 24)); + chain->setVolume_l(&v, &v); + masterVolume = (float)((v + (1 << 23)) >> 24); + chain.clear(); + } for (size_t i=0 ; i<count ; i++) { sp<Track> t = activeTracks[i].promote(); @@ -1487,20 +1715,52 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track // The first time a track is added we wait // for all its buffers to be filled before processing it mAudioMixer->setActiveTrack(track->name()); - if (cblk->framesReady() && (track->isReady() || track->isStopped()) && + if (cblk->framesReady() && track->isReady() && !track->isPaused() && !track->isTerminated()) { //LOGV("track %d u=%08x, s=%08x [OK] on thread %p", track->name(), cblk->user, cblk->server, this); + mixedTracks++; + + // track->mainBuffer() != mMixBuffer means there is an effect chain + // connected to the track + chain.clear(); + if (track->mainBuffer() != mMixBuffer) { + chain = getEffectChain_l(track->sessionId()); + // Delegate volume control to effect in track effect chain if needed + if (chain != 0) { + tracksWithEffect++; + } else { + LOGW("prepareTracks_l(): track %08x attached to effect but no chain found on session %d", + track->name(), track->sessionId()); + } + } + + + int param = AudioMixer::VOLUME; + if (track->mFillingUpStatus == Track::FS_FILLED) { + // no ramp for the first volume setting + track->mFillingUpStatus = Track::FS_ACTIVE; + if (track->mState == TrackBase::RESUMING) { + track->mState = TrackBase::ACTIVE; + param = AudioMixer::RAMP_VOLUME; + } + } else if (cblk->server != 0) { + // If the track is stopped before the first frame was mixed, + // do not apply ramp + param = AudioMixer::RAMP_VOLUME; + } + // compute volume for this track - int16_t left, right; - if (track->isMuted() || masterMute || track->isPausing() || + uint32_t vl, vr, va; + if (track->isMuted() || track->isPausing() || mStreamTypes[track->type()].mute) { - left = right = 0; + vl = vr = va = 0; if (track->isPausing()) { track->setPaused(); } } else { + // read original volumes with volume control float typeVolume = mStreamTypes[track->type()].volume; #ifdef LVMX @@ -1515,31 +1775,37 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track } #endif float v = masterVolume * typeVolume; - float v_clamped = v * cblk->volume[0]; - if (v_clamped > MAX_GAIN) v_clamped = MAX_GAIN; - left = int16_t(v_clamped); - v_clamped = v * cblk->volume[1]; - if (v_clamped > MAX_GAIN) v_clamped = MAX_GAIN; - right = int16_t(v_clamped); - } - - // XXX: these things DON'T need to be done each time - mAudioMixer->setBufferProvider(track); - mAudioMixer->enable(AudioMixer::MIXING); + vl = (uint32_t)(v * cblk->volume[0]) << 12; + vr = (uint32_t)(v * cblk->volume[1]) << 12; - int param = AudioMixer::VOLUME; - if (track->mFillingUpStatus == Track::FS_FILLED) { - // no ramp for the first volume setting - track->mFillingUpStatus = Track::FS_ACTIVE; - if (track->mState == TrackBase::RESUMING) { - track->mState = TrackBase::ACTIVE; - param = AudioMixer::RAMP_VOLUME; + va = (uint32_t)(v * cblk->sendLevel); + } + // Delegate volume control to effect in track effect chain if needed + if (chain != 0 && chain->setVolume_l(&vl, &vr)) { + // Do not ramp volume if volume is controlled by effect + param = AudioMixer::VOLUME; + track->mHasVolumeController = true; + } else { + // force no volume ramp when volume controller was just disabled or removed + // from effect chain to avoid volume spike + if (track->mHasVolumeController) { + param = AudioMixer::VOLUME; } - } else if (cblk->server != 0) { - // If the track is stopped before the first frame was mixed, - // do not apply ramp - param = AudioMixer::RAMP_VOLUME; + track->mHasVolumeController = false; } + + // Convert volumes from 8.24 to 4.12 format + int16_t left, right, aux; + uint32_t v_clamped = (vl + (1 << 11)) >> 12; + if (v_clamped > MAX_GAIN_INT) v_clamped = MAX_GAIN_INT; + left = int16_t(v_clamped); + v_clamped = (vr + (1 << 11)) >> 12; + if (v_clamped > MAX_GAIN_INT) v_clamped = MAX_GAIN_INT; + right = int16_t(v_clamped); + + if (va > MAX_GAIN_INT) va = MAX_GAIN_INT; + aux = int16_t(va); + #ifdef LVMX if ( tracksConnectedChanged || stateChanged ) { @@ -1547,18 +1813,30 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track param = AudioMixer::VOLUME; } #endif - mAudioMixer->setParameter(param, AudioMixer::VOLUME0, left); - mAudioMixer->setParameter(param, AudioMixer::VOLUME1, right); + + // XXX: these things DON'T need to be done each time + mAudioMixer->setBufferProvider(track); + mAudioMixer->enable(AudioMixer::MIXING); + + mAudioMixer->setParameter(param, AudioMixer::VOLUME0, (void *)left); + mAudioMixer->setParameter(param, AudioMixer::VOLUME1, (void *)right); + mAudioMixer->setParameter(param, AudioMixer::AUXLEVEL, (void *)aux); mAudioMixer->setParameter( AudioMixer::TRACK, - AudioMixer::FORMAT, track->format()); + AudioMixer::FORMAT, (void *)track->format()); mAudioMixer->setParameter( AudioMixer::TRACK, - AudioMixer::CHANNEL_COUNT, track->channelCount()); + AudioMixer::CHANNEL_COUNT, (void *)track->channelCount()); mAudioMixer->setParameter( AudioMixer::RESAMPLE, AudioMixer::SAMPLE_RATE, - int(cblk->sampleRate)); + (void *)(cblk->sampleRate)); + mAudioMixer->setParameter( + AudioMixer::TRACK, + AudioMixer::MAIN_BUFFER, (void *)track->mainBuffer()); + mAudioMixer->setParameter( + AudioMixer::TRACK, + AudioMixer::AUX_BUFFER, (void *)track->auxBuffer()); // reset retry count track->mRetryCount = kMaxTrackRetries; @@ -1572,19 +1850,19 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track // We have consumed all the buffers of this track. // Remove it from the list of active tracks. tracksToRemove->add(track); - mAudioMixer->disable(AudioMixer::MIXING); } else { // No buffers for this track. Give it a few chances to // fill a buffer, then remove it from active list. if (--(track->mRetryCount) <= 0) { LOGV("BUFFER TIMEOUT: remove(%d) from active list on thread %p", track->name(), this); tracksToRemove->add(track); + // indicate to client process that the track was disabled because of underrun + cblk->flags |= CBLK_DISABLED_ON; } else if (mixerStatus != MIXER_TRACKS_READY) { mixerStatus = MIXER_TRACKS_ENABLED; } - - mAudioMixer->disable(AudioMixer::MIXING); } + mAudioMixer->disable(AudioMixer::MIXING); } } @@ -1594,6 +1872,13 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track for (size_t i=0 ; i<count ; i++) { const sp<Track>& track = tracksToRemove->itemAt(i); mActiveTracks.remove(track); + if (track->mainBuffer() != mMixBuffer) { + chain = getEffectChain_l(track->sessionId()); + if (chain != 0) { + LOGV("stopping track on chain %p for session Id: %d", chain.get(), track->sessionId()); + chain->stopTrack(); + } + } if (track->isTerminated()) { mTracks.remove(track); deleteTrackName_l(track->mName); @@ -1601,69 +1886,34 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track } } + // mix buffer must be cleared if all tracks are connected to an + // effect chain as in this case the mixer will not write to + // mix buffer and track effects will accumulate into it + if (mixedTracks != 0 && mixedTracks == tracksWithEffect) { + memset(mMixBuffer, 0, mFrameCount * mChannelCount * sizeof(int16_t)); + } + return mixerStatus; } -void AudioFlinger::MixerThread::getTracks( - SortedVector < sp<Track> >& tracks, - SortedVector < wp<Track> >& activeTracks, - int streamType) +void AudioFlinger::MixerThread::invalidateTracks(int streamType) { - LOGV ("MixerThread::getTracks() mixer %p, mTracks.size %d, mActiveTracks.size %d", this, mTracks.size(), mActiveTracks.size()); + LOGV ("MixerThread::invalidateTracks() mixer %p, streamType %d, mTracks.size %d", + this, streamType, mTracks.size()); Mutex::Autolock _l(mLock); + size_t size = mTracks.size(); for (size_t i = 0; i < size; i++) { sp<Track> t = mTracks[i]; if (t->type() == streamType) { - tracks.add(t); - int j = mActiveTracks.indexOf(t); - if (j >= 0) { - t = mActiveTracks[j].promote(); - if (t != NULL) { - activeTracks.add(t); - } - } + t->mCblk->lock.lock(); + t->mCblk->flags |= CBLK_INVALID_ON; + t->mCblk->cv.signal(); + t->mCblk->lock.unlock(); } } - - size = activeTracks.size(); - for (size_t i = 0; i < size; i++) { - mActiveTracks.remove(activeTracks[i]); - } - - size = tracks.size(); - for (size_t i = 0; i < size; i++) { - sp<Track> t = tracks[i]; - mTracks.remove(t); - deleteTrackName_l(t->name()); - } } -void AudioFlinger::MixerThread::putTracks( - SortedVector < sp<Track> >& tracks, - SortedVector < wp<Track> >& activeTracks) -{ - LOGV ("MixerThread::putTracks() mixer %p, tracks.size %d, activeTracks.size %d", this, tracks.size(), activeTracks.size()); - Mutex::Autolock _l(mLock); - size_t size = tracks.size(); - for (size_t i = 0; i < size ; i++) { - sp<Track> t = tracks[i]; - int name = getTrackName_l(); - - if (name < 0) return; - - t->mName = name; - t->mThread = this; - mTracks.add(t); - - int j = activeTracks.indexOf(t); - if (j >= 0) { - mActiveTracks.add(t); - // force buffer refilling and no ramp volume when the track is mixed for the first time - t->mFillingUpStatus = Track::FS_FILLING; - } - } -} // getTrackName_l() must be called with ThreadBase::mLock held int AudioFlinger::MixerThread::getTrackName_l() @@ -1716,6 +1966,15 @@ bool AudioFlinger::MixerThread::checkForNewParameters_l() reconfig = true; } } + if (param.getInt(String8(AudioParameter::keyRouting), value) == NO_ERROR) { + // forward device change to effects that have requested to be + // aware of attached audio device. + mDevice = (uint32_t)value; + for (size_t i = 0; i < mEffectChains.size(); i++) { + mEffectChains[i]->setDevice_l(mDevice); + } + } + if (status == NO_ERROR) { status = mOutput->setParameters(keyValuePair); if (!mStandby && status == INVALID_OPERATION) { @@ -1771,13 +2030,17 @@ uint32_t AudioFlinger::MixerThread::activeSleepTimeUs() uint32_t AudioFlinger::MixerThread::idleSleepTimeUs() { - return (uint32_t)((mFrameCount * 1000) / mSampleRate) * 1000; + return (uint32_t)(((mFrameCount * 1000) / mSampleRate) * 1000) / 2; +} + +uint32_t AudioFlinger::MixerThread::suspendSleepTimeUs() +{ + return (uint32_t)(((mFrameCount * 1000) / mSampleRate) * 1000); } // ---------------------------------------------------------------------------- -AudioFlinger::DirectOutputThread::DirectOutputThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id) - : PlaybackThread(audioFlinger, output, id), - mLeftVolume (1.0), mRightVolume(1.0) +AudioFlinger::DirectOutputThread::DirectOutputThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id, uint32_t device) + : PlaybackThread(audioFlinger, output, id, device) { mType = PlaybackThread::DIRECT; } @@ -1787,6 +2050,102 @@ AudioFlinger::DirectOutputThread::~DirectOutputThread() } +static inline int16_t clamp16(int32_t sample) +{ + if ((sample>>15) ^ (sample>>31)) + sample = 0x7FFF ^ (sample>>31); + return sample; +} + +static inline +int32_t mul(int16_t in, int16_t v) +{ +#if defined(__arm__) && !defined(__thumb__) + int32_t out; + asm( "smulbb %[out], %[in], %[v] \n" + : [out]"=r"(out) + : [in]"%r"(in), [v]"r"(v) + : ); + return out; +#else + return in * int32_t(v); +#endif +} + +void AudioFlinger::DirectOutputThread::applyVolume(uint16_t leftVol, uint16_t rightVol, bool ramp) +{ + // Do not apply volume on compressed audio + if (!AudioSystem::isLinearPCM(mFormat)) { + return; + } + + // convert to signed 16 bit before volume calculation + if (mFormat == AudioSystem::PCM_8_BIT) { + size_t count = mFrameCount * mChannelCount; + uint8_t *src = (uint8_t *)mMixBuffer + count-1; + int16_t *dst = mMixBuffer + count-1; + while(count--) { + *dst-- = (int16_t)(*src--^0x80) << 8; + } + } + + size_t frameCount = mFrameCount; + int16_t *out = mMixBuffer; + if (ramp) { + if (mChannelCount == 1) { + int32_t d = ((int32_t)leftVol - (int32_t)mLeftVolShort) << 16; + int32_t vlInc = d / (int32_t)frameCount; + int32_t vl = ((int32_t)mLeftVolShort << 16); + do { + out[0] = clamp16(mul(out[0], vl >> 16) >> 12); + out++; + vl += vlInc; + } while (--frameCount); + + } else { + int32_t d = ((int32_t)leftVol - (int32_t)mLeftVolShort) << 16; + int32_t vlInc = d / (int32_t)frameCount; + d = ((int32_t)rightVol - (int32_t)mRightVolShort) << 16; + int32_t vrInc = d / (int32_t)frameCount; + int32_t vl = ((int32_t)mLeftVolShort << 16); + int32_t vr = ((int32_t)mRightVolShort << 16); + do { + out[0] = clamp16(mul(out[0], vl >> 16) >> 12); + out[1] = clamp16(mul(out[1], vr >> 16) >> 12); + out += 2; + vl += vlInc; + vr += vrInc; + } while (--frameCount); + } + } else { + if (mChannelCount == 1) { + do { + out[0] = clamp16(mul(out[0], leftVol) >> 12); + out++; + } while (--frameCount); + } else { + do { + out[0] = clamp16(mul(out[0], leftVol) >> 12); + out[1] = clamp16(mul(out[1], rightVol) >> 12); + out += 2; + } while (--frameCount); + } + } + + // convert back to unsigned 8 bit after volume calculation + if (mFormat == AudioSystem::PCM_8_BIT) { + size_t count = mFrameCount * mChannelCount; + int16_t *src = mMixBuffer; + uint8_t *dst = (uint8_t *)mMixBuffer; + while(count--) { + *dst++ = (uint8_t)(((int32_t)*src++ + (1<<7)) >> 8)^0x80; + } + } + + mLeftVolShort = leftVol; + mRightVolShort = rightVol; +} + bool AudioFlinger::DirectOutputThread::threadLoop() { uint32_t mixerStatus = MIXER_IDLE; @@ -1802,9 +2161,13 @@ bool AudioFlinger::DirectOutputThread::threadLoop() // hardware resources as soon as possible nsecs_t standbyDelay = microseconds(activeSleepTime*2); - while (!exitPending()) { + bool rampVolume; + uint16_t leftVol; + uint16_t rightVol; + Vector< sp<EffectChain> > effectChains; + processConfigEvents(); mixerStatus = MIXER_IDLE; @@ -1856,6 +2219,8 @@ bool AudioFlinger::DirectOutputThread::threadLoop() } } + effectChains = mEffectChains; + // find out which tracks need to be processed if (mActiveTracks.size() != 0) { sp<Track> t = mActiveTracks[0].promote(); @@ -1866,11 +2231,24 @@ bool AudioFlinger::DirectOutputThread::threadLoop() // The first time a track is added we wait // for all its buffers to be filled before processing it - if (cblk->framesReady() && (track->isReady() || track->isStopped()) && + if (cblk->framesReady() && track->isReady() && !track->isPaused() && !track->isTerminated()) { //LOGV("track %d u=%08x, s=%08x [OK]", track->name(), cblk->user, cblk->server); + if (track->mFillingUpStatus == Track::FS_FILLED) { + track->mFillingUpStatus = Track::FS_ACTIVE; + mLeftVolFloat = mRightVolFloat = 0; + mLeftVolShort = mRightVolShort = 0; + if (track->mState == TrackBase::RESUMING) { + track->mState = TrackBase::ACTIVE; + rampVolume = true; + } + } else if (cblk->server != 0) { + // If the track is stopped before the first frame was mixed, + // do not apply ramp + rampVolume = true; + } // compute volume for this track float left, right; if (track->isMuted() || mMasterMute || track->isPausing() || @@ -1890,17 +2268,42 @@ bool AudioFlinger::DirectOutputThread::threadLoop() right = v_clamped/MAX_GAIN; } - if (left != mLeftVolume || right != mRightVolume) { - mOutput->setVolume(left, right); - left = mLeftVolume; - right = mRightVolume; - } + if (left != mLeftVolFloat || right != mRightVolFloat) { + mLeftVolFloat = left; + mRightVolFloat = right; - if (track->mFillingUpStatus == Track::FS_FILLED) { - track->mFillingUpStatus = Track::FS_ACTIVE; - if (track->mState == TrackBase::RESUMING) { - track->mState = TrackBase::ACTIVE; + // If audio HAL implements volume control, + // force software volume to nominal value + if (mOutput->setVolume(left, right) == NO_ERROR) { + left = 1.0f; + right = 1.0f; + } + + // Convert volumes from float to 8.24 + uint32_t vl = (uint32_t)(left * (1 << 24)); + uint32_t vr = (uint32_t)(right * (1 << 24)); + + // Delegate volume control to effect in track effect chain if needed + // only one effect chain can be present on DirectOutputThread, so if + // there is one, the track is connected to it + if (!effectChains.isEmpty()) { + // Do not ramp volume if volume is controlled by effect + if(effectChains[0]->setVolume_l(&vl, &vr)) { + rampVolume = false; + } } + + // Convert volumes from 8.24 to 4.12 format + uint32_t v_clamped = (vl + (1 << 11)) >> 12; + if (v_clamped > MAX_GAIN_INT) v_clamped = MAX_GAIN_INT; + leftVol = (uint16_t)v_clamped; + v_clamped = (vr + (1 << 11)) >> 12; + if (v_clamped > MAX_GAIN_INT) v_clamped = MAX_GAIN_INT; + rightVol = (uint16_t)v_clamped; + } else { + leftVol = mLeftVolShort; + rightVol = mRightVolShort; + rampVolume = false; } // reset retry count @@ -1932,11 +2335,18 @@ bool AudioFlinger::DirectOutputThread::threadLoop() // remove all the tracks that need to be... if (UNLIKELY(trackToRemove != 0)) { mActiveTracks.remove(trackToRemove); + if (!effectChains.isEmpty()) { + LOGV("stopping track on chain %p for session Id: %d", effectChains[0].get(), + trackToRemove->sessionId()); + effectChains[0]->stopTrack(); + } if (trackToRemove->isTerminated()) { mTracks.remove(trackToRemove); deleteTrackName_l(trackToRemove->mName); } } + + lockEffectChains_l(effectChains); } if (LIKELY(mixerStatus == MIXER_TRACKS_READY)) { @@ -1944,7 +2354,7 @@ bool AudioFlinger::DirectOutputThread::threadLoop() size_t frameCount = mFrameCount; curBuf = (int8_t *)mMixBuffer; // output audio to hardware - while(frameCount) { + while (frameCount) { buffer.frameCount = frameCount; activeTrack->getNextBuffer(&buffer); if (UNLIKELY(buffer.raw == 0)) { @@ -1972,10 +2382,18 @@ bool AudioFlinger::DirectOutputThread::threadLoop() } if (mSuspended) { - sleepTime = idleSleepTime; + sleepTime = suspendSleepTimeUs(); } // sleepTime == 0 means we must write to audio hardware if (sleepTime == 0) { + if (mixerStatus == MIXER_TRACKS_READY) { + applyVolume(leftVol, rightVol, rampVolume); + } + for (size_t i = 0; i < effectChains.size(); i ++) { + effectChains[i]->process_l(); + } + unlockEffectChains(effectChains); + mLastWriteTime = systemTime(); mInWrite = true; mBytesWritten += mixBufferSize; @@ -1985,6 +2403,7 @@ bool AudioFlinger::DirectOutputThread::threadLoop() mInWrite = false; mStandby = false; } else { + unlockEffectChains(effectChains); usleep(sleepTime); } @@ -1993,6 +2412,10 @@ bool AudioFlinger::DirectOutputThread::threadLoop() // same lock. trackToRemove.clear(); activeTrack.clear(); + + // Effect chains will be actually deleted here if they were removed from + // mEffectChains list during mixing or effects processing + effectChains.clear(); } if (!mStandby) { @@ -2073,17 +2496,29 @@ uint32_t AudioFlinger::DirectOutputThread::idleSleepTimeUs() { uint32_t time; if (AudioSystem::isLinearPCM(mFormat)) { - time = (uint32_t)((mFrameCount * 1000) / mSampleRate) * 1000; + time = (uint32_t)(((mFrameCount * 1000) / mSampleRate) * 1000) / 2; + } else { + time = 10000; + } + return time; +} + +uint32_t AudioFlinger::DirectOutputThread::suspendSleepTimeUs() +{ + uint32_t time; + if (AudioSystem::isLinearPCM(mFormat)) { + time = (uint32_t)(((mFrameCount * 1000) / mSampleRate) * 1000); } else { time = 10000; } return time; } + // ---------------------------------------------------------------------------- AudioFlinger::DuplicatingThread::DuplicatingThread(const sp<AudioFlinger>& audioFlinger, AudioFlinger::MixerThread* mainThread, int id) - : MixerThread(audioFlinger, mainThread->getOutput(), id), mWaitTimeMs(UINT_MAX) + : MixerThread(audioFlinger, mainThread->getOutput(), id, mainThread->device()), mWaitTimeMs(UINT_MAX) { mType = PlaybackThread::DUPLICATING; addOutputTrack(mainThread); @@ -2099,7 +2534,6 @@ AudioFlinger::DuplicatingThread::~DuplicatingThread() bool AudioFlinger::DuplicatingThread::threadLoop() { - int16_t* curBuf = mMixBuffer; Vector< sp<Track> > tracksToRemove; uint32_t mixerStatus = MIXER_IDLE; nsecs_t standbyTime = systemTime(); @@ -2109,6 +2543,7 @@ bool AudioFlinger::DuplicatingThread::threadLoop() uint32_t activeSleepTime = activeSleepTimeUs(); uint32_t idleSleepTime = idleSleepTimeUs(); uint32_t sleepTime = idleSleepTime; + Vector< sp<EffectChain> > effectChains; while (!exitPending()) { @@ -2169,14 +2604,19 @@ bool AudioFlinger::DuplicatingThread::threadLoop() } mixerStatus = prepareTracks_l(activeTracks, &tracksToRemove); + + // prevent any changes in effect chain list and in each effect chain + // during mixing and effect process as the audio buffers could be deleted + // or modified if an effect is created or deleted + lockEffectChains_l(effectChains); } if (LIKELY(mixerStatus == MIXER_TRACKS_READY)) { // mix buffers... if (outputsReady(outputTracks)) { - mAudioMixer->process(curBuf); + mAudioMixer->process(); } else { - memset(curBuf, 0, mixBufferSize); + memset(mMixBuffer, 0, mixBufferSize); } sleepTime = 0; writeFrames = mFrameCount; @@ -2193,6 +2633,7 @@ bool AudioFlinger::DuplicatingThread::threadLoop() if (outputTracks[i]->isActive()) { sleepTime = 0; writeFrames = 0; + memset(mMixBuffer, 0, mixBufferSize); break; } } @@ -2200,17 +2641,25 @@ bool AudioFlinger::DuplicatingThread::threadLoop() } if (mSuspended) { - sleepTime = idleSleepTime; + sleepTime = suspendSleepTimeUs(); } // sleepTime == 0 means we must write to audio hardware if (sleepTime == 0) { + for (size_t i = 0; i < effectChains.size(); i ++) { + effectChains[i]->process_l(); + } + // enable changes in effect chain + unlockEffectChains(effectChains); + standbyTime = systemTime() + kStandbyTimeInNsecs; for (size_t i = 0; i < outputTracks.size(); i++) { - outputTracks[i]->write(curBuf, writeFrames); + outputTracks[i]->write(mMixBuffer, writeFrames); } mStandby = false; mBytesWritten += mixBufferSize; } else { + // enable changes in effect chain + unlockEffectChains(effectChains); usleep(sleepTime); } @@ -2219,6 +2668,10 @@ bool AudioFlinger::DuplicatingThread::threadLoop() // same lock. tracksToRemove.clear(); outputTracks.clear(); + + // Effect chains will be actually deleted here if they were removed from + // mEffectChains list during mixing or effects processing + effectChains.clear(); } return false; @@ -2303,7 +2756,8 @@ AudioFlinger::ThreadBase::TrackBase::TrackBase( int channelCount, int frameCount, uint32_t flags, - const sp<IMemory>& sharedBuffer) + const sp<IMemory>& sharedBuffer, + int sessionId) : RefBase(), mThread(thread), mClient(client), @@ -2312,7 +2766,8 @@ AudioFlinger::ThreadBase::TrackBase::TrackBase( mState(IDLE), mClientTid(-1), mFormat(format), - mFlags(flags & ~SYSTEM_FLAGS_MASK) + mFlags(flags & ~SYSTEM_FLAGS_MASK), + mSessionId(sessionId) { LOGV_IF(sharedBuffer != 0, "sharedBuffer: %p, size: %d", sharedBuffer->pointer(), sharedBuffer->size()); @@ -2332,13 +2787,13 @@ AudioFlinger::ThreadBase::TrackBase::TrackBase( // clear all buffers mCblk->frameCount = frameCount; mCblk->sampleRate = sampleRate; - mCblk->channels = (uint8_t)channelCount; + mCblk->channelCount = (uint8_t)channelCount; if (sharedBuffer == 0) { mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t); memset(mBuffer, 0, frameCount*channelCount*sizeof(int16_t)); // Force underrun condition to avoid false underrun callback until first data is - // written to buffer - mCblk->flowControlFlag = 1; + // written to buffer (other flags are cleared) + mCblk->flags = CBLK_UNDERRUN_ON; } else { mBuffer = sharedBuffer->pointer(); } @@ -2356,12 +2811,12 @@ AudioFlinger::ThreadBase::TrackBase::TrackBase( // clear all buffers mCblk->frameCount = frameCount; mCblk->sampleRate = sampleRate; - mCblk->channels = (uint8_t)channelCount; + mCblk->channelCount = (uint8_t)channelCount; mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t); memset(mBuffer, 0, frameCount*channelCount*sizeof(int16_t)); // Force underrun condition to avoid false underrun callback until first data is - // written to buffer - mCblk->flowControlFlag = 1; + // written to buffer (other flags are cleared) + mCblk->flags = CBLK_UNDERRUN_ON; mBufferEnd = (uint8_t *)mBuffer + bufferSize; } } @@ -2423,7 +2878,7 @@ int AudioFlinger::ThreadBase::TrackBase::sampleRate() const { } int AudioFlinger::ThreadBase::TrackBase::channelCount() const { - return (int)mCblk->channels; + return (int)mCblk->channelCount; } void* AudioFlinger::ThreadBase::TrackBase::getBuffer(uint32_t offset, uint32_t frames) const { @@ -2435,9 +2890,9 @@ void* AudioFlinger::ThreadBase::TrackBase::getBuffer(uint32_t offset, uint32_t f if (bufferStart < mBuffer || bufferStart > bufferEnd || bufferEnd > mBufferEnd || ((unsigned long)bufferStart & (unsigned long)(cblk->frameSize - 1))) { LOGE("TrackBase::getBuffer buffer out of range:\n start: %p, end %p , mBuffer %p mBufferEnd %p\n \ - server %d, serverBase %d, user %d, userBase %d, channels %d", + server %d, serverBase %d, user %d, userBase %d, channelCount %d", bufferStart, bufferEnd, mBuffer, mBufferEnd, - cblk->server, cblk->serverBase, cblk->user, cblk->userBase, cblk->channels); + cblk->server, cblk->serverBase, cblk->user, cblk->userBase, cblk->channelCount); return 0; } @@ -2455,15 +2910,18 @@ AudioFlinger::PlaybackThread::Track::Track( int format, int channelCount, int frameCount, - const sp<IMemory>& sharedBuffer) - : TrackBase(thread, client, sampleRate, format, channelCount, frameCount, 0, sharedBuffer), - mMute(false), mSharedBuffer(sharedBuffer), mName(-1) + const sp<IMemory>& sharedBuffer, + int sessionId) + : TrackBase(thread, client, sampleRate, format, channelCount, frameCount, 0, sharedBuffer, sessionId), + mMute(false), mSharedBuffer(sharedBuffer), mName(-1), mMainBuffer(NULL), mAuxBuffer(NULL), + mAuxEffectId(0), mHasVolumeController(false) { if (mCblk != NULL) { sp<ThreadBase> baseThread = thread.promote(); if (baseThread != 0) { PlaybackThread *playbackThread = (PlaybackThread *)baseThread.get(); mName = playbackThread->getTrackName_l(); + mMainBuffer = playbackThread->mixBuffer(); } LOGV("Track constructor name %d, calling thread %d", mName, IPCThreadState::self()->getCallingPid()); if (mName < 0) { @@ -2504,7 +2962,9 @@ void AudioFlinger::PlaybackThread::Track::destroy() if (thread != 0) { if (!isOutputTrack()) { if (mState == ACTIVE || mState == RESUMING) { - AudioSystem::stopOutput(thread->id(), (AudioSystem::stream_type)mStreamType); + AudioSystem::stopOutput(thread->id(), + (AudioSystem::stream_type)mStreamType, + mSessionId); } AudioSystem::releaseOutput(thread->id()); } @@ -2517,12 +2977,13 @@ void AudioFlinger::PlaybackThread::Track::destroy() void AudioFlinger::PlaybackThread::Track::dump(char* buffer, size_t size) { - snprintf(buffer, size, " %5d %5d %3u %3u %3u %04u %1d %1d %1d %5u %5u %5u %08x %08x\n", + snprintf(buffer, size, " %05d %05d %03u %03u %03u %05u %04u %1d %1d %1d %05u %05u %05u 0x%08x 0x%08x 0x%08x 0x%08x\n", mName - AudioMixer::TRACK0, (mClient == NULL) ? getpid() : mClient->pid(), mStreamType, mFormat, - mCblk->channels, + mCblk->channelCount, + mSessionId, mFrameCount, mState, mMute, @@ -2531,7 +2992,9 @@ void AudioFlinger::PlaybackThread::Track::dump(char* buffer, size_t size) mCblk->volume[0], mCblk->volume[1], mCblk->server, - mCblk->user); + mCblk->user, + (int)mMainBuffer, + (int)mAuxBuffer); } status_t AudioFlinger::PlaybackThread::Track::getNextBuffer(AudioBufferProvider::Buffer* buffer) @@ -2576,12 +3039,12 @@ getNextBuffer_exit: } bool AudioFlinger::PlaybackThread::Track::isReady() const { - if (mFillingUpStatus != FS_FILLING) return true; + if (mFillingUpStatus != FS_FILLING || isStopped() || isPausing()) return true; if (mCblk->framesReady() >= mCblk->frameCount || - mCblk->forceReady) { + (mCblk->flags & CBLK_FORCEREADY_MSK)) { mFillingUpStatus = FS_FILLED; - mCblk->forceReady = 0; + mCblk->flags &= ~CBLK_FORCEREADY_MSK; return true; } return false; @@ -2590,7 +3053,8 @@ bool AudioFlinger::PlaybackThread::Track::isReady() const { status_t AudioFlinger::PlaybackThread::Track::start() { status_t status = NO_ERROR; - LOGV("start(%d), calling thread %d", mName, IPCThreadState::self()->getCallingPid()); + LOGV("start(%d), calling thread %d session %d", + mName, IPCThreadState::self()->getCallingPid(), mSessionId); sp<ThreadBase> thread = mThread.promote(); if (thread != 0) { Mutex::Autolock _l(thread->mLock); @@ -2607,7 +3071,9 @@ status_t AudioFlinger::PlaybackThread::Track::start() if (!isOutputTrack() && state != ACTIVE && state != RESUMING) { thread->mLock.unlock(); - status = AudioSystem::startOutput(thread->id(), (AudioSystem::stream_type)mStreamType); + status = AudioSystem::startOutput(thread->id(), + (AudioSystem::stream_type)mStreamType, + mSessionId); thread->mLock.lock(); } if (status == NO_ERROR) { @@ -2640,7 +3106,9 @@ void AudioFlinger::PlaybackThread::Track::stop() } if (!isOutputTrack() && (state == ACTIVE || state == RESUMING)) { thread->mLock.unlock(); - AudioSystem::stopOutput(thread->id(), (AudioSystem::stream_type)mStreamType); + AudioSystem::stopOutput(thread->id(), + (AudioSystem::stream_type)mStreamType, + mSessionId); thread->mLock.lock(); } } @@ -2657,7 +3125,9 @@ void AudioFlinger::PlaybackThread::Track::pause() LOGV("ACTIVE/RESUMING => PAUSING (%d) on thread %p", mName, thread.get()); if (!isOutputTrack()) { thread->mLock.unlock(); - AudioSystem::stopOutput(thread->id(), (AudioSystem::stream_type)mStreamType); + AudioSystem::stopOutput(thread->id(), + (AudioSystem::stream_type)mStreamType, + mSessionId); thread->mLock.lock(); } } @@ -2696,8 +3166,8 @@ void AudioFlinger::PlaybackThread::Track::reset() TrackBase::reset(); // Force underrun condition to avoid false underrun callback until first data is // written to buffer - mCblk->flowControlFlag = 1; - mCblk->forceReady = 0; + mCblk->flags |= CBLK_UNDERRUN_ON; + mCblk->flags &= ~CBLK_FORCEREADY_MSK; mFillingUpStatus = FS_FILLING; mResetDone = true; } @@ -2714,6 +3184,23 @@ void AudioFlinger::PlaybackThread::Track::setVolume(float left, float right) mVolume[1] = right; } +status_t AudioFlinger::PlaybackThread::Track::attachAuxEffect(int EffectId) +{ + status_t status = DEAD_OBJECT; + sp<ThreadBase> thread = mThread.promote(); + if (thread != 0) { + PlaybackThread *playbackThread = (PlaybackThread *)thread.get(); + status = playbackThread->attachAuxEffect(this, EffectId); + } + return status; +} + +void AudioFlinger::PlaybackThread::Track::setAuxBuffer(int EffectId, int32_t *buffer) +{ + mAuxEffectId = EffectId; + mAuxBuffer = buffer; +} + // ---------------------------------------------------------------------------- // RecordTrack constructor must be called with AudioFlinger::mLock held @@ -2724,9 +3211,10 @@ AudioFlinger::RecordThread::RecordTrack::RecordTrack( int format, int channelCount, int frameCount, - uint32_t flags) + uint32_t flags, + int sessionId) : TrackBase(thread, client, sampleRate, format, - channelCount, frameCount, flags, 0), + channelCount, frameCount, flags, 0, sessionId), mOverflow(false) { if (mCblk != NULL) { @@ -2808,16 +3296,17 @@ void AudioFlinger::RecordThread::RecordTrack::stop() TrackBase::reset(); // Force overerrun condition to avoid false overrun callback until first data is // read from buffer - mCblk->flowControlFlag = 1; + mCblk->flags |= CBLK_UNDERRUN_ON; } } void AudioFlinger::RecordThread::RecordTrack::dump(char* buffer, size_t size) { - snprintf(buffer, size, " %05d %03u %03u %04u %01d %05u %08x %08x\n", + snprintf(buffer, size, " %05d %03u %03u %05d %04u %01d %05u %08x %08x\n", (mClient == NULL) ? getpid() : mClient->pid(), mFormat, - mCblk->channels, + mCblk->channelCount, + mSessionId, mFrameCount, mState, mCblk->sampleRate, @@ -2835,19 +3324,19 @@ AudioFlinger::PlaybackThread::OutputTrack::OutputTrack( int format, int channelCount, int frameCount) - : Track(thread, NULL, AudioSystem::NUM_STREAM_TYPES, sampleRate, format, channelCount, frameCount, NULL), + : Track(thread, NULL, AudioSystem::NUM_STREAM_TYPES, sampleRate, format, channelCount, frameCount, NULL, 0), mActive(false), mSourceThread(sourceThread) { PlaybackThread *playbackThread = (PlaybackThread *)thread.unsafe_get(); if (mCblk != NULL) { - mCblk->out = 1; + mCblk->flags |= CBLK_DIRECTION_OUT; mCblk->buffers = (char*)mCblk + sizeof(audio_track_cblk_t); mCblk->volume[0] = mCblk->volume[1] = 0x1000; mOutBuffer.frameCount = 0; playbackThread->mTracks.add(this); - LOGV("OutputTrack constructor mCblk %p, mBuffer %p, mCblk->buffers %p, mCblk->frameCount %d, mCblk->sampleRate %d, mCblk->channels %d mBufferEnd %p", - mCblk, mBuffer, mCblk->buffers, mCblk->frameCount, mCblk->sampleRate, mCblk->channels, mBufferEnd); + LOGV("OutputTrack constructor mCblk %p, mBuffer %p, mCblk->buffers %p, mCblk->frameCount %d, mCblk->sampleRate %d, mCblk->channelCount %d mBufferEnd %p", + mCblk, mBuffer, mCblk->buffers, mCblk->frameCount, mCblk->sampleRate, mCblk->channelCount, mBufferEnd); } else { LOGW("Error creating output track on thread %p", playbackThread); } @@ -2882,7 +3371,7 @@ bool AudioFlinger::PlaybackThread::OutputTrack::write(int16_t* data, uint32_t fr { Buffer *pInBuffer; Buffer inBuffer; - uint32_t channels = mCblk->channels; + uint32_t channelCount = mCblk->channelCount; bool outputBufferFull = false; inBuffer.frameCount = frames; inBuffer.i16 = data; @@ -2898,10 +3387,10 @@ bool AudioFlinger::PlaybackThread::OutputTrack::write(int16_t* data, uint32_t fr if (mBufferQueue.size() < kMaxOverFlowBuffers) { uint32_t startFrames = (mCblk->frameCount - frames); pInBuffer = new Buffer; - pInBuffer->mBuffer = new int16_t[startFrames * channels]; + pInBuffer->mBuffer = new int16_t[startFrames * channelCount]; pInBuffer->frameCount = startFrames; pInBuffer->i16 = pInBuffer->mBuffer; - memset(pInBuffer->raw, 0, startFrames * channels * sizeof(int16_t)); + memset(pInBuffer->raw, 0, startFrames * channelCount * sizeof(int16_t)); mBufferQueue.add(pInBuffer); } else { LOGW ("OutputTrack::write() %p no more buffers in queue", this); @@ -2939,12 +3428,12 @@ bool AudioFlinger::PlaybackThread::OutputTrack::write(int16_t* data, uint32_t fr } uint32_t outFrames = pInBuffer->frameCount > mOutBuffer.frameCount ? mOutBuffer.frameCount : pInBuffer->frameCount; - memcpy(mOutBuffer.raw, pInBuffer->raw, outFrames * channels * sizeof(int16_t)); + memcpy(mOutBuffer.raw, pInBuffer->raw, outFrames * channelCount * sizeof(int16_t)); mCblk->stepUser(outFrames); pInBuffer->frameCount -= outFrames; - pInBuffer->i16 += outFrames * channels; + pInBuffer->i16 += outFrames * channelCount; mOutBuffer.frameCount -= outFrames; - mOutBuffer.i16 += outFrames * channels; + mOutBuffer.i16 += outFrames * channelCount; if (pInBuffer->frameCount == 0) { if (mBufferQueue.size()) { @@ -2964,10 +3453,10 @@ bool AudioFlinger::PlaybackThread::OutputTrack::write(int16_t* data, uint32_t fr if (thread != 0 && !thread->standby()) { if (mBufferQueue.size() < kMaxOverFlowBuffers) { pInBuffer = new Buffer; - pInBuffer->mBuffer = new int16_t[inBuffer.frameCount * channels]; + pInBuffer->mBuffer = new int16_t[inBuffer.frameCount * channelCount]; pInBuffer->frameCount = inBuffer.frameCount; pInBuffer->i16 = pInBuffer->mBuffer; - memcpy(pInBuffer->raw, inBuffer.raw, inBuffer.frameCount * channels * sizeof(int16_t)); + memcpy(pInBuffer->raw, inBuffer.raw, inBuffer.frameCount * channelCount * sizeof(int16_t)); mBufferQueue.add(pInBuffer); LOGV("OutputTrack::write() %p thread %p adding overflow buffer %d", this, mThread.unsafe_get(), mBufferQueue.size()); } else { @@ -2983,10 +3472,10 @@ bool AudioFlinger::PlaybackThread::OutputTrack::write(int16_t* data, uint32_t fr if (mCblk->user < mCblk->frameCount) { frames = mCblk->frameCount - mCblk->user; pInBuffer = new Buffer; - pInBuffer->mBuffer = new int16_t[frames * channels]; + pInBuffer->mBuffer = new int16_t[frames * channelCount]; pInBuffer->frameCount = frames; pInBuffer->i16 = pInBuffer->mBuffer; - memset(pInBuffer->raw, 0, frames * channels * sizeof(int16_t)); + memset(pInBuffer->raw, 0, frames * channelCount * sizeof(int16_t)); mBufferQueue.add(pInBuffer); } else if (mActive) { stop(); @@ -3086,6 +3575,28 @@ const sp<MemoryDealer>& AudioFlinger::Client::heap() const // ---------------------------------------------------------------------------- +AudioFlinger::NotificationClient::NotificationClient(const sp<AudioFlinger>& audioFlinger, + const sp<IAudioFlingerClient>& client, + pid_t pid) + : mAudioFlinger(audioFlinger), mPid(pid), mClient(client) +{ +} + +AudioFlinger::NotificationClient::~NotificationClient() +{ + mClient.clear(); +} + +void AudioFlinger::NotificationClient::binderDied(const wp<IBinder>& who) +{ + sp<NotificationClient> keep(this); + { + mAudioFlinger->removeNotificationClient(mPid); + } +} + +// ---------------------------------------------------------------------------- + AudioFlinger::TrackHandle::TrackHandle(const sp<AudioFlinger::PlaybackThread::Track>& track) : BnAudioTrack(), mTrack(track) @@ -3128,6 +3639,11 @@ sp<IMemory> AudioFlinger::TrackHandle::getCblk() const { return mTrack->getCblk(); } +status_t AudioFlinger::TrackHandle::attachAuxEffect(int EffectId) +{ + return mTrack->attachAuxEffect(EffectId); +} + status_t AudioFlinger::TrackHandle::onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { @@ -3144,6 +3660,7 @@ sp<IAudioRecord> AudioFlinger::openRecord( int channelCount, int frameCount, uint32_t flags, + int *sessionId, status_t *status) { sp<RecordThread::RecordTrack> recordTrack; @@ -3153,6 +3670,7 @@ sp<IAudioRecord> AudioFlinger::openRecord( status_t lStatus; RecordThread *thread; size_t inFrameCount; + int lSessionId; // check calling permissions if (!recordingAllowed()) { @@ -3177,9 +3695,18 @@ sp<IAudioRecord> AudioFlinger::openRecord( mClients.add(pid, client); } + // If no audio session id is provided, create one here + if (sessionId != NULL && *sessionId != AudioSystem::SESSION_OUTPUT_MIX) { + lSessionId = *sessionId; + } else { + lSessionId = nextUniqueId(); + if (sessionId != NULL) { + *sessionId = lSessionId; + } + } // create new record track. The record track uses one track in mHardwareMixerThread by convention. recordTrack = new RecordThread::RecordTrack(thread, client, sampleRate, - format, channelCount, frameCount, flags); + format, channelCount, frameCount, flags, lSessionId); } if (recordTrack->getCblk() == NULL) { // remove local strong reference to Client before deleting the RecordTrack so that the Client @@ -3242,7 +3769,6 @@ AudioFlinger::RecordThread::RecordThread(const sp<AudioFlinger>& audioFlinger, A mReqChannelCount = AudioSystem::popCount(channels); mReqSampleRate = sampleRate; readInputParameters(); - sendConfigEvent(AudioSystem::INPUT_OPENED); } @@ -3270,6 +3796,8 @@ bool AudioFlinger::RecordThread::threadLoop() AudioBufferProvider::Buffer buffer; sp<RecordTrack> activeTrack; + nsecs_t lastWarning = 0; + // start recording while (!exitPending()) { @@ -3339,7 +3867,7 @@ bool AudioFlinger::RecordThread::threadLoop() framesIn = framesOut; mRsmpInIndex += framesIn; framesOut -= framesIn; - if (mChannelCount == mReqChannelCount || + if ((int)mChannelCount == mReqChannelCount || mFormat != AudioSystem::PCM_16_BIT) { memcpy(dst, src, framesIn * mFrameSize); } else { @@ -3360,7 +3888,7 @@ bool AudioFlinger::RecordThread::threadLoop() } if (framesOut && mFrameCount == mRsmpInIndex) { if (framesOut == mFrameCount && - (mChannelCount == mReqChannelCount || mFormat != AudioSystem::PCM_16_BIT)) { + ((int)mChannelCount == mReqChannelCount || mFormat != AudioSystem::PCM_16_BIT)) { mBytesRead = mInput->read(buffer.raw, mInputBytes); framesOut = 0; } else { @@ -3411,8 +3939,13 @@ bool AudioFlinger::RecordThread::threadLoop() } // client isn't retrieving buffers fast enough else { - if (!mActiveTrack->setOverflow()) - LOGW("RecordThread: buffer overflow"); + if (!mActiveTrack->setOverflow()) { + nsecs_t now = systemTime(); + if ((now - lastWarning) > kWarningThrottle) { + LOGW("RecordThread: buffer overflow"); + lastWarning = now; + } + } // Release the processor for a while before asking for a new buffer. // This will give the application more chance to read from the buffer and // clear the overflow. @@ -3518,7 +4051,7 @@ status_t AudioFlinger::RecordThread::dump(int fd, const Vector<String16>& args) if (mActiveTrack != 0) { result.append("Active Track:\n"); - result.append(" Clien Fmt Chn Buf S SRate Serv User\n"); + result.append(" Clien Fmt Chn Session Buf S SRate Serv User\n"); mActiveTrack->dump(buffer, SIZE); result.append(buffer); @@ -3657,14 +4190,14 @@ String8 AudioFlinger::RecordThread::getParameters(const String8& keys) return mInput->getParameters(keys); } -void AudioFlinger::RecordThread::audioConfigChanged(int event, int param) { +void AudioFlinger::RecordThread::audioConfigChanged_l(int event, int param) { AudioSystem::OutputDescriptor desc; void *param2 = 0; switch (event) { case AudioSystem::INPUT_OPENED: case AudioSystem::INPUT_CONFIG_CHANGED: - desc.channels = mChannelCount; + desc.channels = mChannels; desc.samplingRate = mSampleRate; desc.format = mFormat; desc.frameCount = mFrameCount; @@ -3676,7 +4209,6 @@ void AudioFlinger::RecordThread::audioConfigChanged(int event, int param) { default: break; } - Mutex::Autolock _l(mAudioFlinger->mLock); mAudioFlinger->audioConfigChanged_l(event, mId, param2); } @@ -3688,9 +4220,10 @@ void AudioFlinger::RecordThread::readInputParameters() mResampler = 0; mSampleRate = mInput->sampleRate(); - mChannelCount = AudioSystem::popCount(mInput->channels()); + mChannels = mInput->channels(); + mChannelCount = (uint16_t)AudioSystem::popCount(mChannels); mFormat = mInput->format(); - mFrameSize = mInput->frameSize(); + mFrameSize = (uint16_t)mInput->frameSize(); mInputBytes = mInput->bufferSize(); mFrameCount = mInputBytes / mFrameSize; mRsmpInBuffer = new int16_t[mFrameCount * mChannelCount]; @@ -3767,14 +4300,15 @@ int AudioFlinger::openOutput(uint32_t *pDevices, mHardwareStatus = AUDIO_HW_IDLE; if (output != 0) { + int id = nextUniqueId(); if ((flags & AudioSystem::OUTPUT_FLAG_DIRECT) || (format != AudioSystem::PCM_16_BIT) || (channels != AudioSystem::CHANNEL_OUT_STEREO)) { - thread = new DirectOutputThread(this, output, ++mNextThreadId); - LOGV("openOutput() created direct output: ID %d thread %p", mNextThreadId, thread); + thread = new DirectOutputThread(this, output, id, *pDevices); + LOGV("openOutput() created direct output: ID %d thread %p", id, thread); } else { - thread = new MixerThread(this, output, ++mNextThreadId); - LOGV("openOutput() created mixer output: ID %d thread %p", mNextThreadId, thread); + thread = new MixerThread(this, output, id, *pDevices); + LOGV("openOutput() created mixer output: ID %d thread %p", id, thread); #ifdef LVMX unsigned bitsPerSample = @@ -3788,14 +4322,16 @@ int AudioFlinger::openOutput(uint32_t *pDevices, #endif } - mPlaybackThreads.add(mNextThreadId, thread); + mPlaybackThreads.add(id, thread); if (pSamplingRate) *pSamplingRate = samplingRate; if (pFormat) *pFormat = format; if (pChannels) *pChannels = channels; if (pLatencyMs) *pLatencyMs = thread->latency(); - return mNextThreadId; + // notify client processes of the new output creation + thread->audioConfigChanged_l(AudioSystem::OUTPUT_OPENED); + return id; } return 0; @@ -3812,11 +4348,13 @@ int AudioFlinger::openDuplicateOutput(int output1, int output2) return 0; } - - DuplicatingThread *thread = new DuplicatingThread(this, thread1, ++mNextThreadId); + int id = nextUniqueId(); + DuplicatingThread *thread = new DuplicatingThread(this, thread1, id); thread->addOutputTrack(thread2); - mPlaybackThreads.add(mNextThreadId, thread); - return mNextThreadId; + mPlaybackThreads.add(id, thread); + // notify client processes of the new output creation + thread->audioConfigChanged_l(AudioSystem::OUTPUT_OPENED); + return id; } status_t AudioFlinger::closeOutput(int output) @@ -3935,17 +4473,20 @@ int AudioFlinger::openInput(uint32_t *pDevices, } if (input != 0) { + int id = nextUniqueId(); // Start record thread - thread = new RecordThread(this, input, reqSamplingRate, reqChannels, ++mNextThreadId); - mRecordThreads.add(mNextThreadId, thread); - LOGV("openInput() created record thread: ID %d thread %p", mNextThreadId, thread); + thread = new RecordThread(this, input, reqSamplingRate, reqChannels, id); + mRecordThreads.add(id, thread); + LOGV("openInput() created record thread: ID %d thread %p", id, thread); if (pSamplingRate) *pSamplingRate = reqSamplingRate; if (pFormat) *pFormat = format; if (pChannels) *pChannels = reqChannels; input->standby(); - return mNextThreadId; + // notify client processes of the new input creation + thread->audioConfigChanged_l(AudioSystem::INPUT_OPENED); + return id; } return 0; @@ -3985,26 +4526,26 @@ status_t AudioFlinger::setStreamOutput(uint32_t stream, int output) } LOGV("setStreamOutput() stream %d to output %d", stream, output); + audioConfigChanged_l(AudioSystem::STREAM_CONFIG_CHANGED, output, &stream); for (size_t i = 0; i < mPlaybackThreads.size(); i++) { PlaybackThread *thread = mPlaybackThreads.valueAt(i).get(); if (thread != dstThread && thread->type() != PlaybackThread::DIRECT) { MixerThread *srcThread = (MixerThread *)thread; - SortedVector < sp<MixerThread::Track> > tracks; - SortedVector < wp<MixerThread::Track> > activeTracks; - srcThread->getTracks(tracks, activeTracks, stream); - if (tracks.size()) { - dstThread->putTracks(tracks, activeTracks); - } + srcThread->invalidateTracks(stream); } } - dstThread->sendConfigEvent(AudioSystem::STREAM_CONFIG_CHANGED, stream); - return NO_ERROR; } + +int AudioFlinger::newAudioSessionId() +{ + return nextUniqueId(); +} + // checkPlaybackThread_l() must be called with AudioFlinger::mLock held AudioFlinger::PlaybackThread *AudioFlinger::checkPlaybackThread_l(int output) const { @@ -4037,19 +4578,1888 @@ AudioFlinger::RecordThread *AudioFlinger::checkRecordThread_l(int input) const return thread; } +int AudioFlinger::nextUniqueId() +{ + return android_atomic_inc(&mNextUniqueId); +} + +// ---------------------------------------------------------------------------- +// Effect management // ---------------------------------------------------------------------------- -status_t AudioFlinger::onTransact( - uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) + +status_t AudioFlinger::loadEffectLibrary(const char *libPath, int *handle) { - return BnAudioFlinger::onTransact(code, data, reply, flags); + // check calling permissions + if (!settingsAllowed()) { + return PERMISSION_DENIED; + } + // only allow libraries loaded from /system/lib/soundfx for now + if (strncmp(gEffectLibPath, libPath, strlen(gEffectLibPath)) != 0) { + return PERMISSION_DENIED; + } + + Mutex::Autolock _l(mLock); + return EffectLoadLibrary(libPath, handle); +} + +status_t AudioFlinger::unloadEffectLibrary(int handle) +{ + // check calling permissions + if (!settingsAllowed()) { + return PERMISSION_DENIED; + } + + Mutex::Autolock _l(mLock); + return EffectUnloadLibrary(handle); +} + +status_t AudioFlinger::queryNumberEffects(uint32_t *numEffects) +{ + Mutex::Autolock _l(mLock); + return EffectQueryNumberEffects(numEffects); +} + +status_t AudioFlinger::queryEffect(uint32_t index, effect_descriptor_t *descriptor) +{ + Mutex::Autolock _l(mLock); + return EffectQueryEffect(index, descriptor); +} + +status_t AudioFlinger::getEffectDescriptor(effect_uuid_t *pUuid, effect_descriptor_t *descriptor) +{ + Mutex::Autolock _l(mLock); + return EffectGetDescriptor(pUuid, descriptor); +} + + +// this UUID must match the one defined in media/libeffects/EffectVisualizer.cpp +static const effect_uuid_t VISUALIZATION_UUID_ = + {0xd069d9e0, 0x8329, 0x11df, 0x9168, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}}; + +sp<IEffect> AudioFlinger::createEffect(pid_t pid, + effect_descriptor_t *pDesc, + const sp<IEffectClient>& effectClient, + int32_t priority, + int output, + int sessionId, + status_t *status, + int *id, + int *enabled) +{ + status_t lStatus = NO_ERROR; + sp<EffectHandle> handle; + effect_interface_t itfe; + effect_descriptor_t desc; + sp<Client> client; + wp<Client> wclient; + + LOGV("createEffect pid %d, client %p, priority %d, sessionId %d, output %d", + pid, effectClient.get(), priority, sessionId, output); + + if (pDesc == NULL) { + lStatus = BAD_VALUE; + goto Exit; + } + + // check audio settings permission for global effects + if (sessionId == AudioSystem::SESSION_OUTPUT_MIX && !settingsAllowed()) { + lStatus = PERMISSION_DENIED; + goto Exit; + } + + // Session AudioSystem::SESSION_OUTPUT_STAGE is reserved for output stage effects + // that can only be created by audio policy manager (running in same process) + if (sessionId == AudioSystem::SESSION_OUTPUT_STAGE && getpid() != pid) { + lStatus = PERMISSION_DENIED; + goto Exit; + } + + // check recording permission for visualizer + if ((memcmp(&pDesc->type, SL_IID_VISUALIZATION, sizeof(effect_uuid_t)) == 0 || + memcmp(&pDesc->uuid, &VISUALIZATION_UUID_, sizeof(effect_uuid_t)) == 0) && + !recordingAllowed()) { + lStatus = PERMISSION_DENIED; + goto Exit; + } + + if (output == 0) { + if (sessionId == AudioSystem::SESSION_OUTPUT_STAGE) { + // output must be specified by AudioPolicyManager when using session + // AudioSystem::SESSION_OUTPUT_STAGE + lStatus = BAD_VALUE; + goto Exit; + } else if (sessionId == AudioSystem::SESSION_OUTPUT_MIX) { + // if the output returned by getOutputForEffect() is removed before we lock the + // mutex below, the call to checkPlaybackThread_l(output) below will detect it + // and we will exit safely + output = AudioSystem::getOutputForEffect(&desc); + } + } + + { + Mutex::Autolock _l(mLock); + + + if (!EffectIsNullUuid(&pDesc->uuid)) { + // if uuid is specified, request effect descriptor + lStatus = EffectGetDescriptor(&pDesc->uuid, &desc); + if (lStatus < 0) { + LOGW("createEffect() error %d from EffectGetDescriptor", lStatus); + goto Exit; + } + } else { + // if uuid is not specified, look for an available implementation + // of the required type in effect factory + if (EffectIsNullUuid(&pDesc->type)) { + LOGW("createEffect() no effect type"); + lStatus = BAD_VALUE; + goto Exit; + } + uint32_t numEffects = 0; + effect_descriptor_t d; + bool found = false; + + lStatus = EffectQueryNumberEffects(&numEffects); + if (lStatus < 0) { + LOGW("createEffect() error %d from EffectQueryNumberEffects", lStatus); + goto Exit; + } + for (uint32_t i = 0; i < numEffects; i++) { + lStatus = EffectQueryEffect(i, &desc); + if (lStatus < 0) { + LOGW("createEffect() error %d from EffectQueryEffect", lStatus); + continue; + } + if (memcmp(&desc.type, &pDesc->type, sizeof(effect_uuid_t)) == 0) { + // If matching type found save effect descriptor. If the session is + // 0 and the effect is not auxiliary, continue enumeration in case + // an auxiliary version of this effect type is available + found = true; + memcpy(&d, &desc, sizeof(effect_descriptor_t)); + if (sessionId != AudioSystem::SESSION_OUTPUT_MIX || + (desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) { + break; + } + } + } + if (!found) { + lStatus = BAD_VALUE; + LOGW("createEffect() effect not found"); + goto Exit; + } + // For same effect type, chose auxiliary version over insert version if + // connect to output mix (Compliance to OpenSL ES) + if (sessionId == AudioSystem::SESSION_OUTPUT_MIX && + (d.flags & EFFECT_FLAG_TYPE_MASK) != EFFECT_FLAG_TYPE_AUXILIARY) { + memcpy(&desc, &d, sizeof(effect_descriptor_t)); + } + } + + // Do not allow auxiliary effects on a session different from 0 (output mix) + if (sessionId != AudioSystem::SESSION_OUTPUT_MIX && + (desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) { + lStatus = INVALID_OPERATION; + goto Exit; + } + + // return effect descriptor + memcpy(pDesc, &desc, sizeof(effect_descriptor_t)); + + // If output is not specified try to find a matching audio session ID in one of the + // output threads. + // If output is 0 here, sessionId is neither SESSION_OUTPUT_STAGE nor SESSION_OUTPUT_MIX + // because of code checking output when entering the function. + if (output == 0) { + // look for the thread where the specified audio session is present + for (size_t i = 0; i < mPlaybackThreads.size(); i++) { + if (mPlaybackThreads.valueAt(i)->hasAudioSession(sessionId) != 0) { + output = mPlaybackThreads.keyAt(i); + break; + } + } + // If no output thread contains the requested session ID, default to + // first output. The effect chain will be moved to the correct output + // thread when a track with the same session ID is created + if (output == 0 && mPlaybackThreads.size()) { + output = mPlaybackThreads.keyAt(0); + } + } + LOGV("createEffect() got output %d for effect %s", output, desc.name); + PlaybackThread *thread = checkPlaybackThread_l(output); + if (thread == NULL) { + LOGE("createEffect() unknown output thread"); + lStatus = BAD_VALUE; + goto Exit; + } + + // TODO: allow attachment of effect to inputs + + wclient = mClients.valueFor(pid); + + if (wclient != NULL) { + client = wclient.promote(); + } else { + client = new Client(this, pid); + mClients.add(pid, client); + } + + // create effect on selected output trhead + handle = thread->createEffect_l(client, effectClient, priority, sessionId, + &desc, enabled, &lStatus); + if (handle != 0 && id != NULL) { + *id = handle->id(); + } + } + +Exit: + if(status) { + *status = lStatus; + } + return handle; +} + +status_t AudioFlinger::moveEffects(int session, int srcOutput, int dstOutput) +{ + LOGV("moveEffects() session %d, srcOutput %d, dstOutput %d", + session, srcOutput, dstOutput); + Mutex::Autolock _l(mLock); + if (srcOutput == dstOutput) { + LOGW("moveEffects() same dst and src outputs %d", dstOutput); + return NO_ERROR; + } + PlaybackThread *srcThread = checkPlaybackThread_l(srcOutput); + if (srcThread == NULL) { + LOGW("moveEffects() bad srcOutput %d", srcOutput); + return BAD_VALUE; + } + PlaybackThread *dstThread = checkPlaybackThread_l(dstOutput); + if (dstThread == NULL) { + LOGW("moveEffects() bad dstOutput %d", dstOutput); + return BAD_VALUE; + } + + Mutex::Autolock _dl(dstThread->mLock); + Mutex::Autolock _sl(srcThread->mLock); + moveEffectChain_l(session, srcThread, dstThread, false); + + return NO_ERROR; +} + +// moveEffectChain_l mustbe called with both srcThread and dstThread mLocks held +status_t AudioFlinger::moveEffectChain_l(int session, + AudioFlinger::PlaybackThread *srcThread, + AudioFlinger::PlaybackThread *dstThread, + bool reRegister) +{ + LOGV("moveEffectChain_l() session %d from thread %p to thread %p", + session, srcThread, dstThread); + + sp<EffectChain> chain = srcThread->getEffectChain_l(session); + if (chain == 0) { + LOGW("moveEffectChain_l() effect chain for session %d not on source thread %p", + session, srcThread); + return INVALID_OPERATION; + } + + // remove chain first. This is useful only if reconfiguring effect chain on same output thread, + // so that a new chain is created with correct parameters when first effect is added. This is + // otherwise unecessary as removeEffect_l() will remove the chain when last effect is + // removed. + srcThread->removeEffectChain_l(chain); + + // transfer all effects one by one so that new effect chain is created on new thread with + // correct buffer sizes and audio parameters and effect engines reconfigured accordingly + int dstOutput = dstThread->id(); + sp<EffectChain> dstChain; + uint32_t strategy; + sp<EffectModule> effect = chain->getEffectFromId_l(0); + while (effect != 0) { + srcThread->removeEffect_l(effect); + dstThread->addEffect_l(effect); + // if the move request is not received from audio policy manager, the effect must be + // re-registered with the new strategy and output + if (dstChain == 0) { + dstChain = effect->chain().promote(); + if (dstChain == 0) { + LOGW("moveEffectChain_l() cannot get chain from effect %p", effect.get()); + srcThread->addEffect_l(effect); + return NO_INIT; + } + strategy = dstChain->strategy(); + } + if (reRegister) { + AudioSystem::unregisterEffect(effect->id()); + AudioSystem::registerEffect(&effect->desc(), + dstOutput, + strategy, + session, + effect->id()); + } + effect = chain->getEffectFromId_l(0); + } + + return NO_ERROR; +} + +// PlaybackThread::createEffect_l() must be called with AudioFlinger::mLock held +sp<AudioFlinger::EffectHandle> AudioFlinger::PlaybackThread::createEffect_l( + const sp<AudioFlinger::Client>& client, + const sp<IEffectClient>& effectClient, + int32_t priority, + int sessionId, + effect_descriptor_t *desc, + int *enabled, + status_t *status + ) +{ + sp<EffectModule> effect; + sp<EffectHandle> handle; + status_t lStatus; + sp<Track> track; + sp<EffectChain> chain; + bool chainCreated = false; + bool effectCreated = false; + bool effectRegistered = false; + + if (mOutput == 0) { + LOGW("createEffect_l() Audio driver not initialized."); + lStatus = NO_INIT; + goto Exit; + } + + // Do not allow auxiliary effect on session other than 0 + if ((desc->flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY && + sessionId != AudioSystem::SESSION_OUTPUT_MIX) { + LOGW("createEffect_l() Cannot add auxiliary effect %s to session %d", + desc->name, sessionId); + lStatus = BAD_VALUE; + goto Exit; + } + + // Do not allow effects with session ID 0 on direct output or duplicating threads + // TODO: add rule for hw accelerated effects on direct outputs with non PCM format + if (sessionId == AudioSystem::SESSION_OUTPUT_MIX && mType != MIXER) { + LOGW("createEffect_l() Cannot add auxiliary effect %s to session %d", + desc->name, sessionId); + lStatus = BAD_VALUE; + goto Exit; + } + + LOGV("createEffect_l() thread %p effect %s on session %d", this, desc->name, sessionId); + + { // scope for mLock + Mutex::Autolock _l(mLock); + + // check for existing effect chain with the requested audio session + chain = getEffectChain_l(sessionId); + if (chain == 0) { + // create a new chain for this session + LOGV("createEffect_l() new effect chain for session %d", sessionId); + chain = new EffectChain(this, sessionId); + addEffectChain_l(chain); + chain->setStrategy(getStrategyForSession_l(sessionId)); + chainCreated = true; + } else { + effect = chain->getEffectFromDesc_l(desc); + } + + LOGV("createEffect_l() got effect %p on chain %p", effect == 0 ? 0 : effect.get(), chain.get()); + + if (effect == 0) { + int id = mAudioFlinger->nextUniqueId(); + // Check CPU and memory usage + lStatus = AudioSystem::registerEffect(desc, mId, chain->strategy(), sessionId, id); + if (lStatus != NO_ERROR) { + goto Exit; + } + effectRegistered = true; + // create a new effect module if none present in the chain + effect = new EffectModule(this, chain, desc, id, sessionId); + lStatus = effect->status(); + if (lStatus != NO_ERROR) { + goto Exit; + } + lStatus = chain->addEffect_l(effect); + if (lStatus != NO_ERROR) { + goto Exit; + } + effectCreated = true; + + effect->setDevice(mDevice); + effect->setMode(mAudioFlinger->getMode()); + } + // create effect handle and connect it to effect module + handle = new EffectHandle(effect, client, effectClient, priority); + lStatus = effect->addHandle(handle); + if (enabled) { + *enabled = (int)effect->isEnabled(); + } + } + +Exit: + if (lStatus != NO_ERROR && lStatus != ALREADY_EXISTS) { + Mutex::Autolock _l(mLock); + if (effectCreated) { + chain->removeEffect_l(effect); + } + if (effectRegistered) { + AudioSystem::unregisterEffect(effect->id()); + } + if (chainCreated) { + removeEffectChain_l(chain); + } + handle.clear(); + } + + if(status) { + *status = lStatus; + } + return handle; +} + +// PlaybackThread::addEffect_l() must be called with AudioFlinger::mLock and +// PlaybackThread::mLock held +status_t AudioFlinger::PlaybackThread::addEffect_l(const sp<EffectModule>& effect) +{ + // check for existing effect chain with the requested audio session + int sessionId = effect->sessionId(); + sp<EffectChain> chain = getEffectChain_l(sessionId); + bool chainCreated = false; + + if (chain == 0) { + // create a new chain for this session + LOGV("addEffect_l() new effect chain for session %d", sessionId); + chain = new EffectChain(this, sessionId); + addEffectChain_l(chain); + chain->setStrategy(getStrategyForSession_l(sessionId)); + chainCreated = true; + } + LOGV("addEffect_l() %p chain %p effect %p", this, chain.get(), effect.get()); + + if (chain->getEffectFromId_l(effect->id()) != 0) { + LOGW("addEffect_l() %p effect %s already present in chain %p", + this, effect->desc().name, chain.get()); + return BAD_VALUE; + } + + status_t status = chain->addEffect_l(effect); + if (status != NO_ERROR) { + if (chainCreated) { + removeEffectChain_l(chain); + } + return status; + } + + effect->setDevice(mDevice); + effect->setMode(mAudioFlinger->getMode()); + return NO_ERROR; +} + +void AudioFlinger::PlaybackThread::removeEffect_l(const sp<EffectModule>& effect) { + + LOGV("removeEffect_l() %p effect %p", this, effect.get()); + effect_descriptor_t desc = effect->desc(); + if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) { + detachAuxEffect_l(effect->id()); + } + + sp<EffectChain> chain = effect->chain().promote(); + if (chain != 0) { + // remove effect chain if removing last effect + if (chain->removeEffect_l(effect) == 0) { + removeEffectChain_l(chain); + } + } else { + LOGW("removeEffect_l() %p cannot promote chain for effect %p", this, effect.get()); + } +} + +void AudioFlinger::PlaybackThread::disconnectEffect(const sp<EffectModule>& effect, + const wp<EffectHandle>& handle) { + Mutex::Autolock _l(mLock); + LOGV("disconnectEffect() %p effect %p", this, effect.get()); + // delete the effect module if removing last handle on it + if (effect->removeHandle(handle) == 0) { + removeEffect_l(effect); + AudioSystem::unregisterEffect(effect->id()); + } +} + +status_t AudioFlinger::PlaybackThread::addEffectChain_l(const sp<EffectChain>& chain) +{ + int session = chain->sessionId(); + int16_t *buffer = mMixBuffer; + bool ownsBuffer = false; + + LOGV("addEffectChain_l() %p on thread %p for session %d", chain.get(), this, session); + if (session > 0) { + // Only one effect chain can be present in direct output thread and it uses + // the mix buffer as input + if (mType != DIRECT) { + size_t numSamples = mFrameCount * mChannelCount; + buffer = new int16_t[numSamples]; + memset(buffer, 0, numSamples * sizeof(int16_t)); + LOGV("addEffectChain_l() creating new input buffer %p session %d", buffer, session); + ownsBuffer = true; + } + + // Attach all tracks with same session ID to this chain. + for (size_t i = 0; i < mTracks.size(); ++i) { + sp<Track> track = mTracks[i]; + if (session == track->sessionId()) { + LOGV("addEffectChain_l() track->setMainBuffer track %p buffer %p", track.get(), buffer); + track->setMainBuffer(buffer); + } + } + + // indicate all active tracks in the chain + for (size_t i = 0 ; i < mActiveTracks.size() ; ++i) { + sp<Track> track = mActiveTracks[i].promote(); + if (track == 0) continue; + if (session == track->sessionId()) { + LOGV("addEffectChain_l() activating track %p on session %d", track.get(), session); + chain->startTrack(); + } + } + } + + chain->setInBuffer(buffer, ownsBuffer); + chain->setOutBuffer(mMixBuffer); + // Effect chain for session AudioSystem::SESSION_OUTPUT_STAGE is inserted at end of effect + // chains list in order to be processed last as it contains output stage effects + // Effect chain for session AudioSystem::SESSION_OUTPUT_MIX is inserted before + // session AudioSystem::SESSION_OUTPUT_STAGE to be processed + // after track specific effects and before output stage + // It is therefore mandatory that AudioSystem::SESSION_OUTPUT_MIX == 0 and + // that AudioSystem::SESSION_OUTPUT_STAGE < AudioSystem::SESSION_OUTPUT_MIX + // Effect chain for other sessions are inserted at beginning of effect + // chains list to be processed before output mix effects. Relative order between other + // sessions is not important + size_t size = mEffectChains.size(); + size_t i = 0; + for (i = 0; i < size; i++) { + if (mEffectChains[i]->sessionId() < session) break; + } + mEffectChains.insertAt(chain, i); + + return NO_ERROR; +} + +size_t AudioFlinger::PlaybackThread::removeEffectChain_l(const sp<EffectChain>& chain) +{ + int session = chain->sessionId(); + + LOGV("removeEffectChain_l() %p from thread %p for session %d", chain.get(), this, session); + + for (size_t i = 0; i < mEffectChains.size(); i++) { + if (chain == mEffectChains[i]) { + mEffectChains.removeAt(i); + // detach all tracks with same session ID from this chain + for (size_t i = 0; i < mTracks.size(); ++i) { + sp<Track> track = mTracks[i]; + if (session == track->sessionId()) { + track->setMainBuffer(mMixBuffer); + } + } + break; + } + } + return mEffectChains.size(); +} + +void AudioFlinger::PlaybackThread::lockEffectChains_l( + Vector<sp <AudioFlinger::EffectChain> >& effectChains) +{ + effectChains = mEffectChains; + for (size_t i = 0; i < mEffectChains.size(); i++) { + mEffectChains[i]->lock(); + } +} + +void AudioFlinger::PlaybackThread::unlockEffectChains( + Vector<sp <AudioFlinger::EffectChain> >& effectChains) +{ + for (size_t i = 0; i < effectChains.size(); i++) { + effectChains[i]->unlock(); + } +} + + +sp<AudioFlinger::EffectModule> AudioFlinger::PlaybackThread::getEffect_l(int sessionId, int effectId) +{ + sp<EffectModule> effect; + + sp<EffectChain> chain = getEffectChain_l(sessionId); + if (chain != 0) { + effect = chain->getEffectFromId_l(effectId); + } + return effect; +} + +status_t AudioFlinger::PlaybackThread::attachAuxEffect( + const sp<AudioFlinger::PlaybackThread::Track> track, int EffectId) +{ + Mutex::Autolock _l(mLock); + return attachAuxEffect_l(track, EffectId); +} + +status_t AudioFlinger::PlaybackThread::attachAuxEffect_l( + const sp<AudioFlinger::PlaybackThread::Track> track, int EffectId) +{ + status_t status = NO_ERROR; + + if (EffectId == 0) { + track->setAuxBuffer(0, NULL); + } else { + // Auxiliary effects are always in audio session AudioSystem::SESSION_OUTPUT_MIX + sp<EffectModule> effect = getEffect_l(AudioSystem::SESSION_OUTPUT_MIX, EffectId); + if (effect != 0) { + if ((effect->desc().flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) { + track->setAuxBuffer(EffectId, (int32_t *)effect->inBuffer()); + } else { + status = INVALID_OPERATION; + } + } else { + status = BAD_VALUE; + } + } + return status; +} + +void AudioFlinger::PlaybackThread::detachAuxEffect_l(int effectId) +{ + for (size_t i = 0; i < mTracks.size(); ++i) { + sp<Track> track = mTracks[i]; + if (track->auxEffectId() == effectId) { + attachAuxEffect_l(track, 0); + } + } +} + +// ---------------------------------------------------------------------------- +// EffectModule implementation +// ---------------------------------------------------------------------------- + +#undef LOG_TAG +#define LOG_TAG "AudioFlinger::EffectModule" + +AudioFlinger::EffectModule::EffectModule(const wp<ThreadBase>& wThread, + const wp<AudioFlinger::EffectChain>& chain, + effect_descriptor_t *desc, + int id, + int sessionId) + : mThread(wThread), mChain(chain), mId(id), mSessionId(sessionId), mEffectInterface(NULL), + mStatus(NO_INIT), mState(IDLE) +{ + LOGV("Constructor %p", this); + int lStatus; + sp<ThreadBase> thread = mThread.promote(); + if (thread == 0) { + return; + } + PlaybackThread *p = (PlaybackThread *)thread.get(); + + memcpy(&mDescriptor, desc, sizeof(effect_descriptor_t)); + + // create effect engine from effect factory + mStatus = EffectCreate(&desc->uuid, sessionId, p->id(), &mEffectInterface); + + if (mStatus != NO_ERROR) { + return; + } + lStatus = init(); + if (lStatus < 0) { + mStatus = lStatus; + goto Error; + } + + LOGV("Constructor success name %s, Interface %p", mDescriptor.name, mEffectInterface); + return; +Error: + EffectRelease(mEffectInterface); + mEffectInterface = NULL; + LOGV("Constructor Error %d", mStatus); +} + +AudioFlinger::EffectModule::~EffectModule() +{ + LOGV("Destructor %p", this); + if (mEffectInterface != NULL) { + // release effect engine + EffectRelease(mEffectInterface); + } +} + +status_t AudioFlinger::EffectModule::addHandle(sp<EffectHandle>& handle) +{ + status_t status; + + Mutex::Autolock _l(mLock); + // First handle in mHandles has highest priority and controls the effect module + int priority = handle->priority(); + size_t size = mHandles.size(); + sp<EffectHandle> h; + size_t i; + for (i = 0; i < size; i++) { + h = mHandles[i].promote(); + if (h == 0) continue; + if (h->priority() <= priority) break; + } + // if inserted in first place, move effect control from previous owner to this handle + if (i == 0) { + if (h != 0) { + h->setControl(false, true); + } + handle->setControl(true, false); + status = NO_ERROR; + } else { + status = ALREADY_EXISTS; + } + mHandles.insertAt(handle, i); + return status; +} + +size_t AudioFlinger::EffectModule::removeHandle(const wp<EffectHandle>& handle) +{ + Mutex::Autolock _l(mLock); + size_t size = mHandles.size(); + size_t i; + for (i = 0; i < size; i++) { + if (mHandles[i] == handle) break; + } + if (i == size) { + return size; + } + mHandles.removeAt(i); + size = mHandles.size(); + // if removed from first place, move effect control from this handle to next in line + if (i == 0 && size != 0) { + sp<EffectHandle> h = mHandles[0].promote(); + if (h != 0) { + h->setControl(true, true); + } + } + + // Release effect engine here so that it is done immediately. Otherwise it will be released + // by the destructor when the last strong reference on the this object is released which can + // happen after next process is called on this effect. + if (size == 0 && mEffectInterface != NULL) { + // release effect engine + EffectRelease(mEffectInterface); + mEffectInterface = NULL; + } + + return size; +} + +void AudioFlinger::EffectModule::disconnect(const wp<EffectHandle>& handle) +{ + // keep a strong reference on this EffectModule to avoid calling the + // destructor before we exit + sp<EffectModule> keep(this); + { + sp<ThreadBase> thread = mThread.promote(); + if (thread != 0) { + PlaybackThread *playbackThread = (PlaybackThread *)thread.get(); + playbackThread->disconnectEffect(keep, handle); + } + } +} + +void AudioFlinger::EffectModule::updateState() { + Mutex::Autolock _l(mLock); + + switch (mState) { + case RESTART: + reset_l(); + // FALL THROUGH + + case STARTING: + // clear auxiliary effect input buffer for next accumulation + if ((mDescriptor.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) { + memset(mConfig.inputCfg.buffer.raw, + 0, + mConfig.inputCfg.buffer.frameCount*sizeof(int32_t)); + } + start_l(); + mState = ACTIVE; + break; + case STOPPING: + stop_l(); + mDisableWaitCnt = mMaxDisableWaitCnt; + mState = STOPPED; + break; + case STOPPED: + // mDisableWaitCnt is forced to 1 by process() when the engine indicates the end of the + // turn off sequence. + if (--mDisableWaitCnt == 0) { + reset_l(); + mState = IDLE; + } + break; + default: //IDLE , ACTIVE + break; + } +} + +void AudioFlinger::EffectModule::process() +{ + Mutex::Autolock _l(mLock); + + if (mEffectInterface == NULL || + mConfig.inputCfg.buffer.raw == NULL || + mConfig.outputCfg.buffer.raw == NULL) { + return; + } + + if (isProcessEnabled()) { + // do 32 bit to 16 bit conversion for auxiliary effect input buffer + if ((mDescriptor.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) { + AudioMixer::ditherAndClamp(mConfig.inputCfg.buffer.s32, + mConfig.inputCfg.buffer.s32, + mConfig.inputCfg.buffer.frameCount/2); + } + + // do the actual processing in the effect engine + int ret = (*mEffectInterface)->process(mEffectInterface, + &mConfig.inputCfg.buffer, + &mConfig.outputCfg.buffer); + + // force transition to IDLE state when engine is ready + if (mState == STOPPED && ret == -ENODATA) { + mDisableWaitCnt = 1; + } + + // clear auxiliary effect input buffer for next accumulation + if ((mDescriptor.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) { + memset(mConfig.inputCfg.buffer.raw, 0, mConfig.inputCfg.buffer.frameCount*sizeof(int32_t)); + } + } else if ((mDescriptor.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_INSERT && + mConfig.inputCfg.buffer.raw != mConfig.outputCfg.buffer.raw){ + // If an insert effect is idle and input buffer is different from output buffer, copy input to + // output + sp<EffectChain> chain = mChain.promote(); + if (chain != 0 && chain->activeTracks() != 0) { + size_t size = mConfig.inputCfg.buffer.frameCount * sizeof(int16_t); + if (mConfig.inputCfg.channels == CHANNEL_STEREO) { + size *= 2; + } + memcpy(mConfig.outputCfg.buffer.raw, mConfig.inputCfg.buffer.raw, size); + } + } +} + +void AudioFlinger::EffectModule::reset_l() +{ + if (mEffectInterface == NULL) { + return; + } + (*mEffectInterface)->command(mEffectInterface, EFFECT_CMD_RESET, 0, NULL, 0, NULL); +} + +status_t AudioFlinger::EffectModule::configure() +{ + uint32_t channels; + if (mEffectInterface == NULL) { + return NO_INIT; + } + + sp<ThreadBase> thread = mThread.promote(); + if (thread == 0) { + return DEAD_OBJECT; + } + + // TODO: handle configuration of effects replacing track process + if (thread->channelCount() == 1) { + channels = CHANNEL_MONO; + } else { + channels = CHANNEL_STEREO; + } + + if ((mDescriptor.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) { + mConfig.inputCfg.channels = CHANNEL_MONO; + } else { + mConfig.inputCfg.channels = channels; + } + mConfig.outputCfg.channels = channels; + mConfig.inputCfg.format = SAMPLE_FORMAT_PCM_S15; + mConfig.outputCfg.format = SAMPLE_FORMAT_PCM_S15; + mConfig.inputCfg.samplingRate = thread->sampleRate(); + mConfig.outputCfg.samplingRate = mConfig.inputCfg.samplingRate; + mConfig.inputCfg.bufferProvider.cookie = NULL; + mConfig.inputCfg.bufferProvider.getBuffer = NULL; + mConfig.inputCfg.bufferProvider.releaseBuffer = NULL; + mConfig.outputCfg.bufferProvider.cookie = NULL; + mConfig.outputCfg.bufferProvider.getBuffer = NULL; + mConfig.outputCfg.bufferProvider.releaseBuffer = NULL; + mConfig.inputCfg.accessMode = EFFECT_BUFFER_ACCESS_READ; + // Insert effect: + // - in session AudioSystem::SESSION_OUTPUT_MIX or AudioSystem::SESSION_OUTPUT_STAGE, + // always overwrites output buffer: input buffer == output buffer + // - in other sessions: + // last effect in the chain accumulates in output buffer: input buffer != output buffer + // other effect: overwrites output buffer: input buffer == output buffer + // Auxiliary effect: + // accumulates in output buffer: input buffer != output buffer + // Therefore: accumulate <=> input buffer != output buffer + if (mConfig.inputCfg.buffer.raw != mConfig.outputCfg.buffer.raw) { + mConfig.outputCfg.accessMode = EFFECT_BUFFER_ACCESS_ACCUMULATE; + } else { + mConfig.outputCfg.accessMode = EFFECT_BUFFER_ACCESS_WRITE; + } + mConfig.inputCfg.mask = EFFECT_CONFIG_ALL; + mConfig.outputCfg.mask = EFFECT_CONFIG_ALL; + mConfig.inputCfg.buffer.frameCount = thread->frameCount(); + mConfig.outputCfg.buffer.frameCount = mConfig.inputCfg.buffer.frameCount; + + LOGV("configure() %p thread %p buffer %p framecount %d", + this, thread.get(), mConfig.inputCfg.buffer.raw, mConfig.inputCfg.buffer.frameCount); + + status_t cmdStatus; + uint32_t size = sizeof(int); + status_t status = (*mEffectInterface)->command(mEffectInterface, + EFFECT_CMD_CONFIGURE, + sizeof(effect_config_t), + &mConfig, + &size, + &cmdStatus); + if (status == 0) { + status = cmdStatus; + } + + mMaxDisableWaitCnt = (MAX_DISABLE_TIME_MS * mConfig.outputCfg.samplingRate) / + (1000 * mConfig.outputCfg.buffer.frameCount); + + return status; +} + +status_t AudioFlinger::EffectModule::init() +{ + Mutex::Autolock _l(mLock); + if (mEffectInterface == NULL) { + return NO_INIT; + } + status_t cmdStatus; + uint32_t size = sizeof(status_t); + status_t status = (*mEffectInterface)->command(mEffectInterface, + EFFECT_CMD_INIT, + 0, + NULL, + &size, + &cmdStatus); + if (status == 0) { + status = cmdStatus; + } + return status; +} + +status_t AudioFlinger::EffectModule::start_l() +{ + if (mEffectInterface == NULL) { + return NO_INIT; + } + status_t cmdStatus; + uint32_t size = sizeof(status_t); + status_t status = (*mEffectInterface)->command(mEffectInterface, + EFFECT_CMD_ENABLE, + 0, + NULL, + &size, + &cmdStatus); + if (status == 0) { + status = cmdStatus; + } + return status; +} + +status_t AudioFlinger::EffectModule::stop_l() +{ + if (mEffectInterface == NULL) { + return NO_INIT; + } + status_t cmdStatus; + uint32_t size = sizeof(status_t); + status_t status = (*mEffectInterface)->command(mEffectInterface, + EFFECT_CMD_DISABLE, + 0, + NULL, + &size, + &cmdStatus); + if (status == 0) { + status = cmdStatus; + } + return status; +} + +status_t AudioFlinger::EffectModule::command(uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t *replySize, + void *pReplyData) +{ + Mutex::Autolock _l(mLock); +// LOGV("command(), cmdCode: %d, mEffectInterface: %p", cmdCode, mEffectInterface); + + if (mEffectInterface == NULL) { + return NO_INIT; + } + status_t status = (*mEffectInterface)->command(mEffectInterface, + cmdCode, + cmdSize, + pCmdData, + replySize, + pReplyData); + if (cmdCode != EFFECT_CMD_GET_PARAM && status == NO_ERROR) { + uint32_t size = (replySize == NULL) ? 0 : *replySize; + for (size_t i = 1; i < mHandles.size(); i++) { + sp<EffectHandle> h = mHandles[i].promote(); + if (h != 0) { + h->commandExecuted(cmdCode, cmdSize, pCmdData, size, pReplyData); + } + } + } + return status; +} + +status_t AudioFlinger::EffectModule::setEnabled(bool enabled) +{ + Mutex::Autolock _l(mLock); + LOGV("setEnabled %p enabled %d", this, enabled); + + if (enabled != isEnabled()) { + switch (mState) { + // going from disabled to enabled + case IDLE: + mState = STARTING; + break; + case STOPPED: + mState = RESTART; + break; + case STOPPING: + mState = ACTIVE; + break; + + // going from enabled to disabled + case RESTART: + mState = STOPPED; + break; + case STARTING: + mState = IDLE; + break; + case ACTIVE: + mState = STOPPING; + break; + } + for (size_t i = 1; i < mHandles.size(); i++) { + sp<EffectHandle> h = mHandles[i].promote(); + if (h != 0) { + h->setEnabled(enabled); + } + } + } + return NO_ERROR; +} + +bool AudioFlinger::EffectModule::isEnabled() +{ + switch (mState) { + case RESTART: + case STARTING: + case ACTIVE: + return true; + case IDLE: + case STOPPING: + case STOPPED: + default: + return false; + } +} + +bool AudioFlinger::EffectModule::isProcessEnabled() +{ + switch (mState) { + case RESTART: + case ACTIVE: + case STOPPING: + case STOPPED: + return true; + case IDLE: + case STARTING: + default: + return false; + } +} + +status_t AudioFlinger::EffectModule::setVolume(uint32_t *left, uint32_t *right, bool controller) +{ + Mutex::Autolock _l(mLock); + status_t status = NO_ERROR; + + // Send volume indication if EFFECT_FLAG_VOLUME_IND is set and read back altered volume + // if controller flag is set (Note that controller == TRUE => EFFECT_FLAG_VOLUME_CTRL set) + if (isProcessEnabled() && + ((mDescriptor.flags & EFFECT_FLAG_VOLUME_MASK) == EFFECT_FLAG_VOLUME_CTRL || + (mDescriptor.flags & EFFECT_FLAG_VOLUME_MASK) == EFFECT_FLAG_VOLUME_IND)) { + status_t cmdStatus; + uint32_t volume[2]; + uint32_t *pVolume = NULL; + uint32_t size = sizeof(volume); + volume[0] = *left; + volume[1] = *right; + if (controller) { + pVolume = volume; + } + status = (*mEffectInterface)->command(mEffectInterface, + EFFECT_CMD_SET_VOLUME, + size, + volume, + &size, + pVolume); + if (controller && status == NO_ERROR && size == sizeof(volume)) { + *left = volume[0]; + *right = volume[1]; + } + } + return status; +} + +status_t AudioFlinger::EffectModule::setDevice(uint32_t device) +{ + Mutex::Autolock _l(mLock); + status_t status = NO_ERROR; + if ((mDescriptor.flags & EFFECT_FLAG_DEVICE_MASK) == EFFECT_FLAG_DEVICE_IND) { + // convert device bit field from AudioSystem to EffectApi format. + device = deviceAudioSystemToEffectApi(device); + if (device == 0) { + return BAD_VALUE; + } + status_t cmdStatus; + uint32_t size = sizeof(status_t); + status = (*mEffectInterface)->command(mEffectInterface, + EFFECT_CMD_SET_DEVICE, + sizeof(uint32_t), + &device, + &size, + &cmdStatus); + if (status == NO_ERROR) { + status = cmdStatus; + } + } + return status; +} + +status_t AudioFlinger::EffectModule::setMode(uint32_t mode) +{ + Mutex::Autolock _l(mLock); + status_t status = NO_ERROR; + if ((mDescriptor.flags & EFFECT_FLAG_AUDIO_MODE_MASK) == EFFECT_FLAG_AUDIO_MODE_IND) { + // convert audio mode from AudioSystem to EffectApi format. + int effectMode = modeAudioSystemToEffectApi(mode); + if (effectMode < 0) { + return BAD_VALUE; + } + status_t cmdStatus; + uint32_t size = sizeof(status_t); + status = (*mEffectInterface)->command(mEffectInterface, + EFFECT_CMD_SET_AUDIO_MODE, + sizeof(int), + &effectMode, + &size, + &cmdStatus); + if (status == NO_ERROR) { + status = cmdStatus; + } + } + return status; +} + +// update this table when AudioSystem::audio_devices or audio_device_e (in EffectApi.h) are modified +const uint32_t AudioFlinger::EffectModule::sDeviceConvTable[] = { + DEVICE_EARPIECE, // AudioSystem::DEVICE_OUT_EARPIECE + DEVICE_SPEAKER, // AudioSystem::DEVICE_OUT_SPEAKER + DEVICE_WIRED_HEADSET, // case AudioSystem::DEVICE_OUT_WIRED_HEADSET + DEVICE_WIRED_HEADPHONE, // AudioSystem::DEVICE_OUT_WIRED_HEADPHONE + DEVICE_BLUETOOTH_SCO, // AudioSystem::DEVICE_OUT_BLUETOOTH_SCO + DEVICE_BLUETOOTH_SCO_HEADSET, // AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_HEADSET + DEVICE_BLUETOOTH_SCO_CARKIT, // AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_CARKIT + DEVICE_BLUETOOTH_A2DP, // AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP + DEVICE_BLUETOOTH_A2DP_HEADPHONES, // AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES + DEVICE_BLUETOOTH_A2DP_SPEAKER, // AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER + DEVICE_AUX_DIGITAL // AudioSystem::DEVICE_OUT_AUX_DIGITAL +}; + +uint32_t AudioFlinger::EffectModule::deviceAudioSystemToEffectApi(uint32_t device) +{ + uint32_t deviceOut = 0; + while (device) { + const uint32_t i = 31 - __builtin_clz(device); + device &= ~(1 << i); + if (i >= sizeof(sDeviceConvTable)/sizeof(uint32_t)) { + LOGE("device convertion error for AudioSystem device 0x%08x", device); + return 0; + } + deviceOut |= (uint32_t)sDeviceConvTable[i]; + } + return deviceOut; +} + +// update this table when AudioSystem::audio_mode or audio_mode_e (in EffectApi.h) are modified +const uint32_t AudioFlinger::EffectModule::sModeConvTable[] = { + AUDIO_MODE_NORMAL, // AudioSystem::MODE_NORMAL + AUDIO_MODE_RINGTONE, // AudioSystem::MODE_RINGTONE + AUDIO_MODE_IN_CALL // AudioSystem::MODE_IN_CALL +}; + +int AudioFlinger::EffectModule::modeAudioSystemToEffectApi(uint32_t mode) +{ + int modeOut = -1; + if (mode < sizeof(sModeConvTable) / sizeof(uint32_t)) { + modeOut = (int)sModeConvTable[mode]; + } + return modeOut; +} + +status_t AudioFlinger::EffectModule::dump(int fd, const Vector<String16>& args) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + snprintf(buffer, SIZE, "\tEffect ID %d:\n", mId); + result.append(buffer); + + bool locked = tryLock(mLock); + // failed to lock - AudioFlinger is probably deadlocked + if (!locked) { + result.append("\t\tCould not lock Fx mutex:\n"); + } + + result.append("\t\tSession Status State Engine:\n"); + snprintf(buffer, SIZE, "\t\t%05d %03d %03d 0x%08x\n", + mSessionId, mStatus, mState, (uint32_t)mEffectInterface); + result.append(buffer); + + result.append("\t\tDescriptor:\n"); + snprintf(buffer, SIZE, "\t\t- UUID: %08X-%04X-%04X-%04X-%02X%02X%02X%02X%02X%02X\n", + mDescriptor.uuid.timeLow, mDescriptor.uuid.timeMid, mDescriptor.uuid.timeHiAndVersion, + mDescriptor.uuid.clockSeq, mDescriptor.uuid.node[0], mDescriptor.uuid.node[1],mDescriptor.uuid.node[2], + mDescriptor.uuid.node[3],mDescriptor.uuid.node[4],mDescriptor.uuid.node[5]); + result.append(buffer); + snprintf(buffer, SIZE, "\t\t- TYPE: %08X-%04X-%04X-%04X-%02X%02X%02X%02X%02X%02X\n", + mDescriptor.type.timeLow, mDescriptor.type.timeMid, mDescriptor.type.timeHiAndVersion, + mDescriptor.type.clockSeq, mDescriptor.type.node[0], mDescriptor.type.node[1],mDescriptor.type.node[2], + mDescriptor.type.node[3],mDescriptor.type.node[4],mDescriptor.type.node[5]); + result.append(buffer); + snprintf(buffer, SIZE, "\t\t- apiVersion: %04X\n\t\t- flags: %08X\n", + mDescriptor.apiVersion, + mDescriptor.flags); + result.append(buffer); + snprintf(buffer, SIZE, "\t\t- name: %s\n", + mDescriptor.name); + result.append(buffer); + snprintf(buffer, SIZE, "\t\t- implementor: %s\n", + mDescriptor.implementor); + result.append(buffer); + + result.append("\t\t- Input configuration:\n"); + result.append("\t\t\tBuffer Frames Smp rate Channels Format\n"); + snprintf(buffer, SIZE, "\t\t\t0x%08x %05d %05d %08x %d\n", + (uint32_t)mConfig.inputCfg.buffer.raw, + mConfig.inputCfg.buffer.frameCount, + mConfig.inputCfg.samplingRate, + mConfig.inputCfg.channels, + mConfig.inputCfg.format); + result.append(buffer); + + result.append("\t\t- Output configuration:\n"); + result.append("\t\t\tBuffer Frames Smp rate Channels Format\n"); + snprintf(buffer, SIZE, "\t\t\t0x%08x %05d %05d %08x %d\n", + (uint32_t)mConfig.outputCfg.buffer.raw, + mConfig.outputCfg.buffer.frameCount, + mConfig.outputCfg.samplingRate, + mConfig.outputCfg.channels, + mConfig.outputCfg.format); + result.append(buffer); + + snprintf(buffer, SIZE, "\t\t%d Clients:\n", mHandles.size()); + result.append(buffer); + result.append("\t\t\tPid Priority Ctrl Locked client server\n"); + for (size_t i = 0; i < mHandles.size(); ++i) { + sp<EffectHandle> handle = mHandles[i].promote(); + if (handle != 0) { + handle->dump(buffer, SIZE); + result.append(buffer); + } + } + + result.append("\n"); + + write(fd, result.string(), result.length()); + + if (locked) { + mLock.unlock(); + } + + return NO_ERROR; +} + +// ---------------------------------------------------------------------------- +// EffectHandle implementation +// ---------------------------------------------------------------------------- + +#undef LOG_TAG +#define LOG_TAG "AudioFlinger::EffectHandle" + +AudioFlinger::EffectHandle::EffectHandle(const sp<EffectModule>& effect, + const sp<AudioFlinger::Client>& client, + const sp<IEffectClient>& effectClient, + int32_t priority) + : BnEffect(), + mEffect(effect), mEffectClient(effectClient), mClient(client), mPriority(priority), mHasControl(false) +{ + LOGV("constructor %p", this); + + int bufOffset = ((sizeof(effect_param_cblk_t) - 1) / sizeof(int) + 1) * sizeof(int); + mCblkMemory = client->heap()->allocate(EFFECT_PARAM_BUFFER_SIZE + bufOffset); + if (mCblkMemory != 0) { + mCblk = static_cast<effect_param_cblk_t *>(mCblkMemory->pointer()); + + if (mCblk) { + new(mCblk) effect_param_cblk_t(); + mBuffer = (uint8_t *)mCblk + bufOffset; + } + } else { + LOGE("not enough memory for Effect size=%u", EFFECT_PARAM_BUFFER_SIZE + sizeof(effect_param_cblk_t)); + return; + } +} + +AudioFlinger::EffectHandle::~EffectHandle() +{ + LOGV("Destructor %p", this); + disconnect(); +} + +status_t AudioFlinger::EffectHandle::enable() +{ + if (!mHasControl) return INVALID_OPERATION; + if (mEffect == 0) return DEAD_OBJECT; + + return mEffect->setEnabled(true); +} + +status_t AudioFlinger::EffectHandle::disable() +{ + if (!mHasControl) return INVALID_OPERATION; + if (mEffect == NULL) return DEAD_OBJECT; + + return mEffect->setEnabled(false); +} + +void AudioFlinger::EffectHandle::disconnect() +{ + if (mEffect == 0) { + return; + } + mEffect->disconnect(this); + // release sp on module => module destructor can be called now + mEffect.clear(); + if (mCblk) { + mCblk->~effect_param_cblk_t(); // destroy our shared-structure. + } + mCblkMemory.clear(); // and free the shared memory + if (mClient != 0) { + Mutex::Autolock _l(mClient->audioFlinger()->mLock); + mClient.clear(); + } +} + +status_t AudioFlinger::EffectHandle::command(uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t *replySize, + void *pReplyData) +{ +// LOGV("command(), cmdCode: %d, mHasControl: %d, mEffect: %p", +// cmdCode, mHasControl, (mEffect == 0) ? 0 : mEffect.get()); + + // only get parameter command is permitted for applications not controlling the effect + if (!mHasControl && cmdCode != EFFECT_CMD_GET_PARAM) { + return INVALID_OPERATION; + } + if (mEffect == 0) return DEAD_OBJECT; + + // handle commands that are not forwarded transparently to effect engine + if (cmdCode == EFFECT_CMD_SET_PARAM_COMMIT) { + // No need to trylock() here as this function is executed in the binder thread serving a particular client process: + // no risk to block the whole media server process or mixer threads is we are stuck here + Mutex::Autolock _l(mCblk->lock); + if (mCblk->clientIndex > EFFECT_PARAM_BUFFER_SIZE || + mCblk->serverIndex > EFFECT_PARAM_BUFFER_SIZE) { + mCblk->serverIndex = 0; + mCblk->clientIndex = 0; + return BAD_VALUE; + } + status_t status = NO_ERROR; + while (mCblk->serverIndex < mCblk->clientIndex) { + int reply; + uint32_t rsize = sizeof(int); + int *p = (int *)(mBuffer + mCblk->serverIndex); + int size = *p++; + if (((uint8_t *)p + size) > mBuffer + mCblk->clientIndex) { + LOGW("command(): invalid parameter block size"); + break; + } + effect_param_t *param = (effect_param_t *)p; + if (param->psize == 0 || param->vsize == 0) { + LOGW("command(): null parameter or value size"); + mCblk->serverIndex += size; + continue; + } + uint32_t psize = sizeof(effect_param_t) + + ((param->psize - 1) / sizeof(int) + 1) * sizeof(int) + + param->vsize; + status_t ret = mEffect->command(EFFECT_CMD_SET_PARAM, + psize, + p, + &rsize, + &reply); + // stop at first error encountered + if (ret != NO_ERROR) { + status = ret; + *(int *)pReplyData = reply; + break; + } else if (reply != NO_ERROR) { + *(int *)pReplyData = reply; + break; + } + mCblk->serverIndex += size; + } + mCblk->serverIndex = 0; + mCblk->clientIndex = 0; + return status; + } else if (cmdCode == EFFECT_CMD_ENABLE) { + *(int *)pReplyData = NO_ERROR; + return enable(); + } else if (cmdCode == EFFECT_CMD_DISABLE) { + *(int *)pReplyData = NO_ERROR; + return disable(); + } + + return mEffect->command(cmdCode, cmdSize, pCmdData, replySize, pReplyData); +} + +sp<IMemory> AudioFlinger::EffectHandle::getCblk() const { + return mCblkMemory; +} + +void AudioFlinger::EffectHandle::setControl(bool hasControl, bool signal) +{ + LOGV("setControl %p control %d", this, hasControl); + + mHasControl = hasControl; + if (signal && mEffectClient != 0) { + mEffectClient->controlStatusChanged(hasControl); + } +} + +void AudioFlinger::EffectHandle::commandExecuted(uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t replySize, + void *pReplyData) +{ + if (mEffectClient != 0) { + mEffectClient->commandExecuted(cmdCode, cmdSize, pCmdData, replySize, pReplyData); + } +} + + + +void AudioFlinger::EffectHandle::setEnabled(bool enabled) +{ + if (mEffectClient != 0) { + mEffectClient->enableStatusChanged(enabled); + } +} + +status_t AudioFlinger::EffectHandle::onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) +{ + return BnEffect::onTransact(code, data, reply, flags); +} + + +void AudioFlinger::EffectHandle::dump(char* buffer, size_t size) +{ + bool locked = tryLock(mCblk->lock); + + snprintf(buffer, size, "\t\t\t%05d %05d %01u %01u %05u %05u\n", + (mClient == NULL) ? getpid() : mClient->pid(), + mPriority, + mHasControl, + !locked, + mCblk->clientIndex, + mCblk->serverIndex + ); + + if (locked) { + mCblk->lock.unlock(); + } +} + +#undef LOG_TAG +#define LOG_TAG "AudioFlinger::EffectChain" + +AudioFlinger::EffectChain::EffectChain(const wp<ThreadBase>& wThread, + int sessionId) + : mThread(wThread), mSessionId(sessionId), mActiveTrackCnt(0), mOwnInBuffer(false), + mVolumeCtrlIdx(-1), mLeftVolume(UINT_MAX), mRightVolume(UINT_MAX), + mNewLeftVolume(UINT_MAX), mNewRightVolume(UINT_MAX) +{ + mStrategy = AudioSystem::getStrategyForStream(AudioSystem::MUSIC); +} + +AudioFlinger::EffectChain::~EffectChain() +{ + if (mOwnInBuffer) { + delete mInBuffer; + } + +} + +// getEffectFromDesc_l() must be called with PlaybackThread::mLock held +sp<AudioFlinger::EffectModule> AudioFlinger::EffectChain::getEffectFromDesc_l(effect_descriptor_t *descriptor) +{ + sp<EffectModule> effect; + size_t size = mEffects.size(); + + for (size_t i = 0; i < size; i++) { + if (memcmp(&mEffects[i]->desc().uuid, &descriptor->uuid, sizeof(effect_uuid_t)) == 0) { + effect = mEffects[i]; + break; + } + } + return effect; +} + +// getEffectFromId_l() must be called with PlaybackThread::mLock held +sp<AudioFlinger::EffectModule> AudioFlinger::EffectChain::getEffectFromId_l(int id) +{ + sp<EffectModule> effect; + size_t size = mEffects.size(); + + for (size_t i = 0; i < size; i++) { + // by convention, return first effect if id provided is 0 (0 is never a valid id) + if (id == 0 || mEffects[i]->id() == id) { + effect = mEffects[i]; + break; + } + } + return effect; +} + +// Must be called with EffectChain::mLock locked +void AudioFlinger::EffectChain::process_l() +{ + sp<ThreadBase> thread = mThread.promote(); + if (thread == 0) { + LOGW("process_l(): cannot promote mixer thread"); + return; + } + PlaybackThread *playbackThread = (PlaybackThread *)thread.get(); + bool isGlobalSession = (mSessionId == AudioSystem::SESSION_OUTPUT_MIX) || + (mSessionId == AudioSystem::SESSION_OUTPUT_STAGE); + bool tracksOnSession = false; + if (!isGlobalSession) { + tracksOnSession = + playbackThread->hasAudioSession(mSessionId) & PlaybackThread::TRACK_SESSION; + } + + size_t size = mEffects.size(); + // do not process effect if no track is present in same audio session + if (isGlobalSession || tracksOnSession) { + for (size_t i = 0; i < size; i++) { + mEffects[i]->process(); + } + } + for (size_t i = 0; i < size; i++) { + mEffects[i]->updateState(); + } + // if no track is active, input buffer must be cleared here as the mixer process + // will not do it + if (tracksOnSession && + activeTracks() == 0) { + size_t numSamples = playbackThread->frameCount() * playbackThread->channelCount(); + memset(mInBuffer, 0, numSamples * sizeof(int16_t)); + } +} + +// addEffect_l() must be called with PlaybackThread::mLock held +status_t AudioFlinger::EffectChain::addEffect_l(const sp<EffectModule>& effect) +{ + effect_descriptor_t desc = effect->desc(); + uint32_t insertPref = desc.flags & EFFECT_FLAG_INSERT_MASK; + + Mutex::Autolock _l(mLock); + effect->setChain(this); + sp<ThreadBase> thread = mThread.promote(); + if (thread == 0) { + return NO_INIT; + } + effect->setThread(thread); + + if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) { + // Auxiliary effects are inserted at the beginning of mEffects vector as + // they are processed first and accumulated in chain input buffer + mEffects.insertAt(effect, 0); + + // the input buffer for auxiliary effect contains mono samples in + // 32 bit format. This is to avoid saturation in AudoMixer + // accumulation stage. Saturation is done in EffectModule::process() before + // calling the process in effect engine + size_t numSamples = thread->frameCount(); + int32_t *buffer = new int32_t[numSamples]; + memset(buffer, 0, numSamples * sizeof(int32_t)); + effect->setInBuffer((int16_t *)buffer); + // auxiliary effects output samples to chain input buffer for further processing + // by insert effects + effect->setOutBuffer(mInBuffer); + } else { + // Insert effects are inserted at the end of mEffects vector as they are processed + // after track and auxiliary effects. + // Insert effect order as a function of indicated preference: + // if EFFECT_FLAG_INSERT_EXCLUSIVE, insert in first position or reject if + // another effect is present + // else if EFFECT_FLAG_INSERT_FIRST, insert in first position or after the + // last effect claiming first position + // else if EFFECT_FLAG_INSERT_LAST, insert in last position or before the + // first effect claiming last position + // else if EFFECT_FLAG_INSERT_ANY insert after first or before last + // Reject insertion if an effect with EFFECT_FLAG_INSERT_EXCLUSIVE is + // already present + + int size = (int)mEffects.size(); + int idx_insert = size; + int idx_insert_first = -1; + int idx_insert_last = -1; + + for (int i = 0; i < size; i++) { + effect_descriptor_t d = mEffects[i]->desc(); + uint32_t iMode = d.flags & EFFECT_FLAG_TYPE_MASK; + uint32_t iPref = d.flags & EFFECT_FLAG_INSERT_MASK; + if (iMode == EFFECT_FLAG_TYPE_INSERT) { + // check invalid effect chaining combinations + if (insertPref == EFFECT_FLAG_INSERT_EXCLUSIVE || + iPref == EFFECT_FLAG_INSERT_EXCLUSIVE) { + LOGW("addEffect_l() could not insert effect %s: exclusive conflict with %s", desc.name, d.name); + return INVALID_OPERATION; + } + // remember position of first insert effect and by default + // select this as insert position for new effect + if (idx_insert == size) { + idx_insert = i; + } + // remember position of last insert effect claiming + // first position + if (iPref == EFFECT_FLAG_INSERT_FIRST) { + idx_insert_first = i; + } + // remember position of first insert effect claiming + // last position + if (iPref == EFFECT_FLAG_INSERT_LAST && + idx_insert_last == -1) { + idx_insert_last = i; + } + } + } + + // modify idx_insert from first position if needed + if (insertPref == EFFECT_FLAG_INSERT_LAST) { + if (idx_insert_last != -1) { + idx_insert = idx_insert_last; + } else { + idx_insert = size; + } + } else { + if (idx_insert_first != -1) { + idx_insert = idx_insert_first + 1; + } + } + + // always read samples from chain input buffer + effect->setInBuffer(mInBuffer); + + // if last effect in the chain, output samples to chain + // output buffer, otherwise to chain input buffer + if (idx_insert == size) { + if (idx_insert != 0) { + mEffects[idx_insert-1]->setOutBuffer(mInBuffer); + mEffects[idx_insert-1]->configure(); + } + effect->setOutBuffer(mOutBuffer); + } else { + effect->setOutBuffer(mInBuffer); + } + mEffects.insertAt(effect, idx_insert); + + LOGV("addEffect_l() effect %p, added in chain %p at rank %d", effect.get(), this, idx_insert); + } + effect->configure(); + return NO_ERROR; +} + +// removeEffect_l() must be called with PlaybackThread::mLock held +size_t AudioFlinger::EffectChain::removeEffect_l(const sp<EffectModule>& effect) +{ + Mutex::Autolock _l(mLock); + int size = (int)mEffects.size(); + int i; + uint32_t type = effect->desc().flags & EFFECT_FLAG_TYPE_MASK; + + for (i = 0; i < size; i++) { + if (effect == mEffects[i]) { + if (type == EFFECT_FLAG_TYPE_AUXILIARY) { + delete[] effect->inBuffer(); + } else { + if (i == size - 1 && i != 0) { + mEffects[i - 1]->setOutBuffer(mOutBuffer); + mEffects[i - 1]->configure(); + } + } + mEffects.removeAt(i); + LOGV("removeEffect_l() effect %p, removed from chain %p at rank %d", effect.get(), this, i); + break; + } + } + + return mEffects.size(); +} + +// setDevice_l() must be called with PlaybackThread::mLock held +void AudioFlinger::EffectChain::setDevice_l(uint32_t device) +{ + size_t size = mEffects.size(); + for (size_t i = 0; i < size; i++) { + mEffects[i]->setDevice(device); + } +} + +// setMode_l() must be called with PlaybackThread::mLock held +void AudioFlinger::EffectChain::setMode_l(uint32_t mode) +{ + size_t size = mEffects.size(); + for (size_t i = 0; i < size; i++) { + mEffects[i]->setMode(mode); + } } +// setVolume_l() must be called with PlaybackThread::mLock held +bool AudioFlinger::EffectChain::setVolume_l(uint32_t *left, uint32_t *right) +{ + uint32_t newLeft = *left; + uint32_t newRight = *right; + bool hasControl = false; + int ctrlIdx = -1; + size_t size = mEffects.size(); + + // first update volume controller + for (size_t i = size; i > 0; i--) { + if (mEffects[i - 1]->isProcessEnabled() && + (mEffects[i - 1]->desc().flags & EFFECT_FLAG_VOLUME_MASK) == EFFECT_FLAG_VOLUME_CTRL) { + ctrlIdx = i - 1; + hasControl = true; + break; + } + } + + if (ctrlIdx == mVolumeCtrlIdx && *left == mLeftVolume && *right == mRightVolume) { + if (hasControl) { + *left = mNewLeftVolume; + *right = mNewRightVolume; + } + return hasControl; + } + + mVolumeCtrlIdx = ctrlIdx; + mLeftVolume = newLeft; + mRightVolume = newRight; + + // second get volume update from volume controller + if (ctrlIdx >= 0) { + mEffects[ctrlIdx]->setVolume(&newLeft, &newRight, true); + mNewLeftVolume = newLeft; + mNewRightVolume = newRight; + } + // then indicate volume to all other effects in chain. + // Pass altered volume to effects before volume controller + // and requested volume to effects after controller + uint32_t lVol = newLeft; + uint32_t rVol = newRight; + + for (size_t i = 0; i < size; i++) { + if ((int)i == ctrlIdx) continue; + // this also works for ctrlIdx == -1 when there is no volume controller + if ((int)i > ctrlIdx) { + lVol = *left; + rVol = *right; + } + mEffects[i]->setVolume(&lVol, &rVol, false); + } + *left = newLeft; + *right = newRight; + + return hasControl; +} + +status_t AudioFlinger::EffectChain::dump(int fd, const Vector<String16>& args) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + snprintf(buffer, SIZE, "Effects for session %d:\n", mSessionId); + result.append(buffer); + + bool locked = tryLock(mLock); + // failed to lock - AudioFlinger is probably deadlocked + if (!locked) { + result.append("\tCould not lock mutex:\n"); + } + + result.append("\tNum fx In buffer Out buffer Active tracks:\n"); + snprintf(buffer, SIZE, "\t%02d 0x%08x 0x%08x %d\n", + mEffects.size(), + (uint32_t)mInBuffer, + (uint32_t)mOutBuffer, + mActiveTrackCnt); + result.append(buffer); + write(fd, result.string(), result.size()); + + for (size_t i = 0; i < mEffects.size(); ++i) { + sp<EffectModule> effect = mEffects[i]; + if (effect != 0) { + effect->dump(fd, args); + } + } + + if (locked) { + mLock.unlock(); + } + + return NO_ERROR; +} + +#undef LOG_TAG +#define LOG_TAG "AudioFlinger" + // ---------------------------------------------------------------------------- -void AudioFlinger::instantiate() { - defaultServiceManager()->addService( - String16("media.audio_flinger"), new AudioFlinger()); +status_t AudioFlinger::onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) +{ + return BnAudioFlinger::onTransact(code, data, reply, flags); } }; // namespace android diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h index 739ec33..5917632 100644 --- a/services/audioflinger/AudioFlinger.h +++ b/services/audioflinger/AudioFlinger.h @@ -31,10 +31,12 @@ #include <utils/Atomic.h> #include <utils/Errors.h> #include <utils/threads.h> -#include <binder/MemoryDealer.h> #include <utils/SortedVector.h> #include <utils/Vector.h> +#include <binder/BinderService.h> +#include <binder/MemoryDealer.h> + #include <hardware_legacy/AudioHardwareInterface.h> #include "AudioBufferProvider.h" @@ -42,6 +44,7 @@ namespace android { class audio_track_cblk_t; +class effect_param_cblk_t; class AudioMixer; class AudioBuffer; class AudioResampler; @@ -57,10 +60,13 @@ class AudioResampler; static const nsecs_t kStandbyTimeInNsecs = seconds(3); -class AudioFlinger : public BnAudioFlinger, public IBinder::DeathRecipient +class AudioFlinger : + public BinderService<AudioFlinger>, + public BnAudioFlinger { + friend class BinderService<AudioFlinger>; public: - static void instantiate(); + static char const* getServiceName() { return "media.audio_flinger"; } virtual status_t dump(int fd, const Vector<String16>& args); @@ -75,6 +81,7 @@ public: uint32_t flags, const sp<IMemory>& sharedBuffer, int output, + int *sessionId, status_t *status); virtual uint32_t sampleRate(int output) const; @@ -139,8 +146,29 @@ public: virtual status_t getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames, int output); - // IBinder::DeathRecipient - virtual void binderDied(const wp<IBinder>& who); + virtual int newAudioSessionId(); + + virtual status_t loadEffectLibrary(const char *libPath, int *handle); + + virtual status_t unloadEffectLibrary(int handle); + + virtual status_t queryNumberEffects(uint32_t *numEffects); + + virtual status_t queryEffect(uint32_t index, effect_descriptor_t *descriptor); + + virtual status_t getEffectDescriptor(effect_uuid_t *pUuid, effect_descriptor_t *descriptor); + + virtual sp<IEffect> createEffect(pid_t pid, + effect_descriptor_t *pDesc, + const sp<IEffectClient>& effectClient, + int32_t priority, + int output, + int sessionId, + status_t *status, + int *id, + int *enabled); + + virtual status_t moveEffects(int session, int srcOutput, int dstOutput); enum hardware_call_state { AUDIO_HW_IDLE = 0, @@ -170,6 +198,7 @@ public: int channelCount, int frameCount, uint32_t flags, + int *sessionId, status_t *status); virtual status_t onTransact( @@ -178,6 +207,8 @@ public: Parcel* reply, uint32_t flags); + uint32_t getMode() { return mMode; } + private: AudioFlinger(); virtual ~AudioFlinger(); @@ -205,6 +236,27 @@ private: pid_t mPid; }; + // --- Notification Client --- + class NotificationClient : public IBinder::DeathRecipient { + public: + NotificationClient(const sp<AudioFlinger>& audioFlinger, + const sp<IAudioFlingerClient>& client, + pid_t pid); + virtual ~NotificationClient(); + + sp<IAudioFlingerClient> client() { return mClient; } + + // IBinder::DeathRecipient + virtual void binderDied(const wp<IBinder>& who); + + private: + NotificationClient(const NotificationClient&); + NotificationClient& operator = (const NotificationClient&); + + sp<AudioFlinger> mAudioFlinger; + pid_t mPid; + sp<IAudioFlingerClient> mClient; + }; class TrackHandle; class RecordHandle; @@ -215,6 +267,9 @@ private: class DuplicatingThread; class Track; class RecordTrack; + class EffectModule; + class EffectHandle; + class EffectChain; class ThreadBase : public Thread { public: @@ -250,13 +305,15 @@ private: int channelCount, int frameCount, uint32_t flags, - const sp<IMemory>& sharedBuffer); + const sp<IMemory>& sharedBuffer, + int sessionId); ~TrackBase(); virtual status_t start() = 0; virtual void stop() = 0; sp<IMemory> getCblk() const; audio_track_cblk_t* cblk() const { return mCblk; } + int sessionId() { return mSessionId; } protected: friend class ThreadBase; @@ -305,6 +362,7 @@ private: int mClientTid; uint8_t mFormat; uint32_t mFlags; + int mSessionId; }; class ConfigEvent { @@ -324,7 +382,7 @@ private: virtual bool checkForNewParameters_l() = 0; virtual status_t setParameters(const String8& keyValuePairs); virtual String8 getParameters(const String8& keys) = 0; - virtual void audioConfigChanged(int event, int param = 0) = 0; + virtual void audioConfigChanged_l(int event, int param = 0) = 0; void sendConfigEvent(int event, int param = 0); void sendConfigEvent_l(int event, int param = 0); void processConfigEvents(); @@ -348,9 +406,10 @@ private: sp<AudioFlinger> mAudioFlinger; uint32_t mSampleRate; size_t mFrameCount; - int mChannelCount; + uint32_t mChannels; + uint16_t mChannelCount; + uint16_t mFrameSize; int mFormat; - uint32_t mFrameSize; Condition mParamCond; Vector<String8> mNewParameters; status_t mParamStatus; @@ -386,7 +445,8 @@ private: int format, int channelCount, int frameCount, - const sp<IMemory>& sharedBuffer); + const sp<IMemory>& sharedBuffer, + int sessionId); ~Track(); void dump(char* buffer, size_t size); @@ -405,6 +465,12 @@ private: int type() const { return mStreamType; } + status_t attachAuxEffect(int EffectId); + void setAuxBuffer(int EffectId, int32_t *buffer); + int32_t *auxBuffer() { return mAuxBuffer; } + void setMainBuffer(int16_t *buffer) { mMainBuffer = buffer; } + int16_t *mainBuffer() { return mMainBuffer; } + int auxEffectId() { return mAuxEffectId; } protected: @@ -445,6 +511,10 @@ private: bool mResetDone; int mStreamType; int mName; + int16_t *mMainBuffer; + int32_t *mAuxBuffer; + int mAuxEffectId; + bool mHasVolumeController; }; // end of Track @@ -486,7 +556,7 @@ private: DuplicatingThread* mSourceThread; }; // end of OutputTrack - PlaybackThread (const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id); + PlaybackThread (const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id, uint32_t device); virtual ~PlaybackThread(); virtual status_t dump(int fd, const Vector<String16>& args); @@ -519,6 +589,7 @@ private: int channelCount, int frameCount, const sp<IMemory>& sharedBuffer, + int sessionId, status_t *status); AudioStreamOut* getOutput() { return mOutput; } @@ -528,8 +599,49 @@ private: void restore() { if (mSuspended) mSuspended--; } bool isSuspended() { return (mSuspended != 0); } virtual String8 getParameters(const String8& keys); - virtual void audioConfigChanged(int event, int param = 0); + virtual void audioConfigChanged_l(int event, int param = 0); virtual status_t getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames); + int16_t *mixBuffer() { return mMixBuffer; }; + + sp<EffectHandle> createEffect_l( + const sp<AudioFlinger::Client>& client, + const sp<IEffectClient>& effectClient, + int32_t priority, + int sessionId, + effect_descriptor_t *desc, + int *enabled, + status_t *status); + void disconnectEffect(const sp< EffectModule>& effect, + const wp<EffectHandle>& handle); + + // return values for hasAudioSession (bit field) + enum effect_state { + EFFECT_SESSION = 0x1, // the audio session corresponds to at least one + // effect + TRACK_SESSION = 0x2 // the audio session corresponds to at least one + // track + }; + + uint32_t hasAudioSession(int sessionId); + sp<EffectChain> getEffectChain(int sessionId); + sp<EffectChain> getEffectChain_l(int sessionId); + status_t addEffectChain_l(const sp<EffectChain>& chain); + size_t removeEffectChain_l(const sp<EffectChain>& chain); + void lockEffectChains_l(Vector<sp <EffectChain> >& effectChains); + void unlockEffectChains(Vector<sp <EffectChain> >& effectChains); + + sp<AudioFlinger::EffectModule> getEffect_l(int sessionId, int effectId); + void detachAuxEffect_l(int effectId); + status_t attachAuxEffect(const sp<AudioFlinger::PlaybackThread::Track> track, + int EffectId); + status_t attachAuxEffect_l(const sp<AudioFlinger::PlaybackThread::Track> track, + int EffectId); + void setMode(uint32_t mode); + + status_t addEffect_l(const sp< EffectModule>& effect); + void removeEffect_l(const sp< EffectModule>& effect); + + uint32_t getStrategyForSession_l(int sessionId); struct stream_type_t { stream_type_t() @@ -553,6 +665,7 @@ private: virtual void deleteTrackName_l(int name) = 0; virtual uint32_t activeSleepTimeUs() = 0; virtual uint32_t idleSleepTimeUs() = 0; + virtual uint32_t suspendSleepTimeUs() = 0; private: @@ -572,8 +685,11 @@ private: void readOutputParameters(); + uint32_t device() { return mDevice; } + virtual status_t dumpInternals(int fd, const Vector<String16>& args); status_t dumpTracks(int fd, const Vector<String16>& args); + status_t dumpEffectChains(int fd, const Vector<String16>& args); SortedVector< sp<Track> > mTracks; // mStreamTypes[] uses 1 additionnal stream type internally for the OutputTrack used by DuplicatingThread @@ -584,30 +700,33 @@ private: int mNumWrites; int mNumDelayedWrites; bool mInWrite; + Vector< sp<EffectChain> > mEffectChains; + uint32_t mDevice; }; class MixerThread : public PlaybackThread { public: - MixerThread (const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id); + MixerThread (const sp<AudioFlinger>& audioFlinger, + AudioStreamOut* output, + int id, + uint32_t device); virtual ~MixerThread(); // Thread virtuals virtual bool threadLoop(); - void getTracks(SortedVector < sp<Track> >& tracks, - SortedVector < wp<Track> >& activeTracks, - int streamType); - void putTracks(SortedVector < sp<Track> >& tracks, - SortedVector < wp<Track> >& activeTracks); + void invalidateTracks(int streamType); virtual bool checkForNewParameters_l(); virtual status_t dumpInternals(int fd, const Vector<String16>& args); protected: - uint32_t prepareTracks_l(const SortedVector< wp<Track> >& activeTracks, Vector< sp<Track> > *tracksToRemove); + uint32_t prepareTracks_l(const SortedVector< wp<Track> >& activeTracks, + Vector< sp<Track> > *tracksToRemove); virtual int getTrackName_l(); virtual void deleteTrackName_l(int name); virtual uint32_t activeSleepTimeUs(); virtual uint32_t idleSleepTimeUs(); + virtual uint32_t suspendSleepTimeUs(); AudioMixer* mAudioMixer; }; @@ -615,7 +734,7 @@ private: class DirectOutputThread : public PlaybackThread { public: - DirectOutputThread (const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id); + DirectOutputThread (const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id, uint32_t device); ~DirectOutputThread(); // Thread virtuals @@ -628,10 +747,15 @@ private: virtual void deleteTrackName_l(int name); virtual uint32_t activeSleepTimeUs(); virtual uint32_t idleSleepTimeUs(); + virtual uint32_t suspendSleepTimeUs(); private: - float mLeftVolume; - float mRightVolume; + void applyVolume(uint16_t leftVol, uint16_t rightVol, bool ramp); + + float mLeftVolFloat; + float mRightVolFloat; + uint16_t mLeftVolShort; + uint16_t mRightVolShort; }; class DuplicatingThread : public MixerThread { @@ -661,6 +785,12 @@ private: float streamVolumeInternal(int stream) const { return mStreamTypes[stream].volume; } void audioConfigChanged_l(int event, int ioHandle, void *param2); + int nextUniqueId(); + status_t moveEffectChain_l(int session, + AudioFlinger::PlaybackThread *srcThread, + AudioFlinger::PlaybackThread *dstThread, + bool reRegister); + friend class AudioBuffer; class TrackHandle : public android::BnAudioTrack { @@ -674,6 +804,7 @@ private: virtual void pause(); virtual void setVolume(float left, float right); virtual sp<IMemory> getCblk() const; + virtual status_t attachAuxEffect(int effectId); virtual status_t onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags); private: @@ -685,6 +816,7 @@ private: void removeClient_l(pid_t pid); + void removeNotificationClient(pid_t pid); // record thread @@ -701,7 +833,8 @@ private: int format, int channelCount, int frameCount, - uint32_t flags); + uint32_t flags, + int sessionId); ~RecordTrack(); virtual status_t start(); @@ -744,7 +877,7 @@ private: virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer); virtual bool checkForNewParameters_l(); virtual String8 getParameters(const String8& keys); - virtual void audioConfigChanged(int event, int param = 0); + virtual void audioConfigChanged_l(int event, int param = 0); void readInputParameters(); virtual unsigned int getInputFramesLost(); @@ -776,6 +909,260 @@ private: sp<RecordThread::RecordTrack> mRecordTrack; }; + //--- Audio Effect Management + + // EffectModule and EffectChain classes both have their own mutex to protect + // state changes or resource modifications. Always respect the following order + // if multiple mutexes must be acquired to avoid cross deadlock: + // AudioFlinger -> ThreadBase -> EffectChain -> EffectModule + + // The EffectModule class is a wrapper object controlling the effect engine implementation + // in the effect library. It prevents concurrent calls to process() and command() functions + // from different client threads. It keeps a list of EffectHandle objects corresponding + // to all client applications using this effect and notifies applications of effect state, + // control or parameter changes. It manages the activation state machine to send appropriate + // reset, enable, disable commands to effect engine and provide volume + // ramping when effects are activated/deactivated. + // When controlling an auxiliary effect, the EffectModule also provides an input buffer used by + // the attached track(s) to accumulate their auxiliary channel. + class EffectModule: public RefBase { + public: + EffectModule(const wp<ThreadBase>& wThread, + const wp<AudioFlinger::EffectChain>& chain, + effect_descriptor_t *desc, + int id, + int sessionId); + ~EffectModule(); + + enum effect_state { + IDLE, + RESTART, + STARTING, + ACTIVE, + STOPPING, + STOPPED + }; + + int id() { return mId; } + void process(); + void updateState(); + status_t command(uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t *replySize, + void *pReplyData); + + void reset_l(); + status_t configure(); + status_t init(); + uint32_t state() { + return mState; + } + uint32_t status() { + return mStatus; + } + int sessionId() { + return mSessionId; + } + status_t setEnabled(bool enabled); + bool isEnabled(); + bool isProcessEnabled(); + + void setInBuffer(int16_t *buffer) { mConfig.inputCfg.buffer.s16 = buffer; } + int16_t *inBuffer() { return mConfig.inputCfg.buffer.s16; } + void setOutBuffer(int16_t *buffer) { mConfig.outputCfg.buffer.s16 = buffer; } + int16_t *outBuffer() { return mConfig.outputCfg.buffer.s16; } + void setChain(const wp<EffectChain>& chain) { mChain = chain; } + void setThread(const wp<ThreadBase>& thread) { mThread = thread; } + + status_t addHandle(sp<EffectHandle>& handle); + void disconnect(const wp<EffectHandle>& handle); + size_t removeHandle (const wp<EffectHandle>& handle); + + effect_descriptor_t& desc() { return mDescriptor; } + wp<EffectChain>& chain() { return mChain; } + + status_t setDevice(uint32_t device); + status_t setVolume(uint32_t *left, uint32_t *right, bool controller); + status_t setMode(uint32_t mode); + + status_t dump(int fd, const Vector<String16>& args); + + protected: + + // Maximum time allocated to effect engines to complete the turn off sequence + static const uint32_t MAX_DISABLE_TIME_MS = 10000; + + EffectModule(const EffectModule&); + EffectModule& operator = (const EffectModule&); + + status_t start_l(); + status_t stop_l(); + + // update this table when AudioSystem::audio_devices or audio_device_e (in EffectApi.h) are modified + static const uint32_t sDeviceConvTable[]; + static uint32_t deviceAudioSystemToEffectApi(uint32_t device); + + // update this table when AudioSystem::audio_mode or audio_mode_e (in EffectApi.h) are modified + static const uint32_t sModeConvTable[]; + static int modeAudioSystemToEffectApi(uint32_t mode); + + Mutex mLock; // mutex for process, commands and handles list protection + wp<ThreadBase> mThread; // parent thread + wp<EffectChain> mChain; // parent effect chain + int mId; // this instance unique ID + int mSessionId; // audio session ID + effect_descriptor_t mDescriptor;// effect descriptor received from effect engine + effect_config_t mConfig; // input and output audio configuration + effect_interface_t mEffectInterface; // Effect module C API + status_t mStatus; // initialization status + uint32_t mState; // current activation state (effect_state) + Vector< wp<EffectHandle> > mHandles; // list of client handles + uint32_t mMaxDisableWaitCnt; // maximum grace period before forcing an effect off after + // sending disable command. + uint32_t mDisableWaitCnt; // current process() calls count during disable period. + }; + + // The EffectHandle class implements the IEffect interface. It provides resources + // to receive parameter updates, keeps track of effect control + // ownership and state and has a pointer to the EffectModule object it is controlling. + // There is one EffectHandle object for each application controlling (or using) + // an effect module. + // The EffectHandle is obtained by calling AudioFlinger::createEffect(). + class EffectHandle: public android::BnEffect { + public: + + EffectHandle(const sp<EffectModule>& effect, + const sp<AudioFlinger::Client>& client, + const sp<IEffectClient>& effectClient, + int32_t priority); + virtual ~EffectHandle(); + + // IEffect + virtual status_t enable(); + virtual status_t disable(); + virtual status_t command(uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t *replySize, + void *pReplyData); + virtual void disconnect(); + virtual sp<IMemory> getCblk() const; + virtual status_t onTransact(uint32_t code, const Parcel& data, + Parcel* reply, uint32_t flags); + + + // Give or take control of effect module + void setControl(bool hasControl, bool signal); + void commandExecuted(uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t replySize, + void *pReplyData); + void setEnabled(bool enabled); + + // Getters + int id() { return mEffect->id(); } + int priority() { return mPriority; } + bool hasControl() { return mHasControl; } + sp<EffectModule> effect() { return mEffect; } + + void dump(char* buffer, size_t size); + + protected: + + EffectHandle(const EffectHandle&); + EffectHandle& operator =(const EffectHandle&); + + sp<EffectModule> mEffect; // pointer to controlled EffectModule + sp<IEffectClient> mEffectClient; // callback interface for client notifications + sp<Client> mClient; // client for shared memory allocation + sp<IMemory> mCblkMemory; // shared memory for control block + effect_param_cblk_t* mCblk; // control block for deferred parameter setting via shared memory + uint8_t* mBuffer; // pointer to parameter area in shared memory + int mPriority; // client application priority to control the effect + bool mHasControl; // true if this handle is controlling the effect + }; + + // the EffectChain class represents a group of effects associated to one audio session. + // There can be any number of EffectChain objects per output mixer thread (PlaybackThread). + // The EffecChain with session ID 0 contains global effects applied to the output mix. + // Effects in this chain can be insert or auxiliary. Effects in other chains (attached to tracks) + // are insert only. The EffectChain maintains an ordered list of effect module, the order corresponding + // in the effect process order. When attached to a track (session ID != 0), it also provide it's own + // input buffer used by the track as accumulation buffer. + class EffectChain: public RefBase { + public: + EffectChain(const wp<ThreadBase>& wThread, int sessionId); + ~EffectChain(); + + void process_l(); + + void lock() { + mLock.lock(); + } + void unlock() { + mLock.unlock(); + } + + status_t addEffect_l(const sp<EffectModule>& handle); + size_t removeEffect_l(const sp<EffectModule>& handle); + + int sessionId() { + return mSessionId; + } + + sp<EffectModule> getEffectFromDesc_l(effect_descriptor_t *descriptor); + sp<EffectModule> getEffectFromId_l(int id); + bool setVolume_l(uint32_t *left, uint32_t *right); + void setDevice_l(uint32_t device); + void setMode_l(uint32_t mode); + + void setInBuffer(int16_t *buffer, bool ownsBuffer = false) { + mInBuffer = buffer; + mOwnInBuffer = ownsBuffer; + } + int16_t *inBuffer() { + return mInBuffer; + } + void setOutBuffer(int16_t *buffer) { + mOutBuffer = buffer; + } + int16_t *outBuffer() { + return mOutBuffer; + } + + void startTrack() {mActiveTrackCnt++;} + void stopTrack() {mActiveTrackCnt--;} + int activeTracks() { return mActiveTrackCnt;} + + uint32_t strategy() { return mStrategy; } + void setStrategy(uint32_t strategy) + { mStrategy = strategy; } + + status_t dump(int fd, const Vector<String16>& args); + + protected: + + EffectChain(const EffectChain&); + EffectChain& operator =(const EffectChain&); + + wp<ThreadBase> mThread; // parent mixer thread + Mutex mLock; // mutex protecting effect list + Vector<sp<EffectModule> > mEffects; // list of effect modules + int mSessionId; // audio session ID + int16_t *mInBuffer; // chain input buffer + int16_t *mOutBuffer; // chain output buffer + int mActiveTrackCnt; // number of active tracks connected + bool mOwnInBuffer; // true if the chain owns its input buffer + int mVolumeCtrlIdx; // index of insert effect having control over volume + uint32_t mLeftVolume; // previous volume on left channel + uint32_t mRightVolume; // previous volume on right channel + uint32_t mNewLeftVolume; // new volume on left channel + uint32_t mNewRightVolume; // new volume on right channel + uint32_t mStrategy; // strategy for this effect chain + }; + friend class RecordThread; friend class PlaybackThread; @@ -796,8 +1183,13 @@ private: DefaultKeyedVector< int, sp<RecordThread> > mRecordThreads; - SortedVector< sp<IBinder> > mNotificationClients; - int mNextThreadId; + 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 19a442a..8aaa325 100644 --- a/services/audioflinger/AudioMixer.cpp +++ b/services/audioflinger/AudioMixer.cpp @@ -56,6 +56,8 @@ AudioMixer::AudioMixer(size_t frameCount, uint32_t sampleRate) t->volume[1] = UNITY_GAIN; t->volumeInc[0] = 0; t->volumeInc[1] = 0; + t->auxLevel = 0; + t->auxInc = 0; t->channelCount = 2; t->enabled = 0; t->format = 16; @@ -65,6 +67,8 @@ AudioMixer::AudioMixer(size_t frameCount, uint32_t sampleRate) t->resampler = 0; t->sampleRate = mSampleRate; t->in = 0; + t->mainBuffer = NULL; + t->auxBuffer = NULL; t++; } } @@ -169,28 +173,48 @@ status_t AudioMixer::setActiveTrack(int track) return NO_ERROR; } -status_t AudioMixer::setParameter(int target, int name, int value) +status_t AudioMixer::setParameter(int target, int name, void *value) { + int valueInt = (int)value; + int32_t *valueBuf = (int32_t *)value; + switch (target) { case TRACK: if (name == CHANNEL_COUNT) { - if ((uint32_t(value) <= MAX_NUM_CHANNELS) && (value)) { - if (mState.tracks[ mActiveTrack ].channelCount != value) { - mState.tracks[ mActiveTrack ].channelCount = value; - LOGV("setParameter(TRACK, CHANNEL_COUNT, %d)", value); + if ((uint32_t(valueInt) <= MAX_NUM_CHANNELS) && (valueInt)) { + if (mState.tracks[ mActiveTrack ].channelCount != valueInt) { + mState.tracks[ mActiveTrack ].channelCount = valueInt; + LOGV("setParameter(TRACK, CHANNEL_COUNT, %d)", valueInt); invalidateState(1<<mActiveTrack); } return NO_ERROR; } } + if (name == MAIN_BUFFER) { + if (mState.tracks[ mActiveTrack ].mainBuffer != valueBuf) { + mState.tracks[ mActiveTrack ].mainBuffer = valueBuf; + LOGV("setParameter(TRACK, MAIN_BUFFER, %p)", valueBuf); + invalidateState(1<<mActiveTrack); + } + return NO_ERROR; + } + if (name == AUX_BUFFER) { + if (mState.tracks[ mActiveTrack ].auxBuffer != valueBuf) { + mState.tracks[ mActiveTrack ].auxBuffer = valueBuf; + LOGV("setParameter(TRACK, AUX_BUFFER, %p)", valueBuf); + invalidateState(1<<mActiveTrack); + } + return NO_ERROR; + } + break; case RESAMPLE: if (name == SAMPLE_RATE) { - if (value > 0) { + if (valueInt > 0) { track_t& track = mState.tracks[ mActiveTrack ]; - if (track.setResampler(uint32_t(value), mSampleRate)) { + if (track.setResampler(uint32_t(valueInt), mSampleRate)) { LOGV("setParameter(RESAMPLE, SAMPLE_RATE, %u)", - uint32_t(value)); + uint32_t(valueInt)); invalidateState(1<<mActiveTrack); } return NO_ERROR; @@ -201,18 +225,39 @@ status_t AudioMixer::setParameter(int target, int name, int value) case VOLUME: if ((uint32_t(name-VOLUME0) < MAX_NUM_CHANNELS)) { track_t& track = mState.tracks[ mActiveTrack ]; - if (track.volume[name-VOLUME0] != value) { + if (track.volume[name-VOLUME0] != valueInt) { + LOGV("setParameter(VOLUME, VOLUME0/1: %04x)", valueInt); track.prevVolume[name-VOLUME0] = track.volume[name-VOLUME0] << 16; - track.volume[name-VOLUME0] = value; + track.volume[name-VOLUME0] = valueInt; if (target == VOLUME) { - track.prevVolume[name-VOLUME0] = value << 16; + track.prevVolume[name-VOLUME0] = valueInt << 16; track.volumeInc[name-VOLUME0] = 0; } else { - int32_t d = (value<<16) - track.prevVolume[name-VOLUME0]; + int32_t d = (valueInt<<16) - track.prevVolume[name-VOLUME0]; int32_t volInc = d / int32_t(mState.frameCount); track.volumeInc[name-VOLUME0] = volInc; if (volInc == 0) { - track.prevVolume[name-VOLUME0] = value << 16; + track.prevVolume[name-VOLUME0] = valueInt << 16; + } + } + invalidateState(1<<mActiveTrack); + } + return NO_ERROR; + } else if (name == AUXLEVEL) { + track_t& track = mState.tracks[ mActiveTrack ]; + if (track.auxLevel != valueInt) { + LOGV("setParameter(VOLUME, AUXLEVEL: %04x)", valueInt); + track.prevAuxLevel = track.auxLevel << 16; + track.auxLevel = valueInt; + if (target == VOLUME) { + track.prevAuxLevel = valueInt << 16; + track.auxInc = 0; + } else { + int32_t d = (valueInt<<16) - track.prevAuxLevel; + int32_t volInc = d / int32_t(mState.frameCount); + track.auxInc = volInc; + if (volInc == 0) { + track.prevAuxLevel = valueInt << 16; } } invalidateState(1<<mActiveTrack); @@ -245,7 +290,7 @@ bool AudioMixer::track_t::doesResample() const } inline -void AudioMixer::track_t::adjustVolumeRamp() +void AudioMixer::track_t::adjustVolumeRamp(bool aux) { for (int i=0 ; i<2 ; i++) { if (((volumeInc[i]>0) && (((prevVolume[i]+volumeInc[i])>>16) >= volume[i])) || @@ -254,6 +299,13 @@ void AudioMixer::track_t::adjustVolumeRamp() prevVolume[i] = volume[i]<<16; } } + if (aux) { + if (((auxInc>0) && (((prevAuxLevel+auxInc)>>16) >= auxLevel)) || + ((auxInc<0) && (((prevAuxLevel+auxInc)>>16) <= auxLevel))) { + auxInc = 0; + prevAuxLevel = auxLevel<<16; + } + } } @@ -265,13 +317,13 @@ status_t AudioMixer::setBufferProvider(AudioBufferProvider* buffer) -void AudioMixer::process(void* output) +void AudioMixer::process() { - mState.hook(&mState, output); + mState.hook(&mState); } -void AudioMixer::process__validate(state_t* state, void* output) +void AudioMixer::process__validate(state_t* state) { LOGW_IF(!state->needsChanged, "in process__validate() but nothing's invalid"); @@ -308,7 +360,10 @@ void AudioMixer::process__validate(state_t* state, void* output) n |= NEEDS_CHANNEL_1 + t.channelCount - 1; n |= NEEDS_FORMAT_16; n |= t.doesResample() ? NEEDS_RESAMPLE_ENABLED : NEEDS_RESAMPLE_DISABLED; - + if (t.auxLevel != 0 && t.auxBuffer != NULL) { + n |= NEEDS_AUX_ENABLED; + } + if (t.volumeInc[0]|t.volumeInc[1]) { volumeRamp = 1; } else if (!t.doesResample() && t.volumeRL == 0) { @@ -319,6 +374,9 @@ void AudioMixer::process__validate(state_t* state, void* output) if ((n & NEEDS_MUTE__MASK) == NEEDS_MUTE_ENABLED) { t.hook = track__nop; } else { + if ((n & NEEDS_AUX__MASK) == NEEDS_AUX_ENABLED) { + all16BitsStereoNoResample = 0; + } if ((n & NEEDS_RESAMPLE__MASK) == NEEDS_RESAMPLE_ENABLED) { all16BitsStereoNoResample = 0; resampling = 1; @@ -369,7 +427,7 @@ void AudioMixer::process__validate(state_t* state, void* output) countActiveTracks, state->enabledTracks, all16BitsStereoNoResample, resampling, volumeRamp); - state->hook(state, output); + state->hook(state); // Now that the volume ramp has been done, set optimal state and // track hooks for subsequent mixer process @@ -390,7 +448,7 @@ void AudioMixer::process__validate(state_t* state, void* output) } if (allMuted) { state->hook = process__nop; - } else if (!resampling && all16BitsStereoNoResample) { + } else if (all16BitsStereoNoResample) { if (countActiveTracks == 1) { state->hook = process__OneTrack16BitsStereoNoResampling; } @@ -481,30 +539,44 @@ int32_t mulRL(int left, uint32_t inRL, uint32_t vRL) } -void AudioMixer::track__genericResample(track_t* t, int32_t* out, size_t outFrameCount, int32_t* temp) +void AudioMixer::track__genericResample(track_t* t, int32_t* out, size_t outFrameCount, int32_t* temp, int32_t* aux) { t->resampler->setSampleRate(t->sampleRate); // ramp gain - resample to temp buffer and scale/mix in 2nd step - if UNLIKELY(t->volumeInc[0]|t->volumeInc[1]) { + if (aux != NULL) { + // always resample with unity gain when sending to auxiliary buffer to be able + // to apply send level after resampling + // TODO: modify each resampler to support aux channel? t->resampler->setVolume(UNITY_GAIN, UNITY_GAIN); memset(temp, 0, outFrameCount * MAX_NUM_CHANNELS * sizeof(int32_t)); t->resampler->resample(temp, outFrameCount, t->bufferProvider); - volumeRampStereo(t, out, outFrameCount, temp); - } + if UNLIKELY(t->volumeInc[0]|t->volumeInc[1]|t->auxInc) { + volumeRampStereo(t, out, outFrameCount, temp, aux); + } else { + volumeStereo(t, out, outFrameCount, temp, aux); + } + } else { + if UNLIKELY(t->volumeInc[0]|t->volumeInc[1]) { + t->resampler->setVolume(UNITY_GAIN, UNITY_GAIN); + memset(temp, 0, outFrameCount * MAX_NUM_CHANNELS * sizeof(int32_t)); + t->resampler->resample(temp, outFrameCount, t->bufferProvider); + volumeRampStereo(t, out, outFrameCount, temp, aux); + } - // constant gain - else { - t->resampler->setVolume(t->volume[0], t->volume[1]); - t->resampler->resample(out, outFrameCount, t->bufferProvider); + // constant gain + else { + t->resampler->setVolume(t->volume[0], t->volume[1]); + t->resampler->resample(out, outFrameCount, t->bufferProvider); + } } } -void AudioMixer::track__nop(track_t* t, int32_t* out, size_t outFrameCount, int32_t* temp) +void AudioMixer::track__nop(track_t* t, int32_t* out, size_t outFrameCount, int32_t* temp, int32_t* aux) { } -void AudioMixer::volumeRampStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp) +void AudioMixer::volumeRampStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux) { int32_t vl = t->prevVolume[0]; int32_t vr = t->prevVolume[1]; @@ -514,98 +586,238 @@ void AudioMixer::volumeRampStereo(track_t* t, int32_t* out, size_t frameCount, i //LOGD("[0] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], // (vl + vlInc*frameCount)/65536.0f, frameCount); - + // ramp volume - do { - *out++ += (vl >> 16) * (*temp++ >> 12); - *out++ += (vr >> 16) * (*temp++ >> 12); - vl += vlInc; - vr += vrInc; - } while (--frameCount); + if UNLIKELY(aux != NULL) { + int32_t va = t->prevAuxLevel; + const int32_t vaInc = t->auxInc; + int32_t l; + int32_t r; + do { + l = (*temp++ >> 12); + r = (*temp++ >> 12); + *out++ += (vl >> 16) * l; + *out++ += (vr >> 16) * r; + *aux++ += (va >> 17) * (l + r); + vl += vlInc; + vr += vrInc; + va += vaInc; + } while (--frameCount); + t->prevAuxLevel = va; + } else { + do { + *out++ += (vl >> 16) * (*temp++ >> 12); + *out++ += (vr >> 16) * (*temp++ >> 12); + vl += vlInc; + vr += vrInc; + } while (--frameCount); + } t->prevVolume[0] = vl; t->prevVolume[1] = vr; - t->adjustVolumeRamp(); + t->adjustVolumeRamp((aux != NULL)); } -void AudioMixer::track__16BitsStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp) +void AudioMixer::volumeStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux) { - int16_t const *in = static_cast<int16_t const *>(t->in); - - // ramp gain - if UNLIKELY(t->volumeInc[0]|t->volumeInc[1]) { - int32_t vl = t->prevVolume[0]; - int32_t vr = t->prevVolume[1]; - const int32_t vlInc = t->volumeInc[0]; - const int32_t vrInc = t->volumeInc[1]; - - // LOGD("[1] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", - // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], - // (vl + vlInc*frameCount)/65536.0f, frameCount); + const int16_t vl = t->volume[0]; + const int16_t vr = t->volume[1]; + if UNLIKELY(aux != NULL) { + const int16_t va = (int16_t)t->auxLevel; do { - *out++ += (vl >> 16) * (int32_t) *in++; - *out++ += (vr >> 16) * (int32_t) *in++; - vl += vlInc; - vr += vrInc; + int16_t l = (int16_t)(*temp++ >> 12); + int16_t r = (int16_t)(*temp++ >> 12); + out[0] = mulAdd(l, vl, out[0]); + int16_t a = (int16_t)(((int32_t)l + r) >> 1); + out[1] = mulAdd(r, vr, out[1]); + out += 2; + aux[0] = mulAdd(a, va, aux[0]); + aux++; } while (--frameCount); - - t->prevVolume[0] = vl; - t->prevVolume[1] = vr; - t->adjustVolumeRamp(); - } - - // constant gain - else { - const uint32_t vrl = t->volumeRL; + } else { do { - uint32_t rl = *reinterpret_cast<uint32_t const *>(in); - in += 2; - out[0] = mulAddRL(1, rl, vrl, out[0]); - out[1] = mulAddRL(0, rl, vrl, out[1]); + int16_t l = (int16_t)(*temp++ >> 12); + int16_t r = (int16_t)(*temp++ >> 12); + out[0] = mulAdd(l, vl, out[0]); + out[1] = mulAdd(r, vr, out[1]); out += 2; } while (--frameCount); } +} + +void AudioMixer::track__16BitsStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux) +{ + int16_t const *in = static_cast<int16_t const *>(t->in); + + if UNLIKELY(aux != NULL) { + int32_t l; + int32_t r; + // ramp gain + if UNLIKELY(t->volumeInc[0]|t->volumeInc[1]|t->auxInc) { + int32_t vl = t->prevVolume[0]; + int32_t vr = t->prevVolume[1]; + int32_t va = t->prevAuxLevel; + const int32_t vlInc = t->volumeInc[0]; + const int32_t vrInc = t->volumeInc[1]; + const int32_t vaInc = t->auxInc; + // LOGD("[1] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", + // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], + // (vl + vlInc*frameCount)/65536.0f, frameCount); + + do { + l = (int32_t)*in++; + r = (int32_t)*in++; + *out++ += (vl >> 16) * l; + *out++ += (vr >> 16) * r; + *aux++ += (va >> 17) * (l + r); + vl += vlInc; + vr += vrInc; + va += vaInc; + } while (--frameCount); + + t->prevVolume[0] = vl; + t->prevVolume[1] = vr; + t->prevAuxLevel = va; + t->adjustVolumeRamp(true); + } + + // constant gain + else { + const uint32_t vrl = t->volumeRL; + const int16_t va = (int16_t)t->auxLevel; + do { + uint32_t rl = *reinterpret_cast<uint32_t const *>(in); + int16_t a = (int16_t)(((int32_t)in[0] + in[1]) >> 1); + in += 2; + out[0] = mulAddRL(1, rl, vrl, out[0]); + out[1] = mulAddRL(0, rl, vrl, out[1]); + out += 2; + aux[0] = mulAdd(a, va, aux[0]); + aux++; + } while (--frameCount); + } + } else { + // ramp gain + if UNLIKELY(t->volumeInc[0]|t->volumeInc[1]) { + int32_t vl = t->prevVolume[0]; + int32_t vr = t->prevVolume[1]; + const int32_t vlInc = t->volumeInc[0]; + const int32_t vrInc = t->volumeInc[1]; + + // LOGD("[1] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", + // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], + // (vl + vlInc*frameCount)/65536.0f, frameCount); + + do { + *out++ += (vl >> 16) * (int32_t) *in++; + *out++ += (vr >> 16) * (int32_t) *in++; + vl += vlInc; + vr += vrInc; + } while (--frameCount); + + t->prevVolume[0] = vl; + t->prevVolume[1] = vr; + t->adjustVolumeRamp(false); + } + + // constant gain + else { + const uint32_t vrl = t->volumeRL; + do { + uint32_t rl = *reinterpret_cast<uint32_t const *>(in); + in += 2; + out[0] = mulAddRL(1, rl, vrl, out[0]); + out[1] = mulAddRL(0, rl, vrl, out[1]); + out += 2; + } while (--frameCount); + } + } t->in = in; } -void AudioMixer::track__16BitsMono(track_t* t, int32_t* out, size_t frameCount, int32_t* temp) +void AudioMixer::track__16BitsMono(track_t* t, int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux) { int16_t const *in = static_cast<int16_t const *>(t->in); - // ramp gain - if UNLIKELY(t->volumeInc[0]|t->volumeInc[1]) { - int32_t vl = t->prevVolume[0]; - int32_t vr = t->prevVolume[1]; - const int32_t vlInc = t->volumeInc[0]; - const int32_t vrInc = t->volumeInc[1]; + if UNLIKELY(aux != NULL) { + // ramp gain + if UNLIKELY(t->volumeInc[0]|t->volumeInc[1]|t->auxInc) { + int32_t vl = t->prevVolume[0]; + int32_t vr = t->prevVolume[1]; + int32_t va = t->prevAuxLevel; + const int32_t vlInc = t->volumeInc[0]; + const int32_t vrInc = t->volumeInc[1]; + const int32_t vaInc = t->auxInc; - // LOGD("[2] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", - // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], - // (vl + vlInc*frameCount)/65536.0f, frameCount); + // LOGD("[2] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", + // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], + // (vl + vlInc*frameCount)/65536.0f, frameCount); - do { - int32_t l = *in++; - *out++ += (vl >> 16) * l; - *out++ += (vr >> 16) * l; - vl += vlInc; - vr += vrInc; - } while (--frameCount); - - t->prevVolume[0] = vl; - t->prevVolume[1] = vr; - t->adjustVolumeRamp(); - } - // constant gain - else { - const int16_t vl = t->volume[0]; - const int16_t vr = t->volume[1]; - do { - int16_t l = *in++; - out[0] = mulAdd(l, vl, out[0]); - out[1] = mulAdd(l, vr, out[1]); - out += 2; - } while (--frameCount); + do { + int32_t l = *in++; + *out++ += (vl >> 16) * l; + *out++ += (vr >> 16) * l; + *aux++ += (va >> 16) * l; + vl += vlInc; + vr += vrInc; + va += vaInc; + } while (--frameCount); + + t->prevVolume[0] = vl; + t->prevVolume[1] = vr; + t->prevAuxLevel = va; + t->adjustVolumeRamp(true); + } + // constant gain + else { + const int16_t vl = t->volume[0]; + const int16_t vr = t->volume[1]; + const int16_t va = (int16_t)t->auxLevel; + do { + int16_t l = *in++; + out[0] = mulAdd(l, vl, out[0]); + out[1] = mulAdd(l, vr, out[1]); + out += 2; + aux[0] = mulAdd(l, va, aux[0]); + aux++; + } while (--frameCount); + } + } else { + // ramp gain + if UNLIKELY(t->volumeInc[0]|t->volumeInc[1]) { + int32_t vl = t->prevVolume[0]; + int32_t vr = t->prevVolume[1]; + const int32_t vlInc = t->volumeInc[0]; + const int32_t vrInc = t->volumeInc[1]; + + // LOGD("[2] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", + // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], + // (vl + vlInc*frameCount)/65536.0f, frameCount); + + do { + int32_t l = *in++; + *out++ += (vl >> 16) * l; + *out++ += (vr >> 16) * l; + vl += vlInc; + vr += vrInc; + } while (--frameCount); + + t->prevVolume[0] = vl; + t->prevVolume[1] = vr; + t->adjustVolumeRamp(false); + } + // constant gain + else { + const int16_t vl = t->volume[0]; + const int16_t vr = t->volume[1]; + do { + int16_t l = *in++; + out[0] = mulAdd(l, vl, out[0]); + out[1] = mulAdd(l, vr, out[1]); + out += 2; + } while (--frameCount); + } } t->in = in; } @@ -624,37 +836,56 @@ void AudioMixer::ditherAndClamp(int32_t* out, int32_t const *sums, size_t c) } // no-op case -void AudioMixer::process__nop(state_t* state, void* output) +void AudioMixer::process__nop(state_t* state) { - // this assumes output 16 bits stereo, no resampling - memset(output, 0, state->frameCount*4); - uint32_t en = state->enabledTracks; - while (en) { - const int i = 31 - __builtin_clz(en); - en &= ~(1<<i); - track_t& t = state->tracks[i]; - size_t outFrames = state->frameCount; - while (outFrames) { - t.buffer.frameCount = outFrames; - t.bufferProvider->getNextBuffer(&t.buffer); - if (!t.buffer.raw) break; - outFrames -= t.buffer.frameCount; - t.bufferProvider->releaseBuffer(&t.buffer); + uint32_t e0 = state->enabledTracks; + size_t bufSize = state->frameCount * sizeof(int16_t) * MAX_NUM_CHANNELS; + while (e0) { + // process by group of tracks with same output buffer to + // avoid multiple memset() on same buffer + uint32_t e1 = e0, e2 = e0; + int i = 31 - __builtin_clz(e1); + track_t& t1 = state->tracks[i]; + e2 &= ~(1<<i); + while (e2) { + i = 31 - __builtin_clz(e2); + e2 &= ~(1<<i); + track_t& t2 = state->tracks[i]; + if UNLIKELY(t2.mainBuffer != t1.mainBuffer) { + e1 &= ~(1<<i); + } + } + e0 &= ~(e1); + + memset(t1.mainBuffer, 0, bufSize); + + while (e1) { + i = 31 - __builtin_clz(e1); + e1 &= ~(1<<i); + t1 = state->tracks[i]; + size_t outFrames = state->frameCount; + while (outFrames) { + t1.buffer.frameCount = outFrames; + t1.bufferProvider->getNextBuffer(&t1.buffer); + if (!t1.buffer.raw) break; + outFrames -= t1.buffer.frameCount; + t1.bufferProvider->releaseBuffer(&t1.buffer); + } } } } // generic code without resampling -void AudioMixer::process__genericNoResampling(state_t* state, void* output) +void AudioMixer::process__genericNoResampling(state_t* state) { int32_t outTemp[BLOCKSIZE * MAX_NUM_CHANNELS] __attribute__((aligned(32))); // acquire each track's buffer uint32_t enabledTracks = state->enabledTracks; - uint32_t en = enabledTracks; - while (en) { - const int i = 31 - __builtin_clz(en); - en &= ~(1<<i); + uint32_t e0 = enabledTracks; + while (e0) { + const int i = 31 - __builtin_clz(e0); + e0 &= ~(1<<i); track_t& t = state->tracks[i]; t.buffer.frameCount = state->frameCount; t.bufferProvider->getNextBuffer(&t.buffer); @@ -666,110 +897,156 @@ void AudioMixer::process__genericNoResampling(state_t* state, void* output) enabledTracks &= ~(1<<i); } - // this assumes output 16 bits stereo, no resampling - int32_t* out = static_cast<int32_t*>(output); - size_t numFrames = state->frameCount; - do { - memset(outTemp, 0, sizeof(outTemp)); - - en = enabledTracks; - while (en) { - const int i = 31 - __builtin_clz(en); - en &= ~(1<<i); - track_t& t = state->tracks[i]; - size_t outFrames = BLOCKSIZE; - - while (outFrames) { - size_t inFrames = (t.frameCount > outFrames)?outFrames:t.frameCount; - if (inFrames) { - (t.hook)(&t, outTemp + (BLOCKSIZE-outFrames)*MAX_NUM_CHANNELS, inFrames, state->resampleTemp); - t.frameCount -= inFrames; - outFrames -= inFrames; + e0 = enabledTracks; + while (e0) { + // process by group of tracks with same output buffer to + // optimize cache use + uint32_t e1 = e0, e2 = e0; + int j = 31 - __builtin_clz(e1); + track_t& t1 = state->tracks[j]; + e2 &= ~(1<<j); + while (e2) { + j = 31 - __builtin_clz(e2); + e2 &= ~(1<<j); + track_t& t2 = state->tracks[j]; + if UNLIKELY(t2.mainBuffer != t1.mainBuffer) { + e1 &= ~(1<<j); + } + } + e0 &= ~(e1); + // this assumes output 16 bits stereo, no resampling + int32_t *out = t1.mainBuffer; + size_t numFrames = 0; + do { + memset(outTemp, 0, sizeof(outTemp)); + e2 = e1; + while (e2) { + const int i = 31 - __builtin_clz(e2); + e2 &= ~(1<<i); + track_t& t = state->tracks[i]; + size_t outFrames = BLOCKSIZE; + int32_t *aux = NULL; + if UNLIKELY((t.needs & NEEDS_AUX__MASK) == NEEDS_AUX_ENABLED) { + aux = t.auxBuffer + numFrames; } - if (t.frameCount == 0 && outFrames) { - t.bufferProvider->releaseBuffer(&t.buffer); - t.buffer.frameCount = numFrames - (BLOCKSIZE - outFrames); - t.bufferProvider->getNextBuffer(&t.buffer); - t.in = t.buffer.raw; - if (t.in == NULL) { - enabledTracks &= ~(1<<i); - break; + while (outFrames) { + size_t inFrames = (t.frameCount > outFrames)?outFrames:t.frameCount; + if (inFrames) { + (t.hook)(&t, outTemp + (BLOCKSIZE-outFrames)*MAX_NUM_CHANNELS, inFrames, state->resampleTemp, aux); + t.frameCount -= inFrames; + outFrames -= inFrames; + if UNLIKELY(aux != NULL) { + aux += inFrames; + } } - t.frameCount = t.buffer.frameCount; - } + if (t.frameCount == 0 && outFrames) { + t.bufferProvider->releaseBuffer(&t.buffer); + t.buffer.frameCount = (state->frameCount - numFrames) - (BLOCKSIZE - outFrames); + t.bufferProvider->getNextBuffer(&t.buffer); + t.in = t.buffer.raw; + if (t.in == NULL) { + enabledTracks &= ~(1<<i); + e1 &= ~(1<<i); + break; + } + t.frameCount = t.buffer.frameCount; + } + } } - } - - ditherAndClamp(out, outTemp, BLOCKSIZE); - out += BLOCKSIZE; - numFrames -= BLOCKSIZE; - } while (numFrames); - + ditherAndClamp(out, outTemp, BLOCKSIZE); + out += BLOCKSIZE; + numFrames += BLOCKSIZE; + } while (numFrames < state->frameCount); + } // release each track's buffer - en = enabledTracks; - while (en) { - const int i = 31 - __builtin_clz(en); - en &= ~(1<<i); + e0 = enabledTracks; + while (e0) { + const int i = 31 - __builtin_clz(e0); + e0 &= ~(1<<i); track_t& t = state->tracks[i]; t.bufferProvider->releaseBuffer(&t.buffer); } } -// generic code with resampling -void AudioMixer::process__genericResampling(state_t* state, void* output) + + // generic code with resampling +void AudioMixer::process__genericResampling(state_t* state) { int32_t* const outTemp = state->outputTemp; const size_t size = sizeof(int32_t) * MAX_NUM_CHANNELS * state->frameCount; memset(outTemp, 0, size); - int32_t* out = static_cast<int32_t*>(output); size_t numFrames = state->frameCount; - uint32_t en = state->enabledTracks; - while (en) { - const int i = 31 - __builtin_clz(en); - en &= ~(1<<i); - track_t& t = state->tracks[i]; + uint32_t e0 = state->enabledTracks; + while (e0) { + // process by group of tracks with same output buffer + // to optimize cache use + uint32_t e1 = e0, e2 = e0; + int j = 31 - __builtin_clz(e1); + track_t& t1 = state->tracks[j]; + e2 &= ~(1<<j); + while (e2) { + j = 31 - __builtin_clz(e2); + e2 &= ~(1<<j); + track_t& t2 = state->tracks[j]; + if UNLIKELY(t2.mainBuffer != t1.mainBuffer) { + e1 &= ~(1<<j); + } + } + e0 &= ~(e1); + int32_t *out = t1.mainBuffer; + while (e1) { + const int i = 31 - __builtin_clz(e1); + e1 &= ~(1<<i); + track_t& t = state->tracks[i]; + int32_t *aux = NULL; + if UNLIKELY((t.needs & NEEDS_AUX__MASK) == NEEDS_AUX_ENABLED) { + aux = t.auxBuffer; + } - // this is a little goofy, on the resampling case we don't - // acquire/release the buffers because it's done by - // the resampler. - if ((t.needs & NEEDS_RESAMPLE__MASK) == NEEDS_RESAMPLE_ENABLED) { - (t.hook)(&t, outTemp, numFrames, state->resampleTemp); - } else { + // this is a little goofy, on the resampling case we don't + // acquire/release the buffers because it's done by + // the resampler. + if ((t.needs & NEEDS_RESAMPLE__MASK) == NEEDS_RESAMPLE_ENABLED) { + (t.hook)(&t, outTemp, numFrames, state->resampleTemp, aux); + } else { - size_t outFrames = numFrames; - - while (outFrames) { - t.buffer.frameCount = outFrames; - t.bufferProvider->getNextBuffer(&t.buffer); - t.in = t.buffer.raw; - // t.in == NULL can happen if the track was flushed just after having - // been enabled for mixing. - if (t.in == NULL) break; - - (t.hook)(&t, outTemp + (numFrames-outFrames)*MAX_NUM_CHANNELS, t.buffer.frameCount, state->resampleTemp); - outFrames -= t.buffer.frameCount; - t.bufferProvider->releaseBuffer(&t.buffer); + size_t outFrames = 0; + + while (outFrames < numFrames) { + t.buffer.frameCount = numFrames - outFrames; + t.bufferProvider->getNextBuffer(&t.buffer); + t.in = t.buffer.raw; + // t.in == NULL can happen if the track was flushed just after having + // been enabled for mixing. + if (t.in == NULL) break; + + if UNLIKELY(aux != NULL) { + aux += outFrames; + } + (t.hook)(&t, outTemp + outFrames*MAX_NUM_CHANNELS, t.buffer.frameCount, state->resampleTemp, aux); + outFrames += t.buffer.frameCount; + t.bufferProvider->releaseBuffer(&t.buffer); + } } } + ditherAndClamp(out, outTemp, numFrames); } - - ditherAndClamp(out, outTemp, numFrames); } // one track, 16 bits stereo without resampling is the most common case -void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state, void* output) +void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state) { const int i = 31 - __builtin_clz(state->enabledTracks); const track_t& t = state->tracks[i]; AudioBufferProvider::Buffer& b(t.buffer); - - int32_t* out = static_cast<int32_t*>(output); + + int32_t* out = t.mainBuffer; size_t numFrames = state->frameCount; - + const int16_t vl = t.volume[0]; const int16_t vr = t.volume[1]; const uint32_t vrl = t.volumeRL; @@ -787,7 +1064,7 @@ void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state, void* return; } size_t outFrames = b.frameCount; - + if (UNLIKELY(uint32_t(vl) > UNITY_GAIN || uint32_t(vr) > UNITY_GAIN)) { // volume is boosted, so we might need to clamp even though // we process only one track. @@ -816,7 +1093,9 @@ void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state, void* } // 2 tracks is also a common case -void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state, void* output) +// NEVER used in current implementation of process__validate() +// only use if the 2 tracks have the same output buffer +void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state) { int i; uint32_t en = state->enabledTracks; @@ -829,24 +1108,25 @@ void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state, void i = 31 - __builtin_clz(en); const track_t& t1 = state->tracks[i]; AudioBufferProvider::Buffer& b1(t1.buffer); - + int16_t const *in0; const int16_t vl0 = t0.volume[0]; const int16_t vr0 = t0.volume[1]; size_t frameCount0 = 0; - + int16_t const *in1; const int16_t vl1 = t1.volume[0]; const int16_t vr1 = t1.volume[1]; size_t frameCount1 = 0; - - int32_t* out = static_cast<int32_t*>(output); + + //FIXME: only works if two tracks use same buffer + int32_t* out = t0.mainBuffer; size_t numFrames = state->frameCount; int16_t const *buff = NULL; - + while (numFrames) { - + if (frameCount0 == 0) { b0.frameCount = numFrames; t0.bufferProvider->getNextBuffer(&b0); @@ -875,13 +1155,13 @@ void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state, void } frameCount1 = b1.frameCount; } - + size_t outFrames = frameCount0 < frameCount1?frameCount0:frameCount1; numFrames -= outFrames; frameCount0 -= outFrames; frameCount1 -= outFrames; - + do { int32_t l0 = *in0++; int32_t r0 = *in0++; @@ -896,17 +1176,17 @@ void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state, void r = clamp16(r); *out++ = (r<<16) | (l & 0xFFFF); } while (--outFrames); - + if (frameCount0 == 0) { t0.bufferProvider->releaseBuffer(&b0); } if (frameCount1 == 0) { t1.bufferProvider->releaseBuffer(&b1); } - } - + } + if (buff != NULL) { - delete [] buff; + delete [] buff; } } diff --git a/services/audioflinger/AudioMixer.h b/services/audioflinger/AudioMixer.h index 15766cd..aee3e17 100644 --- a/services/audioflinger/AudioMixer.h +++ b/services/audioflinger/AudioMixer.h @@ -63,11 +63,14 @@ public: // for target TRACK CHANNEL_COUNT = 0x4000, FORMAT = 0x4001, + MAIN_BUFFER = 0x4002, + AUX_BUFFER = 0x4003, // for TARGET RESAMPLE SAMPLE_RATE = 0x4100, // for TARGET VOLUME (8 channels max) VOLUME0 = 0x4200, VOLUME1 = 0x4201, + AUXLEVEL = 0x4210, }; @@ -78,10 +81,10 @@ public: status_t disable(int name); status_t setActiveTrack(int track); - status_t setParameter(int target, int name, int value); + status_t setParameter(int target, int name, void *value); status_t setBufferProvider(AudioBufferProvider* bufferProvider); - void process(void* output); + void process(); uint32_t trackNames() const { return mTrackNames; } @@ -94,6 +97,7 @@ private: NEEDS_FORMAT__MASK = 0x000000F0, NEEDS_MUTE__MASK = 0x00000100, NEEDS_RESAMPLE__MASK = 0x00001000, + NEEDS_AUX__MASK = 0x00010000, }; enum { @@ -107,6 +111,9 @@ private: NEEDS_RESAMPLE_DISABLED = 0x00000000, NEEDS_RESAMPLE_ENABLED = 0x00001000, + + NEEDS_AUX_DISABLED = 0x00000000, + NEEDS_AUX_ENABLED = 0x00010000, }; static inline int32_t applyVolume(int32_t in, int32_t v) { @@ -115,9 +122,10 @@ private: struct state_t; + struct track_t; - typedef void (*mix_t)(state_t* state, void* output); - + typedef void (*mix_t)(state_t* state); + typedef void (*hook_t)(track_t* t, int32_t* output, size_t numOutFrames, int32_t* temp, int32_t* aux); static const int BLOCKSIZE = 16; // 4 cache lines struct track_t { @@ -131,6 +139,9 @@ private: int32_t prevVolume[2]; int32_t volumeInc[2]; + int32_t auxLevel; + int32_t auxInc; + int32_t prevAuxLevel; uint16_t frameCount; @@ -142,15 +153,17 @@ private: AudioBufferProvider* bufferProvider; mutable AudioBufferProvider::Buffer buffer; - void (*hook)(track_t* t, int32_t* output, size_t numOutFrames, int32_t* temp); + hook_t hook; void const* in; // current location in buffer AudioResampler* resampler; uint32_t sampleRate; + int32_t* mainBuffer; + int32_t* auxBuffer; bool setResampler(uint32_t sampleRate, uint32_t devSampleRate); bool doesResample() const; - void adjustVolumeRamp(); + void adjustVolumeRamp(bool aux); }; // pad to 32-bytes to fill cache line @@ -173,18 +186,19 @@ private: void invalidateState(uint32_t mask); - static void track__genericResample(track_t* t, int32_t* out, size_t numFrames, int32_t* temp); - static void track__nop(track_t* t, int32_t* out, size_t numFrames, int32_t* temp); - static void volumeRampStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp); - static void track__16BitsStereo(track_t* t, int32_t* out, size_t numFrames, int32_t* temp); - static void track__16BitsMono(track_t* t, int32_t* out, size_t numFrames, int32_t* temp); - - static void process__validate(state_t* state, void* output); - static void process__nop(state_t* state, void* output); - static void process__genericNoResampling(state_t* state, void* output); - static void process__genericResampling(state_t* state, void* output); - static void process__OneTrack16BitsStereoNoResampling(state_t* state, void* output); - static void process__TwoTracks16BitsStereoNoResampling(state_t* state, void* output); + static void track__genericResample(track_t* t, int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux); + static void track__nop(track_t* t, int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux); + static void track__16BitsStereo(track_t* t, int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux); + static void track__16BitsMono(track_t* t, int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux); + static void volumeRampStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux); + static void volumeStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux); + + static void process__validate(state_t* state); + static void process__nop(state_t* state); + static void process__genericNoResampling(state_t* state); + static void process__genericResampling(state_t* state); + static void process__OneTrack16BitsStereoNoResampling(state_t* state); + static void process__TwoTracks16BitsStereoNoResampling(state_t* state); }; // ---------------------------------------------------------------------------- diff --git a/services/audioflinger/AudioPolicyManagerBase.cpp b/services/audioflinger/AudioPolicyManagerBase.cpp index c8b3f48..425ca31 100644 --- a/services/audioflinger/AudioPolicyManagerBase.cpp +++ b/services/audioflinger/AudioPolicyManagerBase.cpp @@ -133,7 +133,7 @@ status_t AudioPolicyManagerBase::setDeviceConnectionState(AudioSystem::audio_dev // request routing change if necessary uint32_t newDevice = getNewDevice(mHardwareOutput, false); #ifdef WITH_A2DP - checkOutputForAllStrategies(newDevice); + checkOutputForAllStrategies(); // A2DP outputs must be closed after checkOutputForAllStrategies() is executed if (state == AudioSystem::DEVICE_STATE_UNAVAILABLE && AudioSystem::isA2dpDevice(device)) { closeA2dpOutputs(); @@ -274,7 +274,7 @@ void AudioPolicyManagerBase::setPhoneState(int state) // check for device and output changes triggered by new phone state newDevice = getNewDevice(mHardwareOutput, false); #ifdef WITH_A2DP - checkOutputForAllStrategies(newDevice); + checkOutputForAllStrategies(); // suspend A2DP output if a SCO device is present. if (mA2dpOutput != 0 && mScoDeviceAddress != "") { if (oldState == AudioSystem::MODE_NORMAL) { @@ -386,13 +386,28 @@ 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(newDevice); + checkOutputForAllStrategies(); #endif updateDeviceForStrategy(); setOutputDevice(mHardwareOutput, newDevice); if (forceVolumeReeval) { applyStreamVolumes(mHardwareOutput, newDevice); } + + audio_io_handle_t activeInput = getActiveInput(); + if (activeInput != 0) { + AudioInputDescriptor *inputDesc = mInputs.valueFor(activeInput); + newDevice = getDeviceForInputSource(inputDesc->mInputSource); + if (newDevice != inputDesc->mDevice) { + LOGV("setForceUse() changing device from %x to %x for input %d", + inputDesc->mDevice, newDevice, activeInput); + inputDesc->mDevice = newDevice; + AudioParameter param = AudioParameter(); + param.addInt(String8(AudioParameter::keyRouting), (int)newDevice); + mpClientInterface->setParameters(activeInput, param.toString()); + } + } + } AudioSystem::forced_config AudioPolicyManagerBase::getForceUse(AudioSystem::force_use usage) @@ -538,9 +553,11 @@ audio_io_handle_t AudioPolicyManagerBase::getOutput(AudioSystem::stream_type str return output; } -status_t AudioPolicyManagerBase::startOutput(audio_io_handle_t output, AudioSystem::stream_type stream) +status_t AudioPolicyManagerBase::startOutput(audio_io_handle_t output, + AudioSystem::stream_type stream, + int session) { - LOGV("startOutput() output %d, stream %d", output, stream); + LOGV("startOutput() output %d, stream %d, session %d", output, stream, session); ssize_t index = mOutputs.indexOfKey(output); if (index < 0) { LOGW("startOutput() unknow output %d", output); @@ -574,9 +591,11 @@ status_t AudioPolicyManagerBase::startOutput(audio_io_handle_t output, AudioSyst return NO_ERROR; } -status_t AudioPolicyManagerBase::stopOutput(audio_io_handle_t output, AudioSystem::stream_type stream) +status_t AudioPolicyManagerBase::stopOutput(audio_io_handle_t output, + AudioSystem::stream_type stream, + int session) { - LOGV("stopOutput() output %d, stream %d", output, stream); + LOGV("stopOutput() output %d, stream %d, session %d", output, stream, session); ssize_t index = mOutputs.indexOfKey(output); if (index < 0) { LOGW("stopOutput() unknow output %d", output); @@ -602,8 +621,12 @@ status_t AudioPolicyManagerBase::stopOutput(audio_io_handle_t output, AudioSyste setOutputDevice(output, getNewDevice(output)); #ifdef WITH_A2DP - if (mA2dpOutput != 0 && !a2dpUsedForSonification() && strategy == STRATEGY_SONIFICATION) { - setStrategyMute(STRATEGY_MEDIA, false, mA2dpOutput, mOutputs.valueFor(mHardwareOutput)->mLatency*2); + if (mA2dpOutput != 0 && !a2dpUsedForSonification() && + strategy == STRATEGY_SONIFICATION) { + setStrategyMute(STRATEGY_MEDIA, + false, + mA2dpOutput, + mOutputs.valueFor(mHardwareOutput)->mLatency*2); } #endif if (output != mHardwareOutput) { @@ -826,6 +849,85 @@ status_t AudioPolicyManagerBase::getStreamVolumeIndex(AudioSystem::stream_type s return NO_ERROR; } +audio_io_handle_t AudioPolicyManagerBase::getOutputForEffect(effect_descriptor_t *desc) +{ + LOGV("getOutputForEffect()"); + // apply simple rule where global effects are attached to the same output as MUSIC streams + return getOutput(AudioSystem::MUSIC); +} + +status_t AudioPolicyManagerBase::registerEffect(effect_descriptor_t *desc, + audio_io_handle_t output, + uint32_t strategy, + int session, + int id) +{ + ssize_t index = mOutputs.indexOfKey(output); + if (index < 0) { + LOGW("registerEffect() unknown output %d", output); + return INVALID_OPERATION; + } + + if (mTotalEffectsCpuLoad + desc->cpuLoad > getMaxEffectsCpuLoad()) { + LOGW("registerEffect() CPU Load limit exceeded for Fx %s, CPU %f MIPS", + desc->name, (float)desc->cpuLoad/10); + return INVALID_OPERATION; + } + if (mTotalEffectsMemory + desc->memoryUsage > getMaxEffectsMemory()) { + LOGW("registerEffect() memory limit exceeded for Fx %s, Memory %d KB", + desc->name, desc->memoryUsage); + return INVALID_OPERATION; + } + mTotalEffectsCpuLoad += desc->cpuLoad; + mTotalEffectsMemory += desc->memoryUsage; + LOGV("registerEffect() effect %s, output %d, strategy %d session %d id %d", + desc->name, output, strategy, session, id); + + LOGV("registerEffect() CPU %d, memory %d", desc->cpuLoad, desc->memoryUsage); + LOGV(" total CPU %d, total memory %d", mTotalEffectsCpuLoad, mTotalEffectsMemory); + + EffectDescriptor *pDesc = new EffectDescriptor(); + memcpy (&pDesc->mDesc, desc, sizeof(effect_descriptor_t)); + pDesc->mOutput = output; + pDesc->mStrategy = (routing_strategy)strategy; + pDesc->mSession = session; + mEffects.add(id, pDesc); + + return NO_ERROR; +} + +status_t AudioPolicyManagerBase::unregisterEffect(int id) +{ + ssize_t index = mEffects.indexOfKey(id); + if (index < 0) { + LOGW("unregisterEffect() unknown effect ID %d", id); + return INVALID_OPERATION; + } + + EffectDescriptor *pDesc = mEffects.valueAt(index); + + if (mTotalEffectsCpuLoad < pDesc->mDesc.cpuLoad) { + LOGW("unregisterEffect() CPU load %d too high for total %d", + pDesc->mDesc.cpuLoad, mTotalEffectsCpuLoad); + pDesc->mDesc.cpuLoad = mTotalEffectsCpuLoad; + } + mTotalEffectsCpuLoad -= pDesc->mDesc.cpuLoad; + if (mTotalEffectsMemory < pDesc->mDesc.memoryUsage) { + LOGW("unregisterEffect() memory %d too big for total %d", + pDesc->mDesc.memoryUsage, mTotalEffectsMemory); + pDesc->mDesc.memoryUsage = mTotalEffectsMemory; + } + mTotalEffectsMemory -= pDesc->mDesc.memoryUsage; + LOGV("unregisterEffect() effect %s, ID %d, CPU %d, memory %d", + pDesc->mDesc.name, id, pDesc->mDesc.cpuLoad, pDesc->mDesc.memoryUsage); + LOGV(" total CPU %d, total memory %d", mTotalEffectsCpuLoad, mTotalEffectsMemory); + + mEffects.removeItem(id); + delete pDesc; + + return NO_ERROR; +} + status_t AudioPolicyManagerBase::dump(int fd) { const size_t SIZE = 256; @@ -890,6 +992,19 @@ status_t AudioPolicyManagerBase::dump(int fd) write(fd, buffer, strlen(buffer)); } + snprintf(buffer, SIZE, "\nTotal Effects CPU: %f MIPS, Total Effects memory: %d KB\n", + (float)mTotalEffectsCpuLoad/10, mTotalEffectsMemory); + write(fd, buffer, strlen(buffer)); + + snprintf(buffer, SIZE, "Registered effects:\n"); + write(fd, buffer, strlen(buffer)); + for (size_t i = 0; i < mEffects.size(); i++) { + snprintf(buffer, SIZE, "- Effect %d dump:\n", mEffects.keyAt(i)); + write(fd, buffer, strlen(buffer)); + mEffects.valueAt(i)->dump(fd); + } + + return NO_ERROR; } @@ -902,7 +1017,8 @@ AudioPolicyManagerBase::AudioPolicyManagerBase(AudioPolicyClientInterface *clien #ifdef AUDIO_POLICY_TEST Thread(false), #endif //AUDIO_POLICY_TEST - mPhoneState(AudioSystem::MODE_NORMAL), mRingerMode(0), mMusicStopTime(0), mLimitRingtoneVolume(false) + mPhoneState(AudioSystem::MODE_NORMAL), mRingerMode(0), mMusicStopTime(0), + mLimitRingtoneVolume(false), mTotalEffectsCpuLoad(0), mTotalEffectsMemory(0) { mpClientInterface = clientInterface; @@ -938,6 +1054,7 @@ AudioPolicyManagerBase::AudioPolicyManagerBase(AudioPolicyClientInterface *clien } else { addOutput(mHardwareOutput, outputDesc); setOutputDevice(mHardwareOutput, (uint32_t)AudioSystem::DEVICE_OUT_SPEAKER, true); + //TODO: configure audio effect output stage here } updateDeviceForStrategy(); @@ -1152,6 +1269,9 @@ status_t AudioPolicyManagerBase::handleA2dpConnection(AudioSystem::audio_devices if (mA2dpOutput) { // add A2DP output descriptor addOutput(mA2dpOutput, outputDesc); + + //TODO: configure audio effect output stage here + // set initial stream volume for A2DP device applyStreamVolumes(mA2dpOutput, device); if (a2dpUsedForSonification()) { @@ -1249,6 +1369,17 @@ void AudioPolicyManagerBase::closeA2dpOutputs() LOGV("setDeviceConnectionState() closing A2DP and duplicated output!"); if (mDuplicatedOutput != 0) { + AudioOutputDescriptor *dupOutputDesc = mOutputs.valueFor(mDuplicatedOutput); + AudioOutputDescriptor *hwOutputDesc = mOutputs.valueFor(mHardwareOutput); + // As all active tracks on duplicated output will be deleted, + // and as they were also referenced on hardware output, the reference + // count for their stream type must be adjusted accordingly on + // hardware output. + for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) { + int refCount = dupOutputDesc->mRefCount[i]; + hwOutputDesc->changeRefCount((AudioSystem::stream_type)i,-refCount); + } + mpClientInterface->closeOutput(mDuplicatedOutput); delete mOutputs.valueFor(mDuplicatedOutput); mOutputs.removeItem(mDuplicatedOutput); @@ -1258,6 +1389,7 @@ void AudioPolicyManagerBase::closeA2dpOutputs() AudioParameter param; param.add(String8("closing"), String8("true")); mpClientInterface->setParameters(mA2dpOutput, param.toString()); + mpClientInterface->closeOutput(mA2dpOutput); delete mOutputs.valueFor(mA2dpOutput); mOutputs.removeItem(mA2dpOutput); @@ -1265,78 +1397,65 @@ void AudioPolicyManagerBase::closeA2dpOutputs() } } -void AudioPolicyManagerBase::checkOutputForStrategy(routing_strategy strategy, uint32_t &newDevice) +void AudioPolicyManagerBase::checkOutputForStrategy(routing_strategy strategy) { uint32_t prevDevice = getDeviceForStrategy(strategy); uint32_t curDevice = getDeviceForStrategy(strategy, false); bool a2dpWasUsed = AudioSystem::isA2dpDevice((AudioSystem::audio_devices)(prevDevice & ~AudioSystem::DEVICE_OUT_SPEAKER)); bool a2dpIsUsed = AudioSystem::isA2dpDevice((AudioSystem::audio_devices)(curDevice & ~AudioSystem::DEVICE_OUT_SPEAKER)); - AudioOutputDescriptor *hwOutputDesc = mOutputs.valueFor(mHardwareOutput); - AudioOutputDescriptor *a2dpOutputDesc; + audio_io_handle_t srcOutput = 0; + audio_io_handle_t dstOutput = 0; if (a2dpWasUsed && !a2dpIsUsed) { bool dupUsed = a2dpUsedForSonification() && a2dpWasUsed && (AudioSystem::popCount(prevDevice) == 2); - + dstOutput = mHardwareOutput; if (dupUsed) { - LOGV("checkOutputForStrategy() moving strategy %d to duplicated", strategy); - a2dpOutputDesc = mOutputs.valueFor(mDuplicatedOutput); + LOGV("checkOutputForStrategy() moving strategy %d from duplicated", strategy); + srcOutput = mDuplicatedOutput; } else { - LOGV("checkOutputForStrategy() moving strategy %d to a2dp", strategy); - a2dpOutputDesc = mOutputs.valueFor(mA2dpOutput); - } - - for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) { - if (getStrategy((AudioSystem::stream_type)i) == strategy) { - mpClientInterface->setStreamOutput((AudioSystem::stream_type)i, mHardwareOutput); - int refCount = a2dpOutputDesc->mRefCount[i]; - // in the case of duplicated output, the ref count is first incremented - // and then decremented on hardware output tus keeping its value - hwOutputDesc->changeRefCount((AudioSystem::stream_type)i, refCount); - a2dpOutputDesc->changeRefCount((AudioSystem::stream_type)i,-refCount); - } - } - // do not change newDevice if it was already set before this call by a previous call to - // getNewDevice() or checkOutputForStrategy() for a strategy with higher priority - if (newDevice == 0 && hwOutputDesc->isUsedByStrategy(strategy)) { - newDevice = getDeviceForStrategy(strategy, false); + LOGV("checkOutputForStrategy() moving strategy %d from a2dp", strategy); + srcOutput = mA2dpOutput; } } if (a2dpIsUsed && !a2dpWasUsed) { bool dupUsed = a2dpUsedForSonification() && a2dpIsUsed && (AudioSystem::popCount(curDevice) == 2); - audio_io_handle_t a2dpOutput; - + srcOutput = mHardwareOutput; if (dupUsed) { - LOGV("checkOutputForStrategy() moving strategy %d from duplicated", strategy); - a2dpOutputDesc = mOutputs.valueFor(mDuplicatedOutput); - a2dpOutput = mDuplicatedOutput; + LOGV("checkOutputForStrategy() moving strategy %d to duplicated", strategy); + dstOutput = mDuplicatedOutput; } else { - LOGV("checkOutputForStrategy() moving strategy %d from a2dp", strategy); - a2dpOutputDesc = mOutputs.valueFor(mA2dpOutput); - a2dpOutput = mA2dpOutput; + LOGV("checkOutputForStrategy() moving strategy %d to a2dp", strategy); + dstOutput = mA2dpOutput; } + } + if (srcOutput != 0 && dstOutput != 0) { + // Move effects associated to this strategy from previous output to new output + for (size_t i = 0; i < mEffects.size(); i++) { + EffectDescriptor *desc = mEffects.valueAt(i); + if (desc->mSession != AudioSystem::SESSION_OUTPUT_STAGE && + desc->mStrategy == strategy && + desc->mOutput == srcOutput) { + LOGV("checkOutputForStrategy() moving effect %d to output %d", mEffects.keyAt(i), dstOutput); + mpClientInterface->moveEffects(desc->mSession, srcOutput, dstOutput); + desc->mOutput = dstOutput; + } + } + // Move tracks associated to this strategy from previous output to new output for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) { if (getStrategy((AudioSystem::stream_type)i) == strategy) { - mpClientInterface->setStreamOutput((AudioSystem::stream_type)i, a2dpOutput); - int refCount = hwOutputDesc->mRefCount[i]; - // in the case of duplicated output, the ref count is first incremented - // and then decremented on hardware output tus keeping its value - a2dpOutputDesc->changeRefCount((AudioSystem::stream_type)i, refCount); - hwOutputDesc->changeRefCount((AudioSystem::stream_type)i,-refCount); + mpClientInterface->setStreamOutput((AudioSystem::stream_type)i, dstOutput); } } } } -void AudioPolicyManagerBase::checkOutputForAllStrategies(uint32_t &newDevice) +void AudioPolicyManagerBase::checkOutputForAllStrategies() { - // Check strategies in order of priority so that once newDevice is set - // for a given strategy it is not modified by subsequent calls to - // checkOutputForStrategy() - checkOutputForStrategy(STRATEGY_PHONE, newDevice); - checkOutputForStrategy(STRATEGY_SONIFICATION, newDevice); - checkOutputForStrategy(STRATEGY_MEDIA, newDevice); - checkOutputForStrategy(STRATEGY_DTMF, newDevice); + checkOutputForStrategy(STRATEGY_PHONE); + checkOutputForStrategy(STRATEGY_SONIFICATION); + checkOutputForStrategy(STRATEGY_MEDIA); + checkOutputForStrategy(STRATEGY_DTMF); } #endif @@ -1370,8 +1489,12 @@ uint32_t AudioPolicyManagerBase::getNewDevice(audio_io_handle_t output, bool fro return device; } -AudioPolicyManagerBase::routing_strategy AudioPolicyManagerBase::getStrategy(AudioSystem::stream_type stream) -{ +uint32_t AudioPolicyManagerBase::getStrategyForStream(AudioSystem::stream_type stream) { + return (uint32_t)getStrategy(stream); +} + +AudioPolicyManagerBase::routing_strategy AudioPolicyManagerBase::getStrategy( + AudioSystem::stream_type stream) { // stream to strategy mapping switch (stream) { case AudioSystem::VOICE_CALL: @@ -1835,6 +1958,16 @@ bool AudioPolicyManagerBase::needsDirectOuput(AudioSystem::stream_type stream, (format !=0 && !AudioSystem::isLinearPCM(format))); } +uint32_t AudioPolicyManagerBase::getMaxEffectsCpuLoad() +{ + return MAX_EFFECTS_CPU_LOAD; +} + +uint32_t AudioPolicyManagerBase::getMaxEffectsMemory() +{ + return MAX_EFFECTS_MEMORY; +} + // --- AudioOutputDescriptor class implementation AudioPolicyManagerBase::AudioOutputDescriptor::AudioOutputDescriptor() @@ -1968,5 +2101,27 @@ void AudioPolicyManagerBase::StreamDescriptor::dump(char* buffer, size_t size) mCanBeMuted); } +// --- EffectDescriptor class implementation + +status_t AudioPolicyManagerBase::EffectDescriptor::dump(int fd) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + snprintf(buffer, SIZE, " Output: %d\n", mOutput); + result.append(buffer); + snprintf(buffer, SIZE, " Strategy: %d\n", mStrategy); + result.append(buffer); + snprintf(buffer, SIZE, " Session: %d\n", mSession); + result.append(buffer); + snprintf(buffer, SIZE, " Name: %s\n", mDesc.name); + result.append(buffer); + write(fd, result.string(), result.size()); + + return NO_ERROR; +} + + }; // namespace android diff --git a/services/audioflinger/AudioPolicyService.cpp b/services/audioflinger/AudioPolicyService.cpp index bb3905c..f24e08e 100644 --- a/services/audioflinger/AudioPolicyService.cpp +++ b/services/audioflinger/AudioPolicyService.cpp @@ -119,7 +119,8 @@ status_t AudioPolicyService::setDeviceConnectionState(AudioSystem::audio_devices if (!AudioSystem::isOutputDevice(device) && !AudioSystem::isInputDevice(device)) { return BAD_VALUE; } - if (state != AudioSystem::DEVICE_STATE_AVAILABLE && state != AudioSystem::DEVICE_STATE_UNAVAILABLE) { + if (state != AudioSystem::DEVICE_STATE_AVAILABLE && + state != AudioSystem::DEVICE_STATE_UNAVAILABLE) { return BAD_VALUE; } @@ -128,8 +129,9 @@ status_t AudioPolicyService::setDeviceConnectionState(AudioSystem::audio_devices return mpPolicyManager->setDeviceConnectionState(device, state, device_address); } -AudioSystem::device_connection_state AudioPolicyService::getDeviceConnectionState(AudioSystem::audio_devices device, - const char *device_address) +AudioSystem::device_connection_state AudioPolicyService::getDeviceConnectionState( + AudioSystem::audio_devices device, + const char *device_address) { if (mpPolicyManager == NULL) { return AudioSystem::DEVICE_STATE_UNAVAILABLE; @@ -175,7 +177,8 @@ status_t AudioPolicyService::setRingerMode(uint32_t mode, uint32_t mask) return NO_ERROR; } -status_t AudioPolicyService::setForceUse(AudioSystem::force_use usage, AudioSystem::forced_config config) +status_t AudioPolicyService::setForceUse(AudioSystem::force_use usage, + AudioSystem::forced_config config) { if (mpPolicyManager == NULL) { return NO_INIT; @@ -223,24 +226,28 @@ audio_io_handle_t AudioPolicyService::getOutput(AudioSystem::stream_type stream, return mpPolicyManager->getOutput(stream, samplingRate, format, channels, flags); } -status_t AudioPolicyService::startOutput(audio_io_handle_t output, AudioSystem::stream_type stream) +status_t AudioPolicyService::startOutput(audio_io_handle_t output, + AudioSystem::stream_type stream, + int session) { if (mpPolicyManager == NULL) { return NO_INIT; } LOGV("startOutput() tid %d", gettid()); Mutex::Autolock _l(mLock); - return mpPolicyManager->startOutput(output, stream); + return mpPolicyManager->startOutput(output, stream, session); } -status_t AudioPolicyService::stopOutput(audio_io_handle_t output, AudioSystem::stream_type stream) +status_t AudioPolicyService::stopOutput(audio_io_handle_t output, + AudioSystem::stream_type stream, + int session) { if (mpPolicyManager == NULL) { return NO_INIT; } LOGV("stopOutput() tid %d", gettid()); Mutex::Autolock _l(mLock); - return mpPolicyManager->stopOutput(output, stream); + return mpPolicyManager->stopOutput(output, stream, session); } void AudioPolicyService::releaseOutput(audio_io_handle_t output) @@ -339,8 +346,46 @@ status_t AudioPolicyService::getStreamVolumeIndex(AudioSystem::stream_type strea return mpPolicyManager->getStreamVolumeIndex(stream, index); } +uint32_t AudioPolicyService::getStrategyForStream(AudioSystem::stream_type stream) +{ + if (mpPolicyManager == NULL) { + return 0; + } + return mpPolicyManager->getStrategyForStream(stream); +} + +audio_io_handle_t AudioPolicyService::getOutputForEffect(effect_descriptor_t *desc) +{ + if (mpPolicyManager == NULL) { + return NO_INIT; + } + Mutex::Autolock _l(mLock); + return mpPolicyManager->getOutputForEffect(desc); +} + +status_t AudioPolicyService::registerEffect(effect_descriptor_t *desc, + audio_io_handle_t output, + uint32_t strategy, + int session, + int id) +{ + if (mpPolicyManager == NULL) { + return NO_INIT; + } + return mpPolicyManager->registerEffect(desc, output, strategy, session, id); +} + +status_t AudioPolicyService::unregisterEffect(int id) +{ + if (mpPolicyManager == NULL) { + return NO_INIT; + } + return mpPolicyManager->unregisterEffect(id); +} + void AudioPolicyService::binderDied(const wp<IBinder>& who) { - LOGW("binderDied() %p, tid %d, calling tid %d", who.unsafe_get(), gettid(), IPCThreadState::self()->getCallingPid()); + LOGW("binderDied() %p, tid %d, calling tid %d", who.unsafe_get(), gettid(), + IPCThreadState::self()->getCallingPid()); } static bool tryLock(Mutex& mutex) @@ -447,10 +492,16 @@ audio_io_handle_t AudioPolicyService::openOutput(uint32_t *pDevices, return 0; } - return af->openOutput(pDevices, pSamplingRate, (uint32_t *)pFormat, pChannels, pLatencyMs, flags); + return af->openOutput(pDevices, + pSamplingRate, + (uint32_t *)pFormat, + pChannels, + pLatencyMs, + flags); } -audio_io_handle_t AudioPolicyService::openDuplicateOutput(audio_io_handle_t output1, audio_io_handle_t output2) +audio_io_handle_t AudioPolicyService::openDuplicateOutput(audio_io_handle_t output1, + audio_io_handle_t output2) { sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); if (af == 0) { @@ -514,12 +565,16 @@ status_t AudioPolicyService::closeInput(audio_io_handle_t input) return af->closeInput(input); } -status_t AudioPolicyService::setStreamVolume(AudioSystem::stream_type stream, float volume, audio_io_handle_t output, int delayMs) +status_t AudioPolicyService::setStreamVolume(AudioSystem::stream_type stream, + float volume, + audio_io_handle_t output, + int delayMs) { return mAudioCommandThread->volumeCommand((int)stream, volume, (int)output, delayMs); } -status_t AudioPolicyService::setStreamOutput(AudioSystem::stream_type stream, audio_io_handle_t output) +status_t AudioPolicyService::setStreamOutput(AudioSystem::stream_type stream, + audio_io_handle_t output) { sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); if (af == 0) return PERMISSION_DENIED; @@ -527,8 +582,18 @@ status_t AudioPolicyService::setStreamOutput(AudioSystem::stream_type stream, au return af->setStreamOutput(stream, output); } +status_t AudioPolicyService::moveEffects(int session, audio_io_handle_t srcOutput, + audio_io_handle_t dstOutput) +{ + sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); + if (af == 0) return PERMISSION_DENIED; + + return af->moveEffects(session, (int)srcOutput, (int)dstOutput); +} -void AudioPolicyService::setParameters(audio_io_handle_t ioHandle, const String8& keyValuePairs, int delayMs) +void AudioPolicyService::setParameters(audio_io_handle_t ioHandle, + const String8& keyValuePairs, + int delayMs) { mAudioCommandThread->parametersCommand((int)ioHandle, keyValuePairs, delayMs); } @@ -539,7 +604,8 @@ String8 AudioPolicyService::getParameters(audio_io_handle_t ioHandle, const Stri return result; } -status_t AudioPolicyService::startTone(ToneGenerator::tone_type tone, AudioSystem::stream_type stream) +status_t AudioPolicyService::startTone(ToneGenerator::tone_type tone, + AudioSystem::stream_type stream) { mTonePlaybackThread->startToneCommand(tone, stream); return NO_ERROR; @@ -623,8 +689,11 @@ bool AudioPolicyService::AudioCommandThread::threadLoop() }break; case SET_VOLUME: { VolumeData *data = (VolumeData *)command->mParam; - LOGV("AudioCommandThread() processing set volume stream %d, volume %f, output %d", data->mStream, data->mVolume, data->mIO); - command->mStatus = AudioSystem::setStreamVolume(data->mStream, data->mVolume, data->mIO); + LOGV("AudioCommandThread() processing set volume stream %d, \ + volume %f, output %d", data->mStream, data->mVolume, data->mIO); + command->mStatus = AudioSystem::setStreamVolume(data->mStream, + data->mVolume, + data->mIO); if (command->mWaitStatus) { command->mCond.signal(); mWaitWorkCV.wait(mLock); @@ -633,7 +702,8 @@ bool AudioPolicyService::AudioCommandThread::threadLoop() }break; case SET_PARAMETERS: { ParametersData *data = (ParametersData *)command->mParam; - LOGV("AudioCommandThread() processing set parameters string %s, io %d", data->mKeyValuePairs.string(), data->mIO); + LOGV("AudioCommandThread() processing set parameters string %s, io %d", + data->mKeyValuePairs.string(), data->mIO); command->mStatus = AudioSystem::setParameters(data->mIO, data->mKeyValuePairs); if (command->mWaitStatus) { command->mCond.signal(); @@ -643,7 +713,8 @@ bool AudioPolicyService::AudioCommandThread::threadLoop() }break; case SET_VOICE_VOLUME: { VoiceVolumeData *data = (VoiceVolumeData *)command->mParam; - LOGV("AudioCommandThread() processing set voice volume volume %f", data->mVolume); + LOGV("AudioCommandThread() processing set voice volume volume %f", + data->mVolume); command->mStatus = AudioSystem::setVoiceVolume(data->mVolume); if (command->mWaitStatus) { command->mCond.signal(); @@ -734,7 +805,10 @@ void AudioPolicyService::AudioCommandThread::stopToneCommand() mWaitWorkCV.signal(); } -status_t AudioPolicyService::AudioCommandThread::volumeCommand(int stream, float volume, int output, int delayMs) +status_t AudioPolicyService::AudioCommandThread::volumeCommand(int stream, + float volume, + int output, + int delayMs) { status_t status = NO_ERROR; @@ -752,7 +826,8 @@ status_t AudioPolicyService::AudioCommandThread::volumeCommand(int stream, float } Mutex::Autolock _l(mLock); insertCommand_l(command, delayMs); - LOGV("AudioCommandThread() adding set volume stream %d, volume %f, output %d", stream, volume, output); + LOGV("AudioCommandThread() adding set volume stream %d, volume %f, output %d", + stream, volume, output); mWaitWorkCV.signal(); if (command->mWaitStatus) { command->mCond.wait(mLock); @@ -762,7 +837,9 @@ status_t AudioPolicyService::AudioCommandThread::volumeCommand(int stream, float return status; } -status_t AudioPolicyService::AudioCommandThread::parametersCommand(int ioHandle, const String8& keyValuePairs, int delayMs) +status_t AudioPolicyService::AudioCommandThread::parametersCommand(int ioHandle, + const String8& keyValuePairs, + int delayMs) { status_t status = NO_ERROR; @@ -779,7 +856,8 @@ status_t AudioPolicyService::AudioCommandThread::parametersCommand(int ioHandle, } Mutex::Autolock _l(mLock); insertCommand_l(command, delayMs); - LOGV("AudioCommandThread() adding set parameter string %s, io %d ,delay %d", keyValuePairs.string(), ioHandle, delayMs); + LOGV("AudioCommandThread() adding set parameter string %s, io %d ,delay %d", + keyValuePairs.string(), ioHandle, delayMs); mWaitWorkCV.signal(); if (command->mWaitStatus) { command->mCond.wait(mLock); @@ -840,7 +918,8 @@ void AudioPolicyService::AudioCommandThread::insertCommand_l(AudioCommand *comma ParametersData *data = (ParametersData *)command->mParam; ParametersData *data2 = (ParametersData *)command2->mParam; if (data->mIO != data2->mIO) break; - LOGV("Comparing parameter command %s to new command %s", data2->mKeyValuePairs.string(), data->mKeyValuePairs.string()); + LOGV("Comparing parameter command %s to new command %s", + data2->mKeyValuePairs.string(), data->mKeyValuePairs.string()); AudioParameter param = AudioParameter(data->mKeyValuePairs); AudioParameter param2 = AudioParameter(data2->mKeyValuePairs); for (size_t j = 0; j < param.size(); j++) { @@ -872,7 +951,8 @@ void AudioPolicyService::AudioCommandThread::insertCommand_l(AudioCommand *comma VolumeData *data2 = (VolumeData *)command2->mParam; if (data->mIO != data2->mIO) break; if (data->mStream != data2->mStream) break; - LOGV("Filtering out volume command on output %d for stream %d", data->mIO, data->mStream); + LOGV("Filtering out volume command on output %d for stream %d", + data->mIO, data->mStream); removedCommands.add(command2); } break; case START_TONE: @@ -896,7 +976,8 @@ void AudioPolicyService::AudioCommandThread::insertCommand_l(AudioCommand *comma removedCommands.clear(); // insert command at the right place according to its time stamp - LOGV("inserting command: %d at index %d, num commands %d", command->mCommand, (int)i+1, mAudioCommands.size()); + LOGV("inserting command: %d at index %d, num commands %d", + command->mCommand, (int)i+1, mAudioCommands.size()); mAudioCommands.insertAt(command, i + 1); } diff --git a/services/audioflinger/AudioPolicyService.h b/services/audioflinger/AudioPolicyService.h index a13d0bd..558f455 100644 --- a/services/audioflinger/AudioPolicyService.h +++ b/services/audioflinger/AudioPolicyService.h @@ -28,7 +28,8 @@ class String8; // ---------------------------------------------------------------------------- -class AudioPolicyService: public BnAudioPolicyService, public AudioPolicyClientInterface, public IBinder::DeathRecipient +class AudioPolicyService: public BnAudioPolicyService, public AudioPolicyClientInterface, + public IBinder::DeathRecipient { public: @@ -43,8 +44,9 @@ public: virtual status_t setDeviceConnectionState(AudioSystem::audio_devices device, AudioSystem::device_connection_state state, const char *device_address); - virtual AudioSystem::device_connection_state getDeviceConnectionState(AudioSystem::audio_devices device, - const char *device_address); + virtual AudioSystem::device_connection_state getDeviceConnectionState( + AudioSystem::audio_devices device, + const char *device_address); virtual status_t setPhoneState(int state); virtual status_t setRingerMode(uint32_t mode, uint32_t mask); virtual status_t setForceUse(AudioSystem::force_use usage, AudioSystem::forced_config config); @@ -53,15 +55,21 @@ public: uint32_t samplingRate = 0, uint32_t format = AudioSystem::FORMAT_DEFAULT, uint32_t channels = 0, - AudioSystem::output_flags flags = AudioSystem::OUTPUT_FLAG_INDIRECT); - virtual status_t startOutput(audio_io_handle_t output, AudioSystem::stream_type stream); - virtual status_t stopOutput(audio_io_handle_t output, AudioSystem::stream_type stream); + AudioSystem::output_flags flags = + AudioSystem::OUTPUT_FLAG_INDIRECT); + virtual status_t startOutput(audio_io_handle_t output, + AudioSystem::stream_type stream, + int session = 0); + virtual status_t stopOutput(audio_io_handle_t output, + AudioSystem::stream_type stream, + int session = 0); virtual void releaseOutput(audio_io_handle_t output); virtual audio_io_handle_t getInput(int inputSource, uint32_t samplingRate = 0, uint32_t format = AudioSystem::FORMAT_DEFAULT, uint32_t channels = 0, - AudioSystem::audio_in_acoustics acoustics = (AudioSystem::audio_in_acoustics)0); + AudioSystem::audio_in_acoustics acoustics = + (AudioSystem::audio_in_acoustics)0); virtual status_t startInput(audio_io_handle_t input); virtual status_t stopInput(audio_io_handle_t input); virtual void releaseInput(audio_io_handle_t input); @@ -71,6 +79,16 @@ public: virtual status_t setStreamVolumeIndex(AudioSystem::stream_type stream, int index); virtual status_t getStreamVolumeIndex(AudioSystem::stream_type stream, int *index); + virtual uint32_t getStrategyForStream(AudioSystem::stream_type stream); + + virtual audio_io_handle_t getOutputForEffect(effect_descriptor_t *desc); + virtual status_t registerEffect(effect_descriptor_t *desc, + audio_io_handle_t output, + uint32_t strategy, + int session, + int id); + virtual status_t unregisterEffect(int id); + virtual status_t onTransact( uint32_t code, const Parcel& data, @@ -89,7 +107,8 @@ public: uint32_t *pChannels, uint32_t *pLatencyMs, AudioSystem::output_flags flags); - virtual audio_io_handle_t openDuplicateOutput(audio_io_handle_t output1, audio_io_handle_t output2); + virtual audio_io_handle_t openDuplicateOutput(audio_io_handle_t output1, + audio_io_handle_t output2); virtual status_t closeOutput(audio_io_handle_t output); virtual status_t suspendOutput(audio_io_handle_t output); virtual status_t restoreOutput(audio_io_handle_t output); @@ -99,13 +118,21 @@ public: uint32_t *pChannels, uint32_t acoustics); virtual status_t closeInput(audio_io_handle_t input); - virtual status_t setStreamVolume(AudioSystem::stream_type stream, float volume, audio_io_handle_t output, int delayMs = 0); + virtual status_t setStreamVolume(AudioSystem::stream_type stream, + float volume, + audio_io_handle_t output, + int delayMs = 0); virtual status_t setStreamOutput(AudioSystem::stream_type stream, audio_io_handle_t output); - virtual void setParameters(audio_io_handle_t ioHandle, const String8& keyValuePairs, int delayMs = 0); + virtual void setParameters(audio_io_handle_t ioHandle, + const String8& keyValuePairs, + int delayMs = 0); virtual String8 getParameters(audio_io_handle_t ioHandle, const String8& keys); virtual status_t startTone(ToneGenerator::tone_type tone, AudioSystem::stream_type stream); virtual status_t stopTone(); virtual status_t setVoiceVolume(float volume, int delayMs = 0); + virtual status_t moveEffects(int session, + audio_io_handle_t srcOutput, + audio_io_handle_t dstOutput); private: AudioPolicyService(); diff --git a/services/camera/libcameraservice/Android.mk b/services/camera/libcameraservice/Android.mk index df5c166..87975af 100644 --- a/services/camera/libcameraservice/Android.mk +++ b/services/camera/libcameraservice/Android.mk @@ -1,15 +1,14 @@ LOCAL_PATH:= $(call my-dir) -# -# Set USE_CAMERA_STUB for non-emulator and non-simulator builds, if you want -# the camera service to use the fake camera. For emulator or simulator builds, -# we always use the fake camera. +# Set USE_CAMERA_STUB if you don't want to use the hardware camera. -ifeq ($(USE_CAMERA_STUB),) -USE_CAMERA_STUB:=false +# force these builds to use camera stub only ifneq ($(filter sooner generic sim,$(TARGET_DEVICE)),) -USE_CAMERA_STUB:=true -endif #libcamerastub + USE_CAMERA_STUB:=true +endif + +ifeq ($(USE_CAMERA_STUB),) + USE_CAMERA_STUB:=false endif ifeq ($(USE_CAMERA_STUB),true) @@ -54,18 +53,14 @@ LOCAL_SHARED_LIBRARIES:= \ LOCAL_MODULE:= libcameraservice -LOCAL_CFLAGS += -DLOG_TAG=\"CameraService\" - ifeq ($(TARGET_SIMULATOR),true) LOCAL_CFLAGS += -DSINGLE_PROCESS endif ifeq ($(USE_CAMERA_STUB), true) LOCAL_STATIC_LIBRARIES += libcamerastub -LOCAL_CFLAGS += -include CameraHardwareStub.h else LOCAL_SHARED_LIBRARIES += libcamera endif include $(BUILD_SHARED_LIBRARY) - diff --git a/services/camera/libcameraservice/CameraHardwareStub.cpp b/services/camera/libcameraservice/CameraHardwareStub.cpp index 8b66389..b3e0ee6 100644 --- a/services/camera/libcameraservice/CameraHardwareStub.cpp +++ b/services/camera/libcameraservice/CameraHardwareStub.cpp @@ -47,14 +47,14 @@ void CameraHardwareStub::initDefaultParameters() { CameraParameters p; - p.set("preview-size-values","320x240"); + p.set(CameraParameters::KEY_SUPPORTED_PREVIEW_SIZES, "320x240"); p.setPreviewSize(320, 240); p.setPreviewFrameRate(15); - p.setPreviewFormat("yuv422sp"); + p.setPreviewFormat(CameraParameters::PIXEL_FORMAT_YUV420SP); - p.set("picture-size-values", "320x240"); + p.set(CameraParameters::KEY_SUPPORTED_PICTURE_SIZES, "320x240"); p.setPictureSize(320, 240); - p.setPictureFormat("jpeg"); + p.setPictureFormat(CameraParameters::PIXEL_FORMAT_JPEG); if (setParameters(p) != NO_ERROR) { LOGE("Failed to set default parameters?!"); @@ -66,14 +66,14 @@ void CameraHardwareStub::initHeapLocked() // Create raw heap. int picture_width, picture_height; mParameters.getPictureSize(&picture_width, &picture_height); - mRawHeap = new MemoryHeapBase(picture_width * 2 * picture_height); + mRawHeap = new MemoryHeapBase(picture_width * picture_height * 3 / 2); int preview_width, preview_height; mParameters.getPreviewSize(&preview_width, &preview_height); LOGD("initHeapLocked: preview size=%dx%d", preview_width, preview_height); - // Note that we enforce yuv422 in setParameters(). - int how_big = preview_width * preview_height * 2; + // Note that we enforce yuv420sp in setParameters(). + int how_big = preview_width * preview_height * 3 / 2; // If we are being reinitialized to the same size as before, no // work needs to be done. @@ -99,7 +99,6 @@ CameraHardwareStub::~CameraHardwareStub() { delete mFakeCamera; mFakeCamera = 0; // paranoia - singleton.clear(); } sp<IMemoryHeap> CameraHardwareStub::getPreviewHeap() const @@ -175,7 +174,7 @@ int CameraHardwareStub::previewThread() // Fill the current frame with the fake camera. uint8_t *frame = ((uint8_t *)base) + offset; - fakeCamera->getNextFrameAsYuv422(frame); + fakeCamera->getNextFrameAsYuv420(frame); //LOGV("previewThread: generated frame to buffer %d", mCurrentPreviewFrame); @@ -288,9 +287,9 @@ int CameraHardwareStub::pictureThread() // In the meantime just make another fake camera picture. int w, h; mParameters.getPictureSize(&w, &h); - sp<MemoryBase> mem = new MemoryBase(mRawHeap, 0, w * 2 * h); + sp<MemoryBase> mem = new MemoryBase(mRawHeap, 0, w * h * 3 / 2); FakeCamera cam(w, h); - cam.getNextFrameAsYuv422((uint8_t *)mRawHeap->base()); + cam.getNextFrameAsYuv420((uint8_t *)mRawHeap->base()); mDataCb(CAMERA_MSG_RAW_IMAGE, mem, mCallbackCookie); } @@ -307,7 +306,7 @@ status_t CameraHardwareStub::takePicture() { stopPreview(); if (createThread(beginPictureThread, this) == false) - return -1; + return UNKNOWN_ERROR; return NO_ERROR; } @@ -339,12 +338,14 @@ status_t CameraHardwareStub::setParameters(const CameraParameters& params) Mutex::Autolock lock(mLock); // XXX verify params - if (strcmp(params.getPreviewFormat(), "yuv422sp") != 0) { - LOGE("Only yuv422sp preview is supported"); + if (strcmp(params.getPreviewFormat(), + CameraParameters::PIXEL_FORMAT_YUV420SP) != 0) { + LOGE("Only yuv420sp preview is supported"); return -1; } - if (strcmp(params.getPictureFormat(), "jpeg") != 0) { + if (strcmp(params.getPictureFormat(), + CameraParameters::PIXEL_FORMAT_JPEG) != 0) { LOGE("Only jpeg still pictures are supported"); return -1; } @@ -379,22 +380,29 @@ void CameraHardwareStub::release() { } -wp<CameraHardwareInterface> CameraHardwareStub::singleton; - sp<CameraHardwareInterface> CameraHardwareStub::createInstance() { - if (singleton != 0) { - sp<CameraHardwareInterface> hardware = singleton.promote(); - if (hardware != 0) { - return hardware; - } + return new CameraHardwareStub(); +} + +static CameraInfo sCameraInfo[] = { + { + CAMERA_FACING_BACK, + 90, /* orientation */ } - sp<CameraHardwareInterface> hardware(new CameraHardwareStub()); - singleton = hardware; - return hardware; +}; + +extern "C" int HAL_getNumberOfCameras() +{ + return sizeof(sCameraInfo) / sizeof(sCameraInfo[0]); +} + +extern "C" void HAL_getCameraInfo(int cameraId, struct CameraInfo* cameraInfo) +{ + memcpy(cameraInfo, &sCameraInfo[cameraId], sizeof(CameraInfo)); } -extern "C" sp<CameraHardwareInterface> openCameraHardware() +extern "C" sp<CameraHardwareInterface> HAL_openCameraHardware(int cameraId) { return CameraHardwareStub::createInstance(); } diff --git a/services/camera/libcameraservice/CameraHardwareStub.h b/services/camera/libcameraservice/CameraHardwareStub.h index 957813a4..d3427ba 100644 --- a/services/camera/libcameraservice/CameraHardwareStub.h +++ b/services/camera/libcameraservice/CameraHardwareStub.h @@ -67,8 +67,6 @@ private: CameraHardwareStub(); virtual ~CameraHardwareStub(); - static wp<CameraHardwareInterface> singleton; - static const int kBufferCount = 4; class PreviewThread : public Thread { diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp index 5a55704..a64ddcf 100644 --- a/services/camera/libcameraservice/CameraService.cpp +++ b/services/camera/libcameraservice/CameraService.cpp @@ -16,383 +16,440 @@ */ #define LOG_TAG "CameraService" -#include <utils/Log.h> -#include <binder/IServiceManager.h> +#include <stdio.h> +#include <sys/types.h> +#include <pthread.h> + #include <binder/IPCThreadState.h> -#include <utils/String16.h> -#include <utils/Errors.h> +#include <binder/IServiceManager.h> #include <binder/MemoryBase.h> #include <binder/MemoryHeapBase.h> -#include <camera/ICameraService.h> +#include <cutils/atomic.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> -#include <hardware/hardware.h> - -#include <media/mediaplayer.h> -#include <media/AudioSystem.h> #include "CameraService.h" -#include <cutils/atomic.h> - namespace android { -extern "C" { -#include <stdio.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <pthread.h> -#include <signal.h> -} - -// When you enable this, as well as DEBUG_REFS=1 and -// DEBUG_REFS_ENABLED_BY_DEFAULT=0 in libutils/RefBase.cpp, this will track all -// references to the CameraService::Client in order to catch the case where the -// client is being destroyed while a callback from the CameraHardwareInterface -// is outstanding. This is a serious bug because if we make another call into -// CameraHardwreInterface that itself triggers a callback, we will deadlock. - -#define DEBUG_CLIENT_REFERENCES 0 +// ---------------------------------------------------------------------------- +// Logging support -- this is for debugging only +// Use "adb shell dumpsys media.camera -v 1" to change it. +static volatile int32_t gLogLevel = 0; -#define PICTURE_TIMEOUT seconds(5) +#define LOG1(...) LOGD_IF(gLogLevel >= 1, __VA_ARGS__); +#define LOG2(...) LOGD_IF(gLogLevel >= 2, __VA_ARGS__); -#define DEBUG_DUMP_PREVIEW_FRAME_TO_FILE 0 /* n-th frame to write */ -#define DEBUG_DUMP_JPEG_SNAPSHOT_TO_FILE 0 -#define DEBUG_DUMP_YUV_SNAPSHOT_TO_FILE 0 -#define DEBUG_DUMP_POSTVIEW_SNAPSHOT_TO_FILE 0 +static void setLogLevel(int level) { + android_atomic_write(level, &gLogLevel); +} -#if DEBUG_DUMP_PREVIEW_FRAME_TO_FILE -static int debug_frame_cnt; -#endif +// ---------------------------------------------------------------------------- static int getCallingPid() { return IPCThreadState::self()->getCallingPid(); } -// ---------------------------------------------------------------------------- - -void CameraService::instantiate() { - defaultServiceManager()->addService( - String16("media.camera"), new CameraService()); +static int getCallingUid() { + return IPCThreadState::self()->getCallingUid(); } // ---------------------------------------------------------------------------- -CameraService::CameraService() : - BnCameraService() +// This is ugly and only safe if we never re-create the CameraService, but +// should be ok for now. +static CameraService *gCameraService; + +CameraService::CameraService() +:mSoundRef(0) { - LOGI("CameraService started: pid=%d", getpid()); - mUsers = 0; + LOGI("CameraService started (pid=%d)", getpid()); + + mNumberOfCameras = HAL_getNumberOfCameras(); + if (mNumberOfCameras > MAX_CAMERAS) { + LOGE("Number of cameras(%d) > MAX_CAMERAS(%d).", + mNumberOfCameras, MAX_CAMERAS); + mNumberOfCameras = MAX_CAMERAS; + } + + for (int i = 0; i < mNumberOfCameras; i++) { + setCameraFree(i); + } + + gCameraService = this; } -CameraService::~CameraService() -{ - if (mClient != 0) { - LOGE("mClient was still connected in destructor!"); +CameraService::~CameraService() { + for (int i = 0; i < mNumberOfCameras; i++) { + if (mBusy[i]) { + LOGE("camera %d is still in use in destructor!", i); + } + } + + gCameraService = NULL; +} + +int32_t CameraService::getNumberOfCameras() { + return mNumberOfCameras; +} + +status_t CameraService::getCameraInfo(int cameraId, + struct CameraInfo* cameraInfo) { + if (cameraId < 0 || cameraId >= mNumberOfCameras) { + return BAD_VALUE; } + + HAL_getCameraInfo(cameraId, cameraInfo); + return OK; } -sp<ICamera> CameraService::connect(const sp<ICameraClient>& cameraClient) -{ +sp<ICamera> CameraService::connect( + const sp<ICameraClient>& cameraClient, int cameraId) { int callingPid = getCallingPid(); - LOGV("CameraService::connect E (pid %d, client %p)", callingPid, - cameraClient->asBinder().get()); + LOG1("CameraService::connect E (pid %d, id %d)", callingPid, cameraId); - Mutex::Autolock lock(mServiceLock); sp<Client> client; - if (mClient != 0) { - sp<Client> currentClient = mClient.promote(); - if (currentClient != 0) { - sp<ICameraClient> currentCameraClient(currentClient->getCameraClient()); - if (cameraClient->asBinder() == currentCameraClient->asBinder()) { - // This is the same client reconnecting... - LOGV("CameraService::connect X (pid %d, same client %p) is reconnecting...", - callingPid, cameraClient->asBinder().get()); - return currentClient; - } else { - // It's another client... reject it - LOGV("CameraService::connect X (pid %d, new client %p) rejected. " - "(old pid %d, old client %p)", - callingPid, cameraClient->asBinder().get(), - currentClient->mClientPid, currentCameraClient->asBinder().get()); - if (kill(currentClient->mClientPid, 0) == -1 && errno == ESRCH) { - LOGV("The old client is dead!"); - } + if (cameraId < 0 || cameraId >= mNumberOfCameras) { + LOGE("CameraService::connect X (pid %d) rejected (invalid cameraId %d).", + callingPid, cameraId); + return NULL; + } + + Mutex::Autolock lock(mServiceLock); + if (mClient[cameraId] != 0) { + client = mClient[cameraId].promote(); + if (client != 0) { + if (cameraClient->asBinder() == client->getCameraClient()->asBinder()) { + LOG1("CameraService::connect X (pid %d) (the same client)", + callingPid); return client; - } - } else { - // can't promote, the previous client has died... - LOGV("New client (pid %d) connecting, old reference was dangling...", + } else { + LOGW("CameraService::connect X (pid %d) rejected (existing client).", callingPid); - mClient.clear(); + return NULL; + } } + mClient[cameraId].clear(); } - if (mUsers > 0) { - LOGV("Still have client, rejected"); - return client; + if (mBusy[cameraId]) { + LOGW("CameraService::connect X (pid %d) rejected" + " (camera %d is still busy).", callingPid, cameraId); + return NULL; } - // create a new Client object - client = new Client(this, cameraClient, callingPid); - mClient = client; -#if DEBUG_CLIENT_REFERENCES - // Enable tracking for this object, and track increments and decrements of - // the refcount. - client->trackMe(true, true); -#endif - LOGV("CameraService::connect X"); + sp<CameraHardwareInterface> hardware = HAL_openCameraHardware(cameraId); + if (hardware == NULL) { + LOGE("Fail to open camera hardware (id=%d)", cameraId); + return NULL; + } + CameraInfo info; + HAL_getCameraInfo(cameraId, &info); + client = new Client(this, cameraClient, hardware, cameraId, info.facing, + callingPid); + mClient[cameraId] = client; + LOG1("CameraService::connect X"); return client; } -void CameraService::removeClient(const sp<ICameraClient>& cameraClient) -{ +void CameraService::removeClient(const sp<ICameraClient>& cameraClient) { int callingPid = getCallingPid(); + LOG1("CameraService::removeClient E (pid %d)", callingPid); - // Declare this outside the lock to make absolutely sure the - // destructor won't be called with the lock held. - sp<Client> client; + for (int i = 0; i < mNumberOfCameras; i++) { + // Declare this before the lock to make absolutely sure the + // destructor won't be called with the lock held. + sp<Client> client; - Mutex::Autolock lock(mServiceLock); + Mutex::Autolock lock(mServiceLock); - if (mClient == 0) { - // This happens when we have already disconnected. - LOGV("removeClient (pid %d): already disconnected", callingPid); - return; - } + // This happens when we have already disconnected (or this is + // just another unused camera). + if (mClient[i] == 0) continue; - // Promote mClient. It can fail if we are called from this path: - // Client::~Client() -> disconnect() -> removeClient(). - client = mClient.promote(); - if (client == 0) { - LOGV("removeClient (pid %d): no more strong reference", callingPid); - mClient.clear(); - return; + // Promote mClient. It can fail if we are called from this path: + // Client::~Client() -> disconnect() -> removeClient(). + client = mClient[i].promote(); + + if (client == 0) { + mClient[i].clear(); + continue; + } + + if (cameraClient->asBinder() == client->getCameraClient()->asBinder()) { + // Found our camera, clear and leave. + LOG1("removeClient: clear camera %d", i); + mClient[i].clear(); + break; + } } - if (cameraClient->asBinder() != client->getCameraClient()->asBinder()) { - // ugh! that's not our client!! - LOGW("removeClient (pid %d): mClient doesn't match!", callingPid); - } else { - // okay, good, forget about mClient - mClient.clear(); + LOG1("CameraService::removeClient X (pid %d)", callingPid); +} + +sp<CameraService::Client> CameraService::getClientById(int cameraId) { + if (cameraId < 0 || cameraId >= mNumberOfCameras) return NULL; + return mClient[cameraId].promote(); +} + +status_t CameraService::onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { + // Permission checks + switch (code) { + case BnCameraService::CONNECT: + const int pid = getCallingPid(); + const int self_pid = getpid(); + if (pid != self_pid) { + // we're called from a different process, do the real check + if (!checkCallingPermission( + String16("android.permission.CAMERA"))) { + const int uid = getCallingUid(); + LOGE("Permission Denial: " + "can't use the camera pid=%d, uid=%d", pid, uid); + return PERMISSION_DENIED; + } + } + break; } - LOGV("removeClient (pid %d) done", callingPid); + return BnCameraService::onTransact(code, data, reply, flags); } -// The reason we need this count is a new CameraService::connect() request may -// come in while the previous Client's destructor has not been run or is still -// running. If the last strong reference of the previous Client is gone but -// destructor has not been run, we should not allow the new Client to be created -// because we need to wait for the previous Client to tear down the hardware -// first. -void CameraService::incUsers() { - android_atomic_inc(&mUsers); +// The reason we need this busy bit is a new CameraService::connect() request +// may come in while the previous Client's destructor has not been run or is +// still running. If the last strong reference of the previous Client is gone +// but the destructor has not been finished, we should not allow the new Client +// to be created because we need to wait for the previous Client to tear down +// the hardware first. +void CameraService::setCameraBusy(int cameraId) { + android_atomic_write(1, &mBusy[cameraId]); } -void CameraService::decUsers() { - android_atomic_dec(&mUsers); +void CameraService::setCameraFree(int cameraId) { + android_atomic_write(0, &mBusy[cameraId]); } -static sp<MediaPlayer> newMediaPlayer(const char *file) -{ - sp<MediaPlayer> mp = new MediaPlayer(); - if (mp->setDataSource(file, NULL /* headers */) == NO_ERROR) { +// We share the media players for shutter and recording sound for all clients. +// A reference count is kept to determine when we will actually release the +// media players. + +static MediaPlayer* newMediaPlayer(const char *file) { + MediaPlayer* mp = new MediaPlayer(); + if (mp->setDataSource(file, NULL) == NO_ERROR) { mp->setAudioStreamType(AudioSystem::ENFORCED_AUDIBLE); mp->prepare(); } else { - mp.clear(); - LOGE("Failed to load CameraService sounds."); + LOGE("Failed to load CameraService sounds: %s", file); + return NULL; } return mp; } +void CameraService::loadSound() { + Mutex::Autolock lock(mSoundLock); + LOG1("CameraService::loadSound ref=%d", mSoundRef); + if (mSoundRef++) return; + + mSoundPlayer[SOUND_SHUTTER] = newMediaPlayer("/system/media/audio/ui/camera_click.ogg"); + mSoundPlayer[SOUND_RECORDING] = newMediaPlayer("/system/media/audio/ui/VideoRecord.ogg"); +} + +void CameraService::releaseSound() { + Mutex::Autolock lock(mSoundLock); + LOG1("CameraService::releaseSound ref=%d", mSoundRef); + if (--mSoundRef) return; + + for (int i = 0; i < NUM_SOUNDS; i++) { + if (mSoundPlayer[i] != 0) { + mSoundPlayer[i]->disconnect(); + mSoundPlayer[i].clear(); + } + } +} + +void CameraService::playSound(sound_kind kind) { + LOG1("playSound(%d)", kind); + Mutex::Autolock lock(mSoundLock); + sp<MediaPlayer> player = mSoundPlayer[kind]; + if (player != 0) { + // do not play the sound if stream volume is 0 + // (typically because ringer mode is silent). + int index; + AudioSystem::getStreamVolumeIndex(AudioSystem::ENFORCED_AUDIBLE, &index); + if (index != 0) { + player->seekTo(0); + player->start(); + } + } +} + +// ---------------------------------------------------------------------------- + CameraService::Client::Client(const sp<CameraService>& cameraService, - const sp<ICameraClient>& cameraClient, pid_t clientPid) -{ + const sp<ICameraClient>& cameraClient, + const sp<CameraHardwareInterface>& hardware, + int cameraId, int cameraFacing, int clientPid) { int callingPid = getCallingPid(); - LOGV("Client::Client E (pid %d)", callingPid); + LOG1("Client::Client E (pid %d)", callingPid); + mCameraService = cameraService; mCameraClient = cameraClient; + mHardware = hardware; + mCameraId = cameraId; + mCameraFacing = cameraFacing; mClientPid = clientPid; - mHardware = openCameraHardware(); mUseOverlay = mHardware->useOverlay(); + mMsgEnabled = 0; mHardware->setCallbacks(notifyCallback, dataCallback, dataCallbackTimestamp, - mCameraService.get()); + (void *)cameraId); // Enable zoom, error, and focus messages by default - mHardware->enableMsgType(CAMERA_MSG_ERROR | - CAMERA_MSG_ZOOM | - CAMERA_MSG_FOCUS); - - mMediaPlayerClick = newMediaPlayer("/system/media/audio/ui/camera_click.ogg"); - mMediaPlayerBeep = newMediaPlayer("/system/media/audio/ui/VideoRecord.ogg"); + 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 = 0; - cameraService->incUsers(); - LOGV("Client::Client X (pid %d)", callingPid); + mOrientation = getOrientation(0, mCameraFacing == CAMERA_FACING_FRONT); + mOrientationChanged = false; + cameraService->setCameraBusy(cameraId); + cameraService->loadSound(); + LOG1("Client::Client X (pid %d)", callingPid); } -status_t CameraService::Client::checkPid() -{ +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(); + mCameraService->releaseSound(); + LOG1("Client::~Client X (pid %d, this %p)", callingPid, this); +} + +// ---------------------------------------------------------------------------- + +status_t CameraService::Client::checkPid() const { int callingPid = getCallingPid(); - if (mClientPid == callingPid) return NO_ERROR; - LOGW("Attempt to use locked camera (client %p) from different process " - " (old pid %d, new pid %d)", - getCameraClient()->asBinder().get(), mClientPid, callingPid); - return -EBUSY; + if (callingPid == mClientPid) return NO_ERROR; + + LOGW("attempt to use a locked camera from a different process" + " (old pid %d, new pid %d)", mClientPid, callingPid); + return EBUSY; } -status_t CameraService::Client::lock() -{ +status_t CameraService::Client::checkPidAndHardware() const { + status_t result = checkPid(); + if (result != NO_ERROR) return result; + if (mHardware == 0) { + LOGE("attempt to use a camera after disconnect() (pid %d)", getCallingPid()); + return INVALID_OPERATION; + } + return NO_ERROR; +} + +status_t CameraService::Client::lock() { int callingPid = getCallingPid(); - LOGV("lock from pid %d (mClientPid %d)", callingPid, mClientPid); - Mutex::Autolock _l(mLock); + LOG1("lock (pid %d)", callingPid); + Mutex::Autolock lock(mLock); + // lock camera to this client if the the camera is unlocked if (mClientPid == 0) { mClientPid = callingPid; return NO_ERROR; } - // returns NO_ERROR if the client already owns the camera, -EBUSY otherwise + + // returns NO_ERROR if the client already owns the camera, EBUSY otherwise return checkPid(); } -status_t CameraService::Client::unlock() -{ +status_t CameraService::Client::unlock() { int callingPid = getCallingPid(); - LOGV("unlock from pid %d (mClientPid %d)", callingPid, mClientPid); - Mutex::Autolock _l(mLock); - // allow anyone to use camera + LOG1("unlock (pid %d)", callingPid); + Mutex::Autolock lock(mLock); + + // allow anyone to use camera (after they lock the camera) status_t result = checkPid(); if (result == NO_ERROR) { mClientPid = 0; - LOGV("clear mCameraClient (pid %d)", callingPid); - // we need to remove the reference so that when app goes - // away, the reference count goes to 0. + LOG1("clear mCameraClient (pid %d)", callingPid); + // we need to remove the reference to ICameraClient so that when the app + // goes away, the reference count goes to 0. mCameraClient.clear(); } return result; } -status_t CameraService::Client::connect(const sp<ICameraClient>& client) -{ +// connect a new client to the camera +status_t CameraService::Client::connect(const sp<ICameraClient>& client) { int callingPid = getCallingPid(); + LOG1("connect E (pid %d)", callingPid); + Mutex::Autolock lock(mLock); - // connect a new process to the camera - LOGV("Client::connect E (pid %d, client %p)", callingPid, client->asBinder().get()); - - // I hate this hack, but things get really ugly when the media recorder - // service is handing back the camera to the app. The ICameraClient - // destructor will be called during the same IPC, making it look like - // the remote client is trying to disconnect. This hack temporarily - // sets the mClientPid to an invalid pid to prevent the hardware from - // being torn down. - { - - // hold a reference to the old client or we will deadlock if the client is - // in the same process and we hold the lock when we remove the reference - sp<ICameraClient> oldClient; - { - Mutex::Autolock _l(mLock); - if (mClientPid != 0 && checkPid() != NO_ERROR) { - LOGW("Tried to connect to locked camera (old pid %d, new pid %d)", - mClientPid, callingPid); - return -EBUSY; - } - oldClient = mCameraClient; - - // did the client actually change? - if ((mCameraClient != NULL) && (client->asBinder() == mCameraClient->asBinder())) { - LOGV("Connect to the same client"); - return NO_ERROR; - } - - mCameraClient = client; - mClientPid = -1; - mPreviewCallbackFlag = FRAME_CALLBACK_FLAG_NOOP; - LOGV("Connect to the new client (pid %d, client %p)", - callingPid, mCameraClient->asBinder().get()); - } + if (mClientPid != 0 && checkPid() != NO_ERROR) { + LOGW("Tried to connect to a locked camera (old pid %d, new pid %d)", + mClientPid, callingPid); + return EBUSY; + } + if (mCameraClient != 0 && (client->asBinder() == mCameraClient->asBinder())) { + LOG1("Connect to the same client"); + return NO_ERROR; } - // the old client destructor is called when oldClient goes out of scope - // now we set the new PID to lock the interface again + + mPreviewCallbackFlag = FRAME_CALLBACK_FLAG_NOOP; mClientPid = callingPid; + mCameraClient = client; + LOG1("connect X (pid %d)", callingPid); return NO_ERROR; } -#if HAVE_ANDROID_OS -static void *unregister_surface(void *arg) -{ - ISurface *surface = (ISurface *)arg; - surface->unregisterBuffers(); - IPCThreadState::self()->flushCommands(); - return NULL; -} -#endif - -CameraService::Client::~Client() -{ +void CameraService::Client::disconnect() { int callingPid = getCallingPid(); + LOG1("disconnect E (pid %d)", callingPid); + Mutex::Autolock lock(mLock); - // tear down client - LOGV("Client::~Client E (pid %d, client %p)", - callingPid, getCameraClient()->asBinder().get()); - if (mSurface != 0 && !mUseOverlay) { -#if HAVE_ANDROID_OS - 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, - unregister_surface, - mSurface.get()); - pthread_join(thr, NULL); -#else - mSurface->unregisterBuffers(); -#endif - } - - if (mMediaPlayerBeep.get() != NULL) { - mMediaPlayerBeep->disconnect(); - mMediaPlayerBeep.clear(); - } - if (mMediaPlayerClick.get() != NULL) { - mMediaPlayerClick->disconnect(); - mMediaPlayerClick.clear(); + if (checkPid() != NO_ERROR) { + LOGW("different client - don't disconnect"); + return; } - // make sure we tear down the hardware - mClientPid = callingPid; - disconnect(); - LOGV("Client::~Client X (pid %d)", mClientPid); -} - -void CameraService::Client::disconnect() -{ - int callingPid = getCallingPid(); - - LOGV("Client::disconnect() E (pid %d client %p)", - callingPid, getCameraClient()->asBinder().get()); - - Mutex::Autolock lock(mLock); if (mClientPid <= 0) { - LOGV("camera is unlocked (mClientPid = %d), don't tear down hardware", mClientPid); - return; - } - if (checkPid() != NO_ERROR) { - LOGV("Different client - don't disconnect"); + LOG1("camera is unlocked (mClientPid = %d), don't tear down hardware", mClientPid); return; } @@ -400,508 +457,546 @@ void CameraService::Client::disconnect() // from the user directly, or called by the destructor. if (mHardware == 0) return; - LOGV("hardware teardown"); + LOG1("hardware teardown"); // Before destroying mHardware, we must make sure it's in the // idle state. + // Turn off all messages. + disableMsgType(CAMERA_MSG_ALL_MSGS); mHardware->stopPreview(); - // Cancel all picture callbacks. - mHardware->disableMsgType(CAMERA_MSG_SHUTTER | - CAMERA_MSG_POSTVIEW_FRAME | - CAMERA_MSG_RAW_IMAGE | - CAMERA_MSG_COMPRESSED_IMAGE); mHardware->cancelPicture(); - // Turn off remaining messages. - mHardware->disableMsgType(CAMERA_MSG_ALL_MSGS); // Release the hardware resources. mHardware->release(); // Release the held overlay resources. - if (mUseOverlay) - { + if (mUseOverlay) { mOverlayRef = 0; } mHardware.clear(); mCameraService->removeClient(mCameraClient); - mCameraService->decUsers(); + mCameraService->setCameraFree(mCameraId); - LOGV("Client::disconnect() X (pid %d)", callingPid); + LOG1("disconnect X (pid %d)", callingPid); } -// pass the buffered ISurface to the camera service -status_t CameraService::Client::setPreviewDisplay(const sp<ISurface>& surface) -{ - LOGV("setPreviewDisplay(%p) (pid %d)", - ((surface == NULL) ? NULL : surface.get()), getCallingPid()); +// ---------------------------------------------------------------------------- + +// set the ISurface that the preview will use +status_t CameraService::Client::setPreviewDisplay(const sp<ISurface>& surface) { + LOG1("setPreviewDisplay(%p) (pid %d)", surface.get(), getCallingPid()); Mutex::Autolock lock(mLock); - status_t result = checkPid(); + status_t result = checkPidAndHardware(); if (result != NO_ERROR) return result; - Mutex::Autolock surfaceLock(mSurfaceLock); result = NO_ERROR; - // asBinder() is safe on NULL (returns NULL) - if (surface->asBinder() != mSurface->asBinder()) { - if (mSurface != 0) { - LOGV("clearing old preview surface %p", mSurface.get()); - if ( !mUseOverlay) - { - mSurface->unregisterBuffers(); - } - else - { - // Force the destruction of any previous overlay - sp<Overlay> dummy; - mHardware->setOverlay( dummy ); - } - } - mSurface = surface; - mOverlayRef = 0; - // If preview has been already started, set overlay or register preview - // buffers now. - if (mHardware->previewEnabled()) { - if (mUseOverlay) { - result = setOverlay(); - } else if (mSurface != 0) { - result = registerPreviewBuffers(); - } - } - } - return result; -} - -// set the preview callback flag to affect how the received frames from -// preview are handled. -void CameraService::Client::setPreviewCallbackFlag(int callback_flag) -{ - LOGV("setPreviewCallbackFlag (pid %d)", getCallingPid()); - Mutex::Autolock lock(mLock); - if (checkPid() != NO_ERROR) return; - mPreviewCallbackFlag = callback_flag; - - if(mUseOverlay) { - if(mPreviewCallbackFlag & FRAME_CALLBACK_FLAG_ENABLE_MASK) - mHardware->enableMsgType(CAMERA_MSG_PREVIEW_FRAME); - else - mHardware->disableMsgType(CAMERA_MSG_PREVIEW_FRAME); - } -} - -// start preview mode -status_t CameraService::Client::startCameraMode(camera_mode mode) -{ - int callingPid = getCallingPid(); - - LOGV("startCameraMode(%d) (pid %d)", mode, callingPid); - - /* we cannot call into mHardware with mLock held because - * mHardware has callbacks onto us which acquire this lock - */ - Mutex::Autolock lock(mLock); - status_t result = checkPid(); - if (result != NO_ERROR) return result; - - if (mHardware == 0) { - LOGE("mHardware is NULL, returning."); - return INVALID_OPERATION; + // return if no change in surface. + // asBinder() is safe on NULL (returns NULL) + if (surface->asBinder() == mSurface->asBinder()) { + return result; } - switch(mode) { - case CAMERA_RECORDING_MODE: - if (mSurface == 0) { - LOGE("setPreviewDisplay must be called before startRecordingMode."); - return INVALID_OPERATION; + 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(); } - return startRecordingMode(); - - default: // CAMERA_PREVIEW_MODE - if (mSurface == 0) { - LOGV("mSurface is not set yet."); + } + mSurface = surface; + mOverlayRef = 0; + // If preview has been already started, set overlay or register preview + // buffers now. + if (mHardware->previewEnabled()) { + if (mUseOverlay) { + result = setOverlay(); + } else if (mSurface != 0) { + result = registerPreviewBuffers(); } - return startPreviewMode(); } -} -status_t CameraService::Client::startRecordingMode() -{ - LOGV("startRecordingMode (pid %d)", getCallingPid()); - - status_t ret = UNKNOWN_ERROR; + return result; +} - // if preview has not been started, start preview first - if (!mHardware->previewEnabled()) { - ret = startPreviewMode(); - if (ret != NO_ERROR) { - return ret; - } - } +status_t CameraService::Client::registerPreviewBuffers() { + int w, h; + CameraParameters params(mHardware->getParameters()); + params.getPreviewSize(&w, &h); - // if recording has been enabled, nothing needs to be done - if (mHardware->recordingEnabled()) { - return NO_ERROR; - } + // 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()); - // start recording mode - ret = mHardware->startRecording(); - if (ret != NO_ERROR) { - LOGE("mHardware->startRecording() failed with status %d", ret); + status_t result = mSurface->registerBuffers(buffers); + if (result != NO_ERROR) { + LOGE("registerBuffers failed with status %d", result); } - return ret; + return result; } -status_t CameraService::Client::setOverlay() -{ - LOGV("setOverlay"); +status_t CameraService::Client::setOverlay() { int w, h; CameraParameters params(mHardware->getParameters()); params.getPreviewSize(&w, &h); - if ( w != mOverlayW || h != mOverlayH ) - { + if (w != mOverlayW || h != mOverlayH || mOrientationChanged) { // Force the destruction of any previous overlay sp<Overlay> dummy; - mHardware->setOverlay( dummy ); + mHardware->setOverlay(dummy); mOverlayRef = 0; + mOrientationChanged = false; } - status_t ret = NO_ERROR; - if (mSurface != 0) { - if (mOverlayRef.get() == NULL) { - + status_t result = NO_ERROR; + if (mSurface == 0) { + result = mHardware->setOverlay(NULL); + } 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 + // 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 != NULL) break; + if (mOverlayRef != 0) break; LOGW("Overlay create failed - retrying"); usleep(20000); } - if ( mOverlayRef.get() == NULL ) - { + if (mOverlayRef == 0) { LOGE("Overlay Creation Failed!"); return -EINVAL; } - ret = mHardware->setOverlay(new Overlay(mOverlayRef)); + result = mHardware->setOverlay(new Overlay(mOverlayRef)); } - } else { - ret = mHardware->setOverlay(NULL); } - if (ret != NO_ERROR) { - LOGE("mHardware->setOverlay() failed with status %d\n", ret); + if (result != NO_ERROR) { + LOGE("mHardware->setOverlay() failed with status %d\n", result); + return result; } mOverlayW = w; mOverlayH = h; - return ret; + return result; } -status_t CameraService::Client::registerPreviewBuffers() -{ - int w, h; - CameraParameters params(mHardware->getParameters()); - params.getPreviewSize(&w, &h); +// set the preview callback flag to affect how the received frames from +// preview are handled. +void CameraService::Client::setPreviewCallbackFlag(int callback_flag) { + LOG1("setPreviewCallbackFlag(%d) (pid %d)", callback_flag, getCallingPid()); + Mutex::Autolock lock(mLock); + if (checkPidAndHardware() != NO_ERROR) return; - // don't use a hardcoded format here - ISurface::BufferHeap buffers(w, h, w, h, - HAL_PIXEL_FORMAT_YCrCb_420_SP, - mOrientation, - 0, - mHardware->getPreviewHeap()); + mPreviewCallbackFlag = callback_flag; - status_t ret = mSurface->registerBuffers(buffers); - if (ret != NO_ERROR) { - LOGE("registerBuffers failed with status %d", ret); + // 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); + } } - return ret; } -status_t CameraService::Client::startPreviewMode() -{ - LOGV("startPreviewMode (pid %d)", getCallingPid()); +// start preview mode +status_t CameraService::Client::startPreview() { + LOG1("startPreview (pid %d)", getCallingPid()); + return startCameraMode(CAMERA_PREVIEW_MODE); +} + +// start recording mode +status_t CameraService::Client::startRecording() { + LOG1("startRecording (pid %d)", getCallingPid()); + return startCameraMode(CAMERA_RECORDING_MODE); +} + +// start preview or recording +status_t CameraService::Client::startCameraMode(camera_mode mode) { + LOG1("startCameraMode(%d)", mode); + Mutex::Autolock lock(mLock); + status_t result = checkPidAndHardware(); + if (result != NO_ERROR) return result; + + switch(mode) { + case CAMERA_PREVIEW_MODE: + if (mSurface == 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."); + return INVALID_OPERATION; + } + return startRecordingMode(); + default: + return UNKNOWN_ERROR; + } +} + +status_t CameraService::Client::startPreviewMode() { + LOG1("startPreviewMode"); + status_t result = NO_ERROR; // if preview has been enabled, nothing needs to be done if (mHardware->previewEnabled()) { return NO_ERROR; } - // start preview mode -#if DEBUG_DUMP_PREVIEW_FRAME_TO_FILE - debug_frame_cnt = 0; -#endif - status_t ret = NO_ERROR; - if (mUseOverlay) { // If preview display has been set, set overlay now. if (mSurface != 0) { - ret = setOverlay(); + result = setOverlay(); } - if (ret != NO_ERROR) return ret; - ret = mHardware->startPreview(); + if (result != NO_ERROR) return result; + result = mHardware->startPreview(); } else { - mHardware->enableMsgType(CAMERA_MSG_PREVIEW_FRAME); - ret = mHardware->startPreview(); - if (ret != NO_ERROR) return ret; + 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 registered with raw heap. + // Unregister here because the surface may be previously registered + // with the raw (snapshot) heap. mSurface->unregisterBuffers(); - ret = registerPreviewBuffers(); + result = registerPreviewBuffers(); } } - return ret; + return result; } -status_t CameraService::Client::startPreview() -{ - LOGV("startPreview (pid %d)", getCallingPid()); - - return startCameraMode(CAMERA_PREVIEW_MODE); -} +status_t CameraService::Client::startRecordingMode() { + LOG1("startRecordingMode"); + status_t result = NO_ERROR; -status_t CameraService::Client::startRecording() -{ - LOGV("startRecording (pid %d)", getCallingPid()); + // if recording has been enabled, nothing needs to be done + if (mHardware->recordingEnabled()) { + return NO_ERROR; + } - if (mMediaPlayerBeep.get() != NULL) { - // do not play record jingle if stream volume is 0 - // (typically because ringer mode is silent). - int index; - AudioSystem::getStreamVolumeIndex(AudioSystem::ENFORCED_AUDIBLE, &index); - if (index != 0) { - mMediaPlayerBeep->seekTo(0); - mMediaPlayerBeep->start(); + // if preview has not been started, start preview first + if (!mHardware->previewEnabled()) { + result = startPreviewMode(); + if (result != NO_ERROR) { + return result; } } - mHardware->enableMsgType(CAMERA_MSG_VIDEO_FRAME); - - return startCameraMode(CAMERA_RECORDING_MODE); + // start recording mode + enableMsgType(CAMERA_MSG_VIDEO_FRAME); + mCameraService->playSound(SOUND_RECORDING); + result = mHardware->startRecording(); + if (result != NO_ERROR) { + LOGE("mHardware->startRecording() failed with status %d", result); + } + return result; } // stop preview mode -void CameraService::Client::stopPreview() -{ - LOGV("stopPreview (pid %d)", getCallingPid()); - - // hold main lock during state transition - { - Mutex::Autolock lock(mLock); - if (checkPid() != NO_ERROR) return; - - if (mHardware == 0) { - LOGE("mHardware is NULL, returning."); - return; - } +void CameraService::Client::stopPreview() { + LOG1("stopPreview (pid %d)", getCallingPid()); + Mutex::Autolock lock(mLock); + if (checkPidAndHardware() != NO_ERROR) return; - mHardware->stopPreview(); - mHardware->disableMsgType(CAMERA_MSG_PREVIEW_FRAME); - LOGV("stopPreview(), hardware stopped OK"); + disableMsgType(CAMERA_MSG_PREVIEW_FRAME); + mHardware->stopPreview(); - if (mSurface != 0 && !mUseOverlay) { - mSurface->unregisterBuffers(); - } + if (mSurface != 0 && !mUseOverlay) { + mSurface->unregisterBuffers(); } - // hold preview buffer lock - { - Mutex::Autolock lock(mPreviewLock); - mPreviewBuffer.clear(); - } + mPreviewBuffer.clear(); } // stop recording mode -void CameraService::Client::stopRecording() -{ - LOGV("stopRecording (pid %d)", getCallingPid()); - - // hold main lock during state transition - { - Mutex::Autolock lock(mLock); - if (checkPid() != NO_ERROR) return; - - if (mHardware == 0) { - LOGE("mHardware is NULL, returning."); - return; - } +void CameraService::Client::stopRecording() { + LOG1("stopRecording (pid %d)", getCallingPid()); + Mutex::Autolock lock(mLock); + if (checkPidAndHardware() != NO_ERROR) return; - if (mMediaPlayerBeep.get() != NULL) { - mMediaPlayerBeep->seekTo(0); - mMediaPlayerBeep->start(); - } + mCameraService->playSound(SOUND_RECORDING); + disableMsgType(CAMERA_MSG_VIDEO_FRAME); + mHardware->stopRecording(); - mHardware->stopRecording(); - mHardware->disableMsgType(CAMERA_MSG_VIDEO_FRAME); - LOGV("stopRecording(), hardware stopped OK"); - } - - // hold preview buffer lock - { - Mutex::Autolock lock(mPreviewLock); - mPreviewBuffer.clear(); - } + mPreviewBuffer.clear(); } // release a recording frame -void CameraService::Client::releaseRecordingFrame(const sp<IMemory>& mem) -{ +void CameraService::Client::releaseRecordingFrame(const sp<IMemory>& mem) { Mutex::Autolock lock(mLock); - if (checkPid() != NO_ERROR) return; - - if (mHardware == 0) { - LOGE("mHardware is NULL, returning."); - return; - } - + if (checkPidAndHardware() != NO_ERROR) return; mHardware->releaseRecordingFrame(mem); } -bool CameraService::Client::previewEnabled() -{ +bool CameraService::Client::previewEnabled() { + LOG1("previewEnabled (pid %d)", getCallingPid()); + Mutex::Autolock lock(mLock); - if (mHardware == 0) return false; + if (checkPidAndHardware() != NO_ERROR) return false; return mHardware->previewEnabled(); } -bool CameraService::Client::recordingEnabled() -{ +bool CameraService::Client::recordingEnabled() { + LOG1("recordingEnabled (pid %d)", getCallingPid()); + Mutex::Autolock lock(mLock); - if (mHardware == 0) return false; + if (checkPidAndHardware() != NO_ERROR) return false; return mHardware->recordingEnabled(); } -// Safely retrieves a strong pointer to the client during a hardware callback. -sp<CameraService::Client> CameraService::Client::getClientFromCookie(void* user) -{ - sp<Client> client = 0; - CameraService *service = static_cast<CameraService*>(user); - if (service != NULL) { - Mutex::Autolock ourLock(service->mServiceLock); - if (service->mClient != 0) { - client = service->mClient.promote(); - if (client == 0) { - LOGE("getClientFromCookie: client appears to have died"); - service->mClient.clear(); - } - } else { - LOGE("getClientFromCookie: got callback but client was NULL"); - } - } - return client; -} +status_t CameraService::Client::autoFocus() { + LOG1("autoFocus (pid %d)", getCallingPid()); + Mutex::Autolock lock(mLock); + status_t result = checkPidAndHardware(); + if (result != NO_ERROR) return result; -#if DEBUG_DUMP_JPEG_SNAPSHOT_TO_FILE || \ - DEBUG_DUMP_YUV_SNAPSHOT_TO_FILE || \ - DEBUG_DUMP_PREVIEW_FRAME_TO_FILE -static void dump_to_file(const char *fname, - uint8_t *buf, uint32_t size) -{ - int nw, cnt = 0; - uint32_t written = 0; + return mHardware->autoFocus(); +} - LOGV("opening file [%s]\n", fname); - int fd = open(fname, O_RDWR | O_CREAT); - if (fd < 0) { - LOGE("failed to create file [%s]: %s", fname, strerror(errno)); - return; - } +status_t CameraService::Client::cancelAutoFocus() { + LOG1("cancelAutoFocus (pid %d)", getCallingPid()); - LOGV("writing %d bytes to file [%s]\n", size, fname); - while (written < size) { - nw = ::write(fd, - buf + written, - size - written); - if (nw < 0) { - LOGE("failed to write to file [%s]: %s", - fname, strerror(errno)); - break; - } - written += nw; - cnt++; - } - LOGV("done writing %d bytes to file [%s] in %d passes\n", - size, fname, cnt); - ::close(fd); + Mutex::Autolock lock(mLock); + status_t result = checkPidAndHardware(); + if (result != NO_ERROR) return result; + + return mHardware->cancelAutoFocus(); } -#endif -status_t CameraService::Client::autoFocus() -{ - LOGV("autoFocus (pid %d)", getCallingPid()); +// take a picture - image is returned in callback +status_t CameraService::Client::takePicture() { + LOG1("takePicture (pid %d)", getCallingPid()); Mutex::Autolock lock(mLock); - status_t result = checkPid(); + status_t result = checkPidAndHardware(); if (result != NO_ERROR) return result; - if (mHardware == 0) { - LOGE("mHardware is NULL, returning."); - return INVALID_OPERATION; - } + enableMsgType(CAMERA_MSG_SHUTTER | + CAMERA_MSG_POSTVIEW_FRAME | + CAMERA_MSG_RAW_IMAGE | + CAMERA_MSG_COMPRESSED_IMAGE); - return mHardware->autoFocus(); + return mHardware->takePicture(); } -status_t CameraService::Client::cancelAutoFocus() -{ - LOGV("cancelAutoFocus (pid %d)", getCallingPid()); +// set preview/capture parameters - key/value pairs +status_t CameraService::Client::setParameters(const String8& params) { + LOG1("setParameters (pid %d) (%s)", getCallingPid(), params.string()); Mutex::Autolock lock(mLock); - status_t result = checkPid(); + status_t result = checkPidAndHardware(); if (result != NO_ERROR) return result; - if (mHardware == 0) { - LOGE("mHardware is NULL, returning."); - return INVALID_OPERATION; - } - - return mHardware->cancelAutoFocus(); + CameraParameters p(params); + return mHardware->setParameters(p); } -// take a picture - image is returned in callback -status_t CameraService::Client::takePicture() -{ - LOGV("takePicture (pid %d)", getCallingPid()); +// get preview/capture parameters - key/value pairs +String8 CameraService::Client::getParameters() const { + Mutex::Autolock lock(mLock); + if (checkPidAndHardware() != NO_ERROR) return String8(); + + String8 params(mHardware->getParameters().flatten()); + LOG1("getParameters (pid %d) (%s)", getCallingPid(), params.string()); + return params; +} +status_t CameraService::Client::sendCommand(int32_t cmd, int32_t arg1, int32_t arg2) { + LOG1("sendCommand (pid %d)", getCallingPid()); + int orientation; Mutex::Autolock lock(mLock); - status_t result = checkPid(); + status_t result = checkPidAndHardware(); if (result != NO_ERROR) return result; - if (mHardware == 0) { - LOGE("mHardware is NULL, returning."); - return INVALID_OPERATION; + if (cmd == CAMERA_CMD_SET_DISPLAY_ORIENTATION) { + // The orientation cannot be set during preview. + if (mHardware->previewEnabled()) { + return INVALID_OPERATION; + } + // Mirror the preview if the camera is front-facing. + orientation = getOrientation(arg1, mCameraFacing == CAMERA_FACING_FRONT); + if (orientation == -1) return BAD_VALUE; + + if (mOrientation != orientation) { + mOrientation = orientation; + if (mOverlayRef != 0) mOrientationChanged = true; + } + return OK; } - mHardware->enableMsgType(CAMERA_MSG_SHUTTER | - CAMERA_MSG_POSTVIEW_FRAME | - CAMERA_MSG_RAW_IMAGE | - CAMERA_MSG_COMPRESSED_IMAGE); + return mHardware->sendCommand(cmd, arg1, arg2); +} + +// ---------------------------------------------------------------------------- - return mHardware->takePicture(); +void CameraService::Client::enableMsgType(int32_t msgType) { + android_atomic_or(msgType, &mMsgEnabled); + mHardware->enableMsgType(msgType); } -// snapshot taken -void CameraService::Client::handleShutter( - image_rect_type *size // The width and height of yuv picture for - // registerBuffer. If this is NULL, use the picture - // size from parameters. -) -{ - // Play shutter sound. - if (mMediaPlayerClick.get() != NULL) { - // do not play shutter sound if stream volume is 0 - // (typically because ringer mode is silent). - int index; - AudioSystem::getStreamVolumeIndex(AudioSystem::ENFORCED_AUDIBLE, &index); - if (index != 0) { - mMediaPlayerClick->seekTo(0); - mMediaPlayerClick->start(); +void CameraService::Client::disableMsgType(int32_t msgType) { + android_atomic_and(~msgType, &mMsgEnabled); + mHardware->disableMsgType(msgType); +} + +#define CHECK_MESSAGE_INTERVAL 10 // 10ms +bool CameraService::Client::lockIfMessageWanted(int32_t msgType) { + int sleepCount = 0; + while (mMsgEnabled & msgType) { + if (mLock.tryLock() == NO_ERROR) { + if (sleepCount > 0) { + LOG1("lockIfMessageWanted(%d): waited for %d ms", + msgType, sleepCount * CHECK_MESSAGE_INTERVAL); + } + return true; } + if (sleepCount++ == 0) { + LOG1("lockIfMessageWanted(%d): enter sleep", msgType); + } + usleep(CHECK_MESSAGE_INTERVAL * 1000); + } + LOGW("lockIfMessageWanted(%d): dropped unwanted message", msgType); + return false; +} + +// ---------------------------------------------------------------------------- + +// Converts from a raw pointer to the client to a strong pointer during a +// hardware callback. This requires the callbacks only happen when the client +// is still alive. +sp<CameraService::Client> CameraService::Client::getClientFromCookie(void* user) { + sp<Client> client = gCameraService->getClientById((int) user); + + // This could happen if the Client is in the process of shutting down (the + // last strong reference is gone, but the destructor hasn't finished + // stopping the hardware). + if (client == 0) return NULL; + + // The checks below are not necessary and are for debugging only. + if (client->mCameraService.get() != gCameraService) { + LOGE("mismatch service!"); + return NULL; + } + + if (client->mHardware == 0) { + LOGE("mHardware == 0: callback after disconnect()?"); + return NULL; + } + + return client; +} + +// Callback messages can be dispatched to internal handlers or pass to our +// client's callback functions, depending on the message type. +// +// notifyCallback: +// CAMERA_MSG_SHUTTER handleShutter +// (others) c->notifyCallback +// dataCallback: +// CAMERA_MSG_PREVIEW_FRAME handlePreviewData +// CAMERA_MSG_POSTVIEW_FRAME handlePostview +// CAMERA_MSG_RAW_IMAGE handleRawPicture +// CAMERA_MSG_COMPRESSED_IMAGE handleCompressedPicture +// (others) c->dataCallback +// dataCallbackTimestamp +// (others) c->dataCallbackTimestamp +// +// NOTE: the *Callback functions grab mLock of the client before passing +// control to handle* functions. So the handle* functions must release the +// lock before calling the ICameraClient's callbacks, so those callbacks can +// invoke methods in the Client class again (For example, the preview frame +// callback may want to releaseRecordingFrame). The handle* functions must +// release the lock after all accesses to member variables, so it must be +// handled very carefully. + +void CameraService::Client::notifyCallback(int32_t msgType, int32_t ext1, + int32_t ext2, void* user) { + LOG2("notifyCallback(%d)", msgType); + + sp<Client> client = getClientFromCookie(user); + if (client == 0) return; + if (!client->lockIfMessageWanted(msgType)) return; + + switch (msgType) { + case CAMERA_MSG_SHUTTER: + // ext1 is the dimension of the yuv picture. + client->handleShutter((image_rect_type *)ext1); + break; + default: + client->handleGenericNotify(msgType, ext1, ext2); + break; + } +} + +void CameraService::Client::dataCallback(int32_t msgType, + const sp<IMemory>& dataPtr, void* user) { + LOG2("dataCallback(%d)", msgType); + + sp<Client> client = getClientFromCookie(user); + if (client == 0) return; + if (!client->lockIfMessageWanted(msgType)) return; + + if (dataPtr == 0) { + LOGE("Null data returned in data callback"); + client->handleGenericNotify(CAMERA_MSG_ERROR, UNKNOWN_ERROR, 0); + return; + } + + switch (msgType) { + case CAMERA_MSG_PREVIEW_FRAME: + client->handlePreviewData(dataPtr); + break; + case CAMERA_MSG_POSTVIEW_FRAME: + client->handlePostview(dataPtr); + break; + case CAMERA_MSG_RAW_IMAGE: + client->handleRawPicture(dataPtr); + break; + case CAMERA_MSG_COMPRESSED_IMAGE: + client->handleCompressedPicture(dataPtr); + break; + default: + client->handleGenericData(msgType, dataPtr); + break; } +} + +void CameraService::Client::dataCallbackTimestamp(nsecs_t timestamp, + int32_t msgType, const sp<IMemory>& dataPtr, void* user) { + LOG2("dataCallbackTimestamp(%d)", msgType); + + sp<Client> client = getClientFromCookie(user); + if (client == 0) return; + if (!client->lockIfMessageWanted(msgType)) return; + + if (dataPtr == 0) { + LOGE("Null data returned in data with timestamp callback"); + client->handleGenericNotify(CAMERA_MSG_ERROR, UNKNOWN_ERROR, 0); + return; + } + + client->handleGenericDataTimestamp(timestamp, msgType, dataPtr); +} + +// snapshot taken callback +// "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) { @@ -909,10 +1004,12 @@ void CameraService::Client::handleShutter( } sp<ICameraClient> c = mCameraClient; - if (c != NULL) { + if (c != 0) { + mLock.unlock(); c->notifyCallback(CAMERA_MSG_SHUTTER, 0, 0); + if (!lockIfMessageWanted(CAMERA_MSG_SHUTTER)) return; } - mHardware->disableMsgType(CAMERA_MSG_SHUTTER); + disableMsgType(CAMERA_MSG_SHUTTER); // It takes some time before yuvPicture callback to be called. // Register the buffer for raw image here to reduce latency. @@ -926,7 +1023,7 @@ void CameraService::Client::handleShutter( h = size->height; w &= ~1; h &= ~1; - LOGV("Snapshot image width=%d, height=%d", w, h); + LOG1("Snapshot image width=%d, height=%d", w, h); } // FIXME: don't use hardcoded format constants here ISurface::BufferHeap buffers(w, h, w, h, @@ -934,38 +1031,20 @@ void CameraService::Client::handleShutter( mHardware->getRawHeap()); mSurface->registerBuffers(buffers); + IPCThreadState::self()->flushCommands(); } + + mLock.unlock(); } // preview callback - frame buffer update -void CameraService::Client::handlePreviewData(const sp<IMemory>& mem) -{ +void CameraService::Client::handlePreviewData(const sp<IMemory>& mem) { ssize_t offset; size_t size; sp<IMemoryHeap> heap = mem->getMemory(&offset, &size); -#if DEBUG_HEAP_LEAKS && 0 // debugging - if (gWeakHeap == NULL) { - if (gWeakHeap != heap) { - LOGV("SETTING PREVIEW HEAP"); - heap->trackMe(true, true); - gWeakHeap = heap; - } - } -#endif -#if DEBUG_DUMP_PREVIEW_FRAME_TO_FILE - { - if (debug_frame_cnt++ == DEBUG_DUMP_PREVIEW_FRAME_TO_FILE) { - dump_to_file("/data/preview.yuv", - (uint8_t *)heap->base() + offset, size); - } - } -#endif - - if (!mUseOverlay) - { - Mutex::Autolock surfaceLock(mSurfaceLock); - if (mSurface != NULL) { + if (!mUseOverlay) { + if (mSurface != 0) { mSurface->postBuffer(offset); } } @@ -976,7 +1055,8 @@ void CameraService::Client::handlePreviewData(const sp<IMemory>& mem) // is callback enabled? if (!(flags & FRAME_CALLBACK_FLAG_ENABLE_MASK)) { // If the enable bit is off, the copy-out and one-shot bits are ignored - LOGV("frame callback is diabled"); + LOG2("frame callback is disabled"); + mLock.unlock(); return; } @@ -984,61 +1064,49 @@ void CameraService::Client::handlePreviewData(const sp<IMemory>& mem) sp<ICameraClient> c = mCameraClient; // clear callback flags if no client or one-shot mode - if ((c == NULL) || (mPreviewCallbackFlag & FRAME_CALLBACK_FLAG_ONE_SHOT_MASK)) { - LOGV("Disable preview callback"); + if (c == 0 || (mPreviewCallbackFlag & FRAME_CALLBACK_FLAG_ONE_SHOT_MASK)) { + LOG2("Disable preview callback"); mPreviewCallbackFlag &= ~(FRAME_CALLBACK_FLAG_ONE_SHOT_MASK | - FRAME_CALLBACK_FLAG_COPY_OUT_MASK | - FRAME_CALLBACK_FLAG_ENABLE_MASK); - // TODO: Shouldn't we use this API for non-overlay hardware as well? - if (mUseOverlay) - mHardware->disableMsgType(CAMERA_MSG_PREVIEW_FRAME); + FRAME_CALLBACK_FLAG_COPY_OUT_MASK | + FRAME_CALLBACK_FLAG_ENABLE_MASK); + if (mUseOverlay) { + disableMsgType(CAMERA_MSG_PREVIEW_FRAME); + } } - // Is the received frame copied out or not? - if (flags & FRAME_CALLBACK_FLAG_COPY_OUT_MASK) { - LOGV("frame is copied"); - copyFrameAndPostCopiedFrame(c, heap, offset, size); + if (c != 0) { + // Is the received frame copied out or not? + if (flags & FRAME_CALLBACK_FLAG_COPY_OUT_MASK) { + LOG2("frame is copied"); + copyFrameAndPostCopiedFrame(c, heap, offset, size); + } else { + LOG2("frame is forwarded"); + mLock.unlock(); + c->dataCallback(CAMERA_MSG_PREVIEW_FRAME, mem); + } } else { - LOGV("frame is forwarded"); - c->dataCallback(CAMERA_MSG_PREVIEW_FRAME, mem); + mLock.unlock(); } } // picture callback - postview image ready -void CameraService::Client::handlePostview(const sp<IMemory>& mem) -{ -#if DEBUG_DUMP_POSTVIEW_SNAPSHOT_TO_FILE // for testing pursposes only - { - ssize_t offset; - size_t size; - sp<IMemoryHeap> heap = mem->getMemory(&offset, &size); - dump_to_file("/data/postview.yuv", - (uint8_t *)heap->base() + offset, size); - } -#endif +void CameraService::Client::handlePostview(const sp<IMemory>& mem) { + disableMsgType(CAMERA_MSG_POSTVIEW_FRAME); sp<ICameraClient> c = mCameraClient; - if (c != NULL) { + mLock.unlock(); + if (c != 0) { c->dataCallback(CAMERA_MSG_POSTVIEW_FRAME, mem); } - mHardware->disableMsgType(CAMERA_MSG_POSTVIEW_FRAME); } // picture callback - raw image ready -void CameraService::Client::handleRawPicture(const sp<IMemory>& mem) -{ +void CameraService::Client::handleRawPicture(const sp<IMemory>& mem) { + disableMsgType(CAMERA_MSG_RAW_IMAGE); + ssize_t offset; size_t size; sp<IMemoryHeap> heap = mem->getMemory(&offset, &size); -#if DEBUG_HEAP_LEAKS && 0 // debugging - gWeakHeap = heap; // debugging -#endif - - //LOGV("handleRawPicture(%d, %d)", offset, size); -#if DEBUG_DUMP_YUV_SNAPSHOT_TO_FILE // for testing pursposes only - dump_to_file("/data/photo.yuv", - (uint8_t *)heap->base() + offset, size); -#endif // Put the YUV version of the snapshot in the preview display. if (mSurface != 0 && !mUseOverlay) { @@ -1046,250 +1114,112 @@ void CameraService::Client::handleRawPicture(const sp<IMemory>& mem) } sp<ICameraClient> c = mCameraClient; - if (c != NULL) { + mLock.unlock(); + if (c != 0) { c->dataCallback(CAMERA_MSG_RAW_IMAGE, mem); } - mHardware->disableMsgType(CAMERA_MSG_RAW_IMAGE); } // picture callback - compressed picture ready -void CameraService::Client::handleCompressedPicture(const sp<IMemory>& mem) -{ -#if DEBUG_DUMP_JPEG_SNAPSHOT_TO_FILE // for testing pursposes only - { - ssize_t offset; - size_t size; - sp<IMemoryHeap> heap = mem->getMemory(&offset, &size); - dump_to_file("/data/photo.jpg", - (uint8_t *)heap->base() + offset, size); - } -#endif +void CameraService::Client::handleCompressedPicture(const sp<IMemory>& mem) { + disableMsgType(CAMERA_MSG_COMPRESSED_IMAGE); sp<ICameraClient> c = mCameraClient; - if (c != NULL) { + mLock.unlock(); + if (c != 0) { c->dataCallback(CAMERA_MSG_COMPRESSED_IMAGE, mem); } - mHardware->disableMsgType(CAMERA_MSG_COMPRESSED_IMAGE); } -void CameraService::Client::notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2, void* user) -{ - LOGV("notifyCallback(%d)", msgType); - - sp<Client> client = getClientFromCookie(user); - if (client == 0) { - return; - } - - switch (msgType) { - case CAMERA_MSG_SHUTTER: - // ext1 is the dimension of the yuv picture. - client->handleShutter((image_rect_type *)ext1); - break; - default: - sp<ICameraClient> c = client->mCameraClient; - if (c != NULL) { - c->notifyCallback(msgType, ext1, ext2); - } - break; - } -#if DEBUG_CLIENT_REFERENCES - if (client->getStrongCount() == 1) { - LOGE("++++++++++++++++ (NOTIFY CALLBACK) THIS WILL CAUSE A LOCKUP!"); - client->printRefs(); +void CameraService::Client::handleGenericNotify(int32_t msgType, + int32_t ext1, int32_t ext2) { + sp<ICameraClient> c = mCameraClient; + mLock.unlock(); + if (c != 0) { + c->notifyCallback(msgType, ext1, ext2); } -#endif } -void CameraService::Client::dataCallback(int32_t msgType, const sp<IMemory>& dataPtr, void* user) -{ - LOGV("dataCallback(%d)", msgType); - - sp<Client> client = getClientFromCookie(user); - if (client == 0) { - return; - } - - sp<ICameraClient> c = client->mCameraClient; - if (dataPtr == NULL) { - LOGE("Null data returned in data callback"); - if (c != NULL) { - c->notifyCallback(CAMERA_MSG_ERROR, UNKNOWN_ERROR, 0); - c->dataCallback(msgType, NULL); - } - return; - } - - switch (msgType) { - case CAMERA_MSG_PREVIEW_FRAME: - client->handlePreviewData(dataPtr); - break; - case CAMERA_MSG_POSTVIEW_FRAME: - client->handlePostview(dataPtr); - break; - case CAMERA_MSG_RAW_IMAGE: - client->handleRawPicture(dataPtr); - break; - case CAMERA_MSG_COMPRESSED_IMAGE: - client->handleCompressedPicture(dataPtr); - break; - default: - if (c != NULL) { - c->dataCallback(msgType, dataPtr); - } - break; - } - -#if DEBUG_CLIENT_REFERENCES - if (client->getStrongCount() == 1) { - LOGE("++++++++++++++++ (DATA CALLBACK) THIS WILL CAUSE A LOCKUP!"); - client->printRefs(); +void CameraService::Client::handleGenericData(int32_t msgType, + const sp<IMemory>& dataPtr) { + sp<ICameraClient> c = mCameraClient; + mLock.unlock(); + if (c != 0) { + c->dataCallback(msgType, dataPtr); } -#endif } -void CameraService::Client::dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, - const sp<IMemory>& dataPtr, void* user) -{ - LOGV("dataCallbackTimestamp(%d)", msgType); - - sp<Client> client = getClientFromCookie(user); - if (client == 0) { - return; - } - sp<ICameraClient> c = client->mCameraClient; - - if (dataPtr == NULL) { - LOGE("Null data returned in data with timestamp callback"); - if (c != NULL) { - c->notifyCallback(CAMERA_MSG_ERROR, UNKNOWN_ERROR, 0); - c->dataCallbackTimestamp(0, msgType, NULL); - } - return; - } - - if (c != NULL) { +void CameraService::Client::handleGenericDataTimestamp(nsecs_t timestamp, + int32_t msgType, const sp<IMemory>& dataPtr) { + sp<ICameraClient> c = mCameraClient; + mLock.unlock(); + if (c != 0) { c->dataCallbackTimestamp(timestamp, msgType, dataPtr); } - -#if DEBUG_CLIENT_REFERENCES - if (client->getStrongCount() == 1) { - LOGE("++++++++++++++++ (DATA CALLBACK TIMESTAMP) THIS WILL CAUSE A LOCKUP!"); - client->printRefs(); - } -#endif } -// set preview/capture parameters - key/value pairs -status_t CameraService::Client::setParameters(const String8& params) -{ - LOGV("setParameters(%s)", params.string()); - - Mutex::Autolock lock(mLock); - status_t result = checkPid(); - if (result != NO_ERROR) return result; - - if (mHardware == 0) { - LOGE("mHardware is NULL, returning."); - return INVALID_OPERATION; - } - - CameraParameters p(params); - - return mHardware->setParameters(p); -} - -// get preview/capture parameters - key/value pairs -String8 CameraService::Client::getParameters() const -{ - Mutex::Autolock lock(mLock); - - if (mHardware == 0) { - LOGE("mHardware is NULL, returning."); - return String8(); - } - - String8 params(mHardware->getParameters().flatten()); - LOGV("getParameters(%s)", params.string()); - return params; -} - -status_t CameraService::Client::sendCommand(int32_t cmd, int32_t arg1, int32_t arg2) -{ - LOGV("sendCommand (pid %d)", getCallingPid()); - Mutex::Autolock lock(mLock); - status_t result = checkPid(); - if (result != NO_ERROR) return result; - - if (cmd == CAMERA_CMD_SET_DISPLAY_ORIENTATION) { - // The orientation cannot be set during preview. - if (mHardware->previewEnabled()) { - return INVALID_OPERATION; - } - switch (arg1) { - case 0: - mOrientation = ISurface::BufferHeap::ROT_0; - break; - case 90: - mOrientation = ISurface::BufferHeap::ROT_90; - break; - case 180: - mOrientation = ISurface::BufferHeap::ROT_180; - break; - case 270: - mOrientation = ISurface::BufferHeap::ROT_270; - break; - default: - return BAD_VALUE; - } - return OK; - } - - if (mHardware == 0) { - LOGE("mHardware is NULL, returning."); - return INVALID_OPERATION; - } - - return mHardware->sendCommand(cmd, arg1, arg2); -} - -void CameraService::Client::copyFrameAndPostCopiedFrame(const sp<ICameraClient>& client, - const sp<IMemoryHeap>& heap, size_t offset, size_t size) -{ - LOGV("copyFrameAndPostCopiedFrame"); +void CameraService::Client::copyFrameAndPostCopiedFrame( + const sp<ICameraClient>& client, const sp<IMemoryHeap>& heap, + size_t offset, size_t size) { + LOG2("copyFrameAndPostCopiedFrame"); // It is necessary to copy out of pmem before sending this to // the callback. For efficiency, reuse the same MemoryHeapBase // provided it's big enough. Don't allocate the memory or // perform the copy if there's no callback. - // hold the preview lock while we grab a reference to the preview buffer sp<MemoryHeapBase> previewBuffer; - { - Mutex::Autolock lock(mPreviewLock); - if (mPreviewBuffer == 0) { - mPreviewBuffer = new MemoryHeapBase(size, 0, NULL); - } else if (size > mPreviewBuffer->virtualSize()) { - mPreviewBuffer.clear(); - mPreviewBuffer = new MemoryHeapBase(size, 0, NULL); - } - if (mPreviewBuffer == 0) { - LOGE("failed to allocate space for preview buffer"); - return; - } - previewBuffer = mPreviewBuffer; + + if (mPreviewBuffer == 0) { + mPreviewBuffer = new MemoryHeapBase(size, 0, NULL); + } else if (size > mPreviewBuffer->virtualSize()) { + mPreviewBuffer.clear(); + mPreviewBuffer = new MemoryHeapBase(size, 0, NULL); } - memcpy(previewBuffer->base(), - (uint8_t *)heap->base() + offset, size); + if (mPreviewBuffer == 0) { + LOGE("failed to allocate space for preview buffer"); + mLock.unlock(); + return; + } + previewBuffer = mPreviewBuffer; + + memcpy(previewBuffer->base(), (uint8_t *)heap->base() + offset, size); sp<MemoryBase> frame = new MemoryBase(previewBuffer, 0, size); if (frame == 0) { LOGE("failed to allocate space for frame callback"); + mLock.unlock(); return; } + + mLock.unlock(); client->dataCallback(CAMERA_MSG_PREVIEW_FRAME, frame); } +int CameraService::Client::getOrientation(int degrees, bool mirror) { + if (!mirror) { + if (degrees == 0) return 0; + else if (degrees == 90) return HAL_TRANSFORM_ROT_90; + else if (degrees == 180) return HAL_TRANSFORM_ROT_180; + else if (degrees == 270) return HAL_TRANSFORM_ROT_270; + } else { // Do mirror (horizontal flip) + if (degrees == 0) { // FLIP_H and ROT_0 + return HAL_TRANSFORM_FLIP_H; + } else if (degrees == 90) { // FLIP_H and ROT_90 + return HAL_TRANSFORM_FLIP_H | HAL_TRANSFORM_ROT_90; + } else if (degrees == 180) { // FLIP_H and ROT_180 + return HAL_TRANSFORM_FLIP_V; + } else if (degrees == 270) { // FLIP_H and ROT_270 + return HAL_TRANSFORM_FLIP_V | HAL_TRANSFORM_ROT_90; + } + } + LOGE("Invalid setDisplayOrientation degrees=%d", degrees); + return -1; +} + + +// ---------------------------------------------------------------------------- + static const int kDumpLockRetries = 50; static const int kDumpLockSleep = 60000; @@ -1306,8 +1236,7 @@ static bool tryLock(Mutex& mutex) return locked; } -status_t CameraService::dump(int fd, const Vector<String16>& args) -{ +status_t CameraService::dump(int fd, const Vector<String16>& args) { static const char* kDeadlockedString = "CameraService may be deadlocked\n"; const size_t SIZE = 256; @@ -1317,7 +1246,7 @@ status_t CameraService::dump(int fd, const Vector<String16>& args) snprintf(buffer, SIZE, "Permission Denial: " "can't dump CameraService from pid=%d, uid=%d\n", getCallingPid(), - IPCThreadState::self()->getCallingUid()); + getCallingUid()); result.append(buffer); write(fd, result.string(), result.size()); } else { @@ -1328,89 +1257,39 @@ status_t CameraService::dump(int fd, const Vector<String16>& args) write(fd, result.string(), result.size()); } - if (mClient != 0) { - sp<Client> currentClient = mClient.promote(); - sprintf(buffer, "Client (%p) PID: %d\n", - currentClient->getCameraClient()->asBinder().get(), - currentClient->mClientPid); + bool hasClient = false; + for (int i = 0; i < mNumberOfCameras; i++) { + sp<Client> client = mClient[i].promote(); + if (client == 0) continue; + hasClient = true; + sprintf(buffer, "Client[%d] (%p) PID: %d\n", + i, + client->getCameraClient()->asBinder().get(), + client->mClientPid); result.append(buffer); write(fd, result.string(), result.size()); - currentClient->mHardware->dump(fd, args); - } else { + client->mHardware->dump(fd, args); + } + if (!hasClient) { result.append("No camera client yet.\n"); write(fd, result.string(), result.size()); } if (locked) mServiceLock.unlock(); - } - return NO_ERROR; -} - -status_t CameraService::onTransact( - uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) -{ - // permission checks... - switch (code) { - case BnCameraService::CONNECT: - IPCThreadState* ipc = IPCThreadState::self(); - const int pid = ipc->getCallingPid(); - const int self_pid = getpid(); - if (pid != self_pid) { - // we're called from a different process, do the real check - if (!checkCallingPermission( - String16("android.permission.CAMERA"))) - { - const int uid = ipc->getCallingUid(); - LOGE("Permission Denial: " - "can't use the camera pid=%d, uid=%d", pid, uid); - return PERMISSION_DENIED; - } - } - break; - } - - status_t err = BnCameraService::onTransact(code, data, reply, flags); - -#if DEBUG_HEAP_LEAKS - LOGV("+++ onTransact err %d code %d", err, code); - - if (err == UNKNOWN_TRANSACTION || err == PERMISSION_DENIED) { - // the 'service' command interrogates this binder for its name, and then supplies it - // even for the debugging commands. that means we need to check for it here, using - // ISurfaceComposer (since we delegated the INTERFACE_TRANSACTION handling to - // BnSurfaceComposer before falling through to this code). - - LOGV("+++ onTransact code %d", code); - - CHECK_INTERFACE(ICameraService, data, reply); - - switch(code) { - case 1000: - { - if (gWeakHeap != 0) { - sp<IMemoryHeap> h = gWeakHeap.promote(); - IMemoryHeap *p = gWeakHeap.unsafe_get(); - LOGV("CHECKING WEAK REFERENCE %p (%p)", h.get(), p); - if (h != 0) - h->printRefs(); - bool attempt_to_delete = data.readInt32() == 1; - if (attempt_to_delete) { - // NOT SAFE! - LOGV("DELETING WEAK REFERENCE %p (%p)", h.get(), p); - if (p) delete p; - } - return NO_ERROR; + // change logging level + int n = args.size(); + for (int i = 0; i + 1 < n; i++) { + if (args[i] == String16("-v")) { + String8 levelStr(args[i+1]); + int level = atoi(levelStr.string()); + sprintf(buffer, "Set Log Level to %d", level); + result.append(buffer); + setLogLevel(level); } } - break; - default: - break; - } } -#endif // DEBUG_HEAP_LEAKS - - return err; + return NO_ERROR; } }; // namespace android diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h index 75e96c6..f09773d 100644 --- a/services/camera/libcameraservice/CameraService.h +++ b/services/camera/libcameraservice/CameraService.h @@ -18,209 +18,188 @@ #ifndef ANDROID_SERVERS_CAMERA_CAMERASERVICE_H #define ANDROID_SERVERS_CAMERA_CAMERASERVICE_H +#include <binder/BinderService.h> + #include <camera/ICameraService.h> #include <camera/CameraHardwareInterface.h> -#include <camera/Camera.h> + +/* This needs to be increased if we can have more cameras */ +#define MAX_CAMERAS 2 namespace android { class MemoryHeapBase; class MediaPlayer; -// ---------------------------------------------------------------------------- - -#define LIKELY( exp ) (__builtin_expect( (exp) != 0, true )) -#define UNLIKELY( exp ) (__builtin_expect( (exp) != 0, false )) - -// When enabled, this feature allows you to send an event to the CameraService -// so that you can cause all references to the heap object gWeakHeap, defined -// below, to be printed. You will also need to set DEBUG_REFS=1 and -// DEBUG_REFS_ENABLED_BY_DEFAULT=0 in libutils/RefBase.cpp. You just have to -// set gWeakHeap to the appropriate heap you want to track. - -#define DEBUG_HEAP_LEAKS 0 - -// ---------------------------------------------------------------------------- - -class CameraService : public BnCameraService +class CameraService : + public BinderService<CameraService>, + public BnCameraService { class Client; - + friend class BinderService<CameraService>; public: - static void instantiate(); - - // ICameraService interface - virtual sp<ICamera> connect(const sp<ICameraClient>& cameraClient); - - virtual status_t dump(int fd, const Vector<String16>& args); - - void removeClient(const sp<ICameraClient>& cameraClient); + static char const* getServiceName() { return "media.camera"; } + + CameraService(); + virtual ~CameraService(); + + virtual int32_t getNumberOfCameras(); + virtual status_t getCameraInfo(int cameraId, + struct CameraInfo* cameraInfo); + virtual sp<ICamera> connect(const sp<ICameraClient>& cameraClient, int cameraId); + virtual void removeClient(const sp<ICameraClient>& cameraClient); + virtual sp<Client> getClientById(int cameraId); + + virtual status_t dump(int fd, const Vector<String16>& args); + virtual status_t onTransact(uint32_t code, const Parcel& data, + Parcel* reply, uint32_t flags); + + enum sound_kind { + SOUND_SHUTTER = 0, + SOUND_RECORDING = 1, + NUM_SOUNDS + }; - virtual status_t onTransact( - uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags); + void loadSound(); + void playSound(sound_kind kind); + void releaseSound(); private: - -// ---------------------------------------------------------------------------- - - class Client : public BnCamera { - + Mutex mServiceLock; + wp<Client> mClient[MAX_CAMERAS]; // protected by mServiceLock + int mNumberOfCameras; + + // atomics to record whether the hardware is allocated to some client. + volatile int32_t mBusy[MAX_CAMERAS]; + void setCameraBusy(int cameraId); + void setCameraFree(int cameraId); + + // sounds + Mutex mSoundLock; + sp<MediaPlayer> mSoundPlayer[NUM_SOUNDS]; + int mSoundRef; // reference count (release all MediaPlayer when 0) + + class Client : public BnCamera + { public: + // ICamera interface (see ICamera for details) virtual void disconnect(); - - // connect new client with existing camera remote virtual status_t connect(const sp<ICameraClient>& client); - - // prevent other processes from using this ICamera interface virtual status_t lock(); - - // allow other processes to use this ICamera interface virtual status_t unlock(); - - // pass the buffered ISurface to the camera service virtual status_t setPreviewDisplay(const sp<ISurface>& surface); - - // set the preview callback flag to affect how the received frames from - // preview are handled. - virtual void setPreviewCallbackFlag(int callback_flag); - - // start preview mode, must call setPreviewDisplay first + virtual void setPreviewCallbackFlag(int flag); virtual status_t startPreview(); - - // stop preview mode virtual void stopPreview(); - - // get preview state virtual bool previewEnabled(); - - // start recording mode virtual status_t startRecording(); - - // stop recording mode virtual void stopRecording(); - - // get recording state virtual bool recordingEnabled(); - - // release a recording frame virtual void releaseRecordingFrame(const sp<IMemory>& mem); - - // auto focus virtual status_t autoFocus(); - - // cancel auto focus virtual status_t cancelAutoFocus(); - - // take a picture - returns an IMemory (ref-counted mmap) virtual status_t takePicture(); - - // set preview/capture parameters - key/value pairs virtual status_t setParameters(const String8& params); - - // get preview/capture parameters - key/value pairs virtual String8 getParameters() const; - - // send command to camera driver virtual status_t sendCommand(int32_t cmd, int32_t arg1, int32_t arg2); - - // our client... - const sp<ICameraClient>& getCameraClient() const { return mCameraClient; } - private: friend class CameraService; Client(const sp<CameraService>& cameraService, - const sp<ICameraClient>& cameraClient, - pid_t clientPid); - Client(); - virtual ~Client(); - - status_t checkPid(); - - 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); - static void dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, - const sp<IMemory>& dataPtr, void* user); + const sp<ICameraClient>& cameraClient, + const sp<CameraHardwareInterface>& hardware, + int cameraId, + int cameraFacing, + int clientPid); + ~Client(); - static sp<Client> getClientFromCookie(void* user); + // return our camera client + const sp<ICameraClient>& getCameraClient() { return mCameraClient; } - void handlePreviewData(const sp<IMemory>&); - void handleShutter(image_rect_type *image); - void handlePostview(const sp<IMemory>&); - void handleRawPicture(const sp<IMemory>&); - void handleCompressedPicture(const sp<IMemory>&); + // check whether the calling process matches mClientPid. + status_t checkPid() const; + status_t checkPidAndHardware() const; // also check mHardware != 0 - void copyFrameAndPostCopiedFrame(const sp<ICameraClient>& client, - const sp<IMemoryHeap>& heap, size_t offset, size_t size); + // these are internal functions used to set up preview buffers + status_t registerPreviewBuffers(); + status_t setOverlay(); // camera operation mode enum camera_mode { CAMERA_PREVIEW_MODE = 0, // frame automatically released CAMERA_RECORDING_MODE = 1, // frame has to be explicitly released by releaseRecordingFrame() }; + // these are internal functions used for preview/recording status_t startCameraMode(camera_mode mode); status_t startPreviewMode(); status_t startRecordingMode(); - status_t setOverlay(); - status_t registerPreviewBuffers(); + + // 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); + static void dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr, void* user); + // convert client from cookie + static sp<Client> getClientFromCookie(void* user); + // handlers for messages + void handleShutter(image_rect_type *size); + void handlePreviewData(const sp<IMemory>& mem); + void handlePostview(const sp<IMemory>& mem); + void handleRawPicture(const sp<IMemory>& mem); + void handleCompressedPicture(const sp<IMemory>& mem); + void handleGenericNotify(int32_t msgType, int32_t ext1, int32_t ext2); + void handleGenericData(int32_t msgType, const sp<IMemory>& dataPtr); + void handleGenericDataTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr); + + void copyFrameAndPostCopiedFrame( + const sp<ICameraClient>& client, + const sp<IMemoryHeap>& heap, + size_t offset, size_t size); + + int getOrientation(int orientation, bool mirror); + + // these are initialized in the constructor. + sp<CameraService> mCameraService; // immutable after constructor + sp<ICameraClient> mCameraClient; + int mCameraId; // immutable after constructor + 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; // Ensures atomicity among the public methods - mutable Mutex mLock; - - // mSurfaceLock synchronizes access to mSurface between - // setPreviewSurface() and postPreviewFrame(). Note that among - // the public methods, all accesses to mSurface are - // syncrhonized by mLock. However, postPreviewFrame() is called - // by the CameraHardwareInterface callback, and needs to - // access mSurface. It cannot hold mLock, however, because - // stopPreview() may be holding that lock while attempting - // to stop preview, and stopPreview itself will block waiting - // for a callback from CameraHardwareInterface. If this - // happens, it will cause a deadlock. - mutable Mutex mSurfaceLock; - mutable Condition mReady; - sp<CameraService> mCameraService; - sp<ISurface> mSurface; - int mPreviewCallbackFlag; - int mOrientation; - - sp<MediaPlayer> mMediaPlayerClick; - sp<MediaPlayer> mMediaPlayerBeep; - - // these are immutable once the object is created, - // they don't need to be protected by a lock - sp<ICameraClient> mCameraClient; - sp<CameraHardwareInterface> mHardware; - pid_t mClientPid; - bool mUseOverlay; - - sp<OverlayRef> mOverlayRef; - int mOverlayW; - int mOverlayH; - - mutable Mutex mPreviewLock; - sp<MemoryHeapBase> mPreviewBuffer; + mutable Mutex mLock; + sp<ISurface> mSurface; + + // 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. + sp<MemoryHeapBase> mPreviewBuffer; + + // We need to avoid the deadlock when the incoming command thread and + // the CameraHardwareInterface callback thread both want to grab mLock. + // An extra flag is used to tell the callback thread that it should stop + // trying to deliver the callback messages if the client is not + // interested in it anymore. For example, if the client is calling + // stopPreview(), the preview frame messages do not need to be delivered + // anymore. + + // This function takes the same parameter as the enableMsgType() and + // disableMsgType() functions in CameraHardwareInterface. + void enableMsgType(int32_t msgType); + void disableMsgType(int32_t msgType); + volatile int32_t mMsgEnabled; + + // This function keeps trying to grab mLock, or give up if the message + // is found to be disabled. It returns true if mLock is grabbed. + bool lockIfMessageWanted(int32_t msgType); }; - -// ---------------------------------------------------------------------------- - - CameraService(); - virtual ~CameraService(); - - // We use a count for number of clients (shoule only be 0 or 1). - volatile int32_t mUsers; - virtual void incUsers(); - virtual void decUsers(); - - mutable Mutex mServiceLock; - wp<Client> mClient; - -#if DEBUG_HEAP_LEAKS - wp<IMemoryHeap> gWeakHeap; -#endif }; -// ---------------------------------------------------------------------------- - -}; // namespace android +} // namespace android #endif diff --git a/services/camera/libcameraservice/FakeCamera.cpp b/services/camera/libcameraservice/FakeCamera.cpp index 6749899..f3a6a67 100644 --- a/services/camera/libcameraservice/FakeCamera.cpp +++ b/services/camera/libcameraservice/FakeCamera.cpp @@ -198,10 +198,11 @@ static const int SHIFT2 = 16; static const int DELTA = kYb*(1 << SHIFT2); static const int GAMMA = kYr*(1 << SHIFT2); -int32_t ccrgb16toyuv_wo_colorkey(uint8_t *rgb16,uint8_t *yuv422,uint32_t *param,uint8_t *table[]) +int32_t ccrgb16toyuv_wo_colorkey(uint8_t *rgb16, uint8_t *yuv420, + uint32_t *param, uint8_t *table[]) { uint16_t *inputRGB = (uint16_t*)rgb16; - uint8_t *outYUV = yuv422; + uint8_t *outYUV = yuv420; int32_t width_dst = param[0]; int32_t height_dst = param[1]; int32_t pitch_dst = param[2]; @@ -260,12 +261,14 @@ uint32_t temp; tempY[0] = y0; tempY[1] = y1; - tempU[0] = u; - tempV[0] = v; - tempY += 2; - tempU += 2; - tempV += 2; + + if ((j&1) == 0) { + tempU[0] = u; + tempV[0] = v; + tempU += 2; + tempV += 2; + } } inputRGB += pitch_src; @@ -277,7 +280,7 @@ uint32_t temp; #define min(a,b) ((a)<(b)?(a):(b)) #define max(a,b) ((a)>(b)?(a):(b)) -static void convert_rgb16_to_yuv422(uint8_t *rgb, uint8_t *yuv, int width, int height) +static void convert_rgb16_to_yuv420(uint8_t *rgb, uint8_t *yuv, int width, int height) { if (!tables_initialized) { initYtab(); @@ -326,7 +329,7 @@ void FakeCamera::setSize(int width, int height) mCheckY = 0; // This will cause it to be reallocated on the next call - // to getNextFrameAsYuv422(). + // to getNextFrameAsYuv420(). delete[] mTmpRgb16Buffer; mTmpRgb16Buffer = 0; } @@ -347,13 +350,13 @@ void FakeCamera::getNextFrameAsRgb565(uint16_t *buffer) mCounter++; } -void FakeCamera::getNextFrameAsYuv422(uint8_t *buffer) +void FakeCamera::getNextFrameAsYuv420(uint8_t *buffer) { if (mTmpRgb16Buffer == 0) mTmpRgb16Buffer = new uint16_t[mWidth * mHeight]; getNextFrameAsRgb565(mTmpRgb16Buffer); - convert_rgb16_to_yuv422((uint8_t*)mTmpRgb16Buffer, buffer, mWidth, mHeight); + convert_rgb16_to_yuv420((uint8_t*)mTmpRgb16Buffer, buffer, mWidth, mHeight); } void FakeCamera::drawSquare(uint16_t *dst, int x, int y, int size, int color, int shadow) diff --git a/services/camera/libcameraservice/FakeCamera.h b/services/camera/libcameraservice/FakeCamera.h index f7f8803..724de20 100644 --- a/services/camera/libcameraservice/FakeCamera.h +++ b/services/camera/libcameraservice/FakeCamera.h @@ -40,7 +40,7 @@ public: ~FakeCamera(); void setSize(int width, int height); - void getNextFrameAsYuv422(uint8_t *buffer); + void getNextFrameAsYuv420(uint8_t *buffer); // Write to the fd a string representing the current state. void dump(int fd) const; diff --git a/services/camera/tests/CameraServiceTest/Android.mk b/services/camera/tests/CameraServiceTest/Android.mk index 9bb190a..cf4e42f 100644 --- a/services/camera/tests/CameraServiceTest/Android.mk +++ b/services/camera/tests/CameraServiceTest/Android.mk @@ -21,4 +21,6 @@ LOCAL_SHARED_LIBRARIES += \ libcamera_client \ libsurfaceflinger_client -include $(BUILD_EXECUTABLE) +# Disable it because the ISurface interface may change, and before we have a +# chance to fix this test, we don't want to break normal builds. +#include $(BUILD_EXECUTABLE) diff --git a/services/camera/tests/CameraServiceTest/CameraServiceTest.cpp b/services/camera/tests/CameraServiceTest/CameraServiceTest.cpp index 9fc795b..3c8d553 100644 --- a/services/camera/tests/CameraServiceTest/CameraServiceTest.cpp +++ b/services/camera/tests/CameraServiceTest/CameraServiceTest.cpp @@ -38,7 +38,7 @@ void assert_fail(const char *file, int line, const char *func, const char *expr) INFO("assertion failed at file %s, line %d, function %s:", file, line, func); INFO("%s", expr); - exit(1); + abort(); } void assert_eq_fail(const char *file, int line, const char *func, @@ -46,7 +46,7 @@ void assert_eq_fail(const char *file, int line, const char *func, INFO("assertion failed at file %s, line %d, function %s:", file, line, func); INFO("(expected) %s != (actual) %d", expr, actual); - exit(1); + abort(); } #define ASSERT(e) \ @@ -155,7 +155,7 @@ public: virtual void notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2); virtual void dataCallback(int32_t msgType, const sp<IMemory>& data); virtual void dataCallbackTimestamp(nsecs_t timestamp, - int32_t msgType, const sp<IMemory>& data) {} + int32_t msgType, const sp<IMemory>& data); // new functions void clearStat(); @@ -176,6 +176,7 @@ private: DefaultKeyedVector<int32_t, int> mDataCount; DefaultKeyedVector<int32_t, int> mDataSize; bool test(OP op, int v1, int v2); + void assertTest(OP op, int v1, int v2); ICamera *mReleaser; }; @@ -199,26 +200,33 @@ bool MCameraClient::test(OP op, int v1, int v2) { return false; } +void MCameraClient::assertTest(OP op, int v1, int v2) { + if (!test(op, v1, v2)) { + LOGE("assertTest failed: op=%d, v1=%d, v2=%d", op, v1, v2); + ASSERT(0); + } +} + void MCameraClient::assertNotify(int32_t msgType, OP op, int count) { Mutex::Autolock _l(mLock); int v = mNotifyCount.valueFor(msgType); - ASSERT(test(op, v, count)); + assertTest(op, v, count); } void MCameraClient::assertData(int32_t msgType, OP op, int count) { Mutex::Autolock _l(mLock); int v = mDataCount.valueFor(msgType); - ASSERT(test(op, v, count)); + assertTest(op, v, count); } void MCameraClient::assertDataSize(int32_t msgType, OP op, int dataSize) { Mutex::Autolock _l(mLock); int v = mDataSize.valueFor(msgType); - ASSERT(test(op, v, dataSize)); + assertTest(op, v, dataSize); } void MCameraClient::notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2) { - INFO(__func__); + INFO("%s", __func__); Mutex::Autolock _l(mLock); ssize_t i = mNotifyCount.indexOfKey(msgType); if (i < 0) { @@ -230,7 +238,7 @@ void MCameraClient::notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2) } void MCameraClient::dataCallback(int32_t msgType, const sp<IMemory>& data) { - INFO(__func__); + INFO("%s", __func__); int dataSize = data->size(); INFO("data type = %d, size = %d", msgType, dataSize); Mutex::Autolock _l(mLock); @@ -250,6 +258,11 @@ void MCameraClient::dataCallback(int32_t msgType, const sp<IMemory>& data) { } } +void MCameraClient::dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, + const sp<IMemory>& data) { + dataCallback(msgType, data); +} + void MCameraClient::waitNotify(int32_t msgType, OP op, int count) { INFO("waitNotify: %d, %d, %d", msgType, op, count); Mutex::Autolock _l(mLock); @@ -285,6 +298,7 @@ public: virtual sp<OverlayRef> createOverlay( uint32_t w, uint32_t h, int32_t format, int32_t orientation); virtual sp<GraphicBuffer> requestBuffer(int bufferIdx, int usage); + virtual status_t setBufferCount(int bufferCount); // new functions void clearStat(); @@ -300,7 +314,7 @@ private: }; status_t MSurface::registerBuffers(const BufferHeap& buffers) { - INFO(__func__); + INFO("%s", __func__); Mutex::Autolock _l(mLock); ++registerBuffersCount; mCond.signal(); @@ -308,21 +322,26 @@ status_t MSurface::registerBuffers(const BufferHeap& buffers) { } void MSurface::postBuffer(ssize_t offset) { - // INFO(__func__); + // INFO("%s", __func__); Mutex::Autolock _l(mLock); ++postBufferCount; mCond.signal(); } void MSurface::unregisterBuffers() { - INFO(__func__); + INFO("%s", __func__); Mutex::Autolock _l(mLock); ++unregisterBuffersCount; mCond.signal(); } sp<GraphicBuffer> MSurface::requestBuffer(int bufferIdx, int usage) { - INFO(__func__); + INFO("%s", __func__); + return NULL; +} + +status_t MSurface::setBufferCount(int bufferCount) { + INFO("%s", __func__); return NULL; } @@ -348,10 +367,9 @@ void MSurface::waitUntil(int c0, int c1, int c2) { sp<OverlayRef> MSurface::createOverlay(uint32_t w, uint32_t h, int32_t format, int32_t orientation) { - // We don't expect this to be called in current hardware. + // Not implemented. ASSERT(0); - sp<OverlayRef> dummy; - return dummy; + return NULL; } // @@ -368,17 +386,17 @@ sp<IHolder> getHolder() { } void putTempObject(sp<IBinder> obj) { - INFO(__func__); + INFO("%s", __func__); getHolder()->put(obj); } sp<IBinder> getTempObject() { - INFO(__func__); + INFO("%s", __func__); return getHolder()->get(); } void clearTempObject() { - INFO(__func__); + INFO("%s", __func__); getHolder()->clear(); } @@ -395,64 +413,71 @@ sp<ICameraService> getCameraService() { return cs; } +int getNumberOfCameras() { + sp<ICameraService> cs = getCameraService(); + return cs->getNumberOfCameras(); +} + // // Various Connect Tests // -void testConnect() { - INFO(__func__); +void testConnect(int cameraId) { + INFO("%s", __func__); sp<ICameraService> cs = getCameraService(); sp<MCameraClient> cc = new MCameraClient(); - sp<ICamera> c = cs->connect(cc); + sp<ICamera> c = cs->connect(cc, cameraId); ASSERT(c != 0); c->disconnect(); } -void testAllowConnectOnceOnly() { - INFO(__func__); +void testAllowConnectOnceOnly(int cameraId) { + INFO("%s", __func__); sp<ICameraService> cs = getCameraService(); // Connect the first client. sp<MCameraClient> cc = new MCameraClient(); - sp<ICamera> c = cs->connect(cc); + sp<ICamera> c = cs->connect(cc, cameraId); ASSERT(c != 0); // Same client -- ok. - ASSERT(cs->connect(cc) != 0); + ASSERT(cs->connect(cc, cameraId) != 0); // Different client -- not ok. sp<MCameraClient> cc2 = new MCameraClient(); - ASSERT(cs->connect(cc2) == 0); + ASSERT(cs->connect(cc2, cameraId) == 0); c->disconnect(); } void testReconnectFailed() { - INFO(__func__); + INFO("%s", __func__); sp<ICamera> c = interface_cast<ICamera>(getTempObject()); - sp<MCameraClient> cc2 = new MCameraClient(); - ASSERT(c->connect(cc2) != NO_ERROR); + sp<MCameraClient> cc = new MCameraClient(); + ASSERT(c->connect(cc) != NO_ERROR); } void testReconnectSuccess() { - INFO(__func__); + INFO("%s", __func__); sp<ICamera> c = interface_cast<ICamera>(getTempObject()); sp<MCameraClient> cc = new MCameraClient(); ASSERT(c->connect(cc) == NO_ERROR); + c->disconnect(); } void testLockFailed() { - INFO(__func__); + INFO("%s", __func__); sp<ICamera> c = interface_cast<ICamera>(getTempObject()); ASSERT(c->lock() != NO_ERROR); } void testLockUnlockSuccess() { - INFO(__func__); + INFO("%s", __func__); sp<ICamera> c = interface_cast<ICamera>(getTempObject()); ASSERT(c->lock() == NO_ERROR); ASSERT(c->unlock() == NO_ERROR); } void testLockSuccess() { - INFO(__func__); + INFO("%s", __func__); sp<ICamera> c = interface_cast<ICamera>(getTempObject()); ASSERT(c->lock() == NO_ERROR); + c->disconnect(); } // @@ -499,11 +524,11 @@ void runInAnotherProcess(const char *tag) { } } -void testReconnect() { - INFO(__func__); +void testReconnect(int cameraId) { + INFO("%s", __func__); sp<ICameraService> cs = getCameraService(); sp<MCameraClient> cc = new MCameraClient(); - sp<ICamera> c = cs->connect(cc); + sp<ICamera> c = cs->connect(cc, cameraId); ASSERT(c != 0); // Reconnect to the same client -- ok. ASSERT(c->connect(cc) == NO_ERROR); @@ -514,10 +539,10 @@ void testReconnect() { cc->assertNotify(CAMERA_MSG_ERROR, MCameraClient::EQ, 0); } -void testLockUnlock() { +void testLockUnlock(int cameraId) { sp<ICameraService> cs = getCameraService(); sp<MCameraClient> cc = new MCameraClient(); - sp<ICamera> c = cs->connect(cc); + sp<ICamera> c = cs->connect(cc, cameraId); ASSERT(c != 0); // We can lock as many times as we want. ASSERT(c->lock() == NO_ERROR); @@ -530,16 +555,15 @@ void testLockUnlock() { runInAnotherProcess("testLockUnlockSuccess"); // Unlock then lock from a different process -- ok. runInAnotherProcess("testLockSuccess"); - c->disconnect(); clearTempObject(); } -void testReconnectFromAnotherProcess() { - INFO(__func__); +void testReconnectFromAnotherProcess(int cameraId) { + INFO("%s", __func__); sp<ICameraService> cs = getCameraService(); sp<MCameraClient> cc = new MCameraClient(); - sp<ICamera> c = cs->connect(cc); + sp<ICamera> c = cs->connect(cc, cameraId); ASSERT(c != 0); // Reconnect from a different process -- not ok. putTempObject(c->asBinder()); @@ -547,7 +571,6 @@ void testReconnectFromAnotherProcess() { // Unlock then reconnect from a different process -- ok. ASSERT(c->unlock() == NO_ERROR); runInAnotherProcess("testReconnectSuccess"); - c->disconnect(); clearTempObject(); } @@ -560,10 +583,11 @@ static void flushCommands() { } // Run a test case -#define RUN(class_name) do { \ +#define RUN(class_name, cameraId) do { \ { \ INFO(#class_name); \ class_name instance; \ + instance.init(cameraId); \ instance.run(); \ } \ flushCommands(); \ @@ -571,19 +595,21 @@ static void flushCommands() { // Base test case after the the camera is connected. class AfterConnect { -protected: - sp<ICameraService> cs; - sp<MCameraClient> cc; - sp<ICamera> c; - - AfterConnect() { +public: + void init(int cameraId) { cs = getCameraService(); cc = new MCameraClient(); - c = cs->connect(cc); + c = cs->connect(cc, cameraId); ASSERT(c != 0); } +protected: + sp<ICameraService> cs; + sp<MCameraClient> cc; + sp<ICamera> c; + ~AfterConnect() { + c->disconnect(); c.clear(); cc.clear(); cs.clear(); @@ -612,19 +638,16 @@ public: surface->waitUntil(1, 10, 0); // needs 1 registerBuffers and 10 postBuffer surface->clearStat(); - c->disconnect(); - // TODO: CameraService crashes for this. Fix it. -#if 0 sp<MSurface> another_surface = new MSurface(); c->setPreviewDisplay(another_surface); // just to make sure unregisterBuffers // is called. surface->waitUntil(0, 0, 1); // needs unregisterBuffers -#endif + cc->assertNotify(CAMERA_MSG_ERROR, MCameraClient::EQ, 0); } }; -class TestStartPreviewWithoutDisplay : AfterConnect { +class TestStartPreviewWithoutDisplay : public AfterConnect { public: void run() { ASSERT(c->startPreview() == NO_ERROR); @@ -636,15 +659,17 @@ public: // Base test case after the the camera is connected and the preview is started. class AfterStartPreview : public AfterConnect { -protected: - sp<MSurface> surface; - - AfterStartPreview() { +public: + void init(int cameraId) { + AfterConnect::init(cameraId); surface = new MSurface(); ASSERT(c->setPreviewDisplay(surface) == NO_ERROR); ASSERT(c->startPreview() == NO_ERROR); } +protected: + sp<MSurface> surface; + ~AfterStartPreview() { surface.clear(); } @@ -680,9 +705,6 @@ public: cc->waitData(CAMERA_MSG_RAW_IMAGE, MCameraClient::EQ, 1); cc->waitData(CAMERA_MSG_COMPRESSED_IMAGE, MCameraClient::EQ, 1); c->stopPreview(); -#if 1 // TODO: It crashes if we don't have this. Fix it. - usleep(100000); -#endif c->disconnect(); cc->assertNotify(CAMERA_MSG_ERROR, MCameraClient::EQ, 0); } @@ -697,7 +719,6 @@ public: cc->waitNotify(CAMERA_MSG_SHUTTER, MCameraClient::EQ, 1); cc->waitData(CAMERA_MSG_RAW_IMAGE, MCameraClient::EQ, 1); cc->waitData(CAMERA_MSG_COMPRESSED_IMAGE, MCameraClient::EQ, 1); - usleep(100000); // 100ms } c->disconnect(); cc->assertNotify(CAMERA_MSG_ERROR, MCameraClient::EQ, 0); @@ -708,36 +729,71 @@ class TestGetParameters: public AfterStartPreview { public: void run() { String8 param_str = c->getParameters(); - INFO(param_str); + INFO("%s", static_cast<const char*>(param_str)); } }; +static bool getNextSize(const char **ptrS, int *w, int *h) { + const char *s = *ptrS; + + // skip over ',' + if (*s == ',') s++; + + // remember start position in p + const char *p = s; + while (*s != '\0' && *s != 'x') { + s++; + } + if (*s == '\0') return false; + + // get the width + *w = atoi(p); + + // skip over 'x' + ASSERT(*s == 'x'); + p = s + 1; + while (*s != '\0' && *s != ',') { + s++; + } + + // get the height + *h = atoi(p); + *ptrS = s; + return true; +} + class TestPictureSize : public AfterStartPreview { public: void checkOnePicture(int w, int h) { - const float rate = 0.5; // byte per pixel limit + const float rate = 0.9; // byte per pixel limit int pixels = w * h; CameraParameters param(c->getParameters()); param.setPictureSize(w, h); + // disable thumbnail to get more accurate size. + param.set(CameraParameters::KEY_JPEG_THUMBNAIL_WIDTH, 0); + param.set(CameraParameters::KEY_JPEG_THUMBNAIL_HEIGHT, 0); c->setParameters(param.flatten()); cc->clearStat(); ASSERT(c->takePicture() == NO_ERROR); cc->waitData(CAMERA_MSG_RAW_IMAGE, MCameraClient::EQ, 1); - cc->assertDataSize(CAMERA_MSG_RAW_IMAGE, MCameraClient::EQ, pixels*3/2); + //cc->assertDataSize(CAMERA_MSG_RAW_IMAGE, MCameraClient::EQ, pixels*3/2); cc->waitData(CAMERA_MSG_COMPRESSED_IMAGE, MCameraClient::EQ, 1); cc->assertDataSize(CAMERA_MSG_COMPRESSED_IMAGE, MCameraClient::LT, int(pixels * rate)); cc->assertDataSize(CAMERA_MSG_COMPRESSED_IMAGE, MCameraClient::GT, 0); cc->assertNotify(CAMERA_MSG_ERROR, MCameraClient::EQ, 0); - usleep(100000); // 100ms } void run() { - checkOnePicture(2048, 1536); - checkOnePicture(1600, 1200); - checkOnePicture(1024, 768); + CameraParameters param(c->getParameters()); + int w, h; + const char *s = param.get(CameraParameters::KEY_SUPPORTED_PICTURE_SIZES); + while (getNextSize(&s, &w, &h)) { + LOGD("checking picture size %dx%d", w, h); + checkOnePicture(w, h); + } } }; @@ -749,6 +805,8 @@ public: // Try all flag combinations. for (int v = 0; v < 8; v++) { + LOGD("TestPreviewCallbackFlag: flag=%d", v); + usleep(100000); // sleep a while to clear the in-flight callbacks. cc->clearStat(); c->setPreviewCallbackFlag(v); ASSERT(c->previewEnabled() == false); @@ -781,6 +839,7 @@ public: ASSERT(c->recordingEnabled() == true); sleep(2); c->stopRecording(); + usleep(100000); // sleep a while to clear the in-flight callbacks. cc->setReleaser(NULL); cc->assertData(CAMERA_MSG_VIDEO_FRAME, MCameraClient::GE, 10); } @@ -806,9 +865,13 @@ public: } void run() { - checkOnePicture(480, 320); - checkOnePicture(352, 288); - checkOnePicture(176, 144); + CameraParameters param(c->getParameters()); + int w, h; + const char *s = param.get(CameraParameters::KEY_SUPPORTED_PREVIEW_SIZES); + while (getNextSize(&s, &w, &h)) { + LOGD("checking preview size %dx%d", w, h); + checkOnePicture(w, h); + } } }; @@ -827,23 +890,30 @@ int main(int argc, char **argv) INFO("CameraServiceTest start"); gExecutable = argv[0]; runHolderService(); - - testConnect(); flushCommands(); - testAllowConnectOnceOnly(); flushCommands(); - testReconnect(); flushCommands(); - testLockUnlock(); flushCommands(); - testReconnectFromAnotherProcess(); flushCommands(); - - RUN(TestSetPreviewDisplay); - RUN(TestStartPreview); - RUN(TestStartPreviewWithoutDisplay); - RUN(TestAutoFocus); - RUN(TestStopPreview); - RUN(TestTakePicture); - RUN(TestTakeMultiplePictures); - RUN(TestGetParameters); - RUN(TestPictureSize); - RUN(TestPreviewCallbackFlag); - RUN(TestRecording); - RUN(TestPreviewSize); + int n = getNumberOfCameras(); + INFO("%d Cameras available", n); + + for (int id = 0; id < n; id++) { + INFO("Testing camera %d", id); + testConnect(id); flushCommands(); + testAllowConnectOnceOnly(id); flushCommands(); + testReconnect(id); flushCommands(); + testLockUnlock(id); flushCommands(); + testReconnectFromAnotherProcess(id); flushCommands(); + + RUN(TestSetPreviewDisplay, id); + RUN(TestStartPreview, id); + RUN(TestStartPreviewWithoutDisplay, id); + RUN(TestAutoFocus, id); + RUN(TestStopPreview, id); + RUN(TestTakePicture, id); + RUN(TestTakeMultiplePictures, id); + RUN(TestGetParameters, id); + RUN(TestPictureSize, id); + RUN(TestPreviewCallbackFlag, id); + RUN(TestRecording, id); + RUN(TestPreviewSize, id); + } + + INFO("CameraServiceTest finished"); } diff --git a/services/java/Android.mk b/services/java/Android.mk index 934712c..c756d29 100644 --- a/services/java/Android.mk +++ b/services/java/Android.mk @@ -13,7 +13,9 @@ LOCAL_MODULE:= services LOCAL_JAVA_LIBRARIES := android.policy +LOCAL_NO_EMMA_INSTRUMENT := true +LOCAL_NO_EMMA_COMPILE := true + include $(BUILD_JAVA_LIBRARY) include $(BUILD_DROIDDOC) - diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java index 8852cc3..4931cc7 100644 --- a/services/java/com/android/server/AlarmManagerService.java +++ b/services/java/com/android/server/AlarmManagerService.java @@ -38,9 +38,11 @@ import android.text.TextUtils; import android.text.format.Time; import android.util.EventLog; import android.util.Slog; +import android.util.TimeUtils; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; @@ -464,25 +466,28 @@ class AlarmManagerService extends IAlarmManager.Stub { synchronized (mLock) { pw.println("Current Alarm Manager state:"); if (mRtcWakeupAlarms.size() > 0 || mRtcAlarms.size() > 0) { + final long now = System.currentTimeMillis(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); pw.println(" "); pw.print(" Realtime wakeup (now="); - pw.print(System.currentTimeMillis()); pw.println("):"); + pw.print(sdf.format(new Date(now))); pw.println("):"); if (mRtcWakeupAlarms.size() > 0) { - dumpAlarmList(pw, mRtcWakeupAlarms, " ", "RTC_WAKEUP"); + dumpAlarmList(pw, mRtcWakeupAlarms, " ", "RTC_WAKEUP", now); } if (mRtcAlarms.size() > 0) { - dumpAlarmList(pw, mRtcAlarms, " ", "RTC"); + dumpAlarmList(pw, mRtcAlarms, " ", "RTC", now); } } if (mElapsedRealtimeWakeupAlarms.size() > 0 || mElapsedRealtimeAlarms.size() > 0) { + final long now = SystemClock.elapsedRealtime(); pw.println(" "); pw.print(" Elapsed realtime wakeup (now="); - pw.print(SystemClock.elapsedRealtime()); pw.println("):"); + TimeUtils.formatDuration(now, pw); pw.println("):"); if (mElapsedRealtimeWakeupAlarms.size() > 0) { - dumpAlarmList(pw, mElapsedRealtimeWakeupAlarms, " ", "ELAPSED_WAKEUP"); + dumpAlarmList(pw, mElapsedRealtimeWakeupAlarms, " ", "ELAPSED_WAKEUP", now); } if (mElapsedRealtimeAlarms.size() > 0) { - dumpAlarmList(pw, mElapsedRealtimeAlarms, " ", "ELAPSED"); + dumpAlarmList(pw, mElapsedRealtimeAlarms, " ", "ELAPSED", now); } } @@ -507,12 +512,13 @@ class AlarmManagerService extends IAlarmManager.Stub { } } - private static final void dumpAlarmList(PrintWriter pw, ArrayList<Alarm> list, String prefix, String label) { + private static final void dumpAlarmList(PrintWriter pw, ArrayList<Alarm> list, + String prefix, String label, long now) { for (int i=list.size()-1; i>=0; i--) { Alarm a = list.get(i); pw.print(prefix); pw.print(label); pw.print(" #"); pw.print(i); pw.print(": "); pw.println(a); - a.dump(pw, prefix + " "); + a.dump(pw, prefix + " ", now); } } @@ -627,10 +633,9 @@ class AlarmManagerService extends IAlarmManager.Stub { return sb.toString(); } - public void dump(PrintWriter pw, String prefix) - { + public void dump(PrintWriter pw, String prefix, long now) { pw.print(prefix); pw.print("type="); pw.print(type); - pw.print(" when="); pw.print(when); + pw.print(" when="); TimeUtils.formatDuration(when, now, pw); pw.print(" repeatInterval="); pw.print(repeatInterval); pw.print(" count="); pw.println(count); pw.print(prefix); pw.print("operation="); pw.println(operation); diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java index dc5fd30..731fb22 100644 --- a/services/java/com/android/server/AppWidgetService.java +++ b/services/java/com/android/server/AppWidgetService.java @@ -26,6 +26,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageInfo; import android.content.pm.ResolveInfo; @@ -1099,7 +1100,7 @@ class AppWidgetService extends IAppWidgetService.Stub if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); added = true; - } if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { + } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); added = false; } else { @@ -1160,7 +1161,9 @@ class AppWidgetService extends IAppWidgetService.Stub for (int i=0; i<N; i++) { ResolveInfo ri = broadcastReceivers.get(i); ActivityInfo ai = ri.activityInfo; - + if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { + continue; + } if (pkgName.equals(ai.packageName)) { addProviderLocked(ri); } @@ -1179,6 +1182,9 @@ class AppWidgetService extends IAppWidgetService.Stub for (int i=0; i<N; i++) { ResolveInfo ri = broadcastReceivers.get(i); ActivityInfo ai = ri.activityInfo; + if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { + continue; + } if (pkgName.equals(ai.packageName)) { ComponentName component = new ComponentName(ai.packageName, ai.name); Provider p = lookupProviderLocked(component); @@ -1204,6 +1210,7 @@ class AppWidgetService extends IAppWidgetService.Stub // If it's currently showing, call back with the new AppWidgetProviderInfo. for (int j=0; j<M; j++) { AppWidgetId id = p.instances.get(j); + id.views = null; if (id.host != null && id.host.callbacks != null) { try { id.host.callbacks.providerChanged(id.appWidgetId, p.info); diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index 6e307a5..a7e02a5 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -17,8 +17,8 @@ package com.android.server; import android.app.ActivityManagerNative; -import android.app.ActivityThread; import android.app.AlarmManager; +import android.app.AppGlobals; import android.app.IActivityManager; import android.app.IApplicationThread; import android.app.IBackupAgent; @@ -54,6 +54,7 @@ import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; +import android.os.WorkSource; import android.provider.Settings; import android.util.EventLog; import android.util.Slog; @@ -399,7 +400,7 @@ class BackupManagerService extends IBackupManager.Stub { public BackupManagerService(Context context) { mContext = context; mPackageManager = context.getPackageManager(); - mPackageManagerBinder = ActivityThread.getPackageManager(); + mPackageManagerBinder = AppGlobals.getPackageManager(); mActivityManager = ActivityManagerNative.getDefault(); mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); @@ -421,7 +422,7 @@ class BackupManagerService extends IBackupManager.Stub { Settings.Secure.BACKUP_AUTO_RESTORE, 1) != 0; // If Encrypted file systems is enabled or disabled, this call will return the // correct directory. - mBaseStateDir = new File(Environment.getDataDirectory(), "backup"); + mBaseStateDir = new File(Environment.getSecureDataDirectory(), "backup"); mBaseStateDir.mkdirs(); mDataDir = Environment.getDownloadCacheDirectory(); @@ -504,7 +505,7 @@ class BackupManagerService extends IBackupManager.Stub { parseLeftoverJournals(); // Power management - mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "backup"); + mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*"); // Start the backup passes going setBackupEnabled(areEnabled); @@ -663,12 +664,12 @@ class BackupManagerService extends IBackupManager.Stub { // backup. RandomAccessFile in = null; try { - Slog.i(TAG, "Found stale backup journal, scheduling:"); + Slog.i(TAG, "Found stale backup journal, scheduling"); in = new RandomAccessFile(f, "r"); while (true) { String packageName = in.readUTF(); - Slog.i(TAG, " + " + packageName); - dataChanged(packageName); + Slog.i(TAG, " " + packageName); + dataChangedImpl(packageName); } } catch (EOFException e) { // no more data; we're done @@ -740,7 +741,7 @@ class BackupManagerService extends IBackupManager.Stub { int uid = mBackupParticipants.keyAt(i); HashSet<ApplicationInfo> participants = mBackupParticipants.valueAt(i); for (ApplicationInfo app: participants) { - dataChanged(app.packageName); + dataChangedImpl(app.packageName); } } } @@ -896,7 +897,7 @@ class BackupManagerService extends IBackupManager.Stub { if (!mEverStoredApps.contains(pkg.packageName)) { if (DEBUG) Slog.i(TAG, "New app " + pkg.packageName + " never backed up; scheduling"); - dataChanged(pkg.packageName); + dataChangedImpl(pkg.packageName); } } } @@ -1327,7 +1328,7 @@ class BackupManagerService extends IBackupManager.Stub { if (status != BackupConstants.TRANSPORT_OK) { Slog.w(TAG, "Backup pass unsuccessful, restaging"); for (BackupRequest req : mQueue) { - dataChanged(req.appInfo.packageName); + dataChangedImpl(req.appInfo.packageName); } // We also want to reset the backup schedule based on whatever @@ -1363,6 +1364,7 @@ class BackupManagerService extends IBackupManager.Stub { ? IApplicationThread.BACKUP_MODE_FULL : IApplicationThread.BACKUP_MODE_INCREMENTAL; try { + mWakelock.setWorkSource(new WorkSource(request.appInfo.uid)); agent = bindToAgentSynchronous(request.appInfo, mode); if (agent != null) { int result = processOneBackup(request, agent, transport); @@ -1378,6 +1380,8 @@ class BackupManagerService extends IBackupManager.Stub { } } + mWakelock.setWorkSource(null); + return BackupConstants.TRANSPORT_OK; } @@ -1997,25 +2001,66 @@ class BackupManagerService extends IBackupManager.Stub { } } + private void dataChangedImpl(String packageName) { + HashSet<ApplicationInfo> targets = dataChangedTargets(packageName); + dataChangedImpl(packageName, targets); + } - // ----- IBackupManager binder interface ----- - - public void dataChanged(String packageName) { + private void dataChangedImpl(String packageName, HashSet<ApplicationInfo> targets) { // Record that we need a backup pass for the caller. Since multiple callers // may share a uid, we need to note all candidates within that uid and schedule // a backup pass for each of them. EventLog.writeEvent(EventLogTags.BACKUP_DATA_CHANGED, packageName); + if (targets == null) { + Slog.w(TAG, "dataChanged but no participant pkg='" + packageName + "'" + + " uid=" + Binder.getCallingUid()); + return; + } + + synchronized (mQueueLock) { + // Note that this client has made data changes that need to be backed up + for (ApplicationInfo app : targets) { + // validate the caller-supplied package name against the known set of + // packages associated with this uid + if (app.packageName.equals(packageName)) { + // 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) { + // 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. + writeToJournalLocked(packageName); + + if (DEBUG) { + int numKeys = mPendingBackups.size(); + Slog.d(TAG, "Now awaiting backup for " + numKeys + " participants:"); + for (BackupRequest b : mPendingBackups.values()) { + Slog.d(TAG, " + " + b + " agent=" + b.appInfo.backupAgentName); + } + } + } + } + } + } + } + + // Note: packageName is currently unused, but may be in the future + private HashSet<ApplicationInfo> dataChangedTargets(String packageName) { // If the caller does not hold the BACKUP permission, it can only request a // backup of its own data. - HashSet<ApplicationInfo> targets; if ((mContext.checkPermission(android.Manifest.permission.BACKUP, Binder.getCallingPid(), Binder.getCallingUid())) == PackageManager.PERMISSION_DENIED) { - targets = mBackupParticipants.get(Binder.getCallingUid()); - } else { - // a caller with full permission can ask to back up any participating app - // !!! TODO: allow backup of ANY app? - targets = new HashSet<ApplicationInfo>(); + synchronized (mBackupParticipants) { + return mBackupParticipants.get(Binder.getCallingUid()); + } + } + + // a caller with full permission can ask to back up any participating app + // !!! TODO: allow backup of ANY app? + HashSet<ApplicationInfo> targets = new HashSet<ApplicationInfo>(); + synchronized (mBackupParticipants) { int N = mBackupParticipants.size(); for (int i = 0; i < N; i++) { HashSet<ApplicationInfo> s = mBackupParticipants.valueAt(i); @@ -2024,37 +2069,7 @@ class BackupManagerService extends IBackupManager.Stub { } } } - if (targets != null) { - synchronized (mQueueLock) { - // Note that this client has made data changes that need to be backed up - for (ApplicationInfo app : targets) { - // validate the caller-supplied package name against the known set of - // packages associated with this uid - if (app.packageName.equals(packageName)) { - // 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) { - // 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. - writeToJournalLocked(packageName); - - if (DEBUG) { - int numKeys = mPendingBackups.size(); - Slog.d(TAG, "Now awaiting backup for " + numKeys + " participants:"); - for (BackupRequest b : mPendingBackups.values()) { - Slog.d(TAG, " + " + b + " agent=" + b.appInfo.backupAgentName); - } - } - } - } - } - } - } else { - Slog.w(TAG, "dataChanged but no participant pkg='" + packageName + "'" - + " uid=" + Binder.getCallingUid()); - } + return targets; } private void writeToJournalLocked(String str) { @@ -2072,6 +2087,23 @@ class BackupManagerService extends IBackupManager.Stub { } } + // ----- IBackupManager binder interface ----- + + public void dataChanged(final String packageName) { + final HashSet<ApplicationInfo> targets = dataChangedTargets(packageName); + if (targets == null) { + Slog.w(TAG, "dataChanged but no participant pkg='" + packageName + "'" + + " uid=" + Binder.getCallingUid()); + return; + } + + mBackupHandler.post(new Runnable() { + public void run() { + dataChangedImpl(packageName, targets); + } + }); + } + // Clear the given package's backup data from the current transport public void clearBackupData(String packageName) { if (DEBUG) Slog.v(TAG, "clearBackupData() of " + packageName); diff --git a/services/java/com/android/server/BatteryService.java b/services/java/com/android/server/BatteryService.java index 5cf61bd..fc4e06f 100644 --- a/services/java/com/android/server/BatteryService.java +++ b/services/java/com/android/server/BatteryService.java @@ -26,6 +26,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.os.BatteryManager; import android.os.Binder; +import android.os.FileUtils; import android.os.IBinder; import android.os.DropBoxManager; import android.os.RemoteException; @@ -210,9 +211,6 @@ class BatteryService extends Binder { boolean logOutlier = false; long dischargeDuration = 0; - shutdownIfNoPower(); - shutdownIfOverTemp(); - mBatteryLevelCritical = mBatteryLevel <= CRITICAL_BATTERY_LEVEL; if (mAcOnline) { mPlugType = BatteryManager.BATTERY_PLUGGED_AC; @@ -221,6 +219,19 @@ class BatteryService extends Binder { } else { mPlugType = BATTERY_PLUGGED_NONE; } + + // Let the battery stats keep track of the current level. + try { + mBatteryStats.setBatteryState(mBatteryStatus, mBatteryHealth, + mPlugType, mBatteryLevel, mBatteryTemperature, + mBatteryVoltage); + } catch (RemoteException e) { + // Should never happen. + } + + shutdownIfNoPower(); + shutdownIfOverTemp(); + if (mBatteryStatus != mLastBatteryStatus || mBatteryHealth != mLastBatteryHealth || mBatteryPresent != mLastBatteryPresent || @@ -263,16 +274,6 @@ class BatteryService extends Binder { EventLog.writeEvent(EventLogTags.BATTERY_LEVEL, mBatteryLevel, mBatteryVoltage, mBatteryTemperature); } - if (mBatteryLevel != mLastBatteryLevel && mPlugType == BATTERY_PLUGGED_NONE) { - // If the battery level has changed and we are on battery, update the current level. - // This is used for discharge cycle tracking so this shouldn't be updated while the - // battery is charging. - try { - mBatteryStats.recordCurrentLevel(mBatteryLevel); - } catch (RemoteException e) { - // Should never happen. - } - } if (mBatteryLevelCritical && !mLastBatteryLevelCritical && mPlugType == BATTERY_PLUGGED_NONE) { // We want to make sure we log discharge cycle outliers @@ -342,11 +343,6 @@ class BatteryService extends Binder { Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_REPLACE_PENDING); - try { - mBatteryStats.setOnBattery(mPlugType == BATTERY_PLUGGED_NONE, mBatteryLevel); - } catch (RemoteException e) { - // Should never happen. - } int icon = getIcon(mBatteryLevel); @@ -389,7 +385,7 @@ class BatteryService extends Binder { dumpFile = new File(DUMPSYS_DATA_PATH + BATTERY_STATS_SERVICE_NAME + ".dump"); dumpStream = new FileOutputStream(dumpFile); batteryInfoService.dump(dumpStream.getFD(), DUMPSYS_ARGS); - dumpStream.getFD().sync(); + FileUtils.sync(dumpStream); // add dump file to drop box db.addFile("BATTERY_DISCHARGE_INFO", dumpFile, DropBoxManager.IS_TEXT); diff --git a/services/java/com/android/server/BootReceiver.java b/services/java/com/android/server/BootReceiver.java index f409751..b9ff8d0 100644 --- a/services/java/com/android/server/BootReceiver.java +++ b/services/java/com/android/server/BootReceiver.java @@ -165,7 +165,9 @@ public class BootReceiver extends BroadcastReceiver { if (prefs != null) { long lastTime = prefs.getLong(filename, 0); if (lastTime == fileTime) return; // Already logged this particular file - prefs.edit().putLong(filename, fileTime).commit(); + // TODO: move all these SharedPreferences Editor commits + // outside this function to the end of logBootEvents + prefs.edit().putLong(filename, fileTime).apply(); } Slog.i(TAG, "Copying " + filename + " to DropBox (" + tag + ")"); diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 9edce20..7c9b547 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -49,6 +49,7 @@ import com.android.server.connectivity.Tethering; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.GregorianCalendar; import java.util.List; import java.net.InetAddress; import java.net.UnknownHostException; @@ -58,7 +59,7 @@ import java.net.UnknownHostException; */ public class ConnectivityService extends IConnectivityManager.Stub { - private static final boolean DBG = true; + private static final boolean DBG = false; private static final String TAG = "ConnectivityService"; // how long to wait before switching back to a radio's default network @@ -92,12 +93,75 @@ public class ConnectivityService extends IConnectivityManager.Stub { private Context mContext; private int mNetworkPreference; private int mActiveDefaultNetwork = -1; + // 0 is full bad, 100 is full good + private int mDefaultInetCondition = 0; + private int mDefaultInetConditionPublished = 0; + private boolean mInetConditionChangeInFlight = false; + private int mDefaultConnectionSequence = 0; private int mNumDnsEntries; private boolean mTestMode; private static ConnectivityService sServiceInstance; + private static final int ENABLED = 1; + private static final int DISABLED = 0; + + // Share the event space with NetworkStateTracker (which can't see this + // internal class but sends us events). If you change these, change + // NetworkStateTracker.java too. + private static final int MIN_NETWORK_STATE_TRACKER_EVENT = 1; + private static final int MAX_NETWORK_STATE_TRACKER_EVENT = 100; + + /** + * used internally as a delayed event to make us switch back to the + * default network + */ + private static final int EVENT_RESTORE_DEFAULT_NETWORK = + MAX_NETWORK_STATE_TRACKER_EVENT + 1; + + /** + * used internally to change our mobile data enabled flag + */ + private static final int EVENT_CHANGE_MOBILE_DATA_ENABLED = + MAX_NETWORK_STATE_TRACKER_EVENT + 2; + + /** + * used internally to change our network preference setting + * arg1 = networkType to prefer + */ + private static final int EVENT_SET_NETWORK_PREFERENCE = + MAX_NETWORK_STATE_TRACKER_EVENT + 3; + + /** + * used internally to synchronize inet condition reports + * arg1 = networkType + * arg2 = condition (0 bad, 100 good) + */ + private static final int EVENT_INET_CONDITION_CHANGE = + MAX_NETWORK_STATE_TRACKER_EVENT + 4; + + /** + * used internally to mark the end of inet condition hold periods + * arg1 = networkType + */ + private static final int EVENT_INET_CONDITION_HOLD_END = + MAX_NETWORK_STATE_TRACKER_EVENT + 5; + + /** + * used internally to set the background data preference + * arg1 = TRUE for enabled, FALSE for disabled + */ + private static final int EVENT_SET_BACKGROUND_DATA = + MAX_NETWORK_STATE_TRACKER_EVENT + 6; + + /** + * used internally to set enable/disable cellular data + * arg1 = ENBALED or DISABLED + */ + private static final int EVENT_SET_MOBILE_DATA = + MAX_NETWORK_STATE_TRACKER_EVENT + 7; + private Handler mHandler; // list of DeathRecipients used to make sure features are turned off when @@ -107,6 +171,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { private boolean mSystemReady; private Intent mInitialBroadcast; + // used in DBG mode to track inet condition reports + private static final int INET_CONDITION_LOG_MAX_SIZE = 15; + private ArrayList mInetLog; + private static class NetworkAttributes { /** * Class for holding settings read from resources. @@ -327,6 +395,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { mTethering.getTetherableWifiRegexs().length != 0) && mTethering.getUpstreamIfaceRegexs().length != 0); + if (DBG) { + mInetLog = new ArrayList(); + } } @@ -334,30 +405,36 @@ public class ConnectivityService extends IConnectivityManager.Stub { * Sets the preferred network. * @param preference the new preference */ - public synchronized void setNetworkPreference(int preference) { + public void setNetworkPreference(int preference) { enforceChangePermission(); + + mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_NETWORK_PREFERENCE, preference, 0)); + } + + public int getNetworkPreference() { + enforceAccessPermission(); + int preference; + synchronized(this) { + preference = mNetworkPreference; + } + return preference; + } + + private void handleSetNetworkPreference(int preference) { if (ConnectivityManager.isNetworkTypeValid(preference) && mNetAttributes[preference] != null && mNetAttributes[preference].isDefault()) { if (mNetworkPreference != preference) { - persistNetworkPreference(preference); - mNetworkPreference = preference; + final ContentResolver cr = mContext.getContentResolver(); + Settings.Secure.putInt(cr, Settings.Secure.NETWORK_PREFERENCE, preference); + synchronized(this) { + mNetworkPreference = preference; + } enforcePreference(); } } } - public int getNetworkPreference() { - enforceAccessPermission(); - return mNetworkPreference; - } - - private void persistNetworkPreference(int networkPreference) { - final ContentResolver cr = mContext.getContentResolver(); - Settings.Secure.putInt(cr, Settings.Secure.NETWORK_PREFERENCE, - networkPreference); - } - private int getPersistedNetworkPreference() { final ContentResolver cr = mContext.getContentResolver(); @@ -576,8 +653,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { mNetRequestersPids[usedNetworkType].add(currentPid); } } - mHandler.sendMessageDelayed(mHandler.obtainMessage( - NetworkStateTracker.EVENT_RESTORE_DEFAULT_NETWORK, + mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_RESTORE_DEFAULT_NETWORK, f), getRestoreDefaultNetworkDelay()); @@ -585,7 +661,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { !network.isTeardownRequested()) { if (ni.isConnected() == true) { // add the pid-specific dns - handleDnsConfigurationChange(); + handleDnsConfigurationChange(networkType); if (DBG) Slog.d(TAG, "special network already active"); return Phone.APN_ALREADY_ACTIVE; } @@ -603,8 +679,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { synchronized(this) { mFeatureUsers.add(f); } - mHandler.sendMessageDelayed(mHandler.obtainMessage( - NetworkStateTracker.EVENT_RESTORE_DEFAULT_NETWORK, + mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_RESTORE_DEFAULT_NETWORK, f), getRestoreDefaultNetworkDelay()); return network.startUsingNetworkFeature(feature, @@ -802,15 +877,18 @@ public class ConnectivityService extends IConnectivityManager.Stub { android.Manifest.permission.CHANGE_BACKGROUND_DATA_SETTING, "ConnectivityService"); - if (getBackgroundDataSetting() == allowBackgroundDataUsage) return; - - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.BACKGROUND_DATA, - allowBackgroundDataUsage ? 1 : 0); + mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_BACKGROUND_DATA, + (allowBackgroundDataUsage ? ENABLED : DISABLED), 0)); + } - Intent broadcast = new Intent( - ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED); - mContext.sendBroadcast(broadcast); + 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); + } } /** @@ -827,10 +905,15 @@ public class ConnectivityService extends IConnectivityManager.Stub { /** * @see ConnectivityManager#setMobileDataEnabled(boolean) */ - public synchronized void setMobileDataEnabled(boolean enabled) { + public void setMobileDataEnabled(boolean enabled) { enforceChangePermission(); if (DBG) Slog.d(TAG, "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(), @@ -838,7 +921,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (enabled) { if (mNetTrackers[ConnectivityManager.TYPE_MOBILE] != null) { - if (DBG) Slog.d(TAG, "starting up " + mNetTrackers[ConnectivityManager.TYPE_MOBILE]); + if (DBG) { + Slog.d(TAG, "starting up " + mNetTrackers[ConnectivityManager.TYPE_MOBILE]); + } mNetTrackers[ConnectivityManager.TYPE_MOBILE].reconnect(); } } else { @@ -920,7 +1005,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { } Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); if (info.isFailover()) { intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true); @@ -939,13 +1023,21 @@ public class ConnectivityService extends IConnectivityManager.Stub { newNet = tryFailover(prevNetType); if (newNet != null) { NetworkInfo switchTo = newNet.getNetworkInfo(); + if (!switchTo.isConnected()) { + // if the other net is connected they've already reset this and perhaps even gotten + // a positive report we don't want to overwrite, but if not we need to clear this now + // to turn our cellular sig strength white + mDefaultInetConditionPublished = 0; + } intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo); } else { + mDefaultInetConditionPublished = 0; // we're not connected anymore intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true); } } + intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION, mDefaultInetConditionPublished); // do this before we broadcast the change - handleConnectivityChange(); + handleConnectivityChange(prevNetType); sendStickyBroadcast(intent); /* @@ -977,10 +1069,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (mNetAttributes[checkType] == null) continue; if (mNetAttributes[checkType].mRadio == ConnectivityManager.TYPE_MOBILE && noMobileData) { - if (DBG) { - Slog.d(TAG, "not failing over to mobile type " + checkType + - " because Mobile Data Disabled"); - } + Slog.e(TAG, "not failing over to mobile type " + checkType + + " because Mobile Data Disabled"); continue; } if (mNetAttributes[checkType].isDefault()) { @@ -1028,6 +1118,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { newNet = null; // not officially avail.. try anyway, but // report no failover } + } else { + Slog.e(TAG, "Network failover failing."); } } @@ -1035,8 +1127,15 @@ public class ConnectivityService extends IConnectivityManager.Stub { } private void sendConnectedBroadcast(NetworkInfo info) { - Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + sendGeneralBroadcast(info, ConnectivityManager.CONNECTIVITY_ACTION); + } + + private void sendInetConditionBroadcast(NetworkInfo info) { + sendGeneralBroadcast(info, ConnectivityManager.INET_CONDITION_ACTION); + } + + private void sendGeneralBroadcast(NetworkInfo info, String bcastType) { + Intent intent = new Intent(bcastType); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); if (info.isFailover()) { intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true); @@ -1049,6 +1148,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo()); } + intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION, mDefaultInetConditionPublished); sendStickyBroadcast(intent); } @@ -1062,19 +1162,15 @@ public class ConnectivityService extends IConnectivityManager.Stub { String reason = info.getReason(); String extraInfo = info.getExtraInfo(); - if (DBG) { - String reasonText; - if (reason == null) { - reasonText = "."; - } else { - reasonText = " (" + reason + ")."; - } - Slog.v(TAG, "Attempt to connect to " + info.getTypeName() + - " failed" + reasonText); + String reasonText; + if (reason == null) { + reasonText = "."; + } else { + reasonText = " (" + reason + ")."; } + Slog.e(TAG, "Attempt to connect to " + info.getTypeName() + " failed" + reasonText); Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); if (getActiveNetworkInfo() == null) { intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true); @@ -1095,15 +1191,20 @@ public class ConnectivityService extends IConnectivityManager.Stub { newNet = tryFailover(info.getType()); if (newNet != null) { NetworkInfo switchTo = newNet.getNetworkInfo(); + if (!switchTo.isConnected()) { + // if the other net is connected they've already reset this and perhaps + // even gotten a positive report we don't want to overwrite, but if not + // we need to clear this now to turn our cellular sig strength white + mDefaultInetConditionPublished = 0; + } intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo); } else { + mDefaultInetConditionPublished = 0; intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true); } } - // do this before we broadcast the change - handleConnectivityChange(); - + intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION, mDefaultInetConditionPublished); sendStickyBroadcast(intent); /* * If the failover network is already connected, then immediately send @@ -1171,10 +1272,18 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } mActiveDefaultNetwork = type; + // this will cause us to come up initially as unconnected and switching + // to connected after our normal pause unless somebody reports us as reall + // disconnected + mDefaultInetConditionPublished = 0; + mDefaultConnectionSequence++; + mInetConditionChangeInFlight = false; + // Don't do this - if we never sign in stay, grey + //reportNetworkCondition(mActiveDefaultNetwork, 100); } thisNet.setTeardownRequested(false); thisNet.updateNetworkSettings(); - handleConnectivityChange(); + handleConnectivityChange(type); sendConnectedBroadcast(info); } @@ -1201,38 +1310,29 @@ public class ConnectivityService extends IConnectivityManager.Stub { } /** - * After any kind of change in the connectivity state of any network, - * make sure that anything that depends on the connectivity state of - * more than one network is set up correctly. 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. + * 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 + * according to which networks are connected, and ensuring that the + * right routing table entries exist. */ - private void handleConnectivityChange() { + private void handleConnectivityChange(int netType) { /* * If a non-default network is enabled, add the host routes that - * will allow it's DNS servers to be accessed. Only - * If both mobile and wifi are enabled, add the host routes that - * will allow MMS traffic to pass on the mobile network. But - * remove the default route for the mobile network, so that there - * will be only one default route, to ensure that all traffic - * except MMS will travel via Wi-Fi. + * will allow it's DNS servers to be accessed. */ - handleDnsConfigurationChange(); + handleDnsConfigurationChange(netType); - for (int netType : mPriorityList) { - if (mNetTrackers[netType].getNetworkInfo().isConnected()) { - if (mNetAttributes[netType].isDefault()) { - mNetTrackers[netType].addDefaultRoute(); - } else { - mNetTrackers[netType].addPrivateDnsRoutes(); - } + if (mNetTrackers[netType].getNetworkInfo().isConnected()) { + if (mNetAttributes[netType].isDefault()) { + mNetTrackers[netType].addDefaultRoute(); } else { - if (mNetAttributes[netType].isDefault()) { - mNetTrackers[netType].removeDefaultRoute(); - } else { - mNetTrackers[netType].removePrivateDnsRoutes(); - } + mNetTrackers[netType].addPrivateDnsRoutes(); + } + } else { + if (mNetAttributes[netType].isDefault()) { + mNetTrackers[netType].removeDefaultRoute(); + } else { + mNetTrackers[netType].removePrivateDnsRoutes(); } } } @@ -1303,41 +1403,36 @@ public class ConnectivityService extends IConnectivityManager.Stub { SystemProperties.set("net.dnschange", "" + (n+1)); } - private void handleDnsConfigurationChange() { + private void handleDnsConfigurationChange(int netType) { // add default net's dns entries - for (int x = mPriorityList.length-1; x>= 0; x--) { - int netType = mPriorityList[x]; - NetworkStateTracker nt = mNetTrackers[netType]; - if (nt != null && nt.getNetworkInfo().isConnected() && - !nt.isTeardownRequested()) { - String[] dnsList = nt.getNameServers(); - if (mNetAttributes[netType].isDefault()) { - int j = 1; - for (String dns : dnsList) { - if (dns != null && !TextUtils.equals(dns, "0.0.0.0")) { - if (DBG) { - Slog.d(TAG, "adding dns " + dns + " for " + - nt.getNetworkInfo().getTypeName()); - } - SystemProperties.set("net.dns" + j++, dns); + NetworkStateTracker nt = mNetTrackers[netType]; + if (nt != null && nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) { + String[] dnsList = nt.getNameServers(); + if (mNetAttributes[netType].isDefault()) { + int j = 1; + for (String dns : dnsList) { + if (dns != null && !TextUtils.equals(dns, "0.0.0.0")) { + if (DBG) { + Slog.d(TAG, "adding dns " + dns + " for " + + nt.getNetworkInfo().getTypeName()); } + SystemProperties.set("net.dns" + j++, dns); } - for (int k=j ; k<mNumDnsEntries; k++) { - if (DBG) Slog.d(TAG, "erasing net.dns" + k); - SystemProperties.set("net.dns" + k, ""); - } - mNumDnsEntries = j; - } else { - // set per-pid dns for attached secondary nets - List pids = mNetRequestersPids[netType]; - for (int y=0; y< pids.size(); y++) { - Integer pid = (Integer)pids.get(y); - writePidDns(dnsList, pid.intValue()); - } + } + for (int k=j ; k<mNumDnsEntries; k++) { + if (DBG) Slog.d(TAG, "erasing net.dns" + k); + SystemProperties.set("net.dns" + k, ""); + } + mNumDnsEntries = j; + } else { + // set per-pid dns for attached secondary nets + List pids = mNetRequestersPids[netType]; + for (int y=0; y< pids.size(); y++) { + Integer pid = (Integer)pids.get(y); + writePidDns(dnsList, pid.intValue()); } } } - bumpDns(); } @@ -1394,6 +1489,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { pw.println(); mTethering.dump(fd, pw, args); + + if (mInetLog != null) { + pw.println(); + pw.println("Inet condition reports:"); + for(int i = 0; i < mInetLog.size(); i++) { + pw.println(mInetLog.get(i)); + } + } } // must be stateless - things change under us. @@ -1468,9 +1571,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { case NetworkStateTracker.EVENT_NOTIFICATION_CHANGED: handleNotificationChange(msg.arg1 == 1, msg.arg2, (Notification) msg.obj); + break; case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED: - handleDnsConfigurationChange(); + info = (NetworkInfo) msg.obj; + type = info.getType(); + handleDnsConfigurationChange(type); break; case NetworkStateTracker.EVENT_ROAMING_CHANGED: @@ -1480,10 +1586,42 @@ public class ConnectivityService extends IConnectivityManager.Stub { case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED: // fill me in break; - case NetworkStateTracker.EVENT_RESTORE_DEFAULT_NETWORK: + case EVENT_RESTORE_DEFAULT_NETWORK: FeatureUser u = (FeatureUser)msg.obj; u.expire(); break; + case EVENT_INET_CONDITION_CHANGE: + { + int netType = msg.arg1; + int condition = msg.arg2; + handleInetConditionChange(netType, condition); + break; + } + case EVENT_INET_CONDITION_HOLD_END: + { + int netType = msg.arg1; + int sequence = msg.arg2; + handleInetConditionHoldEnd(netType, sequence); + break; + } + case EVENT_SET_NETWORK_PREFERENCE: + { + int preference = msg.arg1; + handleSetNetworkPreference(preference); + break; + } + case EVENT_SET_BACKGROUND_DATA: + { + boolean enabled = (msg.arg1 == ENABLED); + handleSetBackgroundData(enabled); + break; + } + case EVENT_SET_MOBILE_DATA: + { + boolean enabled = (msg.arg1 == ENABLED); + handleSetMobileData(enabled); + break; + } } } } @@ -1567,4 +1705,92 @@ public class ConnectivityService extends IConnectivityManager.Stub { Settings.Secure.TETHER_SUPPORTED, defaultVal) != 0); return tetherEnabledInSettings && mTetheringConfigValid; } + + // 100 percent is full good, 0 is full bad. + public void reportInetCondition(int networkType, int percentage) { + if (DBG) Slog.d(TAG, "reportNetworkCondition(" + networkType + ", " + percentage + ")"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.STATUS_BAR, + "ConnectivityService"); + + if (DBG) { + int pid = getCallingPid(); + int uid = getCallingUid(); + String s = pid + "(" + uid + ") reports inet is " + + (percentage > 50 ? "connected" : "disconnected") + " (" + percentage + ") on " + + "network Type " + networkType + " at " + GregorianCalendar.getInstance().getTime(); + mInetLog.add(s); + while(mInetLog.size() > INET_CONDITION_LOG_MAX_SIZE) { + mInetLog.remove(0); + } + } + mHandler.sendMessage(mHandler.obtainMessage( + EVENT_INET_CONDITION_CHANGE, networkType, percentage)); + } + + private void handleInetConditionChange(int netType, int condition) { + if (DBG) { + Slog.d(TAG, "Inet connectivity change, net=" + + netType + ", condition=" + condition + + ",mActiveDefaultNetwork=" + mActiveDefaultNetwork); + } + if (mActiveDefaultNetwork == -1) { + if (DBG) Slog.d(TAG, "no active default network - aborting"); + return; + } + if (mActiveDefaultNetwork != netType) { + if (DBG) Slog.d(TAG, "given net not default - aborting"); + return; + } + mDefaultInetCondition = condition; + int delay; + if (mInetConditionChangeInFlight == false) { + if (DBG) Slog.d(TAG, "starting a change hold"); + // setup a new hold to debounce this + if (mDefaultInetCondition > 50) { + delay = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.INET_CONDITION_DEBOUNCE_UP_DELAY, 500); + } else { + delay = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.INET_CONDITION_DEBOUNCE_DOWN_DELAY, 3000); + } + mInetConditionChangeInFlight = true; + mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_INET_CONDITION_HOLD_END, + mActiveDefaultNetwork, mDefaultConnectionSequence), delay); + } 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"); + } + } + + private void handleInetConditionHoldEnd(int netType, int sequence) { + if (DBG) { + Slog.d(TAG, "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"); + return; + } + if (mDefaultConnectionSequence != sequence) { + if (DBG) Slog.d(TAG, "event hold for obsolete network - aborting"); + return; + } + if (mDefaultInetConditionPublished == mDefaultInetCondition) { + if (DBG) Slog.d(TAG, "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"); + return; + } + mDefaultInetConditionPublished = mDefaultInetCondition; + sendInetConditionBroadcast(networkInfo); + return; + } } diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java index 19d146d..1538003 100644 --- a/services/java/com/android/server/DevicePolicyManagerService.java +++ b/services/java/com/android/server/DevicePolicyManagerService.java @@ -17,6 +17,7 @@ package com.android.server; import com.android.internal.content.PackageMonitor; +import com.android.internal.os.storage.ExternalStorageFormatter; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.JournaledFile; import com.android.internal.util.XmlUtils; @@ -41,6 +42,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.os.Binder; import android.os.IBinder; import android.os.IPowerManager; +import android.os.PowerManager; import android.os.RecoverySystem; import android.os.RemoteCallback; import android.os.RemoteException; @@ -55,6 +57,7 @@ import android.view.WindowManagerPolicy; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; @@ -70,6 +73,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final Context mContext; final MyPackageMonitor mMonitor; + final PowerManager.WakeLock mWakeLock; IPowerManager mIPowerManager; @@ -215,6 +219,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mContext = context; mMonitor = new MyPackageMonitor(); mMonitor.register(context, true); + mWakeLock = ((PowerManager)context.getSystemService(Context.POWER_SERVICE)) + .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "DPM"); } private IPowerManager getIPowerManager() { @@ -451,6 +457,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Slog.w(TAG, "failed parsing " + file + " " + e); } catch (XmlPullParserException e) { Slog.w(TAG, "failed parsing " + file + " " + e); + } catch (FileNotFoundException e) { + // Don't be noisy, this is normal if we haven't defined any policies. } catch (IOException e) { Slog.w(TAG, "failed parsing " + file + " " + e); } catch (IndexOutOfBoundsException e) { @@ -859,10 +867,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } void wipeDataLocked(int flags) { - try { - RecoverySystem.rebootWipeUserData(mContext); - } catch (IOException e) { - Slog.w(TAG, "Failed requesting data wipe", e); + if ((flags&DevicePolicyManager.WIPE_EXTERNAL_STORAGE) != 0) { + Intent intent = new Intent(ExternalStorageFormatter.FORMAT_AND_FACTORY_RESET); + intent.setComponent(ExternalStorageFormatter.COMPONENT_NAME); + mWakeLock.acquire(10000); + mContext.startService(intent); + } else { + try { + RecoverySystem.rebootWipeUserData(mContext); + } catch (IOException e) { + Slog.w(TAG, "Failed requesting data wipe", e); + } } } diff --git a/services/java/com/android/server/DeviceStorageMonitorService.java b/services/java/com/android/server/DeviceStorageMonitorService.java index 4a0df59..0b1a4a3 100644 --- a/services/java/com/android/server/DeviceStorageMonitorService.java +++ b/services/java/com/android/server/DeviceStorageMonitorService.java @@ -69,10 +69,12 @@ class DeviceStorageMonitorService extends Binder { 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; + private static final int DEFAULT_FULL_THRESHOLD_BYTES = 1024*1024; // 1MB private long mFreeMem; // on /data private long mLastReportedFreeMem; private long mLastReportedFreeMemTime; private boolean mLowMemFlag=false; + private boolean mMemFullFlag=false; private Context mContext; private ContentResolver mContentResolver; private long mTotalMemory; // on /data @@ -87,9 +89,13 @@ class DeviceStorageMonitorService extends Binder { private boolean mClearingCache; private Intent mStorageLowIntent; private Intent mStorageOkIntent; + private Intent mStorageFullIntent; + private Intent mStorageNotFullIntent; private CachePackageDataObserver mClearCacheObserver; private static final int _TRUE = 1; private static final int _FALSE = 0; + private long mMemLowThreshold; + private int mMemFullThreshold; /** * This string is used for ServiceManager access to this class. @@ -103,7 +109,7 @@ class DeviceStorageMonitorService extends Binder { Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { - //dont handle an invalid message + //don't handle an invalid message if (msg.what != DEVICE_MEMORY_WHAT) { Slog.e(TAG, "Will not process invalid message"); return; @@ -184,7 +190,7 @@ class DeviceStorageMonitorService extends Binder { try { if (localLOGV) Slog.i(TAG, "Clearing cache"); IPackageManager.Stub.asInterface(ServiceManager.getService("package")). - freeStorageAndNotify(getMemThreshold(), mClearCacheObserver); + freeStorageAndNotify(mMemLowThreshold, mClearCacheObserver); } catch (RemoteException e) { Slog.w(TAG, "Failed to get handle for PackageManger Exception: "+e); mClearingCache = false; @@ -209,8 +215,7 @@ class DeviceStorageMonitorService extends Binder { if (localLOGV) Slog.v(TAG, "freeMemory="+mFreeMem); //post intent to NotificationManager to display icon if necessary - long memThreshold = getMemThreshold(); - if (mFreeMem < memThreshold) { + if (mFreeMem < mMemLowThreshold) { if (!mLowMemFlag) { if (checkCache) { // See if clearing cache helps @@ -235,6 +240,17 @@ class DeviceStorageMonitorService extends Binder { mLowMemFlag = false; } } + if (mFreeMem < mMemFullThreshold) { + if (!mMemFullFlag) { + sendFullNotification(); + mMemFullFlag = true; + } + } else { + if (mMemFullFlag) { + cancelFullNotification(); + mMemFullFlag = false; + } + } } if(localLOGV) Slog.i(TAG, "Posting Message again"); //keep posting messages to itself periodically @@ -264,6 +280,20 @@ class DeviceStorageMonitorService extends Binder { return mTotalMemory*value; } + /* + * just query settings to retrieve the memory full threshold. + * Preferred this over using a ContentObserver since Settings.Secure caches the value + * any way + */ + private int getMemFullThreshold() { + int value = Settings.Secure.getInt( + mContentResolver, + Settings.Secure.SYS_STORAGE_FULL_THRESHOLD_BYTES, + DEFAULT_FULL_THRESHOLD_BYTES); + if(localLOGV) Slog.v(TAG, "Full Threshold Bytes="+value); + return value; + } + /** * Constructor to run service. initializes the disk space threshold value * and posts an empty message to kickstart the process. @@ -283,6 +313,13 @@ class DeviceStorageMonitorService extends Binder { mStorageLowIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); mStorageOkIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK); mStorageOkIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mStorageFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_FULL); + mStorageFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mStorageNotFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL); + mStorageNotFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + // cache storage thresholds + mMemLowThreshold = getMemThreshold(); + mMemFullThreshold = getMemFullThreshold(); checkMemory(true); } @@ -332,6 +369,23 @@ class DeviceStorageMonitorService extends Binder { mContext.sendBroadcast(mStorageOkIntent); } + /** + * Send a notification when storage is full. + */ + private final void sendFullNotification() { + if(localLOGV) Slog.i(TAG, "Sending memory full notification"); + mContext.sendStickyBroadcast(mStorageFullIntent); + } + + /** + * Cancels memory full notification and sends "not full" intent. + */ + private final void cancelFullNotification() { + if(localLOGV) Slog.i(TAG, "Canceling memory full notification"); + mContext.removeStickyBroadcast(mStorageFullIntent); + mContext.sendBroadcast(mStorageNotFullIntent); + } + public void updateMemory() { int callingUid = getCallingUid(); if(callingUid != Process.SYSTEM_UID) { diff --git a/services/java/com/android/server/DropBoxManagerService.java b/services/java/com/android/server/DropBoxManagerService.java index 0de11c6..0e45145 100644 --- a/services/java/com/android/server/DropBoxManagerService.java +++ b/services/java/com/android/server/DropBoxManagerService.java @@ -26,6 +26,7 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Debug; import android.os.DropBoxManager; +import android.os.FileUtils; import android.os.Handler; import android.os.ParcelFileDescriptor; import android.os.StatFs; @@ -36,6 +37,7 @@ import android.util.Slog; import com.android.internal.os.IDropBoxManagerService; +import java.io.BufferedOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileOutputStream; @@ -179,7 +181,11 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { // the data in uncompressed form. temp = new File(mDropBoxDir, "drop" + Thread.currentThread().getId() + ".tmp"); - output = new FileOutputStream(temp); + int bufferSize = mBlockSize; + if (bufferSize > 4096) bufferSize = 4096; + if (bufferSize < 512) bufferSize = 512; + FileOutputStream foutput = new FileOutputStream(temp); + output = new BufferedOutputStream(foutput, bufferSize); if (read == buffer.length && ((flags & DropBoxManager.IS_GZIPPED) == 0)) { output = new GZIPOutputStream(output); flags = flags | DropBoxManager.IS_GZIPPED; @@ -196,6 +202,7 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { read = input.read(buffer); if (read <= 0) { + FileUtils.sync(foutput); output.close(); // Get a final size measurement output = null; } else { @@ -690,8 +697,6 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { // was lost. Tombstones are expunged by age (see above). if (mAllFiles.blocks > mCachedQuotaBlocks) { - Slog.i(TAG, "Usage (" + mAllFiles.blocks + ") > Quota (" + mCachedQuotaBlocks + ")"); - // Find a fair share amount of space to limit each tag int unsqueezed = mAllFiles.blocks, squeezed = 0; TreeSet<FileList> tags = new TreeSet<FileList>(mFilesByTag.values()); diff --git a/services/java/com/android/server/EntropyService.java b/services/java/com/android/server/EntropyService.java index 81ae26f..0f1fc78 100644 --- a/services/java/com/android/server/EntropyService.java +++ b/services/java/com/android/server/EntropyService.java @@ -139,6 +139,7 @@ public class EntropyService extends Binder { out.println(SystemProperties.get("ro.bootloader")); out.println(SystemProperties.get("ro.hardware")); out.println(SystemProperties.get("ro.revision")); + out.println(new Object().hashCode()); out.println(System.currentTimeMillis()); out.println(System.nanoTime()); } catch (IOException e) { diff --git a/services/java/com/android/server/status/StatusBarException.java b/services/java/com/android/server/InputApplication.java index be58f59..38420d4 100644 --- a/services/java/com/android/server/status/StatusBarException.java +++ b/services/java/com/android/server/InputApplication.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. @@ -14,10 +14,20 @@ * limitations under the License. */ -package com.android.server.status; +package com.android.server; -public class StatusBarException extends RuntimeException { - StatusBarException(String msg) { - super(msg); - } +/** + * Describes input-related application properties for use by the input dispatcher. + * + * @hide + */ +public final class InputApplication { + // Application name. + public String name; + + // Dispatching timeout. + public long dispatchingTimeoutNanos; + + // The application window token. + public Object token; } diff --git a/services/java/com/android/server/InputDevice.java b/services/java/com/android/server/InputDevice.java deleted file mode 100644 index 414b69f..0000000 --- a/services/java/com/android/server/InputDevice.java +++ /dev/null @@ -1,1025 +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.util.Slog; -import android.view.Display; -import android.view.MotionEvent; -import android.view.Surface; -import android.view.WindowManagerPolicy; - -import java.io.PrintWriter; - -public class InputDevice { - static final boolean DEBUG_POINTERS = false; - static final boolean DEBUG_HACKS = false; - - /** Amount that trackball needs to move in order to generate a key event. */ - static final int TRACKBALL_MOVEMENT_THRESHOLD = 6; - - /** Maximum number of pointers we will track and report. */ - static final int MAX_POINTERS = 10; - - /** - * Slop distance for jumpy pointer detection. - * The vertical range of the screen divided by this is our epsilon value. - */ - private static final int JUMPY_EPSILON_DIVISOR = 212; - - /** Number of jumpy points to drop for touchscreens that need it. */ - private static final int JUMPY_TRANSITION_DROPS = 3; - private static final int JUMPY_DROP_LIMIT = 3; - - final int id; - final int classes; - final String name; - final AbsoluteInfo absX; - final AbsoluteInfo absY; - final AbsoluteInfo absPressure; - final AbsoluteInfo absSize; - - long mKeyDownTime = 0; - int mMetaKeysState = 0; - - // For use by KeyInputQueue for keeping track of the current touch - // data in the old non-multi-touch protocol. - final int[] curTouchVals = new int[MotionEvent.NUM_SAMPLE_DATA * 2]; - - final MotionState mAbs = new MotionState(0, 0); - final MotionState mRel = new MotionState(TRACKBALL_MOVEMENT_THRESHOLD, - TRACKBALL_MOVEMENT_THRESHOLD); - - static class MotionState { - int xPrecision; - int yPrecision; - float xMoveScale; - float yMoveScale; - MotionEvent currentMove = null; - boolean changed = false; - boolean everChanged = false; - long mDownTime = 0; - - // The currently assigned pointer IDs, corresponding to the last data. - int[] mPointerIds = new int[MAX_POINTERS]; - - // This is the last generated pointer data, ordered to match - // mPointerIds. - boolean mSkipLastPointers; - int mLastNumPointers = 0; - final int[] mLastData = new int[MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS]; - - // This is the next set of pointer data being generated. It is not - // in any known order, and will be propagated in to mLastData - // as part of mapping it to the appropriate pointer IDs. - // Note that we have one extra sample of data here, to help clients - // avoid doing bounds checking. - int mNextNumPointers = 0; - final int[] mNextData = new int[(MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS) - + MotionEvent.NUM_SAMPLE_DATA]; - - // Used to determine whether we dropped bad data, to avoid doing - // it repeatedly. - final boolean[] mDroppedBadPoint = new boolean[MAX_POINTERS]; - - // Used to count the number of jumpy points dropped. - private int mJumpyPointsDropped = 0; - - // Used to perform averaging of reported coordinates, to smooth - // the data and filter out transients during a release. - static final int HISTORY_SIZE = 5; - int[] mHistoryDataStart = new int[MAX_POINTERS]; - int[] mHistoryDataEnd = new int[MAX_POINTERS]; - final int[] mHistoryData = new int[(MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS) - * HISTORY_SIZE]; - final int[] mAveragedData = new int[MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS]; - - // Temporary data structures for doing the pointer ID mapping. - final int[] mLast2Next = new int[MAX_POINTERS]; - final int[] mNext2Last = new int[MAX_POINTERS]; - final long[] mNext2LastDistance = new long[MAX_POINTERS]; - - // Temporary data structure for generating the final motion data. - final float[] mReportData = new float[MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS]; - - // This is not used here, but can be used by callers for state tracking. - int mAddingPointerOffset = 0; - final boolean[] mDown = new boolean[MAX_POINTERS]; - - void dumpIntArray(PrintWriter pw, int[] array) { - pw.print("["); - for (int i=0; i<array.length; i++) { - if (i > 0) pw.print(", "); - pw.print(array[i]); - } - pw.print("]"); - } - - void dumpBooleanArray(PrintWriter pw, boolean[] array) { - pw.print("["); - for (int i=0; i<array.length; i++) { - if (i > 0) pw.print(", "); - pw.print(array[i] ? "true" : "false"); - } - pw.print("]"); - } - - void dump(PrintWriter pw, String prefix) { - pw.print(prefix); pw.print("xPrecision="); pw.print(xPrecision); - pw.print(" yPrecision="); pw.println(yPrecision); - pw.print(prefix); pw.print("xMoveScale="); pw.print(xMoveScale); - pw.print(" yMoveScale="); pw.println(yMoveScale); - if (currentMove != null) { - pw.print(prefix); pw.print("currentMove="); pw.println(currentMove); - } - if (changed || mDownTime != 0) { - pw.print(prefix); pw.print("changed="); pw.print(changed); - pw.print(" mDownTime="); pw.println(mDownTime); - } - pw.print(prefix); pw.print("mPointerIds="); dumpIntArray(pw, mPointerIds); - pw.println(""); - if (mSkipLastPointers || mLastNumPointers != 0) { - pw.print(prefix); pw.print("mSkipLastPointers="); pw.print(mSkipLastPointers); - pw.print(" mLastNumPointers="); pw.println(mLastNumPointers); - pw.print(prefix); pw.print("mLastData="); dumpIntArray(pw, mLastData); - pw.println(""); - } - if (mNextNumPointers != 0) { - pw.print(prefix); pw.print("mNextNumPointers="); pw.println(mNextNumPointers); - pw.print(prefix); pw.print("mNextData="); dumpIntArray(pw, mNextData); - pw.println(""); - } - pw.print(prefix); pw.print("mDroppedBadPoint="); - dumpBooleanArray(pw, mDroppedBadPoint); pw.println(""); - pw.print(prefix); pw.print("mAddingPointerOffset="); pw.println(mAddingPointerOffset); - pw.print(prefix); pw.print("mDown="); - dumpBooleanArray(pw, mDown); pw.println(""); - } - - MotionState(int mx, int my) { - xPrecision = mx; - yPrecision = my; - xMoveScale = mx != 0 ? (1.0f/mx) : 1.0f; - yMoveScale = my != 0 ? (1.0f/my) : 1.0f; - for (int i=0; i<MAX_POINTERS; i++) { - mPointerIds[i] = i; - } - } - - /** - * 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. - */ - void dropBadPoint(InputDevice dev) { - // We should always have absY, but let's be paranoid. - if (dev.absY == null) { - return; - } - // 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 (mNextNumPointers != mLastNumPointers) { - return; - } - - // 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. - final int maxDy = ((dev.absY.maxValue-dev.absY.minValue)*7)/16; - - // Look through all new points and see if any are farther than - // acceptable from all previous points. - for (int i=mNextNumPointers-1; i>=0; i--) { - final int ioff = i * MotionEvent.NUM_SAMPLE_DATA; - //final int x = mNextData[ioff + MotionEvent.SAMPLE_X]; - final int y = mNextData[ioff + MotionEvent.SAMPLE_Y]; - if (DEBUG_HACKS) Slog.v("InputDevice", "Looking at next point #" + i + ": y=" + y); - boolean dropped = false; - if (!mDroppedBadPoint[i] && mLastNumPointers > 0) { - dropped = true; - int closestDy = -1; - int closestY = -1; - // We will drop this new point if it is sufficiently - // far away from -all- last points. - for (int j=mLastNumPointers-1; j>=0; j--) { - final int joff = j * MotionEvent.NUM_SAMPLE_DATA; - //int dx = x - mLastData[joff + MotionEvent.SAMPLE_X]; - int dy = y - mLastData[joff + MotionEvent.SAMPLE_Y]; - //if (dx < 0) dx = -dx; - if (dy < 0) dy = -dy; - if (DEBUG_HACKS) Slog.v("InputDevice", "Comparing with last point #" + j - + ": y=" + mLastData[joff] + " dy=" + dy); - if (dy < maxDy) { - dropped = false; - break; - } else if (closestDy < 0 || dy < closestDy) { - closestDy = dy; - closestY = mLastData[joff + MotionEvent.SAMPLE_Y]; - } - } - if (dropped) { - dropped = true; - Slog.i("InputDevice", "Dropping bad point #" + i - + ": newY=" + y + " closestDy=" + closestDy - + " maxDy=" + maxDy); - mNextData[ioff + MotionEvent.SAMPLE_Y] = closestY; - break; - } - } - mDroppedBadPoint[i] = dropped; - } - } - - void dropJumpyPoint(InputDevice dev) { - // We should always have absY, but let's be paranoid. - if (dev.absY == null) { - return; - } - final int jumpyEpsilon = dev.absY.range / JUMPY_EPSILON_DIVISOR; - - final int nextNumPointers = mNextNumPointers; - final int lastNumPointers = mLastNumPointers; - final int[] nextData = mNextData; - final int[] lastData = mLastData; - - if (nextNumPointers != mLastNumPointers) { - if (DEBUG_HACKS) { - Slog.d("InputDevice", "Different pointer count " + lastNumPointers + - " -> " + nextNumPointers); - for (int i = 0; i < nextNumPointers; i++) { - int ioff = i * MotionEvent.NUM_SAMPLE_DATA; - Slog.d("InputDevice", "Pointer " + i + " (" + - mNextData[ioff + MotionEvent.SAMPLE_X] + ", " + - mNextData[ioff + MotionEvent.SAMPLE_Y] + ")"); - } - } - - // Just drop the first few events going from 1 to 2 pointers. - // They're bad often enough that they're not worth considering. - if (lastNumPointers == 1 && nextNumPointers == 2 - && mJumpyPointsDropped < JUMPY_TRANSITION_DROPS) { - mNextNumPointers = 1; - mJumpyPointsDropped++; - } else if (lastNumPointers == 2 && nextNumPointers == 1 - && mJumpyPointsDropped < JUMPY_TRANSITION_DROPS) { - // The event when we go from 2 -> 1 tends to be messed up too - System.arraycopy(lastData, 0, nextData, 0, - lastNumPointers * MotionEvent.NUM_SAMPLE_DATA); - mNextNumPointers = lastNumPointers; - mJumpyPointsDropped++; - - if (DEBUG_HACKS) { - for (int i = 0; i < mNextNumPointers; i++) { - int ioff = i * MotionEvent.NUM_SAMPLE_DATA; - Slog.d("InputDevice", "Pointer " + i + " replaced (" + - mNextData[ioff + MotionEvent.SAMPLE_X] + ", " + - mNextData[ioff + MotionEvent.SAMPLE_Y] + ")"); - } - } - } else { - mJumpyPointsDropped = 0; - - if (DEBUG_HACKS) { - Slog.d("InputDevice", "Transition - drop limit reset"); - } - } - return; - } - - // 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 (nextNumPointers < 2) { - return; - } - - int badPointerIndex = -1; - int badPointerReplaceXWith = 0; - int badPointerReplaceYWith = 0; - int badPointerDistance = Integer.MIN_VALUE; - for (int i = nextNumPointers - 1; i >= 0; i--) { - boolean dropx = false; - boolean dropy = false; - - // Limit how many times a jumpy point can get dropped. - if (mJumpyPointsDropped < JUMPY_DROP_LIMIT) { - final int ioff = i * MotionEvent.NUM_SAMPLE_DATA; - final int x = nextData[ioff + MotionEvent.SAMPLE_X]; - final int y = nextData[ioff + MotionEvent.SAMPLE_Y]; - - if (DEBUG_HACKS) { - Slog.d("InputDevice", "Point " + i + " (" + x + ", " + y + ")"); - } - - // Check if a touch point is too close to another's coordinates - for (int j = 0; j < nextNumPointers && !dropx && !dropy; j++) { - if (j == i) { - continue; - } - - final int joff = j * MotionEvent.NUM_SAMPLE_DATA; - final int xOther = nextData[joff + MotionEvent.SAMPLE_X]; - final int yOther = nextData[joff + MotionEvent.SAMPLE_Y]; - - dropx = Math.abs(x - xOther) <= jumpyEpsilon; - dropy = Math.abs(y - yOther) <= jumpyEpsilon; - } - - if (dropx) { - int xreplace = lastData[MotionEvent.SAMPLE_X]; - int yreplace = lastData[MotionEvent.SAMPLE_Y]; - int distance = Math.abs(yreplace - y); - for (int j = 1; j < lastNumPointers; j++) { - final int joff = j * MotionEvent.NUM_SAMPLE_DATA; - int lasty = lastData[joff + MotionEvent.SAMPLE_Y]; - int currDist = Math.abs(lasty - y); - if (currDist < distance) { - xreplace = lastData[joff + MotionEvent.SAMPLE_X]; - yreplace = lasty; - distance = currDist; - } - } - - int badXDelta = Math.abs(xreplace - x); - if (badXDelta > badPointerDistance) { - badPointerDistance = badXDelta; - badPointerIndex = i; - badPointerReplaceXWith = xreplace; - badPointerReplaceYWith = yreplace; - } - } else if (dropy) { - int xreplace = lastData[MotionEvent.SAMPLE_X]; - int yreplace = lastData[MotionEvent.SAMPLE_Y]; - int distance = Math.abs(xreplace - x); - for (int j = 1; j < lastNumPointers; j++) { - final int joff = j * MotionEvent.NUM_SAMPLE_DATA; - int lastx = lastData[joff + MotionEvent.SAMPLE_X]; - int currDist = Math.abs(lastx - x); - if (currDist < distance) { - xreplace = lastx; - yreplace = lastData[joff + MotionEvent.SAMPLE_Y]; - distance = currDist; - } - } - - int badYDelta = Math.abs(yreplace - y); - if (badYDelta > badPointerDistance) { - badPointerDistance = badYDelta; - badPointerIndex = i; - badPointerReplaceXWith = xreplace; - badPointerReplaceYWith = yreplace; - } - } - } - } - if (badPointerIndex >= 0) { - if (DEBUG_HACKS) { - Slog.d("InputDevice", "Replacing bad pointer " + badPointerIndex + - " with (" + badPointerReplaceXWith + ", " + badPointerReplaceYWith + - ")"); - } - - final int offset = badPointerIndex * MotionEvent.NUM_SAMPLE_DATA; - nextData[offset + MotionEvent.SAMPLE_X] = badPointerReplaceXWith; - nextData[offset + MotionEvent.SAMPLE_Y] = badPointerReplaceYWith; - mJumpyPointsDropped++; - } else { - mJumpyPointsDropped = 0; - } - } - - /** - * 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. - */ - int[] generateAveragedData(int upOrDownPointer, int lastNumPointers, - int nextNumPointers) { - final int numPointers = mLastNumPointers; - final int[] rawData = mLastData; - if (DEBUG_HACKS) Slog.v("InputDevice", "lastNumPointers=" + lastNumPointers - + " nextNumPointers=" + nextNumPointers - + " numPointers=" + numPointers); - for (int i=0; i<numPointers; i++) { - final int ioff = i * MotionEvent.NUM_SAMPLE_DATA; - // We keep the average data in offsets based on the pointer - // ID, so we don't need to move it around as fingers are - // pressed and released. - final int p = mPointerIds[i]; - final int poff = p * MotionEvent.NUM_SAMPLE_DATA * HISTORY_SIZE; - if (i == upOrDownPointer && lastNumPointers != nextNumPointers) { - if (lastNumPointers < nextNumPointers) { - // This pointer is going down. Clear its history - // and start fresh. - if (DEBUG_HACKS) Slog.v("InputDevice", "Pointer down @ index " - + upOrDownPointer + " id " + mPointerIds[i]); - mHistoryDataStart[i] = 0; - mHistoryDataEnd[i] = 0; - System.arraycopy(rawData, ioff, mHistoryData, poff, - MotionEvent.NUM_SAMPLE_DATA); - System.arraycopy(rawData, ioff, mAveragedData, ioff, - MotionEvent.NUM_SAMPLE_DATA); - continue; - } else { - // The pointer is going up. Just fall through to - // recompute the last averaged point (and don't add - // it as a new point to include in the average). - if (DEBUG_HACKS) Slog.v("InputDevice", "Pointer up @ index " - + upOrDownPointer + " id " + mPointerIds[i]); - } - } else { - int end = mHistoryDataEnd[i]; - int eoff = poff + (end*MotionEvent.NUM_SAMPLE_DATA); - int oldX = mHistoryData[eoff + MotionEvent.SAMPLE_X]; - int oldY = mHistoryData[eoff + MotionEvent.SAMPLE_Y]; - int newX = rawData[ioff + MotionEvent.SAMPLE_X]; - int newY = rawData[ioff + MotionEvent.SAMPLE_Y]; - int dx = newX-oldX; - int dy = newY-oldY; - int delta = dx*dx + dy*dy; - if (DEBUG_HACKS) Slog.v("InputDevice", "Delta from last: " + delta); - if (delta >= (75*75)) { - // Magic number, if moving farther than this, turn - // off filtering to avoid lag in response. - mHistoryDataStart[i] = 0; - mHistoryDataEnd[i] = 0; - System.arraycopy(rawData, ioff, mHistoryData, poff, - MotionEvent.NUM_SAMPLE_DATA); - System.arraycopy(rawData, ioff, mAveragedData, ioff, - MotionEvent.NUM_SAMPLE_DATA); - continue; - } else { - end++; - if (end >= HISTORY_SIZE) { - end -= HISTORY_SIZE; - } - mHistoryDataEnd[i] = end; - int noff = poff + (end*MotionEvent.NUM_SAMPLE_DATA); - mHistoryData[noff + MotionEvent.SAMPLE_X] = newX; - mHistoryData[noff + MotionEvent.SAMPLE_Y] = newY; - mHistoryData[noff + MotionEvent.SAMPLE_PRESSURE] - = rawData[ioff + MotionEvent.SAMPLE_PRESSURE]; - int start = mHistoryDataStart[i]; - if (end == start) { - start++; - if (start >= HISTORY_SIZE) { - start -= HISTORY_SIZE; - } - mHistoryDataStart[i] = start; - } - } - } - - // Now compute the average. - int start = mHistoryDataStart[i]; - int end = mHistoryDataEnd[i]; - int x=0, y=0; - int totalPressure = 0; - while (start != end) { - int soff = poff + (start*MotionEvent.NUM_SAMPLE_DATA); - int pressure = mHistoryData[soff + MotionEvent.SAMPLE_PRESSURE]; - if (pressure <= 0) pressure = 1; - x += mHistoryData[soff + MotionEvent.SAMPLE_X] * pressure; - y += mHistoryData[soff + MotionEvent.SAMPLE_Y] * pressure; - totalPressure += pressure; - start++; - if (start >= HISTORY_SIZE) start = 0; - } - int eoff = poff + (end*MotionEvent.NUM_SAMPLE_DATA); - int pressure = mHistoryData[eoff + MotionEvent.SAMPLE_PRESSURE]; - if (pressure <= 0) pressure = 1; - x += mHistoryData[eoff + MotionEvent.SAMPLE_X] * pressure; - y += mHistoryData[eoff + MotionEvent.SAMPLE_Y] * pressure; - totalPressure += pressure; - x /= totalPressure; - y /= totalPressure; - if (DEBUG_HACKS) Slog.v("InputDevice", "Averaging " + totalPressure - + " weight: (" + x + "," + y + ")"); - mAveragedData[ioff + MotionEvent.SAMPLE_X] = x; - mAveragedData[ioff + MotionEvent.SAMPLE_Y] = y; - mAveragedData[ioff + MotionEvent.SAMPLE_PRESSURE] = - rawData[ioff + MotionEvent.SAMPLE_PRESSURE]; - mAveragedData[ioff + MotionEvent.SAMPLE_SIZE] = - rawData[ioff + MotionEvent.SAMPLE_SIZE]; - } - return mAveragedData; - } - - private boolean assignPointer(int nextIndex, boolean allowOverlap) { - final int lastNumPointers = mLastNumPointers; - final int[] next2Last = mNext2Last; - final long[] next2LastDistance = mNext2LastDistance; - final int[] last2Next = mLast2Next; - final int[] lastData = mLastData; - final int[] nextData = mNextData; - final int id = nextIndex * MotionEvent.NUM_SAMPLE_DATA; - - if (DEBUG_POINTERS) Slog.v("InputDevice", "assignPointer: nextIndex=" - + nextIndex + " dataOff=" + id); - final int x1 = nextData[id + MotionEvent.SAMPLE_X]; - final int y1 = nextData[id + MotionEvent.SAMPLE_Y]; - - long bestDistance = -1; - int bestIndex = -1; - for (int j=0; j<lastNumPointers; j++) { - // If we are not allowing multiple new points to be assigned - // to the same old pointer, then skip this one if it is already - // detected as a conflict (-2). - if (!allowOverlap && last2Next[j] < -1) { - continue; - } - final int jd = j * MotionEvent.NUM_SAMPLE_DATA; - final int xd = lastData[jd + MotionEvent.SAMPLE_X] - x1; - final int yd = lastData[jd + MotionEvent.SAMPLE_Y] - y1; - final long distance = xd*(long)xd + yd*(long)yd; - if (bestDistance == -1 || distance < bestDistance) { - bestDistance = distance; - bestIndex = j; - } - } - - if (DEBUG_POINTERS) Slog.v("InputDevice", "New index " + nextIndex - + " best old index=" + bestIndex + " (distance=" - + bestDistance + ")"); - next2Last[nextIndex] = bestIndex; - next2LastDistance[nextIndex] = bestDistance; - - if (bestIndex < 0) { - return true; - } - - if (last2Next[bestIndex] == -1) { - last2Next[bestIndex] = nextIndex; - return false; - } - - if (DEBUG_POINTERS) Slog.v("InputDevice", "Old index " + bestIndex - + " has multiple best new pointers!"); - - last2Next[bestIndex] = -2; - return true; - } - - private int updatePointerIdentifiers() { - final int[] lastData = mLastData; - final int[] nextData = mNextData; - final int nextNumPointers = mNextNumPointers; - final int lastNumPointers = mLastNumPointers; - - if (nextNumPointers == 1 && lastNumPointers == 1) { - System.arraycopy(nextData, 0, lastData, 0, - MotionEvent.NUM_SAMPLE_DATA); - return -1; - } - - // Clear our old state. - final int[] last2Next = mLast2Next; - for (int i=0; i<lastNumPointers; i++) { - last2Next[i] = -1; - } - - if (DEBUG_POINTERS) Slog.v("InputDevice", - "Update pointers: lastNumPointers=" + lastNumPointers - + " nextNumPointers=" + nextNumPointers); - - // Figure out the closes new points to the previous points. - final int[] next2Last = mNext2Last; - final long[] next2LastDistance = mNext2LastDistance; - boolean conflicts = false; - for (int i=0; i<nextNumPointers; i++) { - conflicts |= assignPointer(i, true); - } - - // Resolve ambiguities in pointer mappings, when two or more - // new pointer locations find their best previous location is - // the same. - if (conflicts) { - if (DEBUG_POINTERS) Slog.v("InputDevice", "Resolving conflicts"); - - for (int i=0; i<lastNumPointers; i++) { - if (last2Next[i] != -2) { - continue; - } - - // Note that this algorithm is far from perfect. Ideally - // we should do something like the one described at - // http://portal.acm.org/citation.cfm?id=997856 - - if (DEBUG_POINTERS) Slog.v("InputDevice", - "Resolving last index #" + i); - - int numFound; - do { - numFound = 0; - long worstDistance = 0; - int worstJ = -1; - for (int j=0; j<nextNumPointers; j++) { - if (next2Last[j] != i) { - continue; - } - numFound++; - if (worstDistance < next2LastDistance[j]) { - worstDistance = next2LastDistance[j]; - worstJ = j; - } - } - - if (worstJ >= 0) { - if (DEBUG_POINTERS) Slog.v("InputDevice", - "Worst new pointer: " + worstJ - + " (distance=" + worstDistance + ")"); - if (assignPointer(worstJ, false)) { - // In this case there is no last pointer - // remaining for this new one! - next2Last[worstJ] = -1; - } - } - } while (numFound > 2); - } - } - - int retIndex = -1; - - if (lastNumPointers < nextNumPointers) { - // We have one or more new pointers that are down. Create a - // new pointer identifier for one of them. - if (DEBUG_POINTERS) Slog.v("InputDevice", "Adding new pointer"); - int nextId = 0; - int i=0; - while (i < lastNumPointers) { - if (mPointerIds[i] > nextId) { - // Found a hole, insert the pointer here. - if (DEBUG_POINTERS) Slog.v("InputDevice", - "Inserting new pointer at hole " + i); - System.arraycopy(mPointerIds, i, mPointerIds, - i+1, lastNumPointers-i); - System.arraycopy(lastData, i*MotionEvent.NUM_SAMPLE_DATA, - lastData, (i+1)*MotionEvent.NUM_SAMPLE_DATA, - (lastNumPointers-i)*MotionEvent.NUM_SAMPLE_DATA); - System.arraycopy(next2Last, i, next2Last, - i+1, lastNumPointers-i); - break; - } - i++; - nextId++; - } - - if (DEBUG_POINTERS) Slog.v("InputDevice", - "New pointer id " + nextId + " at index " + i); - - mLastNumPointers++; - retIndex = i; - mPointerIds[i] = nextId; - - // And assign this identifier to the first new pointer. - for (int j=0; j<nextNumPointers; j++) { - if (next2Last[j] < 0) { - if (DEBUG_POINTERS) Slog.v("InputDevice", - "Assigning new id to new pointer index " + j); - next2Last[j] = i; - break; - } - } - } - - // Propagate all of the current data into the appropriate - // location in the old data to match the pointer ID that was - // assigned to it. - for (int i=0; i<nextNumPointers; i++) { - int lastIndex = next2Last[i]; - if (lastIndex >= 0) { - if (DEBUG_POINTERS) Slog.v("InputDevice", - "Copying next pointer index " + i - + " to last index " + lastIndex); - System.arraycopy(nextData, i*MotionEvent.NUM_SAMPLE_DATA, - lastData, lastIndex*MotionEvent.NUM_SAMPLE_DATA, - MotionEvent.NUM_SAMPLE_DATA); - } - } - - if (lastNumPointers > nextNumPointers) { - // One or more pointers has gone up. Find the first one, - // and adjust accordingly. - if (DEBUG_POINTERS) Slog.v("InputDevice", "Removing old pointer"); - for (int i=0; i<lastNumPointers; i++) { - if (last2Next[i] == -1) { - if (DEBUG_POINTERS) Slog.v("InputDevice", - "Removing old pointer at index " + i); - retIndex = i; - break; - } - } - } - - return retIndex; - } - - void removeOldPointer(int index) { - final int lastNumPointers = mLastNumPointers; - if (index >= 0 && index < lastNumPointers) { - System.arraycopy(mPointerIds, index+1, mPointerIds, - index, lastNumPointers-index-1); - System.arraycopy(mLastData, (index+1)*MotionEvent.NUM_SAMPLE_DATA, - mLastData, (index)*MotionEvent.NUM_SAMPLE_DATA, - (lastNumPointers-index-1)*MotionEvent.NUM_SAMPLE_DATA); - mLastNumPointers--; - } - } - - MotionEvent generateAbsMotion(InputDevice device, long curTime, - long curTimeNano, Display display, int orientation, - int metaState) { - - if (mSkipLastPointers) { - mSkipLastPointers = false; - mLastNumPointers = 0; - } - - if (mNextNumPointers <= 0 && mLastNumPointers <= 0) { - return null; - } - - final int lastNumPointers = mLastNumPointers; - final int nextNumPointers = mNextNumPointers; - if (mNextNumPointers > MAX_POINTERS) { - Slog.w("InputDevice", "Number of pointers " + mNextNumPointers - + " exceeded maximum of " + MAX_POINTERS); - mNextNumPointers = MAX_POINTERS; - } - - int upOrDownPointer = updatePointerIdentifiers(); - - final float[] reportData = mReportData; - final int[] rawData; - if (KeyInputQueue.BAD_TOUCH_HACK) { - rawData = generateAveragedData(upOrDownPointer, lastNumPointers, - nextNumPointers); - } else { - rawData = mLastData; - } - - final int numPointers = mLastNumPointers; - - if (DEBUG_POINTERS) Slog.v("InputDevice", "Processing " - + numPointers + " pointers (going from " + lastNumPointers - + " to " + nextNumPointers + ")"); - - for (int i=0; i<numPointers; i++) { - final int pos = i * MotionEvent.NUM_SAMPLE_DATA; - reportData[pos + MotionEvent.SAMPLE_X] = rawData[pos + MotionEvent.SAMPLE_X]; - reportData[pos + MotionEvent.SAMPLE_Y] = rawData[pos + MotionEvent.SAMPLE_Y]; - reportData[pos + MotionEvent.SAMPLE_PRESSURE] = rawData[pos + MotionEvent.SAMPLE_PRESSURE]; - reportData[pos + MotionEvent.SAMPLE_SIZE] = rawData[pos + MotionEvent.SAMPLE_SIZE]; - } - - int action; - int edgeFlags = 0; - if (nextNumPointers != lastNumPointers) { - if (nextNumPointers > lastNumPointers) { - if (lastNumPointers == 0) { - action = MotionEvent.ACTION_DOWN; - mDownTime = curTime; - } else { - action = MotionEvent.ACTION_POINTER_DOWN - | (upOrDownPointer << MotionEvent.ACTION_POINTER_INDEX_SHIFT); - } - } else { - if (numPointers == 1) { - action = MotionEvent.ACTION_UP; - } else { - action = MotionEvent.ACTION_POINTER_UP - | (upOrDownPointer << MotionEvent.ACTION_POINTER_INDEX_SHIFT); - } - } - currentMove = null; - } else { - action = MotionEvent.ACTION_MOVE; - } - - final int dispW = display.getWidth()-1; - final int dispH = display.getHeight()-1; - int w = dispW; - int h = dispH; - if (orientation == Surface.ROTATION_90 - || orientation == Surface.ROTATION_270) { - int tmp = w; - w = h; - h = tmp; - } - - final AbsoluteInfo absX = device.absX; - final AbsoluteInfo absY = device.absY; - final AbsoluteInfo absPressure = device.absPressure; - final AbsoluteInfo absSize = device.absSize; - for (int i=0; i<numPointers; i++) { - final int j = i * MotionEvent.NUM_SAMPLE_DATA; - - if (absX != null) { - reportData[j + MotionEvent.SAMPLE_X] = - ((reportData[j + MotionEvent.SAMPLE_X]-absX.minValue) - / absX.range) * w; - } - if (absY != null) { - reportData[j + MotionEvent.SAMPLE_Y] = - ((reportData[j + MotionEvent.SAMPLE_Y]-absY.minValue) - / absY.range) * h; - } - if (absPressure != null) { - reportData[j + MotionEvent.SAMPLE_PRESSURE] = - ((reportData[j + MotionEvent.SAMPLE_PRESSURE]-absPressure.minValue) - / (float)absPressure.range); - } - if (absSize != null) { - reportData[j + MotionEvent.SAMPLE_SIZE] = - ((reportData[j + MotionEvent.SAMPLE_SIZE]-absSize.minValue) - / (float)absSize.range); - } - - switch (orientation) { - case Surface.ROTATION_90: { - final float temp = reportData[j + MotionEvent.SAMPLE_X]; - reportData[j + MotionEvent.SAMPLE_X] = reportData[j + MotionEvent.SAMPLE_Y]; - reportData[j + MotionEvent.SAMPLE_Y] = w-temp; - break; - } - case Surface.ROTATION_180: { - reportData[j + MotionEvent.SAMPLE_X] = w-reportData[j + MotionEvent.SAMPLE_X]; - reportData[j + MotionEvent.SAMPLE_Y] = h-reportData[j + MotionEvent.SAMPLE_Y]; - break; - } - case Surface.ROTATION_270: { - final float temp = reportData[j + MotionEvent.SAMPLE_X]; - reportData[j + MotionEvent.SAMPLE_X] = h-reportData[j + MotionEvent.SAMPLE_Y]; - reportData[j + MotionEvent.SAMPLE_Y] = temp; - break; - } - } - } - - // We only consider the first pointer when computing the edge - // flags, since they are global to the event. - if (action == MotionEvent.ACTION_DOWN) { - if (reportData[MotionEvent.SAMPLE_X] <= 0) { - edgeFlags |= MotionEvent.EDGE_LEFT; - } else if (reportData[MotionEvent.SAMPLE_X] >= dispW) { - edgeFlags |= MotionEvent.EDGE_RIGHT; - } - if (reportData[MotionEvent.SAMPLE_Y] <= 0) { - edgeFlags |= MotionEvent.EDGE_TOP; - } else if (reportData[MotionEvent.SAMPLE_Y] >= dispH) { - edgeFlags |= MotionEvent.EDGE_BOTTOM; - } - } - - if (currentMove != null) { - if (false) Slog.i("InputDevice", "Adding batch x=" - + reportData[MotionEvent.SAMPLE_X] - + " y=" + reportData[MotionEvent.SAMPLE_Y] - + " to " + currentMove); - currentMove.addBatch(curTime, reportData, metaState); - if (WindowManagerPolicy.WATCH_POINTER) { - Slog.i("KeyInputQueue", "Updating: " + currentMove); - } - return null; - } - - MotionEvent me = MotionEvent.obtainNano(mDownTime, curTime, - curTimeNano, action, numPointers, mPointerIds, reportData, - metaState, xPrecision, yPrecision, device.id, edgeFlags); - if (action == MotionEvent.ACTION_MOVE) { - currentMove = me; - } - - if (nextNumPointers < lastNumPointers) { - removeOldPointer(upOrDownPointer); - } - - return me; - } - - boolean hasMore() { - return mLastNumPointers != mNextNumPointers; - } - - void finish() { - mNextNumPointers = mAddingPointerOffset = 0; - mNextData[MotionEvent.SAMPLE_PRESSURE] = 0; - } - - MotionEvent generateRelMotion(InputDevice device, long curTime, - long curTimeNano, int orientation, int metaState) { - - final float[] scaled = mReportData; - - // For now we only support 1 pointer with relative motions. - scaled[MotionEvent.SAMPLE_X] = mNextData[MotionEvent.SAMPLE_X]; - scaled[MotionEvent.SAMPLE_Y] = mNextData[MotionEvent.SAMPLE_Y]; - scaled[MotionEvent.SAMPLE_PRESSURE] = 1.0f; - scaled[MotionEvent.SAMPLE_SIZE] = 0; - int edgeFlags = 0; - - int action; - if (mNextNumPointers != mLastNumPointers) { - mNextData[MotionEvent.SAMPLE_X] = - mNextData[MotionEvent.SAMPLE_Y] = 0; - if (mNextNumPointers > 0 && mLastNumPointers == 0) { - action = MotionEvent.ACTION_DOWN; - mDownTime = curTime; - } else if (mNextNumPointers == 0) { - action = MotionEvent.ACTION_UP; - } else { - action = MotionEvent.ACTION_MOVE; - } - mLastNumPointers = mNextNumPointers; - currentMove = null; - } else { - action = MotionEvent.ACTION_MOVE; - } - - scaled[MotionEvent.SAMPLE_X] *= xMoveScale; - scaled[MotionEvent.SAMPLE_Y] *= yMoveScale; - switch (orientation) { - case Surface.ROTATION_90: { - final float temp = scaled[MotionEvent.SAMPLE_X]; - scaled[MotionEvent.SAMPLE_X] = scaled[MotionEvent.SAMPLE_Y]; - scaled[MotionEvent.SAMPLE_Y] = -temp; - break; - } - case Surface.ROTATION_180: { - scaled[MotionEvent.SAMPLE_X] = -scaled[MotionEvent.SAMPLE_X]; - scaled[MotionEvent.SAMPLE_Y] = -scaled[MotionEvent.SAMPLE_Y]; - break; - } - case Surface.ROTATION_270: { - final float temp = scaled[MotionEvent.SAMPLE_X]; - scaled[MotionEvent.SAMPLE_X] = -scaled[MotionEvent.SAMPLE_Y]; - scaled[MotionEvent.SAMPLE_Y] = temp; - break; - } - } - - if (currentMove != null) { - if (false) Slog.i("InputDevice", "Adding batch x=" - + scaled[MotionEvent.SAMPLE_X] - + " y=" + scaled[MotionEvent.SAMPLE_Y] - + " to " + currentMove); - currentMove.addBatch(curTime, scaled, metaState); - if (WindowManagerPolicy.WATCH_POINTER) { - Slog.i("KeyInputQueue", "Updating: " + currentMove); - } - return null; - } - - MotionEvent me = MotionEvent.obtainNano(mDownTime, curTime, - curTimeNano, action, 1, mPointerIds, scaled, metaState, - xPrecision, yPrecision, device.id, edgeFlags); - if (action == MotionEvent.ACTION_MOVE) { - currentMove = me; - } - return me; - } - } - - static class AbsoluteInfo { - int minValue; - int maxValue; - int range; - int flat; - int fuzz; - - final void dump(PrintWriter pw) { - pw.print("minValue="); pw.print(minValue); - pw.print(" maxValue="); pw.print(maxValue); - pw.print(" range="); pw.print(range); - pw.print(" flat="); pw.print(flat); - pw.print(" fuzz="); pw.print(fuzz); - } - }; - - InputDevice(int _id, int _classes, String _name, - AbsoluteInfo _absX, AbsoluteInfo _absY, - AbsoluteInfo _absPressure, AbsoluteInfo _absSize) { - id = _id; - classes = _classes; - name = _name; - absX = _absX; - absY = _absY; - absPressure = _absPressure; - absSize = _absSize; - } -}; diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java new file mode 100644 index 0000000..df41264 --- /dev/null +++ b/services/java/com/android/server/InputManager.java @@ -0,0 +1,536 @@ +/* + * 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.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.os.Environment; +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.Surface; + +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. + */ +public class InputManager { + static final String TAG = "InputManager"; + + private static final boolean DEBUG = false; + + private final Callbacks mCallbacks; + private final Context mContext; + private final WindowManagerService mWindowManagerService; + + private static native void nativeInit(Callbacks callbacks); + 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); + + private static native int nativeGetScanCodeState(int deviceId, int sourceMask, + int scanCode); + private static native int nativeGetKeyCodeState(int deviceId, int sourceMask, + int keyCode); + private static native int nativeGetSwitchState(int deviceId, int sourceMask, + int sw); + private static native boolean nativeHasKeys(int deviceId, int sourceMask, + int[] keyCodes, boolean[] keyExists); + private static native void nativeRegisterInputChannel(InputChannel inputChannel, + 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 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 String nativeDump(); + + // Input event injection constants defined in InputDispatcher.h. + static final int INPUT_EVENT_INJECTION_SUCCEEDED = 0; + static final int INPUT_EVENT_INJECTION_PERMISSION_DENIED = 1; + static final int INPUT_EVENT_INJECTION_FAILED = 2; + static final int INPUT_EVENT_INJECTION_TIMED_OUT = 3; + + // Input event injection synchronization modes defined in InputDispatcher.h + static final int INPUT_EVENT_INJECTION_SYNC_NONE = 0; + static final int INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT = 1; + static final int INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH = 2; + + // Key states (may be returned by queries about the current state of a + // particular key code, scan code or switch). + + /** The key state is unknown or the requested key itself is not supported. */ + public static final int KEY_STATE_UNKNOWN = -1; + + /** The key is up. /*/ + public static final int KEY_STATE_UP = 0; + + /** The key is down. */ + public static final int KEY_STATE_DOWN = 1; + + /** The key is down but is a virtual key press that is being emulated by the system. */ + public static final int KEY_STATE_VIRTUAL = 2; + + public InputManager(Context context, WindowManagerService windowManagerService) { + this.mContext = context; + this.mWindowManagerService = windowManagerService; + + this.mCallbacks = new Callbacks(); + + init(); + } + + private void init() { + Slog.i(TAG, "Initializing input manager"); + nativeInit(mCallbacks); + } + + public void start() { + Slog.i(TAG, "Starting input manager"); + nativeStart(); + } + + public void setDisplaySize(int displayId, int width, int height) { + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("Invalid display id or dimensions."); + } + + if (DEBUG) { + Slog.d(TAG, "Setting display #" + displayId + " size to " + width + "x" + height); + } + nativeSetDisplaySize(displayId, width, height); + } + + public void setDisplayOrientation(int displayId, int rotation) { + if (rotation < Surface.ROTATION_0 || rotation > Surface.ROTATION_270) { + throw new IllegalArgumentException("Invalid rotation."); + } + + if (DEBUG) { + Slog.d(TAG, "Setting display #" + displayId + " orientation to " + rotation); + } + nativeSetDisplayOrientation(displayId, rotation); + } + + public void getInputConfiguration(Configuration config) { + if (config == null) { + throw new IllegalArgumentException("config must not be null."); + } + + nativeGetInputConfiguration(config); + } + + /** + * Gets the current state of a key or button by key code. + * @param deviceId The input device id, or -1 to consult all devices. + * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to + * consider all input sources. An input device is consulted if at least one of its + * non-class input source bits matches the specified source mask. + * @param keyCode The key code to check. + * @return The key state. + */ + public int getKeyCodeState(int deviceId, int sourceMask, int keyCode) { + return nativeGetKeyCodeState(deviceId, sourceMask, keyCode); + } + + /** + * Gets the current state of a key or button by scan code. + * @param deviceId The input device id, or -1 to consult all devices. + * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to + * consider all input sources. An input device is consulted if at least one of its + * non-class input source bits matches the specified source mask. + * @param scanCode The scan code to check. + * @return The key state. + */ + public int getScanCodeState(int deviceId, int sourceMask, int scanCode) { + return nativeGetScanCodeState(deviceId, sourceMask, scanCode); + } + + /** + * Gets the current state of a switch by switch code. + * @param deviceId The input device id, or -1 to consult all devices. + * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to + * consider all input sources. An input device is consulted if at least one of its + * non-class input source bits matches the specified source mask. + * @param switchCode The switch code to check. + * @return The switch state. + */ + public int getSwitchState(int deviceId, int sourceMask, int switchCode) { + return nativeGetSwitchState(deviceId, sourceMask, switchCode); + } + + /** + * Determines whether the specified key codes are supported by a particular device. + * @param deviceId The input device id, or -1 to consult all devices. + * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to + * consider all input sources. An input device is consulted if at least one of its + * non-class input source bits matches the specified source mask. + * @param keyCodes The array of key codes to check. + * @param keyExists An array at least as large as keyCodes whose entries will be set + * to true or false based on the presence or absence of support for the corresponding + * key codes. + * @return True if the lookup was successful, false otherwise. + */ + public boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists) { + if (keyCodes == null) { + throw new IllegalArgumentException("keyCodes must not be null."); + } + if (keyExists == null || keyExists.length < keyCodes.length) { + throw new IllegalArgumentException("keyExists must not be null and must be at " + + "least as large as keyCodes."); + } + + return nativeHasKeys(deviceId, sourceMask, keyCodes, keyExists); + } + + /** + * Creates an input channel that will receive all input from the input dispatcher. + * @param inputChannelName The input channel name. + * @return The input channel. + */ + public InputChannel monitorInput(String inputChannelName) { + if (inputChannelName == null) { + throw new IllegalArgumentException("inputChannelName must not be null."); + } + + InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName); + nativeRegisterInputChannel(inputChannels[0], true); + inputChannels[0].dispose(); // don't need to retain the Java object reference + return inputChannels[1]; + } + + /** + * Registers an input channel so that it can be used as an input event target. + * @param inputChannel The input channel to register. + */ + public void registerInputChannel(InputChannel inputChannel) { + if (inputChannel == null) { + throw new IllegalArgumentException("inputChannel must not be null."); + } + + nativeRegisterInputChannel(inputChannel, false); + } + + /** + * Unregisters an input channel. + * @param inputChannel The input channel to unregister. + */ + public void unregisterInputChannel(InputChannel inputChannel) { + if (inputChannel == null) { + throw new IllegalArgumentException("inputChannel must not be null."); + } + + nativeUnregisterInputChannel(inputChannel); + } + + /** + * Injects an input event into the event system on behalf of an application. + * The synchronization mode determines whether the method blocks while waiting for + * input injection to proceed. + * + * {@link #INPUT_EVENT_INJECTION_SYNC_NONE} never blocks. Injection is asynchronous and + * is assumed always to be successful. + * + * {@link #INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT} waits for previous events to be + * dispatched so that the input dispatcher can determine whether input event injection will + * be permitted based on the current input focus. Does not wait for the input event to + * finish processing. + * + * {@link #INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH} waits for the input event to + * be completely processed. + * + * @param event The event to inject. + * @param injectorPid The pid of the injecting application. + * @param injectorUid The uid of the injecting application. + * @param syncMode The synchronization mode. + * @param timeoutMillis The injection timeout in milliseconds. + * @return One of the INPUT_EVENT_INJECTION_XXX constants. + */ + public int injectInputEvent(InputEvent event, int injectorPid, int injectorUid, + int syncMode, int timeoutMillis) { + if (event == null) { + throw new IllegalArgumentException("event must not be null"); + } + if (injectorPid < 0 || injectorUid < 0) { + throw new IllegalArgumentException("injectorPid and injectorUid must not be negative."); + } + if (timeoutMillis <= 0) { + throw new IllegalArgumentException("timeoutMillis must be positive"); + } + + return nativeInjectInputEvent(event, injectorPid, injectorUid, syncMode, timeoutMillis); + } + + /** + * Gets information about the input device with the specified id. + * @param id The device id. + * @return The input device or null if not found. + */ + public InputDevice getInputDevice(int deviceId) { + return nativeGetInputDevice(deviceId); + } + + /** + * Gets the ids of all input devices in the system. + * @return The input device ids. + */ + public int[] getInputDeviceIds() { + return nativeGetInputDeviceIds(); + } + + public void setInputWindows(InputWindow[] windows) { + nativeSetInputWindows(windows); + } + + public void setFocusedApplication(InputApplication application) { + nativeSetFocusedApplication(application); + } + + public void setInputDispatchMode(boolean enabled, boolean frozen) { + nativeSetInputDispatchMode(enabled, frozen); + } + + 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; + } + + /* + * Callbacks from native. + */ + private class Callbacks { + static final String TAG = "InputManager-Callbacks"; + + private static final boolean DEBUG_VIRTUAL_KEYS = false; + private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml"; + private static final String CALIBRATION_DIR_PATH = "usr/idc/"; + + @SuppressWarnings("unused") + public void notifyConfigurationChanged(long whenNanos) { + mWindowManagerService.sendNewConfiguration(); + } + + @SuppressWarnings("unused") + public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) { + mWindowManagerService.mInputMonitor.notifyLidSwitchChanged(whenNanos, lidOpen); + } + + @SuppressWarnings("unused") + public void notifyInputChannelBroken(InputChannel inputChannel) { + mWindowManagerService.mInputMonitor.notifyInputChannelBroken(inputChannel); + } + + @SuppressWarnings("unused") + public long notifyANR(Object token, InputChannel inputChannel) { + return mWindowManagerService.mInputMonitor.notifyANR(token, inputChannel); + } + + @SuppressWarnings("unused") + public int interceptKeyBeforeQueueing(long whenNanos, int keyCode, boolean down, + int policyFlags, boolean isScreenOn) { + return mWindowManagerService.mInputMonitor.interceptKeyBeforeQueueing( + whenNanos, keyCode, down, policyFlags, isScreenOn); + } + + @SuppressWarnings("unused") + public boolean interceptKeyBeforeDispatching(InputChannel focus, int action, + int flags, int keyCode, int metaState, int repeatCount, int policyFlags) { + return mWindowManagerService.mInputMonitor.interceptKeyBeforeDispatching(focus, + action, flags, keyCode, metaState, repeatCount, policyFlags); + } + + @SuppressWarnings("unused") + public boolean checkInjectEventsPermission(int injectorPid, int injectorUid) { + return mContext.checkPermission( + android.Manifest.permission.INJECT_EVENTS, injectorPid, injectorUid) + == PackageManager.PERMISSION_GRANTED; + } + + @SuppressWarnings("unused") + public boolean filterTouchEvents() { + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_filterTouchEvents); + } + + @SuppressWarnings("unused") + public boolean filterJumpyTouchEvents() { + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_filterJumpyTouchEvents); + } + + @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>(); + + // Read partner-provided list of excluded input devices + XmlPullParser parser = null; + // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system". + File confFile = new File(Environment.getRootDirectory(), EXCLUDED_DEVICES_PATH); + FileReader confreader = null; + try { + confreader = new FileReader(confFile); + parser = Xml.newPullParser(); + parser.setInput(confreader); + XmlUtils.beginDocument(parser, "devices"); + + while (true) { + XmlUtils.nextElement(parser); + if (!"device".equals(parser.getName())) { + break; + } + String name = parser.getAttributeValue(null, "name"); + if (name != null) { + names.add(name); + } + } + } catch (FileNotFoundException e) { + // It's ok if the file does not exist. + } catch (Exception e) { + Slog.e(TAG, "Exception while parsing '" + confFile.getAbsolutePath() + "'", e); + } finally { + try { if (confreader != null) confreader.close(); } catch (IOException e) { } + } + + return names.toArray(new String[names.size()]); + } + + @SuppressWarnings("unused") + public int getMaxEventsPerSecond() { + int result = 0; + try { + result = Integer.parseInt(SystemProperties.get("windowsmgr.max_events_per_sec")); + } catch (NumberFormatException e) { + } + if (result < 1) { + result = 60; + } + return result; + } + } +} diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index 5a995ae..ecad3cc 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -26,8 +26,7 @@ import com.android.internal.view.IInputMethodManager; import com.android.internal.view.IInputMethodSession; import com.android.internal.view.InputBindResult; -import com.android.server.status.IconData; -import com.android.server.status.StatusBarService; +import com.android.server.StatusBarManagerService; import org.xmlpull.v1.XmlPullParserException; @@ -113,9 +112,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final Context mContext; final Handler mHandler; final SettingsObserver mSettingsObserver; - final StatusBarService mStatusBar; - final IBinder mInputMethodIcon; - final IconData mInputMethodData; + final StatusBarManagerService mStatusBar; final IWindowManager mIWindowManager; final HandlerCaller mCaller; @@ -450,7 +447,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - public InputMethodManagerService(Context context, StatusBarService statusBar) { + public InputMethodManagerService(Context context, StatusBarManagerService statusBar) { mContext = context; mHandler = new Handler(this); mIWindowManager = IWindowManager.Stub.asInterface( @@ -470,9 +467,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mContext.registerReceiver(new ScreenOnOffReceiver(), screenOnOffFilt); mStatusBar = statusBar; - mInputMethodData = IconData.makeIcon("ime", null, 0, 0, 0); - mInputMethodIcon = statusBar.addIcon(mInputMethodData, null); - statusBar.setIconVisibility(mInputMethodIcon, false); + statusBar.setIconVisibility("ime", false); buildInputMethodListLocked(mMethodList, mMethodMap); @@ -480,8 +475,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mContext.getContentResolver(), Settings.Secure.ENABLED_INPUT_METHODS); Slog.i(TAG, "Enabled input methods: " + enabledStr); - if (enabledStr == null) { - Slog.i(TAG, "Enabled input methods has not been set, enabling all"); + 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"); InputMethodInfo defIm = null; StringBuilder sb = new StringBuilder(256); final int N = mMethodList.size(); @@ -915,7 +912,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mEnabledSession = null; mCurMethod = null; } - mStatusBar.setIconVisibility(mInputMethodIcon, false); + mStatusBar.setIconVisibility("ime", false); } public void onServiceDisconnected(ComponentName name) { @@ -939,23 +936,22 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } public void updateStatusIcon(IBinder token, String packageName, int iconId) { + int uid = Binder.getCallingUid(); long ident = Binder.clearCallingIdentity(); try { if (token == null || mCurToken != token) { - Slog.w(TAG, "Ignoring setInputMethod of token: " + token); + Slog.w(TAG, "Ignoring setInputMethod of uid " + uid + " token: " + token); return; } synchronized (mMethodMap) { if (iconId == 0) { if (DEBUG) Slog.d(TAG, "hide the small icon for the input method"); - mStatusBar.setIconVisibility(mInputMethodIcon, false); + mStatusBar.setIconVisibility("ime", false); } else if (packageName != null) { if (DEBUG) Slog.d(TAG, "show a small icon for the input method"); - mInputMethodData.iconId = iconId; - mInputMethodData.iconPackage = packageName; - mStatusBar.updateIcon(mInputMethodIcon, mInputMethodData, null); - mStatusBar.setIconVisibility(mInputMethodIcon, true); + mStatusBar.setIcon("ime", packageName, iconId, 0); + mStatusBar.setIconVisibility("ime", true); } } } finally { @@ -988,7 +984,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub void setInputMethodLocked(String id) { InputMethodInfo info = mMethodMap.get(id); if (info == null) { - throw new IllegalArgumentException("Unknown id: " + mCurMethodId); + throw new IllegalArgumentException("Unknown id: " + id); } if (id.equals(mCurMethodId)) { @@ -1015,6 +1011,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public boolean showSoftInput(IInputMethodClient client, int flags, ResultReceiver resultReceiver) { + int uid = Binder.getCallingUid(); long ident = Binder.clearCallingIdentity(); try { synchronized (mMethodMap) { @@ -1025,7 +1022,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // focus in the window manager, to allow this call to // be made before input is started in it. if (!mIWindowManager.inputMethodClientHasFocus(client)) { - Slog.w(TAG, "Ignoring showSoftInput of: " + client); + Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client); return false; } } catch (RemoteException e) { @@ -1079,6 +1076,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public boolean hideSoftInput(IInputMethodClient client, int flags, ResultReceiver resultReceiver) { + int uid = Binder.getCallingUid(); long ident = Binder.clearCallingIdentity(); try { synchronized (mMethodMap) { @@ -1089,7 +1087,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // focus in the window manager, to allow this call to // be made before input is started in it. if (!mIWindowManager.inputMethodClientHasFocus(client)) { - Slog.w(TAG, "Ignoring hideSoftInput of: " + client); + if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid " + + uid + ": " + client); return false; } } catch (RemoteException e) { @@ -1224,7 +1223,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub synchronized (mMethodMap) { if (mCurClient == null || client == null || mCurClient.client.asBinder() != client.asBinder()) { - Slog.w(TAG, "Ignoring showInputMethodDialogFromClient of: " + client); + Slog.w(TAG, "Ignoring showInputMethodDialogFromClient of uid " + + Binder.getCallingUid() + ": " + client); } mHandler.sendEmptyMessage(MSG_SHOW_IM_PICKER); @@ -1242,7 +1242,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub + android.Manifest.permission.WRITE_SECURE_SETTINGS); } } else if (mCurToken != token) { - Slog.w(TAG, "Ignoring setInputMethod of token: " + token); + Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid() + + " token: " + token); return; } @@ -1258,7 +1259,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public void hideMySoftInput(IBinder token, int flags) { synchronized (mMethodMap) { if (token == null || mCurToken != token) { - Slog.w(TAG, "Ignoring hideInputMethod of token: " + token); + if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid " + + Binder.getCallingUid() + " token: " + token); return; } long ident = Binder.clearCallingIdentity(); @@ -1273,7 +1275,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public void showMySoftInput(IBinder token, int flags) { synchronized (mMethodMap) { if (token == null || mCurToken != token) { - Slog.w(TAG, "Ignoring hideInputMethod of token: " + token); + Slog.w(TAG, "Ignoring showMySoftInput of uid " + + Binder.getCallingUid() + " token: " + token); return; } long ident = Binder.clearCallingIdentity(); @@ -1486,7 +1489,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub String defaultIme = Settings.Secure.getString(mContext .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); - if (!map.containsKey(defaultIme)) { + if (!TextUtils.isEmpty(defaultIme) && !map.containsKey(defaultIme)) { if (chooseNewDefaultIMELocked()) { updateFromSettingsLocked(); } @@ -1738,8 +1741,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub p.println(" sessionRequested=" + ci.sessionRequested); p.println(" curSession=" + ci.curSession); } - p.println(" mInputMethodIcon=" + mInputMethodIcon); - p.println(" mInputMethodData=" + mInputMethodData); p.println(" mCurMethodId=" + mCurMethodId); client = mCurClient; p.println(" mCurClient=" + client + " mCurSeq=" + mCurSeq); @@ -1758,24 +1759,28 @@ public class InputMethodManagerService extends IInputMethodManager.Stub p.println(" mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn); } + p.println(" "); if (client != null) { - p.println(" "); pw.flush(); try { client.client.asBinder().dump(fd, args); } catch (RemoteException e) { p.println("Input method client dead: " + e); } + } else { + p.println("No input method client."); } + p.println(" "); if (method != null) { - p.println(" "); pw.flush(); try { method.asBinder().dump(fd, args); } catch (RemoteException e) { p.println("Input method service dead: " + e); } + } else { + p.println("No input method service."); } } } diff --git a/services/java/com/android/server/InputWindow.java b/services/java/com/android/server/InputWindow.java new file mode 100644 index 0000000..befc770 --- /dev/null +++ b/services/java/com/android/server/InputWindow.java @@ -0,0 +1,83 @@ +/* + * 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.view.InputChannel; + +/** + * Describes input-related window properties for use by the input dispatcher. + * + * @hide + */ +public final class InputWindow { + // 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. + 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 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() { + inputChannel = null; + } +} diff --git a/services/java/com/android/server/InputWindowList.java b/services/java/com/android/server/InputWindowList.java new file mode 100644 index 0000000..1cbb2cc --- /dev/null +++ b/services/java/com/android/server/InputWindowList.java @@ -0,0 +1,89 @@ +/* + * 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; + + +/** + * A specialized list of window information objects backed by an array. + * + * This class is part of an InputManager optimization to avoid allocating objects and arrays + * unnecessarily. Internally, it keeps an array full of demand-allocated objects that it + * recycles each time the list is cleared. The used portion of the array is padded with a null. + * + * The contents of the list are intended to be Z-ordered from top to bottom. + * + * @hide + */ +public final class InputWindowList { + private InputWindow[] mArray; + private int mCount; + + /** + * Creates an empty list. + */ + public InputWindowList() { + mArray = new InputWindow[8]; + } + + /** + * Clears the list. + */ + public void clear() { + if (mCount == 0) { + return; + } + + int count = mCount; + mCount = 0; + mArray[count] = mArray[0]; + while (count > 0) { + count -= 1; + mArray[count].recycle(); + } + mArray[0] = null; + } + + /** + * Adds an uninitialized input window object to the list and returns it. + */ + public InputWindow add() { + if (mCount + 1 == mArray.length) { + InputWindow[] oldArray = mArray; + mArray = new InputWindow[oldArray.length * 2]; + System.arraycopy(oldArray, 0, mArray, 0, mCount); + } + + // Grab object from tail (after used section) if available. + InputWindow item = mArray[mCount + 1]; + if (item == null) { + item = new InputWindow(); + } + + mArray[mCount] = item; + mCount += 1; + mArray[mCount] = null; + return item; + } + + /** + * Gets the input window objects as a null-terminated array. + * @return The input window array. + */ + public InputWindow[] toNullTerminatedArray() { + return mArray; + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/Installer.java b/services/java/com/android/server/Installer.java index 2eaa58c..85eca60 100644 --- a/services/java/com/android/server/Installer.java +++ b/services/java/com/android/server/Installer.java @@ -166,11 +166,17 @@ class Installer { } } - public int install(String name, int uid, int gid) { + public int install(String name, boolean useEncryptedFilesystem, 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); @@ -203,33 +209,57 @@ class Installer { return execute(builder.toString()); } - public int remove(String name) { + public int remove(String name, boolean useEncryptedFilesystem) { 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) { + public int rename(String oldname, String newname, boolean useEncryptedFilesystem) { 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) { + public int deleteCacheFiles(String name, boolean useEncryptedFilesystem) { 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) { + public int clearUserData(String name, boolean useEncryptedFilesystem) { 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()); } @@ -263,7 +293,7 @@ class Installer { } public int getSizeInfo(String pkgName, String apkPath, - String fwdLockApkPath, PackageStats pStats) { + String fwdLockApkPath, PackageStats pStats, boolean useEncryptedFilesystem) { StringBuilder builder = new StringBuilder("getsize"); builder.append(' '); builder.append(pkgName); @@ -271,6 +301,13 @@ 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(" "); @@ -290,4 +327,33 @@ class Installer { public int moveFiles() { return execute("movefiles"); } + + public int linkNativeLibraryDirectory(String dataPath, String nativeLibPath) { + if (dataPath == null) { + Slog.e(TAG, "unlinkNativeLibraryDirectory dataPath is null"); + return -1; + } else if (nativeLibPath == null) { + Slog.e(TAG, "unlinkNativeLibraryDirectory nativeLibPath is null"); + return -1; + } + + StringBuilder builder = new StringBuilder("linklib "); + builder.append(dataPath); + builder.append(' '); + builder.append(nativeLibPath); + + return execute(builder.toString()); + } + + public int unlinkNativeLibraryDirectory(String dataPath) { + if (dataPath == null) { + Slog.e(TAG, "unlinkNativeLibraryDirectory dataPath is null"); + return -1; + } + + StringBuilder builder = new StringBuilder("unlinklib "); + builder.append(dataPath); + + return execute(builder.toString()); + } } diff --git a/services/java/com/android/server/IntentResolver.java b/services/java/com/android/server/IntentResolver.java index 8ab65e9..e47de13 100644 --- a/services/java/com/android/server/IntentResolver.java +++ b/services/java/com/android/server/IntentResolver.java @@ -28,6 +28,7 @@ import java.util.Map; import java.util.Set; import android.util.Log; +import android.util.PrintWriterPrinter; import android.util.Slog; import android.util.LogPrinter; import android.util.Printer; @@ -92,10 +93,12 @@ public class IntentResolver<F extends IntentFilter, R extends Object> { } boolean dumpMap(PrintWriter out, String titlePrefix, String title, - String prefix, Map<String, ArrayList<F>> map, String packageName) { + String prefix, Map<String, ArrayList<F>> map, String packageName, + boolean printFilter) { String eprefix = prefix + " "; String fprefix = prefix + " "; boolean printedSomething = false; + Printer printer = null; for (Map.Entry<String, ArrayList<F>> e : map.entrySet()) { ArrayList<F> a = e.getValue(); final int N = a.size(); @@ -115,37 +118,44 @@ public class IntentResolver<F extends IntentFilter, R extends Object> { } printedSomething = true; dumpFilter(out, fprefix, filter); + if (printFilter) { + if (printer == null) { + printer = new PrintWriterPrinter(out); + } + filter.dump(printer, fprefix + " "); + } } } return printedSomething; } - public boolean dump(PrintWriter out, String title, String prefix, String packageName) { + public boolean dump(PrintWriter out, String title, String prefix, String packageName, + boolean printFilter) { String innerPrefix = prefix + " "; String sepPrefix = "\n" + prefix; String curPrefix = title + "\n" + prefix; if (dumpMap(out, curPrefix, "Full MIME Types:", innerPrefix, - mTypeToFilter, packageName)) { + mTypeToFilter, packageName, printFilter)) { curPrefix = sepPrefix; } if (dumpMap(out, curPrefix, "Base MIME Types:", innerPrefix, - mBaseTypeToFilter, packageName)) { + mBaseTypeToFilter, packageName, printFilter)) { curPrefix = sepPrefix; } if (dumpMap(out, curPrefix, "Wild MIME Types:", innerPrefix, - mWildTypeToFilter, packageName)) { + mWildTypeToFilter, packageName, printFilter)) { curPrefix = sepPrefix; } if (dumpMap(out, curPrefix, "Schemes:", innerPrefix, - mSchemeToFilter, packageName)) { + mSchemeToFilter, packageName, printFilter)) { curPrefix = sepPrefix; } if (dumpMap(out, curPrefix, "Non-Data Actions:", innerPrefix, - mActionToFilter, packageName)) { + mActionToFilter, packageName, printFilter)) { curPrefix = sepPrefix; } if (dumpMap(out, curPrefix, "MIME Typed Actions:", innerPrefix, - mTypedActionToFilter, packageName)) { + mTypedActionToFilter, packageName, printFilter)) { curPrefix = sepPrefix; } return curPrefix == sepPrefix; diff --git a/services/java/com/android/server/KeyInputQueue.java b/services/java/com/android/server/KeyInputQueue.java deleted file mode 100644 index 6d42141..0000000 --- a/services/java/com/android/server/KeyInputQueue.java +++ /dev/null @@ -1,1386 +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.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.os.Environment; -import android.os.LatencyTimer; -import android.os.PowerManager; -import android.os.SystemClock; -import android.os.SystemProperties; -import android.util.Slog; -import android.util.SparseArray; -import android.util.Xml; -import android.view.Display; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.RawInputEvent; -import android.view.Surface; -import android.view.WindowManagerPolicy; - -import com.android.internal.util.XmlUtils; - -import org.xmlpull.v1.XmlPullParser; - -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; - -public abstract class KeyInputQueue { - static final String TAG = "KeyInputQueue"; - - static final boolean DEBUG = false; - static final boolean DEBUG_VIRTUAL_KEYS = false; - static final boolean DEBUG_POINTERS = false; - - /** - * Turn on some hacks we have to improve the touch interaction with a - * certain device whose screen currently is not all that good. - */ - static boolean BAD_TOUCH_HACK = false; - - /** - * Turn on some hacks to improve touch interaction with another device - * where touch coordinate data can get corrupted. - */ - static boolean JUMPY_TOUCH_HACK = false; - - private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml"; - - final SparseArray<InputDevice> mDevices = new SparseArray<InputDevice>(); - final SparseArray<InputDevice> mIgnoredDevices = new SparseArray<InputDevice>(); - final ArrayList<VirtualKey> mVirtualKeys = new ArrayList<VirtualKey>(); - final HapticFeedbackCallback mHapticFeedbackCallback; - - int mGlobalMetaState = 0; - boolean mHaveGlobalMetaState = false; - - final QueuedEvent mFirst; - final QueuedEvent mLast; - QueuedEvent mCache; - int mCacheCount; - - Display mDisplay = null; - int mDisplayWidth; - int mDisplayHeight; - - int mOrientation = Surface.ROTATION_0; - int[] mKeyRotationMap = null; - - VirtualKey mPressedVirtualKey = null; - - PowerManager.WakeLock mWakeLock; - - static final int[] KEY_90_MAP = new int[] { - KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT, - KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_DPAD_UP, - KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_LEFT, - KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_DOWN, - }; - - static final int[] KEY_180_MAP = new int[] { - KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_UP, - KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_DPAD_LEFT, - KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN, - KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT, - }; - - static final int[] KEY_270_MAP = new int[] { - KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_LEFT, - KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_UP, - KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_RIGHT, - KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_DPAD_DOWN, - }; - - public static final int FILTER_REMOVE = 0; - public static final int FILTER_KEEP = 1; - public static final int FILTER_ABORT = -1; - - private static final boolean MEASURE_LATENCY = false; - private LatencyTimer lt; - - public interface FilterCallback { - int filterEvent(QueuedEvent ev); - } - - public interface HapticFeedbackCallback { - void virtualKeyFeedback(KeyEvent event); - } - - static class QueuedEvent { - InputDevice inputDevice; - long whenNano; - int flags; // From the raw event - int classType; // One of the class constants in InputEvent - Object event; - boolean inQueue; - - void copyFrom(QueuedEvent that) { - this.inputDevice = that.inputDevice; - this.whenNano = that.whenNano; - this.flags = that.flags; - this.classType = that.classType; - this.event = that.event; - } - - @Override - public String toString() { - return "QueuedEvent{" - + Integer.toHexString(System.identityHashCode(this)) - + " " + event + "}"; - } - - // not copied - QueuedEvent prev; - QueuedEvent next; - } - - /** - * A key that exists as a part of the touch-screen, outside of the normal - * display area of the screen. - */ - static class VirtualKey { - int scancode; - int centerx; - int centery; - int width; - int height; - - int hitLeft; - int hitTop; - int hitRight; - int hitBottom; - - InputDevice lastDevice; - int lastKeycode; - - boolean checkHit(int x, int y) { - return (x >= hitLeft && x <= hitRight - && y >= hitTop && y <= hitBottom); - } - - void computeHitRect(InputDevice dev, int dw, int dh) { - if (dev == lastDevice) { - return; - } - - if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "computeHitRect for " + scancode - + ": dev=" + dev + " absX=" + dev.absX + " absY=" + dev.absY); - - lastDevice = dev; - - int minx = dev.absX.minValue; - int maxx = dev.absX.maxValue; - - int halfw = width/2; - int left = centerx - halfw; - int right = centerx + halfw; - hitLeft = minx + ((left*maxx-minx)/dw); - hitRight = minx + ((right*maxx-minx)/dw); - - int miny = dev.absY.minValue; - int maxy = dev.absY.maxValue; - - int halfh = height/2; - int top = centery - halfh; - int bottom = centery + halfh; - hitTop = miny + ((top*maxy-miny)/dh); - hitBottom = miny + ((bottom*maxy-miny)/dh); - } - } - - private void readVirtualKeys(String deviceName) { - 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]); - continue; - } - try { - VirtualKey sb = new VirtualKey(); - sb.scancode = Integer.parseInt(it[i+1]); - sb.centerx = Integer.parseInt(it[i+2]); - sb.centery = Integer.parseInt(it[i+3]); - sb.width = Integer.parseInt(it[i+4]); - sb.height = Integer.parseInt(it[i+5]); - if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Virtual key " - + sb.scancode + ": center=" + sb.centerx + "," - + sb.centery + " size=" + sb.width + "x" - + sb.height); - mVirtualKeys.add(sb); - } catch (NumberFormatException e) { - Slog.w(TAG, "Bad number at region " + i + " in: " - + str, e); - } - } - } - br.close(); - } catch (FileNotFoundException e) { - Slog.i(TAG, "No virtual keys found"); - } catch (IOException e) { - Slog.w(TAG, "Error reading virtual keys", e); - } - } - - private void readExcludedDevices() { - // Read partner-provided list of excluded input devices - XmlPullParser parser = null; - // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system". - File confFile = new File(Environment.getRootDirectory(), EXCLUDED_DEVICES_PATH); - FileReader confreader = null; - try { - confreader = new FileReader(confFile); - parser = Xml.newPullParser(); - parser.setInput(confreader); - XmlUtils.beginDocument(parser, "devices"); - - while (true) { - XmlUtils.nextElement(parser); - if (!"device".equals(parser.getName())) { - break; - } - String name = parser.getAttributeValue(null, "name"); - if (name != null) { - if (DEBUG) Slog.v(TAG, "addExcludedDevice " + name); - addExcludedDevice(name); - } - } - } catch (FileNotFoundException e) { - // It's ok if the file does not exist. - } catch (Exception e) { - Slog.e(TAG, "Exception while parsing '" + confFile.getAbsolutePath() + "'", e); - } finally { - try { if (confreader != null) confreader.close(); } catch (IOException e) { } - } - } - - KeyInputQueue(Context context, HapticFeedbackCallback hapticFeedbackCallback) { - if (MEASURE_LATENCY) { - lt = new LatencyTimer(100, 1000); - } - - Resources r = context.getResources(); - BAD_TOUCH_HACK = r.getBoolean(com.android.internal.R.bool.config_filterTouchEvents); - - JUMPY_TOUCH_HACK = r.getBoolean(com.android.internal.R.bool.config_filterJumpyTouchEvents); - - mHapticFeedbackCallback = hapticFeedbackCallback; - - readExcludedDevices(); - - PowerManager pm = (PowerManager)context.getSystemService( - Context.POWER_SERVICE); - mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, - "KeyInputQueue"); - mWakeLock.setReferenceCounted(false); - - mFirst = new QueuedEvent(); - mLast = new QueuedEvent(); - mFirst.next = mLast; - mLast.prev = mFirst; - } - - void start() { - mThread.start(); - } - - public void setDisplay(Display display) { - mDisplay = display; - - // We assume at this point that the display dimensions reflect the - // natural, unrotated display. We will perform hit tests for soft - // buttons based on that display. - mDisplayWidth = display.getWidth(); - mDisplayHeight = display.getHeight(); - } - - public void getInputConfiguration(Configuration config) { - synchronized (mFirst) { - config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; - config.keyboard = Configuration.KEYBOARD_NOKEYS; - config.navigation = Configuration.NAVIGATION_NONAV; - - final int N = mDevices.size(); - for (int i=0; i<N; i++) { - InputDevice d = mDevices.valueAt(i); - if (d != null) { - if ((d.classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) { - config.touchscreen - = Configuration.TOUCHSCREEN_FINGER; - //Slog.i("foo", "***** HAVE TOUCHSCREEN!"); - } - if ((d.classes&RawInputEvent.CLASS_ALPHAKEY) != 0) { - config.keyboard - = Configuration.KEYBOARD_QWERTY; - //Slog.i("foo", "***** HAVE QWERTY!"); - } - if ((d.classes&RawInputEvent.CLASS_TRACKBALL) != 0) { - config.navigation - = Configuration.NAVIGATION_TRACKBALL; - //Slog.i("foo", "***** HAVE TRACKBALL!"); - } else if ((d.classes&RawInputEvent.CLASS_DPAD) != 0) { - config.navigation - = Configuration.NAVIGATION_DPAD; - //Slog.i("foo", "***** HAVE DPAD!"); - } - } - } - } - } - - public int getScancodeState(int code) { - synchronized (mFirst) { - VirtualKey vk = mPressedVirtualKey; - if (vk != null) { - if (vk.scancode == code) { - return 2; - } - } - return nativeGetScancodeState(code); - } - } - - public int getScancodeState(int deviceId, int code) { - synchronized (mFirst) { - VirtualKey vk = mPressedVirtualKey; - if (vk != null) { - if (vk.scancode == code) { - return 2; - } - } - return nativeGetScancodeState(deviceId, code); - } - } - - public int getTrackballScancodeState(int code) { - synchronized (mFirst) { - final int N = mDevices.size(); - for (int i=0; i<N; i++) { - InputDevice dev = mDevices.valueAt(i); - if ((dev.classes&RawInputEvent.CLASS_TRACKBALL) != 0) { - int res = nativeGetScancodeState(dev.id, code); - if (res > 0) { - return res; - } - } - } - } - - return 0; - } - - public int getDPadScancodeState(int code) { - synchronized (mFirst) { - final int N = mDevices.size(); - for (int i=0; i<N; i++) { - InputDevice dev = mDevices.valueAt(i); - if ((dev.classes&RawInputEvent.CLASS_DPAD) != 0) { - int res = nativeGetScancodeState(dev.id, code); - if (res > 0) { - return res; - } - } - } - } - - return 0; - } - - public int getKeycodeState(int code) { - synchronized (mFirst) { - VirtualKey vk = mPressedVirtualKey; - if (vk != null) { - if (vk.lastKeycode == code) { - return 2; - } - } - return nativeGetKeycodeState(code); - } - } - - public int getKeycodeState(int deviceId, int code) { - synchronized (mFirst) { - VirtualKey vk = mPressedVirtualKey; - if (vk != null) { - if (vk.lastKeycode == code) { - return 2; - } - } - return nativeGetKeycodeState(deviceId, code); - } - } - - public int getTrackballKeycodeState(int code) { - synchronized (mFirst) { - final int N = mDevices.size(); - for (int i=0; i<N; i++) { - InputDevice dev = mDevices.valueAt(i); - if ((dev.classes&RawInputEvent.CLASS_TRACKBALL) != 0) { - int res = nativeGetKeycodeState(dev.id, code); - if (res > 0) { - return res; - } - } - } - } - - return 0; - } - - public int getDPadKeycodeState(int code) { - synchronized (mFirst) { - final int N = mDevices.size(); - for (int i=0; i<N; i++) { - InputDevice dev = mDevices.valueAt(i); - if ((dev.classes&RawInputEvent.CLASS_DPAD) != 0) { - int res = nativeGetKeycodeState(dev.id, code); - if (res > 0) { - return res; - } - } - } - } - - return 0; - } - - public static native String getDeviceName(int deviceId); - public static native int getDeviceClasses(int deviceId); - public static native void addExcludedDevice(String deviceName); - public static native boolean getAbsoluteInfo(int deviceId, int axis, - InputDevice.AbsoluteInfo outInfo); - public static native int getSwitchState(int sw); - public static native int getSwitchState(int deviceId, int sw); - public static native int nativeGetScancodeState(int code); - public static native int nativeGetScancodeState(int deviceId, int code); - public static native int nativeGetKeycodeState(int code); - public static native int nativeGetKeycodeState(int deviceId, int code); - public static native int scancodeToKeycode(int deviceId, int scancode); - public static native boolean hasKeys(int[] keycodes, boolean[] keyExists); - - public static KeyEvent newKeyEvent(InputDevice device, long downTime, - long eventTime, boolean down, int keycode, int repeatCount, - int scancode, int flags) { - return new KeyEvent( - downTime, eventTime, - down ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP, - keycode, repeatCount, - device != null ? device.mMetaKeysState : 0, - device != null ? device.id : -1, scancode, - flags | KeyEvent.FLAG_FROM_SYSTEM); - } - - Thread mThread = new Thread("InputDeviceReader") { - public void run() { - if (DEBUG) Slog.v(TAG, "InputDeviceReader.run()"); - android.os.Process.setThreadPriority( - android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY); - - RawInputEvent ev = new RawInputEvent(); - while (true) { - try { - InputDevice di; - - // block, doesn't release the monitor - readEvent(ev); - - boolean send = false; - boolean configChanged = false; - - if (false) { - Slog.i(TAG, "Input event: dev=0x" - + Integer.toHexString(ev.deviceId) - + " type=0x" + Integer.toHexString(ev.type) - + " scancode=" + ev.scancode - + " keycode=" + ev.keycode - + " value=" + ev.value); - } - - if (ev.type == RawInputEvent.EV_DEVICE_ADDED) { - synchronized (mFirst) { - di = newInputDevice(ev.deviceId); - if (di.classes != 0) { - // If this device is some kind of input class, - // we care about it. - mDevices.put(ev.deviceId, di); - if ((di.classes & RawInputEvent.CLASS_TOUCHSCREEN) != 0) { - readVirtualKeys(di.name); - } - // The configuration may have changed because - // of this device. - configChanged = true; - } else { - // We won't do anything with this device. - mIgnoredDevices.put(ev.deviceId, di); - Slog.i(TAG, "Ignoring non-input device: id=0x" - + Integer.toHexString(di.id) - + ", name=" + di.name); - } - } - } else if (ev.type == RawInputEvent.EV_DEVICE_REMOVED) { - synchronized (mFirst) { - if (false) { - Slog.i(TAG, "Device removed: id=0x" - + Integer.toHexString(ev.deviceId)); - } - di = mDevices.get(ev.deviceId); - if (di != null) { - mDevices.delete(ev.deviceId); - // The configuration may have changed because - // of this device. - configChanged = true; - } else if ((di=mIgnoredDevices.get(ev.deviceId)) != null) { - mIgnoredDevices.remove(ev.deviceId); - } else { - Slog.w(TAG, "Removing bad device id: " - + Integer.toHexString(ev.deviceId)); - continue; - } - } - } else { - di = getInputDevice(ev.deviceId); - if (di == null) { - // This may be some junk from an ignored device. - continue; - } - - // first crack at it - send = preprocessEvent(di, ev); - - if (ev.type == RawInputEvent.EV_KEY) { - di.mMetaKeysState = makeMetaState(ev.keycode, - ev.value != 0, di.mMetaKeysState); - mHaveGlobalMetaState = false; - } - } - - if (configChanged) { - synchronized (mFirst) { - addLocked(di, System.nanoTime(), 0, - RawInputEvent.CLASS_CONFIGURATION_CHANGED, - null); - } - } - - if (!send) { - continue; - } - - synchronized (mFirst) { - // NOTE: The event timebase absolutely must be the same - // timebase as SystemClock.uptimeMillis(). - //curTime = gotOne ? ev.when : SystemClock.uptimeMillis(); - final long curTime = SystemClock.uptimeMillis(); - final long curTimeNano = System.nanoTime(); - //Slog.i(TAG, "curTime=" + curTime + ", systemClock=" + SystemClock.uptimeMillis()); - - final int classes = di.classes; - final int type = ev.type; - final int scancode = ev.scancode; - send = false; - - // Is it a key event? - if (type == RawInputEvent.EV_KEY && - (classes&RawInputEvent.CLASS_KEYBOARD) != 0 && - (scancode < RawInputEvent.BTN_FIRST || - scancode > RawInputEvent.BTN_LAST)) { - boolean down; - if (ev.value != 0) { - down = true; - di.mKeyDownTime = curTime; - } else { - down = false; - } - int keycode = rotateKeyCodeLocked(ev.keycode); - addLocked(di, curTimeNano, ev.flags, - RawInputEvent.CLASS_KEYBOARD, - newKeyEvent(di, di.mKeyDownTime, curTime, down, - keycode, 0, scancode, - ((ev.flags & WindowManagerPolicy.FLAG_WOKE_HERE) != 0) - ? KeyEvent.FLAG_WOKE_HERE : 0)); - - } else if (ev.type == RawInputEvent.EV_KEY) { - // Single touch protocol: touch going down or up. - if (ev.scancode == RawInputEvent.BTN_TOUCH && - (classes&(RawInputEvent.CLASS_TOUCHSCREEN - |RawInputEvent.CLASS_TOUCHSCREEN_MT)) - == RawInputEvent.CLASS_TOUCHSCREEN) { - di.mAbs.changed = true; - di.mAbs.mDown[0] = ev.value != 0; - - // Trackball (mouse) protocol: press down or up. - } else if (ev.scancode == RawInputEvent.BTN_MOUSE && - (classes&RawInputEvent.CLASS_TRACKBALL) != 0) { - di.mRel.changed = true; - di.mRel.mNextNumPointers = ev.value != 0 ? 1 : 0; - send = true; - } - - // Process position events from multitouch protocol. - } else if (ev.type == RawInputEvent.EV_ABS && - (classes&RawInputEvent.CLASS_TOUCHSCREEN_MT) != 0) { - if (ev.scancode == RawInputEvent.ABS_MT_TOUCH_MAJOR) { - di.mAbs.changed = true; - di.mAbs.mNextData[di.mAbs.mAddingPointerOffset - + MotionEvent.SAMPLE_PRESSURE] = ev.value; - } else if (ev.scancode == RawInputEvent.ABS_MT_POSITION_X) { - di.mAbs.changed = true; - di.mAbs.mNextData[di.mAbs.mAddingPointerOffset - + MotionEvent.SAMPLE_X] = ev.value; - if (DEBUG_POINTERS) Slog.v(TAG, "MT @" - + di.mAbs.mAddingPointerOffset - + " X:" + ev.value); - } else if (ev.scancode == RawInputEvent.ABS_MT_POSITION_Y) { - di.mAbs.changed = true; - di.mAbs.mNextData[di.mAbs.mAddingPointerOffset - + MotionEvent.SAMPLE_Y] = ev.value; - if (DEBUG_POINTERS) Slog.v(TAG, "MT @" - + di.mAbs.mAddingPointerOffset - + " Y:" + ev.value); - } else if (ev.scancode == RawInputEvent.ABS_MT_WIDTH_MAJOR) { - di.mAbs.changed = true; - di.mAbs.mNextData[di.mAbs.mAddingPointerOffset - + MotionEvent.SAMPLE_SIZE] = ev.value; - } - - // Process position events from single touch protocol. - } else if (ev.type == RawInputEvent.EV_ABS && - (classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) { - if (ev.scancode == RawInputEvent.ABS_X) { - di.mAbs.changed = true; - di.curTouchVals[MotionEvent.SAMPLE_X] = ev.value; - } else if (ev.scancode == RawInputEvent.ABS_Y) { - di.mAbs.changed = true; - di.curTouchVals[MotionEvent.SAMPLE_Y] = ev.value; - } else if (ev.scancode == RawInputEvent.ABS_PRESSURE) { - di.mAbs.changed = true; - di.curTouchVals[MotionEvent.SAMPLE_PRESSURE] = ev.value; - di.curTouchVals[MotionEvent.NUM_SAMPLE_DATA - + MotionEvent.SAMPLE_PRESSURE] = ev.value; - } else if (ev.scancode == RawInputEvent.ABS_TOOL_WIDTH) { - di.mAbs.changed = true; - di.curTouchVals[MotionEvent.SAMPLE_SIZE] = ev.value; - di.curTouchVals[MotionEvent.NUM_SAMPLE_DATA - + MotionEvent.SAMPLE_SIZE] = ev.value; - } - - // Process movement events from trackball (mouse) protocol. - } else if (ev.type == RawInputEvent.EV_REL && - (classes&RawInputEvent.CLASS_TRACKBALL) != 0) { - // Add this relative movement into our totals. - if (ev.scancode == RawInputEvent.REL_X) { - di.mRel.changed = true; - di.mRel.mNextData[MotionEvent.SAMPLE_X] += ev.value; - } else if (ev.scancode == RawInputEvent.REL_Y) { - di.mRel.changed = true; - di.mRel.mNextData[MotionEvent.SAMPLE_Y] += ev.value; - } - } - - // Handle multitouch protocol sync: tells us that the - // driver has returned all data for -one- of the pointers - // that is currently down. - if (ev.type == RawInputEvent.EV_SYN - && ev.scancode == RawInputEvent.SYN_MT_REPORT - && di.mAbs != null) { - di.mAbs.changed = true; - if (di.mAbs.mNextData[MotionEvent.SAMPLE_PRESSURE] > 0) { - // If the value is <= 0, the pointer is not - // down, so keep it in the count. - - if (di.mAbs.mNextData[di.mAbs.mAddingPointerOffset - + MotionEvent.SAMPLE_PRESSURE] != 0) { - final int num = di.mAbs.mNextNumPointers+1; - di.mAbs.mNextNumPointers = num; - if (DEBUG_POINTERS) Slog.v(TAG, - "MT_REPORT: now have " + num + " pointers"); - final int newOffset = (num <= InputDevice.MAX_POINTERS) - ? (num * MotionEvent.NUM_SAMPLE_DATA) - : (InputDevice.MAX_POINTERS * - MotionEvent.NUM_SAMPLE_DATA); - di.mAbs.mAddingPointerOffset = newOffset; - di.mAbs.mNextData[newOffset - + MotionEvent.SAMPLE_PRESSURE] = 0; - } else { - if (DEBUG_POINTERS) Slog.v(TAG, "MT_REPORT: no pointer"); - } - } - - // Handle general event sync: all data for the current - // event update has been delivered. - } else if (send || (ev.type == RawInputEvent.EV_SYN - && ev.scancode == RawInputEvent.SYN_REPORT)) { - if (mDisplay != null) { - if (!mHaveGlobalMetaState) { - computeGlobalMetaStateLocked(); - } - - MotionEvent me; - - InputDevice.MotionState ms = di.mAbs; - if (ms.changed) { - ms.everChanged = true; - ms.changed = false; - - if ((classes&(RawInputEvent.CLASS_TOUCHSCREEN - |RawInputEvent.CLASS_TOUCHSCREEN_MT)) - == RawInputEvent.CLASS_TOUCHSCREEN) { - ms.mNextNumPointers = 0; - if (ms.mDown[0]) { - System.arraycopy(di.curTouchVals, 0, - ms.mNextData, 0, - MotionEvent.NUM_SAMPLE_DATA); - ms.mNextNumPointers++; - } - } - - if (BAD_TOUCH_HACK) { - ms.dropBadPoint(di); - } - if (JUMPY_TOUCH_HACK) { - ms.dropJumpyPoint(di); - } - - boolean doMotion = !monitorVirtualKey(di, - ev, curTime, curTimeNano); - - if (doMotion && ms.mNextNumPointers > 0 - && (ms.mLastNumPointers == 0 - || ms.mSkipLastPointers)) { - doMotion = !generateVirtualKeyDown(di, - ev, curTime, curTimeNano); - } - - if (doMotion) { - // XXX Need to be able to generate - // multiple events here, for example - // if two fingers change up/down state - // at the same time. - do { - me = ms.generateAbsMotion(di, curTime, - curTimeNano, mDisplay, - mOrientation, mGlobalMetaState); - if (DEBUG_POINTERS) Slog.v(TAG, "Absolute: x=" - + di.mAbs.mNextData[MotionEvent.SAMPLE_X] - + " y=" - + di.mAbs.mNextData[MotionEvent.SAMPLE_Y] - + " ev=" + me); - if (me != null) { - if (WindowManagerPolicy.WATCH_POINTER) { - Slog.i(TAG, "Enqueueing: " + me); - } - addLocked(di, curTimeNano, ev.flags, - RawInputEvent.CLASS_TOUCHSCREEN, me); - } - } while (ms.hasMore()); - } else { - // We are consuming movement in the - // virtual key area... but still - // propagate this to the previous - // data for comparisons. - int num = ms.mNextNumPointers; - if (num > InputDevice.MAX_POINTERS) { - num = InputDevice.MAX_POINTERS; - } - System.arraycopy(ms.mNextData, 0, - ms.mLastData, 0, - num * MotionEvent.NUM_SAMPLE_DATA); - ms.mLastNumPointers = num; - ms.mSkipLastPointers = true; - } - - ms.finish(); - } - - ms = di.mRel; - if (ms.changed) { - ms.everChanged = true; - ms.changed = false; - - me = ms.generateRelMotion(di, curTime, - curTimeNano, - mOrientation, mGlobalMetaState); - if (false) Slog.v(TAG, "Relative: x=" - + di.mRel.mNextData[MotionEvent.SAMPLE_X] - + " y=" - + di.mRel.mNextData[MotionEvent.SAMPLE_Y] - + " ev=" + me); - if (me != null) { - addLocked(di, curTimeNano, ev.flags, - RawInputEvent.CLASS_TRACKBALL, me); - } - } - } - } - } - - } catch (RuntimeException exc) { - Slog.e(TAG, "InputReaderThread uncaught exception", exc); - } - } - } - }; - - private boolean isInsideDisplay(InputDevice dev) { - final InputDevice.AbsoluteInfo absx = dev.absX; - final InputDevice.AbsoluteInfo absy = dev.absY; - final InputDevice.MotionState absm = dev.mAbs; - if (absx == null || absy == null || absm == null) { - return true; - } - - if (absm.mNextData[MotionEvent.SAMPLE_X] >= absx.minValue - && absm.mNextData[MotionEvent.SAMPLE_X] <= absx.maxValue - && absm.mNextData[MotionEvent.SAMPLE_Y] >= absy.minValue - && absm.mNextData[MotionEvent.SAMPLE_Y] <= absy.maxValue) { - if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Input (" - + absm.mNextData[MotionEvent.SAMPLE_X] - + "," + absm.mNextData[MotionEvent.SAMPLE_Y] - + ") inside of display"); - return true; - } - - return false; - } - - private VirtualKey findVirtualKey(InputDevice dev) { - final int N = mVirtualKeys.size(); - if (N <= 0) { - return null; - } - - final InputDevice.MotionState absm = dev.mAbs; - for (int i=0; i<N; i++) { - VirtualKey sb = mVirtualKeys.get(i); - sb.computeHitRect(dev, mDisplayWidth, mDisplayHeight); - if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Hit test (" - + absm.mNextData[MotionEvent.SAMPLE_X] + "," - + absm.mNextData[MotionEvent.SAMPLE_Y] + ") in code " - + sb.scancode + " - (" + sb.hitLeft - + "," + sb.hitTop + ")-(" + sb.hitRight + "," - + sb.hitBottom + ")"); - if (sb.checkHit(absm.mNextData[MotionEvent.SAMPLE_X], - absm.mNextData[MotionEvent.SAMPLE_Y])) { - if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Hit!"); - return sb; - } - } - - return null; - } - - private boolean generateVirtualKeyDown(InputDevice di, RawInputEvent ev, - long curTime, long curTimeNano) { - if (isInsideDisplay(di)) { - // Didn't consume event. - return false; - } - - - VirtualKey vk = findVirtualKey(di); - if (vk != null) { - final InputDevice.MotionState ms = di.mAbs; - mPressedVirtualKey = vk; - vk.lastKeycode = scancodeToKeycode(di.id, vk.scancode); - ms.mLastNumPointers = ms.mNextNumPointers; - di.mKeyDownTime = curTime; - if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, - "Generate key down for: " + vk.scancode - + " (keycode=" + vk.lastKeycode + ")"); - KeyEvent event = newKeyEvent(di, di.mKeyDownTime, curTime, true, - vk.lastKeycode, 0, vk.scancode, - KeyEvent.FLAG_VIRTUAL_HARD_KEY); - mHapticFeedbackCallback.virtualKeyFeedback(event); - addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD, - event); - } - - // We always consume the event, even if we didn't - // generate a key event. There are two reasons for - // this: to avoid spurious touches when holding - // the edges of the device near the touchscreen, - // and to avoid reporting events if there are virtual - // keys on the touchscreen outside of the display - // area. - // Note that for all of this we are only looking at the - // first pointer, since what we are handling here is the - // first pointer going down, and this is the coordinate - // that will be used to dispatch the event. - if (false) { - final InputDevice.AbsoluteInfo absx = di.absX; - final InputDevice.AbsoluteInfo absy = di.absY; - final InputDevice.MotionState absm = di.mAbs; - Slog.v(TAG, "Rejecting (" - + absm.mNextData[MotionEvent.SAMPLE_X] + "," - + absm.mNextData[MotionEvent.SAMPLE_Y] + "): outside of (" - + absx.minValue + "," + absy.minValue - + ")-(" + absx.maxValue + "," - + absx.maxValue + ")"); - } - return true; - } - - private boolean monitorVirtualKey(InputDevice di, RawInputEvent ev, - long curTime, long curTimeNano) { - VirtualKey vk = mPressedVirtualKey; - if (vk == null) { - return false; - } - - final InputDevice.MotionState ms = di.mAbs; - if (ms.mNextNumPointers <= 0) { - mPressedVirtualKey = null; - ms.mLastNumPointers = 0; - if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Generate key up for: " + vk.scancode); - KeyEvent event = newKeyEvent(di, di.mKeyDownTime, curTime, false, - vk.lastKeycode, 0, vk.scancode, - KeyEvent.FLAG_VIRTUAL_HARD_KEY); - mHapticFeedbackCallback.virtualKeyFeedback(event); - addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD, - event); - return true; - - } else if (isInsideDisplay(di)) { - // Whoops the pointer has moved into - // the display area! Cancel the - // virtual key and start a pointer - // motion. - mPressedVirtualKey = null; - if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Cancel key up for: " + vk.scancode); - KeyEvent event = newKeyEvent(di, di.mKeyDownTime, curTime, false, - vk.lastKeycode, 0, vk.scancode, - KeyEvent.FLAG_CANCELED | KeyEvent.FLAG_VIRTUAL_HARD_KEY); - mHapticFeedbackCallback.virtualKeyFeedback(event); - addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD, - event); - ms.mLastNumPointers = 0; - return false; - } - - return true; - } - - /** - * Returns a new meta state for the given keys and old state. - */ - private static final int makeMetaState(int keycode, boolean down, int old) { - int mask; - switch (keycode) { - case KeyEvent.KEYCODE_ALT_LEFT: - mask = KeyEvent.META_ALT_LEFT_ON; - break; - case KeyEvent.KEYCODE_ALT_RIGHT: - mask = KeyEvent.META_ALT_RIGHT_ON; - break; - case KeyEvent.KEYCODE_SHIFT_LEFT: - mask = KeyEvent.META_SHIFT_LEFT_ON; - break; - case KeyEvent.KEYCODE_SHIFT_RIGHT: - mask = KeyEvent.META_SHIFT_RIGHT_ON; - break; - case KeyEvent.KEYCODE_SYM: - mask = KeyEvent.META_SYM_ON; - break; - default: - return old; - } - int result = ~(KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON) - & (down ? (old | mask) : (old & ~mask)); - if (0 != (result & (KeyEvent.META_ALT_LEFT_ON | KeyEvent.META_ALT_RIGHT_ON))) { - result |= KeyEvent.META_ALT_ON; - } - if (0 != (result & (KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_RIGHT_ON))) { - result |= KeyEvent.META_SHIFT_ON; - } - return result; - } - - private void computeGlobalMetaStateLocked() { - int i = mDevices.size(); - mGlobalMetaState = 0; - while ((--i) >= 0) { - mGlobalMetaState |= mDevices.valueAt(i).mMetaKeysState; - } - mHaveGlobalMetaState = true; - } - - /* - * Return true if you want the event to get passed on to the - * rest of the system, and false if you've handled it and want - * it dropped. - */ - abstract boolean preprocessEvent(InputDevice device, RawInputEvent event); - - InputDevice getInputDevice(int deviceId) { - synchronized (mFirst) { - return getInputDeviceLocked(deviceId); - } - } - - private InputDevice getInputDeviceLocked(int deviceId) { - return mDevices.get(deviceId); - } - - public void setOrientation(int orientation) { - synchronized(mFirst) { - mOrientation = orientation; - switch (orientation) { - case Surface.ROTATION_90: - mKeyRotationMap = KEY_90_MAP; - break; - case Surface.ROTATION_180: - mKeyRotationMap = KEY_180_MAP; - break; - case Surface.ROTATION_270: - mKeyRotationMap = KEY_270_MAP; - break; - default: - mKeyRotationMap = null; - break; - } - } - } - - public int rotateKeyCode(int keyCode) { - synchronized(mFirst) { - return rotateKeyCodeLocked(keyCode); - } - } - - private int rotateKeyCodeLocked(int keyCode) { - int[] map = mKeyRotationMap; - if (map != null) { - final int N = map.length; - for (int i=0; i<N; i+=2) { - if (map[i] == keyCode) { - return map[i+1]; - } - } - } - return keyCode; - } - - boolean hasEvents() { - synchronized (mFirst) { - return mFirst.next != mLast; - } - } - - /* - * returns true if we returned an event, and false if we timed out - */ - QueuedEvent getEvent(long timeoutMS) { - long begin = SystemClock.uptimeMillis(); - final long end = begin+timeoutMS; - long now = begin; - synchronized (mFirst) { - while (mFirst.next == mLast && end > now) { - try { - mWakeLock.release(); - mFirst.wait(end-now); - } - catch (InterruptedException e) { - } - now = SystemClock.uptimeMillis(); - if (begin > now) { - begin = now; - } - } - if (mFirst.next == mLast) { - return null; - } - QueuedEvent p = mFirst.next; - mFirst.next = p.next; - mFirst.next.prev = mFirst; - p.inQueue = false; - return p; - } - } - - /** - * Return true if the queue has an up event pending that corresponds - * to the same key as the given key event. - */ - boolean hasKeyUpEvent(KeyEvent origEvent) { - synchronized (mFirst) { - final int keyCode = origEvent.getKeyCode(); - QueuedEvent cur = mLast.prev; - while (cur.prev != null) { - if (cur.classType == RawInputEvent.CLASS_KEYBOARD) { - KeyEvent ke = (KeyEvent)cur.event; - if (ke.getAction() == KeyEvent.ACTION_UP - && ke.getKeyCode() == keyCode) { - return true; - } - } - cur = cur.prev; - } - } - - return false; - } - - void recycleEvent(QueuedEvent ev) { - synchronized (mFirst) { - //Slog.i(TAG, "Recycle event: " + ev); - if (ev.event == ev.inputDevice.mAbs.currentMove) { - ev.inputDevice.mAbs.currentMove = null; - } - if (ev.event == ev.inputDevice.mRel.currentMove) { - if (false) Slog.i(TAG, "Detach rel " + ev.event); - ev.inputDevice.mRel.currentMove = null; - ev.inputDevice.mRel.mNextData[MotionEvent.SAMPLE_X] = 0; - ev.inputDevice.mRel.mNextData[MotionEvent.SAMPLE_Y] = 0; - } - recycleLocked(ev); - } - } - - void filterQueue(FilterCallback cb) { - synchronized (mFirst) { - QueuedEvent cur = mLast.prev; - while (cur.prev != null) { - switch (cb.filterEvent(cur)) { - case FILTER_REMOVE: - cur.prev.next = cur.next; - cur.next.prev = cur.prev; - break; - case FILTER_ABORT: - return; - } - cur = cur.prev; - } - } - } - - private QueuedEvent obtainLocked(InputDevice device, long whenNano, - int flags, int classType, Object event) { - QueuedEvent ev; - if (mCacheCount == 0) { - ev = new QueuedEvent(); - } else { - ev = mCache; - ev.inQueue = false; - mCache = ev.next; - mCacheCount--; - } - ev.inputDevice = device; - ev.whenNano = whenNano; - ev.flags = flags; - ev.classType = classType; - ev.event = event; - return ev; - } - - private void recycleLocked(QueuedEvent ev) { - if (ev.inQueue) { - throw new RuntimeException("Event already in queue!"); - } - if (mCacheCount < 10) { - mCacheCount++; - ev.next = mCache; - mCache = ev; - ev.inQueue = true; - } - } - - private void addLocked(InputDevice device, long whenNano, int flags, - int classType, Object event) { - boolean poke = mFirst.next == mLast; - - QueuedEvent ev = obtainLocked(device, whenNano, flags, classType, event); - QueuedEvent p = mLast.prev; - while (p != mFirst && ev.whenNano < p.whenNano) { - p = p.prev; - } - - ev.next = p.next; - ev.prev = p; - p.next = ev; - ev.next.prev = ev; - ev.inQueue = true; - - if (poke) { - long time; - if (MEASURE_LATENCY) { - time = System.nanoTime(); - } - mFirst.notify(); - mWakeLock.acquire(); - if (MEASURE_LATENCY) { - lt.sample("1 addLocked-queued event ", System.nanoTime() - time); - } - } - } - - private InputDevice newInputDevice(int deviceId) { - int classes = getDeviceClasses(deviceId); - String name = getDeviceName(deviceId); - InputDevice.AbsoluteInfo absX = null; - InputDevice.AbsoluteInfo absY = null; - InputDevice.AbsoluteInfo absPressure = null; - InputDevice.AbsoluteInfo absSize = null; - if (classes != 0) { - Slog.i(TAG, "Device added: id=0x" + Integer.toHexString(deviceId) - + ", name=" + name - + ", classes=" + Integer.toHexString(classes)); - if ((classes&RawInputEvent.CLASS_TOUCHSCREEN_MT) != 0) { - absX = loadAbsoluteInfo(deviceId, - RawInputEvent.ABS_MT_POSITION_X, "X"); - absY = loadAbsoluteInfo(deviceId, - RawInputEvent.ABS_MT_POSITION_Y, "Y"); - absPressure = loadAbsoluteInfo(deviceId, - RawInputEvent.ABS_MT_TOUCH_MAJOR, "Pressure"); - absSize = loadAbsoluteInfo(deviceId, - RawInputEvent.ABS_MT_WIDTH_MAJOR, "Size"); - } else if ((classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) { - absX = loadAbsoluteInfo(deviceId, - RawInputEvent.ABS_X, "X"); - absY = loadAbsoluteInfo(deviceId, - RawInputEvent.ABS_Y, "Y"); - absPressure = loadAbsoluteInfo(deviceId, - RawInputEvent.ABS_PRESSURE, "Pressure"); - absSize = loadAbsoluteInfo(deviceId, - RawInputEvent.ABS_TOOL_WIDTH, "Size"); - } - } - - return new InputDevice(deviceId, classes, name, absX, absY, absPressure, absSize); - } - - private InputDevice.AbsoluteInfo loadAbsoluteInfo(int id, int channel, - String name) { - InputDevice.AbsoluteInfo info = new InputDevice.AbsoluteInfo(); - if (getAbsoluteInfo(id, channel, info) - && info.minValue != info.maxValue) { - Slog.i(TAG, " " + name + ": min=" + info.minValue - + " max=" + info.maxValue - + " flat=" + info.flat - + " fuzz=" + info.fuzz); - info.range = info.maxValue-info.minValue; - return info; - } - Slog.i(TAG, " " + name + ": unknown values"); - return null; - } - private static native boolean readEvent(RawInputEvent outEvent); - - void dump(PrintWriter pw, String prefix) { - synchronized (mFirst) { - for (int i=0; i<mDevices.size(); i++) { - InputDevice dev = mDevices.valueAt(i); - pw.print(prefix); pw.print("Device #"); - pw.print(mDevices.keyAt(i)); pw.print(" "); - pw.print(dev.name); pw.print(" (classes=0x"); - pw.print(Integer.toHexString(dev.classes)); - pw.println("):"); - pw.print(prefix); pw.print(" mKeyDownTime="); - pw.print(dev.mKeyDownTime); pw.print(" mMetaKeysState="); - pw.println(dev.mMetaKeysState); - if (dev.absX != null) { - pw.print(prefix); pw.print(" absX: "); dev.absX.dump(pw); - pw.println(""); - } - if (dev.absY != null) { - pw.print(prefix); pw.print(" absY: "); dev.absY.dump(pw); - pw.println(""); - } - if (dev.absPressure != null) { - pw.print(prefix); pw.print(" absPressure: "); - dev.absPressure.dump(pw); pw.println(""); - } - if (dev.absSize != null) { - pw.print(prefix); pw.print(" absSize: "); - dev.absSize.dump(pw); pw.println(""); - } - if (dev.mAbs.everChanged) { - pw.print(prefix); pw.println(" mAbs:"); - dev.mAbs.dump(pw, prefix + " "); - } - if (dev.mRel.everChanged) { - pw.print(prefix); pw.println(" mRel:"); - dev.mRel.dump(pw, prefix + " "); - } - } - pw.println(" "); - for (int i=0; i<mIgnoredDevices.size(); i++) { - InputDevice dev = mIgnoredDevices.valueAt(i); - pw.print(prefix); pw.print("Ignored Device #"); - pw.print(mIgnoredDevices.keyAt(i)); pw.print(" "); - pw.print(dev.name); pw.print(" (classes=0x"); - pw.print(Integer.toHexString(dev.classes)); - pw.println(")"); - } - pw.println(" "); - for (int i=0; i<mVirtualKeys.size(); i++) { - VirtualKey vk = mVirtualKeys.get(i); - pw.print(prefix); pw.print("Virtual Key #"); - pw.print(i); pw.println(":"); - pw.print(prefix); pw.print(" scancode="); pw.println(vk.scancode); - pw.print(prefix); pw.print(" centerx="); pw.print(vk.centerx); - pw.print(" centery="); pw.print(vk.centery); - pw.print(" width="); pw.print(vk.width); - pw.print(" height="); pw.println(vk.height); - pw.print(prefix); pw.print(" hitLeft="); pw.print(vk.hitLeft); - pw.print(" hitTop="); pw.print(vk.hitTop); - pw.print(" hitRight="); pw.print(vk.hitRight); - pw.print(" hitBottom="); pw.println(vk.hitBottom); - if (vk.lastDevice != null) { - pw.print(prefix); pw.print(" lastDevice=#"); - pw.println(vk.lastDevice.id); - } - if (vk.lastKeycode != 0) { - pw.print(prefix); pw.print(" lastKeycode="); - pw.println(vk.lastKeycode); - } - } - pw.println(" "); - pw.print(prefix); pw.print(" Default keyboard: "); - pw.println(SystemProperties.get("hw.keyboards.0.devname")); - pw.print(prefix); pw.print(" mGlobalMetaState="); - pw.print(mGlobalMetaState); pw.print(" mHaveGlobalMetaState="); - pw.println(mHaveGlobalMetaState); - pw.print(prefix); pw.print(" mDisplayWidth="); - pw.print(mDisplayWidth); pw.print(" mDisplayHeight="); - pw.println(mDisplayHeight); - pw.print(prefix); pw.print(" mOrientation="); - pw.println(mOrientation); - if (mPressedVirtualKey != null) { - pw.print(prefix); pw.print(" mPressedVirtualKey.scancode="); - pw.println(mPressedVirtualKey.scancode); - } - } - } -} diff --git a/services/java/com/android/server/LoadAverageService.java b/services/java/com/android/server/LoadAverageService.java index 0d86429..b6baadb 100644 --- a/services/java/com/android/server/LoadAverageService.java +++ b/services/java/com/android/server/LoadAverageService.java @@ -272,7 +272,7 @@ public class LoadAverageService extends Service { WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, - WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, + WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT); diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index 65f4194..10107c6 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -16,17 +16,6 @@ package com.android.server; -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Observable; -import java.util.Observer; -import java.util.Set; - import android.app.Activity; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -41,6 +30,7 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.Cursor; import android.location.Address; +import android.location.Criteria; import android.location.GeocoderParams; import android.location.IGpsStatusListener; import android.location.IGpsStatusProvider; @@ -50,7 +40,6 @@ import android.location.INetInitiatedListener; import android.location.Location; import android.location.LocationManager; import android.location.LocationProvider; -import android.location.LocationProviderInterface; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; @@ -63,17 +52,34 @@ import android.os.Message; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; +import android.os.WorkSource; import android.provider.Settings; import android.util.Log; import android.util.Slog; import android.util.PrintWriterPrinter; -import com.android.internal.location.GeocoderProxy; -import com.android.internal.location.GpsLocationProvider; +import com.android.internal.content.PackageMonitor; import com.android.internal.location.GpsNetInitiatedHandler; -import com.android.internal.location.LocationProviderProxy; -import com.android.internal.location.MockProvider; -import com.android.internal.location.PassiveProvider; + +import com.android.server.location.GeocoderProxy; +import com.android.server.location.GpsLocationProvider; +import com.android.server.location.LocationProviderInterface; +import com.android.server.location.LocationProviderProxy; +import com.android.server.location.MockProvider; +import com.android.server.location.PassiveProvider; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Observable; +import java.util.Observer; +import java.util.Set; /** * The service class that manages LocationProviders and issues location @@ -111,17 +117,20 @@ public class LocationManagerService extends ILocationManager.Stub implements Run private static boolean sProvidersLoaded = false; private final Context mContext; + private final String mNetworkLocationProviderPackageName; + private final String mGeocodeProviderPackageName; private GeocoderProxy mGeocodeProvider; private IGpsStatusProvider mGpsStatusProvider; private INetInitiatedListener mNetInitiatedListener; private LocationWorkerHandler mLocationHandler; // Cache the real providers for use in addTestProvider() and removeTestProvider() - LocationProviderInterface mNetworkLocationProvider; + LocationProviderProxy mNetworkLocationProvider; LocationProviderInterface mGpsLocationProvider; // Handler messages private static final int MESSAGE_LOCATION_CHANGED = 1; + private static final int MESSAGE_PACKAGE_UPDATED = 2; // wakelock variables private final static String WAKELOCK_KEY = "LocationManagerService"; @@ -153,6 +162,12 @@ public class LocationManagerService extends ILocationManager.Stub implements Run private final HashMap<String,ArrayList<UpdateRecord>> mRecordsByProvider = new HashMap<String,ArrayList<UpdateRecord>>(); + /** + * Temporary filled in when computing min time for a provider. Access is + * protected by global lock mLock. + */ + private final WorkSource mTmpWorkSource = new WorkSource(); + // Proximity listeners private Receiver mProximityReceiver = null; private ILocationListener mProximityListener = null; @@ -209,15 +224,18 @@ public class LocationManagerService extends ILocationManager.Stub implements Run @Override public String toString() { + String result; if (mListener != null) { - return "Receiver{" + result = "Receiver{" + Integer.toHexString(System.identityHashCode(this)) + " Listener " + mKey + "}"; } else { - return "Receiver{" + result = "Receiver{" + Integer.toHexString(System.identityHashCode(this)) + " Intent " + mKey + "}"; } + result += "mUpdateRecords: " + mUpdateRecords; + return result; } public boolean isListener() { @@ -461,19 +479,15 @@ public class LocationManagerService extends ILocationManager.Stub implements Run mEnabledProviders.add(passiveProvider.getName()); // initialize external network location and geocoder services - Resources resources = mContext.getResources(); - String serviceName = resources.getString( - com.android.internal.R.string.config_networkLocationProvider); - if (serviceName != null) { + if (mNetworkLocationProviderPackageName != null) { mNetworkLocationProvider = new LocationProviderProxy(mContext, LocationManager.NETWORK_PROVIDER, - serviceName, mLocationHandler); + mNetworkLocationProviderPackageName, mLocationHandler); addProvider(mNetworkLocationProvider); } - serviceName = resources.getString(com.android.internal.R.string.config_geocodeProvider); - if (serviceName != null) { - mGeocodeProvider = new GeocoderProxy(mContext, serviceName); + if (mGeocodeProviderPackageName != null) { + mGeocodeProvider = new GeocoderProxy(mContext, mGeocodeProviderPackageName); } updateProvidersLocked(); @@ -485,6 +499,12 @@ public class LocationManagerService extends ILocationManager.Stub implements Run public LocationManagerService(Context context) { super(); mContext = context; + Resources resources = context.getResources(); + mNetworkLocationProviderPackageName = resources.getString( + com.android.internal.R.string.config_networkLocationProvider); + mGeocodeProviderPackageName = resources.getString( + com.android.internal.R.string.config_geocodeProvider); + mPackageMonitor.register(context, true); if (LOCAL_LOGV) { Slog.v(TAG, "Constructed LocationManager Service"); @@ -554,15 +574,16 @@ public class LocationManagerService extends ILocationManager.Stub implements Run || LocationManager.PASSIVE_PROVIDER.equals(provider)) && (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED)) { - throw new SecurityException("Requires ACCESS_FINE_LOCATION permission"); + throw new SecurityException("Provider " + provider + + " requires ACCESS_FINE_LOCATION permission"); } if (LocationManager.NETWORK_PROVIDER.equals(provider) && (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) && (mContext.checkCallingOrSelfPermission(ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED)) { - throw new SecurityException( - "Requires ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION permission"); + throw new SecurityException("Provider " + provider + + " requires ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION permission"); } } @@ -609,10 +630,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run return out; } - public List<String> getProviders(boolean enabledOnly) { + public List<String> getProviders(Criteria criteria, boolean enabledOnly) { try { synchronized (mLock) { - return _getProvidersLocked(enabledOnly); + return _getProvidersLocked(criteria, enabledOnly); } } catch (SecurityException se) { throw se; @@ -622,7 +643,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } - private List<String> _getProvidersLocked(boolean enabledOnly) { + private List<String> _getProvidersLocked(Criteria criteria, boolean enabledOnly) { if (LOCAL_LOGV) { Slog.v(TAG, "getProviders"); } @@ -634,25 +655,242 @@ public class LocationManagerService extends ILocationManager.Stub implements Run if (enabledOnly && !isAllowedBySettingsLocked(name)) { continue; } + if (criteria != null && !p.meetsCriteria(criteria)) { + continue; + } out.add(name); } } return out; } + /** + * Returns the next looser power requirement, in the sequence: + * + * POWER_LOW -> POWER_MEDIUM -> POWER_HIGH -> NO_REQUIREMENT + */ + private int nextPower(int power) { + switch (power) { + case Criteria.POWER_LOW: + return Criteria.POWER_MEDIUM; + case Criteria.POWER_MEDIUM: + return Criteria.POWER_HIGH; + case Criteria.POWER_HIGH: + return Criteria.NO_REQUIREMENT; + case Criteria.NO_REQUIREMENT: + default: + return Criteria.NO_REQUIREMENT; + } + } + + /** + * Returns the next looser accuracy requirement, in the sequence: + * + * ACCURACY_FINE -> ACCURACY_APPROXIMATE-> NO_REQUIREMENT + */ + private int nextAccuracy(int accuracy) { + if (accuracy == Criteria.ACCURACY_FINE) { + return Criteria.ACCURACY_COARSE; + } else { + return Criteria.NO_REQUIREMENT; + } + } + + private class LpPowerComparator implements Comparator<LocationProviderInterface> { + public int compare(LocationProviderInterface l1, LocationProviderInterface l2) { + // Smaller is better + return (l1.getPowerRequirement() - l2.getPowerRequirement()); + } + + public boolean equals(LocationProviderInterface l1, LocationProviderInterface l2) { + return (l1.getPowerRequirement() == l2.getPowerRequirement()); + } + } + + private class LpAccuracyComparator implements Comparator<LocationProviderInterface> { + public int compare(LocationProviderInterface l1, LocationProviderInterface l2) { + // Smaller is better + return (l1.getAccuracy() - l2.getAccuracy()); + } + + public boolean equals(LocationProviderInterface l1, LocationProviderInterface l2) { + return (l1.getAccuracy() == l2.getAccuracy()); + } + } + + private class LpCapabilityComparator implements Comparator<LocationProviderInterface> { + + private static final int ALTITUDE_SCORE = 4; + private static final int BEARING_SCORE = 4; + private static final int SPEED_SCORE = 4; + + private int score(LocationProviderInterface p) { + return (p.supportsAltitude() ? ALTITUDE_SCORE : 0) + + (p.supportsBearing() ? BEARING_SCORE : 0) + + (p.supportsSpeed() ? SPEED_SCORE : 0); + } + + public int compare(LocationProviderInterface l1, LocationProviderInterface l2) { + return (score(l2) - score(l1)); // Bigger is better + } + + public boolean equals(LocationProviderInterface l1, LocationProviderInterface l2) { + return (score(l1) == score(l2)); + } + } + + private LocationProviderInterface best(List<String> providerNames) { + ArrayList<LocationProviderInterface> providers; + synchronized (mLock) { + providers = new ArrayList<LocationProviderInterface>(providerNames.size()); + for (String name : providerNames) { + providers.add(mProvidersByName.get(name)); + } + } + + if (providers.size() < 2) { + return providers.get(0); + } + + // First, sort by power requirement + Collections.sort(providers, new LpPowerComparator()); + int power = providers.get(0).getPowerRequirement(); + if (power < providers.get(1).getPowerRequirement()) { + return providers.get(0); + } + + int idx, size; + + ArrayList<LocationProviderInterface> tmp = new ArrayList<LocationProviderInterface>(); + idx = 0; + size = providers.size(); + while ((idx < size) && (providers.get(idx).getPowerRequirement() == power)) { + tmp.add(providers.get(idx)); + idx++; + } + + // Next, sort by accuracy + Collections.sort(tmp, new LpAccuracyComparator()); + int acc = tmp.get(0).getAccuracy(); + if (acc < tmp.get(1).getAccuracy()) { + return tmp.get(0); + } + + ArrayList<LocationProviderInterface> tmp2 = new ArrayList<LocationProviderInterface>(); + idx = 0; + size = tmp.size(); + while ((idx < size) && (tmp.get(idx).getAccuracy() == acc)) { + tmp2.add(tmp.get(idx)); + idx++; + } + + // Finally, sort by capability "score" + Collections.sort(tmp2, new LpCapabilityComparator()); + return tmp2.get(0); + } + + /** + * Returns the name of the provider that best meets the given criteria. Only providers + * that are permitted to be accessed by the calling activity will be + * returned. If several providers meet the criteria, the one with the best + * accuracy is returned. If no provider meets the criteria, + * the criteria are loosened in the following sequence: + * + * <ul> + * <li> power requirement + * <li> accuracy + * <li> bearing + * <li> speed + * <li> altitude + * </ul> + * + * <p> Note that the requirement on monetary cost is not removed + * in this process. + * + * @param criteria the criteria that need to be matched + * @param enabledOnly if true then only a provider that is currently enabled is returned + * @return name of the provider that best matches the requirements + */ + public String getBestProvider(Criteria criteria, boolean enabledOnly) { + List<String> goodProviders = getProviders(criteria, enabledOnly); + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + + // Make a copy of the criteria that we can modify + criteria = new Criteria(criteria); + + // Loosen power requirement + int power = criteria.getPowerRequirement(); + while (goodProviders.isEmpty() && (power != Criteria.NO_REQUIREMENT)) { + power = nextPower(power); + criteria.setPowerRequirement(power); + goodProviders = getProviders(criteria, enabledOnly); + } + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + + // Loosen accuracy requirement + int accuracy = criteria.getAccuracy(); + while (goodProviders.isEmpty() && (accuracy != Criteria.NO_REQUIREMENT)) { + accuracy = nextAccuracy(accuracy); + criteria.setAccuracy(accuracy); + goodProviders = getProviders(criteria, enabledOnly); + } + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + + // Remove bearing requirement + criteria.setBearingRequired(false); + goodProviders = getProviders(criteria, enabledOnly); + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + + // Remove speed requirement + criteria.setSpeedRequired(false); + goodProviders = getProviders(criteria, enabledOnly); + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + + // Remove altitude requirement + criteria.setAltitudeRequired(false); + goodProviders = getProviders(criteria, enabledOnly); + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + + return null; + } + + public boolean providerMeetsCriteria(String provider, Criteria criteria) { + LocationProviderInterface p = mProvidersByName.get(provider); + if (p == null) { + throw new IllegalArgumentException("provider=" + provider); + } + return p.meetsCriteria(criteria); + } + private void updateProvidersLocked() { + boolean changesMade = false; for (int i = mProviders.size() - 1; i >= 0; i--) { LocationProviderInterface p = mProviders.get(i); boolean isEnabled = p.isEnabled(); String name = p.getName(); boolean shouldBeEnabled = isAllowedBySettingsLocked(name); - if (isEnabled && !shouldBeEnabled) { updateProviderListenersLocked(name, false); + changesMade = true; } else if (!isEnabled && shouldBeEnabled) { updateProviderListenersLocked(name, true); + changesMade = true; } - + } + if (changesMade) { + mContext.sendBroadcast(new Intent(LocationManager.PROVIDERS_CHANGED_ACTION)); } } @@ -691,7 +929,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run if (enabled) { p.enable(); if (listeners > 0) { - p.setMinTime(getMinTimeLocked(provider)); + p.setMinTime(getMinTimeLocked(provider), mTmpWorkSource); p.enableLocationTracking(true); } } else { @@ -703,9 +941,21 @@ public class LocationManagerService extends ILocationManager.Stub implements Run private long getMinTimeLocked(String provider) { long minTime = Long.MAX_VALUE; ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); + mTmpWorkSource.clear(); if (records != null) { for (int i=records.size()-1; i>=0; i--) { - minTime = Math.min(minTime, records.get(i).mMinTime); + UpdateRecord ur = records.get(i); + long curTime = ur.mMinTime; + if (curTime < minTime) { + minTime = curTime; + } + } + long inclTime = (minTime*3)/2; + for (int i=records.size()-1; i>=0; i--) { + UpdateRecord ur = records.get(i); + if (ur.mMinTime <= inclTime) { + mTmpWorkSource.add(ur.mUid); + } } } return minTime; @@ -716,6 +966,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run final Receiver mReceiver; final long mMinTime; final float mMinDistance; + final boolean mSingleShot; final int mUid; Location mLastFixBroadcast; long mLastStatusBroadcast; @@ -723,12 +974,13 @@ public class LocationManagerService extends ILocationManager.Stub implements Run /** * Note: must be constructed with lock held. */ - UpdateRecord(String provider, long minTime, float minDistance, + UpdateRecord(String provider, long minTime, float minDistance, boolean singleShot, Receiver receiver, int uid) { mProvider = provider; mReceiver = receiver; mMinTime = minTime; mMinDistance = minDistance; + mSingleShot = singleShot; mUid = uid; ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); @@ -756,13 +1008,14 @@ public class LocationManagerService extends ILocationManager.Stub implements Run public String toString() { return "UpdateRecord{" + Integer.toHexString(System.identityHashCode(this)) - + " " + mProvider + " " + mReceiver + "}"; + + " mProvider: " + mProvider + " mUid: " + mUid + "}"; } void dump(PrintWriter pw, String prefix) { pw.println(prefix + this); pw.println(prefix + "mProvider=" + mProvider + " mReceiver=" + mReceiver); pw.println(prefix + "mMinTime=" + mMinTime + " mMinDistance=" + mMinDistance); + pw.println(prefix + "mSingleShot=" + mSingleShot); pw.println(prefix + "mUid=" + mUid); pw.println(prefix + "mLastFixBroadcast:"); if (mLastFixBroadcast != null) { @@ -818,12 +1071,21 @@ public class LocationManagerService extends ILocationManager.Stub implements Run return false; } - public void requestLocationUpdates(String provider, - long minTime, float minDistance, ILocationListener listener) { - + public void requestLocationUpdates(String provider, Criteria criteria, + long minTime, float minDistance, boolean singleShot, ILocationListener listener) { + if (criteria != null) { + // FIXME - should we consider using multiple providers simultaneously + // rather than only the best one? + // Should we do anything different for single shot fixes? + provider = getBestProvider(criteria, true); + if (provider == null) { + throw new IllegalArgumentException("no providers found for criteria"); + } + } try { synchronized (mLock) { - requestLocationUpdatesLocked(provider, minTime, minDistance, getReceiver(listener)); + requestLocationUpdatesLocked(provider, minTime, minDistance, singleShot, + getReceiver(listener)); } } catch (SecurityException se) { throw se; @@ -834,11 +1096,21 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } - public void requestLocationUpdatesPI(String provider, - long minTime, float minDistance, PendingIntent intent) { + public void requestLocationUpdatesPI(String provider, Criteria criteria, + long minTime, float minDistance, boolean singleShot, PendingIntent intent) { + if (criteria != null) { + // FIXME - should we consider using multiple providers simultaneously + // rather than only the best one? + // Should we do anything different for single shot fixes? + provider = getBestProvider(criteria, true); + if (provider == null) { + throw new IllegalArgumentException("no providers found for criteria"); + } + } try { synchronized (mLock) { - requestLocationUpdatesLocked(provider, minTime, minDistance, getReceiver(intent)); + requestLocationUpdatesLocked(provider, minTime, minDistance, singleShot, + getReceiver(intent)); } } catch (SecurityException se) { throw se; @@ -849,11 +1121,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } - private void requestLocationUpdatesLocked(String provider, - long minTime, float minDistance, Receiver receiver) { - if (LOCAL_LOGV) { - Slog.v(TAG, "_requestLocationUpdates: listener = " + receiver); - } + private void requestLocationUpdatesLocked(String provider, long minTime, float minDistance, + boolean singleShot, Receiver receiver) { LocationProviderInterface p = mProvidersByName.get(provider); if (p == null) { @@ -867,7 +1136,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run boolean newUid = !providerHasListener(provider, callingUid, null); long identity = Binder.clearCallingIdentity(); try { - UpdateRecord r = new UpdateRecord(provider, minTime, minDistance, receiver, callingUid); + UpdateRecord r = new UpdateRecord(provider, minTime, minDistance, singleShot, + receiver, callingUid); UpdateRecord oldRecord = receiver.mUpdateRecords.put(provider, r); if (oldRecord != null) { oldRecord.disposeLocked(); @@ -880,12 +1150,19 @@ public class LocationManagerService extends ILocationManager.Stub implements Run boolean isProviderEnabled = isAllowedBySettingsLocked(provider); if (isProviderEnabled) { long minTimeForProvider = getMinTimeLocked(provider); - p.setMinTime(minTimeForProvider); - p.enableLocationTracking(true); + p.setMinTime(minTimeForProvider, mTmpWorkSource); + // try requesting single shot if singleShot is true, and fall back to + // regular location tracking if requestSingleShotFix() is not supported + if (!singleShot || !p.requestSingleShotFix()) { + p.enableLocationTracking(true); + } } else { // Notify the listener that updates are currently disabled receiver.callProviderEnabledLocked(provider, false); } + if (LOCAL_LOGV) { + Slog.v(TAG, "_requestLocationUpdates: provider = " + provider + " listener = " + receiver); + } } finally { Binder.restoreCallingIdentity(identity); } @@ -974,7 +1251,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run LocationProviderInterface p = mProvidersByName.get(provider); if (p != null) { if (hasOtherListener) { - p.setMinTime(getMinTimeLocked(provider)); + p.setMinTime(getMinTimeLocked(provider), mTmpWorkSource); } else { p.enableLocationTracking(false); } @@ -1287,7 +1564,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run for (int i = mProviders.size() - 1; i >= 0; i--) { LocationProviderInterface provider = mProviders.get(i); - requestLocationUpdatesLocked(provider.getName(), 1000L, 1.0f, mProximityReceiver); + requestLocationUpdatesLocked(provider.getName(), 1000L, 1.0f, + false, mProximityReceiver); } } } @@ -1368,8 +1646,6 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } catch (SecurityException se) { throw se; - } catch (IllegalArgumentException iae) { - throw iae; } catch (Exception e) { Slog.e(TAG, "isProviderEnabled got exception:", e); return false; @@ -1393,20 +1669,21 @@ public class LocationManagerService extends ILocationManager.Stub implements Run LocationProviderInterface p = mProvidersByName.get(provider); if (p == null) { - throw new IllegalArgumentException("provider=" + provider); + return false; } return isAllowedBySettingsLocked(provider); } public Location getLastKnownLocation(String provider) { + if (LOCAL_LOGV) { + Slog.v(TAG, "getLastKnownLocation: " + provider); + } try { synchronized (mLock) { return _getLastKnownLocationLocked(provider); } } catch (SecurityException se) { throw se; - } catch (IllegalArgumentException iae) { - throw iae; } catch (Exception e) { Slog.e(TAG, "getLastKnownLocation got exception:", e); return null; @@ -1418,7 +1695,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run LocationProviderInterface p = mProvidersByName.get(provider); if (p == null) { - throw new IllegalArgumentException("provider=" + provider); + return null; } if (!isAllowedBySettingsLocked(provider)) { @@ -1485,6 +1762,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run for (int i=0; i<N; i++) { UpdateRecord r = records.get(i); Receiver receiver = r.mReceiver; + boolean receiverDead = false; Location lastLoc = r.mLastFixBroadcast; if ((lastLoc == null) || shouldBroadcastSafe(location, lastLoc, r)) { @@ -1496,10 +1774,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } if (!receiver.callLocationChangedLocked(location)) { Slog.w(TAG, "RemoteException calling onLocationChanged on " + receiver); - if (deadReceivers == null) { - deadReceivers = new ArrayList<Receiver>(); - } - deadReceivers.add(receiver); + receiverDead = true; } } @@ -1509,13 +1784,18 @@ public class LocationManagerService extends ILocationManager.Stub implements Run r.mLastStatusBroadcast = newStatusUpdateTime; if (!receiver.callStatusChangedLocked(provider, status, extras)) { + receiverDead = true; Slog.w(TAG, "RemoteException calling onStatusChanged on " + receiver); - if (deadReceivers == null) { - deadReceivers = new ArrayList<Receiver>(); - } - if (!deadReceivers.contains(receiver)) { - deadReceivers.add(receiver); - } + } + } + + // remove receiver if it is dead or we just processed a single shot request + if (receiverDead || r.mSingleShot) { + if (deadReceivers == null) { + deadReceivers = new ArrayList<Receiver>(); + } + if (!deadReceivers.contains(receiver)) { + deadReceivers.add(receiver); } } } @@ -1554,6 +1834,19 @@ public class LocationManagerService extends ILocationManager.Stub implements Run handleLocationChangedLocked(location, passive); } } + } else if (msg.what == MESSAGE_PACKAGE_UPDATED) { + String packageName = (String) msg.obj; + String packageDot = packageName + "."; + + // reconnect to external providers after their packages have been updated + if (mNetworkLocationProvider != null && + mNetworkLocationProviderPackageName.startsWith(packageDot)) { + mNetworkLocationProvider.reconnect(); + } + if (mGeocodeProvider != null && + mGeocodeProviderPackageName.startsWith(packageDot)) { + mGeocodeProvider.reconnect(); + } } } catch (Exception e) { // Log, don't crash! @@ -1653,6 +1946,14 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } }; + private final PackageMonitor mPackageMonitor = new PackageMonitor() { + @Override + public void onPackageUpdateFinished(String packageName, int uid) { + // Called by main thread; divert work to LocationWorker. + Message.obtain(mLocationHandler, MESSAGE_PACKAGE_UPDATED, packageName).sendToTarget(); + } + }; + // Wake locks private void incrementPendingBroadcasts() { @@ -1692,6 +1993,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run // Geocoder + public boolean geocoderIsPresent() { + return mGeocodeProvider != null; + } + public String getFromLocation(double latitude, double longitude, int maxResults, GeocoderParams params, List<Address> addrs) { if (mGeocodeProvider != null) { diff --git a/services/java/com/android/server/MasterClearReceiver.java b/services/java/com/android/server/MasterClearReceiver.java index 27a8a74..bdb5a24 100644 --- a/services/java/com/android/server/MasterClearReceiver.java +++ b/services/java/com/android/server/MasterClearReceiver.java @@ -29,7 +29,7 @@ public class MasterClearReceiver extends BroadcastReceiver { private static final String TAG = "MasterClear"; @Override - public void onReceive(Context context, Intent intent) { + public void onReceive(final Context context, final Intent intent) { if (intent.getAction().equals(Intent.ACTION_REMOTE_INTENT)) { if (!"google.com".equals(intent.getStringExtra("from"))) { Slog.w(TAG, "Ignoring master clear request -- not from trusted server."); @@ -37,12 +37,23 @@ public class MasterClearReceiver extends BroadcastReceiver { } } - try { - Slog.w(TAG, "!!! FACTORY RESET !!!"); - RecoverySystem.rebootWipeUserData(context); - Log.wtf(TAG, "Still running after master clear?!"); - } catch (IOException e) { - Slog.e(TAG, "Can't perform master clear/factory reset", e); - } + Slog.w(TAG, "!!! FACTORY RESET !!!"); + // The reboot call is blocking, so we need to do it on another thread. + Thread thr = new Thread("Reboot") { + @Override + public void run() { + try { + if (intent.hasExtra("enableEFS")) { + RecoverySystem.rebootToggleEFS(context, intent.getBooleanExtra("enableEFS", false)); + } else { + RecoverySystem.rebootWipeUserData(context); + } + Log.wtf(TAG, "Still running after master clear?!"); + } catch (IOException e) { + Slog.e(TAG, "Can't perform master clear/factory reset", e); + } + } + }; + thr.start(); } } diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java index 542c740..297cbbb 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -16,31 +16,56 @@ package com.android.server; +import com.android.internal.app.IMediaContainerService; import com.android.server.am.ActivityManagerService; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.ServiceConnection; import android.content.pm.PackageManager; +import android.content.res.ObbInfo; import android.net.Uri; -import android.os.storage.IMountService; -import android.os.storage.IMountServiceListener; -import android.os.storage.IMountShutdownObserver; -import android.os.storage.StorageResultCode; +import android.os.Binder; +import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; +import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; -import android.os.IBinder; -import android.os.Environment; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.storage.IMountService; +import android.os.storage.IMountServiceListener; +import android.os.storage.IMountShutdownObserver; +import android.os.storage.IObbActionListener; +import android.os.storage.OnObbStateChangeListener; +import android.os.storage.StorageResultCode; import android.util.Slog; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; +import java.math.BigInteger; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; import java.util.ArrayList; +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.Map.Entry; + +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; /** * MountService implements back-end services for platform storage @@ -53,9 +78,12 @@ class MountService extends IMountService.Stub private static final boolean LOCAL_LOGD = false; private static final boolean DEBUG_UNMOUNT = false; private static final boolean DEBUG_EVENTS = false; - + private static final boolean DEBUG_OBB = true; + private static final String TAG = "MountService"; + private static final String VOLD_TAG = "VoldConnector"; + /* * Internal vold volume state constants */ @@ -130,6 +158,114 @@ class MountService extends IMountService.Stub */ final private HashSet<String> mAsecMountSet = new HashSet<String>(); + /** + * The size of the crypto algorithm key in bits for OBB files. Currently + * Twofish is used which takes 128-bit keys. + */ + private static final int CRYPTO_ALGORITHM_KEY_SIZE = 128; + + /** + * The number of times to run SHA1 in the PBKDF2 function for OBB files. + * 1024 is reasonably secure and not too slow. + */ + private static final int PBKDF2_HASH_ROUNDS = 1024; + + /** + * Mounted OBB tracking information. Used to track the current state of all + * OBBs. + */ + final private Map<IBinder, List<ObbState>> mObbMounts = new HashMap<IBinder, List<ObbState>>(); + final private Map<String, ObbState> mObbPathToStateMap = new HashMap<String, ObbState>(); + + class ObbState implements IBinder.DeathRecipient { + public ObbState(String filename, int callerUid, IObbActionListener token, int nonce) + throws RemoteException { + this.filename = filename; + this.callerUid = callerUid; + this.token = token; + this.nonce = nonce; + } + + // OBB source filename + String filename; + + // Binder.callingUid() + final public int callerUid; + + // Token of remote Binder caller + final IObbActionListener token; + + // Identifier to pass back to the token + final int nonce; + + public IBinder getBinder() { + return token.asBinder(); + } + + @Override + public void binderDied() { + ObbAction action = new UnmountObbAction(this, true); + mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action)); + } + + public void link() throws RemoteException { + getBinder().linkToDeath(this, 0); + } + + public void unlink() { + getBinder().unlinkToDeath(this, 0); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("ObbState{"); + sb.append("filename="); + sb.append(filename); + sb.append(",token="); + sb.append(token.toString()); + sb.append(",callerUid="); + sb.append(callerUid); + sb.append('}'); + return sb.toString(); + } + } + + // OBB Action Handler + final private ObbActionHandler mObbActionHandler; + + // OBB action handler messages + private static final int OBB_RUN_ACTION = 1; + private static final int OBB_MCS_BOUND = 2; + private static final int OBB_MCS_UNBIND = 3; + private static final int OBB_MCS_RECONNECT = 4; + private static final int OBB_FLUSH_MOUNT_STATE = 5; + + /* + * Default Container Service information + */ + static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName( + "com.android.defcontainer", "com.android.defcontainer.DefaultContainerService"); + + final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection(); + + class DefaultContainerConnection implements ServiceConnection { + public void onServiceConnected(ComponentName name, IBinder service) { + if (DEBUG_OBB) + Slog.i(TAG, "onServiceConnected"); + IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service); + mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_MCS_BOUND, imcs)); + } + + public void onServiceDisconnected(ComponentName name) { + if (DEBUG_OBB) + Slog.i(TAG, "onServiceDisconnected"); + } + }; + + // Used in the ObbActionHandler + private IMediaContainerService mContainerService = null; + + // Handler messages private static final int H_UNMOUNT_PM_UPDATE = 1; private static final int H_UNMOUNT_PM_DONE = 2; private static final int H_UNMOUNT_MS = 3; @@ -137,9 +273,9 @@ class MountService extends IMountService.Stub private static final int MAX_UNMOUNT_RETRIES = 4; class UnmountCallBack { - String path; + final String path; + final boolean force; int retries; - boolean force; UnmountCallBack(String path, boolean force) { retries = 0; @@ -154,7 +290,7 @@ class MountService extends IMountService.Stub } class UmsEnableCallBack extends UnmountCallBack { - String method; + final String method; UmsEnableCallBack(String path, String method, boolean force) { super(path, force); @@ -287,7 +423,7 @@ class MountService extends IMountService.Stub Slog.w(TAG, "Waiting too long for mReady!"); } } - + private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); @@ -344,12 +480,12 @@ class MountService extends IMountService.Stub MountServiceBinderListener(IMountServiceListener listener) { mListener = listener; - + } public void binderDied() { if (LOCAL_LOGD) Slog.d(TAG, "An IMountServiceListener has died!"); - synchronized(mListeners) { + synchronized (mListeners) { mListeners.remove(this); mListener.asBinder().unlinkToDeath(this, 0); } @@ -380,12 +516,23 @@ class MountService extends IMountService.Stub Slog.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state)); return; } - // Update state on PackageManager + 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); } + String oldState = mLegacyState; mLegacyState = state; @@ -642,10 +789,21 @@ class MountService extends IMountService.Stub } private boolean doGetShareMethodAvailable(String method) { - ArrayList<String> rsp = mConnector.doCommand("share status " + method); + ArrayList<String> rsp; + try { + rsp = mConnector.doCommand("share status " + method); + } catch (NativeDaemonConnectorException ex) { + Slog.e(TAG, "Failed to determine whether share method " + method + " is available."); + return false; + } for (String line : rsp) { - String []tok = line.split(" "); + String[] tok = line.split(" "); + if (tok.length < 3) { + Slog.e(TAG, "Malformed response to share status " + method); + return false; + } + int code; try { code = Integer.parseInt(tok[0]); @@ -727,6 +885,15 @@ class MountService extends IMountService.Stub if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) { return VoldResponseCode.OpFailedVolNotMounted; } + + /* + * Force a GC to make sure AssetManagers in other threads of the + * system_server are cleaned up. We have to do this since AssetManager + * instances are kept as a WeakReference and it's possible we have files + * open on the external storage. + */ + Runtime.getRuntime().gc(); + // Redundant probably. But no harm in updating state again. mPms.updateExternalMediaStatus(false, false); try { @@ -770,10 +937,22 @@ class MountService extends IMountService.Stub private boolean doGetVolumeShared(String path, String method) { String cmd = String.format("volume shared %s %s", path, method); - ArrayList<String> rsp = mConnector.doCommand(cmd); + ArrayList<String> rsp; + + try { + rsp = mConnector.doCommand(cmd); + } catch (NativeDaemonConnectorException ex) { + Slog.e(TAG, "Failed to read response to volume shared " + path + " " + method); + return false; + } for (String line : rsp) { - String []tok = line.split(" "); + String[] tok = line.split(" "); + if (tok.length < 3) { + Slog.e(TAG, "Malformed response to volume shared " + path + " " + method + " command"); + return false; + } + int code; try { code = Integer.parseInt(tok[0]); @@ -782,9 +961,7 @@ class MountService extends IMountService.Stub return false; } if (code == VoldResponseCode.ShareEnabledResult) { - if (tok[2].equals("enabled")) - return true; - return false; + return "enabled".equals(tok[2]); } else { Slog.e(TAG, String.format("Unexpected response code %d", code)); return false; @@ -873,6 +1050,9 @@ class MountService extends IMountService.Stub mHandlerThread.start(); mHandler = new MountServiceHandler(mHandlerThread.getLooper()); + // Add OBB Action Handler to MountService thread. + mObbActionHandler = new ObbActionHandler(mHandlerThread.getLooper()); + /* * Vold does not run in the simulator, so pretend the connector thread * ran and did its thing. @@ -883,9 +1063,15 @@ class MountService extends IMountService.Stub return; } - mConnector = new NativeDaemonConnector(this, "vold", 10, "VoldConnector"); + /* + * Create the connection to vold with a maximum queue of twice the + * amount of containers we'd ever expect to have. This keeps an + * "asec list" from blocking a thread repeatedly. + */ + mConnector = new NativeDaemonConnector(this, "vold", + PackageManagerService.MAX_CONTAINERS * 2, VOLD_TAG); mReady = false; - Thread thread = new Thread(mConnector, NativeDaemonConnector.class.getName()); + Thread thread = new Thread(mConnector, VOLD_TAG); thread.start(); } @@ -1170,6 +1356,14 @@ class MountService extends IMountService.Stub waitForReady(); warnOnNotMounted(); + /* + * Force a GC to make sure AssetManagers in other threads of the + * system_server are cleaned up. We have to do this since AssetManager + * instances are kept as a WeakReference and it's possible we have files + * open on the external storage. + */ + Runtime.getRuntime().gc(); + int rc = StorageResultCode.OperationSucceeded; try { mConnector.doCommand(String.format("asec destroy %s%s", id, (force ? " force" : ""))); @@ -1234,6 +1428,14 @@ class MountService extends IMountService.Stub } } + /* + * Force a GC to make sure AssetManagers in other threads of the + * system_server are cleaned up. We have to do this since AssetManager + * instances are kept as a WeakReference and it's possible we have files + * open on the external storage. + */ + Runtime.getRuntime().gc(); + int rc = StorageResultCode.OperationSucceeded; String cmd = String.format("asec unmount %s%s", id, (force ? " force" : "")); try { @@ -1317,5 +1519,600 @@ class MountService extends IMountService.Stub public void finishMediaUpdate() { mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE); } + + private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) { + if (callerUid == android.os.Process.SYSTEM_UID) { + return true; + } + + if (packageName == null) { + return false; + } + + final int packageUid = mPms.getPackageUid(packageName); + + if (DEBUG_OBB) { + Slog.d(TAG, "packageName = " + packageName + ", packageUid = " + + packageUid + ", callerUid = " + callerUid); + } + + return callerUid == packageUid; + } + + public String getMountedObbPath(String filename) { + if (filename == null) { + throw new IllegalArgumentException("filename cannot be null"); + } + + waitForReady(); + warnOnNotMounted(); + + try { + ArrayList<String> rsp = mConnector.doCommand(String.format("obb path %s", filename)); + String []tok = rsp.get(0).split(" "); + int code = Integer.parseInt(tok[0]); + if (code != VoldResponseCode.AsecPathResult) { + throw new IllegalStateException(String.format("Unexpected response code %d", code)); + } + return tok[1]; + } catch (NativeDaemonConnectorException e) { + int code = e.getCode(); + if (code == VoldResponseCode.OpFailedStorageNotFound) { + return null; + } else { + throw new IllegalStateException(String.format("Unexpected response code %d", code)); + } + } + } + + public boolean isObbMounted(String filename) { + if (filename == null) { + throw new IllegalArgumentException("filename cannot be null"); + } + + synchronized (mObbMounts) { + return mObbPathToStateMap.containsKey(filename); + } + } + + public void mountObb(String filename, String key, IObbActionListener token, int nonce) + throws RemoteException { + if (filename == null) { + throw new IllegalArgumentException("filename cannot be null"); + } + + if (token == null) { + throw new IllegalArgumentException("token cannot be null"); + } + + final int callerUid = Binder.getCallingUid(); + final ObbState obbState = new ObbState(filename, callerUid, token, nonce); + final ObbAction action = new MountObbAction(obbState, key); + mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action)); + + if (DEBUG_OBB) + Slog.i(TAG, "Send to OBB handler: " + action.toString()); + } + + public void unmountObb(String filename, boolean force, IObbActionListener token, int nonce) + throws RemoteException { + if (filename == null) { + throw new IllegalArgumentException("filename cannot be null"); + } + + final int callerUid = Binder.getCallingUid(); + final ObbState obbState = new ObbState(filename, callerUid, token, nonce); + final ObbAction action = new UnmountObbAction(obbState, force); + mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action)); + + if (DEBUG_OBB) + Slog.i(TAG, "Send to OBB handler: " + action.toString()); + } + + private void addObbStateLocked(ObbState obbState) throws RemoteException { + final IBinder binder = obbState.getBinder(); + List<ObbState> obbStates = mObbMounts.get(binder); + + if (obbStates == null) { + obbStates = new ArrayList<ObbState>(); + mObbMounts.put(binder, obbStates); + } else { + for (final ObbState o : obbStates) { + if (o.filename.equals(obbState.filename)) { + throw new IllegalStateException("Attempt to add ObbState twice. " + + "This indicates an error in the MountService logic."); + } + } + } + + obbStates.add(obbState); + try { + obbState.link(); + } catch (RemoteException e) { + /* + * The binder died before we could link it, so clean up our state + * and return failure. + */ + obbStates.remove(obbState); + if (obbStates.isEmpty()) { + mObbMounts.remove(binder); + } + + // Rethrow the error so mountObb can get it + throw e; + } + + mObbPathToStateMap.put(obbState.filename, obbState); + } + + private void removeObbStateLocked(ObbState obbState) { + final IBinder binder = obbState.getBinder(); + final List<ObbState> obbStates = mObbMounts.get(binder); + if (obbStates != null) { + if (obbStates.remove(obbState)) { + obbState.unlink(); + } + if (obbStates.isEmpty()) { + mObbMounts.remove(binder); + } + } + + mObbPathToStateMap.remove(obbState.filename); + } + + private class ObbActionHandler extends Handler { + private boolean mBound = false; + private final List<ObbAction> mActions = new LinkedList<ObbAction>(); + + ObbActionHandler(Looper l) { + super(l); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case OBB_RUN_ACTION: { + final ObbAction action = (ObbAction) msg.obj; + + if (DEBUG_OBB) + Slog.i(TAG, "OBB_RUN_ACTION: " + action.toString()); + + // If a bind was already initiated we don't really + // need to do anything. The pending install + // will be processed later on. + if (!mBound) { + // If this is the only one pending we might + // have to bind to the service again. + if (!connectToService()) { + Slog.e(TAG, "Failed to bind to media container service"); + action.handleError(); + return; + } + } + + mActions.add(action); + break; + } + case OBB_MCS_BOUND: { + if (DEBUG_OBB) + Slog.i(TAG, "OBB_MCS_BOUND"); + if (msg.obj != null) { + mContainerService = (IMediaContainerService) msg.obj; + } + if (mContainerService == null) { + // Something seriously wrong. Bail out + Slog.e(TAG, "Cannot bind to media container service"); + for (ObbAction action : mActions) { + // Indicate service bind error + action.handleError(); + } + mActions.clear(); + } else if (mActions.size() > 0) { + final ObbAction action = mActions.get(0); + if (action != null) { + action.execute(this); + } + } else { + // Should never happen ideally. + Slog.w(TAG, "Empty queue"); + } + break; + } + case OBB_MCS_RECONNECT: { + if (DEBUG_OBB) + Slog.i(TAG, "OBB_MCS_RECONNECT"); + if (mActions.size() > 0) { + if (mBound) { + disconnectService(); + } + if (!connectToService()) { + Slog.e(TAG, "Failed to bind to media container service"); + for (ObbAction action : mActions) { + // Indicate service bind error + action.handleError(); + } + mActions.clear(); + } + } + break; + } + case OBB_MCS_UNBIND: { + if (DEBUG_OBB) + Slog.i(TAG, "OBB_MCS_UNBIND"); + + // Delete pending install + if (mActions.size() > 0) { + mActions.remove(0); + } + if (mActions.size() == 0) { + if (mBound) { + disconnectService(); + } + } else { + // There are more pending requests in queue. + // Just post MCS_BOUND message to trigger processing + // of next pending install. + mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND); + } + break; + } + case OBB_FLUSH_MOUNT_STATE: { + final String path = (String) msg.obj; + + if (DEBUG_OBB) + Slog.i(TAG, "Flushing all OBB state for path " + path); + + synchronized (mObbMounts) { + final List<ObbState> obbStatesToRemove = new LinkedList<ObbState>(); + + final Iterator<Entry<String, ObbState>> i = + mObbPathToStateMap.entrySet().iterator(); + while (i.hasNext()) { + final Entry<String, ObbState> obbEntry = i.next(); + + /* + * If this entry's source file is in the volume path + * that got unmounted, remove it because it's no + * longer valid. + */ + if (obbEntry.getKey().startsWith(path)) { + obbStatesToRemove.add(obbEntry.getValue()); + } + } + + for (final ObbState obbState : obbStatesToRemove) { + if (DEBUG_OBB) + Slog.i(TAG, "Removing state for " + obbState.filename); + + removeObbStateLocked(obbState); + + try { + obbState.token.onObbResult(obbState.filename, obbState.nonce, + OnObbStateChangeListener.UNMOUNTED); + } catch (RemoteException e) { + Slog.i(TAG, "Couldn't send unmount notification for OBB: " + + obbState.filename); + } + } + } + break; + } + } + } + + private boolean connectToService() { + if (DEBUG_OBB) + Slog.i(TAG, "Trying to bind to DefaultContainerService"); + + Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT); + if (mContext.bindService(service, mDefContainerConn, Context.BIND_AUTO_CREATE)) { + mBound = true; + return true; + } + return false; + } + + private void disconnectService() { + mContainerService = null; + mBound = false; + mContext.unbindService(mDefContainerConn); + } + } + + abstract class ObbAction { + private static final int MAX_RETRIES = 3; + private int mRetries; + + ObbState mObbState; + + ObbAction(ObbState obbState) { + mObbState = obbState; + } + + public void execute(ObbActionHandler handler) { + try { + if (DEBUG_OBB) + Slog.i(TAG, "Starting to execute action: " + this.toString()); + mRetries++; + if (mRetries > MAX_RETRIES) { + Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up"); + mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND); + handleError(); + return; + } else { + handleExecute(); + if (DEBUG_OBB) + Slog.i(TAG, "Posting install MCS_UNBIND"); + mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND); + } + } catch (RemoteException e) { + if (DEBUG_OBB) + Slog.i(TAG, "Posting install MCS_RECONNECT"); + mObbActionHandler.sendEmptyMessage(OBB_MCS_RECONNECT); + } catch (Exception e) { + if (DEBUG_OBB) + Slog.d(TAG, "Error handling OBB action", e); + handleError(); + mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND); + } + } + + abstract void handleExecute() throws RemoteException, IOException; + abstract void handleError(); + + protected ObbInfo getObbInfo() throws IOException { + ObbInfo obbInfo; + try { + obbInfo = mContainerService.getObbInfo(mObbState.filename); + } catch (RemoteException e) { + Slog.d(TAG, "Couldn't call DefaultContainerService to fetch OBB info for " + + mObbState.filename); + obbInfo = null; + } + if (obbInfo == null) { + throw new IOException("Couldn't read OBB file: " + mObbState.filename); + } + return obbInfo; + } + + protected void sendNewStatusOrIgnore(int status) { + if (mObbState == null || mObbState.token == null) { + return; + } + + try { + mObbState.token.onObbResult(mObbState.filename, mObbState.nonce, status); + } catch (RemoteException e) { + Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged"); + } + } + } + + class MountObbAction extends ObbAction { + private String mKey; + + MountObbAction(ObbState obbState, String key) { + super(obbState); + mKey = key; + } + + public void handleExecute() throws IOException, RemoteException { + waitForReady(); + warnOnNotMounted(); + + final ObbInfo obbInfo = getObbInfo(); + + if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mObbState.callerUid)) { + Slog.w(TAG, "Denied attempt to mount OBB " + obbInfo.filename + + " which is owned by " + obbInfo.packageName); + sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED); + return; + } + + final boolean isMounted; + synchronized (mObbMounts) { + isMounted = mObbPathToStateMap.containsKey(obbInfo.filename); + } + if (isMounted) { + Slog.w(TAG, "Attempt to mount OBB which is already mounted: " + obbInfo.filename); + sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_ALREADY_MOUNTED); + return; + } + + /* + * The filename passed in might not be the canonical name, so just + * set the filename to the canonicalized version. + */ + mObbState.filename = obbInfo.filename; + + final String hashedKey; + if (mKey == null) { + hashedKey = "none"; + } else { + try { + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + + KeySpec ks = new PBEKeySpec(mKey.toCharArray(), obbInfo.salt, + PBKDF2_HASH_ROUNDS, CRYPTO_ALGORITHM_KEY_SIZE); + SecretKey key = factory.generateSecret(ks); + BigInteger bi = new BigInteger(key.getEncoded()); + hashedKey = bi.toString(16); + } catch (NoSuchAlgorithmException e) { + Slog.e(TAG, "Could not load PBKDF2 algorithm", e); + sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); + return; + } catch (InvalidKeySpecException e) { + Slog.e(TAG, "Invalid key spec when loading PBKDF2 algorithm", e); + sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); + return; + } + } + + int rc = StorageResultCode.OperationSucceeded; + String cmd = String.format("obb mount %s %s %d", mObbState.filename, hashedKey, + mObbState.callerUid); + try { + mConnector.doCommand(cmd); + } catch (NativeDaemonConnectorException e) { + int code = e.getCode(); + if (code != VoldResponseCode.OpFailedStorageBusy) { + rc = StorageResultCode.OperationFailedInternalError; + } + } + + if (rc == StorageResultCode.OperationSucceeded) { + if (DEBUG_OBB) + Slog.d(TAG, "Successfully mounted OBB " + mObbState.filename); + + synchronized (mObbMounts) { + addObbStateLocked(mObbState); + } + + sendNewStatusOrIgnore(OnObbStateChangeListener.MOUNTED); + } else { + Slog.e(TAG, "Couldn't mount OBB file: " + rc); + + sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT); + } + } + + public void handleError() { + sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("MountObbAction{"); + sb.append("filename="); + sb.append(mObbState.filename); + sb.append(",callerUid="); + sb.append(mObbState.callerUid); + sb.append(",token="); + sb.append(mObbState.token != null ? mObbState.token.toString() : "NULL"); + sb.append(",binder="); + sb.append(mObbState.token != null ? mObbState.getBinder().toString() : "null"); + sb.append('}'); + return sb.toString(); + } + } + + class UnmountObbAction extends ObbAction { + private boolean mForceUnmount; + + UnmountObbAction(ObbState obbState, boolean force) { + super(obbState); + mForceUnmount = force; + } + + public void handleExecute() throws IOException { + waitForReady(); + warnOnNotMounted(); + + final ObbInfo obbInfo = getObbInfo(); + + final ObbState obbState; + synchronized (mObbMounts) { + obbState = mObbPathToStateMap.get(obbInfo.filename); + } + + if (obbState == null) { + sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_NOT_MOUNTED); + return; + } + + if (obbState.callerUid != mObbState.callerUid) { + Slog.w(TAG, "Permission denied attempting to unmount OBB " + obbInfo.filename + + " (owned by " + obbInfo.packageName + ")"); + sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED); + return; + } + + mObbState.filename = obbInfo.filename; + + int rc = StorageResultCode.OperationSucceeded; + String cmd = String.format("obb unmount %s%s", mObbState.filename, + (mForceUnmount ? " force" : "")); + try { + mConnector.doCommand(cmd); + } catch (NativeDaemonConnectorException e) { + int code = e.getCode(); + if (code == VoldResponseCode.OpFailedStorageBusy) { + rc = StorageResultCode.OperationFailedStorageBusy; + } else if (code == VoldResponseCode.OpFailedStorageNotFound) { + // If it's not mounted then we've already won. + rc = StorageResultCode.OperationSucceeded; + } else { + rc = StorageResultCode.OperationFailedInternalError; + } + } + + if (rc == StorageResultCode.OperationSucceeded) { + synchronized (mObbMounts) { + removeObbStateLocked(obbState); + } + + sendNewStatusOrIgnore(OnObbStateChangeListener.UNMOUNTED); + } else { + Slog.w(TAG, "Could not mount OBB: " + mObbState.filename); + sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_UNMOUNT); + } + } + + public void handleError() { + sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("UnmountObbAction{"); + sb.append("filename="); + sb.append(mObbState.filename != null ? mObbState.filename : "null"); + sb.append(",force="); + sb.append(mForceUnmount); + sb.append(",callerUid="); + sb.append(mObbState.callerUid); + sb.append(",token="); + sb.append(mObbState.token != null ? mObbState.token.toString() : "null"); + sb.append(",binder="); + sb.append(mObbState.token != null ? mObbState.getBinder().toString() : "null"); + sb.append('}'); + return sb.toString(); + } + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump ActivityManager from from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + + " without permission " + android.Manifest.permission.DUMP); + return; + } + + synchronized (mObbMounts) { + pw.println(" mObbMounts:"); + + final Iterator<Entry<IBinder, List<ObbState>>> binders = mObbMounts.entrySet().iterator(); + while (binders.hasNext()) { + Entry<IBinder, List<ObbState>> e = binders.next(); + pw.print(" Key="); pw.println(e.getKey().toString()); + final List<ObbState> obbStates = e.getValue(); + for (final ObbState obbState : obbStates) { + pw.print(" "); pw.println(obbState.toString()); + } + } + + pw.println(""); + pw.println(" mObbPathToStateMap:"); + final Iterator<Entry<String, ObbState>> maps = mObbPathToStateMap.entrySet().iterator(); + while (maps.hasNext()) { + final Entry<String, ObbState> e = maps.next(); + pw.print(" "); pw.print(e.getKey()); + pw.print(" -> "); pw.println(e.getValue().toString()); + } + } + } } diff --git a/services/java/com/android/server/NativeDaemonConnector.java b/services/java/com/android/server/NativeDaemonConnector.java index 08d7ce6..7b68d68 100644 --- a/services/java/com/android/server/NativeDaemonConnector.java +++ b/services/java/com/android/server/NativeDaemonConnector.java @@ -109,6 +109,10 @@ final class NativeDaemonConnector implements Runnable { int count = inputStream.read(buffer, start, BUFFER_SIZE - start); if (count < 0) break; + // Add our starting point to the count and reset the start. + count += start; + start = 0; + for (int i = 0; i < count; i++) { if (buffer[i] == 0) { String event = new String(buffer, start, i - start); @@ -128,12 +132,11 @@ final class NativeDaemonConnector implements Runnable { Slog.e(TAG, String.format( "Error handling '%s'", event), ex); } - } else { - try { - mResponseQueue.put(event); - } catch (InterruptedException ex) { - Slog.e(TAG, "Failed to put response onto queue", ex); - } + } + try { + mResponseQueue.put(event); + } catch (InterruptedException ex) { + Slog.e(TAG, "Failed to put response onto queue", ex); } } catch (NumberFormatException nfe) { Slog.w(TAG, String.format("Bad msg (%s)", event)); @@ -141,6 +144,9 @@ final class NativeDaemonConnector implements Runnable { start = i + 1; } } + + // We should end at the amount we read. If not, compact then + // buffer and read again. if (start != count) { final int remaining = BUFFER_SIZE - start; System.arraycopy(buffer, start, buffer, 0, remaining); @@ -174,7 +180,8 @@ final class NativeDaemonConnector implements Runnable { } } - private void sendCommand(String command) { + private void sendCommand(String command) + throws NativeDaemonConnectorException { sendCommand(command, null); } @@ -184,11 +191,13 @@ final class NativeDaemonConnector implements Runnable { * @param command The command to send to the daemon * @param argument The argument to send with the command (or null) */ - private void sendCommand(String command, String argument) { + private void sendCommand(String command, String argument) + throws NativeDaemonConnectorException { synchronized (this) { if (LOCAL_LOGD) Slog.d(TAG, String.format("SND -> {%s} {%s}", command, argument)); if (mOutputStream == null) { Slog.e(TAG, "No connection to daemon", new IllegalStateException()); + throw new NativeDaemonConnectorException("No output stream!"); } else { StringBuilder builder = new StringBuilder(command); if (argument != null) { @@ -218,6 +227,7 @@ final class NativeDaemonConnector implements Runnable { while (!complete) { try { + // TODO - this should not block forever String line = mResponseQueue.take(); if (LOCAL_LOGD) Slog.d(TAG, String.format("RSP <- {%s}", line)); String[] tokens = line.split(" "); diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index cbbc7be..f0acdc0 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -35,6 +35,7 @@ import android.text.TextUtils; import android.util.Log; import android.util.Slog; import java.util.ArrayList; +import java.util.NoSuchElementException; import java.util.StringTokenizer; import android.provider.Settings; import android.content.ContentResolver; @@ -46,6 +47,7 @@ import java.lang.IllegalStateException; import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.concurrent.CountDownLatch; /** * @hide @@ -53,6 +55,8 @@ import java.net.UnknownHostException; class NetworkManagementService extends INetworkManagementService.Stub { private static final String TAG = "NetworkManagmentService"; + private static final boolean DBG = false; + private static final String NETD_TAG = "NetdConnector"; class NetdResponseCode { public static final int InterfaceListResult = 110; @@ -83,6 +87,9 @@ class NetworkManagementService extends INetworkManagementService.Stub { */ private NativeDaemonConnector mConnector; + private Thread mThread; + private final CountDownLatch mConnectedSignal = new CountDownLatch(1); + private ArrayList<INetworkManagementEventObserver> mObservers; /** @@ -90,9 +97,8 @@ class NetworkManagementService extends INetworkManagementService.Stub { * * @param context Binder context for this service */ - public NetworkManagementService(Context context) { + private NetworkManagementService(Context context) { mContext = context; - mObservers = new ArrayList<INetworkManagementEventObserver>(); if ("simulator".equals(SystemProperties.get("ro.product.device"))) { @@ -100,9 +106,18 @@ class NetworkManagementService extends INetworkManagementService.Stub { } mConnector = new NativeDaemonConnector( - new NetdCallbackReceiver(), "netd", 10, "NetdConnector"); - Thread thread = new Thread(mConnector, NativeDaemonConnector.class.getName()); - thread.start(); + new NetdCallbackReceiver(), "netd", 10, NETD_TAG); + mThread = new Thread(mConnector, NETD_TAG); + } + + public static NetworkManagementService create(Context context) throws InterruptedException { + NetworkManagementService service = new NetworkManagementService(context); + if (DBG) Slog.d(TAG, "Creating NetworkManagementService"); + service.mThread.start(); + if (DBG) Slog.d(TAG, "Awaiting socket connection"); + service.mConnectedSignal.await(); + if (DBG) Slog.d(TAG, "Connected"); + return service; } public void registerObserver(INetworkManagementEventObserver obs) { @@ -154,6 +169,14 @@ class NetworkManagementService extends INetworkManagementService.Stub { } } + /** + * Let us know the daemon is connected + */ + protected void onConnected() { + if (DBG) Slog.d(TAG, "onConnected"); + mConnectedSignal.countDown(); + } + // // Netd Callback handling @@ -161,6 +184,7 @@ class NetworkManagementService extends INetworkManagementService.Stub { class NetdCallbackReceiver implements INativeDaemonConnectorCallbacks { public void onDaemonConnected() { + NetworkManagementService.this.onConnected(); new Thread() { public void run() { } @@ -226,44 +250,61 @@ class NetworkManagementService extends INetworkManagementService.Stub { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); - return mConnector.doListCommand("interface list", NetdResponseCode.InterfaceListResult); + try { + return mConnector.doListCommand("interface list", NetdResponseCode.InterfaceListResult); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Cannot communicate with native daemon to list interfaces"); + } } public InterfaceConfiguration getInterfaceConfig(String iface) throws IllegalStateException { - String rsp = mConnector.doCommand("interface getcfg " + iface).get(0); + String rsp; + try { + rsp = mConnector.doCommand("interface getcfg " + iface).get(0); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Cannot communicate with native daemon to get interface config"); + } 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] StringTokenizer st = new StringTokenizer(rsp); + InterfaceConfiguration cfg; try { - int code = Integer.parseInt(st.nextToken(" ")); - if (code != NetdResponseCode.InterfaceGetCfgResult) { + try { + int code = Integer.parseInt(st.nextToken(" ")); + if (code != NetdResponseCode.InterfaceGetCfgResult) { + throw new IllegalStateException( + String.format("Expected code %d, but got %d", + NetdResponseCode.InterfaceGetCfgResult, code)); + } + } catch (NumberFormatException nfe) { throw new IllegalStateException( - String.format("Expected code %d, but got %d", - NetdResponseCode.InterfaceGetCfgResult, code)); + String.format("Invalid response from daemon (%s)", rsp)); } - } catch (NumberFormatException nfe) { - throw new IllegalStateException( - String.format("Invalid response from daemon (%s)", rsp)); - } - InterfaceConfiguration cfg = new InterfaceConfiguration(); - cfg.hwAddr = st.nextToken(" "); - try { - cfg.ipAddr = stringToIpAddr(st.nextToken(" ")); - } catch (UnknownHostException uhe) { - Slog.e(TAG, "Failed to parse ipaddr", uhe); - cfg.ipAddr = 0; - } + cfg = new InterfaceConfiguration(); + cfg.hwAddr = st.nextToken(" "); + try { + cfg.ipAddr = stringToIpAddr(st.nextToken(" ")); + } catch (UnknownHostException uhe) { + Slog.e(TAG, "Failed to parse ipaddr", uhe); + cfg.ipAddr = 0; + } - try { - cfg.netmask = stringToIpAddr(st.nextToken(" ")); - } catch (UnknownHostException uhe) { - Slog.e(TAG, "Failed to parse netmask", uhe); - cfg.netmask = 0; + try { + cfg.netmask = stringToIpAddr(st.nextToken(" ")); + } catch (UnknownHostException uhe) { + Slog.e(TAG, "Failed to parse netmask", uhe); + cfg.netmask = 0; + } + cfg.interfaceFlags = st.nextToken("]").trim() +"]"; + } catch (NoSuchElementException nsee) { + throw new IllegalStateException( + String.format("Invalid response from daemon (%s)", rsp)); } - cfg.interfaceFlags = st.nextToken("]").trim() +"]"; Slog.d(TAG, String.format("flags <%s>", cfg.interfaceFlags)); return cfg; } @@ -272,7 +313,12 @@ class NetworkManagementService extends INetworkManagementService.Stub { 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); - mConnector.doCommand(cmd); + try { + mConnector.doCommand(cmd); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Unable to communicate with native daemon to interface setcfg"); + } } public void shutdown() { @@ -289,20 +335,25 @@ class NetworkManagementService extends INetworkManagementService.Stub { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); - ArrayList<String> rsp = mConnector.doCommand("ipfwd status"); + ArrayList<String> rsp; + try { + rsp = mConnector.doCommand("ipfwd status"); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Unable to communicate with native daemon to ipfwd status"); + } for (String line : rsp) { - String []tok = line.split(" "); + String[] tok = line.split(" "); + if (tok.length < 3) { + Slog.e(TAG, "Malformed response from native daemon: " + line); + return false; + } + int code = Integer.parseInt(tok[0]); if (code == NetdResponseCode.IpFwdStatusResult) { // 211 Forwarding <enabled/disabled> - if (tok.length !=2) { - throw new IllegalStateException( - String.format("Malformatted list entry '%s'", line)); - } - if (tok[2].equals("enabled")) - return true; - return false; + return "enabled".equals(tok[2]); } else { throw new IllegalStateException(String.format("Unexpected response code %d", code)); } @@ -326,29 +377,45 @@ class NetworkManagementService extends INetworkManagementService.Stub { for (String d : dhcpRange) { cmd += " " + d; } - mConnector.doCommand(cmd); + + try { + mConnector.doCommand(cmd); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException("Unable to communicate to native daemon"); + } } public void stopTethering() throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand("tether stop"); + try { + mConnector.doCommand("tether stop"); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException("Unable to communicate to native daemon to stop tether"); + } } public boolean isTetheringStarted() throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); - ArrayList<String> rsp = mConnector.doCommand("tether status"); + ArrayList<String> rsp; + try { + rsp = mConnector.doCommand("tether status"); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Unable to communicate to native daemon to get tether status"); + } for (String line : rsp) { - String []tok = line.split(" "); + String[] tok = line.split(" "); + if (tok.length < 3) { + throw new IllegalStateException("Malformed response for tether status: " + line); + } int code = Integer.parseInt(tok[0]); if (code == NetdResponseCode.TetherStatusResult) { // XXX: Tethering services <started/stopped> <TBD>... - if (tok[2].equals("started")) - return true; - return false; + return "started".equals(tok[2]); } else { throw new IllegalStateException(String.format("Unexpected response code %d", code)); } @@ -359,20 +426,35 @@ class NetworkManagementService extends INetworkManagementService.Stub { public void tetherInterface(String iface) throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand("tether interface add " + iface); + try { + mConnector.doCommand("tether interface add " + iface); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Unable to communicate to native daemon for adding tether interface"); + } } public void untetherInterface(String iface) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand("tether interface remove " + iface); + try { + mConnector.doCommand("tether interface remove " + iface); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Unable to communicate to native daemon for removing tether interface"); + } } public String[] listTetheredInterfaces() throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); - return mConnector.doListCommand( - "tether interface list", NetdResponseCode.TetherInterfaceListResult); + try { + return mConnector.doListCommand( + "tether interface list", NetdResponseCode.TetherInterfaceListResult); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Unable to communicate to native daemon for listing tether interfaces"); + } } public void setDnsForwarders(String[] dns) throws IllegalStateException { @@ -383,7 +465,12 @@ class NetworkManagementService extends INetworkManagementService.Stub { for (String s : dns) { cmd += " " + InetAddress.getByName(s).getHostAddress(); } - mConnector.doCommand(cmd); + try { + mConnector.doCommand(cmd); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Unable to communicate to native daemon for setting tether dns"); + } } catch (UnknownHostException e) { throw new IllegalStateException("Error resolving dns name", e); } @@ -392,30 +479,50 @@ class NetworkManagementService extends INetworkManagementService.Stub { public String[] getDnsForwarders() throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); - return mConnector.doListCommand( - "tether dns list", NetdResponseCode.TetherDnsFwdTgtListResult); + try { + return mConnector.doListCommand( + "tether dns list", NetdResponseCode.TetherDnsFwdTgtListResult); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Unable to communicate to native daemon for listing tether dns"); + } } public void enableNat(String internalInterface, String externalInterface) throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand( - String.format("nat enable %s %s", internalInterface, externalInterface)); + try { + mConnector.doCommand( + String.format("nat enable %s %s", internalInterface, externalInterface)); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Unable to communicate to native daemon for enabling NAT interface"); + } } public void disableNat(String internalInterface, String externalInterface) throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand( - String.format("nat disable %s %s", internalInterface, externalInterface)); + try { + mConnector.doCommand( + String.format("nat disable %s %s", internalInterface, externalInterface)); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Unable to communicate to native daemon for disabling NAT interface"); + } } public String[] listTtys() throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); - return mConnector.doListCommand("list_ttys", NetdResponseCode.TtyListResult); + try { + return mConnector.doListCommand("list_ttys", NetdResponseCode.TtyListResult); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Unable to communicate to native daemon for listing TTYs"); + } } public void attachPppd(String tty, String localAddr, String remoteAddr, String dns1Addr, @@ -430,31 +537,52 @@ class NetworkManagementService extends INetworkManagementService.Stub { InetAddress.getByName(dns2Addr).getHostAddress())); } catch (UnknownHostException e) { throw new IllegalStateException("Error resolving addr", e); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException("Error communicating to native daemon to attach pppd", e); } } public void detachPppd(String tty) throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand(String.format("pppd detach %s", tty)); + try { + mConnector.doCommand(String.format("pppd detach %s", tty)); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException("Error communicating to native daemon to detach pppd", e); + } } public void startUsbRNDIS() throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand("usb startrndis"); + try { + mConnector.doCommand("usb startrndis"); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Error communicating to native daemon for starting RNDIS", e); + } } public void stopUsbRNDIS() throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand("usb stoprndis"); + try { + mConnector.doCommand("usb stoprndis"); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException("Error communicating to native daemon", e); + } } public boolean isUsbRNDISStarted() throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); - ArrayList<String> rsp = mConnector.doCommand("usb rndisstatus"); + ArrayList<String> rsp; + try { + rsp = mConnector.doCommand("usb rndisstatus"); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Error communicating to native daemon to check RNDIS status", e); + } for (String line : rsp) { String []tok = line.split(" "); @@ -476,31 +604,35 @@ class NetworkManagementService extends INetworkManagementService.Stub { android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService"); - mConnector.doCommand(String.format("softap stop " + wlanIface)); - mConnector.doCommand(String.format("softap fwreload " + wlanIface + " AP")); - mConnector.doCommand(String.format("softap start " + wlanIface)); - if (wifiConfig == null) { - mConnector.doCommand(String.format("softap set " + wlanIface + " " + softapIface)); - } else { - /** - * softap set arg1 arg2 arg3 [arg4 arg5 arg6 arg7 arg8] - * argv1 - wlan interface - * argv2 - softap interface - * argv3 - SSID - * argv4 - Security - * argv5 - Key - * argv6 - Channel - * 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)); - mConnector.doCommand(str); - } - mConnector.doCommand(String.format("softap startap")); + try { + mConnector.doCommand(String.format("softap stop " + wlanIface)); + mConnector.doCommand(String.format("softap fwreload " + wlanIface + " AP")); + mConnector.doCommand(String.format("softap start " + wlanIface)); + if (wifiConfig == null) { + mConnector.doCommand(String.format("softap set " + wlanIface + " " + softapIface)); + } else { + /** + * softap set arg1 arg2 arg3 [arg4 arg5 arg6 arg7 arg8] + * argv1 - wlan interface + * argv2 - softap interface + * argv3 - SSID + * argv4 - Security + * argv5 - Key + * argv6 - Channel + * 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)); + mConnector.doCommand(str); + } + mConnector.doCommand(String.format("softap startap")); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException("Error communicating to native daemon to start softap", e); + } } private String convertQuotedString(String s) { @@ -516,7 +648,12 @@ class NetworkManagementService extends INetworkManagementService.Stub { android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService"); - mConnector.doCommand("softap stopap"); + try { + mConnector.doCommand("softap stopap"); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException("Error communicating to native daemon to stop soft AP", + e); + } } public void setAccessPoint(WifiConfiguration wifiConfig, String wlanIface, String softapIface) @@ -525,15 +662,19 @@ class NetworkManagementService extends INetworkManagementService.Stub { android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService"); - if (wifiConfig == null) { - mConnector.doCommand(String.format("softap set " + wlanIface + " " + softapIface)); - } else { - 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)); - mConnector.doCommand(str); + try { + if (wifiConfig == null) { + mConnector.doCommand(String.format("softap set " + wlanIface + " " + softapIface)); + } else { + 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)); + mConnector.doCommand(str); + } + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException("Error communicating to native daemon to set soft AP", + e); } } @@ -541,9 +682,22 @@ class NetworkManagementService extends INetworkManagementService.Stub { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); try { - String rsp = mConnector.doCommand( - String.format("interface read%scounter %s", (rx ? "rx" : "tx"), iface)).get(0); - String []tok = rsp.split(" "); + String rsp; + try { + rsp = mConnector.doCommand( + String.format("interface read%scounter %s", (rx ? "rx" : "tx"), iface)).get(0); + } catch (NativeDaemonConnectorException e1) { + Slog.e(TAG, "Error communicating with native daemon", e1); + return -1; + } + + String[] tok = rsp.split(" "); + if (tok.length < 2) { + Slog.e(TAG, String.format("Malformed response for reading %s interface", + (rx ? "rx" : "tx"))); + return -1; + } + int code; try { code = Integer.parseInt(tok[0]); @@ -575,17 +729,34 @@ class NetworkManagementService extends INetworkManagementService.Stub { public void setInterfaceThrottle(String iface, int rxKbps, int txKbps) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand(String.format( - "interface setthrottle %s %d %d", iface, rxKbps, txKbps)); + try { + mConnector.doCommand(String.format( + "interface setthrottle %s %d %d", iface, rxKbps, txKbps)); + } catch (NativeDaemonConnectorException e) { + Slog.e(TAG, "Error communicating with native daemon to set throttle", e); + } } private int getInterfaceThrottle(String iface, boolean rx) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); try { - String rsp = mConnector.doCommand( - String.format("interface getthrottle %s %s", iface,(rx ? "rx" : "tx"))).get(0); - String []tok = rsp.split(" "); + String rsp; + try { + rsp = mConnector.doCommand( + String.format("interface getthrottle %s %s", iface, + (rx ? "rx" : "tx"))).get(0); + } catch (NativeDaemonConnectorException e) { + Slog.e(TAG, "Error communicating with native daemon to getthrottle", e); + return -1; + } + + String[] tok = rsp.split(" "); + if (tok.length < 2) { + Slog.e(TAG, "Malformed response to getthrottle command"); + return -1; + } + int code; try { code = Integer.parseInt(tok[0]); diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index 348accd..4da5eb2 100755 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -16,9 +16,8 @@ package com.android.server; -import com.android.server.status.IconData; -import com.android.server.status.NotificationData; -import com.android.server.status.StatusBarService; +import com.android.internal.statusbar.StatusBarNotification; +import com.android.server.StatusBarManagerService; import android.app.ActivityManagerNative; import android.app.IActivityManager; @@ -39,9 +38,11 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.database.ContentObserver; +import android.hardware.Usb; import android.media.AudioManager; import android.net.Uri; import android.os.BatteryManager; +import android.os.Bundle; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -66,11 +67,14 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; -class NotificationManagerService extends INotificationManager.Stub +/** {@hide} */ +public class NotificationManagerService extends INotificationManager.Stub { private static final String TAG = "NotificationService"; private static final boolean DBG = false; + private static final int MAX_PACKAGE_NOTIFICATIONS = 50; + // message codes private static final int MESSAGE_TIMEOUT = 2; @@ -86,7 +90,7 @@ class NotificationManagerService extends INotificationManager.Stub final IBinder mForegroundToken = new Binder(); private WorkerHandler mHandler; - private StatusBarService mStatusBarService; + private StatusBarManagerService mStatusBar; private LightsService mLightsService; private LightsService.Light mBatteryLight; private LightsService.Light mNotificationLight; @@ -108,10 +112,12 @@ class NotificationManagerService extends INotificationManager.Stub private boolean mScreenOn = true; private boolean mInCall = false; private boolean mNotificationPulseEnabled; + // This is true if we have received a new notification while the screen is off + // (that is, if mLedNotification was set while the screen was off) + // This is reset to false when the screen is turned on. + private boolean mPendingPulseNotification; // for adb connected notifications - private boolean mUsbConnected; - private boolean mAdbEnabled = false; private boolean mAdbNotificationShown = false; private Notification mAdbNotification; @@ -163,16 +169,21 @@ class NotificationManagerService extends INotificationManager.Stub final String pkg; final String tag; final int id; + final int uid; + final int initialPid; ITransientNotification callback; int duration; final Notification notification; IBinder statusBarKey; - NotificationRecord(String pkg, String tag, int id, Notification notification) + NotificationRecord(String pkg, String tag, int id, int uid, int initialPid, + Notification notification) { this.pkg = pkg; this.tag = tag; this.id = id; + this.uid = uid; + this.initialPid = initialPid; this.notification = notification; } @@ -238,8 +249,8 @@ class NotificationManagerService extends INotificationManager.Stub } } - private StatusBarService.NotificationCallbacks mNotificationCallbacks - = new StatusBarService.NotificationCallbacks() { + private StatusBarManagerService.NotificationCallbacks mNotificationCallbacks + = new StatusBarManagerService.NotificationCallbacks() { public void onSetDisabled(int status) { synchronized (mNotificationList) { @@ -302,6 +313,21 @@ class NotificationManagerService extends INotificationManager.Stub updateLightsLocked(); } } + + public void onNotificationError(String pkg, String tag, int id, + 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); + long ident = Binder.clearCallingIdentity(); + try { + ActivityManagerNative.getDefault().crashApplication(uid, initialPid, pkg, + "Bad notification posted from package " + pkg + + ": " + message); + } catch (RemoteException e) { + } + Binder.restoreCallingIdentity(ident); + } }; private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @@ -326,12 +352,14 @@ class NotificationManagerService extends INotificationManager.Stub mBatteryFull = batteryFull; updateLights(); } - } else if (action.equals(Intent.ACTION_UMS_CONNECTED)) { - mUsbConnected = true; - updateAdbNotification(); - } else if (action.equals(Intent.ACTION_UMS_DISCONNECTED)) { - mUsbConnected = false; - updateAdbNotification(); + } else if (action.equals(Usb.ACTION_USB_STATE)) { + Bundle extras = intent.getExtras(); + boolean usbConnected = extras.getBoolean(Usb.USB_CONNECTED); + boolean adbEnabled = (Usb.USB_FUNCTION_ENABLED.equals( + extras.getString(Usb.USB_FUNCTION_ADB))); + updateAdbNotification(usbConnected && adbEnabled); + } else if (action.equals(Usb.ACTION_USB_DISCONNECTED)) { + updateAdbNotification(false); } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED) || action.equals(Intent.ACTION_PACKAGE_RESTARTED) || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART)) @@ -377,8 +405,6 @@ class NotificationManagerService extends INotificationManager.Stub void observe() { ContentResolver resolver = mContext.getContentResolver(); - resolver.registerContentObserver(Settings.Secure.getUriFor( - Settings.Secure.ADB_ENABLED), false, this); resolver.registerContentObserver(Settings.System.getUriFor( Settings.System.NOTIFICATION_LIGHT_PULSE), false, this); update(); @@ -390,12 +416,6 @@ class NotificationManagerService extends INotificationManager.Stub public void update() { ContentResolver resolver = mContext.getContentResolver(); - boolean adbEnabled = Settings.Secure.getInt(resolver, - Settings.Secure.ADB_ENABLED, 0) != 0; - if (mAdbEnabled != adbEnabled) { - mAdbEnabled = adbEnabled; - updateAdbNotification(); - } boolean pulseEnabled = Settings.System.getInt(resolver, Settings.System.NOTIFICATION_LIGHT_PULSE, 0) != 0; if (mNotificationPulseEnabled != pulseEnabled) { @@ -405,7 +425,7 @@ class NotificationManagerService extends INotificationManager.Stub } } - NotificationManagerService(Context context, StatusBarService statusBar, + NotificationManagerService(Context context, StatusBarManagerService statusBar, LightsService lights) { super(); @@ -417,7 +437,7 @@ class NotificationManagerService extends INotificationManager.Stub mToastQueue = new ArrayList<ToastRecord>(); mHandler = new WorkerHandler(); - mStatusBarService = statusBar; + mStatusBar = statusBar; statusBar.setNotificationCallbacks(mNotificationCallbacks); mBatteryLight = lights.getLight(LightsService.LIGHT_ID_BATTERY); @@ -455,8 +475,7 @@ class NotificationManagerService extends INotificationManager.Stub // register for battery changed notifications IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BATTERY_CHANGED); - filter.addAction(Intent.ACTION_UMS_CONNECTED); - filter.addAction(Intent.ACTION_UMS_DISCONNECTED); + filter.addAction(Usb.ACTION_USB_STATE); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); @@ -664,11 +683,40 @@ class NotificationManagerService extends INotificationManager.Stub enqueueNotificationWithTag(pkg, null /* tag */, id, notification, idOut); } - public void enqueueNotificationWithTag(String pkg, String tag, int id, - Notification notification, int[] idOut) + public void enqueueNotificationWithTag(String pkg, String tag, int id, Notification notification, + int[] idOut) + { + enqueueNotificationInternal(pkg, Binder.getCallingUid(), Binder.getCallingPid(), + tag, id, 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) { checkIncomingCall(pkg); + // Limit the number of notifications that any given package except the android + // package can enqueue. Prevents DOS attacks and deals with leaks. + if (!"android".equals(pkg)) { + synchronized (mNotificationList) { + int count = 0; + final int N = mNotificationList.size(); + for (int i=0; i<N; i++) { + final NotificationRecord r = mNotificationList.get(i); + if (r.pkg.equals(pkg)) { + count++; + if (count >= MAX_PACKAGE_NOTIFICATIONS) { + Slog.e(TAG, "Package has already posted " + count + + " notifications. Not showing more. package=" + pkg); + return; + } + } + } + } + } + // This conditional is a dirty hack to limit the logging done on // behalf of the download manager without affecting other apps. if (!pkg.equals("com.android.providers.downloads") @@ -692,7 +740,8 @@ class NotificationManagerService extends INotificationManager.Stub } synchronized (mNotificationList) { - NotificationRecord r = new NotificationRecord(pkg, tag, id, notification); + NotificationRecord r = new NotificationRecord(pkg, tag, id, + callingUid, callingPid, notification); NotificationRecord old = null; int index = indexOfNotificationLocked(pkg, tag, id); @@ -716,36 +765,13 @@ class NotificationManagerService extends INotificationManager.Stub } if (notification.icon != 0) { - IconData icon = IconData.makeIcon(null, pkg, notification.icon, - notification.iconLevel, - notification.number); - CharSequence truncatedTicker = notification.tickerText; - - // TODO: make this restriction do something smarter like never fill - // more than two screens. "Why would anyone need more than 80 characters." :-/ - final int maxTickerLen = 80; - if (truncatedTicker != null && truncatedTicker.length() > maxTickerLen) { - truncatedTicker = truncatedTicker.subSequence(0, maxTickerLen); - } - - NotificationData n = new NotificationData(); - n.pkg = pkg; - n.tag = tag; - n.id = id; - n.when = notification.when; - n.tickerText = truncatedTicker; - n.ongoingEvent = (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0; - if (!n.ongoingEvent && (notification.flags & Notification.FLAG_NO_CLEAR) == 0) { - n.clearable = true; - } - n.contentView = notification.contentView; - n.contentIntent = notification.contentIntent; - n.deleteIntent = notification.deleteIntent; + StatusBarNotification n = new StatusBarNotification(pkg, id, tag, + r.uid, r.initialPid, notification); if (old != null && old.statusBarKey != null) { r.statusBarKey = old.statusBarKey; long identity = Binder.clearCallingIdentity(); try { - mStatusBarService.updateIcon(r.statusBarKey, icon, n); + mStatusBar.updateNotification(r.statusBarKey, n); } finally { Binder.restoreCallingIdentity(identity); @@ -753,21 +779,19 @@ class NotificationManagerService extends INotificationManager.Stub } else { long identity = Binder.clearCallingIdentity(); try { - r.statusBarKey = mStatusBarService.addIcon(icon, n); + r.statusBarKey = mStatusBar.addNotification(n); mAttentionLight.pulse(); } finally { Binder.restoreCallingIdentity(identity); } } - sendAccessibilityEvent(notification, pkg); - } else { if (old != null && old.statusBarKey != null) { long identity = Binder.clearCallingIdentity(); try { - mStatusBarService.removeIcon(old.statusBarKey); + mStatusBar.removeNotification(old.statusBarKey); } finally { Binder.restoreCallingIdentity(identity); @@ -875,7 +899,7 @@ class NotificationManagerService extends INotificationManager.Stub if (r.notification.icon != 0) { long identity = Binder.clearCallingIdentity(); try { - mStatusBarService.removeIcon(r.statusBarKey); + mStatusBar.removeNotification(r.statusBarKey); } finally { Binder.restoreCallingIdentity(identity); @@ -1070,6 +1094,11 @@ class NotificationManagerService extends INotificationManager.Stub mBatteryLight.turnOff(); } + // clear pending pulse notification if screen is on + if (mScreenOn || mLedNotification == null) { + mPendingPulseNotification = false; + } + // handle notification lights if (mLedNotification == null) { // get next notification, if any @@ -1077,11 +1106,14 @@ class NotificationManagerService extends INotificationManager.Stub if (n > 0) { mLedNotification = mLights.get(n-1); } + if (mLedNotification != null && !mScreenOn) { + mPendingPulseNotification = true; + } } // we only flash if screen is off and persistent pulsing is enabled // and we are not currently in a call - if (mLedNotification == null || mScreenOn || mInCall) { + if (!mPendingPulseNotification || mScreenOn || mInCall) { mNotificationLight.turnOff(); } else { int ledARGB = mLedNotification.notification.ledARGB; @@ -1129,8 +1161,8 @@ class NotificationManagerService extends INotificationManager.Stub // This is here instead of StatusBarPolicy because it is an important // security feature that we don't want people customizing the platform // to accidentally lose. - private void updateAdbNotification() { - if (mAdbEnabled && mUsbConnected) { + private void updateAdbNotification(boolean adbEnabled) { + if (adbEnabled) { if ("0".equals(SystemProperties.get("persist.adb.notify"))) { return; } diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index d23c16a..4520f18 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -18,6 +18,7 @@ package com.android.server; import com.android.internal.app.IMediaContainerService; import com.android.internal.app.ResolverActivity; +import com.android.internal.content.NativeLibraryHelper; import com.android.internal.content.PackageHelper; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.JournaledFile; @@ -90,6 +91,7 @@ import android.util.*; import android.view.Display; import android.view.WindowManager; +import java.io.BufferedOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; @@ -112,6 +114,7 @@ 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; @@ -137,11 +140,11 @@ 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_NATIVE = false; private static final boolean MULTIPLE_APPLICATION_UIDS = true; private static final int RADIO_UID = Process.PHONE_UID; private static final int LOG_UID = Process.LOG_UID; + private static final int NFC_UID = Process.NFC_UID; private static final int FIRST_APPLICATION_UID = Process.FIRST_APPLICATION_UID; private static final int MAX_APPLICATION_UIDS = 1000; @@ -150,6 +153,8 @@ 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 = @@ -179,13 +184,20 @@ class PackageManagerService extends IPackageManager.Stub { static final int SCAN_UPDATE_SIGNATURE = 1<<3; static final int SCAN_NEW_INSTALL = 1<<4; static final int SCAN_NO_PATHS = 1<<5; + static final int SCAN_UPDATE_TIME = 1<<6; static final int REMOVE_CHATTY = 1<<16; - + + static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer"; + static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName( - "com.android.defcontainer", + DEFAULT_CONTAINER_PACKAGE, "com.android.defcontainer.DefaultContainerService"); - + + private static final String LIB_DIR_NAME = "lib"; + + static final String mTempContainerPrefix = "smdl2tmp"; + final HandlerThread mHandlerThread = new HandlerThread("PackageManager", Process.THREAD_PRIORITY_BACKGROUND); final PackageHandler mHandler; @@ -204,12 +216,19 @@ 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; // This is the object monitoring the system app dir. final FileObserver mSystemInstallObserver; + // This is the object monitoring the system app dir. + final FileObserver mVendorInstallObserver; + // This is the object monitoring mAppInstallDir. final FileObserver mAppInstallObserver; @@ -222,6 +241,7 @@ class PackageManagerService extends IPackageManager.Stub { final File mFrameworkDir; final File mSystemAppDir; + final File mVendorAppDir; final File mAppInstallDir; final File mDalvikCacheDir; @@ -670,13 +690,6 @@ class PackageManagerService extends IPackageManager.Stub { return false; } - static boolean isFwdLocked(int flags) { - if ((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) { - return true; - } - return false; - } - public static final IPackageManager main(Context context, boolean factoryTest) { PackageManagerService m = new PackageManagerService(context, factoryTest); ServiceManager.addService("package", m); @@ -728,6 +741,10 @@ class PackageManagerService extends IPackageManager.Stub { MULTIPLE_APPLICATION_UIDS ? LOG_UID : FIRST_APPLICATION_UID, ApplicationInfo.FLAG_SYSTEM); + mSettings.addSharedUserLP("android.uid.nfc", + MULTIPLE_APPLICATION_UIDS + ? NFC_UID : FIRST_APPLICATION_UID, + ApplicationInfo.FLAG_SYSTEM); String separateProcesses = SystemProperties.get("debug.separate_processes"); if (separateProcesses != null && separateProcesses.length() > 0) { @@ -768,6 +785,7 @@ 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) { @@ -777,6 +795,7 @@ class PackageManagerService extends IPackageManager.Stub { File miscDir = new File(dataDir, "misc"); miscDir.mkdirs(); mAppDataDir.mkdirs(); + mSecureAppDataDir.mkdirs(); mDrmAppPrivateInstallDir.mkdirs(); } @@ -910,7 +929,7 @@ class PackageManagerService extends IPackageManager.Stub { mFrameworkInstallObserver.startWatching(); scanDirLI(mFrameworkDir, PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR, - scanMode | SCAN_NO_DEX); + scanMode | SCAN_NO_DEX, 0); // Collect all system packages. mSystemAppDir = new File(Environment.getRootDirectory(), "app"); @@ -918,8 +937,16 @@ class PackageManagerService extends IPackageManager.Stub { mSystemAppDir.getPath(), OBSERVER_EVENTS, true); mSystemInstallObserver.startWatching(); scanDirLI(mSystemAppDir, PackageParser.PARSE_IS_SYSTEM - | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode); + | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0); + // Collect all vendor packages. + mVendorAppDir = new File("/vendor/app"); + mVendorInstallObserver = new AppDirObserver( + mVendorAppDir.getPath(), OBSERVER_EVENTS, true); + mVendorInstallObserver.startWatching(); + scanDirLI(mVendorAppDir, PackageParser.PARSE_IS_SYSTEM + | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0); + if (mInstaller != null) { if (DEBUG_UPGRADE) Log.v(TAG, "Running installd update commands"); mInstaller.moveFiles(); @@ -937,7 +964,9 @@ class PackageManagerService extends IPackageManager.Stub { + " no longer exists; wiping its data"; reportSettingsProblem(Log.WARN, msg); if (mInstaller != null) { - mInstaller.remove(ps.name); + // XXX how to set useEncryptedFSDir for packages that + // are not encrypted? + mInstaller.remove(ps.name, true); } } } @@ -963,12 +992,13 @@ class PackageManagerService extends IPackageManager.Stub { mAppInstallObserver = new AppDirObserver( mAppInstallDir.getPath(), OBSERVER_EVENTS, false); mAppInstallObserver.startWatching(); - scanDirLI(mAppInstallDir, 0, scanMode); + scanDirLI(mAppInstallDir, 0, scanMode, 0); mDrmAppInstallObserver = new AppDirObserver( mDrmAppPrivateInstallDir.getPath(), OBSERVER_EVENTS, false); mDrmAppInstallObserver.startWatching(); - scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK, scanMode); + scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK, + scanMode, 0); EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END, SystemClock.uptimeMillis()); @@ -1020,7 +1050,8 @@ class PackageManagerService extends IPackageManager.Stub { void cleanupInstallFailedPackage(PackageSetting ps) { Slog.i(TAG, "Cleaning up incompletely installed app: " + ps.name); if (mInstaller != null) { - int retCode = mInstaller.remove(ps.name); + boolean useSecureFS = useEncryptedFilesystemForPackage(ps.pkg); + int retCode = mInstaller.remove(ps.name, useSecureFS); if (retCode < 0) { Slog.w(TAG, "Couldn't remove app data directory for package: " + ps.name + ", retcode=" + retCode); @@ -1079,27 +1110,6 @@ class PackageManagerService extends IPackageManager.Stub { final File permFile = new File(Environment.getRootDirectory(), "etc/permissions/platform.xml"); readPermissionsFromXml(permFile); - - StringBuilder sb = new StringBuilder(128); - sb.append("Libs:"); - Iterator<String> it = mSharedLibraries.keySet().iterator(); - while (it.hasNext()) { - sb.append(' '); - String name = it.next(); - sb.append(name); - sb.append(':'); - sb.append(mSharedLibraries.get(name)); - } - Log.i(TAG, sb.toString()); - - sb.setLength(0); - sb.append("Features:"); - it = mAvailableFeatures.keySet().iterator(); - while (it.hasNext()) { - sb.append(' '); - sb.append(it.next()); - } - Log.i(TAG, sb.toString()); } private void readPermissionsFromXml(File permFile) { @@ -1316,14 +1326,15 @@ class PackageManagerService extends IPackageManager.Stub { PackageInfo generatePackageInfo(PackageParser.Package p, int flags) { if ((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) { // The package has been uninstalled but has retained data and resources. - return PackageParser.generatePackageInfo(p, null, flags); + return PackageParser.generatePackageInfo(p, null, flags, 0, 0); } final PackageSetting ps = (PackageSetting)p.mExtras; if (ps == null) { return null; } final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps; - return PackageParser.generatePackageInfo(p, gp.gids, flags); + return PackageParser.generatePackageInfo(p, gp.gids, flags, + ps.firstInstallTime, ps.lastUpdateTime); } public PackageInfo getPackageInfo(String packageName, int flags) { @@ -1484,6 +1495,8 @@ class PackageManagerService extends IPackageManager.Stub { ps.pkg.applicationInfo.publicSourceDir = ps.resourcePathString; ps.pkg.applicationInfo.sourceDir = ps.codePathString; ps.pkg.applicationInfo.dataDir = getDataPathForPackage(ps.pkg).getPath(); + ps.pkg.applicationInfo.nativeLibraryDir = ps.nativeLibraryPathString; + ps.pkg.mSetEnabled = ps.enabled; } return generatePackageInfo(ps.pkg, flags); } @@ -1603,6 +1616,18 @@ class PackageManagerService extends IPackageManager.Stub { return null; } + public ProviderInfo getProviderInfo(ComponentName component, int flags) { + synchronized (mPackages) { + PackageParser.Provider p = mProvidersByComponent.get(component); + if (Config.LOGV) Log.v( + TAG, "getProviderInfo " + component + ": " + p); + if (p != null && mSettings.isEnabledLP(p.info, flags)) { + return PackageParser.generateProviderInfo(p, flags); + } + } + return null; + } + public String[] getSystemSharedLibraryNames() { Set<String> libSet; synchronized (mPackages) { @@ -1718,6 +1743,7 @@ class PackageManagerService extends IPackageManager.Stub { static boolean comparePermissionInfos(PermissionInfo pi1, PermissionInfo pi2) { if (pi1.icon != pi2.icon) return false; + if (pi1.logo != pi2.logo) return false; if (pi1.protectionLevel != pi2.protectionLevel) return false; if (!compareStrings(pi1.name, pi2.name)) return false; if (!compareStrings(pi1.nonLocalizedLabel, pi2.nonLocalizedLabel)) return false; @@ -1997,7 +2023,11 @@ class PackageManagerService extends IPackageManager.Stub { ActivityInfo ai = getActivityInfo(pa.mActivity, flags); if (DEBUG_PREFERRED) { Log.v(TAG, "Got preferred activity:"); - ai.dump(new LogPrinter(Log.INFO, TAG), " "); + if (ai != null) { + ai.dump(new LogPrinter(Log.VERBOSE, TAG), " "); + } else { + Log.v(TAG, " null"); + } } if (ai != null) { for (int j=0; j<N; j++) { @@ -2361,8 +2391,7 @@ class PackageManagerService extends IPackageManager.Stub { PackageParser.Package p = i.next(); if (p.applicationInfo != null && (p.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) != 0 - && (!mSafeMode || (p.applicationInfo.flags - &ApplicationInfo.FLAG_SYSTEM) != 0)) { + && (!mSafeMode || isSystemApp(p))) { finalList.add(PackageParser.generateApplicationInfo(p, flags)); } } @@ -2464,10 +2493,16 @@ class PackageManagerService extends IPackageManager.Stub { return finalList; } - private void scanDirLI(File dir, int flags, int scanMode) { - Log.d(TAG, "Scanning app dir " + dir); - + private void scanDirLI(File dir, int flags, int scanMode, long currentTime) { String[] files = dir.list(); + if (files == null) { + Log.d(TAG, "No files in app dir " + dir); + return; + } + + if (false) { + Log.d(TAG, "Scanning app dir " + dir); + } int i; for (i=0; i<files.length; i++) { @@ -2477,7 +2512,7 @@ class PackageManagerService extends IPackageManager.Stub { continue; } PackageParser.Package pkg = scanPackageLI(file, - flags|PackageParser.PARSE_MUST_BE_APK, scanMode); + flags|PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime); // Don't mess around with apps in system partition. if (pkg == null && (flags & PackageParser.PARSE_IS_SYSTEM) == 0 && mLastScanError == PackageManager.INSTALL_FAILED_INVALID_APK) { @@ -2518,7 +2553,7 @@ class PackageManagerService extends IPackageManager.Stub { if (GET_CERTIFICATES) { if (ps != null && ps.codePath.equals(srcFile) - && ps.getTimeStamp() == srcFile.lastModified()) { + && ps.timeStamp == srcFile.lastModified()) { if (ps.signatures.mSignatures != null && ps.signatures.mSignatures.length != 0) { // Optimization: reuse the existing cached certificates @@ -2545,7 +2580,7 @@ class PackageManagerService extends IPackageManager.Stub { * Returns null in case of errors and the error code is stored in mLastScanError */ private PackageParser.Package scanPackageLI(File scanFile, - int parseFlags, int scanMode) { + int parseFlags, int scanMode, long currentTime) { mLastScanError = PackageManager.INSTALL_SUCCEEDED; String scanPath = scanFile.getPath(); parseFlags |= mDefParseFlags; @@ -2579,7 +2614,7 @@ class PackageManagerService extends IPackageManager.Stub { } // First check if this is a system package that may involve an update if (updatedPkg != null && (parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) { - if (!ps.codePath.equals(scanFile)) { + if (ps != null && !ps.codePath.equals(scanFile)) { // The path has changed from what was last scanned... check the // version of the new path against what we have stored to determine // what to do. @@ -2587,7 +2622,7 @@ class PackageManagerService extends IPackageManager.Stub { // The system package has been updated and the code path does not match // Ignore entry. Skip it. Log.i(TAG, "Package " + ps.name + " at " + scanFile - + "ignored: updated version " + ps.versionCode + + " ignored: updated version " + ps.versionCode + " better than this " + pkg.mVersionCode); mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE; return null; @@ -2606,9 +2641,9 @@ class PackageManagerService extends IPackageManager.Stub { + "reverting from " + ps.codePathString + ": new version " + pkg.mVersionCode + " better than installed " + ps.versionCode); - InstallArgs args = new FileInstallArgs(ps.codePathString, ps.resourcePathString); + InstallArgs args = new FileInstallArgs(ps.codePathString, + ps.resourcePathString, ps.nativeLibraryPathString); args.cleanUpResourcesLI(); - removeNativeBinariesLI(pkg); mSettings.enableSystemPackageLP(ps.name); } } @@ -2645,11 +2680,11 @@ class PackageManagerService extends IPackageManager.Stub { // Set application objects path explicitly. setApplicationInfoPaths(pkg, codePath, resPath); // Note that we invoke the following method only if we are about to unpack an application - return scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_SIGNATURE); + return scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_SIGNATURE, currentTime); } - private static void setApplicationInfoPaths(PackageParser.Package pkg, - String destCodePath, String destResPath) { + private static void setApplicationInfoPaths(PackageParser.Package pkg, String destCodePath, + String destResPath) { pkg.mPath = pkg.mScanPath = destCodePath; pkg.applicationInfo.sourceDir = destCodePath; pkg.applicationInfo.publicSourceDir = destResPath; @@ -2743,6 +2778,11 @@ 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) { @@ -2760,11 +2800,18 @@ class PackageManagerService extends IPackageManager.Stub { } private File getDataPathForPackage(PackageParser.Package pkg) { - return new File(mAppDataDir, pkg.packageName); + boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(pkg); + File dataPath; + if (useEncryptedFSDir) { + dataPath = new File(mSecureAppDataDir, pkg.packageName); + } else { + dataPath = new File(mAppDataDir, pkg.packageName); + } + return dataPath; } private PackageParser.Package scanPackageLI(PackageParser.Package pkg, - int parseFlags, int scanMode) { + int parseFlags, int scanMode, long currentTime) { File scanFile = new File(pkg.mScanPath); if (scanFile == null || pkg.applicationInfo.sourceDir == null || pkg.applicationInfo.publicSourceDir == null) { @@ -2821,10 +2868,8 @@ class PackageManagerService extends IPackageManager.Stub { TAG, "Scanning package " + pkg.packageName); if (mPackages.containsKey(pkg.packageName) || mSharedLibraries.containsKey(pkg.packageName)) { - Slog.w(TAG, "*************************************************"); Slog.w(TAG, "Application package " + pkg.packageName + " already installed. Skipping duplicate."); - Slog.w(TAG, "*************************************************"); mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE; return null; } @@ -2986,7 +3031,8 @@ class PackageManagerService extends IPackageManager.Stub { // Just create the setting, don't add it yet. For already existing packages // the PkgSetting exists already and doesn't have to be created. pkgSetting = mSettings.getPackageLP(pkg, origPackage, realName, suid, destCodeFile, - destResourceFile, pkg.applicationInfo.flags, true, false); + destResourceFile, pkg.applicationInfo.nativeLibraryDir, + pkg.applicationInfo.flags, true, false); if (pkgSetting == null) { Slog.w(TAG, "Creating application package " + pkg.packageName + " failed"); mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; @@ -3096,9 +3142,9 @@ class PackageManagerService extends IPackageManager.Stub { } } - long scanFileTime = scanFile.lastModified(); + final long scanFileTime = scanFile.lastModified(); final boolean forceDex = (scanMode&SCAN_FORCE_DEX) != 0; - final boolean scanFileNewer = forceDex || scanFileTime != pkgSetting.getTimeStamp(); + final boolean scanFileNewer = forceDex || scanFileTime != pkgSetting.timeStamp; pkg.applicationInfo.processName = fixProcessName( pkg.applicationInfo.packageName, pkg.applicationInfo.processName, @@ -3111,6 +3157,7 @@ class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.dataDir = dataPath.getPath(); } else { // This is a normal package, need to make its data directory. + boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(pkg); dataPath = getDataPathForPackage(pkg); boolean uidError = false; @@ -3118,16 +3165,16 @@ class PackageManagerService extends IPackageManager.Stub { if (dataPath.exists()) { mOutPermissions[1] = 0; FileUtils.getPermissions(dataPath.getPath(), mOutPermissions); - if (mOutPermissions[1] == pkg.applicationInfo.uid - || !Process.supportsProcesses()) { - pkg.applicationInfo.dataDir = dataPath.getPath(); - } else { + + // If we have mismatched owners for the data path, we have a + // problem (unless we're running in the simulator.) + if (mOutPermissions[1] != pkg.applicationInfo.uid && Process.supportsProcesses()) { boolean recovered = false; if ((parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) { // 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); + int ret = mInstaller.remove(pkgName, useEncryptedFSDir); if (ret >= 0) { // Old data gone! String msg = "System package " + pkg.packageName @@ -3138,7 +3185,7 @@ class PackageManagerService extends IPackageManager.Stub { recovered = true; // And now re-install the app. - ret = mInstaller.install(pkgName, pkg.applicationInfo.uid, + ret = mInstaller.install(pkgName, useEncryptedFSDir, pkg.applicationInfo.uid, pkg.applicationInfo.uid); if (ret == -1) { // Ack should not happen! @@ -3158,6 +3205,7 @@ class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.dataDir = "/mismatched_uid/settings_" + pkg.applicationInfo.uid + "/fs_" + mOutPermissions[1]; + pkg.applicationInfo.nativeLibraryDir = pkg.applicationInfo.dataDir; String msg = "Package " + pkg.packageName + " has mismatched uid: " + mOutPermissions[1] + " on disk, " @@ -3178,7 +3226,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, pkg.applicationInfo.uid, + int ret = mInstaller.install(pkgName, useEncryptedFSDir, pkg.applicationInfo.uid, pkg.applicationInfo.uid); if(ret < 0) { // Error from installer @@ -3201,31 +3249,73 @@ class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.dataDir = null; } } - + + /* + * Set the data dir to the default "/data/data/<package name>/lib" + * if we got here without anyone telling us different (e.g., apps + * stored on SD card have their native libraries stored in the ASEC + * container with the APK). + * + * This happens during an upgrade from a package settings file that + * doesn't have a native library path attribute at all. + */ + if (pkg.applicationInfo.nativeLibraryDir == null && pkg.applicationInfo.dataDir != null) { + if (pkgSetting.nativeLibraryPathString == null) { + final String nativeLibraryPath = new File(dataPath, LIB_DIR_NAME).getPath(); + pkg.applicationInfo.nativeLibraryDir = nativeLibraryPath; + pkgSetting.nativeLibraryPathString = nativeLibraryPath; + } else { + pkg.applicationInfo.nativeLibraryDir = pkgSetting.nativeLibraryPathString; + } + } + pkgSetting.uidError = uidError; } - // Perform shared library installation and dex validation and - // optimization, if this is not a system app. + // If we're running in the simulator, we don't need to unpack anything. if (mInstaller != null) { String path = scanFile.getPath(); - if (scanFileNewer) { - // Note: We don't want to unpack the native binaries for - // system applications, unless they have been updated - // (the binaries are already under /system/lib). - // - // In other words, we're going to unpack the binaries - // only for non-system apps and system app upgrades. - // - int flags = pkg.applicationInfo.flags; - if ((flags & ApplicationInfo.FLAG_SYSTEM) == 0 || - (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { - Log.i(TAG, path + " changed; unpacking"); - int err = cachePackageSharedLibsLI(pkg, scanFile); - if (err != PackageManager.INSTALL_SUCCEEDED) { - mLastScanError = err; - return null; - } + /* Note: We don't want to unpack the native binaries for + * system applications, unless they have been updated + * (the binaries are already under /system/lib). + * Also, don't unpack libs for apps on the external card + * since they should have their libraries in the ASEC + * container already. + * + * In other words, we're going to unpack the binaries + * only for non-system apps and system app upgrades. + */ + if (pkg.applicationInfo.nativeLibraryDir != null) { + final File nativeLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir); + final String dataPathString = dataPath.getPath(); + + if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) { + /* + * Upgrading from a previous version of the OS sometimes + * leaves native libraries in the /data/data/<app>/lib + * directory for system apps even when they shouldn't be. + * Recent changes in the JNI library search path + * necessitates we remove those to match previous behavior. + */ + if (NativeLibraryHelper.removeNativeBinariesFromDirLI(nativeLibraryDir)) { + Log.i(TAG, "removed obsolete native libraries for system package " + path); + } + } else if (nativeLibraryDir.getParent().equals(dataPathString)) { + /* + * If this is an internal application or our + * nativeLibraryPath points to our data directory, unpack + * the libraries. The native library path pointing to the + * data directory for an application in an ASEC container + * can happen for older apps that existed before an OTA to + * Gingerbread. + */ + Slog.i(TAG, "Unpacking native libraries for " + path); + mInstaller.unlinkNativeLibraryDirectory(dataPathString); + NativeLibraryHelper.copyNativeBinariesLI(scanFile, nativeLibraryDir); + } else { + Slog.i(TAG, "Linking native library dir for " + path); + mInstaller.linkNativeLibraryDirectory(dataPathString, + pkg.applicationInfo.nativeLibraryDir); } } pkg.mScanPath = path; @@ -3263,6 +3353,24 @@ class PackageManagerService extends IPackageManager.Stub { // Make sure we don't accidentally delete its data. mSettings.mPackagesToBeCleaned.remove(pkgName); + // Take care of first install / last update times. + if (currentTime != 0) { + if (pkgSetting.firstInstallTime == 0) { + pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = currentTime; + } else if ((scanMode&SCAN_UPDATE_TIME) != 0) { + pkgSetting.lastUpdateTime = currentTime; + } + } else if (pkgSetting.firstInstallTime == 0) { + // We need *something*. Take time time stamp of the file. + pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = scanFileTime; + } else if ((parseFlags&PackageParser.PARSE_IS_SYSTEM_DIR) != 0) { + if (scanFileTime != pkgSetting.timeStamp) { + // A package on the system image has changed; consider this + // to be an update. + pkgSetting.lastUpdateTime = scanFileTime; + } + } + int N = pkg.providers.size(); StringBuilder r = null; int i; @@ -3489,6 +3597,7 @@ class PackageManagerService extends IPackageManager.Stub { a.info.sourceDir = pkg.applicationInfo.sourceDir; a.info.publicSourceDir = pkg.applicationInfo.publicSourceDir; a.info.dataDir = pkg.applicationInfo.dataDir; + a.info.nativeLibraryDir = pkg.applicationInfo.nativeLibraryDir; mInstrumentation.put(a.getComponentName(), a); if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) { if (r == null) { @@ -3529,281 +3638,16 @@ class PackageManagerService extends IPackageManager.Stub { } } - // The following constants are returned by cachePackageSharedLibsForAbiLI - // to indicate if native shared libraries were found in the package. - // Values are: - // PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES => native libraries found and installed - // PACKAGE_INSTALL_NATIVE_NO_LIBRARIES => no native libraries in package - // PACKAGE_INSTALL_NATIVE_ABI_MISMATCH => native libraries for another ABI found - // in package (and not installed) - // - private static final int PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES = 0; - private static final int PACKAGE_INSTALL_NATIVE_NO_LIBRARIES = 1; - private static final int PACKAGE_INSTALL_NATIVE_ABI_MISMATCH = 2; - // Return the path of the directory that will contain the native binaries // of a given installed package. This is relative to the data path. // - private static File getNativeBinaryDirForPackage(PackageParser.Package pkg) { - return new File(pkg.applicationInfo.dataDir + "/lib"); - } - - // Find all files of the form lib/<cpuAbi>/lib<name>.so in the .apk - // and automatically copy them to /data/data/<appname>/lib if present. - // - // NOTE: this method may throw an IOException if the library cannot - // be copied to its final destination, e.g. if there isn't enough - // room left on the data partition, or a ZipException if the package - // file is malformed. - // - private int cachePackageSharedLibsForAbiLI(PackageParser.Package pkg, - File scanFile, String cpuAbi) throws IOException, ZipException { - File sharedLibraryDir = getNativeBinaryDirForPackage(pkg); - final String apkLib = "lib/"; - final int apkLibLen = apkLib.length(); - final int cpuAbiLen = cpuAbi.length(); - final String libPrefix = "lib"; - final int libPrefixLen = libPrefix.length(); - final String libSuffix = ".so"; - final int libSuffixLen = libSuffix.length(); - boolean hasNativeLibraries = false; - boolean installedNativeLibraries = false; - - // the minimum length of a valid native shared library of the form - // lib/<something>/lib<name>.so. - final int minEntryLen = apkLibLen + 2 + libPrefixLen + 1 + libSuffixLen; - - ZipFile zipFile = new ZipFile(scanFile); - Enumeration<ZipEntry> entries = - (Enumeration<ZipEntry>) zipFile.entries(); - - while (entries.hasMoreElements()) { - ZipEntry entry = entries.nextElement(); - // skip directories - if (entry.isDirectory()) { - continue; - } - String entryName = entry.getName(); - - // check that the entry looks like lib/<something>/lib<name>.so - // here, but don't check the ABI just yet. - // - // - must be sufficiently long - // - must end with libSuffix, i.e. ".so" - // - must start with apkLib, i.e. "lib/" - if (entryName.length() < minEntryLen || - !entryName.endsWith(libSuffix) || - !entryName.startsWith(apkLib) ) { - continue; - } - - // file name must start with libPrefix, i.e. "lib" - int lastSlash = entryName.lastIndexOf('/'); - - if (lastSlash < 0 || - !entryName.regionMatches(lastSlash+1, libPrefix, 0, libPrefixLen) ) { - continue; - } - - hasNativeLibraries = true; - - // check the cpuAbi now, between lib/ and /lib<name>.so - // - if (lastSlash != apkLibLen + cpuAbiLen || - !entryName.regionMatches(apkLibLen, cpuAbi, 0, cpuAbiLen) ) - continue; - - // extract the library file name, ensure it doesn't contain - // weird characters. we're guaranteed here that it doesn't contain - // a directory separator though. - String libFileName = entryName.substring(lastSlash+1); - if (!FileUtils.isFilenameSafe(new File(libFileName))) { - continue; - } - - installedNativeLibraries = true; - - String sharedLibraryFilePath = sharedLibraryDir.getPath() + - File.separator + libFileName; - File sharedLibraryFile = new File(sharedLibraryFilePath); - if (! sharedLibraryFile.exists() || - sharedLibraryFile.length() != entry.getSize() || - sharedLibraryFile.lastModified() != entry.getTime()) { - if (Config.LOGD) { - Log.d(TAG, "Caching shared lib " + entry.getName()); - } - if (mInstaller == null) { - sharedLibraryDir.mkdir(); - } - cacheNativeBinaryLI(pkg, zipFile, entry, sharedLibraryDir, - sharedLibraryFile); - } - } - if (!hasNativeLibraries) - return PACKAGE_INSTALL_NATIVE_NO_LIBRARIES; - - if (!installedNativeLibraries) - return PACKAGE_INSTALL_NATIVE_ABI_MISMATCH; - - return PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES; - } - - // Find the gdbserver executable program in a package at - // lib/<cpuAbi>/gdbserver and copy it to /data/data/<name>/lib/gdbserver - // - // Returns PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES on success, - // or PACKAGE_INSTALL_NATIVE_NO_LIBRARIES otherwise. - // - private int cachePackageGdbServerLI(PackageParser.Package pkg, - File scanFile, String cpuAbi) throws IOException, ZipException { - File installGdbServerDir = getNativeBinaryDirForPackage(pkg); - final String GDBSERVER = "gdbserver"; - final String apkGdbServerPath = "lib/" + cpuAbi + "/" + GDBSERVER; - - ZipFile zipFile = new ZipFile(scanFile); - Enumeration<ZipEntry> entries = - (Enumeration<ZipEntry>) zipFile.entries(); - - while (entries.hasMoreElements()) { - ZipEntry entry = entries.nextElement(); - // skip directories - if (entry.isDirectory()) { - continue; - } - String entryName = entry.getName(); - - if (!entryName.equals(apkGdbServerPath)) { - continue; - } - - String installGdbServerPath = installGdbServerDir.getPath() + - "/" + GDBSERVER; - File installGdbServerFile = new File(installGdbServerPath); - if (! installGdbServerFile.exists() || - installGdbServerFile.length() != entry.getSize() || - installGdbServerFile.lastModified() != entry.getTime()) { - if (Config.LOGD) { - Log.d(TAG, "Caching gdbserver " + entry.getName()); - } - if (mInstaller == null) { - installGdbServerDir.mkdir(); - } - cacheNativeBinaryLI(pkg, zipFile, entry, installGdbServerDir, - installGdbServerFile); - } - return PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES; - } - return PACKAGE_INSTALL_NATIVE_NO_LIBRARIES; - } - - // extract shared libraries stored in the APK as lib/<cpuAbi>/lib<name>.so - // and copy them to /data/data/<appname>/lib. - // - // This function will first try the main CPU ABI defined by Build.CPU_ABI - // (which corresponds to ro.product.cpu.abi), and also try an alternate - // one if ro.product.cpu.abi2 is defined. - // - private int cachePackageSharedLibsLI(PackageParser.Package pkg, File scanFile) { - String cpuAbi = Build.CPU_ABI; - try { - int result = cachePackageSharedLibsForAbiLI(pkg, scanFile, cpuAbi); - - // some architectures are capable of supporting several CPU ABIs - // for example, 'armeabi-v7a' also supports 'armeabi' native code - // this is indicated by the definition of the ro.product.cpu.abi2 - // system property. - // - // only scan the package twice in case of ABI mismatch - if (result == PACKAGE_INSTALL_NATIVE_ABI_MISMATCH) { - final String cpuAbi2 = SystemProperties.get("ro.product.cpu.abi2",null); - if (cpuAbi2 != null) { - result = cachePackageSharedLibsForAbiLI(pkg, scanFile, cpuAbi2); - } - - if (result == PACKAGE_INSTALL_NATIVE_ABI_MISMATCH) { - Slog.w(TAG,"Native ABI mismatch from package file"); - return PackageManager.INSTALL_FAILED_INVALID_APK; - } - - if (result == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES) { - cpuAbi = cpuAbi2; - } - } - - // for debuggable packages, also extract gdbserver from lib/<abi> - // into /data/data/<appname>/lib too. - if (result == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES && - (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { - int result2 = cachePackageGdbServerLI(pkg, scanFile, cpuAbi); - if (result2 == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES) { - pkg.applicationInfo.flags |= ApplicationInfo.FLAG_NATIVE_DEBUGGABLE; - } - } - } catch (ZipException e) { - Slog.w(TAG, "Failed to extract data from package file", e); - return PackageManager.INSTALL_FAILED_INVALID_APK; - } catch (IOException e) { - Slog.w(TAG, "Failed to cache package shared libs", e); - return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; - } - return PackageManager.INSTALL_SUCCEEDED; - } - - private void cacheNativeBinaryLI(PackageParser.Package pkg, - ZipFile zipFile, ZipEntry entry, - File binaryDir, - File binaryFile) throws IOException { - InputStream inputStream = zipFile.getInputStream(entry); - try { - File tempFile = File.createTempFile("tmp", "tmp", binaryDir); - String tempFilePath = tempFile.getPath(); - // XXX package manager can't change owner, so the executable files for - // now need to be left as world readable and owned by the system. - if (! FileUtils.copyToFile(inputStream, tempFile) || - ! tempFile.setLastModified(entry.getTime()) || - FileUtils.setPermissions(tempFilePath, - FileUtils.S_IRUSR|FileUtils.S_IWUSR|FileUtils.S_IRGRP - |FileUtils.S_IXUSR|FileUtils.S_IXGRP|FileUtils.S_IXOTH - |FileUtils.S_IROTH, -1, -1) != 0 || - ! tempFile.renameTo(binaryFile)) { - // Failed to properly write file. - tempFile.delete(); - throw new IOException("Couldn't create cached binary " - + binaryFile + " in " + binaryDir); - } - } finally { - inputStream.close(); - } - } - - // Remove the native binaries of a given package. This simply - // gets rid of the files in the 'lib' sub-directory. - private void removeNativeBinariesLI(PackageParser.Package pkg) { - File binaryDir = getNativeBinaryDirForPackage(pkg); - - if (DEBUG_NATIVE) { - Slog.w(TAG,"Deleting native binaries from: " + binaryDir.getPath()); - } - - // Just remove any file in the directory. Since the directory - // is owned by the 'system' UID, the application is not supposed - // to have written anything there. - // - if (binaryDir.exists()) { - File[] binaries = binaryDir.listFiles(); - if (binaries != null) { - for (int nn=0; nn < binaries.length; nn++) { - if (DEBUG_NATIVE) { - Slog.d(TAG," Deleting " + binaries[nn].getName()); - } - if (!binaries[nn].delete()) { - Slog.w(TAG,"Could not delete native binary: " + - binaries[nn].getPath()); - } - } - } - // Do not delete 'lib' directory itself, or this will prevent - // installation of future updates. + private File getNativeBinaryDirForPackage(PackageParser.Package pkg) { + final String nativeLibraryDir = pkg.applicationInfo.nativeLibraryDir; + if (nativeLibraryDir != null) { + return new File(nativeLibraryDir); + } else { + // Fall back for old packages + return new File(pkg.applicationInfo.dataDir, LIB_DIR_NAME); } } @@ -4105,18 +3949,22 @@ class PackageManagerService extends IPackageManager.Stub { allowed = false; } else if (bp.protectionLevel == PermissionInfo.PROTECTION_SIGNATURE || bp.protectionLevel == PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM) { - allowed = (checkSignaturesLP(bp.packageSetting.signatures.mSignatures, pkg.mSignatures) + allowed = (checkSignaturesLP( + bp.packageSetting.signatures.mSignatures, pkg.mSignatures) == PackageManager.SIGNATURE_MATCH) || (checkSignaturesLP(mPlatformPackage.mSignatures, pkg.mSignatures) == PackageManager.SIGNATURE_MATCH); - if (bp.protectionLevel == PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM) { - if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) { + if (!allowed && bp.protectionLevel + == PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM) { + if (isSystemApp(pkg)) { // For updated system applications, the signatureOrSystem permission // is granted only if it had been defined by the original application. - if ((pkg.applicationInfo.flags - & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { - PackageSetting sysPs = mSettings.getDisabledSystemPkg(pkg.packageName); - if(sysPs.grantedPermissions.contains(perm)) { + if (isUpdatedSystemApp(pkg)) { + PackageSetting sysPs = mSettings.getDisabledSystemPkg( + pkg.packageName); + final GrantedPermissions origGp = sysPs.sharedUser != null + ? sysPs.sharedUser : sysPs; + if (origGp.grantedPermissions.contains(perm)) { allowed = true; } else { allowed = false; @@ -4552,6 +4400,8 @@ class PackageManagerService extends IPackageManager.Stub { } }; + private static final boolean DEBUG_OBB = false; + private static final void sendPackageBroadcast(String action, String pkg, Bundle extras, IIntentReceiver finishedReceiver) { IActivityManager am = ActivityManagerNative.getDefault(); @@ -4665,7 +4515,8 @@ class PackageManagerService extends IPackageManager.Stub { | PackageParser.PARSE_IS_SYSTEM_DIR: 0) | PackageParser.PARSE_CHATTY | PackageParser.PARSE_MUST_BE_APK, - SCAN_MONITOR | SCAN_NO_PATHS); + SCAN_MONITOR | SCAN_NO_PATHS | SCAN_UPDATE_TIME, + System.currentTimeMillis()); if (p != null) { synchronized (mPackages) { updatePermissionsLP(p.packageName, p, @@ -4726,6 +4577,29 @@ class PackageManagerService extends IPackageManager.Stub { mHandler.sendMessage(msg); } + public void setPackageObbPath(String packageName, String path) { + if (DEBUG_OBB) + Log.v(TAG, "Setting .obb path for " + packageName + " to: " + path); + 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) { + 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 set .obb file from pid=" + + Binder.getCallingPid() + ", uid=" + uid + ", package uid=" + + pkgSetting.userId); + } + pkgSetting.obbPathString = path; + mSettings.writeLP(); + } + } + 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() { @@ -4877,7 +4751,7 @@ class PackageManagerService extends IPackageManager.Stub { // App explictly prefers external. Let policy decide } else { // Prefer previous location - if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { + if (isExternal(pkg)) { return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; } return PackageHelper.RECOMMEND_INSTALL_INTERNAL; @@ -4918,7 +4792,15 @@ class PackageManagerService extends IPackageManager.Stub { ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; } else { // Remote call to find out default install location - PackageInfoLite pkgLite = mContainerService.getMinimalPackageInfo(packageURI, flags); + final PackageInfoLite pkgLite; + try { + mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI, + Intent.FLAG_GRANT_READ_URI_PERMISSION); + pkgLite = mContainerService.getMinimalPackageInfo(packageURI, flags); + } finally { + mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + int loc = pkgLite.recommendedInstallLocation; if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION){ ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; @@ -4960,7 +4842,12 @@ class PackageManagerService extends IPackageManager.Stub { @Override void handleReturnCode() { - processPendingInstall(mArgs, mRet); + // If mArgs is null, then MCS couldn't be reached. When it + // reconnects, it will try again to install. At that point, this + // will succeed. + if (mArgs != null) { + processPendingInstall(mArgs, mRet); + } } @Override @@ -4984,16 +4871,16 @@ class PackageManagerService extends IPackageManager.Stub { final InstallArgs srcArgs; final InstallArgs targetArgs; int mRet; - MoveParams(InstallArgs srcArgs, - IPackageMoveObserver observer, - int flags, String packageName) { + + MoveParams(InstallArgs srcArgs, IPackageMoveObserver observer, int flags, + String packageName, String dataDir) { this.srcArgs = srcArgs; this.observer = observer; this.flags = flags; this.packageName = packageName; if (srcArgs != null) { Uri packageUri = Uri.fromFile(new File(srcArgs.getCodePath())); - targetArgs = createInstallArgs(packageUri, flags, packageName); + targetArgs = createInstallArgs(packageUri, flags, packageName, dataDir); } else { targetArgs = null; } @@ -5049,21 +4936,22 @@ class PackageManagerService extends IPackageManager.Stub { } } - private InstallArgs createInstallArgs(int flags, String fullCodePath, String fullResourcePath) { + private InstallArgs createInstallArgs(int flags, String fullCodePath, String fullResourcePath, + String nativeLibraryPath) { if (installOnSd(flags)) { - return new SdInstallArgs(fullCodePath, fullResourcePath); + return new SdInstallArgs(fullCodePath, fullResourcePath, nativeLibraryPath); } else { - return new FileInstallArgs(fullCodePath, fullResourcePath); + return new FileInstallArgs(fullCodePath, fullResourcePath, nativeLibraryPath); } } - private InstallArgs createInstallArgs(Uri packageURI, int flags, - String pkgName) { + // Used by package mover + private InstallArgs createInstallArgs(Uri packageURI, int flags, String pkgName, String dataDir) { if (installOnSd(flags)) { String cid = getNextCodePath(null, pkgName, "/" + SdInstallArgs.RES_FILE_NAME); return new SdInstallArgs(packageURI, cid); } else { - return new FileInstallArgs(packageURI, pkgName); + return new FileInstallArgs(packageURI, pkgName, dataDir); } } @@ -5090,6 +4978,7 @@ class PackageManagerService extends IPackageManager.Stub { abstract int doPostInstall(int status); abstract String getCodePath(); abstract String getResourcePath(); + abstract String getNativeLibraryPath(); // Need installer lock especially for dex file removal. abstract void cleanUpResourcesLI(); abstract boolean doPostDeleteLI(boolean delete); @@ -5100,6 +4989,7 @@ class PackageManagerService extends IPackageManager.Stub { File installDir; String codeFileName; String resourceFileName; + String libraryPath; boolean created = false; FileInstallArgs(InstallParams params) { @@ -5107,25 +4997,32 @@ class PackageManagerService extends IPackageManager.Stub { params.flags, params.installerPackageName); } - FileInstallArgs(String fullCodePath, String fullResourcePath) { + FileInstallArgs(String fullCodePath, String fullResourcePath, String nativeLibraryPath) { super(null, null, 0, null); File codeFile = new File(fullCodePath); installDir = codeFile.getParentFile(); codeFileName = fullCodePath; resourceFileName = fullResourcePath; + libraryPath = nativeLibraryPath; } - FileInstallArgs(Uri packageURI, String pkgName) { + FileInstallArgs(Uri packageURI, String pkgName, String dataDir) { super(packageURI, null, 0, null); - boolean fwdLocked = isFwdLocked(flags); - installDir = fwdLocked ? mDrmAppPrivateInstallDir : mAppInstallDir; + installDir = isFwdLocked() ? mDrmAppPrivateInstallDir : mAppInstallDir; String apkName = getNextCodePath(null, pkgName, ".apk"); codeFileName = new File(installDir, apkName + ".apk").getPath(); resourceFileName = getResourcePathFromCodePath(); + libraryPath = new File(dataDir, LIB_DIR_NAME).getPath(); } - boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException { - return imcs.checkFreeStorage(false, packageURI); + boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException { + try { + mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI, + Intent.FLAG_GRANT_READ_URI_PERMISSION); + return imcs.checkFreeStorage(false, packageURI); + } finally { + mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); + } } String getCodePath() { @@ -5133,8 +5030,7 @@ class PackageManagerService extends IPackageManager.Stub { } void createCopyFile() { - boolean fwdLocked = isFwdLocked(flags); - installDir = fwdLocked ? mDrmAppPrivateInstallDir : mAppInstallDir; + installDir = isFwdLocked() ? mDrmAppPrivateInstallDir : mAppInstallDir; codeFileName = createTempPackageFile(installDir).getPath(); resourceFileName = getResourcePathFromCodePath(); created = true; @@ -5162,8 +5058,7 @@ class PackageManagerService extends IPackageManager.Stub { } ParcelFileDescriptor out = null; try { - out = ParcelFileDescriptor.open(codeFile, - ParcelFileDescriptor.MODE_READ_WRITE); + out = ParcelFileDescriptor.open(codeFile, ParcelFileDescriptor.MODE_READ_WRITE); } catch (FileNotFoundException e) { Slog.e(TAG, "Failed to create file descritpor for : " + codeFileName); return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; @@ -5171,12 +5066,16 @@ class PackageManagerService extends IPackageManager.Stub { // Copy the resource now int ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; try { + mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI, + Intent.FLAG_GRANT_READ_URI_PERMISSION); if (imcs.copyResource(packageURI, out)) { ret = PackageManager.INSTALL_SUCCEEDED; } } finally { try { if (out != null) out.close(); } catch (IOException e) {} + mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); } + return ret; } @@ -5232,6 +5131,11 @@ class PackageManagerService extends IPackageManager.Stub { } } + @Override + String getNativeLibraryPath() { + return libraryPath; + } + private boolean cleanUp() { boolean ret = true; String sourceDir = getCodePath(); @@ -5272,7 +5176,7 @@ class PackageManagerService extends IPackageManager.Stub { private boolean setPermissions() { // TODO Do this in a more elegant way later on. for now just a hack - if (!isFwdLocked(flags)) { + if (!isFwdLocked()) { final int filePermissions = FileUtils.S_IRUSR|FileUtils.S_IWUSR|FileUtils.S_IRGRP |FileUtils.S_IROTH; @@ -5290,35 +5194,42 @@ class PackageManagerService extends IPackageManager.Stub { } boolean doPostDeleteLI(boolean delete) { + // XXX err, shouldn't we respect the delete flag? cleanUpResourcesLI(); return true; } + + private boolean isFwdLocked() { + return (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0; + } } class SdInstallArgs extends InstallArgs { - String cid; - String cachePath; static final String RES_FILE_NAME = "pkg.apk"; + String cid; + String packagePath; + String libraryPath; + SdInstallArgs(InstallParams params) { super(params.packageURI, params.observer, params.flags, params.installerPackageName); } - SdInstallArgs(String fullCodePath, String fullResourcePath) { + SdInstallArgs(String fullCodePath, String fullResourcePath, String nativeLibraryPath) { super(null, null, PackageManager.INSTALL_EXTERNAL, null); // Extract cid from fullCodePath int eidx = fullCodePath.lastIndexOf("/"); String subStr1 = fullCodePath.substring(0, eidx); int sidx = subStr1.lastIndexOf("/"); cid = subStr1.substring(sidx+1, eidx); - cachePath = subStr1; + setCachePath(subStr1); } SdInstallArgs(String cid) { super(null, null, PackageManager.INSTALL_EXTERNAL, null); this.cid = cid; - cachePath = PackageHelper.getSdDir(cid); + setCachePath(PackageHelper.getSdDir(cid)); } SdInstallArgs(Uri packageURI, String cid) { @@ -5330,29 +5241,52 @@ class PackageManagerService extends IPackageManager.Stub { cid = getTempContainerId(); } - boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException { - return imcs.checkFreeStorage(true, packageURI); + boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException { + try { + mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI, + Intent.FLAG_GRANT_READ_URI_PERMISSION); + return imcs.checkFreeStorage(true, packageURI); + } finally { + mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); + } } int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException { if (temp) { createCopyFile(); } - cachePath = imcs.copyResourceToContainer( - packageURI, cid, - getEncryptKey(), RES_FILE_NAME); - return (cachePath == null) ? PackageManager.INSTALL_FAILED_CONTAINER_ERROR : - PackageManager.INSTALL_SUCCEEDED; + + final String newCachePath; + try { + mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI, + Intent.FLAG_GRANT_READ_URI_PERMISSION); + newCachePath = imcs.copyResourceToContainer(packageURI, cid, + getEncryptKey(), RES_FILE_NAME); + } finally { + mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + + if (newCachePath != null) { + setCachePath(newCachePath); + return PackageManager.INSTALL_SUCCEEDED; + } else { + return PackageManager.INSTALL_FAILED_CONTAINER_ERROR; + } } @Override String getCodePath() { - return cachePath + "/" + RES_FILE_NAME; + return packagePath; } @Override String getResourcePath() { - return cachePath + "/" + RES_FILE_NAME; + return packagePath; + } + + @Override + String getNativeLibraryPath() { + return libraryPath; } int doPreInstall(int status) { @@ -5362,8 +5296,11 @@ class PackageManagerService extends IPackageManager.Stub { } else { boolean mounted = PackageHelper.isContainerMounted(cid); if (!mounted) { - cachePath = PackageHelper.mountSdDir(cid, getEncryptKey(), Process.SYSTEM_UID); - if (cachePath == null) { + String newCachePath = PackageHelper.mountSdDir(cid, getEncryptKey(), + Process.SYSTEM_UID); + if (newCachePath != null) { + setCachePath(newCachePath); + } else { return PackageManager.INSTALL_FAILED_CONTAINER_ERROR; } } @@ -5409,13 +5346,19 @@ class PackageManagerService extends IPackageManager.Stub { return false; } Log.i(TAG, "Succesfully renamed " + cid + - " at path: " + cachePath + " to " + newCacheId + + " to " + newCacheId + " at new path: " + newCachePath); cid = newCacheId; - cachePath = newCachePath; + setCachePath(newCachePath); return true; } + private void setCachePath(String newCachePath) { + File cachePath = new File(newCachePath); + libraryPath = new File(cachePath, LIB_DIR_NAME).getPath(); + packagePath = new File(cachePath, RES_FILE_NAME).getPath(); + } + int doPostInstall(int status) { if (status != PackageManager.INSTALL_SUCCEEDED) { cleanUp(); @@ -5590,7 +5533,8 @@ class PackageManagerService extends IPackageManager.Stub { } } mLastScanError = PackageManager.INSTALL_SUCCEEDED; - PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanMode); + PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanMode, + System.currentTimeMillis()); if (newPackage == null) { Slog.w(TAG, "Package couldn't be installed in " + pkg.mPath); if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) { @@ -5610,7 +5554,7 @@ class PackageManagerService extends IPackageManager.Stub { deletePackageLI( pkgName, false, dataDirExists ? PackageManager.DONT_DELETE_DATA : 0, - res.removedInfo); + res.removedInfo, true); } } } @@ -5631,7 +5575,7 @@ class PackageManagerService extends IPackageManager.Stub { return; } } - boolean sysPkg = ((oldPackage.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0); + boolean sysPkg = (isSystemApp(oldPackage)); if (sysPkg) { replaceSystemPackageLI(oldPackage, pkg, parseFlags, scanMode, installerPackageName, res); } else { @@ -5653,16 +5597,24 @@ class PackageManagerService extends IPackageManager.Stub { oldInstallerPackageName = mSettings.getInstallerPackageName(pkgName); } + long origUpdateTime; + if (pkg.mExtras != null) { + origUpdateTime = ((PackageSetting)pkg.mExtras).lastUpdateTime; + } else { + origUpdateTime = 0; + } + // First delete the existing package while retaining the data directory if (!deletePackageLI(pkgName, true, PackageManager.DONT_DELETE_DATA, - res.removedInfo)) { - // If the existing package was'nt successfully deleted + res.removedInfo, true)) { + // If the existing package wasn't successfully deleted res.returnCode = PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE; deletedPkg = false; } else { // Successfully deleted the old package. Now proceed with re-installation mLastScanError = PackageManager.INSTALL_SUCCEEDED; - newPackage = scanPackageLI(pkg, parseFlags, scanMode); + newPackage = scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_TIME, + System.currentTimeMillis()); if (newPackage == null) { Slog.w(TAG, "Package couldn't be installed in " + pkg.mPath); if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) { @@ -5685,7 +5637,7 @@ class PackageManagerService extends IPackageManager.Stub { deletePackageLI( pkgName, true, PackageManager.DONT_DELETE_DATA, - res.removedInfo); + res.removedInfo, true); } // Since we failed to install the new package we need to restore the old // package that we deleted. @@ -5700,8 +5652,10 @@ class PackageManagerService extends IPackageManager.Stub { int oldParseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY | (isForwardLocked(deletedPackage) ? PackageParser.PARSE_FORWARD_LOCK : 0) | (oldOnSd ? PackageParser.PARSE_ON_SDCARD : 0); - int oldScanMode = (oldOnSd ? 0 : SCAN_MONITOR) | SCAN_UPDATE_SIGNATURE; - if (scanPackageLI(restoreFile, oldParseFlags, oldScanMode) == null) { + int oldScanMode = (oldOnSd ? 0 : SCAN_MONITOR) | SCAN_UPDATE_SIGNATURE + | SCAN_UPDATE_TIME; + if (scanPackageLI(restoreFile, oldParseFlags, oldScanMode, + origUpdateTime) == null) { Slog.e(TAG, "Failed to restore package : " + pkgName + " after failed upgrade"); return; } @@ -5752,13 +5706,18 @@ class PackageManagerService extends IPackageManager.Stub { // Successfully disabled the old package. Now proceed with re-installation mLastScanError = PackageManager.INSTALL_SUCCEEDED; pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; - newPackage = scanPackageLI(pkg, parseFlags, scanMode); + newPackage = scanPackageLI(pkg, parseFlags, scanMode, 0); if (newPackage == null) { Slog.w(TAG, "Package couldn't be installed in " + pkg.mPath); if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) { res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK; } } else { + if (newPackage.mExtras != null) { + final PackageSetting newPkgSetting = (PackageSetting)newPackage.mExtras; + newPkgSetting.firstInstallTime = oldPkgSetting.firstInstallTime; + newPkgSetting.lastUpdateTime = System.currentTimeMillis(); + } updateSettingsLI(newPackage, installerPackageName, res); updatedSettings = true; } @@ -5770,12 +5729,10 @@ class PackageManagerService extends IPackageManager.Stub { removePackageLI(newPackage, true); } // Add back the old system package - scanPackageLI(oldPkg, parseFlags, - SCAN_MONITOR - | SCAN_UPDATE_SIGNATURE); + scanPackageLI(oldPkg, parseFlags, SCAN_MONITOR | SCAN_UPDATE_SIGNATURE, 0); // Restore the old system information in Settings synchronized(mPackages) { - if(updatedSettings) { + if (updatedSettings) { mSettings.enableSystemPackageLP(packageName); mSettings.setInstallerPackageName(packageName, oldPkgSetting.installerPackageName); @@ -5789,9 +5746,8 @@ class PackageManagerService extends IPackageManager.Stub { PackageSetting ps = mSettings.getDisabledSystemPkg(packageName); if (ps != null && ps.codePathString != null && !ps.codePathString.equals(oldPkgSetting.codePathString)) { - int installFlags = 0; res.removedInfo.args = createInstallArgs(0, oldPkgSetting.codePathString, - oldPkgSetting.resourcePathString); + oldPkgSetting.resourcePathString, oldPkgSetting.nativeLibraryPathString); } } } @@ -5803,8 +5759,17 @@ class PackageManagerService extends IPackageManager.Stub { if ((newPackage.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) { retCode = mInstaller.movedex(newPackage.mScanPath, newPackage.mPath); if (retCode != 0) { - Slog.e(TAG, "Couldn't rename dex file: " + newPackage.mPath); - return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; + if (mNoDexOpt) { + /* + * If we're in an engineering build, programs are lazily run + * through dexopt. If the .dex file doesn't exist yet, it + * will be created when the program is run next. + */ + Slog.i(TAG, "dex file doesn't exist, skipping move: " + newPackage.mPath); + } else { + Slog.e(TAG, "Couldn't rename dex file: " + newPackage.mPath); + return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; + } } } return PackageManager.INSTALL_SUCCEEDED; @@ -5933,6 +5898,7 @@ class PackageManagerService extends IPackageManager.Stub { } // Set application objects path explicitly after the rename setApplicationInfoPaths(pkg, args.getCodePath(), args.getResourcePath()); + pkg.applicationInfo.nativeLibraryDir = args.getNativeLibraryPath(); if (replace) { replacePackageLI(pkg, parseFlags, scanMode, installerPackageName, res); @@ -5947,8 +5913,7 @@ class PackageManagerService extends IPackageManager.Stub { int retCode = 0; // TODO Gross hack but fix later. Ideally move this to be a post installation // check after alloting uid. - if ((newPackage.applicationInfo.flags - & ApplicationInfo.FLAG_FORWARD_LOCK) != 0) { + if (isForwardLocked(newPackage)) { File destResourceFile = new File(newPackage.applicationInfo.publicSourceDir); try { extractPublicFiles(newPackage, destResourceFile); @@ -5983,18 +5948,26 @@ class PackageManagerService extends IPackageManager.Stub { return PackageManager.INSTALL_SUCCEEDED; } - private boolean isForwardLocked(PackageParser.Package pkg) { - return ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0); + private static boolean isForwardLocked(PackageParser.Package pkg) { + return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0; } - private boolean isExternal(PackageParser.Package pkg) { - return ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0); + private static boolean isExternal(PackageParser.Package pkg) { + return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0; + } + + private static boolean isSystemApp(PackageParser.Package pkg) { + return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + } + + private static boolean isUpdatedSystemApp(PackageParser.Package pkg) { + return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; } private void extractPublicFiles(PackageParser.Package newPackage, File publicZipFile) throws IOException { - final ZipOutputStream publicZipOutStream = - new ZipOutputStream(new FileOutputStream(publicZipFile)); + final FileOutputStream fstr = new FileOutputStream(publicZipFile); + final ZipOutputStream publicZipOutStream = new ZipOutputStream(fstr); final ZipFile privateZip = new ZipFile(newPackage.mPath); // Copy manifest, resources.arsc and res directory to public zip @@ -6019,6 +5992,9 @@ class PackageManagerService extends IPackageManager.Stub { } } + publicZipOutStream.finish(); + publicZipOutStream.flush(); + FileUtils.sync(fstr); publicZipOutStream.close(); FileUtils.setPermissions( publicZipFile.getAbsolutePath(), @@ -6136,7 +6112,7 @@ class PackageManagerService extends IPackageManager.Stub { synchronized (mInstallLock) { res = deletePackageLI(packageName, deleteCodeAndResources, - flags | REMOVE_CHATTY, info); + flags | REMOVE_CHATTY, info, true); } if(res && sendBroadCast) { @@ -6197,7 +6173,7 @@ class PackageManagerService extends IPackageManager.Stub { * delete a partially installed application. */ private void removePackageDataLI(PackageParser.Package p, PackageRemovedInfo outInfo, - int flags) { + int flags, boolean writeSettings) { String packageName = p.packageName; if (outInfo != null) { outInfo.removedPackage = packageName; @@ -6209,24 +6185,24 @@ 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); + int retCode = mInstaller.remove(packageName, useEncryptedFSDir); if (retCode < 0) { Slog.w(TAG, "Couldn't remove app data or cache directory for package: " + packageName + ", retcode=" + retCode); // we don't consider this to be a failure of the core package deletion } } else { - //for emulator + // for simulator PackageParser.Package pkg = mPackages.get(packageName); File dataDir = new File(pkg.applicationInfo.dataDir); dataDir.delete(); } + schedulePackageCleaning(packageName); } synchronized (mPackages) { if (deletedPs != null) { - schedulePackageCleaning(packageName); - if ((flags&PackageManager.DONT_DELETE_DATA) == 0) { if (outInfo != null) { outInfo.removedUid = mSettings.removePackageLP(packageName); @@ -6250,8 +6226,10 @@ class PackageManagerService extends IPackageManager.Stub { mSettings.mPreferredActivities.removeFilter(pa); } } - // Save settings now - mSettings.writeLP(); + if (writeSettings) { + // Save settings now + mSettings.writeLP(); + } } } @@ -6259,7 +6237,7 @@ class PackageManagerService extends IPackageManager.Stub { * Tries to delete system package. */ private boolean deleteSystemPackageLI(PackageParser.Package p, - int flags, PackageRemovedInfo outInfo) { + int flags, PackageRemovedInfo outInfo, boolean writeSettings) { ApplicationInfo applicationInfo = p.applicationInfo; //applicable for non-partially installed applications only if (applicationInfo == null) { @@ -6281,33 +6259,31 @@ class PackageManagerService extends IPackageManager.Stub { } // Delete the updated package outInfo.isRemovedPackageSystemUpdate = true; - boolean deleteCodeAndResources = false; - if (ps.versionCode < p.mVersionCode) { + final boolean deleteCodeAndResources; + if (ps.versionCode < p.mVersionCode) { // Delete code and resources for downgrades deleteCodeAndResources = true; - if ((flags & PackageManager.DONT_DELETE_DATA) == 0) { - flags &= ~PackageManager.DONT_DELETE_DATA; - } + flags &= ~PackageManager.DONT_DELETE_DATA; } else { // Preserve data by setting flag - if ((flags & PackageManager.DONT_DELETE_DATA) == 0) { - flags |= PackageManager.DONT_DELETE_DATA; - } + deleteCodeAndResources = false; + flags |= PackageManager.DONT_DELETE_DATA; } - boolean ret = deleteInstalledPackageLI(p, deleteCodeAndResources, flags, outInfo); + boolean ret = deleteInstalledPackageLI(p, deleteCodeAndResources, flags, outInfo, + writeSettings); if (!ret) { return false; } synchronized (mPackages) { // Reinstate the old system package mSettings.enableSystemPackageLP(p.packageName); - // Remove any native libraries. - removeNativeBinariesLI(p); + // Remove any native libraries from the upgraded package. + NativeLibraryHelper.removeNativeBinariesLI(p.applicationInfo.nativeLibraryDir); } // Install the system package PackageParser.Package newPkg = scanPackageLI(ps.codePath, PackageParser.PARSE_MUST_BE_APK | PackageParser.PARSE_IS_SYSTEM, - SCAN_MONITOR | SCAN_NO_PATHS); + SCAN_MONITOR | SCAN_NO_PATHS, 0); if (newPkg == null) { Slog.w(TAG, "Failed to restore system package:"+p.packageName+" with error:" + mLastScanError); @@ -6315,13 +6291,16 @@ class PackageManagerService extends IPackageManager.Stub { } synchronized (mPackages) { updatePermissionsLP(newPkg.packageName, newPkg, true, true, false); - mSettings.writeLP(); + if (writeSettings) { + mSettings.writeLP(); + } } return true; } private boolean deleteInstalledPackageLI(PackageParser.Package p, - boolean deleteCodeAndResources, int flags, PackageRemovedInfo outInfo) { + boolean deleteCodeAndResources, int flags, PackageRemovedInfo outInfo, + boolean writeSettings) { ApplicationInfo applicationInfo = p.applicationInfo; if (applicationInfo == null) { Slog.w(TAG, "Package " + p.packageName + " has no applicationInfo."); @@ -6332,17 +6311,15 @@ class PackageManagerService extends IPackageManager.Stub { } // Delete package data from internal structures and also remove data if flag is set - removePackageDataLI(p, outInfo, flags); + removePackageDataLI(p, outInfo, flags, writeSettings); // Delete application code and resources if (deleteCodeAndResources) { // TODO can pick up from PackageSettings as well - int installFlags = ((p.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE)!=0) ? - PackageManager.INSTALL_EXTERNAL : 0; - installFlags |= ((p.applicationInfo.flags & ApplicationInfo.FLAG_FORWARD_LOCK)!=0) ? - PackageManager.INSTALL_FORWARD_LOCK : 0; - outInfo.args = createInstallArgs(installFlags, - applicationInfo.sourceDir, applicationInfo.publicSourceDir); + int installFlags = isExternal(p) ? PackageManager.INSTALL_EXTERNAL : 0; + installFlags |= isForwardLocked(p) ? PackageManager.INSTALL_FORWARD_LOCK : 0; + outInfo.args = createInstallArgs(installFlags, applicationInfo.sourceDir, + applicationInfo.publicSourceDir, applicationInfo.nativeLibraryDir); } return true; } @@ -6351,7 +6328,8 @@ class PackageManagerService extends IPackageManager.Stub { * This method handles package deletion in general */ private boolean deletePackageLI(String packageName, - boolean deleteCodeAndResources, int flags, PackageRemovedInfo outInfo) { + boolean deleteCodeAndResources, int flags, PackageRemovedInfo outInfo, + boolean writeSettings) { if (packageName == null) { Slog.w(TAG, "Attempt to delete null packageName."); return false; @@ -6378,7 +6356,7 @@ class PackageManagerService extends IPackageManager.Stub { if (dataOnly) { // Delete application data first - removePackageDataLI(p, outInfo, flags); + removePackageDataLI(p, outInfo, flags, writeSettings); return true; } // At this point the package should have ApplicationInfo associated with it @@ -6387,16 +6365,17 @@ class PackageManagerService extends IPackageManager.Stub { return false; } boolean ret = false; - if ( (p.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + if (isSystemApp(p)) { Log.i(TAG, "Removing system package:"+p.packageName); // When an updated system application is deleted we delete the existing resources as well and // fall back to existing code in system partition - ret = deleteSystemPackageLI(p, flags, outInfo); + ret = deleteSystemPackageLI(p, flags, outInfo, writeSettings); } else { Log.i(TAG, "Removing non-system package:"+p.packageName); // Kill application pre-emptively especially for apps on sd. killApplication(packageName, p.applicationInfo.uid); - ret = deleteInstalledPackageLI(p, deleteCodeAndResources, flags, outInfo); + ret = deleteInstalledPackageLI(p, deleteCodeAndResources, flags, outInfo, + writeSettings); } return ret; } @@ -6451,6 +6430,7 @@ class PackageManagerService extends IPackageManager.Stub { p = ps.pkg; } } + boolean useEncryptedFSDir = false; if(!dataOnly) { //need to check this only for fully installed applications @@ -6463,9 +6443,10 @@ 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); + int retCode = mInstaller.clearUserData(packageName, useEncryptedFSDir); if (retCode < 0) { Slog.w(TAG, "Couldn't remove cache files for package: " + packageName); @@ -6516,8 +6497,9 @@ 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); + int retCode = mInstaller.deleteCacheFiles(packageName, useEncryptedFSDir); if (retCode < 0) { Slog.w(TAG, "Couldn't remove cache files for package: " + packageName); @@ -6579,9 +6561,10 @@ 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); + publicSrcDir, pStats, useEncryptedFSDir); if (res < 0) { return false; } else { @@ -6814,6 +6797,7 @@ class PackageManagerService extends IPackageManager.Stub { return; } pkgSetting.enabled = newState; + pkgSetting.pkg.mSetEnabled = newState; } else { // We're dealing with a component level state change switch (newState) { @@ -6972,7 +6956,18 @@ class PackageManagerService extends IPackageManager.Stub { return; } + boolean dumpStar = true; + boolean dumpLibs = false; + boolean dumpFeatures = false; + boolean dumpResolvers = false; + boolean dumpPermissions = false; + boolean dumpPackages = false; + boolean dumpSharedUsers = false; + boolean dumpMessages = false; + boolean dumpProviders = false; + String packageName = null; + boolean showFilters = false; int opti = 0; while (opti < args.length) { @@ -6985,10 +6980,22 @@ class PackageManagerService extends IPackageManager.Stub { // Right now we only know how to print all. } else if ("-h".equals(opt)) { pw.println("Package manager dump options:"); - pw.println(" [-h] [cmd] ..."); + pw.println(" [-h] [-f] [cmd] ..."); + pw.println(" -f: print details of intent filters"); + pw.println(" -h: print this help"); pw.println(" cmd may be one of:"); - pw.println(" [package.name]: info about given package"); + pw.println(" l[ibraries]: list known shared libraries"); + pw.println(" f[ibraries]: list device features"); + pw.println(" r[esolvers]: dump intent resolvers"); + pw.println(" perm[issions]: dump permissions"); + pw.println(" prov[iders]: dump content providers"); + pw.println(" p[ackages]: dump installed packages"); + pw.println(" s[hared-users]: dump shared user IDs"); + pw.println(" m[essages]: print collected runtime messages"); + pw.println(" <package.name>: info about given package"); return; + } else if ("-f".equals(opt)) { + showFilters = true; } else { pw.println("Unknown argument: " + opt + "; use -h for help"); } @@ -7001,32 +7008,87 @@ class PackageManagerService extends IPackageManager.Stub { // Is this a package name? if ("android".equals(cmd) || cmd.contains(".")) { packageName = cmd; + } else if ("l".equals(cmd) || "libraries".equals(cmd)) { + dumpStar = false; + dumpLibs = true; + } else if ("f".equals(cmd) || "features".equals(cmd)) { + dumpStar = false; + dumpFeatures = true; + } else if ("r".equals(cmd) || "resolvers".equals(cmd)) { + dumpStar = false; + dumpResolvers = true; + } else if ("perm".equals(cmd) || "permissions".equals(cmd)) { + dumpStar = false; + dumpPermissions = true; + } else if ("p".equals(cmd) || "packages".equals(cmd)) { + dumpStar = false; + dumpPackages = true; + } else if ("s".equals(cmd) || "shared-users".equals(cmd)) { + dumpStar = false; + dumpSharedUsers = true; + } else if ("prov".equals(cmd) || "providers".equals(cmd)) { + dumpStar = false; + dumpProviders = true; + } else if ("m".equals(cmd) || "messages".equals(cmd)) { + dumpStar = false; + dumpMessages = true; } } boolean printedTitle = false; synchronized (mPackages) { - if (mActivities.dump(pw, "Activity Resolver Table:", " ", packageName)) { - printedTitle = true; - } - if (mReceivers.dump(pw, printedTitle - ? "\nReceiver Resolver Table:" : "Receiver Resolver Table:", - " ", packageName)) { + if ((dumpStar || dumpLibs) && packageName == null) { + if (printedTitle) pw.println(" "); printedTitle = true; + pw.println("Libraries:"); + Iterator<String> it = mSharedLibraries.keySet().iterator(); + while (it.hasNext()) { + String name = it.next(); + pw.print(" "); + pw.print(name); + pw.print(" -> "); + pw.println(mSharedLibraries.get(name)); + } } - if (mServices.dump(pw, printedTitle - ? "\nService Resolver Table:" : "Service Resolver Table:", - " ", packageName)) { + + if ((dumpStar || dumpFeatures) && packageName == null) { + if (printedTitle) pw.println(" "); printedTitle = true; + pw.println("Features:"); + Iterator<String> it = mAvailableFeatures.keySet().iterator(); + while (it.hasNext()) { + String name = it.next(); + pw.print(" "); + pw.println(name); + } } - if (mSettings.mPreferredActivities.dump(pw, printedTitle - ? "\nPreferred Activities:" : "Preferred Activities:", - " ", packageName)) { - printedTitle = true; + + if (dumpStar || dumpResolvers) { + if (mActivities.dump(pw, printedTitle + ? "\nActivity Resolver Table:" : "Activity Resolver Table:", + " ", packageName, showFilters)) { + printedTitle = true; + } + if (mReceivers.dump(pw, printedTitle + ? "\nReceiver Resolver Table:" : "Receiver Resolver Table:", + " ", packageName, showFilters)) { + printedTitle = true; + } + if (mServices.dump(pw, printedTitle + ? "\nService Resolver Table:" : "Service Resolver Table:", + " ", packageName, showFilters)) { + printedTitle = true; + } + if (mSettings.mPreferredActivities.dump(pw, printedTitle + ? "\nPreferred Activities:" : "Preferred Activities:", + " ", packageName, showFilters)) { + printedTitle = true; + } } + boolean printedSomething = false; - { + if (dumpStar || dumpPermissions) { for (BasePermission p : mSettings.mPermissions.values()) { if (packageName != null && !packageName.equals(p.sourcePackage)) { continue; @@ -7053,9 +7115,29 @@ class PackageManagerService extends IPackageManager.Stub { } } } + + if (dumpStar || dumpProviders) { + printedSomething = false; + for (PackageParser.Provider p : mProviders.values()) { + if (packageName != null && !packageName.equals(p.info.packageName)) { + continue; + } + if (!printedSomething) { + if (printedTitle) pw.println(" "); + pw.println("Registered ContentProviders:"); + printedSomething = true; + printedTitle = true; + } + pw.print(" ["); pw.print(p.info.authority); pw.print("]: "); + pw.println(p.toString()); + } + } + printedSomething = false; SharedUserSetting packageSharedUser = null; - { + if (dumpStar || dumpPackages) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + Date date = new Date(); for (PackageSetting ps : mSettings.mPackages.values()) { if (packageName != null && !packageName.equals(ps.realName) && !packageName.equals(ps.name)) { @@ -7082,12 +7164,25 @@ class PackageManagerService extends IPackageManager.Stub { pw.print(" pkg="); pw.println(ps.pkg); pw.print(" codePath="); pw.println(ps.codePathString); pw.print(" resourcePath="); pw.println(ps.resourcePathString); + pw.print(" nativeLibraryPath="); pw.println(ps.nativeLibraryPathString); + pw.print(" obbPath="); pw.println(ps.obbPathString); + pw.print(" versionCode="); pw.println(ps.versionCode); if (ps.pkg != null) { + pw.print(" versionName="); pw.println(ps.pkg.mVersionName); pw.print(" dataDir="); pw.println(ps.pkg.applicationInfo.dataDir); pw.print(" targetSdk="); pw.println(ps.pkg.applicationInfo.targetSdkVersion); + if (ps.pkg.mOperationPending) { + pw.println(" mOperationPending=true"); + } pw.print(" supportsScreens=["); boolean first = true; if ((ps.pkg.applicationInfo.flags & + ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) { + if (!first) pw.print(", "); + first = false; + pw.print("small"); + } + if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS) != 0) { if (!first) pw.print(", "); first = false; @@ -7100,10 +7195,10 @@ class PackageManagerService extends IPackageManager.Stub { pw.print("large"); } if ((ps.pkg.applicationInfo.flags & - ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) { + ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) { if (!first) pw.print(", "); first = false; - pw.print("small"); + pw.print("xlarge"); } if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) { @@ -7119,7 +7214,12 @@ class PackageManagerService extends IPackageManager.Stub { } } pw.println("]"); - pw.print(" timeStamp="); pw.println(ps.getTimeStampStr()); + pw.print(" timeStamp="); + date.setTime(ps.timeStamp); pw.println(sdf.format(date)); + pw.print(" firstInstallTime="); + date.setTime(ps.firstInstallTime); pw.println(sdf.format(date)); + pw.print(" lastUpdateTime="); + date.setTime(ps.lastUpdateTime); pw.println(sdf.format(date)); pw.print(" signatures="); pw.println(ps.signatures); pw.print(" permissionsFixed="); pw.print(ps.permissionsFixed); pw.print(" haveGids="); pw.println(ps.haveGids); @@ -7147,52 +7247,54 @@ class PackageManagerService extends IPackageManager.Stub { } } printedSomething = false; - if (mSettings.mRenamedPackages.size() > 0) { - for (HashMap.Entry<String, String> e - : mSettings.mRenamedPackages.entrySet()) { - if (packageName != null && !packageName.equals(e.getKey()) - && !packageName.equals(e.getValue())) { - continue; - } - if (!printedSomething) { - if (printedTitle) pw.println(" "); - pw.println("Renamed packages:"); - printedSomething = true; - printedTitle = true; + if (dumpStar || dumpPackages) { + if (mSettings.mRenamedPackages.size() > 0) { + for (HashMap.Entry<String, String> e + : mSettings.mRenamedPackages.entrySet()) { + if (packageName != null && !packageName.equals(e.getKey()) + && !packageName.equals(e.getValue())) { + continue; + } + if (!printedSomething) { + if (printedTitle) pw.println(" "); + pw.println("Renamed packages:"); + printedSomething = true; + printedTitle = true; + } + pw.print(" "); pw.print(e.getKey()); pw.print(" -> "); + pw.println(e.getValue()); } - pw.print(" "); pw.print(e.getKey()); pw.print(" -> "); - pw.println(e.getValue()); } - } - printedSomething = false; - if (mSettings.mDisabledSysPackages.size() > 0) { - for (PackageSetting ps : mSettings.mDisabledSysPackages.values()) { - if (packageName != null && !packageName.equals(ps.realName) - && !packageName.equals(ps.name)) { - continue; - } - if (!printedSomething) { - if (printedTitle) pw.println(" "); - pw.println("Hidden system packages:"); - printedSomething = true; - printedTitle = true; - } - pw.print(" Package ["); - pw.print(ps.realName != null ? ps.realName : ps.name); - pw.print("] ("); - pw.print(Integer.toHexString(System.identityHashCode(ps))); - pw.println("):"); - if (ps.realName != null) { - pw.print(" compat name="); pw.println(ps.name); + printedSomething = false; + if (mSettings.mDisabledSysPackages.size() > 0) { + for (PackageSetting ps : mSettings.mDisabledSysPackages.values()) { + if (packageName != null && !packageName.equals(ps.realName) + && !packageName.equals(ps.name)) { + continue; + } + if (!printedSomething) { + if (printedTitle) pw.println(" "); + pw.println("Hidden system packages:"); + printedSomething = true; + printedTitle = true; + } + pw.print(" Package ["); + pw.print(ps.realName != null ? ps.realName : ps.name); + pw.print("] ("); + pw.print(Integer.toHexString(System.identityHashCode(ps))); + pw.println("):"); + if (ps.realName != null) { + pw.print(" compat name="); pw.println(ps.name); + } + pw.print(" userId="); pw.println(ps.userId); + pw.print(" sharedUser="); pw.println(ps.sharedUser); + pw.print(" codePath="); pw.println(ps.codePathString); + pw.print(" resourcePath="); pw.println(ps.resourcePathString); } - pw.print(" userId="); pw.println(ps.userId); - pw.print(" sharedUser="); pw.println(ps.sharedUser); - pw.print(" codePath="); pw.println(ps.codePathString); - pw.print(" resourcePath="); pw.println(ps.resourcePathString); } } printedSomething = false; - { + if (dumpStar || dumpSharedUsers) { for (SharedUserSetting su : mSettings.mSharedUsers.values()) { if (packageName != null && su != packageSharedUser) { continue; @@ -7215,11 +7317,11 @@ class PackageManagerService extends IPackageManager.Stub { } } - if (packageName == null) { + if ((dumpStar || dumpMessages) && packageName == null) { if (printedTitle) pw.println(" "); printedTitle = true; pw.println("Settings parse messages:"); - pw.println(mSettings.mReadMessages.toString()); + pw.print(mSettings.mReadMessages.toString()); pw.println(" "); pw.println("Package warning messages:"); @@ -7230,29 +7332,12 @@ class PackageManagerService extends IPackageManager.Stub { int avail = in.available(); byte[] data = new byte[avail]; in.read(data); - pw.println(new String(data)); + pw.print(new String(data)); } catch (FileNotFoundException e) { } catch (IOException e) { } } } - - synchronized (mProviders) { - boolean printedSomething = false; - for (PackageParser.Provider p : mProviders.values()) { - if (packageName != null && !packageName.equals(p.info.packageName)) { - continue; - } - if (!printedSomething) { - if (printedTitle) pw.println(" "); - pw.println("Registered ContentProviders:"); - printedSomething = true; - printedTitle = true; - } - pw.print(" ["); pw.print(p.info.authority); pw.print("]: "); - pw.println(p.toString()); - } - } } static final class BasePermission { @@ -7627,7 +7712,8 @@ class PackageManagerService extends IPackageManager.Stub { this.pkgFlags = pkgFlags & ( ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_FORWARD_LOCK | - ApplicationInfo.FLAG_EXTERNAL_STORAGE); + ApplicationInfo.FLAG_EXTERNAL_STORAGE | + ApplicationInfo.FLAG_NEVER_ENCRYPT); } } @@ -7641,8 +7727,11 @@ class PackageManagerService extends IPackageManager.Stub { String codePathString; File resourcePath; String resourcePathString; - private long timeStamp; - private String timeStampString = "0"; + String nativeLibraryPathString; + String obbPathString; + long timeStamp; + long firstInstallTime; + long lastUpdateTime; int versionCode; boolean uidError; @@ -7665,21 +7754,23 @@ class PackageManagerService extends IPackageManager.Stub { String installerPackageName; PackageSettingBase(String name, String realName, File codePath, File resourcePath, - int pVersionCode, int pkgFlags) { + String nativeLibraryPathString, int pVersionCode, int pkgFlags) { super(pkgFlags); this.name = name; this.realName = realName; - init(codePath, resourcePath, pVersionCode); + init(codePath, resourcePath, nativeLibraryPathString, pVersionCode); } - void init(File codePath, File resourcePath, int pVersionCode) { + void init(File codePath, File resourcePath, String nativeLibraryPathString, + int pVersionCode) { this.codePath = codePath; this.codePathString = codePath.toString(); this.resourcePath = resourcePath; this.resourcePathString = resourcePath.toString(); + this.nativeLibraryPathString = nativeLibraryPathString; this.versionCode = pVersionCode; } - + public void setInstallerPackageName(String packageName) { installerPackageName = packageName; } @@ -7697,23 +7788,7 @@ class PackageManagerService extends IPackageManager.Stub { } public void setTimeStamp(long newStamp) { - if (newStamp != timeStamp) { - timeStamp = newStamp; - timeStampString = Long.toString(newStamp); - } - } - - public void setTimeStamp(long newStamp, String newStampStr) { timeStamp = newStamp; - timeStampString = newStampStr; - } - - public long getTimeStamp() { - return timeStamp; - } - - public String getTimeStampStr() { - return timeStampString; } public void copyFrom(PackageSettingBase base) { @@ -7721,7 +7796,8 @@ class PackageManagerService extends IPackageManager.Stub { gids = base.gids; timeStamp = base.timeStamp; - timeStampString = base.timeStampString; + firstInstallTime = base.firstInstallTime; + lastUpdateTime = base.lastUpdateTime; signatures = base.signatures; permissionsFixed = base.permissionsFixed; haveGids = base.haveGids; @@ -7769,8 +7845,9 @@ class PackageManagerService extends IPackageManager.Stub { SharedUserSetting sharedUser; PackageSetting(String name, String realName, File codePath, File resourcePath, - int pVersionCode, int pkgFlags) { - super(name, realName, codePath, resourcePath, pVersionCode, pkgFlags); + String nativeLibraryPathString, int pVersionCode, int pkgFlags) { + super(name, realName, codePath, resourcePath, nativeLibraryPathString, pVersionCode, + pkgFlags); } @Override @@ -7882,8 +7959,9 @@ class PackageManagerService extends IPackageManager.Stub { final int sharedId; PendingPackage(String name, String realName, File codePath, File resourcePath, - int sharedId, int pVersionCode, int pkgFlags) { - super(name, realName, codePath, resourcePath, pVersionCode, pkgFlags); + String nativeLibraryPathString, int sharedId, int pVersionCode, int pkgFlags) { + super(name, realName, codePath, resourcePath, nativeLibraryPathString, + pVersionCode, pkgFlags); this.sharedId = sharedId; } } @@ -7894,11 +7972,17 @@ class PackageManagerService extends IPackageManager.Stub { File dataDir = Environment.getDataDirectory(); File systemDir = new File(dataDir, "system"); // TODO(oam): This secure dir creation needs to be moved somewhere else (later) + File systemSecureDir = new File(dataDir, "secure/system"); systemDir.mkdirs(); + systemSecureDir.mkdirs(); FileUtils.setPermissions(systemDir.toString(), FileUtils.S_IRWXU|FileUtils.S_IRWXG |FileUtils.S_IROTH|FileUtils.S_IXOTH, -1, -1); + FileUtils.setPermissions(systemSecureDir.toString(), + FileUtils.S_IRWXU|FileUtils.S_IRWXG + |FileUtils.S_IROTH|FileUtils.S_IXOTH, + -1, -1); mSettingsFilename = new File(systemDir, "packages.xml"); mBackupSettingsFilename = new File(systemDir, "packages-backup.xml"); mPackageListFilename = new File(systemDir, "packages.list"); @@ -7906,10 +7990,10 @@ class PackageManagerService extends IPackageManager.Stub { PackageSetting getPackageLP(PackageParser.Package pkg, PackageSetting origPackage, String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, - int pkgFlags, boolean create, boolean add) { + String nativeLibraryPathString, int pkgFlags, boolean create, boolean add) { final String name = pkg.packageName; PackageSetting p = getPackageLP(name, origPackage, realName, sharedUser, codePath, - resourcePath, pkg.mVersionCode, pkgFlags, create, add); + resourcePath, nativeLibraryPathString, pkg.mVersionCode, pkgFlags, create, add); return p; } @@ -8005,14 +8089,14 @@ class PackageManagerService extends IPackageManager.Stub { if((p.pkg != null) && (p.pkg.applicationInfo != null)) { p.pkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; } - PackageSetting ret = addPackageLP(name, p.realName, p.codePath, - p.resourcePath, p.userId, p.versionCode, p.pkgFlags); + PackageSetting ret = addPackageLP(name, p.realName, p.codePath, p.resourcePath, + p.nativeLibraryPathString, p.userId, p.versionCode, p.pkgFlags); mDisabledSysPackages.remove(name); return ret; } - PackageSetting addPackageLP(String name, String realName, File codePath, - File resourcePath, int uid, int vc, int pkgFlags) { + PackageSetting addPackageLP(String name, String realName, File codePath, File resourcePath, + String nativeLibraryPathString, int uid, int vc, int pkgFlags) { PackageSetting p = mPackages.get(name); if (p != null) { if (p.userId == uid) { @@ -8022,7 +8106,8 @@ class PackageManagerService extends IPackageManager.Stub { "Adding duplicate package, keeping first: " + name); return null; } - p = new PackageSetting(name, realName, codePath, resourcePath, vc, pkgFlags); + p = new PackageSetting(name, realName, codePath, resourcePath, nativeLibraryPathString, + vc, pkgFlags); p.userId = uid; if (addUserIdLP(uid, p, name)) { mPackages.put(name, p); @@ -8077,12 +8162,12 @@ class PackageManagerService extends IPackageManager.Stub { private PackageSetting getPackageLP(String name, PackageSetting origPackage, String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, - int vc, int pkgFlags, boolean create, boolean add) { + String nativeLibraryPathString, int vc, int pkgFlags, boolean create, boolean add) { PackageSetting p = mPackages.get(name); if (p != null) { if (!p.codePath.equals(codePath)) { // Check to see if its a disabled system app - if((p != null) && ((p.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0)) { + if ((p.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0) { // This is an updated system app with versions in both system // and data partition. Just let the most recent version // take precedence. @@ -8093,6 +8178,13 @@ class PackageManagerService extends IPackageManager.Stub { // let's log a message about it. Slog.i(TAG, "Package " + name + " codePath changed from " + p.codePath + " to " + codePath + "; Retaining data and using new"); + /* + * Since we've changed paths, we need to prefer the new + * native library path over the one stored in the + * package settings since we might have moved from + * internal to external storage or vice versa. + */ + p.nativeLibraryPathString = nativeLibraryPathString; } } if (p.sharedUser != sharedUser) { @@ -8120,8 +8212,8 @@ class PackageManagerService extends IPackageManager.Stub { } if (origPackage != null) { // We are consuming the data from an existing package. - p = new PackageSetting(origPackage.name, name, codePath, - resourcePath, vc, pkgFlags); + p = new PackageSetting(origPackage.name, name, codePath, resourcePath, + nativeLibraryPathString, vc, pkgFlags); if (DEBUG_UPGRADE) Log.v(TAG, "Package " + name + " is adopting original package " + origPackage.name); // Note that we will retain the new package's signature so @@ -8137,7 +8229,8 @@ class PackageManagerService extends IPackageManager.Stub { // Update new package state. p.setTimeStamp(codePath.lastModified()); } else { - p = new PackageSetting(name, realName, codePath, resourcePath, vc, pkgFlags); + p = new PackageSetting(name, realName, codePath, resourcePath, + nativeLibraryPathString, vc, pkgFlags); p.setTimeStamp(codePath.lastModified()); p.sharedUser = sharedUser; if (sharedUser != null) { @@ -8185,8 +8278,9 @@ class PackageManagerService extends IPackageManager.Stub { private void insertPackageSettingLP(PackageSetting p, PackageParser.Package pkg) { p.pkg = pkg; - String codePath = pkg.applicationInfo.sourceDir; - String resourcePath = pkg.applicationInfo.publicSourceDir; + pkg.mSetEnabled = p.enabled; + final String codePath = pkg.applicationInfo.sourceDir; + final String resourcePath = pkg.applicationInfo.publicSourceDir; // Update code path if needed if (!codePath.equalsIgnoreCase(p.codePathString)) { Slog.w(TAG, "Code path for pkg : " + p.pkg.packageName + @@ -8201,6 +8295,12 @@ class PackageManagerService extends IPackageManager.Stub { p.resourcePath = new File(resourcePath); p.resourcePathString = resourcePath; } + // Update the native library path if needed + final String nativeLibraryPath = pkg.applicationInfo.nativeLibraryDir; + if (nativeLibraryPath != null + && !nativeLibraryPath.equalsIgnoreCase(p.nativeLibraryPathString)) { + p.nativeLibraryPathString = nativeLibraryPath; + } // Update version code if needed if (pkg.mVersionCode != p.versionCode) { p.versionCode = pkg.mVersionCode; @@ -8396,7 +8496,8 @@ class PackageManagerService extends IPackageManager.Stub { mPastSignatures.clear(); try { - FileOutputStream str = new FileOutputStream(mSettingsFilename); + FileOutputStream fstr = new FileOutputStream(mSettingsFilename); + BufferedOutputStream str = new BufferedOutputStream(fstr); //XmlSerializer serializer = XmlUtils.serializerInstance(); XmlSerializer serializer = new FastXmlSerializer(); @@ -8477,6 +8578,7 @@ class PackageManagerService extends IPackageManager.Stub { serializer.endDocument(); str.flush(); + FileUtils.sync(fstr); str.close(); // New settings successfully written, old ones are no longer @@ -8493,12 +8595,13 @@ class PackageManagerService extends IPackageManager.Stub { File tempFile = new File(mPackageListFilename.toString() + ".tmp"); JournaledFile journal = new JournaledFile(mPackageListFilename, tempFile); - str = new FileOutputStream(journal.chooseForWrite()); + fstr = new FileOutputStream(journal.chooseForWrite()); + str = new BufferedOutputStream(fstr); try { StringBuilder sb = new StringBuilder(); for (PackageSetting pkg : mPackages.values()) { ApplicationInfo ai = pkg.pkg.applicationInfo; - String dataPath = ai.dataDir; + String dataPath = ai.dataDir; boolean isDebug = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; // Avoid any application that has a space in its path @@ -8529,6 +8632,7 @@ class PackageManagerService extends IPackageManager.Stub { str.write(sb.toString().getBytes()); } str.flush(); + FileUtils.sync(fstr); str.close(); journal.commit(); } @@ -8566,11 +8670,16 @@ class PackageManagerService extends IPackageManager.Stub { serializer.attribute(null, "realName", pkg.realName); } serializer.attribute(null, "codePath", pkg.codePathString); - serializer.attribute(null, "ts", pkg.getTimeStampStr()); + serializer.attribute(null, "ft", Long.toHexString(pkg.timeStamp)); + serializer.attribute(null, "it", Long.toHexString(pkg.firstInstallTime)); + serializer.attribute(null, "ut", Long.toHexString(pkg.lastUpdateTime)); serializer.attribute(null, "version", String.valueOf(pkg.versionCode)); if (!pkg.resourcePathString.equals(pkg.codePathString)) { serializer.attribute(null, "resourcePath", pkg.resourcePathString); } + if (pkg.nativeLibraryPathString != null) { + serializer.attribute(null, "nativeLibraryPath", pkg.nativeLibraryPathString); + } if (pkg.sharedUser == null) { serializer.attribute(null, "userId", Integer.toString(pkg.userId)); @@ -8610,9 +8719,14 @@ class PackageManagerService extends IPackageManager.Stub { if (!pkg.resourcePathString.equals(pkg.codePathString)) { serializer.attribute(null, "resourcePath", pkg.resourcePathString); } + if (pkg.nativeLibraryPathString != null) { + serializer.attribute(null, "nativeLibraryPath", pkg.nativeLibraryPathString); + } serializer.attribute(null, "flags", Integer.toString(pkg.pkgFlags)); - serializer.attribute(null, "ts", pkg.getTimeStampStr()); + serializer.attribute(null, "ft", Long.toHexString(pkg.timeStamp)); + serializer.attribute(null, "it", Long.toHexString(pkg.firstInstallTime)); + serializer.attribute(null, "ut", Long.toHexString(pkg.lastUpdateTime)); serializer.attribute(null, "version", String.valueOf(pkg.versionCode)); if (pkg.sharedUser == null) { serializer.attribute(null, "userId", @@ -8635,6 +8749,9 @@ class PackageManagerService extends IPackageManager.Stub { if (pkg.installerPackageName != null) { serializer.attribute(null, "installer", pkg.installerPackageName); } + if (pkg.obbPathString != null) { + serializer.attribute(null, "obbPath", pkg.obbPathString); + } pkg.signatures.writeXml(serializer, "sigs", mPastSignatures); if ((pkg.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) { serializer.startTag(null, "perms"); @@ -8730,7 +8847,7 @@ class PackageManagerService extends IPackageManager.Stub { try { str = new FileInputStream(mBackupSettingsFilename); mReadMessages.append("Reading from backup settings file\n"); - Log.i(TAG, "Reading from backup settings file!"); + reportSettingsProblem(Log.INFO, "Need to read from backup settings file"); if (mSettingsFilename.exists()) { // If both the backup and settings file exist, we // ignore the settings since it might have been @@ -8749,7 +8866,7 @@ class PackageManagerService extends IPackageManager.Stub { if (str == null) { if (!mSettingsFilename.exists()) { mReadMessages.append("No settings file found\n"); - Slog.i(TAG, "No current settings file!"); + reportSettingsProblem(Log.INFO, "No settings file; creating initial state"); return false; } str = new FileInputStream(mSettingsFilename); @@ -8765,7 +8882,7 @@ class PackageManagerService extends IPackageManager.Stub { if (type != XmlPullParser.START_TAG) { mReadMessages.append("No start tag found in settings file\n"); - Slog.e(TAG, "No start tag found in package manager settings"); + reportSettingsProblem(Log.WARN, "No start tag found in package manager settings"); return false; } @@ -8828,10 +8945,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); } 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); } @@ -8842,10 +8961,10 @@ class PackageManagerService extends IPackageManager.Stub { Object idObj = getUserIdLP(pp.sharedId); if (idObj != null && idObj instanceof SharedUserSetting) { PackageSetting p = getPackageLP(pp.name, null, pp.realName, - (SharedUserSetting)idObj, pp.codePath, pp.resourcePath, - pp.versionCode, pp.pkgFlags, true, true); + (SharedUserSetting) idObj, pp.codePath, pp.resourcePath, + pp.nativeLibraryPathString, pp.versionCode, pp.pkgFlags, true, true); if (p == null) { - Slog.w(TAG, "Unable to create application package for " + reportSettingsProblem(Log.WARN, "Unable to create application package for " + pp.name); continue; } @@ -8855,13 +8974,13 @@ class PackageManagerService extends IPackageManager.Stub { + " has shared uid " + pp.sharedId + " that is not a shared uid\n"; mReadMessages.append(msg); - Slog.e(TAG, msg); + reportSettingsProblem(Log.ERROR, msg); } else { String msg = "Bad package setting: package " + pp.name + " has shared uid " + pp.sharedId + " that is not defined\n"; mReadMessages.append(msg); - Slog.e(TAG, msg); + reportSettingsProblem(Log.ERROR, msg); } } mPendingPackages.clear(); @@ -8948,6 +9067,7 @@ class PackageManagerService extends IPackageManager.Stub { String realName = parser.getAttributeValue(null, "realName"); String codePathStr = parser.getAttributeValue(null, "codePath"); String resourcePathStr = parser.getAttributeValue(null, "resourcePath"); + String nativeLibraryPathStr = parser.getAttributeValue(null, "nativeLibraryPath"); if (resourcePathStr == null) { resourcePathStr = codePathStr; } @@ -8962,14 +9082,36 @@ class PackageManagerService extends IPackageManager.Stub { int pkgFlags = 0; pkgFlags |= ApplicationInfo.FLAG_SYSTEM; - PackageSetting ps = new PackageSetting(name, realName, - new File(codePathStr), - new File(resourcePathStr), versionCode, pkgFlags); - String timeStampStr = parser.getAttributeValue(null, "ts"); + PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr), + new File(resourcePathStr), nativeLibraryPathStr, versionCode, pkgFlags); + String timeStampStr = parser.getAttributeValue(null, "ft"); + if (timeStampStr != null) { + try { + long timeStamp = Long.parseLong(timeStampStr, 16); + ps.setTimeStamp(timeStamp); + } catch (NumberFormatException e) { + } + } else { + timeStampStr = parser.getAttributeValue(null, "ts"); + if (timeStampStr != null) { + try { + long timeStamp = Long.parseLong(timeStampStr); + ps.setTimeStamp(timeStamp); + } catch (NumberFormatException e) { + } + } + } + timeStampStr = parser.getAttributeValue(null, "it"); if (timeStampStr != null) { try { - long timeStamp = Long.parseLong(timeStampStr); - ps.setTimeStamp(timeStamp, timeStampStr); + ps.firstInstallTime = Long.parseLong(timeStampStr, 16); + } catch (NumberFormatException e) { + } + } + timeStampStr = parser.getAttributeValue(null, "ut"); + if (timeStampStr != null) { + try { + ps.lastUpdateTime = Long.parseLong(timeStampStr, 16); } catch (NumberFormatException e) { } } @@ -9011,12 +9153,15 @@ class PackageManagerService extends IPackageManager.Stub { String sharedIdStr = null; String codePathStr = null; String resourcePathStr = null; + String nativeLibraryPathStr = null; + String obbPathStr = null; String systemStr = null; String installerPackageName = null; String uidError = null; int pkgFlags = 0; - String timeStampStr; long timeStamp = 0; + long firstInstallTime = 0; + long lastUpdateTime = 0; PackageSettingBase packageSetting = null; String version = null; int versionCode = 0; @@ -9028,6 +9173,8 @@ class PackageManagerService extends IPackageManager.Stub { sharedIdStr = parser.getAttributeValue(null, "sharedUserId"); codePathStr = parser.getAttributeValue(null, "codePath"); resourcePathStr = parser.getAttributeValue(null, "resourcePath"); + nativeLibraryPathStr = parser.getAttributeValue(null, "nativeLibraryPath"); + obbPathStr = parser.getAttributeValue(null, "obbPath"); version = parser.getAttributeValue(null, "version"); if (version != null) { try { @@ -9054,10 +9201,32 @@ class PackageManagerService extends IPackageManager.Stub { pkgFlags |= ApplicationInfo.FLAG_SYSTEM; } } - timeStampStr = parser.getAttributeValue(null, "ts"); + String timeStampStr = parser.getAttributeValue(null, "ft"); if (timeStampStr != null) { try { - timeStamp = Long.parseLong(timeStampStr); + timeStamp = Long.parseLong(timeStampStr, 16); + } catch (NumberFormatException e) { + } + } else { + timeStampStr = parser.getAttributeValue(null, "ts"); + if (timeStampStr != null) { + try { + timeStamp = Long.parseLong(timeStampStr); + } catch (NumberFormatException e) { + } + } + } + timeStampStr = parser.getAttributeValue(null, "it"); + if (timeStampStr != null) { + try { + firstInstallTime = Long.parseLong(timeStampStr, 16); + } catch (NumberFormatException e) { + } + } + timeStampStr = parser.getAttributeValue(null, "ut"); + if (timeStampStr != null) { + try { + lastUpdateTime = Long.parseLong(timeStampStr, 16); } catch (NumberFormatException e) { } } @@ -9079,9 +9248,9 @@ class PackageManagerService extends IPackageManager.Stub { "Error in package manager settings: <package> has no codePath at " + parser.getPositionDescription()); } else if (userId > 0) { - packageSetting = addPackageLP(name.intern(), realName, - new File(codePathStr), new File(resourcePathStr), - userId, versionCode, pkgFlags); + packageSetting = addPackageLP(name.intern(), realName, new File(codePathStr), + new File(resourcePathStr), nativeLibraryPathStr, userId, versionCode, + pkgFlags); if (DEBUG_SETTINGS) Log.i(TAG, "Reading package " + name + ": userId=" + userId + " pkg=" + packageSetting); if (packageSetting == null) { @@ -9090,7 +9259,9 @@ class PackageManagerService extends IPackageManager.Stub { + " while parsing settings at " + parser.getPositionDescription()); } else { - packageSetting.setTimeStamp(timeStamp, timeStampStr); + packageSetting.setTimeStamp(timeStamp); + packageSetting.firstInstallTime = firstInstallTime; + packageSetting.lastUpdateTime = lastUpdateTime; } } else if (sharedIdStr != null) { userId = sharedIdStr != null @@ -9098,8 +9269,10 @@ class PackageManagerService extends IPackageManager.Stub { if (userId > 0) { packageSetting = new PendingPackage(name.intern(), realName, new File(codePathStr), new File(resourcePathStr), - userId, versionCode, pkgFlags); - packageSetting.setTimeStamp(timeStamp, timeStampStr); + nativeLibraryPathStr, userId, versionCode, pkgFlags); + packageSetting.setTimeStamp(timeStamp); + packageSetting.firstInstallTime = firstInstallTime; + packageSetting.lastUpdateTime = lastUpdateTime; mPendingPackages.add((PendingPackage) packageSetting); if (DEBUG_SETTINGS) Log.i(TAG, "Reading package " + name + ": sharedUserId=" + userId + " pkg=" @@ -9125,6 +9298,8 @@ class PackageManagerService extends IPackageManager.Stub { if (packageSetting != null) { packageSetting.uidError = "true".equals(uidError); packageSetting.installerPackageName = installerPackageName; + packageSetting.nativeLibraryPathString = nativeLibraryPathStr; + packageSetting.obbPathString = obbPathStr; final String enabledStr = parser.getAttributeValue(null, "enabled"); if (enabledStr != null) { if (enabledStr.equalsIgnoreCase("true")) { @@ -9404,6 +9579,9 @@ class PackageManagerService extends IPackageManager.Stub { } boolean isEnabledLP(ComponentInfo componentInfo, int flags) { + if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { + return true; + } final PackageSetting packageSettings = mPackages.get(componentInfo.packageName); if (Config.LOGV) { Log.v(TAG, "isEnabledLock - packageName = " + componentInfo.packageName @@ -9419,30 +9597,37 @@ class PackageManagerService extends IPackageManager.Stub { Debug.waitForDebugger(); Log.i(TAG, "We will crash!"); } + return false; + } + if (packageSettings.enabled == COMPONENT_ENABLED_STATE_DISABLED + || (packageSettings.pkg != null && !packageSettings.pkg.applicationInfo.enabled + && packageSettings.enabled == COMPONENT_ENABLED_STATE_DEFAULT)) { + return false; + } + if (packageSettings.enabledComponents.contains(componentInfo.name)) { + return true; } - return ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) - || ((componentInfo.enabled - && ((packageSettings.enabled == COMPONENT_ENABLED_STATE_ENABLED) - || (componentInfo.applicationInfo.enabled - && packageSettings.enabled != COMPONENT_ENABLED_STATE_DISABLED)) - && !packageSettings.disabledComponents.contains(componentInfo.name)) - || packageSettings.enabledComponents.contains(componentInfo.name)); + if (packageSettings.disabledComponents.contains(componentInfo.name)) { + return false; + } + return componentInfo.enabled; } } // ------- apps on sdcard specific code ------- static final boolean DEBUG_SD_INSTALL = false; - final private String mSdEncryptKey = "AppsOnSD"; - final private String mSdEncryptAlg = "AES"; + private static final String SD_ENCRYPTION_KEYSTORE_NAME = "AppsOnSD"; + private static final String SD_ENCRYPTION_ALGORITHM = "AES"; + static final int MAX_CONTAINERS = 250; private boolean mMediaMounted = false; - private static final int MAX_CONTAINERS = 250; private String getEncryptKey() { try { - String sdEncKey = SystemKeyStore.getInstance().retrieveKeyHexString(mSdEncryptKey); + String sdEncKey = SystemKeyStore.getInstance().retrieveKeyHexString( + SD_ENCRYPTION_KEYSTORE_NAME); if (sdEncKey == null) { - sdEncKey = SystemKeyStore.getInstance(). - generateNewKeyHexString(128, mSdEncryptAlg, mSdEncryptKey); + sdEncKey = SystemKeyStore.getInstance().generateNewKeyHexString(128, + SD_ENCRYPTION_ALGORITHM, SD_ENCRYPTION_KEYSTORE_NAME); if (sdEncKey == null) { Slog.e(TAG, "Failed to create encryption keys"); return null; @@ -9452,51 +9637,36 @@ class PackageManagerService extends IPackageManager.Stub { } catch (NoSuchAlgorithmException nsae) { Slog.e(TAG, "Failed to create encryption keys with exception: " + nsae); return null; + } catch (IOException ioe) { + Slog.e(TAG, "Failed to retrieve encryption keys with exception: " + + ioe); + return null; } + } - static String getTempContainerId() { - String prefix = "smdl2tmp"; - int tmpIdx = 1; - String list[] = PackageHelper.getSecureContainerList(); - if (list != null) { - int idx = 0; - int idList[] = new int[MAX_CONTAINERS]; - boolean neverFound = true; - for (String name : list) { - // Ignore null entries - if (name == null) { - continue; - } - int sidx = name.indexOf(prefix); - if (sidx == -1) { - // Not a temp file. just ignore - continue; - } - String subStr = name.substring(sidx + prefix.length()); - idList[idx] = -1; - if (subStr != null) { - try { - int cid = Integer.parseInt(subStr); - idList[idx++] = cid; - neverFound = false; - } catch (NumberFormatException e) { - } - } - } - if (!neverFound) { - // Sort idList - Arrays.sort(idList); - for (int j = 1; j <= idList.length; j++) { - if (idList[j-1] != j) { - tmpIdx = j; - break; - } - } - } - } - return prefix + tmpIdx; - } + /* package */ static String getTempContainerId() { + int tmpIdx = 1; + String list[] = PackageHelper.getSecureContainerList(); + if (list != null) { + for (final String name : list) { + // Ignore null and non-temporary container entries + if (name == null || !name.startsWith(mTempContainerPrefix)) { + continue; + } + + String subStr = name.substring(mTempContainerPrefix.length()); + try { + int cid = Integer.parseInt(subStr); + if (cid >= tmpIdx) { + tmpIdx = cid + 1; + } + } catch (NumberFormatException e) { + } + } + } + return mTempContainerPrefix + tmpIdx; + } /* * Update media status on PackageManager. @@ -9661,7 +9831,7 @@ class PackageManagerService extends IPackageManager.Stub { doGc = true; synchronized (mInstallLock) { final PackageParser.Package pkg = scanPackageLI(new File(codePath), - parseFlags, 0); + parseFlags, 0, 0); // Scan the package if (pkg != null) { synchronized (mPackages) { @@ -9711,10 +9881,15 @@ class PackageManagerService extends IPackageManager.Stub { if (doGc) { Runtime.getRuntime().gc(); } - // List stale containers. + // List stale containers and destroy stale temporary containers. if (removeCids != null) { for (String cid : removeCids) { - Log.w(TAG, "Container " + cid + " is stale"); + if (cid.startsWith(mTempContainerPrefix)) { + Log.i(TAG, "Destroying stale temporary container " + cid); + PackageHelper.destroySdDir(cid); + } else { + Log.w(TAG, "Container " + cid + " is stale"); + } } } } @@ -9754,7 +9929,7 @@ class PackageManagerService extends IPackageManager.Stub { PackageRemovedInfo outInfo = new PackageRemovedInfo(); synchronized (mInstallLock) { boolean res = deletePackageLI(pkgName, false, - PackageManager.DONT_DELETE_DATA, outInfo); + PackageManager.DONT_DELETE_DATA, outInfo, false); if (res) { pkgList.add(pkgName); } else { @@ -9763,6 +9938,13 @@ class PackageManagerService extends IPackageManager.Stub { } } } + + synchronized (mPackages) { + // We didn't update the settings after removing each package; + // write them now for all packages. + mSettings.writeLP(); + } + // We have to absolutely send UPDATED_MEDIA_STATUS only // after confirming that all the receivers processed the ordered // broadcast when packages get disabled, force a gc to clean things up. @@ -9796,14 +9978,15 @@ class PackageManagerService extends IPackageManager.Stub { returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST; } else { // Disable moving fwd locked apps and system packages - if (pkg.applicationInfo != null && - (pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + if (pkg.applicationInfo != null && isSystemApp(pkg)) { Slog.w(TAG, "Cannot move system application"); returnCode = PackageManager.MOVE_FAILED_SYSTEM_PACKAGE; - } else if (pkg.applicationInfo != null && - (pkg.applicationInfo.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0) { + } else if (pkg.applicationInfo != null && isForwardLocked(pkg)) { Slog.w(TAG, "Cannot move forward locked app."); returnCode = PackageManager.MOVE_FAILED_FORWARD_LOCKED; + } else if (pkg.mOperationPending) { + Slog.w(TAG, "Attempt to move package which has pending operations"); + returnCode = PackageManager.MOVE_FAILED_OPERATION_PENDING; } else { // Find install location first if ((flags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0 && @@ -9813,23 +9996,26 @@ class PackageManagerService extends IPackageManager.Stub { } else { newFlags = (flags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0 ? PackageManager.INSTALL_EXTERNAL : PackageManager.INSTALL_INTERNAL; - currFlags = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0 ? - PackageManager.INSTALL_EXTERNAL : PackageManager.INSTALL_INTERNAL; + currFlags = isExternal(pkg) ? PackageManager.INSTALL_EXTERNAL + : PackageManager.INSTALL_INTERNAL; if (newFlags == currFlags) { Slog.w(TAG, "No move required. Trying to move to same location"); returnCode = PackageManager.MOVE_FAILED_INVALID_LOCATION; } } + if (returnCode == PackageManager.MOVE_SUCCEEDED) { + pkg.mOperationPending = true; + } } } if (returnCode != PackageManager.MOVE_SUCCEEDED) { - processPendingMove(new MoveParams(null, observer, 0, packageName), returnCode); + processPendingMove(new MoveParams(null, observer, 0, packageName, null), returnCode); } else { Message msg = mHandler.obtainMessage(INIT_COPY); InstallArgs srcArgs = createInstallArgs(currFlags, pkg.applicationInfo.sourceDir, - pkg.applicationInfo.publicSourceDir); - MoveParams mp = new MoveParams(srcArgs, observer, newFlags, - packageName); + pkg.applicationInfo.publicSourceDir, pkg.applicationInfo.nativeLibraryDir); + MoveParams mp = new MoveParams(srcArgs, observer, newFlags, packageName, + pkg.applicationInfo.dataDir); msg.obj = mp; mHandler.sendMessage(msg); } @@ -9847,7 +10033,7 @@ class PackageManagerService extends IPackageManager.Stub { ArrayList<String> pkgList = null; synchronized (mPackages) { PackageParser.Package pkg = mPackages.get(mp.packageName); - if (pkg == null ) { + if (pkg == null) { Slog.w(TAG, " Package " + mp.packageName + " doesn't exist. Aborting move"); returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST; @@ -9870,36 +10056,58 @@ class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { PackageParser.Package pkg = mPackages.get(mp.packageName); // Recheck for package again. - if (pkg == null ) { - Slog.w(TAG, " Package " + mp.packageName + - " doesn't exist. Aborting move"); - returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST; + if (pkg == null) { + Slog.w(TAG, " Package " + mp.packageName + + " doesn't exist. Aborting move"); + returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST; } else if (!mp.srcArgs.getCodePath().equals(pkg.applicationInfo.sourceDir)) { Slog.w(TAG, "Package " + mp.packageName + " code path changed from " + mp.srcArgs.getCodePath() + " to " + pkg.applicationInfo.sourceDir + " Aborting move and returning error"); returnCode = PackageManager.MOVE_FAILED_INTERNAL_ERROR; } else { - String oldCodePath = pkg.mPath; - String newCodePath = mp.targetArgs.getCodePath(); - String newResPath = mp.targetArgs.getResourcePath(); - pkg.mPath = newCodePath; - // Move dex files around - if (moveDexFilesLI(pkg) - != PackageManager.INSTALL_SUCCEEDED) { - // Moving of dex files failed. Set - // error code and abort move. - pkg.mPath = pkg.mScanPath; - returnCode = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE; - } else { + final String oldCodePath = pkg.mPath; + final String newCodePath = mp.targetArgs.getCodePath(); + final String newResPath = mp.targetArgs.getResourcePath(); + final String newNativePath = mp.targetArgs.getNativeLibraryPath(); + + if ((mp.flags & PackageManager.INSTALL_EXTERNAL) == 0) { + if (mInstaller + .unlinkNativeLibraryDirectory(pkg.applicationInfo.dataDir) < 0) { + returnCode = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE; + } else { + NativeLibraryHelper.copyNativeBinariesLI( + new File(newCodePath), new File(newNativePath)); + } + } else { + if (mInstaller.linkNativeLibraryDirectory( + pkg.applicationInfo.dataDir, newNativePath) < 0) { + returnCode = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE; + } + } + + if (returnCode == PackageManager.MOVE_SUCCEEDED) { + pkg.mPath = newCodePath; + // Move dex files around + if (moveDexFilesLI(pkg) != PackageManager.INSTALL_SUCCEEDED) { + // Moving of dex files failed. Set + // error code and abort move. + pkg.mPath = pkg.mScanPath; + returnCode = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE; + } + } + + if (returnCode == PackageManager.MOVE_SUCCEEDED) { pkg.mScanPath = newCodePath; pkg.applicationInfo.sourceDir = newCodePath; pkg.applicationInfo.publicSourceDir = newResPath; + pkg.applicationInfo.nativeLibraryDir = newNativePath; PackageSetting ps = (PackageSetting) pkg.mExtras; ps.codePath = new File(pkg.applicationInfo.sourceDir); ps.codePathString = ps.codePath.getPath(); ps.resourcePath = new File(pkg.applicationInfo.publicSourceDir); ps.resourcePathString = ps.resourcePath.getPath(); + ps.nativeLibraryPathString = newNativePath; // Set the application info flag correctly. if ((mp.flags & PackageManager.INSTALL_EXTERNAL) != 0) { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_EXTERNAL_STORAGE; @@ -9932,6 +10140,18 @@ class PackageManagerService extends IPackageManager.Stub { mp.srcArgs.doPostDeleteLI(true); } } + + // Allow more operations on this file if we didn't fail because + // an operation was already pending for this package. + if (returnCode != PackageManager.MOVE_FAILED_OPERATION_PENDING) { + synchronized (mPackages) { + PackageParser.Package pkg = mPackages.get(mp.packageName); + if (pkg != null) { + pkg.mOperationPending = false; + } + } + } + IPackageMoveObserver observer = mp.observer; if (observer != null) { try { diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java index 493a348..a6daaef 100644 --- a/services/java/com/android/server/PowerManagerService.java +++ b/services/java/com/android/server/PowerManagerService.java @@ -25,6 +25,7 @@ import android.app.IActivityManager; import android.content.BroadcastReceiver; import android.content.ContentQueryMap; import android.content.ContentResolver; +import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -36,6 +37,7 @@ import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; +import android.os.BatteryManager; import android.os.BatteryStats; import android.os.Binder; import android.os.Environment; @@ -50,6 +52,7 @@ 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; import android.provider.Settings; import android.util.EventLog; @@ -62,6 +65,8 @@ import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE; import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; import static android.provider.Settings.System.STAY_ON_WHILE_PLUGGED_IN; +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; @@ -108,6 +113,9 @@ class PowerManagerService extends IPowerManager.Stub // Cached secure settings; see updateSettingsValues() private int mShortKeylightDelay = SHORT_KEYLIGHT_DELAY_DEFAULT; + // Default timeout for screen off, if not found in settings database = 15 seconds. + private static final int DEFAULT_SCREEN_OFF_TIMEOUT = 15000; + // flags for setPowerState private static final int SCREEN_ON_BIT = 0x00000001; private static final int SCREEN_BRIGHT_BIT = 0x00000002; @@ -135,9 +143,7 @@ class PowerManagerService extends IPowerManager.Stub // used for noChangeLights in setPowerState() private static final int LIGHTS_MASK = SCREEN_BRIGHT_BIT | BUTTON_BRIGHT_BIT | KEYBOARD_BRIGHT_BIT; - static final boolean ANIMATE_SCREEN_LIGHTS = true; - static final boolean ANIMATE_BUTTON_LIGHTS = false; - static final boolean ANIMATE_KEYBOARD_LIGHTS = false; + boolean mAnimateScreenLights = true; static final int ANIM_STEPS = 60/4; // Slower animation for autobrightness changes @@ -151,6 +157,7 @@ class PowerManagerService extends IPowerManager.Stub static final int INITIAL_KEYBOARD_BRIGHTNESS = Power.BRIGHTNESS_OFF; private final int MY_UID; + private final int MY_PID; private boolean mDoneBooting = false; private boolean mBootCompleted = false; @@ -194,15 +201,12 @@ class PowerManagerService extends IPowerManager.Stub private UnsynchronizedWakeLock mPreventScreenOnPartialLock; private UnsynchronizedWakeLock mProximityPartialLock; private HandlerThread mHandlerThread; + private HandlerThread mScreenOffThread; + private Handler mScreenOffHandler; private Handler mHandler; private final TimeoutTask mTimeoutTask = new TimeoutTask(); - private final LightAnimator mLightAnimator = new LightAnimator(); private final BrightnessState mScreenBrightness = new BrightnessState(SCREEN_BRIGHT_BIT); - private final BrightnessState mKeyboardBrightness - = new BrightnessState(KEYBOARD_BRIGHT_BIT); - private final BrightnessState mButtonBrightness - = new BrightnessState(BUTTON_BRIGHT_BIT); private boolean mStillNeedSleepNotification; private boolean mIsPowered = false; private IActivityManager mActivityService; @@ -213,6 +217,7 @@ class PowerManagerService extends IPowerManager.Stub private Sensor mLightSensor; private boolean mLightSensorEnabled; private float mLightSensorValue = -1; + private boolean mProxIgnoredBecauseScreenTurnedOff = false; private int mHighestLightSensorValue = -1; private float mLightSensorPendingValue = -1; private int mLightSensorScreenBrightness = -1; @@ -237,6 +242,14 @@ class PowerManagerService extends IPowerManager.Stub private int[] mButtonBacklightValues; private int[] mKeyboardBacklightValues; private int mLightSensorWarmupTime; + boolean mUnplugTurnsOnScreen; + private int mWarningSpewThrottleCount; + private long mWarningSpewThrottleTime; + private int mAnimationSetting = ANIM_SETTING_OFF; + + // Must match with the ISurfaceComposer constants in C++. + private static final int ANIM_SETTING_ON = 0x01; + private static final int ANIM_SETTING_OFF = 0x10; // Used when logging number and duration of touch-down cycles private long mTotalTouchDownTime; @@ -245,8 +258,12 @@ class PowerManagerService extends IPowerManager.Stub // could be either static or controllable at runtime private static final boolean mSpew = false; - private static final boolean mDebugProximitySensor = (true || mSpew); + private static final boolean mDebugProximitySensor = (false || mSpew); private static final boolean mDebugLightSensor = (false || mSpew); + + private native void nativeInit(); + private native void nativeSetPowerState(boolean screenOn, boolean screenBright); + private native void nativeStartSurfaceFlingerAnimation(int mode); /* static PrintStream mLog; @@ -306,7 +323,7 @@ class PowerManagerService extends IPowerManager.Stub long ident = Binder.clearCallingIdentity(); try { PowerManagerService.this.acquireWakeLockLocked(mFlags, mToken, - MY_UID, mTag); + MY_UID, MY_PID, mTag, null); mHeld = true; } finally { Binder.restoreCallingIdentity(ident); @@ -353,8 +370,12 @@ class PowerManagerService extends IPowerManager.Stub // user activity when screen was already on. // temporarily set mUserActivityAllowed to true so this will work // even when the keyguard is on. + // However, you can also set config_unplugTurnsOnScreen to have it + // turn on. Some devices want this because they don't have a + // charging LED. synchronized (mLocks) { - if (!wasPowered || (mPowerState & SCREEN_ON_BIT) != 0) { + if (!wasPowered || (mPowerState & SCREEN_ON_BIT) != 0 || + mUnplugTurnsOnScreen) { forceUserActivityLocked(); } } @@ -406,36 +427,57 @@ class PowerManagerService extends IPowerManager.Stub } private class SettingsObserver implements Observer { - private int getInt(String name) { - return mSettings.getValues(name).getAsInteger(Settings.System.VALUE); + private int getInt(String name, int defValue) { + ContentValues values = mSettings.getValues(name); + Integer iVal = values != null ? values.getAsInteger(Settings.System.VALUE) : null; + return iVal != null ? iVal : defValue; + } + + private float getFloat(String name, float defValue) { + ContentValues values = mSettings.getValues(name); + Float fVal = values != null ? values.getAsFloat(Settings.System.VALUE) : null; + return fVal != null ? fVal : defValue; } public void update(Observable o, Object arg) { synchronized (mLocks) { - // STAY_ON_WHILE_PLUGGED_IN - mStayOnConditions = getInt(STAY_ON_WHILE_PLUGGED_IN); + // STAY_ON_WHILE_PLUGGED_IN, default to when plugged into AC + mStayOnConditions = getInt(STAY_ON_WHILE_PLUGGED_IN, + BatteryManager.BATTERY_PLUGGED_AC); updateWakeLockLocked(); - // SCREEN_OFF_TIMEOUT - mScreenOffTimeoutSetting = getInt(SCREEN_OFF_TIMEOUT); + // SCREEN_OFF_TIMEOUT, default to 15 seconds + mScreenOffTimeoutSetting = getInt(SCREEN_OFF_TIMEOUT, DEFAULT_SCREEN_OFF_TIMEOUT); - // DIM_SCREEN + // DIM_SCREEN //mDimScreen = getInt(DIM_SCREEN) != 0; - // SCREEN_BRIGHTNESS_MODE - setScreenBrightnessMode(getInt(SCREEN_BRIGHTNESS_MODE)); + // SCREEN_BRIGHTNESS_MODE, default to manual + setScreenBrightnessMode(getInt(SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL)); // recalculate everything setScreenOffTimeoutsLocked(); + + final float windowScale = getFloat(WINDOW_ANIMATION_SCALE, 1.0f); + final float transitionScale = getFloat(TRANSITION_ANIMATION_SCALE, 1.0f); + mAnimationSetting = 0; + if (windowScale > 0.5f) { + mAnimationSetting |= ANIM_SETTING_OFF; + } + if (transitionScale > 0.5f) { + // Uncomment this if you want the screen-on animation. + // mAnimationSetting |= ANIM_SETTING_ON; + } } } } - PowerManagerService() - { + PowerManagerService() { // Hack to get our uid... should have a func for this. long token = Binder.clearCallingIdentity(); - MY_UID = Binder.getCallingUid(); + MY_UID = Process.myUid(); + MY_PID = Process.myPid(); Binder.restoreCallingIdentity(token); // XXX remove this when the kernel doesn't timeout wake locks @@ -463,6 +505,35 @@ class PowerManagerService extends IPowerManager.Stub mKeyboardLight = lights.getLight(LightsService.LIGHT_ID_KEYBOARD); mAttentionLight = lights.getLight(LightsService.LIGHT_ID_ATTENTION); + nativeInit(); + synchronized (mLocks) { + updateNativePowerStateLocked(); + } + + mInitComplete = false; + mScreenOffThread = new HandlerThread("PowerManagerService.mScreenOffThread") { + @Override + protected void onLooperPrepared() { + mScreenOffHandler = new Handler(); + synchronized (mScreenOffThread) { + mInitComplete = true; + mScreenOffThread.notifyAll(); + } + } + }; + mScreenOffThread.start(); + + synchronized (mScreenOffThread) { + while (!mInitComplete) { + try { + mScreenOffThread.wait(); + } catch (InterruptedException e) { + // Ignore + } + } + } + + mInitComplete = false; mHandlerThread = new HandlerThread("PowerManagerService") { @Override protected void onLooperPrepared() { @@ -481,6 +552,11 @@ class PowerManagerService extends IPowerManager.Stub } } } + + nativeInit(); + synchronized (mLocks) { + updateNativePowerStateLocked(); + } } void initInThread() { @@ -504,6 +580,12 @@ class PowerManagerService extends IPowerManager.Stub Resources resources = mContext.getResources(); + mAnimateScreenLights = resources.getBoolean( + com.android.internal.R.bool.config_animateScreenLights); + + mUnplugTurnsOnScreen = resources.getBoolean( + com.android.internal.R.bool.config_unplugTurnsOnScreen); + // read settings for auto-brightness mUseSoftwareAutoBrightness = resources.getBoolean( com.android.internal.R.bool.config_automatic_brightness_available); @@ -525,9 +607,11 @@ class PowerManagerService extends IPowerManager.Stub "(" + Settings.System.NAME + "=?) or (" + Settings.System.NAME + "=?) or (" + Settings.System.NAME + "=?) or (" + + Settings.System.NAME + "=?) or (" + + Settings.System.NAME + "=?) or (" + Settings.System.NAME + "=?)", new String[]{STAY_ON_WHILE_PLUGGED_IN, SCREEN_OFF_TIMEOUT, DIM_SCREEN, - SCREEN_BRIGHTNESS_MODE}, + SCREEN_BRIGHTNESS_MODE, WINDOW_ANIMATION_SCALE, TRANSITION_ANIMATION_SCALE}, null); mSettings = new ContentQueryMap(settingsCursor, Settings.System.NAME, true, mHandler); SettingsObserver settingsObserver = new SettingsObserver(); @@ -565,13 +649,13 @@ class PowerManagerService extends IPowerManager.Stub private class WakeLock implements IBinder.DeathRecipient { - WakeLock(int f, IBinder b, String t, int u) { + WakeLock(int f, IBinder b, String t, int u, int p) { super(); flags = f; binder = b; tag = t; uid = u == MY_UID ? Process.SYSTEM_UID : u; - pid = Binder.getCallingPid(); + pid = p; if (u != MY_UID || ( !"KEEP_SCREEN_ON_FLAG".equals(tag) && !"KeyInputQueue".equals(tag))) { @@ -598,6 +682,7 @@ class PowerManagerService extends IPowerManager.Stub final int uid; final int pid; final int monitorType; + WorkSource ws; boolean activated = true; int minState; } @@ -618,38 +703,90 @@ class PowerManagerService extends IPowerManager.Stub int n = flags & LOCK_MASK; return n == PowerManager.FULL_WAKE_LOCK || n == PowerManager.SCREEN_BRIGHT_WAKE_LOCK - || n == PowerManager.SCREEN_DIM_WAKE_LOCK; + || n == PowerManager.SCREEN_DIM_WAKE_LOCK + || n == PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK; } - public void acquireWakeLock(int flags, IBinder lock, String tag) { + void enforceWakeSourcePermission(int uid, int pid) { + if (uid == Process.myUid()) { + return; + } + mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS, + pid, uid, null); + } + + public void acquireWakeLock(int flags, IBinder lock, String tag, WorkSource ws) { int uid = Binder.getCallingUid(); + int pid = Binder.getCallingPid(); if (uid != Process.myUid()) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null); } + if (ws != null) { + enforceWakeSourcePermission(uid, pid); + } long ident = Binder.clearCallingIdentity(); try { synchronized (mLocks) { - acquireWakeLockLocked(flags, lock, uid, tag); + acquireWakeLockLocked(flags, lock, uid, pid, tag, ws); } } finally { Binder.restoreCallingIdentity(ident); } } - public void acquireWakeLockLocked(int flags, IBinder lock, int uid, String tag) { - int acquireUid = -1; - String acquireName = null; - int acquireType = -1; + void noteStartWakeLocked(WakeLock wl, WorkSource ws) { + if (wl.monitorType >= 0) { + long origId = Binder.clearCallingIdentity(); + try { + if (ws != null) { + mBatteryStats.noteStartWakelockFromSource(ws, wl.pid, wl.tag, + wl.monitorType); + } else { + mBatteryStats.noteStartWakelock(wl.uid, wl.pid, wl.tag, wl.monitorType); + } + } catch (RemoteException e) { + // Ignore + } finally { + Binder.restoreCallingIdentity(origId); + } + } + } + + void noteStopWakeLocked(WakeLock wl, WorkSource ws) { + if (wl.monitorType >= 0) { + long origId = Binder.clearCallingIdentity(); + try { + if (ws != null) { + mBatteryStats.noteStopWakelockFromSource(ws, wl.pid, wl.tag, + wl.monitorType); + } else { + mBatteryStats.noteStopWakelock(wl.uid, wl.pid, wl.tag, wl.monitorType); + } + } catch (RemoteException e) { + // Ignore + } finally { + Binder.restoreCallingIdentity(origId); + } + } + } + public void acquireWakeLockLocked(int flags, IBinder lock, int uid, int pid, String tag, + WorkSource ws) { if (mSpew) { Slog.d(TAG, "acquireWakeLock flags=0x" + Integer.toHexString(flags) + " tag=" + tag); } + if (ws != null && ws.size() == 0) { + ws = null; + } + int index = mLocks.getIndex(lock); WakeLock wl; boolean newlock; + boolean diffsource; + WorkSource oldsource; if (index < 0) { - wl = new WakeLock(flags, lock, tag, uid); + wl = new WakeLock(flags, lock, tag, uid, pid); switch (wl.flags & LOCK_MASK) { case PowerManager.FULL_WAKE_LOCK: @@ -676,35 +813,64 @@ class PowerManagerService extends IPowerManager.Stub return; } mLocks.addLock(wl); + if (ws != null) { + wl.ws = new WorkSource(ws); + } newlock = true; + diffsource = false; + oldsource = null; } else { wl = mLocks.get(index); newlock = false; + oldsource = wl.ws; + if (oldsource != null) { + if (ws == null) { + wl.ws = null; + diffsource = true; + } else { + diffsource = oldsource.diff(ws); + } + } else if (ws != null) { + diffsource = true; + } else { + diffsource = false; + } + if (diffsource) { + wl.ws = new WorkSource(ws); + } } if (isScreenLock(flags)) { // if this causes a wakeup, we reactivate all of the locks and // set it to whatever they want. otherwise, we modulate that // by the current state so we never turn it more on than // it already is. - if ((wl.flags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0) { - int oldWakeLockState = mWakeLockState; - mWakeLockState = mLocks.reactivateScreenLocksLocked(); - if (mSpew) { - Slog.d(TAG, "wakeup here mUserState=0x" + Integer.toHexString(mUserState) - + " mWakeLockState=0x" - + Integer.toHexString(mWakeLockState) - + " previous wakeLockState=0x" + Integer.toHexString(oldWakeLockState)); + if ((flags & LOCK_MASK) == PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK) { + mProximityWakeLockCount++; + if (mProximityWakeLockCount == 1) { + enableProximityLockLocked(); } } else { - if (mSpew) { - Slog.d(TAG, "here mUserState=0x" + Integer.toHexString(mUserState) - + " mLocks.gatherState()=0x" - + Integer.toHexString(mLocks.gatherState()) - + " mWakeLockState=0x" + Integer.toHexString(mWakeLockState)); + if ((wl.flags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0) { + int oldWakeLockState = mWakeLockState; + mWakeLockState = mLocks.reactivateScreenLocksLocked(); + if (mSpew) { + Slog.d(TAG, "wakeup here mUserState=0x" + Integer.toHexString(mUserState) + + " mWakeLockState=0x" + + Integer.toHexString(mWakeLockState) + + " previous wakeLockState=0x" + + Integer.toHexString(oldWakeLockState)); + } + } else { + if (mSpew) { + Slog.d(TAG, "here mUserState=0x" + Integer.toHexString(mUserState) + + " mLocks.gatherState()=0x" + + Integer.toHexString(mLocks.gatherState()) + + " mWakeLockState=0x" + Integer.toHexString(mWakeLockState)); + } + mWakeLockState = (mUserState | mWakeLockState) & mLocks.gatherState(); } - mWakeLockState = (mUserState | mWakeLockState) & mLocks.gatherState(); + setPowerState(mWakeLockState | mUserState); } - setPowerState(mWakeLockState | mUserState); } else if ((flags & LOCK_MASK) == PowerManager.PARTIAL_WAKE_LOCK) { if (newlock) { @@ -714,24 +880,37 @@ class PowerManagerService extends IPowerManager.Stub } } Power.acquireWakeLock(Power.PARTIAL_WAKE_LOCK,PARTIAL_NAME); - } else if ((flags & LOCK_MASK) == PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK) { - mProximityWakeLockCount++; - if (mProximityWakeLockCount == 1) { - enableProximityLockLocked(); - } } - if (newlock) { - acquireUid = wl.uid; - acquireName = wl.tag; - acquireType = wl.monitorType; + + if (diffsource) { + // If the lock sources have changed, need to first release the + // old ones. + noteStopWakeLocked(wl, oldsource); + } + if (newlock || diffsource) { + noteStartWakeLocked(wl, ws); } + } - if (acquireType >= 0) { - try { - mBatteryStats.noteStartWakelock(acquireUid, acquireName, acquireType); - } catch (RemoteException e) { - // Ignore + public void updateWakeLockWorkSource(IBinder lock, WorkSource ws) { + int uid = Binder.getCallingUid(); + int pid = Binder.getCallingPid(); + if (ws != null && ws.size() == 0) { + ws = null; + } + if (ws != null) { + enforceWakeSourcePermission(uid, pid); + } + synchronized (mLocks) { + int index = mLocks.getIndex(lock); + if (index < 0) { + throw new IllegalArgumentException("Wake lock not active"); } + WakeLock wl = mLocks.get(index); + WorkSource oldsource = wl.ws; + wl.ws = ws != null ? new WorkSource(ws) : null; + noteStopWakeLocked(wl, oldsource); + noteStartWakeLocked(wl, ws); } } @@ -747,10 +926,6 @@ class PowerManagerService extends IPowerManager.Stub } private void releaseWakeLockLocked(IBinder lock, int flags, boolean death) { - int releaseUid; - String releaseName; - int releaseType; - WakeLock wl = mLocks.removeLock(lock); if (wl == null) { return; @@ -762,12 +937,27 @@ class PowerManagerService extends IPowerManager.Stub } if (isScreenLock(wl.flags)) { - mWakeLockState = mLocks.gatherState(); - // goes in the middle to reduce flicker - if ((wl.flags & PowerManager.ON_AFTER_RELEASE) != 0) { - userActivity(SystemClock.uptimeMillis(), false); + if ((wl.flags & LOCK_MASK) == PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK) { + mProximityWakeLockCount--; + if (mProximityWakeLockCount == 0) { + if (mProximitySensorActive && + ((flags & PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE) != 0)) { + // wait for proximity sensor to go negative before disabling sensor + if (mDebugProximitySensor) { + Slog.d(TAG, "waiting for proximity sensor to go negative"); + } + } else { + disableProximityLockLocked(); + } + } + } else { + mWakeLockState = mLocks.gatherState(); + // goes in the middle to reduce flicker + if ((wl.flags & PowerManager.ON_AFTER_RELEASE) != 0) { + userActivity(SystemClock.uptimeMillis(), -1, false, OTHER_EVENT, false); + } + setPowerState(mWakeLockState | mUserState); } - setPowerState(mWakeLockState | mUserState); } else if ((wl.flags & LOCK_MASK) == PowerManager.PARTIAL_WAKE_LOCK) { mPartialCount--; @@ -775,36 +965,11 @@ class PowerManagerService extends IPowerManager.Stub if (LOG_PARTIAL_WL) EventLog.writeEvent(EventLogTags.POWER_PARTIAL_WAKE_STATE, 0, wl.tag); Power.releaseWakeLock(PARTIAL_NAME); } - } else if ((wl.flags & LOCK_MASK) == PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK) { - mProximityWakeLockCount--; - if (mProximityWakeLockCount == 0) { - if (mProximitySensorActive && - ((flags & PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE) != 0)) { - // wait for proximity sensor to go negative before disabling sensor - if (mDebugProximitySensor) { - Slog.d(TAG, "waiting for proximity sensor to go negative"); - } - } else { - disableProximityLockLocked(); - } - } } // Unlink the lock from the binder. wl.binder.unlinkToDeath(wl, 0); - releaseUid = wl.uid; - releaseName = wl.tag; - releaseType = wl.monitorType; - if (releaseType >= 0) { - long origId = Binder.clearCallingIdentity(); - try { - mBatteryStats.noteStopWakelock(releaseUid, releaseName, releaseType); - } catch (RemoteException e) { - // Ignore - } finally { - Binder.restoreCallingIdentity(origId); - } - } + noteStopWakeLocked(wl, wl.ws); } private class PokeLock implements IBinder.DeathRecipient @@ -982,8 +1147,6 @@ class PowerManagerService extends IPowerManager.Stub pw.println(" mUseSoftwareAutoBrightness=" + mUseSoftwareAutoBrightness); pw.println(" mAutoBrightessEnabled=" + mAutoBrightessEnabled); mScreenBrightness.dump(pw, " mScreenBrightness: "); - mKeyboardBrightness.dump(pw, " mKeyboardBrightness: "); - mButtonBrightness.dump(pw, " mButtonBrightness: "); int N = mLocks.size(); pw.println(); @@ -1022,36 +1185,76 @@ class PowerManagerService extends IPowerManager.Stub } } - private void setTimeoutLocked(long now, int nextState) - { + private void setTimeoutLocked(long now, int nextState) { + setTimeoutLocked(now, -1, nextState); + } + + // If they gave a timeoutOverride it is the number of seconds + // to screen-off. Figure out where in the countdown cycle we + // should jump to. + private void setTimeoutLocked(long now, final long originalTimeoutOverride, int nextState) { + long timeoutOverride = originalTimeoutOverride; if (mBootCompleted) { - mHandler.removeCallbacks(mTimeoutTask); - mTimeoutTask.nextState = nextState; - long when = now; - switch (nextState) - { - case SCREEN_BRIGHT: - when += mKeylightDelay; - break; - case SCREEN_DIM: - if (mDimDelay >= 0) { - when += mDimDelay; - break; - } else { - Slog.w(TAG, "mDimDelay=" + mDimDelay + " while trying to dim"); + synchronized (mLocks) { + long when = 0; + if (timeoutOverride <= 0) { + switch (nextState) + { + case SCREEN_BRIGHT: + when = now + mKeylightDelay; + break; + case SCREEN_DIM: + if (mDimDelay >= 0) { + when = now + mDimDelay; + break; + } else { + Slog.w(TAG, "mDimDelay=" + mDimDelay + " while trying to dim"); + } + case SCREEN_OFF: + synchronized (mLocks) { + when = now + mScreenOffDelay; + } + break; + default: + when = now; + break; } - case SCREEN_OFF: - synchronized (mLocks) { - when += mScreenOffDelay; + } else { + override: { + if (timeoutOverride <= mScreenOffDelay) { + when = now + timeoutOverride; + nextState = SCREEN_OFF; + break override; + } + timeoutOverride -= mScreenOffDelay; + + if (mDimDelay >= 0) { + if (timeoutOverride <= mDimDelay) { + when = now + timeoutOverride; + nextState = SCREEN_DIM; + break override; + } + timeoutOverride -= mDimDelay; + } + + when = now + timeoutOverride; + nextState = SCREEN_BRIGHT; } - break; - } - if (mSpew) { - Slog.d(TAG, "setTimeoutLocked now=" + now + " nextState=" + nextState - + " when=" + when); + } + if (mSpew) { + Slog.d(TAG, "setTimeoutLocked now=" + now + + " timeoutOverride=" + timeoutOverride + + " nextState=" + nextState + " when=" + when); + } + + mHandler.removeCallbacks(mTimeoutTask); + mTimeoutTask.nextState = nextState; + mTimeoutTask.remainingTimeoutOverride = timeoutOverride > 0 + ? (originalTimeoutOverride - timeoutOverride) + : -1; + mHandler.postAtTime(mTimeoutTask, when); + mNextTimeout = when; // for debugging } - mHandler.postAtTime(mTimeoutTask, when); - mNextTimeout = when; // for debugging } } @@ -1064,6 +1267,7 @@ class PowerManagerService extends IPowerManager.Stub private class TimeoutTask implements Runnable { int nextState; // access should be synchronized on mLocks + long remainingTimeoutOverride; public void run() { synchronized (mLocks) { @@ -1084,11 +1288,11 @@ class PowerManagerService extends IPowerManager.Stub { case SCREEN_BRIGHT: if (mDimDelay >= 0) { - setTimeoutLocked(now, SCREEN_DIM); + setTimeoutLocked(now, remainingTimeoutOverride, SCREEN_DIM); break; } case SCREEN_DIM: - setTimeoutLocked(now, SCREEN_OFF); + setTimeoutLocked(now, remainingTimeoutOverride, SCREEN_OFF); break; } } @@ -1557,14 +1761,23 @@ class PowerManagerService extends IPowerManager.Stub } } } + + updateNativePowerStateLocked(); } } + + private void updateNativePowerStateLocked() { + nativeSetPowerState( + (mPowerState & SCREEN_ON_BIT) != 0, + (mPowerState & SCREEN_BRIGHT) == SCREEN_BRIGHT); + } private int screenOffFinishedAnimatingLocked(int reason) { // I don't think we need to check the current state here because all of these // Power.setScreenState and sendNotificationLocked can both handle being // called multiple times in the same state. -joeo - EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 0, reason, mTotalTouchDownTime, mTouchCycles); + EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 0, reason, mTotalTouchDownTime, + mTouchCycles); mLastTouchDown = 0; int err = setScreenStateLocked(false); if (err == 0) { @@ -1581,8 +1794,12 @@ class PowerManagerService extends IPowerManager.Stub private void updateLightsLocked(int newState, int forceState) { final int oldState = mPowerState; - newState = applyButtonState(newState); - newState = applyKeyboardState(newState); + if ((newState & SCREEN_ON_BIT) != 0) { + // Only turn on the buttons or keyboard if the screen is also on. + // We should never see the buttons on but not the screen. + newState = applyButtonState(newState); + newState = applyKeyboardState(newState); + } final int realDifference = (newState ^ oldState); final int difference = realDifference | forceState; if (difference == 0) { @@ -1594,145 +1811,105 @@ class PowerManagerService extends IPowerManager.Stub int onMask = 0; int preferredBrightness = getPreferredBrightness(); - boolean startAnimation = false; if ((difference & KEYBOARD_BRIGHT_BIT) != 0) { - if (ANIMATE_KEYBOARD_LIGHTS) { - if ((newState & KEYBOARD_BRIGHT_BIT) == 0) { - mKeyboardBrightness.setTargetLocked(Power.BRIGHTNESS_OFF, - ANIM_STEPS, INITIAL_KEYBOARD_BRIGHTNESS, - Power.BRIGHTNESS_ON); - } else { - mKeyboardBrightness.setTargetLocked(Power.BRIGHTNESS_ON, - ANIM_STEPS, INITIAL_KEYBOARD_BRIGHTNESS, - Power.BRIGHTNESS_OFF); - } - startAnimation = true; + if ((newState & KEYBOARD_BRIGHT_BIT) == 0) { + offMask |= KEYBOARD_BRIGHT_BIT; } else { - if ((newState & KEYBOARD_BRIGHT_BIT) == 0) { - offMask |= KEYBOARD_BRIGHT_BIT; - } else { - onMask |= KEYBOARD_BRIGHT_BIT; - } + onMask |= KEYBOARD_BRIGHT_BIT; } } if ((difference & BUTTON_BRIGHT_BIT) != 0) { - if (ANIMATE_BUTTON_LIGHTS) { - if ((newState & BUTTON_BRIGHT_BIT) == 0) { - mButtonBrightness.setTargetLocked(Power.BRIGHTNESS_OFF, - ANIM_STEPS, INITIAL_BUTTON_BRIGHTNESS, - Power.BRIGHTNESS_ON); - } else { - mButtonBrightness.setTargetLocked(Power.BRIGHTNESS_ON, - ANIM_STEPS, INITIAL_BUTTON_BRIGHTNESS, - Power.BRIGHTNESS_OFF); - } - startAnimation = true; + if ((newState & BUTTON_BRIGHT_BIT) == 0) { + offMask |= BUTTON_BRIGHT_BIT; } else { - if ((newState & BUTTON_BRIGHT_BIT) == 0) { - offMask |= BUTTON_BRIGHT_BIT; - } else { - onMask |= BUTTON_BRIGHT_BIT; - } + onMask |= BUTTON_BRIGHT_BIT; } } if ((difference & (SCREEN_ON_BIT | SCREEN_BRIGHT_BIT)) != 0) { - if (ANIMATE_SCREEN_LIGHTS) { - int nominalCurrentValue = -1; - // If there was an actual difference in the light state, then - // figure out the "ideal" current value based on the previous - // state. Otherwise, this is a change due to the brightness - // override, so we want to animate from whatever the current - // value is. - if ((realDifference & (SCREEN_ON_BIT | SCREEN_BRIGHT_BIT)) != 0) { - switch (oldState & (SCREEN_BRIGHT_BIT|SCREEN_ON_BIT)) { - case SCREEN_BRIGHT_BIT | SCREEN_ON_BIT: - nominalCurrentValue = preferredBrightness; - break; - case SCREEN_ON_BIT: - nominalCurrentValue = Power.BRIGHTNESS_DIM; - break; - case 0: - nominalCurrentValue = Power.BRIGHTNESS_OFF; - break; - case SCREEN_BRIGHT_BIT: - default: - // not possible - nominalCurrentValue = (int)mScreenBrightness.curValue; - break; - } + int nominalCurrentValue = -1; + // If there was an actual difference in the light state, then + // figure out the "ideal" current value based on the previous + // state. Otherwise, this is a change due to the brightness + // override, so we want to animate from whatever the current + // value is. + if ((realDifference & (SCREEN_ON_BIT | SCREEN_BRIGHT_BIT)) != 0) { + switch (oldState & (SCREEN_BRIGHT_BIT|SCREEN_ON_BIT)) { + case SCREEN_BRIGHT_BIT | SCREEN_ON_BIT: + nominalCurrentValue = preferredBrightness; + break; + case SCREEN_ON_BIT: + nominalCurrentValue = Power.BRIGHTNESS_DIM; + break; + case 0: + nominalCurrentValue = Power.BRIGHTNESS_OFF; + break; + case SCREEN_BRIGHT_BIT: + default: + // not possible + nominalCurrentValue = (int)mScreenBrightness.curValue; + break; } - int brightness = preferredBrightness; - int steps = ANIM_STEPS; - if ((newState & SCREEN_BRIGHT_BIT) == 0) { - // dim or turn off backlight, depending on if the screen is on - // the scale is because the brightness ramp isn't linear and this biases - // it so the later parts take longer. - final float scale = 1.5f; - float ratio = (((float)Power.BRIGHTNESS_DIM)/preferredBrightness); - if (ratio > 1.0f) ratio = 1.0f; - if ((newState & SCREEN_ON_BIT) == 0) { - if ((oldState & SCREEN_BRIGHT_BIT) != 0) { - // was bright - steps = ANIM_STEPS; - } else { - // was dim - steps = (int)(ANIM_STEPS*ratio*scale); - } - brightness = Power.BRIGHTNESS_OFF; + } + int brightness = preferredBrightness; + int steps = ANIM_STEPS; + if ((newState & SCREEN_BRIGHT_BIT) == 0) { + // dim or turn off backlight, depending on if the screen is on + // the scale is because the brightness ramp isn't linear and this biases + // it so the later parts take longer. + final float scale = 1.5f; + float ratio = (((float)Power.BRIGHTNESS_DIM)/preferredBrightness); + if (ratio > 1.0f) ratio = 1.0f; + if ((newState & SCREEN_ON_BIT) == 0) { + if ((oldState & SCREEN_BRIGHT_BIT) != 0) { + // was bright + steps = ANIM_STEPS; } else { - if ((oldState & SCREEN_ON_BIT) != 0) { - // was bright - steps = (int)(ANIM_STEPS*(1.0f-ratio)*scale); - } else { - // was dim - steps = (int)(ANIM_STEPS*ratio); - } - if (mStayOnConditions != 0 && mBatteryService.isPowered(mStayOnConditions)) { - // If the "stay on while plugged in" option is - // turned on, then the screen will often not - // automatically turn off while plugged in. To - // still have a sense of when it is inactive, we - // will then count going dim as turning off. - mScreenOffTime = SystemClock.elapsedRealtime(); - } - brightness = Power.BRIGHTNESS_DIM; + // was dim + steps = (int)(ANIM_STEPS*ratio*scale); } - } - long identity = Binder.clearCallingIdentity(); - try { - mBatteryStats.noteScreenBrightness(brightness); - } catch (RemoteException e) { - // Nothing interesting to do. - } finally { - Binder.restoreCallingIdentity(identity); - } - if (mScreenBrightness.setTargetLocked(brightness, - steps, INITIAL_SCREEN_BRIGHTNESS, nominalCurrentValue)) { - startAnimation = true; - } - } else { - if ((newState & SCREEN_BRIGHT_BIT) == 0) { - // dim or turn off backlight, depending on if the screen is on - if ((newState & SCREEN_ON_BIT) == 0) { - offMask |= SCREEN_BRIGHT_BIT; + brightness = Power.BRIGHTNESS_OFF; + } else { + if ((oldState & SCREEN_ON_BIT) != 0) { + // was bright + steps = (int)(ANIM_STEPS*(1.0f-ratio)*scale); } else { - dimMask |= SCREEN_BRIGHT_BIT; + // was dim + steps = (int)(ANIM_STEPS*ratio); } - } else { - onMask |= SCREEN_BRIGHT_BIT; + if (mStayOnConditions != 0 && mBatteryService.isPowered(mStayOnConditions)) { + // If the "stay on while plugged in" option is + // turned on, then the screen will often not + // automatically turn off while plugged in. To + // still have a sense of when it is inactive, we + // will then count going dim as turning off. + mScreenOffTime = SystemClock.elapsedRealtime(); + } + brightness = Power.BRIGHTNESS_DIM; } } + long identity = Binder.clearCallingIdentity(); + try { + mBatteryStats.noteScreenBrightness(brightness); + } catch (RemoteException e) { + // Nothing interesting to do. + } finally { + Binder.restoreCallingIdentity(identity); + } + mScreenBrightness.setTargetLocked(brightness, steps, + INITIAL_SCREEN_BRIGHTNESS, nominalCurrentValue); } - if (startAnimation) { - if (mSpew) { - Slog.i(TAG, "Scheduling light animator!"); - } - mHandler.removeCallbacks(mLightAnimator); - mHandler.post(mLightAnimator); + if (mSpew) { + Slog.d(TAG, "offMask=0x" + Integer.toHexString(offMask) + + " dimMask=0x" + Integer.toHexString(dimMask) + + " onMask=0x" + Integer.toHexString(onMask) + + " difference=0x" + Integer.toHexString(difference) + + " realDifference=0x" + Integer.toHexString(realDifference) + + " forceState=0x" + Integer.toHexString(forceState) + ); } if (offMask != 0) { @@ -1774,7 +1951,7 @@ class PowerManagerService extends IPowerManager.Stub } } - class BrightnessState { + class BrightnessState implements Runnable { final int mask; boolean initialized; @@ -1794,13 +1971,13 @@ class PowerManagerService extends IPowerManager.Stub + " delta=" + delta); } - boolean setTargetLocked(int target, int stepsToTarget, int initialValue, + void setTargetLocked(int target, int stepsToTarget, int initialValue, int nominalCurrentValue) { if (!initialized) { initialized = true; curValue = (float)initialValue; } else if (targetValue == target) { - return false; + return; } targetValue = target; delta = (targetValue - @@ -1808,13 +1985,18 @@ class PowerManagerService extends IPowerManager.Stub / stepsToTarget; if (mSpew) { String noticeMe = nominalCurrentValue == curValue ? "" : " ******************"; - Slog.i(TAG, "Setting target " + mask + ": cur=" + curValue - + " target=" + targetValue + " delta=" + delta + Slog.i(TAG, "setTargetLocked mask=" + mask + " curValue=" + curValue + + " target=" + target + " targetValue=" + targetValue + " delta=" + delta + " nominalCurrentValue=" + nominalCurrentValue + noticeMe); } animating = true; - return true; + + if (mSpew) { + Slog.i(TAG, "scheduling light animator"); + } + mScreenOffHandler.removeCallbacks(this); + mScreenOffHandler.post(this); } boolean stepLocked() { @@ -1840,31 +2022,51 @@ class PowerManagerService extends IPowerManager.Stub more = false; } } - //Slog.i(TAG, "Animating brightess " + curIntValue + ": " + mask); + if (mSpew) Slog.d(TAG, "Animating curIntValue=" + curIntValue + ": " + mask); setLightBrightness(mask, curIntValue); + finishAnimationLocked(more, curIntValue); + return more; + } + + void jumpToTargetLocked() { + if (mSpew) Slog.d(TAG, "jumpToTargetLocked targetValue=" + targetValue + ": " + mask); + setLightBrightness(mask, targetValue); + final int tv = targetValue; + curValue = tv; + targetValue = -1; + finishAnimationLocked(false, tv); + } + + private void finishAnimationLocked(boolean more, int curIntValue) { animating = more; if (!more) { if (mask == SCREEN_BRIGHT_BIT && curIntValue == Power.BRIGHTNESS_OFF) { screenOffFinishedAnimatingLocked(mScreenOffReason); } } - return more; } - } - private class LightAnimator implements Runnable { public void run() { - synchronized (mLocks) { - long now = SystemClock.uptimeMillis(); - boolean more = mScreenBrightness.stepLocked(); - if (mKeyboardBrightness.stepLocked()) { - more = true; - } - if (mButtonBrightness.stepLocked()) { - more = true; + if (mAnimateScreenLights) { + synchronized (mLocks) { + long now = SystemClock.uptimeMillis(); + boolean more = mScreenBrightness.stepLocked(); + if (more) { + mScreenOffHandler.postAtTime(this, now+(1000/60)); + } } - if (more) { - mHandler.postAtTime(mLightAnimator, now+(1000/60)); + } else { + synchronized (mLocks) { + // we're turning off + final boolean animate = animating && targetValue == Power.BRIGHTNESS_OFF; + if (animate) { + // It's pretty scary to hold mLocks for this long, and we should + // redesign this, but it works for now. + nativeStartSurfaceFlingerAnimation( + mScreenOffReason == WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR + ? 0 : mAnimationSetting); + } + mScreenBrightness.jumpToTargetLocked(); } } } @@ -1945,6 +2147,21 @@ class PowerManagerService extends IPowerManager.Stub return (mScreenBrightness.animating && mScreenBrightness.targetValue == 0); } + private boolean shouldLog(long time) { + synchronized (mLocks) { + if (time > (mWarningSpewThrottleTime + (60*60*1000))) { + mWarningSpewThrottleTime = time; + mWarningSpewThrottleCount = 0; + return true; + } else if (mWarningSpewThrottleCount < 30) { + mWarningSpewThrottleCount++; + return true; + } else { + return false; + } + } + } + private void forceUserActivityLocked() { if (isScreenTurningOffLocked()) { // cancel animation so userActivity will succeed @@ -1958,24 +2175,47 @@ class PowerManagerService extends IPowerManager.Stub public void userActivityWithForce(long time, boolean noChangeLights, boolean force) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); - userActivity(time, noChangeLights, OTHER_EVENT, force); + userActivity(time, -1, noChangeLights, OTHER_EVENT, force); } public void userActivity(long time, boolean noChangeLights) { - userActivity(time, noChangeLights, OTHER_EVENT, false); + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER) + != PackageManager.PERMISSION_GRANTED) { + if (shouldLog(time)) { + Slog.w(TAG, "Caller does not have DEVICE_POWER permission. pid=" + + Binder.getCallingPid() + " uid=" + Binder.getCallingUid()); + } + return; + } + + userActivity(time, -1, noChangeLights, OTHER_EVENT, false); } public void userActivity(long time, boolean noChangeLights, int eventType) { - userActivity(time, noChangeLights, eventType, false); + userActivity(time, -1, noChangeLights, eventType, false); } public void userActivity(long time, boolean noChangeLights, int eventType, boolean force) { - //mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + userActivity(time, -1, noChangeLights, eventType, force); + } + + /* + * Reset the user activity timeout to now + timeout. This overrides whatever else is going + * on with user activity. Don't use this function. + */ + public void clearUserActivityTimeout(long now, long timeout) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + Slog.i(TAG, "clearUserActivity for " + timeout + "ms from now"); + userActivity(now, timeout, false, OTHER_EVENT, false); + } + + private void userActivity(long time, long timeoutOverride, boolean noChangeLights, + int eventType, boolean force) { if (((mPokey & POKE_LOCK_IGNORE_CHEEK_EVENTS) != 0) - && (eventType == CHEEK_EVENT || eventType == TOUCH_EVENT)) { + && (eventType == CHEEK_EVENT)) { if (false) { - Slog.d(TAG, "dropping cheek or short event mPokey=0x" + Integer.toHexString(mPokey)); + Slog.d(TAG, "dropping cheek event mPokey=0x" + Integer.toHexString(mPokey)); } return; } @@ -2004,6 +2244,7 @@ class PowerManagerService extends IPowerManager.Stub + " mUserState=0x" + Integer.toHexString(mUserState) + " mWakeLockState=0x" + Integer.toHexString(mWakeLockState) + " mProximitySensorActive=" + mProximitySensorActive + + " timeoutOverride=" + timeoutOverride + " force=" + force); } // ignore user activity if we are in the process of turning off the screen @@ -2041,7 +2282,7 @@ class PowerManagerService extends IPowerManager.Stub mWakeLockState = mLocks.reactivateScreenLocksLocked(); setPowerState(mUserState | mWakeLockState, noChangeLights, WindowManagerPolicy.OFF_BECAUSE_OF_USER); - setTimeoutLocked(time, SCREEN_BRIGHT); + setTimeoutLocked(time, timeoutOverride, SCREEN_BRIGHT); } } } @@ -2113,6 +2354,14 @@ class PowerManagerService extends IPowerManager.Stub Slog.d(TAG, "lightSensorChangedLocked " + value); } + // Don't do anything if the screen is off. + if ((mPowerState & SCREEN_ON_BIT) == 0) { + if (mDebugLightSensor) { + Slog.d(TAG, "dropping lightSensorChangedLocked because screen is off"); + } + return; + } + // do not allow light sensor value to decrease if (mHighestLightSensorValue < value) { mHighestLightSensorValue = value; @@ -2144,49 +2393,15 @@ class PowerManagerService extends IPowerManager.Stub Slog.d(TAG, "keyboardValue " + keyboardValue); } - boolean startAnimation = false; if (mAutoBrightessEnabled && mScreenBrightnessOverride < 0) { - if (ANIMATE_SCREEN_LIGHTS) { - if (mScreenBrightness.setTargetLocked(lcdValue, - AUTOBRIGHTNESS_ANIM_STEPS, INITIAL_SCREEN_BRIGHTNESS, - (int)mScreenBrightness.curValue)) { - startAnimation = true; - } - } else { - int brightnessMode = (mAutoBrightessEnabled - ? LightsService.BRIGHTNESS_MODE_SENSOR - : LightsService.BRIGHTNESS_MODE_USER); - mLcdLight.setBrightness(lcdValue, brightnessMode); - } + mScreenBrightness.setTargetLocked(lcdValue, AUTOBRIGHTNESS_ANIM_STEPS, + INITIAL_SCREEN_BRIGHTNESS, (int)mScreenBrightness.curValue); } if (mButtonBrightnessOverride < 0) { - if (ANIMATE_BUTTON_LIGHTS) { - if (mButtonBrightness.setTargetLocked(buttonValue, - AUTOBRIGHTNESS_ANIM_STEPS, INITIAL_BUTTON_BRIGHTNESS, - (int)mButtonBrightness.curValue)) { - startAnimation = true; - } - } else { - mButtonLight.setBrightness(buttonValue); - } + mButtonLight.setBrightness(buttonValue); } if (mButtonBrightnessOverride < 0 || !mKeyboardVisible) { - if (ANIMATE_KEYBOARD_LIGHTS) { - if (mKeyboardBrightness.setTargetLocked(keyboardValue, - AUTOBRIGHTNESS_ANIM_STEPS, INITIAL_BUTTON_BRIGHTNESS, - (int)mKeyboardBrightness.curValue)) { - startAnimation = true; - } - } else { - mKeyboardLight.setBrightness(keyboardValue); - } - } - if (startAnimation) { - if (mDebugLightSensor) { - Slog.i(TAG, "lightSensorChangedLocked scheduling light animator"); - } - mHandler.removeCallbacks(mLightAnimator); - mHandler.post(mLightAnimator); + mKeyboardLight.setBrightness(keyboardValue); } } } @@ -2274,11 +2489,23 @@ class PowerManagerService extends IPowerManager.Stub mWakeLockState = SCREEN_OFF; int N = mLocks.size(); int numCleared = 0; + boolean proxLock = false; for (int i=0; i<N; i++) { WakeLock wl = mLocks.get(i); if (isScreenLock(wl.flags)) { - mLocks.get(i).activated = false; - numCleared++; + if (((wl.flags & LOCK_MASK) == PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK) + && reason == WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR) { + proxLock = true; + } else { + mLocks.get(i).activated = false; + numCleared++; + } + } + } + if (!proxLock) { + mProxIgnoredBecauseScreenTurnedOff = true; + if (mDebugProximitySensor) { + Slog.d(TAG, "setting mProxIgnoredBecauseScreenTurnedOff"); } } EventLog.writeEvent(EventLogTags.POWER_SLEEP_REQUESTED, numCleared); @@ -2469,6 +2696,11 @@ class PowerManagerService extends IPowerManager.Stub result |= wl.minState; } } + if (mDebugProximitySensor) { + Slog.d(TAG, "reactivateScreenLocksLocked mProxIgnoredBecauseScreenTurnedOff=" + + mProxIgnoredBecauseScreenTurnedOff); + } + mProxIgnoredBecauseScreenTurnedOff = false; return result; } } @@ -2537,6 +2769,7 @@ class PowerManagerService extends IPowerManager.Stub } } + // for watchdog public void monitor() { synchronized (mLocks) { } } @@ -2556,34 +2789,25 @@ class PowerManagerService extends IPowerManager.Stub public void setBacklightBrightness(int brightness) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); // Don't let applications turn the screen all the way off - brightness = Math.max(brightness, Power.BRIGHTNESS_DIM); - mLcdLight.setBrightness(brightness); - mKeyboardLight.setBrightness(mKeyboardVisible ? brightness : 0); - mButtonLight.setBrightness(brightness); - long identity = Binder.clearCallingIdentity(); - try { - mBatteryStats.noteScreenBrightness(brightness); - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException calling noteScreenBrightness on BatteryStatsService", e); - } finally { - Binder.restoreCallingIdentity(identity); - } + synchronized (mLocks) { + brightness = Math.max(brightness, Power.BRIGHTNESS_DIM); + mLcdLight.setBrightness(brightness); + mKeyboardLight.setBrightness(mKeyboardVisible ? brightness : 0); + mButtonLight.setBrightness(brightness); + long identity = Binder.clearCallingIdentity(); + try { + mBatteryStats.noteScreenBrightness(brightness); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException calling noteScreenBrightness on BatteryStatsService", e); + } finally { + Binder.restoreCallingIdentity(identity); + } - // update our animation state - if (ANIMATE_SCREEN_LIGHTS) { - mScreenBrightness.curValue = brightness; - mScreenBrightness.animating = false; - mScreenBrightness.targetValue = -1; - } - if (ANIMATE_KEYBOARD_LIGHTS) { - mKeyboardBrightness.curValue = brightness; - mKeyboardBrightness.animating = false; - mKeyboardBrightness.targetValue = -1; - } - if (ANIMATE_BUTTON_LIGHTS) { - mButtonBrightness.curValue = brightness; - mButtonBrightness.animating = false; - mButtonBrightness.targetValue = -1; + // update our animation state + synchronized (mLocks) { + mScreenBrightness.targetValue = brightness; + mScreenBrightness.jumpToTargetLocked(); + } } } @@ -2628,7 +2852,13 @@ class PowerManagerService extends IPowerManager.Stub } if (mProximitySensorActive) { mProximitySensorActive = false; - forceUserActivityLocked(); + if (mDebugProximitySensor) { + Slog.d(TAG, "disableProximityLockLocked mProxIgnoredBecauseScreenTurnedOff=" + + mProxIgnoredBecauseScreenTurnedOff); + } + if (!mProxIgnoredBecauseScreenTurnedOff) { + forceUserActivityLocked(); + } } } } @@ -2642,15 +2872,27 @@ class PowerManagerService extends IPowerManager.Stub return; } if (active) { - goToSleepLocked(SystemClock.uptimeMillis(), - WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR); + if (mDebugProximitySensor) { + Slog.d(TAG, "b mProxIgnoredBecauseScreenTurnedOff=" + + mProxIgnoredBecauseScreenTurnedOff); + } + if (!mProxIgnoredBecauseScreenTurnedOff) { + goToSleepLocked(SystemClock.uptimeMillis(), + WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR); + } mProximitySensorActive = true; } else { // proximity sensor negative events trigger as user activity. // temporarily set mUserActivityAllowed to true so this will work // even when the keyguard is on. mProximitySensorActive = false; - forceUserActivityLocked(); + if (mDebugProximitySensor) { + Slog.d(TAG, "b mProxIgnoredBecauseScreenTurnedOff=" + + mProxIgnoredBecauseScreenTurnedOff); + } + if (!mProxIgnoredBecauseScreenTurnedOff) { + forceUserActivityLocked(); + } if (mProximityWakeLockCount == 0) { // disable sensor if we have no listeners left after proximity negative diff --git a/services/java/com/android/server/ProcessStats.java b/services/java/com/android/server/ProcessStats.java index 4fa89d9..43dbcc0 100644 --- a/services/java/com/android/server/ProcessStats.java +++ b/services/java/com/android/server/ProcessStats.java @@ -39,7 +39,7 @@ public class ProcessStats { private static final int[] PROCESS_STATS_FORMAT = new int[] { PROC_SPACE_TERM, - PROC_SPACE_TERM, + PROC_SPACE_TERM|PROC_PARENS, PROC_SPACE_TERM, PROC_SPACE_TERM, PROC_SPACE_TERM, @@ -75,16 +75,29 @@ public class ProcessStats { PROC_SPACE_TERM, PROC_SPACE_TERM, PROC_SPACE_TERM, + PROC_SPACE_TERM|PROC_OUT_LONG, // 9: minor faults PROC_SPACE_TERM, + PROC_SPACE_TERM|PROC_OUT_LONG, // 11: major faults PROC_SPACE_TERM, + PROC_SPACE_TERM|PROC_OUT_LONG, // 13: utime + PROC_SPACE_TERM|PROC_OUT_LONG, // 14: stime PROC_SPACE_TERM, PROC_SPACE_TERM, - PROC_SPACE_TERM|PROC_OUT_LONG, // 13: utime - PROC_SPACE_TERM|PROC_OUT_LONG // 14: stime + PROC_SPACE_TERM, + PROC_SPACE_TERM, + PROC_SPACE_TERM, + PROC_SPACE_TERM, + PROC_SPACE_TERM|PROC_OUT_LONG, // 21: vsize }; - private final String[] mProcessFullStatsStringData = new String[3]; - private final long[] mProcessFullStatsData = new long[3]; + static final int PROCESS_FULL_STAT_MINOR_FAULTS = 1; + static final int PROCESS_FULL_STAT_MAJOR_FAULTS = 2; + static final int PROCESS_FULL_STAT_UTIME = 3; + static final int PROCESS_FULL_STAT_STIME = 4; + static final int PROCESS_FULL_STAT_VSIZE = 5; + + private final String[] mProcessFullStatsStringData = new String[6]; + private final long[] mProcessFullStatsData = new long[6]; private static final int[] SYSTEM_CPU_FORMAT = new int[] { PROC_SPACE_TERM|PROC_COMBINE, @@ -116,6 +129,9 @@ public class ProcessStats { private long mCurrentSampleTime; private long mLastSampleTime; + private long mCurrentSampleRealTime; + private long mLastSampleRealTime; + private long mBaseUserTime; private long mBaseSystemTime; private long mBaseIoWaitTime; @@ -163,10 +179,15 @@ public class ProcessStats { final ArrayList<Stats> threadStats; final ArrayList<Stats> workingThreads; + public boolean interesting; + public String baseName; public String name; int nameWidth; + public long base_uptime; + public long rel_uptime; + public long base_utime; public long base_stime; public int rel_utime; @@ -178,6 +199,7 @@ public class ProcessStats { public int rel_majfaults; public boolean active; + public boolean working; public boolean added; public boolean removed; @@ -211,8 +233,7 @@ public class ProcessStats { private final static Comparator<Stats> sLoadComparator = new Comparator<Stats>() { public final int - compare(Stats sta, Stats stb) - { + compare(Stats sta, Stats stb) { int ta = sta.rel_utime + sta.rel_stime; int tb = stb.rel_utime + stb.rel_stime; if (ta != tb) { @@ -241,31 +262,17 @@ public class ProcessStats { } public void init() { + if (DEBUG) Slog.v(TAG, "Init: " + this); mFirst = true; update(); } public void update() { + if (DEBUG) Slog.v(TAG, "Update: " + this); mLastSampleTime = mCurrentSampleTime; mCurrentSampleTime = SystemClock.uptimeMillis(); - - final float[] loadAverages = mLoadAverageData; - if (Process.readProcFile("/proc/loadavg", LOAD_AVERAGE_FORMAT, - null, null, loadAverages)) { - float load1 = loadAverages[0]; - float load5 = loadAverages[1]; - float load15 = loadAverages[2]; - if (load1 != mLoad1 || load5 != mLoad5 || load15 != mLoad15) { - mLoad1 = load1; - mLoad5 = load5; - mLoad15 = load15; - onLoadChanged(load1, load5, load15); - } - } - - mCurPids = collectStats("/proc", -1, mFirst, mCurPids, - mProcStats, mWorkingProcs); - mFirst = false; + mLastSampleRealTime = mCurrentSampleRealTime; + mCurrentSampleRealTime = SystemClock.elapsedRealtime(); final long[] sysCpu = mSystemCpuData; if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, @@ -288,7 +295,7 @@ public class ProcessStats { mRelSoftIrqTime = (int)(softirqtime - mBaseSoftIrqTime); mRelIdleTime = (int)(idletime - mBaseIdleTime); - if (false) { + if (DEBUG) { Slog.i("Load", "Total U:" + sysCpu[0] + " N:" + sysCpu[1] + " S:" + sysCpu[2] + " I:" + sysCpu[3] + " W:" + sysCpu[4] + " Q:" + sysCpu[5] @@ -305,16 +312,32 @@ public class ProcessStats { mBaseIdleTime = idletime; } + mCurPids = collectStats("/proc", -1, mFirst, mCurPids, mProcStats); + + final float[] loadAverages = mLoadAverageData; + if (Process.readProcFile("/proc/loadavg", LOAD_AVERAGE_FORMAT, + null, null, loadAverages)) { + float load1 = loadAverages[0]; + float load5 = loadAverages[1]; + float load15 = loadAverages[2]; + if (load1 != mLoad1 || load5 != mLoad5 || load15 != mLoad15) { + mLoad1 = load1; + mLoad5 = load5; + mLoad15 = load15; + onLoadChanged(load1, load5, load15); + } + } + + if (DEBUG) Slog.i(TAG, "*** TIME TO COLLECT STATS: " + + (SystemClock.uptimeMillis()-mCurrentSampleTime)); + mWorkingProcsSorted = false; mFirst = false; } private int[] collectStats(String statsFile, int parentPid, boolean first, - int[] curPids, ArrayList<Stats> allProcs, - ArrayList<Stats> workingProcs) { + int[] curPids, ArrayList<Stats> allProcs) { - workingProcs.clear(); - int[] pids = Process.getPids(statsFile, curPids); int NP = (pids == null) ? 0 : pids.length; int NS = allProcs.size(); @@ -330,56 +353,68 @@ public class ProcessStats { if (st != null && st.pid == pid) { // Update an existing process... st.added = false; + st.working = false; curStatsIndex++; - if (localLOGV) Slog.v(TAG, "Existing pid " + pid + ": " + st); + if (DEBUG) Slog.v(TAG, "Existing " + + (parentPid < 0 ? "process" : "thread") + + " pid " + pid + ": " + st); - final long[] procStats = mProcessStatsData; - if (!Process.readProcFile(st.statFile.toString(), - PROCESS_STATS_FORMAT, null, procStats, null)) { - continue; - } - - final long minfaults = procStats[PROCESS_STAT_MINOR_FAULTS]; - final long majfaults = procStats[PROCESS_STAT_MAJOR_FAULTS]; - final long utime = procStats[PROCESS_STAT_UTIME]; - final long stime = procStats[PROCESS_STAT_STIME]; - - if (utime == st.base_utime && stime == st.base_stime) { - st.rel_utime = 0; - st.rel_stime = 0; - st.rel_minfaults = 0; - st.rel_majfaults = 0; - if (st.active) { - st.active = false; + if (st.interesting) { + final long uptime = SystemClock.uptimeMillis(); + + final long[] procStats = mProcessStatsData; + if (!Process.readProcFile(st.statFile.toString(), + PROCESS_STATS_FORMAT, null, procStats, null)) { + continue; } - continue; - } - if (!st.active) { - st.active = true; - } + final long minfaults = procStats[PROCESS_STAT_MINOR_FAULTS]; + final long majfaults = procStats[PROCESS_STAT_MAJOR_FAULTS]; + final long utime = procStats[PROCESS_STAT_UTIME]; + final long stime = procStats[PROCESS_STAT_STIME]; + + if (utime == st.base_utime && stime == st.base_stime) { + st.rel_utime = 0; + st.rel_stime = 0; + st.rel_minfaults = 0; + st.rel_majfaults = 0; + if (st.active) { + st.active = false; + } + continue; + } - if (parentPid < 0) { - getName(st, st.cmdlineFile); - if (st.threadStats != null) { - mCurThreadPids = collectStats(st.threadsDir, pid, false, - mCurThreadPids, st.threadStats, - st.workingThreads); + if (!st.active) { + st.active = true; + } + + if (parentPid < 0) { + getName(st, st.cmdlineFile); + if (st.threadStats != null) { + mCurThreadPids = collectStats(st.threadsDir, pid, false, + mCurThreadPids, st.threadStats); + } } + + if (DEBUG) Slog.v("Load", "Stats changed " + st.name + " pid=" + st.pid + + " utime=" + utime + "-" + st.base_utime + + " stime=" + stime + "-" + st.base_stime + + " minfaults=" + minfaults + "-" + st.base_minfaults + + " majfaults=" + majfaults + "-" + st.base_majfaults); + + st.rel_uptime = uptime - st.base_uptime; + st.base_uptime = uptime; + st.rel_utime = (int)(utime - st.base_utime); + st.rel_stime = (int)(stime - st.base_stime); + st.base_utime = utime; + st.base_stime = stime; + st.rel_minfaults = (int)(minfaults - st.base_minfaults); + st.rel_majfaults = (int)(majfaults - st.base_majfaults); + st.base_minfaults = minfaults; + st.base_majfaults = majfaults; + st.working = true; } - st.rel_utime = (int)(utime - st.base_utime); - st.rel_stime = (int)(stime - st.base_stime); - st.base_utime = utime; - st.base_stime = stime; - st.rel_minfaults = (int)(minfaults - st.base_minfaults); - st.rel_majfaults = (int)(majfaults - st.base_majfaults); - st.base_minfaults = minfaults; - st.base_majfaults = majfaults; - //Slog.i("Load", "Stats changed " + name + " pid=" + st.pid - // + " name=" + st.name + " utime=" + utime - // + " stime=" + stime); - workingProcs.add(st); continue; } @@ -389,19 +424,34 @@ public class ProcessStats { allProcs.add(curStatsIndex, st); curStatsIndex++; NS++; - if (localLOGV) Slog.v(TAG, "New pid " + pid + ": " + st); + if (DEBUG) Slog.v(TAG, "New " + + (parentPid < 0 ? "process" : "thread") + + " pid " + pid + ": " + st); final String[] procStatsString = mProcessFullStatsStringData; final long[] procStats = mProcessFullStatsData; + st.base_uptime = SystemClock.uptimeMillis(); if (Process.readProcFile(st.statFile.toString(), PROCESS_FULL_STATS_FORMAT, procStatsString, procStats, null)) { - st.baseName = parentPid < 0 - ? procStatsString[0] : Integer.toString(pid); - st.base_utime = 0; //procStats[1]; - st.base_stime = 0; //procStats[2]; - st.base_minfaults = st.base_majfaults = 0; + // This is a possible way to filter out processes that + // are actually kernel threads... do we want to? Some + // of them do use CPU, but there can be a *lot* that are + // not doing anything. + if (true || procStats[PROCESS_FULL_STAT_VSIZE] != 0) { + st.interesting = true; + st.baseName = procStatsString[0]; + st.base_minfaults = procStats[PROCESS_FULL_STAT_MINOR_FAULTS]; + st.base_majfaults = procStats[PROCESS_FULL_STAT_MAJOR_FAULTS]; + st.base_utime = procStats[PROCESS_FULL_STAT_UTIME]; + st.base_stime = procStats[PROCESS_FULL_STAT_STIME]; + } else { + Slog.i(TAG, "Skipping kernel process pid " + pid + + " name " + procStatsString[0]); + st.baseName = procStatsString[0]; + } } else { + Slog.w(TAG, "Skipping unknown process pid " + pid); st.baseName = "<unknown>"; st.base_utime = st.base_stime = 0; st.base_minfaults = st.base_majfaults = 0; @@ -409,24 +459,26 @@ public class ProcessStats { if (parentPid < 0) { getName(st, st.cmdlineFile); - } else { - st.name = st.baseName; - st.nameWidth = onMeasureProcessName(st.name); if (st.threadStats != null) { mCurThreadPids = collectStats(st.threadsDir, pid, true, - mCurThreadPids, st.threadStats, - st.workingThreads); + mCurThreadPids, st.threadStats); } + } else if (st.interesting) { + st.name = st.baseName; + st.nameWidth = onMeasureProcessName(st.name); } + + if (DEBUG) Slog.v("Load", "Stats added " + st.name + " pid=" + st.pid + + " utime=" + st.base_utime + " stime=" + st.base_stime + + " minfaults=" + st.base_minfaults + " majfaults=" + st.base_majfaults); - //Slog.i("Load", "New process: " + st.pid + " " + st.name); st.rel_utime = 0; st.rel_stime = 0; st.rel_minfaults = 0; st.rel_majfaults = 0; st.added = true; - if (!first) { - workingProcs.add(st); + if (!first && st.interesting) { + st.working = true; } continue; } @@ -437,10 +489,12 @@ public class ProcessStats { st.rel_minfaults = 0; st.rel_majfaults = 0; st.removed = true; - workingProcs.add(st); + st.working = true; allProcs.remove(curStatsIndex); NS--; - if (localLOGV) Slog.v(TAG, "Removed pid " + st.pid + ": " + st); + if (DEBUG) Slog.v(TAG, "Removed " + + (parentPid < 0 ? "process" : "thread") + + " pid " + pid + ": " + st); // Decrement the loop counter so that we process the current pid // again the next time through the loop. i--; @@ -455,7 +509,7 @@ public class ProcessStats { st.rel_minfaults = 0; st.rel_majfaults = 0; st.removed = true; - workingProcs.add(st); + st.working = true; allProcs.remove(curStatsIndex); NS--; if (localLOGV) Slog.v(TAG, "Removed pid " + st.pid + ": " + st); @@ -569,11 +623,42 @@ public class ProcessStats { / (mRelUserTime+mRelSystemTime+mRelIrqTime+mRelIdleTime); } - final public int countWorkingStats() { + final void buildWorkingProcs() { if (!mWorkingProcsSorted) { + mWorkingProcs.clear(); + final int N = mProcStats.size(); + for (int i=0; i<N; i++) { + Stats stats = mProcStats.get(i); + if (stats.working) { + mWorkingProcs.add(stats); + if (stats.threadStats != null && stats.threadStats.size() > 1) { + stats.workingThreads.clear(); + final int M = stats.threadStats.size(); + for (int j=0; j<M; j++) { + Stats tstats = stats.threadStats.get(j); + if (tstats.working) { + stats.workingThreads.add(tstats); + } + } + Collections.sort(stats.workingThreads, sLoadComparator); + } + } + } Collections.sort(mWorkingProcs, sLoadComparator); mWorkingProcsSorted = true; } + } + + final public int countStats() { + return mProcStats.size(); + } + + final public Stats getStats(int index) { + return mProcStats.get(index); + } + + final public int countWorkingStats() { + buildWorkingProcs(); return mWorkingProcs.size(); } @@ -581,12 +666,7 @@ public class ProcessStats { return mWorkingProcs.get(index); } - final public String printCurrentState() { - if (!mWorkingProcsSorted) { - Collections.sort(mWorkingProcs, sLoadComparator); - mWorkingProcsSorted = true; - } - + final public String printCurrentLoad() { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); pw.print("Load: "); @@ -595,67 +675,111 @@ public class ProcessStats { pw.print(mLoad5); pw.print(" / "); pw.println(mLoad15); + return sw.toString(); + } + + final public String printCurrentState(long now) { + buildWorkingProcs(); - long now = SystemClock.uptimeMillis(); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); pw.print("CPU usage from "); - pw.print(now-mLastSampleTime); - pw.print("ms to "); - pw.print(now-mCurrentSampleTime); - pw.println("ms ago:"); + if (now > mLastSampleTime) { + pw.print(now-mLastSampleTime); + pw.print("ms to "); + pw.print(now-mCurrentSampleTime); + pw.print("ms ago"); + } else { + pw.print(mLastSampleTime-now); + pw.print("ms to "); + pw.print(mCurrentSampleTime-now); + pw.print("ms later"); + } + + long sampleTime = mCurrentSampleTime - mLastSampleTime; + long sampleRealTime = mCurrentSampleRealTime - mLastSampleRealTime; + long percAwake = (sampleTime*100) / sampleRealTime; + if (percAwake != 100) { + pw.print(" with "); + pw.print(percAwake); + pw.print("% awake"); + } + pw.println(":"); final int totalTime = mRelUserTime + mRelSystemTime + mRelIoWaitTime + mRelIrqTime + mRelSoftIrqTime + mRelIdleTime; + if (DEBUG) Slog.i(TAG, "totalTime " + totalTime + " over sample time " + + (mCurrentSampleTime-mLastSampleTime)); + int N = mWorkingProcs.size(); for (int i=0; i<N; i++) { Stats st = mWorkingProcs.get(i); printProcessCPU(pw, st.added ? " +" : (st.removed ? " -": " "), - st.name, totalTime, st.rel_utime, st.rel_stime, 0, 0, 0, - st.rel_minfaults, st.rel_majfaults); + st.pid, st.name, (int)(st.rel_uptime+5)/10, + st.rel_utime, st.rel_stime, 0, 0, 0, st.rel_minfaults, st.rel_majfaults); if (!st.removed && st.workingThreads != null) { int M = st.workingThreads.size(); for (int j=0; j<M; j++) { Stats tst = st.workingThreads.get(j); printProcessCPU(pw, tst.added ? " +" : (tst.removed ? " -": " "), - tst.name, totalTime, tst.rel_utime, tst.rel_stime, - 0, 0, 0, 0, 0); + tst.pid, tst.name, (int)(st.rel_uptime+5)/10, + tst.rel_utime, tst.rel_stime, 0, 0, 0, 0, 0); } } } - printProcessCPU(pw, "", "TOTAL", totalTime, mRelUserTime, mRelSystemTime, + printProcessCPU(pw, "", -1, "TOTAL", totalTime, mRelUserTime, mRelSystemTime, mRelIoWaitTime, mRelIrqTime, mRelSoftIrqTime, 0, 0); return sw.toString(); } - private void printProcessCPU(PrintWriter pw, String prefix, String label, int totalTime, - int user, int system, int iowait, int irq, int softIrq, int minFaults, int majFaults) { + private void printRatio(PrintWriter pw, long numerator, long denominator) { + long thousands = (numerator*1000)/denominator; + long hundreds = thousands/10; + pw.print(hundreds); + if (hundreds < 10) { + long remainder = thousands - (hundreds*10); + if (remainder != 0) { + pw.print('.'); + pw.print(remainder); + } + } + } + + private void printProcessCPU(PrintWriter pw, String prefix, int pid, String label, + int totalTime, int user, int system, int iowait, int irq, int softIrq, + int minFaults, int majFaults) { pw.print(prefix); + if (totalTime == 0) totalTime = 1; + printRatio(pw, user+system+iowait+irq+softIrq, totalTime); + pw.print("% "); + if (pid >= 0) { + pw.print(pid); + pw.print("/"); + } pw.print(label); pw.print(": "); - if (totalTime == 0) totalTime = 1; - pw.print(((user+system+iowait+irq+softIrq)*100)/totalTime); - pw.print("% = "); - pw.print((user*100)/totalTime); + printRatio(pw, user, totalTime); pw.print("% user + "); - pw.print((system*100)/totalTime); + printRatio(pw, system, totalTime); pw.print("% kernel"); if (iowait > 0) { pw.print(" + "); - pw.print((iowait*100)/totalTime); + printRatio(pw, iowait, totalTime); pw.print("% iowait"); } if (irq > 0) { pw.print(" + "); - pw.print((irq*100)/totalTime); + printRatio(pw, irq, totalTime); pw.print("% irq"); } if (softIrq > 0) { pw.print(" + "); - pw.print((softIrq*100)/totalTime); + printRatio(pw, softIrq, totalTime); pw.print("% softirq"); } if (minFaults > 0 || majFaults > 0) { @@ -696,8 +820,9 @@ public class ProcessStats { } private void getName(Stats st, String cmdlineFile) { - String newName = st.baseName; - if (st.baseName == null || st.baseName.equals("app_process")) { + String newName = st.name; + if (st.name == null || st.name.equals("app_process") + || st.name.equals("<pre-initialized>")) { String cmdName = readFile(cmdlineFile, '\0'); if (cmdName != null && cmdName.length() > 1) { newName = cmdName; @@ -706,6 +831,9 @@ public class ProcessStats { newName = newName.substring(i+1); } } + if (newName == null) { + newName = st.baseName; + } } if (st.name == null || !newName.equals(st.name)) { st.name = newName; diff --git a/services/java/com/android/server/SensorService.java b/services/java/com/android/server/SensorService.java deleted file mode 100644 index 9f5718f..0000000 --- a/services/java/com/android/server/SensorService.java +++ /dev/null @@ -1,282 +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.content.Context; -import android.hardware.ISensorService; -import android.os.Binder; -import android.os.Bundle; -import android.os.RemoteException; -import android.os.IBinder; -import android.util.Config; -import android.util.Slog; -import android.util.PrintWriterPrinter; -import android.util.Printer; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; - -import com.android.internal.app.IBatteryStats; -import com.android.server.am.BatteryStatsService; - - -/** - * Class that manages the device's sensors. It register clients and activate - * the needed sensors. The sensor events themselves are not broadcasted from - * this service, instead, a file descriptor is provided to each client they - * can read events from. - */ - -class SensorService extends ISensorService.Stub { - static final String TAG = SensorService.class.getSimpleName(); - private static final boolean DEBUG = false; - private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV; - private static final int SENSOR_DISABLE = -1; - private int mCurrentDelay = 0; - - /** - * Battery statistics to be updated when sensors are enabled and disabled. - */ - final IBatteryStats mBatteryStats = BatteryStatsService.getService(); - - private final class Listener implements IBinder.DeathRecipient { - final IBinder mToken; - final int mUid; - - int mSensors = 0; - int mDelay = 0x7FFFFFFF; - - Listener(IBinder token, int uid) { - mToken = token; - mUid = uid; - } - - void addSensor(int sensor, int delay) { - mSensors |= (1<<sensor); - if (delay < mDelay) - mDelay = delay; - } - - void removeSensor(int sensor) { - mSensors &= ~(1<<sensor); - } - - boolean hasSensor(int sensor) { - return ((mSensors & (1<<sensor)) != 0); - } - - public void binderDied() { - if (localLOGV) Slog.d(TAG, "sensor listener died"); - synchronized(mListeners) { - mListeners.remove(this); - mToken.unlinkToDeath(this, 0); - // go through the lists of sensors used by the listener that - // died and deactivate them. - for (int sensor=0 ; sensor<32 && mSensors!=0 ; sensor++) { - if (hasSensor(sensor)) { - removeSensor(sensor); - deactivateIfUnusedLocked(sensor); - try { - mBatteryStats.noteStopSensor(mUid, sensor); - } catch (RemoteException e) { - // oops. not a big deal. - } - } - } - if (mListeners.size() == 0) { - _sensors_control_wake(); - _sensors_control_close(); - } else { - // TODO: we should recalculate the delay, since removing - // a listener may increase the overall rate. - } - mListeners.notify(); - } - } - } - - @SuppressWarnings("unused") - public SensorService(Context context) { - if (localLOGV) Slog.d(TAG, "SensorService startup"); - _sensors_control_init(); - } - - public Bundle getDataChannel() throws RemoteException { - // synchronize so we do not require sensor HAL to be thread-safe. - synchronized(mListeners) { - return _sensors_control_open(); - } - } - - public boolean enableSensor(IBinder binder, String name, int sensor, int enable) - throws RemoteException { - - if (localLOGV) Slog.d(TAG, "enableSensor " + name + "(#" + sensor + ") " + enable); - - if (binder == null) { - Slog.e(TAG, "listener is null (sensor=" + name + ", id=" + sensor + ")"); - return false; - } - - if (enable < 0 && (enable != SENSOR_DISABLE)) { - Slog.e(TAG, "invalid enable parameter (enable=" + enable + - ", sensor=" + name + ", id=" + sensor + ")"); - return false; - } - - boolean res; - int uid = Binder.getCallingUid(); - synchronized(mListeners) { - res = enableSensorInternalLocked(binder, uid, name, sensor, enable); - if (res == true) { - // Inform battery statistics service of status change - long identity = Binder.clearCallingIdentity(); - if (enable == SENSOR_DISABLE) { - mBatteryStats.noteStopSensor(uid, sensor); - } else { - mBatteryStats.noteStartSensor(uid, sensor); - } - Binder.restoreCallingIdentity(identity); - } - } - return res; - } - - private boolean enableSensorInternalLocked(IBinder binder, int uid, - String name, int sensor, int enable) throws RemoteException { - - // check if we have this listener - Listener l = null; - for (Listener listener : mListeners) { - if (binder == listener.mToken) { - l = listener; - break; - } - } - - if (enable != SENSOR_DISABLE) { - // Activate the requested sensor - if (_sensors_control_activate(sensor, true) == false) { - Slog.w(TAG, "could not enable sensor " + sensor); - return false; - } - - if (l == null) { - /* - * we don't have a listener for this binder yet, so - * create a new one and add it to the list. - */ - l = new Listener(binder, uid); - binder.linkToDeath(l, 0); - mListeners.add(l); - mListeners.notify(); - } - - // take note that this sensor is now used by this client - l.addSensor(sensor, enable); - - } else { - - if (l == null) { - /* - * This client isn't in the list, this usually happens - * when enabling the sensor failed, but the client - * didn't handle the error and later tries to shut that - * sensor off. - */ - Slog.w(TAG, "listener with binder " + binder + - ", doesn't exist (sensor=" + name + - ", id=" + sensor + ")"); - return false; - } - - // remove this sensor from this client - l.removeSensor(sensor); - - // see if we need to deactivate this sensors= - deactivateIfUnusedLocked(sensor); - - // if the listener doesn't have any more sensors active - // we can get rid of it - if (l.mSensors == 0) { - // we won't need this death notification anymore - binder.unlinkToDeath(l, 0); - // remove the listener from the list - mListeners.remove(l); - // and if the list is empty, turn off the whole sensor h/w - if (mListeners.size() == 0) { - _sensors_control_wake(); - _sensors_control_close(); - } - mListeners.notify(); - } - } - - // calculate and set the new delay - int minDelay = 0x7FFFFFFF; - for (Listener listener : mListeners) { - if (listener.mDelay < minDelay) - minDelay = listener.mDelay; - } - if (minDelay != 0x7FFFFFFF) { - mCurrentDelay = minDelay; - _sensors_control_set_delay(minDelay); - } - - return true; - } - - private void deactivateIfUnusedLocked(int sensor) { - int size = mListeners.size(); - for (int i=0 ; i<size ; i++) { - if (mListeners.get(i).hasSensor(sensor)) { - // this sensor is still in use, don't turn it off - return; - } - } - if (_sensors_control_activate(sensor, false) == false) { - Slog.w(TAG, "could not disable sensor " + sensor); - } - } - - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - synchronized (mListeners) { - Printer pr = new PrintWriterPrinter(pw); - int c = 0; - pr.println(mListeners.size() + " listener(s), delay=" + mCurrentDelay + " ms"); - for (Listener l : mListeners) { - pr.println("listener[" + c + "] " + - "sensors=0x" + Integer.toString(l.mSensors, 16) + - ", uid=" + l.mUid + - ", delay=" + - l.mDelay + " ms"); - c++; - } - } - } - - private ArrayList<Listener> mListeners = new ArrayList<Listener>(); - - private static native int _sensors_control_init(); - private static native Bundle _sensors_control_open(); - private static native int _sensors_control_close(); - private static native boolean _sensors_control_activate(int sensor, boolean activate); - private static native int _sensors_control_set_delay(int ms); - private static native int _sensors_control_wake(); -} diff --git a/services/java/com/android/server/StatusBarManagerService.java b/services/java/com/android/server/StatusBarManagerService.java new file mode 100644 index 0000000..e5dfb18 --- /dev/null +++ b/services/java/com/android/server/StatusBarManagerService.java @@ -0,0 +1,471 @@ +/* + * 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.app.PendingIntent; +import android.app.StatusBarManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.net.Uri; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.Binder; +import android.os.Handler; +import android.os.SystemClock; +import android.util.Slog; + +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 java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * A note on locking: We rely on the fact that calls onto mBar are oneway or + * if they are local, that they just enqueue messages to not deadlock. + */ +public class StatusBarManagerService extends IStatusBarService.Stub +{ + static final String TAG = "StatusBarManagerService"; + static final boolean SPEW = false; + + final Context mContext; + Handler mHandler = new Handler(); + NotificationCallbacks mNotificationCallbacks; + volatile IStatusBar mBar; + StatusBarIconList mIcons = new StatusBarIconList(); + HashMap<IBinder,StatusBarNotification> mNotifications + = new HashMap<IBinder,StatusBarNotification>(); + + // for disabling the status bar + ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>(); + int mDisabled = 0; + + private class DisableRecord implements IBinder.DeathRecipient { + String pkg; + int what; + IBinder token; + + public void binderDied() { + Slog.i(TAG, "binder died for pkg=" + pkg); + disable(0, token, pkg); + token.unlinkToDeath(this, 0); + } + } + + public interface NotificationCallbacks { + void onSetDisabled(int status); + void onClearAll(); + void onNotificationClick(String pkg, String tag, int id); + void onPanelRevealed(); + void onNotificationError(String pkg, String tag, int id, + int uid, int initialPid, String message); + } + + /** + * Construct the service, add the status bar view to the window manager + */ + public StatusBarManagerService(Context context) { + mContext = context; + + final Resources res = context.getResources(); + mIcons.defineSlots(res.getStringArray(com.android.internal.R.array.config_statusBarIcons)); + } + + public void setNotificationCallbacks(NotificationCallbacks listener) { + mNotificationCallbacks = listener; + } + + // ================================================================================ + // 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() { + enforceExpandStatusBar(); + + if (mBar != null) { + try { + mBar.animateExpand(); + } catch (RemoteException ex) { + } + } + } + + public void collapse() { + enforceExpandStatusBar(); + + if (mBar != null) { + try { + mBar.animateCollapse(); + } catch (RemoteException ex) { + } + } + } + + public void disable(int what, IBinder token, String pkg) { + enforceStatusBar(); + + // 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) { + } + } + } + } + } + + public void setIcon(String slot, String iconPackage, int iconId, int iconLevel) { + enforceStatusBar(); + + synchronized (mIcons) { + int index = mIcons.getSlotIndex(slot); + if (index < 0) { + throw new SecurityException("invalid status bar icon slot: " + slot); + } + + StatusBarIcon icon = new StatusBarIcon(iconPackage, iconId, iconLevel); + //Slog.d(TAG, "setIcon slot=" + slot + " index=" + index + " icon=" + icon); + mIcons.setIcon(index, icon); + + if (mBar != null) { + try { + mBar.setIcon(index, icon); + } catch (RemoteException ex) { + } + } + } + } + + public void setIconVisibility(String slot, boolean visible) { + enforceStatusBar(); + + synchronized (mIcons) { + int index = mIcons.getSlotIndex(slot); + if (index < 0) { + throw new SecurityException("invalid status bar icon slot: " + slot); + } + + StatusBarIcon icon = mIcons.getIcon(index); + if (icon == null) { + return; + } + + if (icon.visible != visible) { + icon.visible = visible; + + if (mBar != null) { + try { + mBar.setIcon(index, icon); + } catch (RemoteException ex) { + } + } + } + } + } + + public void removeIcon(String slot) { + enforceStatusBar(); + + synchronized (mIcons) { + int index = mIcons.getSlotIndex(slot); + if (index < 0) { + throw new SecurityException("invalid status bar icon slot: " + slot); + } + + mIcons.removeIcon(index); + + if (mBar != null) { + try { + mBar.removeIcon(index); + } catch (RemoteException ex) { + } + } + } + } + + private void enforceStatusBar() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR, + "StatusBarManagerService"); + } + + private void enforceExpandStatusBar() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.EXPAND_STATUS_BAR, + "StatusBarManagerService"); + } + + private void enforceStatusBarService() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE, + "StatusBarManagerService"); + } + + + // ================================================================================ + // Callbacks from the status bar service. + // ================================================================================ + public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList, + List<IBinder> notificationKeys, List<StatusBarNotification> notifications) { + enforceStatusBarService(); + + Slog.i(TAG, "registerStatusBar bar=" + bar); + mBar = bar; + synchronized (mIcons) { + iconList.copyFrom(mIcons); + } + synchronized (mNotifications) { + for (Map.Entry<IBinder,StatusBarNotification> e: mNotifications.entrySet()) { + notificationKeys.add(e.getKey()); + notifications.add(e.getValue()); + } + } + } + + /** + * The status bar service should call this each time the user brings the panel from + * invisible to visible in order to clear the notification light. + */ + public void onPanelRevealed() { + enforceStatusBarService(); + + // tell the notification manager to turn off the lights. + mNotificationCallbacks.onPanelRevealed(); + } + + public void onNotificationClick(String pkg, String tag, int id) { + enforceStatusBarService(); + + mNotificationCallbacks.onNotificationClick(pkg, tag, id); + } + + public void onNotificationError(String pkg, String tag, int id, + int uid, int initialPid, String message) { + enforceStatusBarService(); + + // WARNING: this will call back into us to do the remove. Don't hold any locks. + mNotificationCallbacks.onNotificationError(pkg, tag, id, uid, initialPid, message); + } + + public void onClearAllNotifications() { + enforceStatusBarService(); + + mNotificationCallbacks.onClearAll(); + } + + // ================================================================================ + // Callbacks for NotificationManagerService. + // ================================================================================ + public IBinder addNotification(StatusBarNotification notification) { + synchronized (mNotifications) { + IBinder key = new Binder(); + mNotifications.put(key, notification); + if (mBar != null) { + try { + mBar.addNotification(key, notification); + } catch (RemoteException ex) { + } + } + return key; + } + } + + public void updateNotification(IBinder key, StatusBarNotification notification) { + synchronized (mNotifications) { + if (!mNotifications.containsKey(key)) { + throw new IllegalArgumentException("updateNotification key not found: " + key); + } + mNotifications.put(key, notification); + if (mBar != null) { + try { + mBar.updateNotification(key, notification); + } catch (RemoteException ex) { + } + } + } + } + + public void removeNotification(IBinder key) { + synchronized (mNotifications) { + final StatusBarNotification n = mNotifications.remove(key); + if (n == null) { + throw new IllegalArgumentException("removeNotification key not found: " + key); + } + if (mBar != null) { + try { + mBar.removeNotification(key); + } catch (RemoteException ex) { + } + } + } + } + + // ================================================================================ + // Can be called from any thread + // ================================================================================ + + // lock on mDisableRecords + void manageDisableListLocked(int what, IBinder token, String pkg) { + if (SPEW) { + 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; + } + } + 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); + } + catch (RemoteException ex) { + return; // give up + } + mDisableRecords.add(tok); + } + tok.what = what; + tok.token = token; + tok.pkg = pkg; + } + } + } + + // lock on mDisableRecords + int gatherDisableActionsLocked() { + final int N = mDisableRecords.size(); + // gather the new net flags + int net = 0; + for (int i=0; i<N; i++) { + net |= mDisableRecords.get(i).what; + } + return net; + } + + // ================================================================================ + // Always called from UI thread + // ================================================================================ + + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump StatusBar from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + synchronized (mIcons) { + mIcons.dump(pw); + } + + synchronized (mNotifications) { + int i=0; + pw.println("Notification list:"); + for (Map.Entry<IBinder,StatusBarNotification> e: mNotifications.entrySet()) { + pw.printf(" %2d: %s\n", i, e.getValue().toString()); + i++; + } + } + + synchronized (mDisableRecords) { + final int N = mDisableRecords.size(); + pw.println(" mDisableRecords.size=" + N + + " mDisabled=0x" + Integer.toHexString(mDisabled)); + for (int i=0; i<N; i++) { + DisableRecord tok = mDisableRecords.get(i); + pw.println(" [" + i + "] what=0x" + Integer.toHexString(tok.what) + + " pkg=" + tok.pkg + " token=" + tok.token); + } + } + } + + private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) + || Intent.ACTION_SCREEN_OFF.equals(action)) { + collapse(); + } + /* + else if (Telephony.Intents.SPN_STRINGS_UPDATED_ACTION.equals(action)) { + updateNetworkName(intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_SPN, false), + intent.getStringExtra(Telephony.Intents.EXTRA_SPN), + intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_PLMN, false), + intent.getStringExtra(Telephony.Intents.EXTRA_PLMN)); + } + else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { + updateResources(); + } + */ + } + }; + +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 9d5d035..df69b76 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -17,7 +17,7 @@ package com.android.server; import com.android.server.am.ActivityManagerService; -import com.android.server.status.StatusBarService; +import com.android.internal.app.ShutdownThread; import com.android.internal.os.BinderInternal; import com.android.internal.os.SamplingProfilerIntegration; @@ -35,13 +35,19 @@ import android.content.pm.IPackageManager; import android.database.ContentObserver; import android.database.Cursor; import android.media.AudioService; -import android.os.*; +import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.StrictMode; +import android.os.SystemClock; +import android.os.SystemProperties; import android.provider.Contacts.People; import android.provider.Settings; import android.server.BluetoothA2dpService; import android.server.BluetoothService; import android.server.search.SearchManagerService; import android.util.EventLog; +import android.util.Log; import android.util.Slog; import android.accounts.AccountManagerService; @@ -81,7 +87,26 @@ class ServerThread extends Thread { android.os.Process.THREAD_PRIORITY_FOREGROUND); BinderInternal.disableBackgroundScheduling(true); - + android.os.Process.setCanSelfBackground(false); + + // Check whether we failed to shut down last time we tried. + { + final String shutdownAction = SystemProperties.get( + ShutdownThread.SHUTDOWN_ACTION_PROPERTY, ""); + if (shutdownAction != null && shutdownAction.length() > 0) { + boolean reboot = (shutdownAction.charAt(0) == '1'); + + final String reason; + if (shutdownAction.length() > 1) { + reason = shutdownAction.substring(1, shutdownAction.length()); + } else { + reason = null; + } + + ShutdownThread.rebootOrShutdown(reboot, reason); + } + } + String factoryTestStr = SystemProperties.get("ro.factorytest"); int factoryTest = "".equals(factoryTestStr) ? SystemServer.FACTORY_TEST_OFF : Integer.parseInt(factoryTestStr); @@ -97,6 +122,7 @@ class ServerThread extends Thread { BluetoothA2dpService bluetoothA2dp = null; HeadsetObserver headset = null; DockObserver dock = null; + UsbObserver usb = null; UiModeManagerService uiMode = null; RecognitionManagerService recognition = null; ThrottleService throttle = null; @@ -164,10 +190,6 @@ class ServerThread extends Thread { Watchdog.getInstance().init(context, battery, power, alarm, ActivityManagerService.self()); - // Sensor Service is needed by Window Manager, so this goes first - Slog.i(TAG, "Sensor Service"); - ServiceManager.addService(Context.SENSOR_SERVICE, new SensorService(context)); - Slog.i(TAG, "Window Manager"); wm = WindowManagerService.main(context, power, factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL); @@ -206,7 +228,7 @@ class ServerThread extends Thread { } DevicePolicyManagerService devicePolicy = null; - StatusBarService statusBar = null; + StatusBarManagerService statusBar = null; InputMethodManagerService imm = null; AppWidgetService appWidget = null; NotificationManagerService notification = null; @@ -224,10 +246,10 @@ class ServerThread extends Thread { try { Slog.i(TAG, "Status Bar"); - statusBar = new StatusBarService(context); + statusBar = new StatusBarManagerService(context); ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar); } catch (Throwable e) { - Slog.e(TAG, "Failure starting StatusBarService", e); + Slog.e(TAG, "Failure starting StatusBarManagerService", e); } try { @@ -256,7 +278,8 @@ class ServerThread extends Thread { try { Slog.i(TAG, "NetworkManagement Service"); ServiceManager.addService( - Context.NETWORKMANAGEMENT_SERVICE, new NetworkManagementService(context)); + Context.NETWORKMANAGEMENT_SERVICE, + NetworkManagementService.create(context)); } catch (Throwable e) { Slog.e(TAG, "Failure starting NetworkManagement Service", e); } @@ -374,8 +397,16 @@ class ServerThread extends Thread { } try { + Slog.i(TAG, "USB Observer"); + // Listen for USB changes + usb = new UsbObserver(context); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting UsbObserver", e); + } + + try { Slog.i(TAG, "UI Mode Manager Service"); - // Listen for dock station changes + // Listen for UI mode changes uiMode = new UiModeManagerService(context); } catch (Throwable e) { Slog.e(TAG, "Failure starting UiModeManagerService", e); @@ -403,13 +434,7 @@ class ServerThread extends Thread { } catch (Throwable e) { Slog.e(TAG, "Failure starting Recognition Service", e); } - - try { - com.android.server.status.StatusBarPolicy.installIcons(context, statusBar); - } catch (Throwable e) { - Slog.e(TAG, "Failure installing status bar icons", e); - } - + try { Slog.i(TAG, "DiskStats Service"); ServiceManager.addService("diskstats", new DiskStatsService(context)); @@ -464,9 +489,11 @@ class ServerThread extends Thread { } // These are needed to propagate to the runnable below. + final StatusBarManagerService statusBarF = statusBar; final BatteryService batteryF = battery; final ConnectivityService connectivityF = connectivity; final DockObserver dockF = dock; + final UsbObserver usbF = usb; final ThrottleService throttleF = throttle; final UiModeManagerService uiModeF = uiMode; final AppWidgetService appWidgetF = appWidget; @@ -485,9 +512,11 @@ class ServerThread extends Thread { public void run() { Slog.i(TAG, "Making services ready"); + if (statusBarF != null) statusBarF.systemReady2(); if (batteryF != null) batteryF.systemReady(); if (connectivityF != null) connectivityF.systemReady(); if (dockF != null) dockF.systemReady(); + if (usbF != null) usbF.systemReady(); if (uiModeF != null) uiModeF.systemReady(); if (recognitionF != null) recognitionF.systemReady(); Watchdog.getInstance().start(); @@ -503,6 +532,11 @@ class ServerThread extends Thread { } }); + // For debug builds, log event loop stalls to dropbox for analysis. + if (StrictMode.conditionallyEnableDebugLogging()) { + Slog.i(TAG, "Enabled StrictMode for system server main thread."); + } + Looper.loop(); Slog.d(TAG, "System ServerThread is exiting!"); } @@ -548,6 +582,10 @@ public class SystemServer static Timer timer; static final long SNAPSHOT_INTERVAL = 60 * 60 * 1000; // 1hr + // The earliest supported time. We pick one day into 1970, to + // give any timezone code room without going into negative time. + private static final long EARLIEST_SUPPORTED_TIME = 86400 * 1000; + /** * This method is called from Zygote to initialize the system. This will cause the native * services (SurfaceFlinger, AudioFlinger, etc..) to be started. After that it will call back @@ -556,6 +594,16 @@ public class SystemServer native public static void init1(String[] args); public static void main(String[] args) { + if (System.currentTimeMillis() < EARLIEST_SUPPORTED_TIME) { + // If a device's clock is before 1970 (before 0), a lot of + // APIs crash dealing with negative numbers, notably + // java.io.File#setLastModified, so instead we fake it and + // hope that time from cell towers or NTP fixes it + // shortly. + Slog.w(TAG, "System clock is before 1970; setting to 1970."); + SystemClock.setCurrentTimeMillis(EARLIEST_SUPPORTED_TIME); + } + if (SamplingProfilerIntegration.isEnabled()) { SamplingProfilerIntegration.start(); timer = new Timer(); diff --git a/services/java/com/android/server/TelephonyRegistry.java b/services/java/com/android/server/TelephonyRegistry.java index 664dfa5..3370279 100644 --- a/services/java/com/android/server/TelephonyRegistry.java +++ b/services/java/com/android/server/TelephonyRegistry.java @@ -19,6 +19,7 @@ package com.android.server; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.net.NetworkUtils; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; @@ -253,6 +254,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (!checkNotifyPermission("notifyServiceState()")){ return; } + Slog.i(TAG, "notifyServiceState: " + state); synchronized (mRecords) { mServiceState = state; for (int i = mRecords.size() - 1; i >= 0; i--) { @@ -294,6 +296,7 @@ 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--) { @@ -313,6 +316,7 @@ 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--) { @@ -348,10 +352,14 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifyDataConnection(int state, boolean isDataConnectivityPossible, - String reason, String apn, String[] apnTypes, String interfaceName, int networkType) { + String reason, String apn, String[] apnTypes, String interfaceName, int networkType, + String gateway) { if (!checkNotifyPermission("notifyDataConnection()" )) { return; } + Slog.i(TAG, "notifyDataConnection: state=" + state + " isDataConnectivityPossible=" + + isDataConnectivityPossible + " reason=" + reason + + " interfaceName=" + interfaceName + " networkType=" + networkType); synchronized (mRecords) { mDataConnectionState = state; mDataConnectionPossible = isDataConnectivityPossible; @@ -372,7 +380,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } } broadcastDataConnectionStateChanged(state, isDataConnectivityPossible, reason, apn, - apnTypes, interfaceName); + apnTypes, interfaceName, gateway); } public void notifyDataConnectionFailed(String reason) { @@ -535,7 +543,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private void broadcastDataConnectionStateChanged(int state, boolean isDataConnectivityPossible, - String reason, String apn, String[] apnTypes, String interfaceName) { + String reason, String apn, String[] apnTypes, String interfaceName, String gateway) { // 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. @@ -558,6 +566,12 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } 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); + } + intent.putExtra(Phone.DATA_GATEWAY_KEY, gatewayAddr); + mContext.sendStickyBroadcast(intent); } diff --git a/services/java/com/android/server/UiModeManagerService.java b/services/java/com/android/server/UiModeManagerService.java index 019245f..431cc39 100644 --- a/services/java/com/android/server/UiModeManagerService.java +++ b/services/java/com/android/server/UiModeManagerService.java @@ -566,7 +566,7 @@ class UiModeManagerService extends IUiModeManager.Stub { mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE); } - // Fear not: StatusBarService manages a list of requests to disable + // Fear not: StatusBarManagerService manages a list of requests to disable // features of the status bar; these are ORed together to form the // active disabled list. So if (for example) the device is locked and // the status bar should be totally disabled, the calls below will diff --git a/services/java/com/android/server/UsbObserver.java b/services/java/com/android/server/UsbObserver.java new file mode 100644 index 0000000..d08fe9b --- /dev/null +++ b/services/java/com/android/server/UsbObserver.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.hardware.Usb; +import android.net.Uri; +import android.os.Handler; +import android.os.Message; +import android.os.UEventObserver; +import android.provider.Settings; +import android.util.Log; +import android.util.Slog; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.util.ArrayList; + +/** + * <p>UsbObserver monitors for changes to USB state. + */ +class UsbObserver extends UEventObserver { + private static final String TAG = UsbObserver.class.getSimpleName(); + private static final boolean LOG = false; + + private static final String USB_CONFIGURATION_MATCH = "DEVPATH=/devices/virtual/switch/usb_configuration"; + private static final String USB_FUNCTIONS_MATCH = "DEVPATH=/devices/virtual/usb_composite/"; + private static final String USB_CONFIGURATION_PATH = "/sys/class/switch/usb_configuration/state"; + private static final String USB_COMPOSITE_CLASS_PATH = "/sys/class/usb_composite"; + + private static final int MSG_UPDATE = 0; + + private int mUsbConfig = 0; + private int mPreviousUsbConfig = 0; + + // lists of enabled and disabled USB functions + private final ArrayList<String> mEnabledFunctions = new ArrayList<String>(); + private final ArrayList<String> mDisabledFunctions = new ArrayList<String>(); + + private boolean mSystemReady; + + private final Context mContext; + + private PowerManagerService mPowerManager; + + public UsbObserver(Context context) { + mContext = context; + init(); // set initial status + + startObserving(USB_CONFIGURATION_MATCH); + startObserving(USB_FUNCTIONS_MATCH); + } + + @Override + public void onUEvent(UEventObserver.UEvent event) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Slog.v(TAG, "USB UEVENT: " + event.toString()); + } + + synchronized (this) { + String switchState = event.get("SWITCH_STATE"); + if (switchState != null) { + try { + int newConfig = Integer.parseInt(switchState); + if (newConfig != mUsbConfig) { + mPreviousUsbConfig = mUsbConfig; + mUsbConfig = newConfig; + // trigger an Intent broadcast + if (mSystemReady) { + update(); + } + } + } catch (NumberFormatException e) { + Slog.e(TAG, "Could not parse switch state from event " + event); + } + } else { + String function = event.get("FUNCTION"); + String enabledStr = event.get("ENABLED"); + if (function != null && enabledStr != null) { + // Note: we do not broadcast a change when a function is enabled or disabled. + // We just record the state change for the next broadcast. + boolean enabled = "1".equals(enabledStr); + if (enabled) { + if (!mEnabledFunctions.contains(function)) { + mEnabledFunctions.add(function); + } + mDisabledFunctions.remove(function); + } else { + if (!mDisabledFunctions.contains(function)) { + mDisabledFunctions.add(function); + } + mEnabledFunctions.remove(function); + } + } + } + } + } + private final void init() { + char[] buffer = new char[1024]; + + try { + FileReader file = new FileReader(USB_CONFIGURATION_PATH); + int len = file.read(buffer, 0, 1024); + mPreviousUsbConfig = mUsbConfig = Integer.valueOf((new String(buffer, 0, len)).trim()); + + } catch (FileNotFoundException e) { + Slog.w(TAG, "This kernel does not have USB configuration switch support"); + } catch (Exception e) { + Slog.e(TAG, "" , e); + } + + try { + File[] files = new File(USB_COMPOSITE_CLASS_PATH).listFiles(); + for (int i = 0; i < files.length; i++) { + File file = new File(files[i], "enable"); + FileReader reader = new FileReader(file); + int len = reader.read(buffer, 0, 1024); + int value = Integer.valueOf((new String(buffer, 0, len)).trim()); + String functionName = files[i].getName(); + if (value == 1) { + mEnabledFunctions.add(functionName); + } else { + mDisabledFunctions.add(functionName); + } + } + } catch (FileNotFoundException e) { + Slog.w(TAG, "This kernel does not have USB composite class support"); + } catch (Exception e) { + Slog.e(TAG, "" , e); + } + } + + void systemReady() { + synchronized (this) { + update(); + mSystemReady = true; + } + } + + private final void update() { + mHandler.sendEmptyMessage(MSG_UPDATE); + } + + private final Handler mHandler = new Handler() { + private void addEnabledFunctions(Intent intent) { + // include state of all USB functions in our extras + for (int i = 0; i < mEnabledFunctions.size(); i++) { + intent.putExtra(mEnabledFunctions.get(i), Usb.USB_FUNCTION_ENABLED); + } + for (int i = 0; i < mDisabledFunctions.size(); i++) { + intent.putExtra(mDisabledFunctions.get(i), Usb.USB_FUNCTION_DISABLED); + } + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE: + synchronized (this) { + final ContentResolver cr = mContext.getContentResolver(); + + if (Settings.Secure.getInt(cr, + Settings.Secure.DEVICE_PROVISIONED, 0) == 0) { + Slog.i(TAG, "Device not provisioned, skipping USB broadcast"); + return; + } + // Send an Intent containing connected/disconnected state + // and the enabled/disabled state of all USB functions + Intent intent; + boolean usbConnected = (mUsbConfig != 0); + if (usbConnected) { + intent = new Intent(Usb.ACTION_USB_CONNECTED); + addEnabledFunctions(intent); + } else { + intent = new Intent(Usb.ACTION_USB_DISCONNECTED); + } + mContext.sendBroadcast(intent); + + // send a sticky broadcast for clients interested in both connect and disconnect + intent = new Intent(Usb.ACTION_USB_STATE); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra(Usb.USB_CONNECTED, usbConnected); + addEnabledFunctions(intent); + mContext.sendStickyBroadcast(intent); + } + break; + } + } + }; +} diff --git a/services/java/com/android/server/VibratorService.java b/services/java/com/android/server/VibratorService.java index 2e7e3e1..f0b5955 100755 --- a/services/java/com/android/server/VibratorService.java +++ b/services/java/com/android/server/VibratorService.java @@ -29,6 +29,7 @@ import android.os.RemoteException; import android.os.IBinder; import android.os.Binder; import android.os.SystemClock; +import android.os.WorkSource; import android.util.Slog; import java.util.LinkedList; @@ -39,6 +40,7 @@ public class VibratorService extends IVibratorService.Stub { private final LinkedList<Vibration> mVibrations; private Vibration mCurrentVibration; + private final WorkSource mTmpWorkSource = new WorkSource(); private class Vibration implements IBinder.DeathRecipient { private final IBinder mToken; @@ -46,22 +48,24 @@ public class VibratorService extends IVibratorService.Stub { private final long mStartTime; private final long[] mPattern; private final int mRepeat; + private final int mUid; - Vibration(IBinder token, long millis) { - this(token, millis, null, 0); + Vibration(IBinder token, long millis, int uid) { + this(token, millis, null, 0, uid); } - Vibration(IBinder token, long[] pattern, int repeat) { - this(token, 0, pattern, repeat); + Vibration(IBinder token, long[] pattern, int repeat, int uid) { + this(token, 0, pattern, repeat, uid); } private Vibration(IBinder token, long millis, long[] pattern, - int repeat) { + int repeat, int uid) { mToken = token; mTimeout = millis; mStartTime = SystemClock.uptimeMillis(); mPattern = pattern; mRepeat = repeat; + mUid = uid; } public void binderDied() { @@ -98,7 +102,7 @@ public class VibratorService extends IVibratorService.Stub { mContext = context; PowerManager pm = (PowerManager)context.getSystemService( Context.POWER_SERVICE); - mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*"); mWakeLock.setReferenceCounted(true); mVibrations = new LinkedList<Vibration>(); @@ -113,6 +117,7 @@ public class VibratorService extends IVibratorService.Stub { != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires VIBRATE permission"); } + int uid = Binder.getCallingUid(); // We're running in the system server so we cannot crash. Check for a // timeout of 0 or negative. This will ensure that a vibration has // either a timeout of > 0 or a non-null pattern. @@ -122,7 +127,7 @@ public class VibratorService extends IVibratorService.Stub { // longer than milliseconds. return; } - Vibration vib = new Vibration(token, milliseconds); + Vibration vib = new Vibration(token, milliseconds, uid); synchronized (mVibrations) { removeVibrationLocked(token); doCancelVibrateLocked(); @@ -146,6 +151,7 @@ public class VibratorService extends IVibratorService.Stub { != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires VIBRATE permission"); } + int uid = Binder.getCallingUid(); // so wakelock calls will succeed long identity = Binder.clearCallingIdentity(); try { @@ -165,7 +171,7 @@ public class VibratorService extends IVibratorService.Stub { return; } - Vibration vib = new Vibration(token, pattern, repeat); + Vibration vib = new Vibration(token, pattern, repeat, uid); try { token.linkToDeath(vib, 0); } catch (RemoteException e) { @@ -280,6 +286,8 @@ public class VibratorService extends IVibratorService.Stub { VibrateThread(Vibration vib) { mVibration = vib; + mTmpWorkSource.set(vib.mUid); + mWakeLock.setWorkSource(mTmpWorkSource); mWakeLock.acquire(); } diff --git a/services/java/com/android/server/ViewServer.java b/services/java/com/android/server/ViewServer.java index ae00438..7b5d18a 100644 --- a/services/java/com/android/server/ViewServer.java +++ b/services/java/com/android/server/ViewServer.java @@ -21,6 +21,8 @@ import android.util.Slog; import java.net.ServerSocket; import java.net.Socket; import java.net.InetAddress; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.io.IOException; import java.io.BufferedReader; import java.io.InputStreamReader; @@ -41,11 +43,13 @@ class ViewServer implements Runnable { */ public static final int VIEW_SERVER_DEFAULT_PORT = 4939; + private static final int VIEW_SERVER_MAX_CONNECTIONS = 10; + // Debug facility private static final String LOG_TAG = "ViewServer"; - private static final String VALUE_PROTOCOL_VERSION = "2"; - private static final String VALUE_SERVER_VERSION = "3"; + private static final String VALUE_PROTOCOL_VERSION = "3"; + private static final String VALUE_SERVER_VERSION = "4"; // Protocol commands // Returns the protocol version @@ -54,6 +58,10 @@ class ViewServer implements Runnable { private static final String COMMAND_SERVER_VERSION = "SERVER"; // Lists all of the available windows in the system private static final String COMMAND_WINDOW_MANAGER_LIST = "LIST"; + // Keeps a connection open and notifies when the list of windows changes + private static final String COMMAND_WINDOW_MANAGER_AUTOLIST = "AUTOLIST"; + // Returns the focused window + private static final String COMMAND_WINDOW_MANAGER_GET_FOCUS = "GET_FOCUS"; private ServerSocket mServer; private Thread mThread; @@ -61,6 +69,8 @@ class ViewServer implements Runnable { private final WindowManagerService mWindowManager; private final int mPort; + private ExecutorService mThreadPool; + /** * Creates a new ViewServer associated with the specified window manager. * The server uses the default port {@link #VIEW_SERVER_DEFAULT_PORT}. The server @@ -103,8 +113,9 @@ class ViewServer implements Runnable { return false; } - mServer = new ServerSocket(mPort, 1, InetAddress.getLocalHost()); + mServer = new ServerSocket(mPort, VIEW_SERVER_MAX_CONNECTIONS, InetAddress.getLocalHost()); mThread = new Thread(this, "Remote View Server [port=" + mPort + "]"); + mThreadPool = Executors.newFixedThreadPool(VIEW_SERVER_MAX_CONNECTIONS); mThread.start(); return true; @@ -122,7 +133,16 @@ class ViewServer implements Runnable { */ boolean stop() { if (mThread != null) { + mThread.interrupt(); + if (mThreadPool != null) { + try { + mThreadPool.shutdownNow(); + } catch (SecurityException e) { + Slog.w(LOG_TAG, "Could not stop all view server threads"); + } + } + mThreadPool = null; mThread = null; try { mServer.close(); @@ -152,62 +172,21 @@ class ViewServer implements Runnable { * Main server loop. */ public void run() { - final ServerSocket server = mServer; - while (Thread.currentThread() == mThread) { - Socket client = null; // Any uncaught exception will crash the system process try { - client = server.accept(); - - BufferedReader in = null; - try { - in = new BufferedReader(new InputStreamReader(client.getInputStream()), 1024); - - final String request = in.readLine(); - - String command; - String parameters; - - int index = request.indexOf(' '); - if (index == -1) { - command = request; - parameters = ""; - } else { - command = request.substring(0, index); - parameters = request.substring(index + 1); - } - - boolean result; - if (COMMAND_PROTOCOL_VERSION.equalsIgnoreCase(command)) { - result = writeValue(client, VALUE_PROTOCOL_VERSION); - } else if (COMMAND_SERVER_VERSION.equalsIgnoreCase(command)) { - result = writeValue(client, VALUE_SERVER_VERSION); - } else if (COMMAND_WINDOW_MANAGER_LIST.equalsIgnoreCase(command)) { - result = mWindowManager.viewServerListWindows(client); - } else { - result = mWindowManager.viewServerWindowCommand(client, - command, parameters); - } - - if (!result) { - Slog.w(LOG_TAG, "An error occured with the command: " + command); - } - } finally { - if (in != null) { - in.close(); - } - } - } catch (Exception e) { - Slog.w(LOG_TAG, "Connection error: ", e); - } finally { - if (client != null) { + Socket client = mServer.accept(); + if(mThreadPool != null) { + mThreadPool.submit(new ViewServerWorker(client)); + } else { try { client.close(); } catch (IOException e) { e.printStackTrace(); } } + } catch (Exception e) { + Slog.w(LOG_TAG, "Connection error: ", e); } } } @@ -235,4 +214,133 @@ class ViewServer implements Runnable { } return result; } + + class ViewServerWorker implements Runnable, WindowManagerService.WindowChangeListener { + private Socket mClient; + private boolean mNeedWindowListUpdate; + private boolean mNeedFocusedWindowUpdate; + public ViewServerWorker(Socket client) { + mClient = client; + mNeedWindowListUpdate = false; + mNeedFocusedWindowUpdate = false; + } + + public void run() { + + BufferedReader in = null; + try { + in = new BufferedReader(new InputStreamReader(mClient.getInputStream()), 1024); + + final String request = in.readLine(); + + String command; + String parameters; + + int index = request.indexOf(' '); + if (index == -1) { + command = request; + parameters = ""; + } else { + command = request.substring(0, index); + parameters = request.substring(index + 1); + } + + boolean result; + if (COMMAND_PROTOCOL_VERSION.equalsIgnoreCase(command)) { + result = writeValue(mClient, VALUE_PROTOCOL_VERSION); + } else if (COMMAND_SERVER_VERSION.equalsIgnoreCase(command)) { + result = writeValue(mClient, VALUE_SERVER_VERSION); + } else if (COMMAND_WINDOW_MANAGER_LIST.equalsIgnoreCase(command)) { + result = mWindowManager.viewServerListWindows(mClient); + } else if (COMMAND_WINDOW_MANAGER_GET_FOCUS.equalsIgnoreCase(command)) { + result = mWindowManager.viewServerGetFocusedWindow(mClient); + } else if(COMMAND_WINDOW_MANAGER_AUTOLIST.equalsIgnoreCase(command)) { + result = windowManagerAutolistLoop(); + } else { + result = mWindowManager.viewServerWindowCommand(mClient, + command, parameters); + } + + if (!result) { + Slog.w(LOG_TAG, "An error occured with the command: " + command); + } + } catch(IOException e) { + Slog.w(LOG_TAG, "Connection error: ", e); + } finally { + if (in != null) { + try { + in.close(); + + } catch (IOException e) { + e.printStackTrace(); + } + } + if (mClient != null) { + try { + mClient.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + public void windowsChanged() { + synchronized(this) { + mNeedWindowListUpdate = true; + notifyAll(); + } + } + + public void focusChanged() { + synchronized(this) { + mNeedFocusedWindowUpdate = true; + notifyAll(); + } + } + + private boolean windowManagerAutolistLoop() { + mWindowManager.addWindowChangeListener(this); + BufferedWriter out = null; + try { + out = new BufferedWriter(new OutputStreamWriter(mClient.getOutputStream())); + while (!Thread.interrupted()) { + boolean needWindowListUpdate = false; + boolean needFocusedWindowUpdate = false; + synchronized (this) { + while (!mNeedWindowListUpdate && !mNeedFocusedWindowUpdate) { + wait(); + } + if (mNeedWindowListUpdate) { + mNeedWindowListUpdate = false; + needWindowListUpdate = true; + } + if (mNeedFocusedWindowUpdate) { + mNeedFocusedWindowUpdate = false; + needFocusedWindowUpdate = true; + } + } + if(needWindowListUpdate) { + out.write("LIST UPDATE\n"); + out.flush(); + } + if(needFocusedWindowUpdate) { + out.write("FOCUS UPDATE\n"); + out.flush(); + } + } + } catch (Exception e) { + Slog.w(LOG_TAG, "Connection error: ", e); + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException e) { + } + } + mWindowManager.removeWindowChangeListener(this); + } + return true; + } + } } diff --git a/services/java/com/android/server/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java index 124da4e..859c85c 100644 --- a/services/java/com/android/server/WallpaperManagerService.java +++ b/services/java/com/android/server/WallpaperManagerService.java @@ -35,6 +35,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.os.Binder; import android.os.Bundle; +import android.os.FileUtils; import android.os.IBinder; import android.os.RemoteException; import android.os.FileObserver; @@ -727,9 +728,10 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } } + // Called by SystemBackupAgent after files are restored to disk. void settingsRestored() { if (DEBUG) Slog.v(TAG, "settingsRestored"); - + boolean success = false; synchronized (mLock) { loadSettingsLocked(); @@ -766,7 +768,10 @@ class WallpaperManagerService extends IWallpaperManager.Stub { mName = ""; WALLPAPER_FILE.delete(); } - saveSettingsLocked(); + + synchronized (mLock) { + saveSettingsLocked(); + } } boolean restoreNamedResourceLocked() { @@ -832,6 +837,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } catch (IOException ex) {} } if (fos != null) { + FileUtils.sync(fos); try { fos.close(); } catch (IOException ex) {} diff --git a/services/java/com/android/server/Watchdog.java b/services/java/com/android/server/Watchdog.java index d4133f3..1b885f5 100644 --- a/services/java/com/android/server/Watchdog.java +++ b/services/java/com/android/server/Watchdog.java @@ -39,9 +39,6 @@ import android.util.Log; import android.util.Slog; import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; @@ -57,20 +54,10 @@ public class Watchdog extends Thread { static final boolean RECORD_KERNEL_THREADS = true; static final int MONITOR = 2718; - static final int GLOBAL_PSS = 2719; static final int TIME_TO_RESTART = DB ? 15*1000 : 60*1000; static final int TIME_TO_WAIT = TIME_TO_RESTART / 2; - static final int MEMCHECK_DEFAULT_INTERVAL = DB ? 30 : 30*60; // 30 minutes - static final int MEMCHECK_DEFAULT_LOG_REALTIME_INTERVAL = DB ? 60 : 2*60*60; // 2 hours - static final int MEMCHECK_DEFAULT_SYSTEM_SOFT_THRESHOLD = (DB ? 10:16)*1024*1024; // 16MB - static final int MEMCHECK_DEFAULT_SYSTEM_HARD_THRESHOLD = (DB ? 14:20)*1024*1024; // 20MB - static final int MEMCHECK_DEFAULT_PHONE_SOFT_THRESHOLD = (DB ? 4:8)*1024*1024; // 8MB - static final int MEMCHECK_DEFAULT_PHONE_HARD_THRESHOLD = (DB ? 8:12)*1024*1024; // 12MB - - static final int MEMCHECK_DEFAULT_EXEC_START_TIME = 1*60*60; // 1:00am - static final int MEMCHECK_DEFAULT_EXEC_END_TIME = 5*60*60; // 5:00am static final int MEMCHECK_DEFAULT_MIN_SCREEN_OFF = DB ? 1*60 : 5*60; // 5 minutes static final int MEMCHECK_DEFAULT_MIN_ALARM = DB ? 1*60 : 3*60; // 3 minutes static final int MEMCHECK_DEFAULT_RECHECK_INTERVAL = DB ? 1*60 : 5*60; // 5 minutes @@ -79,14 +66,12 @@ public class Watchdog extends Thread { static final int REBOOT_DEFAULT_START_TIME = 3*60*60; // 3:00am static final int REBOOT_DEFAULT_WINDOW = 60*60; // within 1 hour - static final String CHECKUP_ACTION = "com.android.service.Watchdog.CHECKUP"; static final String REBOOT_ACTION = "com.android.service.Watchdog.REBOOT"; static Watchdog sWatchdog; /* This handler will be used to post message back onto the main thread */ final Handler mHandler; - final Runnable mGlobalPssCollected; final ArrayList<Monitor> mMonitors = new ArrayList<Monitor>(); ContentResolver mResolver; BatteryService mBattery; @@ -97,31 +82,9 @@ public class Watchdog extends Thread { boolean mForceKillSystem; Monitor mCurrentMonitor; - PssRequestor mPhoneReq; int mPhonePid; - int mPhonePss; - - long mLastMemCheckTime = -(MEMCHECK_DEFAULT_INTERVAL*1000); - boolean mHavePss; - long mLastMemCheckRealtime = -(MEMCHECK_DEFAULT_LOG_REALTIME_INTERVAL*1000); - boolean mHaveGlobalPss; - final MemMonitor mSystemMemMonitor = new MemMonitor("system", - Settings.Secure.MEMCHECK_SYSTEM_ENABLED, - Settings.Secure.MEMCHECK_SYSTEM_SOFT_THRESHOLD, - MEMCHECK_DEFAULT_SYSTEM_SOFT_THRESHOLD, - Settings.Secure.MEMCHECK_SYSTEM_HARD_THRESHOLD, - MEMCHECK_DEFAULT_SYSTEM_HARD_THRESHOLD); - final MemMonitor mPhoneMemMonitor = new MemMonitor("com.android.phone", - Settings.Secure.MEMCHECK_PHONE_ENABLED, - Settings.Secure.MEMCHECK_PHONE_SOFT_THRESHOLD, - MEMCHECK_DEFAULT_PHONE_SOFT_THRESHOLD, - Settings.Secure.MEMCHECK_PHONE_HARD_THRESHOLD, - MEMCHECK_DEFAULT_PHONE_HARD_THRESHOLD); final Calendar mCalendar = Calendar.getInstance(); - long mMemcheckLastTime; - long mMemcheckExecStartTime; - long mMemcheckExecEndTime; int mMinScreenOff = MEMCHECK_DEFAULT_MIN_SCREEN_OFF; int mMinAlarm = MEMCHECK_DEFAULT_MIN_ALARM; boolean mNeedScheduledCheck; @@ -140,128 +103,13 @@ public class Watchdog extends Thread { int mReqRecheckInterval= -1; // >= 0 if a specific recheck interval has been requested /** - * This class monitors the memory in a particular process. - */ - final class MemMonitor { - final String mProcessName; - final String mEnabledSetting; - final String mSoftSetting; - final String mHardSetting; - - int mSoftThreshold; - int mHardThreshold; - boolean mEnabled; - long mLastPss; - - static final int STATE_OK = 0; - static final int STATE_SOFT = 1; - static final int STATE_HARD = 2; - int mState; - - MemMonitor(String processName, String enabledSetting, - String softSetting, int defSoftThreshold, - String hardSetting, int defHardThreshold) { - mProcessName = processName; - mEnabledSetting = enabledSetting; - mSoftSetting = softSetting; - mHardSetting = hardSetting; - mSoftThreshold = defSoftThreshold; - mHardThreshold = defHardThreshold; - } - - void retrieveSettings(ContentResolver resolver) { - mSoftThreshold = Settings.Secure.getInt( - resolver, mSoftSetting, mSoftThreshold); - mHardThreshold = Settings.Secure.getInt( - resolver, mHardSetting, mHardThreshold); - mEnabled = Settings.Secure.getInt( - resolver, mEnabledSetting, 0) != 0; - } - - boolean checkLocked(long curTime, int pid, int pss) { - mLastPss = pss; - if (mLastPss < mSoftThreshold) { - mState = STATE_OK; - } else if (mLastPss < mHardThreshold) { - mState = STATE_SOFT; - } else { - mState = STATE_HARD; - } - EventLog.writeEvent(EventLogTags.WATCHDOG_PROC_PSS, mProcessName, pid, mLastPss); - - if (mState == STATE_OK) { - // Memory is good, don't recover. - return false; - } - - if (mState == STATE_HARD) { - // Memory is really bad, kill right now. - EventLog.writeEvent(EventLogTags.WATCHDOG_HARD_RESET, mProcessName, pid, - mHardThreshold, mLastPss); - return mEnabled; - } - - // It is time to schedule a reset... - // Check if we are currently within the time to kill processes due - // to memory use. - computeMemcheckTimesLocked(curTime); - String skipReason = null; - if (curTime < mMemcheckExecStartTime || curTime > mMemcheckExecEndTime) { - skipReason = "time"; - } else { - skipReason = shouldWeBeBrutalLocked(curTime); - } - EventLog.writeEvent(EventLogTags.WATCHDOG_SOFT_RESET, mProcessName, pid, - mSoftThreshold, mLastPss, skipReason != null ? skipReason : ""); - if (skipReason != null) { - mNeedScheduledCheck = true; - return false; - } - return mEnabled; - } - - void clear() { - mLastPss = 0; - mState = STATE_OK; - } - } - - /** * Used for scheduling monitor callbacks and checking memory usage. */ final class HeartbeatHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { - case GLOBAL_PSS: { - if (mHaveGlobalPss) { - // During the last pass we collected pss information, so - // now it is time to report it. - mHaveGlobalPss = false; - if (localLOGV) Slog.v(TAG, "Received global pss, logging."); - logGlobalMemory(); - } - } break; - case MONITOR: { - if (mHavePss) { - // During the last pass we collected pss information, so - // now it is time to report it. - mHavePss = false; - if (localLOGV) Slog.v(TAG, "Have pss, checking memory."); - checkMemory(); - } - - if (mHaveGlobalPss) { - // During the last pass we collected pss information, so - // now it is time to report it. - mHaveGlobalPss = false; - if (localLOGV) Slog.v(TAG, "Have global pss, logging."); - logGlobalMemory(); - } - - long now = SystemClock.uptimeMillis(); - // See if we should force a reboot. int rebootInterval = mReqRebootInterval >= 0 ? mReqRebootInterval : Settings.Secure.getInt( @@ -274,32 +122,6 @@ public class Watchdog extends Thread { checkReboot(false); } - // See if we should check memory conditions. - long memCheckInterval = Settings.Secure.getLong( - mResolver, Settings.Secure.MEMCHECK_INTERVAL, - MEMCHECK_DEFAULT_INTERVAL) * 1000; - if ((mLastMemCheckTime+memCheckInterval) < now) { - // It is now time to collect pss information. This - // is async so we won't report it now. And to keep - // things simple, we will assume that everyone has - // reported back by the next MONITOR message. - mLastMemCheckTime = now; - if (localLOGV) Slog.v(TAG, "Collecting memory usage."); - collectMemory(); - mHavePss = true; - - long memCheckRealtimeInterval = Settings.Secure.getLong( - mResolver, Settings.Secure.MEMCHECK_LOG_REALTIME_INTERVAL, - MEMCHECK_DEFAULT_LOG_REALTIME_INTERVAL) * 1000; - long realtimeNow = SystemClock.elapsedRealtime(); - if ((mLastMemCheckRealtime+memCheckRealtimeInterval) < realtimeNow) { - mLastMemCheckRealtime = realtimeNow; - if (localLOGV) Slog.v(TAG, "Collecting global memory usage."); - collectGlobalMemory(); - mHaveGlobalPss = true; - } - } - final int size = mMonitors.size(); for (int i = 0 ; i < size ; i++) { mCurrentMonitor = mMonitors.get(i); @@ -315,20 +137,6 @@ public class Watchdog extends Thread { } } - final class GlobalPssCollected implements Runnable { - public void run() { - mHandler.sendEmptyMessage(GLOBAL_PSS); - } - } - - final class CheckupReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context c, Intent intent) { - if (localLOGV) Slog.v(TAG, "Alarm went off, checking memory."); - checkMemory(); - } - } - final class RebootReceiver extends BroadcastReceiver { @Override public void onReceive(Context c, Intent intent) { @@ -359,27 +167,6 @@ public class Watchdog extends Thread { void monitor(); } - public interface PssRequestor { - void requestPss(); - } - - public class PssStats { - public int mEmptyPss; - public int mEmptyCount; - public int mBackgroundPss; - public int mBackgroundCount; - public int mServicePss; - public int mServiceCount; - public int mVisiblePss; - public int mVisibleCount; - public int mForegroundPss; - public int mForegroundCount; - - public int mNoPssCount; - - public int mProcDeaths[] = new int[10]; - } - public static Watchdog getInstance() { if (sWatchdog == null) { sWatchdog = new Watchdog(); @@ -391,7 +178,6 @@ public class Watchdog extends Thread { private Watchdog() { super("watchdog"); mHandler = new HeartbeatHandler(); - mGlobalPssCollected = new GlobalPssCollected(); } public void init(Context context, BatteryService battery, @@ -403,11 +189,6 @@ public class Watchdog extends Thread { mAlarm = alarm; mActivity = activity; - context.registerReceiver(new CheckupReceiver(), - new IntentFilter(CHECKUP_ACTION)); - mCheckupIntent = PendingIntent.getBroadcast(context, - 0, new Intent(CHECKUP_ACTION), 0); - context.registerReceiver(new RebootReceiver(), new IntentFilter(REBOOT_ACTION)); mRebootIntent = PendingIntent.getBroadcast(context, @@ -420,20 +201,10 @@ public class Watchdog extends Thread { mBootTime = System.currentTimeMillis(); } - public void processStarted(PssRequestor req, String name, int pid) { + public void processStarted(String name, int pid) { synchronized (this) { if ("com.android.phone".equals(name)) { - mPhoneReq = req; mPhonePid = pid; - mPhonePss = 0; - } - } - } - - public void reportPss(PssRequestor req, String name, int pss) { - synchronized (this) { - if (mPhoneReq == req) { - mPhonePss = pss; } } } @@ -447,152 +218,6 @@ public class Watchdog extends Thread { } } - /** - * Retrieve memory usage information from specific processes being - * monitored. This is an async operation, so must be done before doing - * memory checks. - */ - void collectMemory() { - synchronized (this) { - if (mPhoneReq != null) { - mPhoneReq.requestPss(); - } - } - } - - /** - * Retrieve memory usage over all application processes. This is an - * async operation, so must be done before doing memory checks. - */ - void collectGlobalMemory() { - mActivity.requestPss(mGlobalPssCollected); - } - - /** - * Check memory usage in the system, scheduling kills/reboots as needed. - * This always runs on the mHandler thread. - */ - void checkMemory() { - boolean needScheduledCheck; - long curTime; - long nextTime = 0; - - long recheckInterval = Settings.Secure.getLong( - mResolver, Settings.Secure.MEMCHECK_RECHECK_INTERVAL, - MEMCHECK_DEFAULT_RECHECK_INTERVAL) * 1000; - - mSystemMemMonitor.retrieveSettings(mResolver); - mPhoneMemMonitor.retrieveSettings(mResolver); - retrieveBrutalityAmount(); - - synchronized (this) { - curTime = System.currentTimeMillis(); - mNeedScheduledCheck = false; - - // How is the system doing? - if (mSystemMemMonitor.checkLocked(curTime, Process.myPid(), - (int)Process.getPss(Process.myPid()))) { - // Not good! Time to suicide. - mForceKillSystem = true; - notifyAll(); - return; - } - - // How is the phone process doing? - if (mPhoneReq != null) { - if (mPhoneMemMonitor.checkLocked(curTime, mPhonePid, - mPhonePss)) { - // Just kill the phone process and let it restart. - Slog.i(TAG, "Watchdog is killing the phone process"); - Process.killProcess(mPhonePid); - } - } else { - mPhoneMemMonitor.clear(); - } - - needScheduledCheck = mNeedScheduledCheck; - if (needScheduledCheck) { - // Something is going bad, but now is not a good time to - // tear things down... schedule an alarm to check again soon. - nextTime = curTime + recheckInterval; - if (nextTime < mMemcheckExecStartTime) { - nextTime = mMemcheckExecStartTime; - } else if (nextTime >= mMemcheckExecEndTime){ - // Need to check during next exec time... so that needs - // to be computed. - if (localLOGV) Slog.v(TAG, "Computing next time range"); - computeMemcheckTimesLocked(nextTime); - nextTime = mMemcheckExecStartTime; - } - - if (localLOGV) { - mCalendar.setTimeInMillis(nextTime); - Slog.v(TAG, "Next Alarm Time: " + mCalendar); - } - } - } - - if (needScheduledCheck) { - if (localLOGV) Slog.v(TAG, "Scheduling next memcheck alarm for " - + ((nextTime-curTime)/1000/60) + "m from now"); - mAlarm.remove(mCheckupIntent); - mAlarm.set(AlarmManager.RTC_WAKEUP, nextTime, mCheckupIntent); - } else { - if (localLOGV) Slog.v(TAG, "No need to schedule a memcheck alarm!"); - mAlarm.remove(mCheckupIntent); - } - } - - final PssStats mPssStats = new PssStats(); - final String[] mMemInfoFields = new String[] { - "MemFree:", "Buffers:", "Cached:", - "Active:", "Inactive:", - "AnonPages:", "Mapped:", "Slab:", - "SReclaimable:", "SUnreclaim:", "PageTables:" }; - final long[] mMemInfoSizes = new long[mMemInfoFields.length]; - final String[] mVMStatFields = new String[] { - "pgfree ", "pgactivate ", "pgdeactivate ", - "pgfault ", "pgmajfault " }; - final long[] mVMStatSizes = new long[mVMStatFields.length]; - final long[] mPrevVMStatSizes = new long[mVMStatFields.length]; - long mLastLogGlobalMemoryTime; - - void logGlobalMemory() { - PssStats stats = mPssStats; - mActivity.collectPss(stats); - EventLog.writeEvent(EventLogTags.WATCHDOG_PSS_STATS, - stats.mEmptyPss, stats.mEmptyCount, - stats.mBackgroundPss, stats.mBackgroundCount, - stats.mServicePss, stats.mServiceCount, - stats.mVisiblePss, stats.mVisibleCount, - stats.mForegroundPss, stats.mForegroundCount, - stats.mNoPssCount); - EventLog.writeEvent(EventLogTags.WATCHDOG_PROC_STATS, - stats.mProcDeaths[0], stats.mProcDeaths[1], stats.mProcDeaths[2], - stats.mProcDeaths[3], stats.mProcDeaths[4]); - Process.readProcLines("/proc/meminfo", mMemInfoFields, mMemInfoSizes); - for (int i=0; i<mMemInfoSizes.length; i++) { - mMemInfoSizes[i] *= 1024; - } - EventLog.writeEvent(EventLogTags.WATCHDOG_MEMINFO, - (int)mMemInfoSizes[0], (int)mMemInfoSizes[1], (int)mMemInfoSizes[2], - (int)mMemInfoSizes[3], (int)mMemInfoSizes[4], - (int)mMemInfoSizes[5], (int)mMemInfoSizes[6], (int)mMemInfoSizes[7], - (int)mMemInfoSizes[8], (int)mMemInfoSizes[9], (int)mMemInfoSizes[10]); - long now = SystemClock.uptimeMillis(); - long dur = now - mLastLogGlobalMemoryTime; - mLastLogGlobalMemoryTime = now; - Process.readProcLines("/proc/vmstat", mVMStatFields, mVMStatSizes); - for (int i=0; i<mVMStatSizes.length; i++) { - long v = mVMStatSizes[i]; - mVMStatSizes[i] -= mPrevVMStatSizes[i]; - mPrevVMStatSizes[i] = v; - } - EventLog.writeEvent(EventLogTags.WATCHDOG_VMSTAT, dur, - (int)mVMStatSizes[0], (int)mVMStatSizes[1], (int)mVMStatSizes[2], - (int)mVMStatSizes[3], (int)mVMStatSizes[4]); - } - void checkReboot(boolean fromAlarm) { int rebootInterval = mReqRebootInterval >= 0 ? mReqRebootInterval : Settings.Secure.getInt( @@ -730,47 +355,6 @@ public class Watchdog extends Thread { return null; } - /** - * Compute the times during which we next would like to perform process - * restarts. - * - * @param curTime The current system time. - */ - void computeMemcheckTimesLocked(long curTime) { - if (mMemcheckLastTime == curTime) { - return; - } - - mMemcheckLastTime = curTime; - - long memcheckExecStartTime = Settings.Secure.getLong( - mResolver, Settings.Secure.MEMCHECK_EXEC_START_TIME, - MEMCHECK_DEFAULT_EXEC_START_TIME); - long memcheckExecEndTime = Settings.Secure.getLong( - mResolver, Settings.Secure.MEMCHECK_EXEC_END_TIME, - MEMCHECK_DEFAULT_EXEC_END_TIME); - - mMemcheckExecEndTime = computeCalendarTime(mCalendar, curTime, - memcheckExecEndTime); - if (mMemcheckExecEndTime < curTime) { - memcheckExecStartTime += 24*60*60; - memcheckExecEndTime += 24*60*60; - mMemcheckExecEndTime = computeCalendarTime(mCalendar, curTime, - memcheckExecEndTime); - } - mMemcheckExecStartTime = computeCalendarTime(mCalendar, curTime, - memcheckExecStartTime); - - if (localLOGV) { - mCalendar.setTimeInMillis(curTime); - Slog.v(TAG, "Current Time: " + mCalendar); - mCalendar.setTimeInMillis(mMemcheckExecStartTime); - Slog.v(TAG, "Start Check Time: " + mCalendar); - mCalendar.setTimeInMillis(mMemcheckExecEndTime); - Slog.v(TAG, "End Check Time: " + mCalendar); - } - } - static long computeCalendarTime(Calendar c, long curTime, long secondsSinceMidnight) { @@ -829,9 +413,9 @@ public class Watchdog extends Thread { if (!waitedHalf) { // We've waited half the deadlock-detection interval. Pull a stack // trace and wait another half. - ArrayList pids = new ArrayList(); + ArrayList<Integer> pids = new ArrayList<Integer>(); pids.add(Process.myPid()); - File stack = ActivityManagerService.dumpStackTraces(true, pids); + ActivityManagerService.dumpStackTraces(true, pids, null, null); waitedHalf = true; continue; } @@ -841,15 +425,16 @@ public class Watchdog extends Thread { // First collect stack traces from all threads of the system process. // Then kill this process so that the system will restart. - String name = (mCurrentMonitor != null) ? mCurrentMonitor.getClass().getName() : "null"; + String name = (mCurrentMonitor != null) ? + mCurrentMonitor.getClass().getName() : "null"; EventLog.writeEvent(EventLogTags.WATCHDOG, name); - ArrayList pids = new ArrayList(); + ArrayList<Integer> pids = new ArrayList<Integer>(); pids.add(Process.myPid()); if (mPhonePid > 0) pids.add(mPhonePid); // Pass !waitedHalf so that just in case we somehow wind up here without having // dumped the halfway stacks, we properly re-initialize the trace file. - File stack = ActivityManagerService.dumpStackTraces(!waitedHalf, pids); + File stack = ActivityManagerService.dumpStackTraces(!waitedHalf, pids, null, null); // Give some extra time to make sure the stack traces get written. // The system's been hanging for a minute, another second or two won't hurt much. diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index 8d6ad93..c1e9965 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -63,6 +63,7 @@ import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.WorkSource; import android.provider.Settings; import android.util.Slog; import android.text.TextUtils; @@ -116,6 +117,8 @@ public class WifiService extends IWifiManager.Stub { private final LockList mLocks = new LockList(); // some wifi lock statistics + private int mFullHighPerfLocksAcquired; + private int mFullHighPerfLocksReleased; private int mFullLocksAcquired; private int mFullLocksReleased; private int mScanLocksAcquired; @@ -142,7 +145,7 @@ public class WifiService extends IWifiManager.Stub { */ private static final long DEFAULT_IDLE_MILLIS = 15 * 60 * 1000; /* 15 minutes */ - private static final String WAKELOCK_TAG = "WifiService"; + private static final String WAKELOCK_TAG = "*wifi*"; /** * The maximum amount of time to hold the wake lock after a disconnect @@ -169,6 +172,8 @@ public class WifiService extends IWifiManager.Stub { 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 final WifiHandler mWifiHandler; @@ -185,6 +190,12 @@ public class WifiService extends IWifiManager.Stub { private static final int SCAN_RESULT_BUFFER_SIZE = 512; private boolean mNeedReconfig; + /** + * Temporary for computing UIDS that are responsible for starting WIFI. + * Protected by mWifiStateTracker lock. + */ + private final WorkSource mTmpWorkSource = new WorkSource(); + /* * Last UID that asked to enable WIFI. */ @@ -375,23 +386,12 @@ public class WifiService extends IWifiManager.Stub { /** * see {@link android.net.wifi.WifiManager#startScan()} - * @return {@code true} if the operation succeeds */ - public boolean startScan(boolean forceActive) { + public void startScan(boolean forceActive) { enforceChangePermission(); + if (mWifiHandler == null) return; - switch (mWifiStateTracker.getSupplicantState()) { - case DISCONNECTED: - case INACTIVE: - case SCANNING: - case DORMANT: - break; - default: - mWifiStateTracker.setScanResultHandling( - WifiStateTracker.SUPPL_SCAN_HANDLING_LIST_ONLY); - break; - } - return mWifiStateTracker.scan(forceActive); + Message.obtain(mWifiHandler, MESSAGE_START_SCAN, forceActive ? 1 : 0, 0).sendToTarget(); } /** @@ -525,9 +525,9 @@ public class WifiService extends IWifiManager.Stub { long ident = Binder.clearCallingIdentity(); try { if (wifiState == WIFI_STATE_ENABLED) { - mBatteryStats.noteWifiOn(uid); + mBatteryStats.noteWifiOn(); } else if (wifiState == WIFI_STATE_DISABLED) { - mBatteryStats.noteWifiOff(uid); + mBatteryStats.noteWifiOff(); } } catch (RemoteException e) { } finally { @@ -631,6 +631,7 @@ public class WifiService extends IWifiManager.Stub { } public WifiConfiguration getWifiApConfiguration() { + enforceAccessPermission(); final ContentResolver cr = mContext.getContentResolver(); WifiConfiguration wifiConfig = new WifiConfiguration(); int authType; @@ -648,7 +649,8 @@ public class WifiService extends IWifiManager.Stub { } } - private void persistApConfiguration(WifiConfiguration wifiConfig) { + public void setWifiApConfiguration(WifiConfiguration wifiConfig) { + enforceChangePermission(); final ContentResolver cr = mContext.getContentResolver(); boolean isWpa; if (wifiConfig == null) @@ -679,9 +681,9 @@ public class WifiService extends IWifiManager.Stub { /* Configuration changed on a running access point */ if(enable && (wifiConfig != null)) { try { - persistApConfiguration(wifiConfig); nwService.setAccessPoint(wifiConfig, mWifiStateTracker.getInterfaceName(), SOFTAP_IFACE); + setWifiApConfiguration(wifiConfig); return true; } catch(Exception e) { Slog.e(TAG, "Exception in nwService during AP restart"); @@ -717,7 +719,6 @@ public class WifiService extends IWifiManager.Stub { wifiConfig.SSID = mContext.getString(R.string.wifi_tether_configure_ssid_default); wifiConfig.allowedKeyManagement.set(KeyMgmt.NONE); } - persistApConfiguration(wifiConfig); if (!mWifiStateTracker.loadDriver()) { Slog.e(TAG, "Failed to load Wi-Fi driver for AP mode"); @@ -734,6 +735,8 @@ public class WifiService extends IWifiManager.Stub { return false; } + setWifiApConfiguration(wifiConfig); + } else { try { @@ -781,9 +784,9 @@ public class WifiService extends IWifiManager.Stub { long ident = Binder.clearCallingIdentity(); try { if (wifiAPState == WIFI_AP_STATE_ENABLED) { - mBatteryStats.noteWifiOn(uid); + mBatteryStats.noteWifiOn(); } else if (wifiAPState == WIFI_AP_STATE_DISABLED) { - mBatteryStats.noteWifiOff(uid); + mBatteryStats.noteWifiOff(); } } catch (RemoteException e) { } finally { @@ -1653,13 +1656,26 @@ public class WifiService extends IWifiManager.Stub { Settings.System.getInt(mContext.getContentResolver(), Settings.System.STAY_ON_WHILE_PLUGGED_IN, 0); if (action.equals(Intent.ACTION_SCREEN_ON)) { - Slog.d(TAG, "ACTION_SCREEN_ON"); + if (DBG) { + Slog.d(TAG, "ACTION_SCREEN_ON"); + } mAlarmManager.cancel(mIdleIntent); mDeviceIdle = false; mScreenOff = false; + // Once the screen is on, we are not keeping WIFI running + // because of any locks so clear that tracking immediately. + reportStartWorkSource(); mWifiStateTracker.enableRssiPolling(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(); + } } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { - Slog.d(TAG, "ACTION_SCREEN_OFF"); + if (DBG) { + Slog.d(TAG, "ACTION_SCREEN_OFF"); + } mScreenOff = true; mWifiStateTracker.enableRssiPolling(false); /* @@ -1676,22 +1692,30 @@ public class WifiService extends IWifiManager.Stub { // 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 - Slog.d(TAG, "setting ACTION_DEVICE_IDLE timer for 120,000 ms"); + if (DBG) { + Slog.d(TAG, "setting ACTION_DEVICE_IDLE timer for 120,000 ms"); + } mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent); // // do not keep Wifi awake when screen is off if Wifi is not associated // mDeviceIdle = true; // updateWifiState(); } else { long triggerTime = System.currentTimeMillis() + idleMillis; - Slog.d(TAG, "setting ACTION_DEVICE_IDLE timer for " + idleMillis + "ms"); + if (DBG) { + Slog.d(TAG, "setting ACTION_DEVICE_IDLE timer for " + idleMillis + + "ms"); + } 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)) { - Slog.d(TAG, "got ACTION_DEVICE_IDLE"); + if (DBG) { + Slog.d(TAG, "got ACTION_DEVICE_IDLE"); + } mDeviceIdle = true; + reportStartWorkSource(); } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { /* * Set a timer to put Wi-Fi to sleep, but only if the screen is off @@ -1701,11 +1725,15 @@ public class WifiService extends IWifiManager.Stub { * the already-set timer. */ int pluggedType = intent.getIntExtra("plugged", 0); - Slog.d(TAG, "ACTION_BATTERY_CHANGED pluggedType: " + pluggedType); + if (DBG) { + Slog.d(TAG, "ACTION_BATTERY_CHANGED pluggedType: " + pluggedType); + } if (mScreenOff && shouldWifiStayAwake(stayAwakeConditions, mPluggedType) && !shouldWifiStayAwake(stayAwakeConditions, pluggedType)) { long triggerTime = System.currentTimeMillis() + idleMillis; - Slog.d(TAG, "setting ACTION_DEVICE_IDLE timer for " + idleMillis + "ms"); + if (DBG) { + Slog.d(TAG, "setting ACTION_DEVICE_IDLE timer for " + idleMillis + "ms"); + } mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent); mPluggedType = pluggedType; return; @@ -1779,8 +1807,8 @@ public class WifiService extends IWifiManager.Stub { msg.sendToTarget(); } - private void sendStartMessage(boolean scanOnlyMode) { - Message.obtain(mWifiHandler, MESSAGE_START_WIFI, scanOnlyMode ? 1 : 0, 0).sendToTarget(); + private void sendStartMessage(int lockMode) { + Message.obtain(mWifiHandler, MESSAGE_START_WIFI, lockMode, 0).sendToTarget(); } private void sendAccessPointMessage(boolean enable, WifiConfiguration wifiConfig, int uid) { @@ -1789,6 +1817,23 @@ public class WifiService extends IWifiManager.Stub { uid, 0, wifiConfig).sendToTarget(); } + private void sendEnableNetworksMessage() { + Message.obtain(mWifiHandler, MESSAGE_ENABLE_NETWORKS).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); + } + } + mWifiStateTracker.updateBatteryWorkSourceLocked(mTmpWorkSource); + sWakeLock.setWorkSource(mTmpWorkSource); + } + } + private void updateWifiState() { // send a message so it's all serialized Message.obtain(mWifiHandler, MESSAGE_UPDATE_STATE, 0, 0).sendToTarget(); @@ -1797,13 +1842,21 @@ public class WifiService extends IWifiManager.Stub { private void doUpdateWifiState() { boolean wifiEnabled = getPersistedWifiEnabled(); boolean airplaneMode = isAirplaneModeOn() && !mAirplaneModeOverwridden; - boolean lockHeld = mLocks.hasLocks(); - int strongestLockMode; + + boolean lockHeld; + synchronized (mLocks) { + lockHeld = mLocks.hasLocks(); + } + + int strongestLockMode = WifiManager.WIFI_MODE_FULL; boolean wifiShouldBeEnabled = wifiEnabled && !airplaneMode; boolean wifiShouldBeStarted = !mDeviceIdle || lockHeld; - if (mDeviceIdle && lockHeld) { + + if (lockHeld) { strongestLockMode = mLocks.getStrongestLockMode(); - } else { + } + /* If device is not idle, lockmode cannot be scan only */ + if (!mDeviceIdle && strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY) { strongestLockMode = WifiManager.WIFI_MODE_FULL; } @@ -1824,7 +1877,7 @@ public class WifiService extends IWifiManager.Stub { sWakeLock.acquire(); sendEnableMessage(true, false, mLastEnableUid); sWakeLock.acquire(); - sendStartMessage(strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY); + sendStartMessage(strongestLockMode); } else if (!mWifiStateTracker.isDriverStopped()) { int wakeLockTimeout = Settings.Secure.getInt( @@ -1902,8 +1955,11 @@ public class WifiService extends IWifiManager.Stub { break; case MESSAGE_START_WIFI: - mWifiStateTracker.setScanOnlyMode(msg.arg1 != 0); + 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; @@ -1946,6 +2002,25 @@ public class WifiService extends IWifiManager.Stub { 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; } } } @@ -1983,8 +2058,10 @@ public class WifiService extends IWifiManager.Stub { } pw.println(); pw.println("Locks acquired: " + mFullLocksAcquired + " full, " + + mFullHighPerfLocksAcquired + " full high perf, " + mScanLocksAcquired + " scan"); pw.println("Locks released: " + mFullLocksReleased + " full, " + + mFullHighPerfLocksReleased + " full high perf, " + mScanLocksReleased + " scan"); pw.println(); pw.println("Locks held:"); @@ -2009,8 +2086,8 @@ public class WifiService extends IWifiManager.Stub { } private class WifiLock extends DeathRecipient { - WifiLock(int lockMode, String tag, IBinder binder) { - super(lockMode, tag, binder); + WifiLock(int lockMode, String tag, IBinder binder, WorkSource ws) { + super(lockMode, tag, binder, ws); } public void binderDied() { @@ -2039,11 +2116,15 @@ public class WifiService extends IWifiManager.Stub { if (mList.isEmpty()) { return WifiManager.WIFI_MODE_FULL; } - for (WifiLock l : mList) { - if (l.mMode == WifiManager.WIFI_MODE_FULL) { - return WifiManager.WIFI_MODE_FULL; - } + + if (mFullHighPerfLocksAcquired > mFullHighPerfLocksReleased) { + return WifiManager.WIFI_MODE_FULL_HIGH_PERF; } + + if (mFullLocksAcquired > mFullLocksReleased) { + return WifiManager.WIFI_MODE_FULL; + } + return WifiManager.WIFI_MODE_SCAN_ONLY; } @@ -2080,42 +2161,126 @@ public class WifiService extends IWifiManager.Stub { } } - public boolean acquireWifiLock(IBinder binder, int lockMode, String tag) { + void enforceWakeSourcePermission(int uid, int pid) { + if (uid == Process.myUid()) { + return; + } + mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS, + pid, uid, null); + } + + public boolean acquireWifiLock(IBinder binder, int lockMode, String tag, WorkSource ws) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null); - if (lockMode != WifiManager.WIFI_MODE_FULL && lockMode != WifiManager.WIFI_MODE_SCAN_ONLY) { + if (lockMode != WifiManager.WIFI_MODE_FULL && + lockMode != WifiManager.WIFI_MODE_SCAN_ONLY && + lockMode != WifiManager.WIFI_MODE_FULL_HIGH_PERF) { + Slog.e(TAG, "Illegal argument, lockMode= " + lockMode); + if (DBG) throw new IllegalArgumentException("lockMode=" + lockMode); return false; } - WifiLock wifiLock = new WifiLock(lockMode, tag, binder); + if (ws != null && ws.size() == 0) { + ws = null; + } + if (ws != null) { + enforceWakeSourcePermission(Binder.getCallingUid(), Binder.getCallingPid()); + } + if (ws == null) { + ws = new WorkSource(Binder.getCallingUid()); + } + WifiLock wifiLock = new WifiLock(lockMode, tag, binder, ws); synchronized (mLocks) { return acquireWifiLockLocked(wifiLock); } } + 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: + mBatteryStats.noteScanWifiLockAcquiredFromSource(wifiLock.mWorkSource); + break; + } + } + + 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: + mBatteryStats.noteScanWifiLockReleasedFromSource(wifiLock.mWorkSource); + break; + } + } + private boolean acquireWifiLockLocked(WifiLock wifiLock) { - Slog.d(TAG, "acquireWifiLockLocked: " + wifiLock); + if (DBG) Slog.d(TAG, "acquireWifiLockLocked: " + wifiLock); mLocks.addLock(wifiLock); - int uid = Binder.getCallingUid(); long ident = Binder.clearCallingIdentity(); try { + noteAcquireWifiLock(wifiLock); switch(wifiLock.mMode) { case WifiManager.WIFI_MODE_FULL: ++mFullLocksAcquired; - mBatteryStats.noteFullWifiLockAcquired(uid); + break; + case WifiManager.WIFI_MODE_FULL_HIGH_PERF: + ++mFullHighPerfLocksAcquired; break; case WifiManager.WIFI_MODE_SCAN_ONLY: ++mScanLocksAcquired; - mBatteryStats.noteScanWifiLockAcquired(uid); break; } + + // Be aggressive about adding new locks into the accounted state... + // we want to over-report rather than under-report. + reportStartWorkSource(); + + updateWifiState(); + return true; } catch (RemoteException e) { + return false; } finally { Binder.restoreCallingIdentity(ident); } + } - updateWifiState(); - return true; + public void updateWifiLockWorkSource(IBinder lock, WorkSource ws) { + int uid = Binder.getCallingUid(); + int pid = Binder.getCallingPid(); + if (ws != null && ws.size() == 0) { + ws = null; + } + if (ws != null) { + enforceWakeSourcePermission(uid, pid); + } + long ident = Binder.clearCallingIdentity(); + try { + synchronized (mLocks) { + int index = mLocks.findLockByBinder(lock); + if (index < 0) { + throw new IllegalArgumentException("Wifi lock not active"); + } + WifiLock wl = mLocks.mList.get(index); + noteReleaseWifiLock(wl); + wl.mWorkSource = ws != null ? new WorkSource(ws) : new WorkSource(uid); + noteAcquireWifiLock(wl); + } + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(ident); + } } public boolean releaseWifiLock(IBinder lock) { @@ -2130,31 +2295,35 @@ public class WifiService extends IWifiManager.Stub { WifiLock wifiLock = mLocks.removeLock(lock); - Slog.d(TAG, "releaseWifiLockLocked: " + wifiLock); + if (DBG) Slog.d(TAG, "releaseWifiLockLocked: " + wifiLock); hadLock = (wifiLock != null); - if (hadLock) { - int uid = Binder.getCallingUid(); - long ident = Binder.clearCallingIdentity(); - try { + long ident = Binder.clearCallingIdentity(); + try { + if (hadLock) { + noteAcquireWifiLock(wifiLock); switch(wifiLock.mMode) { case WifiManager.WIFI_MODE_FULL: ++mFullLocksReleased; - mBatteryStats.noteFullWifiLockReleased(uid); + break; + case WifiManager.WIFI_MODE_FULL_HIGH_PERF: + ++mFullHighPerfLocksReleased; break; case WifiManager.WIFI_MODE_SCAN_ONLY: ++mScanLocksReleased; - mBatteryStats.noteScanWifiLockReleased(uid); break; } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(ident); } + + // TODO - should this only happen if you hadLock? + updateWifiState(); + + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(ident); } - // TODO - should this only happen if you hadLock? - updateWifiState(); + return hadLock; } @@ -2163,12 +2332,14 @@ public class WifiService extends IWifiManager.Stub { String mTag; int mMode; IBinder mBinder; + WorkSource mWorkSource; - DeathRecipient(int mode, String tag, IBinder binder) { + DeathRecipient(int mode, String tag, IBinder binder, WorkSource ws) { super(); mTag = tag; mMode = mode; mBinder = binder; + mWorkSource = ws; try { mBinder.linkToDeath(this, 0); } catch (RemoteException e) { @@ -2183,7 +2354,7 @@ public class WifiService extends IWifiManager.Stub { private class Multicaster extends DeathRecipient { Multicaster(String tag, IBinder binder) { - super(Binder.getCallingUid(), tag, binder); + super(Binder.getCallingUid(), tag, binder, null); } public void binderDied() { diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java index 6e1b63a..83d65fa 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -16,11 +16,6 @@ package com.android.server; -import static android.os.LocalPowerManager.CHEEK_EVENT; -import static android.os.LocalPowerManager.OTHER_EVENT; -import static android.os.LocalPowerManager.TOUCH_EVENT; -import static android.os.LocalPowerManager.LONG_TOUCH_EVENT; -import static android.os.LocalPowerManager.TOUCH_UP_EVENT; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_BLUR_BEHIND; @@ -48,7 +43,6 @@ 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.KeyInputQueue.QueuedEvent; import com.android.server.am.BatteryStatsService; import android.Manifest; @@ -63,17 +57,20 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; +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; import android.os.Debug; import android.os.Handler; import android.os.IBinder; -import android.os.LatencyTimer; import android.os.LocalPowerManager; import android.os.Looper; import android.os.Message; @@ -93,34 +90,42 @@ import android.util.EventLog; import android.util.Log; 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; import android.view.IWindow; import android.view.IWindowManager; import android.view.IWindowSession; +import android.view.InputChannel; +import android.view.InputDevice; +import android.view.InputEvent; import android.view.KeyEvent; import android.view.MotionEvent; -import android.view.RawInputEvent; import android.view.Surface; import android.view.SurfaceSession; import android.view.View; -import android.view.ViewConfiguration; 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; import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; @@ -135,7 +140,7 @@ import java.util.List; /** {@hide} */ public class WindowManagerService extends IWindowManager.Stub - implements Watchdog.Monitor, KeyInputQueue.HapticFeedbackCallback { + implements Watchdog.Monitor { static final String TAG = "WindowManager"; static final boolean DEBUG = false; static final boolean DEBUG_FOCUS = false; @@ -156,16 +161,11 @@ public class WindowManagerService extends IWindowManager.Stub static final boolean DEBUG_FREEZE = false; static final boolean SHOW_TRANSACTIONS = false; static final boolean HIDE_STACK_CRAWLS = true; - static final boolean MEASURE_LATENCY = false; - static private LatencyTimer lt; static final boolean PROFILE_ORIENTATION = false; static final boolean BLUR = true; static final boolean localLOGV = DEBUG; - /** How long to wait for subsequent key repeats, in milliseconds */ - static final int KEY_REPEAT_DELAY = 50; - /** How much to multiply the policy's type layer, to reserve room * for multiple windows of the same type and Z-ordering adjustment * with TYPE_LAYER_OFFSET. */ @@ -198,35 +198,19 @@ public class WindowManagerService extends IWindowManager.Stub /** Adjustment to time to perform a dim, to make it more dramatic. */ static final int DIM_DURATION_MULTIPLIER = 6; - - static final int INJECT_FAILED = 0; - static final int INJECT_SUCCEEDED = 1; - static final int INJECT_NO_PERMISSION = -1; + + // Maximum number of milliseconds to wait for input event injection. + // FIXME is this value reasonable? + private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000; + + // Default input dispatching timeout in nanoseconds. + private 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; static final int UPDATE_FOCUS_PLACING_SURFACES = 2; static final int UPDATE_FOCUS_WILL_PLACE_SURFACES = 3; - /** The minimum time between dispatching touch events. */ - int mMinWaitTimeBetweenTouchEvents = 1000 / 35; - - // Last touch event time - long mLastTouchEventTime = 0; - - // Last touch event type - int mLastTouchEventType = OTHER_EVENT; - - // Time to wait before calling useractivity again. This saves CPU usage - // when we get a flood of touch events. - static final int MIN_TIME_BETWEEN_USERACTIVITIES = 1000; - - // Last time we call user activity - long mLastUserActivityCallTime = 0; - - // Last time we updated battery stats - long mLastBatteryStatsCallTime = 0; - private static final String SYSTEM_SECURE = "ro.secure"; private static final String SYSTEM_DEBUGGABLE = "ro.debuggable"; @@ -349,7 +333,7 @@ public class WindowManagerService extends IWindowManager.Stub /** * Z-ordered (bottom-most first) list of all Window objects. */ - final ArrayList mWindows = new ArrayList(); + final ArrayList<WindowState> mWindows = new ArrayList<WindowState>(); /** * Windows that are being resized. Used so we can tell the client about @@ -386,6 +370,7 @@ public class WindowManagerService extends IWindowManager.Stub private DimAnimator mDimAnimator = null; Surface mBlurSurface; boolean mBlurShown; + Watermark mWatermark; int mTransactionSequence = 0; @@ -442,8 +427,6 @@ public class WindowManagerService extends IWindowManager.Stub final ArrayList<AppWindowToken> mToTopApps = new ArrayList<AppWindowToken>(); final ArrayList<AppWindowToken> mToBottomApps = new ArrayList<AppWindowToken>(); - //flag to detect fat touch events - boolean mFatTouch = false; Display mDisplay; H mH = new H(); @@ -477,7 +460,6 @@ public class WindowManagerService extends IWindowManager.Stub float mLastWallpaperY = -1; float mLastWallpaperXStep = -1; float mLastWallpaperYStep = -1; - boolean mSendingPointersToWallpaper = false; // This is set when we are waiting for a wallpaper to tell us it is done // changing its scroll position. WindowState mWaitingOnWallpaper; @@ -495,12 +477,11 @@ public class WindowManagerService extends IWindowManager.Stub float mWindowAnimationScale = 1.0f; float mTransitionAnimationScale = 1.0f; - final KeyWaiter mKeyWaiter = new KeyWaiter(); - final KeyQ mQueue; - final InputDispatcherThread mInputThread; + final InputManager mInputManager; // Who is holding the screen on. Session mHoldingScreenOn; + PowerManager.WakeLock mHoldingScreenWakeLock; boolean mTurnOnScreen; @@ -511,8 +492,14 @@ public class WindowManagerService extends IWindowManager.Stub boolean mInTouchMode = false; private ViewServer mViewServer; + private ArrayList<WindowChangeListener> mWindowChangeListeners = + new ArrayList<WindowChangeListener>(); + private boolean mWindowsChanged = false; - final Rect mTempRect = new Rect(); + public interface WindowChangeListener { + public void windowsChanged(); + public void focusChanged(); + } final Configuration mTempConfiguration = new Configuration(); int mScreenLayout = Configuration.SCREENLAYOUT_SIZE_UNDEFINED; @@ -561,6 +548,7 @@ public class WindowManagerService extends IWindowManager.Stub mHaveInputMethods); android.os.Process.setThreadPriority( android.os.Process.THREAD_PRIORITY_DISPLAY); + android.os.Process.setCanSelfBackground(false); synchronized (this) { mService = s; @@ -596,6 +584,7 @@ public class WindowManagerService extends IWindowManager.Stub // Log.VERBOSE, "WindowManagerPolicy", Log.LOG_ID_SYSTEM)); android.os.Process.setThreadPriority( android.os.Process.THREAD_PRIORITY_FOREGROUND); + android.os.Process.setCanSelfBackground(false); mPolicy.init(mContext, mService, mPM); synchronized (this) { @@ -609,10 +598,6 @@ public class WindowManagerService extends IWindowManager.Stub private WindowManagerService(Context context, PowerManagerService pm, boolean haveInputMethods) { - if (MEASURE_LATENCY) { - lt = new LatencyTimer(100, 1000); - } - mContext = context; mHaveInputMethods = haveInputMethods; mLimitedAlphaCompositing = context.getResources().getBoolean( @@ -639,20 +624,11 @@ public class WindowManagerService extends IWindowManager.Stub filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); mContext.registerReceiver(mBroadcastReceiver, filter); - int max_events_per_sec = 35; - try { - max_events_per_sec = Integer.parseInt(SystemProperties - .get("windowsmgr.max_events_per_sec")); - if (max_events_per_sec < 1) { - max_events_per_sec = 35; - } - } catch (NumberFormatException e) { - } - mMinWaitTimeBetweenTouchEvents = 1000 / max_events_per_sec; - - mQueue = new KeyQ(); + mHoldingScreenWakeLock = pmc.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, + "KEEP_SCREEN_ON_FLAG"); + mHoldingScreenWakeLock.setReferenceCounted(false); - mInputThread = new InputDispatcherThread(); + mInputManager = new InputManager(context, this); PolicyThread thr = new PolicyThread(mPolicy, this, context, pm); thr.start(); @@ -666,8 +642,7 @@ public class WindowManagerService extends IWindowManager.Stub } } - mInputThread.start(); - mQueue.start(); + mInputManager.start(); // Add ourself to the Watchdog monitors. Watchdog.getInstance().addMonitor(this); @@ -688,33 +663,35 @@ public class WindowManagerService extends IWindowManager.Stub } } - private void placeWindowAfter(Object pos, WindowState window) { + private void placeWindowAfter(WindowState pos, WindowState window) { final int i = mWindows.indexOf(pos); if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Slog.v( TAG, "Adding window " + window + " at " + (i+1) + " of " + mWindows.size() + " (after " + pos + ")"); mWindows.add(i+1, window); + mWindowsChanged = true; } - private void placeWindowBefore(Object pos, WindowState window) { + private void placeWindowBefore(WindowState pos, WindowState window) { final int i = mWindows.indexOf(pos); if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Slog.v( TAG, "Adding window " + window + " at " + i + " of " + mWindows.size() + " (before " + pos + ")"); mWindows.add(i, window); + mWindowsChanged = true; } //This method finds out the index of a window that has the same app token as //win. used for z ordering the windows in mWindows private int findIdxBasedOnAppTokens(WindowState win) { //use a local variable to cache mWindows - ArrayList localmWindows = mWindows; + ArrayList<WindowState> localmWindows = mWindows; int jmax = localmWindows.size(); if(jmax == 0) { return -1; } for(int j = (jmax-1); j >= 0; j--) { - WindowState wentry = (WindowState)localmWindows.get(j); + WindowState wentry = localmWindows.get(j); if(wentry.mAppToken == win.mAppToken) { return j; } @@ -725,7 +702,7 @@ public class WindowManagerService extends IWindowManager.Stub private void addWindowToListInOrderLocked(WindowState win, boolean addToToken) { final IWindow client = win.mClient; final WindowToken token = win.mToken; - final ArrayList localmWindows = mWindows; + final ArrayList<WindowState> localmWindows = mWindows; final int N = localmWindows.size(); final WindowState attached = win.mAttachedWindow; @@ -759,6 +736,7 @@ public class WindowManagerService extends IWindowManager.Stub TAG, "Adding window " + win + " at " + (newIdx+1) + " of " + N); localmWindows.add(newIdx+1, win); + mWindowsChanged = true; } } } @@ -769,7 +747,7 @@ public class WindowManagerService extends IWindowManager.Stub // Figure out where the window should go, based on the // order of applications. final int NA = mAppTokens.size(); - Object pos = null; + WindowState pos = null; for (i=NA-1; i>=0; i--) { AppWindowToken t = mAppTokens.get(i); if (t == token) { @@ -789,8 +767,7 @@ public class WindowManagerService extends IWindowManager.Stub // we need to look some more. if (pos != null) { // Move behind any windows attached to this one. - WindowToken atoken = - mTokenMap.get(((WindowState)pos).mClient.asBinder()); + WindowToken atoken = mTokenMap.get(pos.mClient.asBinder()); if (atoken != null) { final int NC = atoken.windows.size(); if (NC > 0) { @@ -816,8 +793,7 @@ public class WindowManagerService extends IWindowManager.Stub if (pos != null) { // Move in front of any windows attached to this // one. - WindowToken atoken = - mTokenMap.get(((WindowState)pos).mClient.asBinder()); + WindowToken atoken = mTokenMap.get(pos.mClient.asBinder()); if (atoken != null) { final int NC = atoken.windows.size(); if (NC > 0) { @@ -832,7 +808,7 @@ public class WindowManagerService extends IWindowManager.Stub // Just search for the start of this layer. final int myLayer = win.mBaseLayer; for (i=0; i<N; i++) { - WindowState w = (WindowState)localmWindows.get(i); + WindowState w = localmWindows.get(i); if (w.mBaseLayer > myLayer) { break; } @@ -841,6 +817,7 @@ public class WindowManagerService extends IWindowManager.Stub TAG, "Adding window " + win + " at " + i + " of " + N); localmWindows.add(i, win); + mWindowsChanged = true; } } } @@ -848,7 +825,7 @@ public class WindowManagerService extends IWindowManager.Stub // Figure out where window should go, based on layer. final int myLayer = win.mBaseLayer; for (i=N-1; i>=0; i--) { - if (((WindowState)localmWindows.get(i)).mBaseLayer <= myLayer) { + if (localmWindows.get(i).mBaseLayer <= myLayer) { i++; break; } @@ -858,6 +835,7 @@ public class WindowManagerService extends IWindowManager.Stub TAG, "Adding window " + win + " at " + i + " of " + N); localmWindows.add(i, win); + mWindowsChanged = true; } if (addToToken) { token.windows.add(tokenWindowsPos, win); @@ -930,13 +908,13 @@ public class WindowManagerService extends IWindowManager.Stub } int findDesiredInputMethodWindowIndexLocked(boolean willMove) { - final ArrayList localmWindows = mWindows; + final ArrayList<WindowState> localmWindows = mWindows; final int N = localmWindows.size(); WindowState w = null; int i = N; while (i > 0) { i--; - w = (WindowState)localmWindows.get(i); + w = localmWindows.get(i); //Slog.i(TAG, "Checking window @" + i + " " + w + " fl=0x" // + Integer.toHexString(w.mAttrs.flags)); @@ -951,7 +929,7 @@ public class WindowManagerService extends IWindowManager.Stub if (!willMove && w.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING && i > 0) { - WindowState wb = (WindowState)localmWindows.get(i-1); + WindowState wb = localmWindows.get(i-1); if (wb.mAppToken == w.mAppToken && canBeImeTarget(wb)) { i--; w = wb; @@ -981,7 +959,7 @@ public class WindowManagerService extends IWindowManager.Stub int pos = 0; pos = localmWindows.indexOf(curTarget); while (pos >= 0) { - WindowState win = (WindowState)localmWindows.get(pos); + WindowState win = localmWindows.get(pos); if (win.mAppToken != token) { break; } @@ -1066,6 +1044,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_WINDOW_MOVEMENT) Slog.v( TAG, "Adding input method window " + win + " at " + pos); mWindows.add(pos, win); + mWindowsChanged = true; moveInputMethodDialogsLocked(pos+1); return; } @@ -1085,7 +1064,7 @@ public class WindowManagerService extends IWindowManager.Stub int wi = imw.mChildWindows.size(); while (wi > 0) { wi--; - WindowState cw = (WindowState)imw.mChildWindows.get(wi); + WindowState cw = imw.mChildWindows.get(wi); cw.mAnimLayer = cw.mLayer + adj; if (DEBUG_LAYERS) Slog.v(TAG, "IM win " + cw + " anim layer: " + cw.mAnimLayer); @@ -1107,10 +1086,11 @@ public class WindowManagerService extends IWindowManager.Stub if (wpos < interestingPos) interestingPos--; if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Temp removing at " + wpos + ": " + win); mWindows.remove(wpos); + mWindowsChanged = true; int NC = win.mChildWindows.size(); while (NC > 0) { NC--; - WindowState cw = (WindowState)win.mChildWindows.get(NC); + WindowState cw = win.mChildWindows.get(NC); int cpos = mWindows.indexOf(cw); if (cpos >= 0) { if (cpos < interestingPos) interestingPos--; @@ -1133,6 +1113,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "ReAdd removing from " + wpos + ": " + win); mWindows.remove(wpos); + mWindowsChanged = true; reAddWindowLocked(wpos, win); } } @@ -1161,7 +1142,7 @@ public class WindowManagerService extends IWindowManager.Stub if (pos >= 0) { final AppWindowToken targetAppToken = mInputMethodTarget.mAppToken; if (pos < mWindows.size()) { - WindowState wp = (WindowState)mWindows.get(pos); + WindowState wp = mWindows.get(pos); if (wp == mInputMethodWindow) { pos++; } @@ -1205,14 +1186,14 @@ public class WindowManagerService extends IWindowManager.Stub // located here, and contiguous. final int N = mWindows.size(); WindowState firstImWin = imPos < N - ? (WindowState)mWindows.get(imPos) : null; + ? mWindows.get(imPos) : null; // Figure out the actual input method window that should be // at the bottom of their stack. WindowState baseImWin = imWin != null ? imWin : mInputMethodDialogs.get(0); if (baseImWin.mChildWindows.size() > 0) { - WindowState cw = (WindowState)baseImWin.mChildWindows.get(0); + WindowState cw = baseImWin.mChildWindows.get(0); if (cw.mSubLayer < 0) baseImWin = cw; } @@ -1221,7 +1202,7 @@ public class WindowManagerService extends IWindowManager.Stub // First find the top IM window. int pos = imPos+1; while (pos < N) { - if (!((WindowState)mWindows.get(pos)).mIsImWindow) { + if (!(mWindows.get(pos)).mIsImWindow) { break; } pos++; @@ -1229,7 +1210,7 @@ public class WindowManagerService extends IWindowManager.Stub pos++; // Now there should be no more input method windows above. while (pos < N) { - if (((WindowState)mWindows.get(pos)).mIsImWindow) { + if ((mWindows.get(pos)).mIsImWindow) { break; } pos++; @@ -1317,7 +1298,7 @@ public class WindowManagerService extends IWindowManager.Stub // First find top-most window that has asked to be on top of the // wallpaper; all wallpapers go behind it. - final ArrayList localmWindows = mWindows; + final ArrayList<WindowState> localmWindows = mWindows; int N = localmWindows.size(); WindowState w = null; WindowState foundW = null; @@ -1327,7 +1308,7 @@ public class WindowManagerService extends IWindowManager.Stub int i = N; while (i > 0) { i--; - w = (WindowState)localmWindows.get(i); + w = localmWindows.get(i); if ((w.mAttrs.type == WindowManager.LayoutParams.TYPE_WALLPAPER)) { if (topCurW == null) { topCurW = w; @@ -1498,7 +1479,7 @@ public class WindowManagerService extends IWindowManager.Stub // AND any starting window associated with it, AND below the // maximum layer the policy allows for wallpapers. while (foundI > 0) { - WindowState wb = (WindowState)localmWindows.get(foundI-1); + WindowState wb = localmWindows.get(foundI-1); if (wb.mBaseLayer < maxLayer && wb.mAttachedWindow != foundW && wb.mAttachedWindow != foundW.mAttachedWindow && @@ -1523,7 +1504,7 @@ public class WindowManagerService extends IWindowManager.Stub } else { // Okay i is the position immediately above the wallpaper. Look at // what is below it for later. - foundW = foundI > 0 ? (WindowState)localmWindows.get(foundI-1) : null; + foundW = foundI > 0 ? localmWindows.get(foundI-1) : null; } if (visible) { @@ -1582,7 +1563,7 @@ public class WindowManagerService extends IWindowManager.Stub if (wallpaper == foundW) { foundI--; foundW = foundI > 0 - ? (WindowState)localmWindows.get(foundI-1) : null; + ? localmWindows.get(foundI-1) : null; continue; } @@ -1594,6 +1575,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Wallpaper removing at " + oldIndex + ": " + wallpaper); localmWindows.remove(oldIndex); + mWindowsChanged = true; if (oldIndex < foundI) { foundI--; } @@ -1605,6 +1587,7 @@ public class WindowManagerService extends IWindowManager.Stub + " from " + oldIndex + " to " + foundI); localmWindows.add(foundI, wallpaper); + mWindowsChanged = true; changed |= ADJUST_WALLPAPER_LAYERS_CHANGED; } } @@ -1794,74 +1777,10 @@ public class WindowManagerService extends IWindowManager.Stub } } } - - void sendPointerToWallpaperLocked(WindowState srcWin, - MotionEvent pointer, long eventTime) { - int curTokenIndex = mWallpaperTokens.size(); - while (curTokenIndex > 0) { - curTokenIndex--; - WindowToken token = mWallpaperTokens.get(curTokenIndex); - int curWallpaperIndex = token.windows.size(); - while (curWallpaperIndex > 0) { - curWallpaperIndex--; - WindowState wallpaper = token.windows.get(curWallpaperIndex); - if ((wallpaper.mAttrs.flags & - WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) { - continue; - } - try { - MotionEvent ev = MotionEvent.obtainNoHistory(pointer); - if (srcWin != null) { - ev.offsetLocation(srcWin.mFrame.left-wallpaper.mFrame.left, - srcWin.mFrame.top-wallpaper.mFrame.top); - } else { - ev.offsetLocation(-wallpaper.mFrame.left, -wallpaper.mFrame.top); - } - switch (pointer.getAction()) { - case MotionEvent.ACTION_DOWN: - mSendingPointersToWallpaper = true; - break; - case MotionEvent.ACTION_UP: - mSendingPointersToWallpaper = false; - break; - } - wallpaper.mClient.dispatchPointer(ev, eventTime, false); - } catch (RemoteException e) { - Slog.w(TAG, "Failure sending pointer to wallpaper", e); - } - } - } - } - - void dispatchPointerElsewhereLocked(WindowState srcWin, WindowState relWin, - MotionEvent pointer, long eventTime, boolean skipped) { - if (relWin != null) { - mPolicy.dispatchedPointerEventLw(pointer, relWin.mFrame.left, relWin.mFrame.top); - } else { - mPolicy.dispatchedPointerEventLw(pointer, 0, 0); - } - - // If we sent an initial down to the wallpaper, then continue - // sending events until the final up. - if (mSendingPointersToWallpaper) { - if (skipped) { - Slog.i(TAG, "Sending skipped pointer to wallpaper!"); - } - sendPointerToWallpaperLocked(relWin, pointer, eventTime); - - // If we are on top of the wallpaper, then the wallpaper also - // gets to see this movement. - } else if (srcWin != null - && pointer.getAction() == MotionEvent.ACTION_DOWN - && mWallpaperTarget == srcWin - && srcWin.mAttrs.type != WindowManager.LayoutParams.TYPE_KEYGUARD) { - sendPointerToWallpaperLocked(relWin, pointer, eventTime); - } - } public int addWindow(Session session, IWindow client, WindowManager.LayoutParams attrs, int viewVisibility, - Rect outContentInsets) { + Rect outContentInsets, InputChannel outInputChannel) { int res = mPolicy.checkAddPermission(attrs); if (res != WindowManagerImpl.ADD_OKAY) { return res; @@ -1880,7 +1799,7 @@ public class WindowManagerService extends IWindowManager.Stub mDisplay = wm.getDefaultDisplay(); mInitialDisplayWidth = mDisplay.getWidth(); mInitialDisplayHeight = mDisplay.getHeight(); - mQueue.setDisplay(mDisplay); + mInputManager.setDisplaySize(0, mInitialDisplayWidth, mInitialDisplayHeight); reportNewConfig = true; } @@ -1973,6 +1892,15 @@ public class WindowManagerService extends IWindowManager.Stub if (res != WindowManagerImpl.ADD_OKAY) { return res; } + + if (outInputChannel != null) { + String name = win.makeInputChannelName(); + InputChannel[] inputChannels = InputChannel.openInputChannelPair(name); + win.mInputChannel = inputChannels[0]; + inputChannels[1].transferToBinderOutParameter(outInputChannel); + + mInputManager.registerInputChannel(win.mInputChannel); + } // From now on, no exceptions or errors allowed! @@ -2026,8 +1954,8 @@ public class WindowManagerService extends IWindowManager.Stub boolean focusChanged = false; if (win.canReceiveKeys()) { - if ((focusChanged=updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS)) - == true) { + focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS); + if (focusChanged) { imMayMove = false; } } @@ -2043,10 +1971,9 @@ public class WindowManagerService extends IWindowManager.Stub //dump(); if (focusChanged) { - if (mCurrentFocus != null) { - mKeyWaiter.handleNewWindowLocked(mCurrentFocus); - } + finishUpdateFocusedWindowAfterAssignLayersLocked(); } + if (localLOGV) Slog.v( TAG, "New client " + client.asBinder() + ": window=" + win); @@ -2088,6 +2015,8 @@ public class WindowManagerService extends IWindowManager.Stub + ", surface=" + win.mSurface); final long origId = Binder.clearCallingIdentity(); + + win.disposeInputChannel(); if (DEBUG_APP_TRANSITIONS) Slog.v( TAG, "Remove " + win + ": mSurface=" + win.mSurface @@ -2148,12 +2077,6 @@ public class WindowManagerService extends IWindowManager.Stub } private void removeWindowInnerLocked(Session session, WindowState win) { - mKeyWaiter.finishedKey(session, win.mClient, true, - KeyWaiter.RETURN_NOTHING); - mKeyWaiter.releaseMotionTarget(win); - mKeyWaiter.releasePendingPointerLocked(win.mSession); - mKeyWaiter.releasePendingTrackballLocked(win.mSession); - win.mRemoved = true; if (mInputMethodTarget == win) { @@ -2171,6 +2094,7 @@ public class WindowManagerService extends IWindowManager.Stub mWindowMap.remove(win.mClient.asBinder()); mWindows.remove(win); + mWindowsChanged = true; if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Final remove of window: " + win); if (mInputMethodWindow == win) { @@ -2231,6 +2155,8 @@ public class WindowManagerService extends IWindowManager.Stub win.mAppToken.updateReportedVisibilityLocked(); } } + + mInputMonitor.updateInputWindowsLw(); } private static void logSurface(WindowState w, String msg, RuntimeException where) { @@ -2474,6 +2400,8 @@ public class WindowManagerService extends IWindowManager.Stub outSurface.release(); } } catch (Exception e) { + mInputMonitor.updateInputWindowsLw(); + Slog.w(TAG, "Exception thrown when creating surface for client " + client + " (" + win.mAttrs.getTitle() + ")", e); @@ -2520,8 +2448,6 @@ public class WindowManagerService extends IWindowManager.Stub applyAnimationLocked(win, transit, false)) { focusMayChange = true; win.mExiting = true; - mKeyWaiter.finishedKey(session, client, true, - KeyWaiter.RETURN_NOTHING); } else if (win.isAnimating()) { // Currently in a hide animation... turn this into // an exit. @@ -2616,6 +2542,8 @@ public class WindowManagerService extends IWindowManager.Stub TAG, "Relayout of " + win + ": focusMayChange=" + focusMayChange); inTouchMode = mInTouchMode; + + mInputMonitor.updateInputWindowsLw(); } if (configChanged) { @@ -2982,8 +2910,6 @@ public class WindowManagerService extends IWindowManager.Stub if (win.isVisibleNow()) { applyAnimationLocked(win, WindowManagerPolicy.TRANSIT_EXIT, false); - mKeyWaiter.finishedKey(win.mSession, win.mClient, true, - KeyWaiter.RETURN_NOTHING); changed = true; } } @@ -3001,6 +2927,7 @@ public class WindowManagerService extends IWindowManager.Stub } } + mInputMonitor.updateInputWindowsLw(); } else { Slog.w(TAG, "Attempted to remove non-existing token: " + token); } @@ -3014,6 +2941,20 @@ public class WindowManagerService extends IWindowManager.Stub "addAppToken()")) { throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); } + + // Get the dispatching timeout here while we are not holding any locks so that it + // can be cached by the AppWindowToken. The timeout value is used later by the + // input dispatcher in code that does hold locks. If we did not cache the value + // here we would run the chance of introducing a deadlock between the window manager + // (which holds locks while updating the input dispatcher state) and the activity manager + // (which holds locks while querying the application token). + long inputDispatchingTimeoutNanos; + try { + inputDispatchingTimeoutNanos = token.getKeyDispatchingTimeout() * 1000000L; + } catch (RemoteException ex) { + Slog.w(TAG, "Could not get dispatching timeout.", ex); + inputDispatchingTimeoutNanos = DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; + } synchronized(mWindowMap) { AppWindowToken wtoken = findAppWindowToken(token.asBinder()); @@ -3022,6 +2963,7 @@ public class WindowManagerService extends IWindowManager.Stub return; } wtoken = new AppWindowToken(token); + wtoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos; wtoken.groupId = groupId; wtoken.appFullscreen = fullscreen; wtoken.requestedOrientation = requestedOrientation; @@ -3057,7 +2999,7 @@ public class WindowManagerService extends IWindowManager.Stub public int getOrientationFromWindowsLocked() { int pos = mWindows.size() - 1; while (pos >= 0) { - WindowState wtoken = (WindowState) mWindows.get(pos); + WindowState wtoken = mWindows.get(pos); pos--; if (wtoken.mAppToken != null) { // We hit an application window. so the orientation will be determined by the @@ -3125,11 +3067,8 @@ public class WindowManagerService extends IWindowManager.Stub } // If this application has requested an explicit orientation, // then use it. - if (or == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || - or == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || - or == ActivityInfo.SCREEN_ORIENTATION_SENSOR || - or == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR || - or == ActivityInfo.SCREEN_ORIENTATION_USER) { + if (or != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + && or != ActivityInfo.SCREEN_ORIENTATION_BEHIND) { return or; } findingBehind |= (or == ActivityInfo.SCREEN_ORIENTATION_BEHIND); @@ -3161,8 +3100,11 @@ public class WindowManagerService extends IWindowManager.Stub } else if (currentConfig != null) { // No obvious action we need to take, but if our current - // state mismatches the activity maanager's, update it + // state mismatches the activity manager's, update it, + // disregarding font scale, which should remain set to + // the value of the previous configuration. mTempConfiguration.setToDefaults(); + mTempConfiguration.fontScale = currentConfig.fontScale; if (computeNewConfigurationLocked(mTempConfiguration)) { if (currentConfig.diff(mTempConfiguration) != 0) { mWaitingForConfig = true; @@ -3285,7 +3227,9 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_FOCUS) Slog.v(TAG, "Clearing focused app, was " + mFocusedApp); changed = mFocusedApp != null; mFocusedApp = null; - mKeyWaiter.tickle(); + if (changed) { + mInputMonitor.setFocusedAppLw(null); + } } else { AppWindowToken newFocus = findAppWindowToken(token); if (newFocus == null) { @@ -3295,7 +3239,9 @@ public class WindowManagerService extends IWindowManager.Stub changed = mFocusedApp != newFocus; mFocusedApp = newFocus; if (DEBUG_FOCUS) Slog.v(TAG, "Set focused app to: " + mFocusedApp); - mKeyWaiter.tickle(); + if (changed) { + mInputMonitor.setFocusedAppLw(newFocus); + } } if (moveFocusNow && changed) { @@ -3436,6 +3382,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Removing starting window: " + startingWindow); mWindows.remove(startingWindow); + mWindowsChanged = true; ttoken.windows.remove(startingWindow); ttoken.allAppWindows.remove(startingWindow); addWindowToListInOrderLocked(startingWindow, true); @@ -3606,8 +3553,6 @@ public class WindowManagerService extends IWindowManager.Stub applyAnimationLocked(win, WindowManagerPolicy.TRANSIT_EXIT, false); } - mKeyWaiter.finishedKey(win.mSession, win.mClient, true, - KeyWaiter.RETURN_NOTHING); changed = true; } } @@ -3635,6 +3580,8 @@ public class WindowManagerService extends IWindowManager.Stub if (performLayout) { updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES); performLayoutAndPlaceSurfacesLocked(); + } else { + mInputMonitor.updateInputWindowsLw(); } } } @@ -3892,7 +3839,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_FOCUS) Slog.v(TAG, "Removing focused app token:" + wtoken); mFocusedApp = null; updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL); - mKeyWaiter.tickle(); + mInputMonitor.setFocusedAppLw(null); } } else { Slog.w(TAG, "Attempted to remove non-existing app token: " + token); @@ -3918,10 +3865,11 @@ public class WindowManagerService extends IWindowManager.Stub WindowState win = token.windows.get(i); if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Tmp removing app window " + win); mWindows.remove(win); + mWindowsChanged = true; int j = win.mChildWindows.size(); while (j > 0) { j--; - WindowState cwin = (WindowState)win.mChildWindows.get(j); + WindowState cwin = win.mChildWindows.get(j); if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Tmp removing child window " + cwin); mWindows.remove(cwin); @@ -3949,7 +3897,7 @@ public class WindowManagerService extends IWindowManager.Stub int i = NW; while (i > 0) { i--; - WindowState win = (WindowState)mWindows.get(i); + WindowState win = mWindows.get(i); if (win.getAppToken() != null) { return i+1; } @@ -3975,7 +3923,7 @@ public class WindowManagerService extends IWindowManager.Stub int j = win.mChildWindows.size(); while (j > 0) { j--; - WindowState cwin = (WindowState)win.mChildWindows.get(j); + WindowState cwin = win.mChildWindows.get(j); if (cwin.mSubLayer >= 0) { for (int pos=NW-1; pos>=0; pos--) { if (mWindows.get(pos) == cwin) { @@ -4003,7 +3951,7 @@ public class WindowManagerService extends IWindowManager.Stub final int NCW = win.mChildWindows.size(); boolean added = false; for (int j=0; j<NCW; j++) { - WindowState cwin = (WindowState)win.mChildWindows.get(j); + WindowState cwin = win.mChildWindows.get(j); if (!added && cwin.mSubLayer >= 0) { if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Re-adding child window at " + index + ": " + cwin); @@ -4022,6 +3970,7 @@ public class WindowManagerService extends IWindowManager.Stub mWindows.add(index, win); index++; } + mWindowsChanged = true; return index; } @@ -4286,7 +4235,7 @@ public class WindowManagerService extends IWindowManager.Stub public void closeSystemDialogs(String reason) { synchronized(mWindowMap) { for (int i=mWindows.size()-1; i>=0; i--) { - WindowState w = (WindowState)mWindows.get(i); + WindowState w = mWindows.get(i); if (w.mSurface != null) { try { w.mClient.closeSystemDialogs(reason); @@ -4357,7 +4306,7 @@ public class WindowManagerService extends IWindowManager.Stub "getSwitchState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return KeyInputQueue.getSwitchState(sw); + return mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, sw); } public int getSwitchStateForDevice(int devid, int sw) { @@ -4365,7 +4314,7 @@ public class WindowManagerService extends IWindowManager.Stub "getSwitchStateForDevice()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return KeyInputQueue.getSwitchState(devid, sw); + return mInputManager.getSwitchState(devid, InputDevice.SOURCE_ANY, sw); } public int getScancodeState(int sw) { @@ -4373,7 +4322,7 @@ public class WindowManagerService extends IWindowManager.Stub "getScancodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getScancodeState(sw); + return mInputManager.getScanCodeState(-1, InputDevice.SOURCE_ANY, sw); } public int getScancodeStateForDevice(int devid, int sw) { @@ -4381,7 +4330,7 @@ public class WindowManagerService extends IWindowManager.Stub "getScancodeStateForDevice()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getScancodeState(devid, sw); + return mInputManager.getScanCodeState(devid, InputDevice.SOURCE_ANY, sw); } public int getTrackballScancodeState(int sw) { @@ -4389,7 +4338,7 @@ public class WindowManagerService extends IWindowManager.Stub "getTrackballScancodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getTrackballScancodeState(sw); + return mInputManager.getScanCodeState(-1, InputDevice.SOURCE_TRACKBALL, sw); } public int getDPadScancodeState(int sw) { @@ -4397,7 +4346,7 @@ public class WindowManagerService extends IWindowManager.Stub "getDPadScancodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getDPadScancodeState(sw); + return mInputManager.getScanCodeState(-1, InputDevice.SOURCE_DPAD, sw); } public int getKeycodeState(int sw) { @@ -4405,7 +4354,7 @@ public class WindowManagerService extends IWindowManager.Stub "getKeycodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getKeycodeState(sw); + return mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_ANY, sw); } public int getKeycodeStateForDevice(int devid, int sw) { @@ -4413,7 +4362,7 @@ public class WindowManagerService extends IWindowManager.Stub "getKeycodeStateForDevice()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getKeycodeState(devid, sw); + return mInputManager.getKeyCodeState(devid, InputDevice.SOURCE_ANY, sw); } public int getTrackballKeycodeState(int sw) { @@ -4421,7 +4370,7 @@ public class WindowManagerService extends IWindowManager.Stub "getTrackballKeycodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getTrackballKeycodeState(sw); + return mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_TRACKBALL, sw); } public int getDPadKeycodeState(int sw) { @@ -4429,11 +4378,27 @@ public class WindowManagerService extends IWindowManager.Stub "getDPadKeycodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getDPadKeycodeState(sw); + return mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_DPAD, sw); } - + public boolean hasKeys(int[] keycodes, boolean[] keyExists) { - return KeyInputQueue.hasKeys(keycodes, keyExists); + return mInputManager.hasKeys(-1, InputDevice.SOURCE_ANY, keycodes, keyExists); + } + + public InputChannel monitorInput(String inputChannelName) { + if (!checkCallingPermission(android.Manifest.permission.READ_INPUT_STATE, + "monitorInput()")) { + throw new SecurityException("Requires READ_INPUT_STATE permission"); + } + return mInputManager.monitorInput(inputChannelName); + } + + public InputDevice getInputDevice(int deviceId) { + return mInputManager.getInputDevice(deviceId); + } + + public int[] getInputDeviceIds() { + return mInputManager.getInputDeviceIds(); } public void enableScreenAfterBoot() { @@ -4470,7 +4435,7 @@ public class WindowManagerService extends IWindowManager.Stub // have been drawn. final int N = mWindows.size(); for (int i=0; i<N; i++) { - WindowState w = (WindowState)mWindows.get(i); + WindowState w = mWindows.get(i); if (w.isVisibleLw() && !w.mObscured && (w.mOrientationChanging || !w.isDrawnLw())) { return; @@ -4578,12 +4543,12 @@ public class WindowManagerService extends IWindowManager.Stub mLayoutNeeded = true; startFreezingDisplayLocked(); Slog.i(TAG, "Setting rotation to " + rotation + ", animFlags=" + animFlags); - mQueue.setOrientation(rotation); + mInputManager.setDisplayOrientation(0, rotation); if (mDisplayEnabled) { Surface.setOrientation(0, rotation, animFlags); } for (int i=mWindows.size()-1; i>=0; i--) { - WindowState w = (WindowState)mWindows.get(i); + WindowState w = mWindows.get(i); if (w.mSurface != null) { w.mOrientationChanging = true; } @@ -4739,11 +4704,10 @@ public class WindowManagerService extends IWindowManager.Stub boolean result = true; - Object[] windows; + WindowState[] windows; synchronized (mWindowMap) { - windows = new Object[mWindows.size()]; //noinspection unchecked - windows = mWindows.toArray(windows); + windows = mWindows.toArray(new WindowState[mWindows.size()]); } BufferedWriter out = null; @@ -4755,7 +4719,7 @@ public class WindowManagerService extends IWindowManager.Stub final int count = windows.length; for (int i = 0; i < count; i++) { - final WindowState w = (WindowState) windows[i]; + final WindowState w = windows[i]; out.write(Integer.toHexString(System.identityHashCode(w))); out.write(' '); out.append(w.mAttrs.getTitle()); @@ -4780,6 +4744,51 @@ public class WindowManagerService extends IWindowManager.Stub } /** + * Returns the focused window in the following format: + * windowHashCodeInHexadecimal windowName + * + * @param client The remote client to send the listing to. + * @return False if an error occurred, true otherwise. + */ + boolean viewServerGetFocusedWindow(Socket client) { + if (isSystemSecure()) { + return false; + } + + boolean result = true; + + WindowState focusedWindow = getFocusedWindow(); + + BufferedWriter out = null; + + // Any uncaught exception will crash the system process + try { + OutputStream clientStream = client.getOutputStream(); + out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024); + + if(focusedWindow != null) { + out.write(Integer.toHexString(System.identityHashCode(focusedWindow))); + out.write(' '); + out.append(focusedWindow.mAttrs.getTitle()); + } + out.write('\n'); + out.flush(); + } catch (Exception e) { + result = false; + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException e) { + result = false; + } + } + } + + return result; + } + + /** * Sends a command to a target window. The result of the command, if any, will be * written in the output stream of the specified socket. * @@ -4808,6 +4817,8 @@ public class WindowManagerService extends IWindowManager.Stub Parcel data = null; Parcel reply = null; + BufferedWriter out = null; + // Any uncaught exception will crash the system process try { // Find the hashcode of the window @@ -4845,6 +4856,12 @@ public class WindowManagerService extends IWindowManager.Stub reply.readException(); + if (!client.isOutputShutdown()) { + out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); + out.write("DONE\n"); + out.flush(); + } + } catch (Exception e) { Slog.w(TAG, "Could not send command " + command + " with parameters " + parameters, e); success = false; @@ -4855,22 +4872,71 @@ public class WindowManagerService extends IWindowManager.Stub if (reply != null) { reply.recycle(); } + if (out != null) { + try { + out.close(); + } catch (IOException e) { + + } + } } return success; } + public void addWindowChangeListener(WindowChangeListener listener) { + synchronized(mWindowMap) { + mWindowChangeListeners.add(listener); + } + } + + public void removeWindowChangeListener(WindowChangeListener listener) { + synchronized(mWindowMap) { + mWindowChangeListeners.remove(listener); + } + } + + private void notifyWindowsChanged() { + WindowChangeListener[] windowChangeListeners; + synchronized(mWindowMap) { + if(mWindowChangeListeners.isEmpty()) { + return; + } + windowChangeListeners = new WindowChangeListener[mWindowChangeListeners.size()]; + windowChangeListeners = mWindowChangeListeners.toArray(windowChangeListeners); + } + int N = windowChangeListeners.length; + for(int i = 0; i < N; i++) { + windowChangeListeners[i].windowsChanged(); + } + } + + private void notifyFocusChanged() { + WindowChangeListener[] windowChangeListeners; + synchronized(mWindowMap) { + if(mWindowChangeListeners.isEmpty()) { + return; + } + windowChangeListeners = new WindowChangeListener[mWindowChangeListeners.size()]; + windowChangeListeners = mWindowChangeListeners.toArray(windowChangeListeners); + } + int N = windowChangeListeners.length; + for(int i = 0; i < N; i++) { + windowChangeListeners[i].focusChanged(); + } + } + private WindowState findWindow(int hashCode) { if (hashCode == -1) { return getFocusedWindow(); } synchronized (mWindowMap) { - final ArrayList windows = mWindows; + final ArrayList<WindowState> windows = mWindows; final int count = windows.size(); for (int i = 0; i < count; i++) { - WindowState w = (WindowState) windows.get(i); + WindowState w = windows.get(i); if (System.identityHashCode(w) == hashCode) { return w; } @@ -4909,7 +4975,8 @@ public class WindowManagerService extends IWindowManager.Stub if (mDisplay == null) { return false; } - mQueue.getInputConfiguration(config); + + mInputManager.getInputConfiguration(config); // Use the effective "visual" dimensions based on current rotation final boolean rotated = (mRotation == Surface.ROTATION_90 @@ -4952,9 +5019,13 @@ public class WindowManagerService extends IWindowManager.Stub mScreenLayout = Configuration.SCREENLAYOUT_SIZE_SMALL | Configuration.SCREENLAYOUT_LONG_NO; } else { - // Is this a large screen? - if (longSize > 640 && shortSize >= 480) { - // VGA or larger screens at medium density are the point + // What size is this screen screen? + if (longSize >= 800 && shortSize >= 600) { + // SVGA 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 // at which we consider it to be a large screen. mScreenLayout = Configuration.SCREENLAYOUT_SIZE_LARGE; } else { @@ -4984,451 +5055,311 @@ public class WindowManagerService extends IWindowManager.Stub 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; - private final void wakeupIfNeeded(WindowState targetWin, int eventType) { - long curTime = SystemClock.uptimeMillis(); - - if (eventType == TOUCH_EVENT || eventType == LONG_TOUCH_EVENT || eventType == CHEEK_EVENT) { - if (mLastTouchEventType == eventType && - (curTime - mLastUserActivityCallTime) < MIN_TIME_BETWEEN_USERACTIVITIES) { - return; - } - mLastUserActivityCallTime = curTime; - mLastTouchEventType = eventType; - } - - if (targetWin == null - || targetWin.mAttrs.type != WindowManager.LayoutParams.TYPE_KEYGUARD) { - mPowerManager.userActivity(curTime, false, eventType, false); - } - } - - // tells if it's a cheek event or not -- this function is stateful - private static final int EVENT_NONE = 0; - private static final int EVENT_UNKNOWN = 0; - private static final int EVENT_CHEEK = 0; - private static final int EVENT_IGNORE_DURATION = 300; // ms - private static final float CHEEK_THRESHOLD = 0.6f; - private int mEventState = EVENT_NONE; - private float mEventSize; - - private int eventType(MotionEvent ev) { - float size = ev.getSize(); - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: - mEventSize = size; - return (mEventSize > CHEEK_THRESHOLD) ? CHEEK_EVENT : TOUCH_EVENT; - case MotionEvent.ACTION_UP: - if (size > mEventSize) mEventSize = size; - return (mEventSize > CHEEK_THRESHOLD) ? CHEEK_EVENT : TOUCH_UP_EVENT; - case MotionEvent.ACTION_MOVE: - final int N = ev.getHistorySize(); - if (size > mEventSize) mEventSize = size; - if (mEventSize > CHEEK_THRESHOLD) return CHEEK_EVENT; - for (int i=0; i<N; i++) { - size = ev.getHistoricalSize(i); - if (size > mEventSize) mEventSize = size; - if (mEventSize > CHEEK_THRESHOLD) return CHEEK_EVENT; - } - if (ev.getEventTime() < ev.getDownTime() + EVENT_IGNORE_DURATION) { - return TOUCH_EVENT; - } else { - return LONG_TOUCH_EVENT; + // 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); } - default: - // not good - return OTHER_EVENT; - } - } - - /** - * @return Returns true if event was dispatched, false if it was dropped for any reason - */ - private int dispatchPointer(QueuedEvent qev, MotionEvent ev, int pid, int uid) { - if (DEBUG_INPUT || WindowManagerPolicy.WATCH_POINTER) Slog.v(TAG, - "dispatchPointer " + ev); - - if (MEASURE_LATENCY) { - lt.sample("3 Wait for last dispatch ", System.nanoTime() - qev.whenNano); } - - Object targetObj = mKeyWaiter.waitForNextEventTarget(null, qev, - ev, true, false, pid, uid); - - if (MEASURE_LATENCY) { - lt.sample("3 Last dispatch finished ", System.nanoTime() - qev.whenNano); - } - - int action = ev.getAction(); - - if (action == MotionEvent.ACTION_UP) { - // let go of our target - mKeyWaiter.mMotionTarget = null; - mPowerManager.logPointerUpEvent(); - } else if (action == MotionEvent.ACTION_DOWN) { - mPowerManager.logPointerDownEvent(); - } - - if (targetObj == null) { - // In this case we are either dropping the event, or have received - // a move or up without a down. It is common to receive move - // events in such a way, since this means the user is moving the - // pointer without actually pressing down. All other cases should - // be atypical, so let's log them. - if (action != MotionEvent.ACTION_MOVE) { - Slog.w(TAG, "No window to dispatch pointer action " + ev.getAction()); + + /* 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; + } + } } - synchronized (mWindowMap) { - dispatchPointerElsewhereLocked(null, null, ev, ev.getEventTime(), true); + + if (appWindowToken == null && token != null) { + appWindowToken = (AppWindowToken) token; + Slog.i(TAG, "Input event dispatching timed out sending to application " + + appWindowToken.stringName); } - if (qev != null) { - mQueue.recycleEvent(qev); + + 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) { + } } - ev.recycle(); - return INJECT_FAILED; + return 0; // abort dispatching } - if (targetObj == mKeyWaiter.CONSUMED_EVENT_TOKEN) { + + private WindowState getWindowStateForInputChannel(InputChannel inputChannel) { synchronized (mWindowMap) { - dispatchPointerElsewhereLocked(null, null, ev, ev.getEventTime(), true); - } - if (qev != null) { - mQueue.recycleEvent(qev); + return getWindowStateForInputChannelLocked(inputChannel); } - ev.recycle(); - return INJECT_SUCCEEDED; } - - WindowState target = (WindowState)targetObj; - - final long eventTime = ev.getEventTime(); - final long eventTimeNano = ev.getEventTimeNano(); - - //Slog.i(TAG, "Sending " + ev + " to " + target); - - if (uid != 0 && uid != target.mSession.mUid) { - if (mContext.checkPermission( - android.Manifest.permission.INJECT_EVENTS, pid, uid) - != PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "Permission denied: injecting pointer event from pid " - + pid + " uid " + uid + " to window " + target - + " owned by uid " + target.mSession.mUid); - if (qev != null) { - mQueue.recycleEvent(qev); + + 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; } - ev.recycle(); - return INJECT_NO_PERMISSION; } + + return null; } - - if (MEASURE_LATENCY) { - lt.sample("4 in dispatchPointer ", System.nanoTime() - eventTimeNano); - } - - if ((target.mAttrs.flags & - WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES) != 0) { - //target wants to ignore fat touch events - boolean cheekPress = mPolicy.isCheekPressedAgainstScreen(ev); - //explicit flag to return without processing event further - boolean returnFlag = false; - if((action == MotionEvent.ACTION_DOWN)) { - mFatTouch = false; - if(cheekPress) { - mFatTouch = true; - returnFlag = true; + + /* 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; } - } else { - if(action == MotionEvent.ACTION_UP) { - if(mFatTouch) { - //earlier even was invalid doesnt matter if current up is cheekpress or not - mFatTouch = false; - returnFlag = true; - } else if(cheekPress) { - //cancel the earlier event - ev.setAction(MotionEvent.ACTION_CANCEL); - action = MotionEvent.ACTION_CANCEL; + + 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; } - } else if(action == MotionEvent.ACTION_MOVE) { - if(mFatTouch) { - //two cases here - //an invalid down followed by 0 or moves(valid or invalid) - //a valid down, invalid move, more moves. want to ignore till up - returnFlag = true; - } else if(cheekPress) { - //valid down followed by invalid moves - //an invalid move have to cancel earlier action - ev.setAction(MotionEvent.ACTION_CANCEL); - action = MotionEvent.ACTION_CANCEL; - if (DEBUG_INPUT) Slog.v(TAG, "Sending cancel for invalid ACTION_MOVE"); - //note that the subsequent invalid moves will not get here - mFatTouch = true; + + 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; } } - } //else if action - if(returnFlag) { - //recycle que, ev - if (qev != null) { - mQueue.recycleEvent(qev); - } - ev.recycle(); - return INJECT_FAILED; } - } //end if target - // Enable this for testing the "right" value - if (false && action == MotionEvent.ACTION_DOWN) { - int max_events_per_sec = 35; - try { - max_events_per_sec = Integer.parseInt(SystemProperties - .get("windowsmgr.max_events_per_sec")); - if (max_events_per_sec < 1) { - max_events_per_sec = 35; - } - } catch (NumberFormatException e) { - } - mMinWaitTimeBetweenTouchEvents = 1000 / max_events_per_sec; + // 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(); } - - /* - * Throttle events to minimize CPU usage when there's a flood of events - * e.g. constant contact with the screen - */ - if (action == MotionEvent.ACTION_MOVE) { - long nextEventTime = mLastTouchEventTime + mMinWaitTimeBetweenTouchEvents; - long now = SystemClock.uptimeMillis(); - if (now < nextEventTime) { - try { - Thread.sleep(nextEventTime - now); - } catch (InterruptedException e) { - } - mLastTouchEventTime = nextEventTime; - } else { - mLastTouchEventTime = now; - } + + /* Notifies that the lid switch changed state. */ + public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) { + mPolicy.notifyLidSwitchChanged(whenNanos, lidOpen); } - - if (MEASURE_LATENCY) { - lt.sample("5 in dispatchPointer ", System.nanoTime() - eventTimeNano); + + /* 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 keyCode, boolean down, + int policyFlags, boolean isScreenOn) { + return mPolicy.interceptKeyBeforeQueueing(whenNanos, + keyCode, down, policyFlags, isScreenOn); } - - synchronized(mWindowMap) { - if (!target.isVisibleLw()) { - // During this motion dispatch, the target window has become - // invisible. - dispatchPointerElsewhereLocked(null, null, ev, ev.getEventTime(), false); - if (qev != null) { - mQueue.recycleEvent(qev); - } - ev.recycle(); - return INJECT_SUCCEEDED; - } - - if (qev != null && action == MotionEvent.ACTION_MOVE) { - mKeyWaiter.bindTargetWindowLocked(target, - KeyWaiter.RETURN_PENDING_POINTER, qev); - ev = null; - } else { - if (action == MotionEvent.ACTION_DOWN) { - WindowState out = mKeyWaiter.mOutsideTouchTargets; - if (out != null) { - MotionEvent oev = MotionEvent.obtain(ev); - oev.setAction(MotionEvent.ACTION_OUTSIDE); - do { - final Rect frame = out.mFrame; - oev.offsetLocation(-(float)frame.left, -(float)frame.top); - try { - out.mClient.dispatchPointer(oev, eventTime, false); - } catch (android.os.RemoteException e) { - Slog.i(TAG, "WINDOW DIED during outside motion dispatch: " + out); - } - oev.offsetLocation((float)frame.left, (float)frame.top); - out = out.mNextOutsideTouch; - } while (out != null); - mKeyWaiter.mOutsideTouchTargets = null; - } - } - - dispatchPointerElsewhereLocked(target, null, ev, ev.getEventTime(), false); - - final Rect frame = target.mFrame; - ev.offsetLocation(-(float)frame.left, -(float)frame.top); - mKeyWaiter.bindTargetWindowLocked(target); - } + + /* 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 metaState, int repeatCount, + int policyFlags) { + WindowState windowState = getWindowStateForInputChannel(focus); + return mPolicy.interceptKeyBeforeDispatching(windowState, action, flags, + keyCode, metaState, repeatCount, policyFlags); } - - // finally offset the event to the target's coordinate system and - // dispatch the event. - try { - if (DEBUG_INPUT || DEBUG_FOCUS || WindowManagerPolicy.WATCH_POINTER) { - Slog.v(TAG, "Delivering pointer " + qev + " to " + target); - } - - if (MEASURE_LATENCY) { - lt.sample("6 before svr->client ipc ", System.nanoTime() - eventTimeNano); + + /* 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); } - target.mClient.dispatchPointer(ev, eventTime, true); - - if (MEASURE_LATENCY) { - lt.sample("7 after svr->client ipc ", System.nanoTime() - eventTimeNano); - } - return INJECT_SUCCEEDED; - } catch (android.os.RemoteException e) { - Slog.i(TAG, "WINDOW DIED during motion dispatch: " + target); - mKeyWaiter.mMotionTarget = null; - try { - removeWindow(target.mSession, target.mClient); - } catch (java.util.NoSuchElementException ex) { - // This will happen if the window has already been - // removed. + 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(); } } - return INJECT_FAILED; - } - - /** - * @return Returns true if event was dispatched, false if it was dropped for any reason - */ - private int dispatchTrackball(QueuedEvent qev, MotionEvent ev, int pid, int uid) { - if (DEBUG_INPUT) Slog.v( - TAG, "dispatchTrackball [" + ev.getAction() +"] <" + ev.getX() + ", " + ev.getY() + ">"); - - Object focusObj = mKeyWaiter.waitForNextEventTarget(null, qev, - ev, false, false, pid, uid); - if (focusObj == null) { - Slog.w(TAG, "No focus window, dropping trackball: " + ev); - if (qev != null) { - mQueue.recycleEvent(qev); + + 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); } - ev.recycle(); - return INJECT_FAILED; } - if (focusObj == mKeyWaiter.CONSUMED_EVENT_TOKEN) { - if (qev != null) { - mQueue.recycleEvent(qev); + + public void pauseDispatchingLw(WindowToken window) { + if (! window.paused) { + if (DEBUG_INPUT) { + Slog.v(TAG, "Pausing WindowToken " + window); + } + + window.paused = true; + updateInputWindowsLw(); } - ev.recycle(); - return INJECT_SUCCEEDED; } - - WindowState focus = (WindowState)focusObj; - - if (uid != 0 && uid != focus.mSession.mUid) { - if (mContext.checkPermission( - android.Manifest.permission.INJECT_EVENTS, pid, uid) - != PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "Permission denied: injecting key event from pid " - + pid + " uid " + uid + " to window " + focus - + " owned by uid " + focus.mSession.mUid); - if (qev != null) { - mQueue.recycleEvent(qev); + + public void resumeDispatchingLw(WindowToken window) { + if (window.paused) { + if (DEBUG_INPUT) { + Slog.v(TAG, "Resuming WindowToken " + window); } - ev.recycle(); - return INJECT_NO_PERMISSION; + + window.paused = false; + updateInputWindowsLw(); } } - - final long eventTime = ev.getEventTime(); - - synchronized(mWindowMap) { - if (qev != null && ev.getAction() == MotionEvent.ACTION_MOVE) { - mKeyWaiter.bindTargetWindowLocked(focus, - KeyWaiter.RETURN_PENDING_TRACKBALL, qev); - // We don't deliver movement events to the client, we hold - // them and wait for them to call back. - ev = null; - } else { - mKeyWaiter.bindTargetWindowLocked(focus); + + public void freezeInputDispatchingLw() { + if (! mInputDispatchFrozen) { + if (DEBUG_INPUT) { + Slog.v(TAG, "Freezing input dispatching"); + } + + mInputDispatchFrozen = true; + updateInputDispatchModeLw(); } } - - try { - focus.mClient.dispatchTrackball(ev, eventTime, true); - return INJECT_SUCCEEDED; - } catch (android.os.RemoteException e) { - Slog.i(TAG, "WINDOW DIED during key dispatch: " + focus); - try { - removeWindow(focus.mSession, focus.mClient); - } catch (java.util.NoSuchElementException ex) { - // This will happen if the window has already been - // removed. + + public void thawInputDispatchingLw() { + if (mInputDispatchFrozen) { + if (DEBUG_INPUT) { + Slog.v(TAG, "Thawing input dispatching"); + } + + mInputDispatchFrozen = false; + updateInputDispatchModeLw(); } } - - return INJECT_FAILED; - } - - /** - * @return Returns true if event was dispatched, false if it was dropped for any reason - */ - private int dispatchKey(KeyEvent event, int pid, int uid) { - if (DEBUG_INPUT) Slog.v(TAG, "Dispatch key: " + event); - - Object focusObj = mKeyWaiter.waitForNextEventTarget(event, null, - null, false, false, pid, uid); - if (focusObj == null) { - Slog.w(TAG, "No focus window, dropping: " + event); - return INJECT_FAILED; - } - if (focusObj == mKeyWaiter.CONSUMED_EVENT_TOKEN) { - return INJECT_SUCCEEDED; - } - - // Okay we have finished waiting for the last event to be processed. - // First off, if this is a repeat event, check to see if there is - // a corresponding up event in the queue. If there is, we will - // just drop the repeat, because it makes no sense to repeat after - // the user has released a key. (This is especially important for - // long presses.) - if (event.getRepeatCount() > 0 && mQueue.hasKeyUpEvent(event)) { - return INJECT_SUCCEEDED; - } - - WindowState focus = (WindowState)focusObj; - - if (DEBUG_INPUT) Slog.v( - TAG, "Dispatching to " + focus + ": " + event); - - if (uid != 0 && uid != focus.mSession.mUid) { - if (mContext.checkPermission( - android.Manifest.permission.INJECT_EVENTS, pid, uid) - != PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "Permission denied: injecting key event from pid " - + pid + " uid " + uid + " to window " + focus - + " owned by uid " + focus.mSession.mUid); - return INJECT_NO_PERMISSION; + + public void setEventDispatchingLw(boolean enabled) { + if (mInputDispatchEnabled != enabled) { + if (DEBUG_INPUT) { + Slog.v(TAG, "Setting event dispatching to " + enabled); + } + + mInputDispatchEnabled = enabled; + updateInputDispatchModeLw(); } } - - synchronized(mWindowMap) { - mKeyWaiter.bindTargetWindowLocked(focus); - } - - // NOSHIP extra state logging - mKeyWaiter.recordDispatchState(event, focus); - // END NOSHIP - - try { - if (DEBUG_INPUT || DEBUG_FOCUS) { - Slog.v(TAG, "Delivering key " + event.getKeyCode() - + " to " + focus); - } - focus.mClient.dispatchKey(event); - return INJECT_SUCCEEDED; - } catch (android.os.RemoteException e) { - Slog.i(TAG, "WINDOW DIED during key dispatch: " + focus); - try { - removeWindow(focus.mSession, focus.mClient); - } catch (java.util.NoSuchElementException ex) { - // This will happen if the window has already been - // removed. - } + + private void updateInputDispatchModeLw() { + mInputManager.setInputDispatchMode(mInputDispatchEnabled, mInputDispatchFrozen); } - - return INJECT_FAILED; } public void pauseKeyDispatching(IBinder _token) { @@ -5440,7 +5371,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { WindowToken token = mTokenMap.get(_token); if (token != null) { - mKeyWaiter.pauseDispatchingLocked(token); + mInputMonitor.pauseDispatchingLw(token); } } } @@ -5454,7 +5385,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { WindowToken token = mTokenMap.get(_token); if (token != null) { - mKeyWaiter.resumeDispatchingLocked(token); + mInputMonitor.resumeDispatchingLw(token); } } } @@ -5466,12 +5397,14 @@ public class WindowManagerService extends IWindowManager.Stub } synchronized (mWindowMap) { - mKeyWaiter.setEventDispatchingLocked(enabled); + mInputMonitor.setEventDispatchingLw(enabled); } } /** * Injects a keystroke event into the UI. + * Even when sync is false, this method may block while waiting for current + * input events to be dispatched. * * @param ev A motion event describing the keystroke action. (Be sure to use * {@link SystemClock#uptimeMillis()} as the timebase.) @@ -5488,34 +5421,36 @@ public class WindowManagerService extends IWindowManager.Stub int metaState = ev.getMetaState(); int deviceId = ev.getDeviceId(); int scancode = ev.getScanCode(); + int source = ev.getSource(); int flags = ev.getFlags(); + + if (source == InputDevice.SOURCE_UNKNOWN) { + source = InputDevice.SOURCE_KEYBOARD; + } if (eventTime == 0) eventTime = SystemClock.uptimeMillis(); if (downTime == 0) downTime = eventTime; KeyEvent newEvent = new KeyEvent(downTime, eventTime, action, code, repeatCount, metaState, - deviceId, scancode, flags | KeyEvent.FLAG_FROM_SYSTEM); + deviceId, scancode, flags | KeyEvent.FLAG_FROM_SYSTEM, source); final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); - final int result = dispatchKey(newEvent, pid, uid); - if (sync) { - mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid); - } + + final int result = mInputManager.injectInputEvent(newEvent, pid, uid, + sync ? InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH + : InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT, + INJECTION_TIMEOUT_MILLIS); + Binder.restoreCallingIdentity(ident); - switch (result) { - case INJECT_NO_PERMISSION: - throw new SecurityException( - "Injecting to another application requires INJECT_EVENTS permission"); - case INJECT_SUCCEEDED: - return true; - } - return false; + return reportInjectionResult(result); } /** * Inject a pointer (touch) event into the UI. + * Even when sync is false, this method may block while waiting for current + * input events to be dispatched. * * @param ev A motion event describing the pointer (touch) action. (As noted in * {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use @@ -5527,23 +5462,25 @@ public class WindowManagerService extends IWindowManager.Stub final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); - final int result = dispatchPointer(null, ev, pid, uid); - if (sync) { - mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid); + + MotionEvent newEvent = MotionEvent.obtain(ev); + if ((newEvent.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) { + newEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN); } + + final int result = mInputManager.injectInputEvent(newEvent, pid, uid, + sync ? InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH + : InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT, + INJECTION_TIMEOUT_MILLIS); + Binder.restoreCallingIdentity(ident); - switch (result) { - case INJECT_NO_PERMISSION: - throw new SecurityException( - "Injecting to another application requires INJECT_EVENTS permission"); - case INJECT_SUCCEEDED: - return true; - } - return false; + return reportInjectionResult(result); } /** * Inject a trackball (navigation device) event into the UI. + * Even when sync is false, this method may block while waiting for current + * input events to be dispatched. * * @param ev A motion event describing the trackball action. (As noted in * {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use @@ -5555,19 +5492,59 @@ public class WindowManagerService extends IWindowManager.Stub final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); - final int result = dispatchTrackball(null, ev, pid, uid); - if (sync) { - mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid); + + MotionEvent newEvent = MotionEvent.obtain(ev); + if ((newEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) == 0) { + newEvent.setSource(InputDevice.SOURCE_TRACKBALL); } + + final int result = mInputManager.injectInputEvent(newEvent, pid, uid, + sync ? InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH + : InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT, + INJECTION_TIMEOUT_MILLIS); + + Binder.restoreCallingIdentity(ident); + return reportInjectionResult(result); + } + + /** + * Inject an input event into the UI without waiting for dispatch to commence. + * This variant is useful for fire-and-forget input event injection. It does not + * block any longer than it takes to enqueue the input event. + * + * @param ev An input event. (Be sure to set the input source correctly.) + * @return Returns true if event was dispatched, false if it was dropped for any reason + */ + public boolean injectInputEventNoWait(InputEvent ev) { + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); + + final int result = mInputManager.injectInputEvent(ev, pid, uid, + InputManager.INPUT_EVENT_INJECTION_SYNC_NONE, + INJECTION_TIMEOUT_MILLIS); + Binder.restoreCallingIdentity(ident); + return reportInjectionResult(result); + } + + private boolean reportInjectionResult(int result) { switch (result) { - case INJECT_NO_PERMISSION: + case InputManager.INPUT_EVENT_INJECTION_PERMISSION_DENIED: + Slog.w(TAG, "Input event injection permission denied."); throw new SecurityException( "Injecting to another application requires INJECT_EVENTS permission"); - case INJECT_SUCCEEDED: + case InputManager.INPUT_EVENT_INJECTION_SUCCEEDED: + //Slog.v(TAG, "Input event injection succeeded."); return true; + case InputManager.INPUT_EVENT_INJECTION_TIMED_OUT: + Slog.w(TAG, "Input event injection timed out."); + return false; + case InputManager.INPUT_EVENT_INJECTION_FAILED: + default: + Slog.w(TAG, "Input event injection failed."); + return false; } - return false; } private WindowState getFocusedWindow() { @@ -5580,894 +5557,6 @@ public class WindowManagerService extends IWindowManager.Stub return mCurrentFocus; } - /** - * This class holds the state for dispatching key events. This state - * is protected by the KeyWaiter instance, NOT by the window lock. You - * can be holding the main window lock while acquire the KeyWaiter lock, - * but not the other way around. - */ - final class KeyWaiter { - // NOSHIP debugging - public class DispatchState { - private KeyEvent event; - private WindowState focus; - private long time; - private WindowState lastWin; - private IBinder lastBinder; - private boolean finished; - private boolean gotFirstWindow; - private boolean eventDispatching; - private long timeToSwitch; - private boolean wasFrozen; - private boolean focusPaused; - private WindowState curFocus; - - DispatchState(KeyEvent theEvent, WindowState theFocus) { - focus = theFocus; - event = theEvent; - time = System.currentTimeMillis(); - // snapshot KeyWaiter state - lastWin = mLastWin; - lastBinder = mLastBinder; - finished = mFinished; - gotFirstWindow = mGotFirstWindow; - eventDispatching = mEventDispatching; - timeToSwitch = mTimeToSwitch; - wasFrozen = mWasFrozen; - curFocus = mCurrentFocus; - // cache the paused state at ctor time as well - if (theFocus == null || theFocus.mToken == null) { - focusPaused = false; - } else { - focusPaused = theFocus.mToken.paused; - } - } - - public String toString() { - return "{{" + event + " to " + focus + " @ " + time - + " lw=" + lastWin + " lb=" + lastBinder - + " fin=" + finished + " gfw=" + gotFirstWindow - + " ed=" + eventDispatching + " tts=" + timeToSwitch - + " wf=" + wasFrozen + " fp=" + focusPaused - + " mcf=" + curFocus + "}}"; - } - }; - private DispatchState mDispatchState = null; - public void recordDispatchState(KeyEvent theEvent, WindowState theFocus) { - mDispatchState = new DispatchState(theEvent, theFocus); - } - // END NOSHIP - - public static final int RETURN_NOTHING = 0; - public static final int RETURN_PENDING_POINTER = 1; - public static final int RETURN_PENDING_TRACKBALL = 2; - - final Object SKIP_TARGET_TOKEN = new Object(); - final Object CONSUMED_EVENT_TOKEN = new Object(); - - private WindowState mLastWin = null; - private IBinder mLastBinder = null; - private boolean mFinished = true; - private boolean mGotFirstWindow = false; - private boolean mEventDispatching = true; - private long mTimeToSwitch = 0; - /* package */ boolean mWasFrozen = false; - - // Target of Motion events - WindowState mMotionTarget; - - // Windows above the target who would like to receive an "outside" - // touch event for any down events outside of them. - WindowState mOutsideTouchTargets; - - /** - * Wait for the last event dispatch to complete, then find the next - * target that should receive the given event and wait for that one - * to be ready to receive it. - */ - Object waitForNextEventTarget(KeyEvent nextKey, QueuedEvent qev, - MotionEvent nextMotion, boolean isPointerEvent, - boolean failIfTimeout, int callingPid, int callingUid) { - long startTime = SystemClock.uptimeMillis(); - long keyDispatchingTimeout = 5 * 1000; - long waitedFor = 0; - - while (true) { - // Figure out which window we care about. It is either the - // last window we are waiting to have process the event or, - // if none, then the next window we think the event should go - // to. Note: we retrieve mLastWin outside of the lock, so - // it may change before we lock. Thus we must check it again. - WindowState targetWin = mLastWin; - boolean targetIsNew = targetWin == null; - if (DEBUG_INPUT) Slog.v( - TAG, "waitForLastKey: mFinished=" + mFinished + - ", mLastWin=" + mLastWin); - if (targetIsNew) { - Object target = findTargetWindow(nextKey, qev, nextMotion, - isPointerEvent, callingPid, callingUid); - if (target == SKIP_TARGET_TOKEN) { - // The user has pressed a special key, and we are - // dropping all pending events before it. - if (DEBUG_INPUT) Slog.v(TAG, "Skipping: " + nextKey - + " " + nextMotion); - return null; - } - if (target == CONSUMED_EVENT_TOKEN) { - if (DEBUG_INPUT) Slog.v(TAG, "Consumed: " + nextKey - + " " + nextMotion); - return target; - } - targetWin = (WindowState)target; - } - - AppWindowToken targetApp = null; - - // Now: is it okay to send the next event to this window? - synchronized (this) { - // First: did we come here based on the last window not - // being null, but it changed by the time we got here? - // If so, try again. - if (!targetIsNew && mLastWin == null) { - continue; - } - - // We never dispatch events if not finished with the - // last one, or the display is frozen. - if (mFinished && !mDisplayFrozen) { - // If event dispatching is disabled, then we - // just consume the events. - if (!mEventDispatching) { - if (DEBUG_INPUT) Slog.v(TAG, - "Skipping event; dispatching disabled: " - + nextKey + " " + nextMotion); - return null; - } - if (targetWin != null) { - // If this is a new target, and that target is not - // paused or unresponsive, then all looks good to - // handle the event. - if (targetIsNew && !targetWin.mToken.paused) { - return targetWin; - } - - // If we didn't find a target window, and there is no - // focused app window, then just eat the events. - } else if (mFocusedApp == null) { - if (DEBUG_INPUT) Slog.v(TAG, - "Skipping event; no focused app: " - + nextKey + " " + nextMotion); - return null; - } - } - - if (DEBUG_INPUT) Slog.v( - TAG, "Waiting for last key in " + mLastBinder - + " target=" + targetWin - + " mFinished=" + mFinished - + " mDisplayFrozen=" + mDisplayFrozen - + " targetIsNew=" + targetIsNew - + " paused=" - + (targetWin != null ? targetWin.mToken.paused : false) - + " mFocusedApp=" + mFocusedApp - + " mCurrentFocus=" + mCurrentFocus); - - targetApp = targetWin != null - ? targetWin.mAppToken : mFocusedApp; - - long curTimeout = keyDispatchingTimeout; - if (mTimeToSwitch != 0) { - long now = SystemClock.uptimeMillis(); - if (mTimeToSwitch <= now) { - // If an app switch key has been pressed, and we have - // waited too long for the current app to finish - // processing keys, then wait no more! - doFinishedKeyLocked(false); - continue; - } - long switchTimeout = mTimeToSwitch - now; - if (curTimeout > switchTimeout) { - curTimeout = switchTimeout; - } - } - - try { - // after that continue - // processing keys, so we don't get stuck. - if (DEBUG_INPUT) Slog.v( - TAG, "Waiting for key dispatch: " + curTimeout); - wait(curTimeout); - if (DEBUG_INPUT) Slog.v(TAG, "Finished waiting @" - + SystemClock.uptimeMillis() + " startTime=" - + startTime + " switchTime=" + mTimeToSwitch - + " target=" + targetWin + " mLW=" + mLastWin - + " mLB=" + mLastBinder + " fin=" + mFinished - + " mCurrentFocus=" + mCurrentFocus); - } catch (InterruptedException e) { - } - } - - // If we were frozen during configuration change, restart the - // timeout checks from now; otherwise look at whether we timed - // out before awakening. - if (mWasFrozen) { - waitedFor = 0; - mWasFrozen = false; - } else { - waitedFor = SystemClock.uptimeMillis() - startTime; - } - - if (waitedFor >= keyDispatchingTimeout && mTimeToSwitch == 0) { - IApplicationToken at = null; - synchronized (this) { - Slog.w(TAG, "Key dispatching timed out sending to " + - (targetWin != null ? targetWin.mAttrs.getTitle() - : "<null>: no window ready for key dispatch")); - // NOSHIP debugging - Slog.w(TAG, "Previous dispatch state: " + mDispatchState); - Slog.w(TAG, "Current dispatch state: " + - new DispatchState(nextKey, targetWin)); - // END NOSHIP - //dump(); - if (targetWin != null) { - at = targetWin.getAppToken(); - } else if (targetApp != null) { - at = targetApp.appToken; - } - } - - boolean abort = true; - if (at != null) { - try { - long timeout = at.getKeyDispatchingTimeout(); - if (timeout > waitedFor) { - // we did not wait the proper amount of time for this application. - // set the timeout to be the real timeout and wait again. - keyDispatchingTimeout = timeout - waitedFor; - continue; - } else { - abort = at.keyDispatchingTimedOut(); - } - } catch (RemoteException ex) { - } - } - - synchronized (this) { - if (abort && (mLastWin == targetWin || targetWin == null)) { - mFinished = true; - if (mLastWin != null) { - if (DEBUG_INPUT) Slog.v(TAG, - "Window " + mLastWin + - " timed out on key input"); - if (mLastWin.mToken.paused) { - Slog.w(TAG, "Un-pausing dispatching to this window"); - mLastWin.mToken.paused = false; - } - } - if (mMotionTarget == targetWin) { - mMotionTarget = null; - } - mLastWin = null; - mLastBinder = null; - if (failIfTimeout || targetWin == null) { - return null; - } - } else { - Slog.w(TAG, "Continuing to wait for key to be dispatched"); - startTime = SystemClock.uptimeMillis(); - } - } - } - } - } - - Object findTargetWindow(KeyEvent nextKey, QueuedEvent qev, - MotionEvent nextMotion, boolean isPointerEvent, - int callingPid, int callingUid) { - mOutsideTouchTargets = null; - - if (nextKey != null) { - // Find the target window for a normal key event. - final int keycode = nextKey.getKeyCode(); - final int repeatCount = nextKey.getRepeatCount(); - final boolean down = nextKey.getAction() != KeyEvent.ACTION_UP; - boolean dispatch = mKeyWaiter.checkShouldDispatchKey(keycode); - - if (!dispatch) { - if (callingUid == 0 || - mContext.checkPermission( - android.Manifest.permission.INJECT_EVENTS, - callingPid, callingUid) - == PackageManager.PERMISSION_GRANTED) { - mPolicy.interceptKeyTi(null, keycode, - nextKey.getMetaState(), down, repeatCount, - nextKey.getFlags()); - } - Slog.w(TAG, "Event timeout during app switch: dropping " - + nextKey); - return SKIP_TARGET_TOKEN; - } - - // System.out.println("##### [" + SystemClock.uptimeMillis() + "] WindowManagerService.dispatchKey(" + keycode + ", " + down + ", " + repeatCount + ")"); - - WindowState focus = null; - synchronized(mWindowMap) { - focus = getFocusedWindowLocked(); - } - - wakeupIfNeeded(focus, LocalPowerManager.BUTTON_EVENT); - - if (callingUid == 0 || - (focus != null && callingUid == focus.mSession.mUid) || - mContext.checkPermission( - android.Manifest.permission.INJECT_EVENTS, - callingPid, callingUid) - == PackageManager.PERMISSION_GRANTED) { - if (mPolicy.interceptKeyTi(focus, - keycode, nextKey.getMetaState(), down, repeatCount, - nextKey.getFlags())) { - return CONSUMED_EVENT_TOKEN; - } - } - - return focus; - - } else if (!isPointerEvent) { - boolean dispatch = mKeyWaiter.checkShouldDispatchKey(-1); - if (!dispatch) { - Slog.w(TAG, "Event timeout during app switch: dropping trackball " - + nextMotion); - return SKIP_TARGET_TOKEN; - } - - WindowState focus = null; - synchronized(mWindowMap) { - focus = getFocusedWindowLocked(); - } - - wakeupIfNeeded(focus, LocalPowerManager.BUTTON_EVENT); - return focus; - } - - if (nextMotion == null) { - return SKIP_TARGET_TOKEN; - } - - boolean dispatch = mKeyWaiter.checkShouldDispatchKey( - KeyEvent.KEYCODE_UNKNOWN); - if (!dispatch) { - Slog.w(TAG, "Event timeout during app switch: dropping pointer " - + nextMotion); - return SKIP_TARGET_TOKEN; - } - - // Find the target window for a pointer event. - int action = nextMotion.getAction(); - final float xf = nextMotion.getX(); - final float yf = nextMotion.getY(); - final long eventTime = nextMotion.getEventTime(); - - final boolean screenWasOff = qev != null - && (qev.flags&WindowManagerPolicy.FLAG_BRIGHT_HERE) != 0; - - WindowState target = null; - - synchronized(mWindowMap) { - synchronized (this) { - if (action == MotionEvent.ACTION_DOWN) { - if (mMotionTarget != null) { - // this is weird, we got a pen down, but we thought it was - // already down! - // XXX: We should probably send an ACTION_UP to the current - // target. - Slog.w(TAG, "Pointer down received while already down in: " - + mMotionTarget); - mMotionTarget = null; - } - - // ACTION_DOWN is special, because we need to lock next events to - // the window we'll land onto. - final int x = (int)xf; - final int y = (int)yf; - - final ArrayList windows = mWindows; - final int N = windows.size(); - WindowState topErrWindow = null; - final Rect tmpRect = mTempRect; - for (int i=N-1; i>=0; i--) { - WindowState child = (WindowState)windows.get(i); - //Slog.i(TAG, "Checking dispatch to: " + child); - final int flags = child.mAttrs.flags; - if ((flags & WindowManager.LayoutParams.FLAG_SYSTEM_ERROR) != 0) { - if (topErrWindow == null) { - topErrWindow = child; - } - } - if (!child.isVisibleLw()) { - //Slog.i(TAG, "Not visible!"); - continue; - } - if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) { - //Slog.i(TAG, "Not touchable!"); - if ((flags & WindowManager.LayoutParams - .FLAG_WATCH_OUTSIDE_TOUCH) != 0) { - child.mNextOutsideTouch = mOutsideTouchTargets; - mOutsideTouchTargets = child; - } - continue; - } - tmpRect.set(child.mFrame); - if (child.mTouchableInsets == ViewTreeObserver - .InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT) { - // The touch is inside of the window if it is - // inside the frame, AND the content part of that - // frame that was given by the application. - tmpRect.left += child.mGivenContentInsets.left; - tmpRect.top += child.mGivenContentInsets.top; - tmpRect.right -= child.mGivenContentInsets.right; - tmpRect.bottom -= child.mGivenContentInsets.bottom; - } else if (child.mTouchableInsets == ViewTreeObserver - .InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE) { - // The touch is inside of the window if it is - // inside the frame, AND the visible part of that - // frame that was given by the application. - tmpRect.left += child.mGivenVisibleInsets.left; - tmpRect.top += child.mGivenVisibleInsets.top; - tmpRect.right -= child.mGivenVisibleInsets.right; - tmpRect.bottom -= child.mGivenVisibleInsets.bottom; - } - final int touchFlags = flags & - (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - |WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL); - if (tmpRect.contains(x, y) || touchFlags == 0) { - //Slog.i(TAG, "Using this target!"); - if (!screenWasOff || (flags & - WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING) != 0) { - mMotionTarget = child; - } else { - //Slog.i(TAG, "Waking, skip!"); - mMotionTarget = null; - } - break; - } - - if ((flags & WindowManager.LayoutParams - .FLAG_WATCH_OUTSIDE_TOUCH) != 0) { - child.mNextOutsideTouch = mOutsideTouchTargets; - mOutsideTouchTargets = child; - //Slog.i(TAG, "Adding to outside target list: " + child); - } - } - - // if there's an error window but it's not accepting - // focus (typically because it is not yet visible) just - // wait for it -- any other focused window may in fact - // be in ANR state. - if (topErrWindow != null && mMotionTarget != topErrWindow) { - mMotionTarget = null; - } - } - - target = mMotionTarget; - } - } - - wakeupIfNeeded(target, eventType(nextMotion)); - - // Pointer events are a little different -- if there isn't a - // target found for any event, then just drop it. - return target != null ? target : SKIP_TARGET_TOKEN; - } - - boolean checkShouldDispatchKey(int keycode) { - synchronized (this) { - if (mPolicy.isAppSwitchKeyTqTiLwLi(keycode)) { - mTimeToSwitch = 0; - return true; - } - if (mTimeToSwitch != 0 - && mTimeToSwitch < SystemClock.uptimeMillis()) { - return false; - } - return true; - } - } - - void bindTargetWindowLocked(WindowState win, - int pendingWhat, QueuedEvent pendingMotion) { - synchronized (this) { - bindTargetWindowLockedLocked(win, pendingWhat, pendingMotion); - } - } - - void bindTargetWindowLocked(WindowState win) { - synchronized (this) { - bindTargetWindowLockedLocked(win, RETURN_NOTHING, null); - } - } - - void bindTargetWindowLockedLocked(WindowState win, - int pendingWhat, QueuedEvent pendingMotion) { - mLastWin = win; - mLastBinder = win.mClient.asBinder(); - mFinished = false; - if (pendingMotion != null) { - final Session s = win.mSession; - if (pendingWhat == RETURN_PENDING_POINTER) { - releasePendingPointerLocked(s); - s.mPendingPointerMove = pendingMotion; - s.mPendingPointerWindow = win; - if (DEBUG_INPUT) Slog.v(TAG, - "bindTargetToWindow " + s.mPendingPointerMove); - } else if (pendingWhat == RETURN_PENDING_TRACKBALL) { - releasePendingTrackballLocked(s); - s.mPendingTrackballMove = pendingMotion; - s.mPendingTrackballWindow = win; - } - } - } - - void releasePendingPointerLocked(Session s) { - if (DEBUG_INPUT) Slog.v(TAG, - "releasePendingPointer " + s.mPendingPointerMove); - if (s.mPendingPointerMove != null) { - mQueue.recycleEvent(s.mPendingPointerMove); - s.mPendingPointerMove = null; - } - } - - void releasePendingTrackballLocked(Session s) { - if (s.mPendingTrackballMove != null) { - mQueue.recycleEvent(s.mPendingTrackballMove); - s.mPendingTrackballMove = null; - } - } - - void releaseMotionTarget(WindowState win) { - if (mMotionTarget == win) { - mMotionTarget = null; - } - } - - MotionEvent finishedKey(Session session, IWindow client, boolean force, - int returnWhat) { - if (DEBUG_INPUT) Slog.v( - TAG, "finishedKey: client=" + client + ", force=" + force); - - if (client == null) { - return null; - } - - MotionEvent res = null; - QueuedEvent qev = null; - WindowState win = null; - - synchronized (this) { - if (DEBUG_INPUT) Slog.v( - TAG, "finishedKey: client=" + client.asBinder() - + ", force=" + force + ", last=" + mLastBinder - + " (token=" + (mLastWin != null ? mLastWin.mToken : null) + ")"); - - if (returnWhat == RETURN_PENDING_POINTER) { - qev = session.mPendingPointerMove; - win = session.mPendingPointerWindow; - session.mPendingPointerMove = null; - session.mPendingPointerWindow = null; - } else if (returnWhat == RETURN_PENDING_TRACKBALL) { - qev = session.mPendingTrackballMove; - win = session.mPendingTrackballWindow; - session.mPendingTrackballMove = null; - session.mPendingTrackballWindow = null; - } - - if (mLastBinder == client.asBinder()) { - if (DEBUG_INPUT) Slog.v( - TAG, "finishedKey: last paused=" - + ((mLastWin != null) ? mLastWin.mToken.paused : "null")); - if (mLastWin != null && (!mLastWin.mToken.paused || force - || !mEventDispatching)) { - doFinishedKeyLocked(true); - } else { - // Make sure to wake up anyone currently waiting to - // dispatch a key, so they can re-evaluate their - // current situation. - mFinished = true; - notifyAll(); - } - } - - if (qev != null) { - res = (MotionEvent)qev.event; - if (DEBUG_INPUT) Slog.v(TAG, - "Returning pending motion: " + res); - mQueue.recycleEvent(qev); - if (win != null && returnWhat == RETURN_PENDING_POINTER) { - res.offsetLocation(-win.mFrame.left, -win.mFrame.top); - } - } - } - - if (res != null && returnWhat == RETURN_PENDING_POINTER) { - synchronized (mWindowMap) { - dispatchPointerElsewhereLocked(win, win, res, res.getEventTime(), false); - } - } - - return res; - } - - void tickle() { - synchronized (this) { - notifyAll(); - } - } - - void handleNewWindowLocked(WindowState newWindow) { - if (!newWindow.canReceiveKeys()) { - return; - } - synchronized (this) { - if (DEBUG_INPUT) Slog.v( - TAG, "New key dispatch window: win=" - + newWindow.mClient.asBinder() - + ", last=" + mLastBinder - + " (token=" + (mLastWin != null ? mLastWin.mToken : null) - + "), finished=" + mFinished + ", paused=" - + newWindow.mToken.paused); - - // 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; - - mGotFirstWindow = true; - - if ((newWindow.mAttrs.flags & FLAG_SYSTEM_ERROR) != 0) { - if (DEBUG_INPUT) Slog.v(TAG, - "New SYSTEM_ERROR window; resetting state"); - mLastWin = null; - mLastBinder = null; - mMotionTarget = null; - mFinished = true; - } else if (mLastWin != null) { - // If the new window is above the window we are - // waiting on, then stop waiting and let key dispatching - // start on the new guy. - if (DEBUG_INPUT) Slog.v( - TAG, "Last win layer=" + mLastWin.mLayer - + ", new win layer=" + newWindow.mLayer); - if (newWindow.mLayer >= mLastWin.mLayer) { - // The new window is above the old; finish pending input to the last - // window and start directing it to the new one. - mLastWin.mToken.paused = false; - doFinishedKeyLocked(false); // does a notifyAll() - return; - } - } - - // Now that we've put a new window state in place, make the event waiter - // take notice and retarget its attentions. - notifyAll(); - } - } - - void pauseDispatchingLocked(WindowToken token) { - synchronized (this) - { - if (DEBUG_INPUT) Slog.v(TAG, "Pausing WindowToken " + token); - token.paused = true; - - /* - if (mLastWin != null && !mFinished && mLastWin.mBaseLayer <= layer) { - mPaused = true; - } else { - if (mLastWin == null) { - Slog.i(TAG, "Key dispatching not paused: no last window."); - } else if (mFinished) { - Slog.i(TAG, "Key dispatching not paused: finished last key."); - } else { - Slog.i(TAG, "Key dispatching not paused: window in higher layer."); - } - } - */ - } - } - - void resumeDispatchingLocked(WindowToken token) { - synchronized (this) { - if (token.paused) { - if (DEBUG_INPUT) Slog.v( - TAG, "Resuming WindowToken " + token - + ", last=" + mLastBinder - + " (token=" + (mLastWin != null ? mLastWin.mToken : null) - + "), finished=" + mFinished + ", paused=" - + token.paused); - token.paused = false; - if (mLastWin != null && mLastWin.mToken == token && mFinished) { - doFinishedKeyLocked(false); - } else { - notifyAll(); - } - } - } - } - - void setEventDispatchingLocked(boolean enabled) { - synchronized (this) { - mEventDispatching = enabled; - notifyAll(); - } - } - - void appSwitchComing() { - synchronized (this) { - // Don't wait for more than .5 seconds for app to finish - // processing the pending events. - long now = SystemClock.uptimeMillis() + 500; - if (DEBUG_INPUT) Slog.v(TAG, "appSwitchComing: " + now); - if (mTimeToSwitch == 0 || now < mTimeToSwitch) { - mTimeToSwitch = now; - } - notifyAll(); - } - } - - private final void doFinishedKeyLocked(boolean force) { - if (mLastWin != null) { - releasePendingPointerLocked(mLastWin.mSession); - releasePendingTrackballLocked(mLastWin.mSession); - } - - if (force || mLastWin == null || !mLastWin.mToken.paused - || !mLastWin.isVisibleLw()) { - // If the current window has been paused, we aren't -really- - // finished... so let the waiters still wait. - mLastWin = null; - mLastBinder = null; - } - mFinished = true; - notifyAll(); - } - } - - private class KeyQ extends KeyInputQueue - implements KeyInputQueue.FilterCallback { - PowerManager.WakeLock mHoldingScreen; - - KeyQ() { - super(mContext, WindowManagerService.this); - PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); - mHoldingScreen = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, - "KEEP_SCREEN_ON_FLAG"); - mHoldingScreen.setReferenceCounted(false); - } - - @Override - boolean preprocessEvent(InputDevice device, RawInputEvent event) { - if (mPolicy.preprocessInputEventTq(event)) { - return true; - } - - switch (event.type) { - case RawInputEvent.EV_KEY: { - // XXX begin hack - if (DEBUG) { - if (event.keycode == KeyEvent.KEYCODE_G) { - if (event.value != 0) { - // G down - mPolicy.screenTurnedOff(WindowManagerPolicy.OFF_BECAUSE_OF_USER); - } - return false; - } - if (event.keycode == KeyEvent.KEYCODE_D) { - if (event.value != 0) { - //dump(); - } - return false; - } - } - // XXX end hack - - boolean screenIsOff = !mPowerManager.isScreenOn(); - boolean screenIsDim = !mPowerManager.isScreenBright(); - int actions = mPolicy.interceptKeyTq(event, !screenIsOff); - - if ((actions & WindowManagerPolicy.ACTION_GO_TO_SLEEP) != 0) { - mPowerManager.goToSleep(event.when); - } - - if (screenIsOff) { - event.flags |= WindowManagerPolicy.FLAG_WOKE_HERE; - } - if (screenIsDim) { - event.flags |= WindowManagerPolicy.FLAG_BRIGHT_HERE; - } - if ((actions & WindowManagerPolicy.ACTION_POKE_USER_ACTIVITY) != 0) { - mPowerManager.userActivity(event.when, false, - LocalPowerManager.BUTTON_EVENT, false); - } - - if ((actions & WindowManagerPolicy.ACTION_PASS_TO_USER) != 0) { - if (event.value != 0 && mPolicy.isAppSwitchKeyTqTiLwLi(event.keycode)) { - filterQueue(this); - mKeyWaiter.appSwitchComing(); - } - return true; - } else { - return false; - } - } - - case RawInputEvent.EV_REL: { - boolean screenIsOff = !mPowerManager.isScreenOn(); - boolean screenIsDim = !mPowerManager.isScreenBright(); - if (screenIsOff) { - if (!mPolicy.isWakeRelMovementTq(event.deviceId, - device.classes, event)) { - //Slog.i(TAG, "dropping because screenIsOff and !isWakeKey"); - return false; - } - event.flags |= WindowManagerPolicy.FLAG_WOKE_HERE; - } - if (screenIsDim) { - event.flags |= WindowManagerPolicy.FLAG_BRIGHT_HERE; - } - return true; - } - - case RawInputEvent.EV_ABS: { - boolean screenIsOff = !mPowerManager.isScreenOn(); - boolean screenIsDim = !mPowerManager.isScreenBright(); - if (screenIsOff) { - if (!mPolicy.isWakeAbsMovementTq(event.deviceId, - device.classes, event)) { - //Slog.i(TAG, "dropping because screenIsOff and !isWakeKey"); - return false; - } - event.flags |= WindowManagerPolicy.FLAG_WOKE_HERE; - } - if (screenIsDim) { - event.flags |= WindowManagerPolicy.FLAG_BRIGHT_HERE; - } - return true; - } - - default: - return true; - } - } - - public int filterEvent(QueuedEvent ev) { - switch (ev.classType) { - case RawInputEvent.CLASS_KEYBOARD: - KeyEvent ke = (KeyEvent)ev.event; - if (mPolicy.isMovementKeyTi(ke.getKeyCode())) { - Slog.w(TAG, "Dropping movement key during app switch: " - + ke.getKeyCode() + ", action=" + ke.getAction()); - return FILTER_REMOVE; - } - return FILTER_ABORT; - default: - return FILTER_KEEP; - } - } - - /** - * Must be called with the main window manager lock held. - */ - void setHoldScreenLocked(boolean holding) { - boolean state = mHoldingScreen.isHeld(); - if (holding != state) { - if (holding) { - mHoldingScreen.acquire(); - } else { - mPolicy.screenOnStoppedLw(); - mHoldingScreen.release(); - } - } - } - } - public boolean detectSafeMode() { mSafeMode = mPolicy.detectSafeMode(); return mSafeMode; @@ -6477,219 +5566,6 @@ public class WindowManagerService extends IWindowManager.Stub mPolicy.systemReady(); } - private final class InputDispatcherThread extends Thread { - // Time to wait when there is nothing to do: 9999 seconds. - static final int LONG_WAIT=9999*1000; - - public InputDispatcherThread() { - super("InputDispatcher"); - } - - @Override - public void run() { - while (true) { - try { - process(); - } catch (Exception e) { - Slog.e(TAG, "Exception in input dispatcher", e); - } - } - } - - private void process() { - android.os.Process.setThreadPriority( - android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY); - - // The last key event we saw - KeyEvent lastKey = null; - - // Last keydown time for auto-repeating keys - long lastKeyTime = SystemClock.uptimeMillis(); - long nextKeyTime = lastKeyTime+LONG_WAIT; - long downTime = 0; - - // How many successive repeats we generated - int keyRepeatCount = 0; - - // Need to report that configuration has changed? - boolean configChanged = false; - - while (true) { - long curTime = SystemClock.uptimeMillis(); - - if (DEBUG_INPUT) Slog.v( - TAG, "Waiting for next key: now=" + curTime - + ", repeat @ " + nextKeyTime); - - // Retrieve next event, waiting only as long as the next - // repeat timeout. If the configuration has changed, then - // don't wait at all -- we'll report the change as soon as - // we have processed all events. - QueuedEvent ev = mQueue.getEvent( - (int)((!configChanged && curTime < nextKeyTime) - ? (nextKeyTime-curTime) : 0)); - - if (DEBUG_INPUT && ev != null) Slog.v( - TAG, "Event: type=" + ev.classType + " data=" + ev.event); - - if (MEASURE_LATENCY) { - lt.sample("2 got event ", System.nanoTime() - ev.whenNano); - } - - if (lastKey != null && !mPolicy.allowKeyRepeat()) { - // cancel key repeat at the request of the policy. - lastKey = null; - downTime = 0; - lastKeyTime = curTime; - nextKeyTime = curTime + LONG_WAIT; - } - try { - if (ev != null) { - curTime = SystemClock.uptimeMillis(); - int eventType; - if (ev.classType == RawInputEvent.CLASS_TOUCHSCREEN) { - eventType = eventType((MotionEvent)ev.event); - } else if (ev.classType == RawInputEvent.CLASS_KEYBOARD || - ev.classType == RawInputEvent.CLASS_TRACKBALL) { - eventType = LocalPowerManager.BUTTON_EVENT; - } else { - eventType = LocalPowerManager.OTHER_EVENT; - } - try { - if ((curTime - mLastBatteryStatsCallTime) - >= MIN_TIME_BETWEEN_USERACTIVITIES) { - mLastBatteryStatsCallTime = curTime; - mBatteryStats.noteInputEvent(); - } - } catch (RemoteException e) { - // Ignore - } - - if (ev.classType == RawInputEvent.CLASS_CONFIGURATION_CHANGED) { - // do not wake screen in this case - } else if (eventType != TOUCH_EVENT - && eventType != LONG_TOUCH_EVENT - && eventType != CHEEK_EVENT) { - mPowerManager.userActivity(curTime, false, - eventType, false); - } else if (mLastTouchEventType != eventType - || (curTime - mLastUserActivityCallTime) - >= MIN_TIME_BETWEEN_USERACTIVITIES) { - mLastUserActivityCallTime = curTime; - mLastTouchEventType = eventType; - mPowerManager.userActivity(curTime, false, - eventType, false); - } - - switch (ev.classType) { - case RawInputEvent.CLASS_KEYBOARD: - KeyEvent ke = (KeyEvent)ev.event; - if (ke.isDown()) { - lastKeyTime = curTime; - if (lastKey != null && - ke.getKeyCode() == lastKey.getKeyCode()) { - keyRepeatCount++; - // Arbitrary long timeout to block - // repeating here since we know that - // the device driver takes care of it. - nextKeyTime = lastKeyTime + LONG_WAIT; - if (DEBUG_INPUT) Slog.v( - TAG, "Received repeated key down"); - } else { - downTime = curTime; - keyRepeatCount = 0; - nextKeyTime = lastKeyTime - + ViewConfiguration.getLongPressTimeout(); - if (DEBUG_INPUT) Slog.v( - TAG, "Received key down: first repeat @ " - + nextKeyTime); - } - lastKey = ke; - } else { - lastKey = null; - downTime = 0; - keyRepeatCount = 0; - // Arbitrary long timeout. - lastKeyTime = curTime; - nextKeyTime = curTime + LONG_WAIT; - if (DEBUG_INPUT) Slog.v( - TAG, "Received key up: ignore repeat @ " - + nextKeyTime); - } - if (keyRepeatCount > 0) { - dispatchKey(KeyEvent.changeTimeRepeat(ke, - ke.getEventTime(), keyRepeatCount), 0, 0); - } else { - dispatchKey(ke, 0, 0); - } - mQueue.recycleEvent(ev); - break; - case RawInputEvent.CLASS_TOUCHSCREEN: - //Slog.i(TAG, "Read next event " + ev); - dispatchPointer(ev, (MotionEvent)ev.event, 0, 0); - break; - case RawInputEvent.CLASS_TRACKBALL: - dispatchTrackball(ev, (MotionEvent)ev.event, 0, 0); - break; - case RawInputEvent.CLASS_CONFIGURATION_CHANGED: - configChanged = true; - break; - default: - mQueue.recycleEvent(ev); - break; - } - - } else if (configChanged) { - configChanged = false; - sendNewConfiguration(); - - } else if (lastKey != null) { - curTime = SystemClock.uptimeMillis(); - - // Timeout occurred while key was down. If it is at or - // past the key repeat time, dispatch the repeat. - if (DEBUG_INPUT) Slog.v( - TAG, "Key timeout: repeat=" + nextKeyTime - + ", now=" + curTime); - if (curTime < nextKeyTime) { - continue; - } - - lastKeyTime = nextKeyTime; - nextKeyTime = nextKeyTime + KEY_REPEAT_DELAY; - keyRepeatCount++; - if (DEBUG_INPUT) Slog.v( - TAG, "Key repeat: count=" + keyRepeatCount - + ", next @ " + nextKeyTime); - KeyEvent newEvent; - if (downTime != 0 && (downTime - + ViewConfiguration.getLongPressTimeout()) - <= curTime) { - newEvent = KeyEvent.changeTimeRepeat(lastKey, - curTime, keyRepeatCount, - lastKey.getFlags() | KeyEvent.FLAG_LONG_PRESS); - downTime = 0; - } else { - newEvent = KeyEvent.changeTimeRepeat(lastKey, - curTime, keyRepeatCount); - } - dispatchKey(newEvent, 0, 0); - - } else { - curTime = SystemClock.uptimeMillis(); - - lastKeyTime = curTime; - nextKeyTime = curTime + LONG_WAIT; - } - - } catch (Exception e) { - Slog.e(TAG, - "Input thread received uncaught exception: " + e, e); - } - } - } - } - // ------------------------------------------------------------- // Client Session State // ------------------------------------------------------------- @@ -6705,20 +5581,6 @@ public class WindowManagerService extends IWindowManager.Stub int mNumWindow = 0; boolean mClientDead = false; - /** - * Current pointer move event being dispatched to client window... must - * hold key lock to access. - */ - QueuedEvent mPendingPointerMove; - WindowState mPendingPointerWindow; - - /** - * Current trackball move event being dispatched to client window... must - * hold key lock to access. - */ - QueuedEvent mPendingTrackballMove; - WindowState mPendingTrackballWindow; - public Session(IInputMethodClient client, IInputContext inputContext) { mClient = client; mInputContext = inputContext; @@ -6794,8 +5656,14 @@ public class WindowManagerService extends IWindowManager.Stub } 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); + return addWindow(this, window, attrs, viewVisibility, outContentInsets, null); } public void remove(IWindow window) { @@ -6806,9 +5674,12 @@ public class WindowManagerService extends IWindowManager.Stub int requestedWidth, int requestedHeight, int viewFlags, boolean insetsPending, Rect outFrame, Rect outContentInsets, Rect outVisibleInsets, Configuration outConfig, Surface outSurface) { - return relayoutWindow(this, window, attrs, + //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) { @@ -6831,27 +5702,6 @@ public class WindowManagerService extends IWindowManager.Stub finishDrawingWindow(this, window); } - public void finishKey(IWindow window) { - if (localLOGV) Slog.v( - TAG, "IWindow finishKey called for " + window); - mKeyWaiter.finishedKey(this, window, false, - KeyWaiter.RETURN_NOTHING); - } - - public MotionEvent getPendingPointerMove(IWindow window) { - if (localLOGV) Slog.v( - TAG, "IWindow getPendingMotionEvent called for " + window); - return mKeyWaiter.finishedKey(this, window, false, - KeyWaiter.RETURN_PENDING_POINTER); - } - - public MotionEvent getPendingTrackballMove(IWindow window) { - if (localLOGV) Slog.v( - TAG, "IWindow getPendingMotionEvent called for " + window); - return mKeyWaiter.finishedKey(this, window, false, - KeyWaiter.RETURN_PENDING_TRACKBALL); - } - public void setInTouchMode(boolean mode) { synchronized(mWindowMap) { mInTouchMode = mode; @@ -6955,16 +5805,6 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(prefix); pw.print("mNumWindow="); pw.print(mNumWindow); pw.print(" mClientDead="); pw.print(mClientDead); pw.print(" mSurfaceSession="); pw.println(mSurfaceSession); - if (mPendingPointerWindow != null || mPendingPointerMove != null) { - pw.print(prefix); - pw.print("mPendingPointerWindow="); pw.print(mPendingPointerWindow); - pw.print(" mPendingPointerMove="); pw.println(mPendingPointerMove); - } - if (mPendingTrackballWindow != null || mPendingTrackballMove != null) { - pw.print(prefix); - pw.print("mPendingTrackballWindow="); pw.print(mPendingTrackballWindow); - pw.print(" mPendingTrackballMove="); pw.println(mPendingTrackballMove); - } } @Override @@ -6987,7 +5827,7 @@ public class WindowManagerService extends IWindowManager.Stub final WindowManager.LayoutParams mAttrs = new WindowManager.LayoutParams(); final DeathRecipient mDeathRecipient; final WindowState mAttachedWindow; - final ArrayList mChildWindows = new ArrayList(); + final ArrayList<WindowState> mChildWindows = new ArrayList<WindowState>(); final int mBaseLayer; final int mSubLayer; final boolean mLayoutAttached; @@ -7015,8 +5855,6 @@ public class WindowManagerService extends IWindowManager.Stub boolean mObscured; boolean mTurnOnScreen; - WindowState mNextOutsideTouch; - int mLayoutSeq = -1; Configuration mConfiguration = null; @@ -7164,6 +6002,9 @@ public class WindowManagerService extends IWindowManager.Stub int mSurfaceLayer; float mSurfaceAlpha; + // Input channel + InputChannel mInputChannel; + WindowState(Session s, IWindow c, WindowToken token, WindowState attachedWindow, WindowManager.LayoutParams a, int viewVisibility) { @@ -7391,6 +6232,12 @@ public class WindowManagerService extends IWindowManager.Stub 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; @@ -7524,14 +6371,6 @@ public class WindowManagerService extends IWindowManager.Stub } void destroySurfaceLocked() { - // Window is no longer on-screen, so can no longer receive - // key events... if we were waiting for it to finish - // handling a key event, the wait is over! - mKeyWaiter.finishedKey(mSession, mClient, true, - KeyWaiter.RETURN_NOTHING); - mKeyWaiter.releasePendingPointerLocked(mSession); - mKeyWaiter.releasePendingTrackballLocked(mSession); - if (mAppToken != null && this == mAppToken.startingWindow) { mAppToken.startingDisplayed = false; } @@ -7544,7 +6383,7 @@ public class WindowManagerService extends IWindowManager.Stub int i = mChildWindows.size(); while (i > 0) { i--; - WindowState c = (WindowState)mChildWindows.get(i); + WindowState c = mChildWindows.get(i); c.mAttachedHidden = true; } @@ -7655,7 +6494,7 @@ public class WindowManagerService extends IWindowManager.Stub int i = mChildWindows.size(); while (i > 0) { i--; - WindowState c = (WindowState)mChildWindows.get(i); + WindowState c = mChildWindows.get(i); if (c.mAttachedHidden) { c.mAttachedHidden = false; if (c.mSurface != null) { @@ -7828,7 +6667,7 @@ public class WindowManagerService extends IWindowManager.Stub final int N = mChildWindows.size(); for (int i=0; i<N; i++) { - ((WindowState)mChildWindows.get(i)).finishExit(); + mChildWindows.get(i).finishExit(); } if (!mExiting) { @@ -7853,7 +6692,6 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG, "Error hiding surface in " + this, e); } mLastHidden = true; - mKeyWaiter.releasePendingPointerLocked(mSession); } mExiting = false; if (mRemoveOnExit) { @@ -8119,31 +6957,6 @@ public class WindowManagerService extends IWindowManager.Stub && (mOrientationChanging || (!mDrawPending && !mCommitDrawPending)); } - public boolean fillsScreenLw(int screenWidth, int screenHeight, - boolean shownFrame, boolean onlyOpaque) { - if (mSurface == null) { - return false; - } - if (mAppToken != null && !mAppToken.appFullscreen) { - return false; - } - if (onlyOpaque && mAttrs.format != PixelFormat.OPAQUE) { - return false; - } - final Rect frame = shownFrame ? mShownFrame : mFrame; - - if ((mAttrs.flags & FLAG_COMPATIBLE_WINDOW) != 0) { - return frame.left <= mCompatibleScreenFrame.left && - frame.top <= mCompatibleScreenFrame.top && - frame.right >= mCompatibleScreenFrame.right && - frame.bottom >= mCompatibleScreenFrame.bottom; - } else { - return frame.left <= 0 && frame.top <= 0 - && frame.right >= screenWidth - && frame.bottom >= screenHeight; - } - } - /** * Return true if the window is opaque and fully drawn. This indicates * it may obscure windows behind it. @@ -8177,6 +6990,8 @@ public class WindowManagerService extends IWindowManager.Stub } void removeLocked() { + disposeInputChannel(); + if (mAttachedWindow != null) { mAttachedWindow.mChildWindows.remove(this); } @@ -8189,6 +7004,15 @@ public class WindowManagerService extends IWindowManager.Stub // 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() { @@ -8430,6 +7254,11 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mWallpaperYStep="); pw.println(mWallpaperYStep); } } + + String makeInputChannelName() { + return Integer.toHexString(System.identityHashCode(this)) + + " " + mAttrs.getTitle(); + } @Override public String toString() { @@ -8532,6 +7361,9 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ -8739,7 +7571,7 @@ public class WindowManagerService extends IWindowManager.Stub final int N = windows.size(); for (int i=0; i<N; i++) { - ((WindowState)windows.get(i)).finishExit(); + windows.get(i).finishExit(); } updateReportedVisibilityLocked(); @@ -8939,6 +7771,7 @@ public class WindowManagerService extends IWindowManager.Stub public static final int ENABLE_SCREEN = 16; public static final int APP_FREEZE_TIMEOUT = 17; public static final int SEND_NEW_CONFIGURATION = 18; + public static final int REPORT_WINDOWS_CHANGE = 19; private Session mLastReportedHold; @@ -8980,6 +7813,7 @@ public class WindowManagerService extends IWindowManager.Stub } catch (RemoteException e) { // Ignore if process has died. } + notifyFocusChanged(); } if (lastFocus != null) { @@ -9164,7 +7998,7 @@ public class WindowManagerService extends IWindowManager.Stub int i = mWindows.size(); while (i > 0) { i--; - WindowState w = (WindowState)mWindows.get(i); + WindowState w = mWindows.get(i); if (w.mOrientationChanging) { w.mOrientationChanging = false; Slog.w(TAG, "Force clearing orientation change: " + w); @@ -9187,12 +8021,12 @@ public class WindowManagerService extends IWindowManager.Stub if (oldHold != newHold) { try { if (oldHold != null) { - mBatteryStats.noteStopWakelock(oldHold.mUid, + mBatteryStats.noteStopWakelock(oldHold.mUid, -1, "window", BatteryStats.WAKE_TYPE_WINDOW); } if (newHold != null) { - mBatteryStats.noteStartWakelock(newHold.mUid, + mBatteryStats.noteStartWakelock(newHold.mUid, -1, "window", BatteryStats.WAKE_TYPE_WINDOW); } @@ -9270,6 +8104,16 @@ public class WindowManagerService extends IWindowManager.Stub break; } + case REPORT_WINDOWS_CHANGE: { + if (mWindowsChanged) { + synchronized (mWindowMap) { + mWindowsChanged = false; + } + notifyWindowsChanged(); + } + break; + } + } } } @@ -9282,7 +8126,8 @@ public class WindowManagerService extends IWindowManager.Stub IInputContext inputContext) { if (client == null) throw new IllegalArgumentException("null client"); if (inputContext == null) throw new IllegalArgumentException("null inputContext"); - return new Session(client, inputContext); + Session session = new Session(client, inputContext); + return session; } public boolean inputMethodClientHasFocus(IInputMethodClient client) { @@ -9292,7 +8137,7 @@ public class WindowManagerService extends IWindowManager.Stub int idx = findDesiredInputMethodWindowIndexLocked(false); WindowState imFocus; if (idx > 0) { - imFocus = (WindowState)mWindows.get(idx-1); + imFocus = mWindows.get(idx-1); if (imFocus != null) { if (imFocus.mSession.mClient != null && imFocus.mSession.mClient.asBinder() == client.asBinder()) { @@ -9350,9 +8195,10 @@ public class WindowManagerService extends IWindowManager.Stub // First remove all existing app windows. i=0; while (i < NW) { - WindowState w = (WindowState)mWindows.get(i); + WindowState w = mWindows.get(i); if (w.mAppToken != null) { - WindowState win = (WindowState)mWindows.remove(i); + WindowState win = mWindows.remove(i); + mWindowsChanged = true; if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Rebuild removing window: " + win); NW--; @@ -9399,7 +8245,7 @@ public class WindowManagerService extends IWindowManager.Stub int i; for (i=0; i<N; i++) { - WindowState w = (WindowState)mWindows.get(i); + WindowState w = mWindows.get(i); if (w.mBaseLayer == curBaseLayer || w.mIsImWindow || (i > 0 && w.mIsWallpaper)) { curLayer += WINDOW_LAYER_MULTIPLIER; @@ -9444,6 +8290,11 @@ public class WindowManagerService extends IWindowManager.Stub return; } + if (mDisplay == null) { + // Not yet initialized, nothing to do. + return; + } + boolean recoveringMemory = false; if (mForceRemoves != null) { recoveringMemory = true; @@ -9488,6 +8339,10 @@ public class WindowManagerService extends IWindowManager.Stub requestAnimationLocked(0); } } + if (mWindowsChanged && !mWindowChangeListeners.isEmpty()) { + mH.removeMessages(H.REPORT_WINDOWS_CHANGE); + mH.sendMessage(mH.obtainMessage(H.REPORT_WINDOWS_CHANGE)); + } } catch (RuntimeException e) { mInLayout = false; Slog.e(TAG, "Unhandled exception while layout out windows", e); @@ -9520,7 +8375,7 @@ public class WindowManagerService extends IWindowManager.Stub // to another window). int topAttached = -1; for (i = N-1; i >= 0; i--) { - WindowState win = (WindowState) mWindows.get(i); + WindowState win = mWindows.get(i); // Don't do layout of a window if it is not visible, or // soon won't be visible, to avoid wasting time and funky @@ -9569,7 +8424,7 @@ public class WindowManagerService extends IWindowManager.Stub // XXX does not deal with windows that are attached to windows // that are themselves attached. for (i = topAttached; i >= 0; i--) { - WindowState win = (WindowState) mWindows.get(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 @@ -9592,6 +8447,9 @@ public class WindowManagerService extends IWindowManager.Stub } } } + + // Window frames may have changed. Tell the input dispatcher about it. + mInputMonitor.updateInputWindowsLw(); return mPolicy.finishLayoutLw(); } @@ -9609,12 +8467,6 @@ public class WindowManagerService extends IWindowManager.Stub updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES); } - if (mFxSession == null) { - mFxSession = new SurfaceSession(); - } - - if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION"); - // Initialize state of exiting tokens. for (i=mExitingTokens.size()-1; i>=0; i--) { mExitingTokens.get(i).hasVisible = false; @@ -9631,8 +8483,24 @@ public class WindowManagerService extends IWindowManager.Stub float buttonBrightness = -1; boolean focusDisplayed = false; boolean animating = false; + boolean createWatermark = false; + + if (mFxSession == null) { + mFxSession = new SurfaceSession(); + createWatermark = true; + } + + if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION"); Surface.openTransaction(); + + if (createWatermark) { + createWatermark(); + } + if (mWatermark != null) { + mWatermark.positionSurface(dw, dh); + } + try { boolean wallpaperForceHidingChanged = false; int repeats = 0; @@ -9713,7 +8581,7 @@ public class WindowManagerService extends IWindowManager.Stub final int N = mWindows.size(); for (i=N-1; i>=0; i--) { - WindowState w = (WindowState)mWindows.get(i); + WindowState w = mWindows.get(i); final WindowManager.LayoutParams attrs = w.mAttrs; @@ -10146,7 +9014,7 @@ public class WindowManagerService extends IWindowManager.Stub // Clear them out. forceHiding = false; for (i=N-1; i>=0; i--) { - WindowState w = (WindowState)mWindows.get(i); + WindowState w = mWindows.get(i); if (w.mSurface != null) { final WindowManager.LayoutParams attrs = w.mAttrs; if (mPolicy.doesForceHide(w, attrs) && w.isVisibleLw()) { @@ -10196,6 +9064,7 @@ 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. @@ -10212,7 +9081,7 @@ public class WindowManagerService extends IWindowManager.Stub final int N = mWindows.size(); for (i=N-1; i>=0; i--) { - WindowState w = (WindowState)mWindows.get(i); + WindowState w = mWindows.get(i); boolean displayed = false; final WindowManager.LayoutParams attrs = w.mAttrs; @@ -10392,7 +9261,6 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG, "Exception hiding surface in " + w); } } - mKeyWaiter.releasePendingPointerLocked(w.mSession); } // If we are waiting for this window to handle an // orientation change, well, it is hidden, so @@ -10652,8 +9520,14 @@ public class WindowManagerService extends IWindowManager.Stub Slog.e(TAG, "Unhandled exception in Window Manager", e); } + mInputMonitor.updateInputWindowsLw(); + Surface.closeTransaction(); + if (mWatermark != null) { + mWatermark.drawIfNeeded(); + } + if (DEBUG_ORIENTATION && mDisplayFrozen) Slog.v(TAG, "With display frozen, orientationChangeComplete=" + orientationChangeComplete); @@ -10777,10 +9651,12 @@ public class WindowManagerService extends IWindowManager.Stub requestAnimationLocked(currentTime+(1000/60)-SystemClock.uptimeMillis()); } + mInputMonitor.updateInputWindowsLw(); + if (DEBUG_FREEZE) Slog.v(TAG, "Layout: mDisplayFrozen=" + mDisplayFrozen + " holdScreen=" + holdScreen); if (!mDisplayFrozen) { - mQueue.setHoldScreenLocked(holdScreen != null); + setHoldScreenLocked(holdScreen != null); if (screenBrightness < 0 || screenBrightness > 1.0f) { mPowerManager.setScreenBrightnessOverride(-1); } else { @@ -10811,6 +9687,21 @@ public class WindowManagerService extends IWindowManager.Stub // be enabled, because the window obscured flags have changed. enableScreenIfNeededLocked(); } + + /** + * Must be called with the main window manager lock held. + */ + void setHoldScreenLocked(boolean holding) { + boolean state = mHoldingScreenWakeLock.isHeld(); + if (holding != state) { + if (holding) { + mHoldingScreenWakeLock.acquire(); + } else { + mPolicy.screenOnStoppedLw(); + mHoldingScreenWakeLock.release(); + } + } + } void requestAnimationLocked(long delay) { if (!mAnimationPending) { @@ -10868,7 +9759,7 @@ public class WindowManagerService extends IWindowManager.Stub boolean leakedSurface = false; Slog.i(TAG, "Out of memory for surface! Looking for leaks..."); for (int i=0; i<N; i++) { - WindowState ws = (WindowState)mWindows.get(i); + WindowState ws = mWindows.get(i); if (ws.mSurface != null) { if (!mSessions.contains(ws.mSession)) { Slog.w(TAG, "LEAKED SURFACE (session doesn't exist): " @@ -10900,7 +9791,7 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG, "No leaked surfaces; killing applicatons!"); SparseIntArray pidCandidates = new SparseIntArray(); for (int i=0; i<N; i++) { - WindowState ws = (WindowState)mWindows.get(i); + WindowState ws = mWindows.get(i); if (ws.mSurface != null) { pidCandidates.append(ws.mSession.mPid, ws.mSession.mPid); } @@ -10967,14 +9858,20 @@ public class WindowManagerService extends IWindowManager.Stub assignLayersLocked(); } } - - if (newFocus != null && mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) { - mKeyWaiter.handleNewWindowLocked(newFocus); + + if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) { + // If we defer assigning layers, then the caller is responsible for + // doing this part. + finishUpdateFocusedWindowAfterAssignLayersLocked(); } return true; } return false; } + + private void finishUpdateFocusedWindowAfterAssignLayersLocked() { + mInputMonitor.setInputFocusLw(mCurrentFocus); + } private WindowState computeFocusedWindowLocked() { WindowState result = null; @@ -10986,7 +9883,7 @@ public class WindowManagerService extends IWindowManager.Stub ? mAppTokens.get(nextAppIndex) : null; while (i >= 0) { - win = (WindowState)mWindows.get(i); + win = mWindows.get(i); if (localLOGV || DEBUG_FOCUS) Slog.v( TAG, "Looking for focus: " + i @@ -11047,15 +9944,6 @@ public class WindowManagerService extends IWindowManager.Stub private void startFreezingDisplayLocked() { if (mDisplayFrozen) { - // Freezing the display also suspends key event delivery, to - // keep events from going astray while the display is reconfigured. - // If someone has changed orientation again while the screen is - // still frozen, the events will continue to be blocked while the - // successive orientation change is processed. To prevent spurious - // ANRs, we reset the event dispatch timeout in this case. - synchronized (mKeyWaiter) { - mKeyWaiter.mWasFrozen = true; - } return; } @@ -11077,6 +9965,9 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_FREEZE) Slog.v(TAG, "*** FREEZING DISPLAY", new RuntimeException()); mDisplayFrozen = true; + + mInputMonitor.freezeInputDispatchingLw(); + if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET; mNextAppTransitionPackage = null; @@ -11108,12 +9999,7 @@ public class WindowManagerService extends IWindowManager.Stub } Surface.unfreezeDisplay(0); - // Reset the key delivery timeout on unfreeze, too. We force a wakeup here - // too because regular key delivery processing should resume immediately. - synchronized (mKeyWaiter) { - mKeyWaiter.mWasFrozen = true; - mKeyWaiter.notifyAll(); - } + mInputMonitor.thawInputDispatchingLw(); // While the display is frozen we don't re-compute the orientation // to avoid inconsistent states. However, something interesting @@ -11135,6 +10021,194 @@ public class WindowManagerService extends IWindowManager.Stub mScreenFrozenLock.release(); } + static int getPropertyInt(String[] tokens, int index, int defUnits, int defDps, + DisplayMetrics dm) { + if (index < tokens.length) { + String str = tokens[index]; + if (str != null && str.length() > 0) { + try { + int val = Integer.parseInt(str); + return val; + } catch (Exception e) { + } + } + } + if (defUnits == TypedValue.COMPLEX_UNIT_PX) { + return defDps; + } + int val = (int)TypedValue.applyDimension(defUnits, defDps, dm); + 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; + } + + File file = new File("/system/etc/setup.conf"); + FileInputStream in = null; + try { + in = new FileInputStream(file); + DataInputStream ind = new DataInputStream(in); + String line = ind.readLine(); + if (line != null) { + String[] toks = line.split("%"); + if (toks != null && toks.length > 0) { + mWatermark = new Watermark(mFxSession, toks); + } + } + } catch (FileNotFoundException e) { + } catch (IOException e) { + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + } + } + } + } + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission("android.permission.DUMP") @@ -11145,14 +10219,13 @@ public class WindowManagerService extends IWindowManager.Stub return; } - pw.println("Input State:"); - mQueue.dump(pw, " "); + mInputManager.dump(pw); pw.println(" "); synchronized(mWindowMap) { pw.println("Current Window Manager state:"); for (int i=mWindows.size()-1; i>=0; i--) { - WindowState w = (WindowState)mWindows.get(i); + WindowState w = mWindows.get(i); pw.print(" Window #"); pw.print(i); pw.print(' '); pw.print(w); pw.println(":"); w.dump(pw, " "); @@ -11363,24 +10436,13 @@ public class WindowManagerService extends IWindowManager.Stub } pw.print(" DisplayWidth="); pw.print(mDisplay.getWidth()); pw.print(" DisplayHeight="); pw.println(mDisplay.getHeight()); - pw.println(" KeyWaiter state:"); - pw.print(" mLastWin="); pw.print(mKeyWaiter.mLastWin); - pw.print(" mLastBinder="); pw.println(mKeyWaiter.mLastBinder); - pw.print(" mFinished="); pw.print(mKeyWaiter.mFinished); - pw.print(" mGotFirstWindow="); pw.print(mKeyWaiter.mGotFirstWindow); - pw.print(" mEventDispatching="); pw.print(mKeyWaiter.mEventDispatching); - pw.print(" mTimeToSwitch="); pw.println(mKeyWaiter.mTimeToSwitch); } } + // Called by the heartbeat to ensure locks are not held indefnitely (for deadlock detection). public void monitor() { synchronized (mWindowMap) { } synchronized (mKeyguardTokenWatcher) { } - synchronized (mKeyWaiter) { } - } - - public void virtualKeyFeedback(KeyEvent event) { - mPolicy.keyFeedbackFromInput(event); } /** diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 51ff959..6a05d3c 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -16,6 +16,7 @@ package com.android.server.am; +import com.android.internal.R; import com.android.internal.os.BatteryStatsImpl; import com.android.server.AttributeCache; import com.android.server.IntentResolver; @@ -24,6 +25,7 @@ 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 dalvik.system.Zygote; @@ -32,19 +34,20 @@ import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.ActivityThread; import android.app.AlertDialog; +import android.app.AppGlobals; import android.app.ApplicationErrorReport; import android.app.Dialog; import android.app.IActivityController; -import android.app.IActivityManager; import android.app.IActivityWatcher; import android.app.IApplicationThread; import android.app.IInstrumentationWatcher; +import android.app.INotificationManager; import android.app.IServiceConnection; import android.app.IThumbnailReceiver; import android.app.Instrumentation; import android.app.Notification; +import android.app.NotificationManager; import android.app.PendingIntent; -import android.app.ResultInfo; import android.app.Service; import android.app.backup.IBackupManager; import android.content.ActivityNotFoundException; @@ -70,6 +73,7 @@ import android.content.pm.PathPermission; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.graphics.Bitmap; import android.net.Uri; @@ -88,11 +92,11 @@ import android.os.Looper; import android.os.Message; import android.os.Parcel; import android.os.ParcelFileDescriptor; -import android.os.PowerManager; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; import android.provider.Settings; @@ -102,21 +106,30 @@ import android.util.Slog; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.SparseArray; +import android.util.TimeUtils; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; import android.view.WindowManagerPolicy; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.File; import java.io.FileDescriptor; +import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.lang.IllegalStateException; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -127,7 +140,8 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; -public final class ActivityManagerService extends ActivityManagerNative implements Watchdog.Monitor { +public final class ActivityManagerService extends ActivityManagerNative + implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback { static final String TAG = "ActivityManager"; static final boolean DEBUG = false; static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV; @@ -139,6 +153,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen static final boolean DEBUG_BROADCAST = localLOGV || false; static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false; static final boolean DEBUG_SERVICE = localLOGV || false; + static final boolean DEBUG_SERVICE_EXECUTING = localLOGV || false; static final boolean DEBUG_VISBILITY = localLOGV || false; static final boolean DEBUG_PROCESSES = localLOGV || false; static final boolean DEBUG_PROVIDER = localLOGV || false; @@ -147,6 +162,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen static final boolean DEBUG_RESULTS = localLOGV || false; static final boolean DEBUG_BACKUP = localLOGV || false; static final boolean DEBUG_CONFIGURATION = localLOGV || false; + static final boolean DEBUG_POWER = localLOGV || false; + static final boolean DEBUG_POWER_QUICK = DEBUG_POWER || false; static final boolean VALIDATE_TOKENS = false; static final boolean SHOW_ACTIVITY_START_TIME = true; @@ -177,47 +194,32 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Maximum number of recent tasks that we can remember. static final int MAX_RECENT_TASKS = 20; - + // Amount of time after a call to stopAppSwitches() during which we will // prevent further untrusted switches from happening. static final long APP_SWITCH_DELAY_TIME = 5*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; - - // Set to true to disable the icon that is shown while a new activity - // is being started. - static final boolean SHOW_APP_STARTING_ICON = true; - - // How long we wait until giving up on the last activity to pause. This - // is short because it directly impacts the responsiveness of starting the - // next activity. - static final int PAUSE_TIMEOUT = 500; - - /** - * How long we can hold the launch wake lock before giving up. - */ - static final int LAUNCH_TIMEOUT = 10*1000; // How long we wait for a launched process to attach to the activity manager // before we decide it's never going to come up for real. static final int PROC_START_TIMEOUT = 10*1000; - // How long we wait until giving up on the last activity telling us it - // is idle. - static final int IDLE_TIMEOUT = 10*1000; - // How long to wait after going idle before forcing apps to GC. static final int GC_TIMEOUT = 5*1000; // The minimum amount of time between successive GC requests for a process. static final int GC_MIN_INTERVAL = 60*1000; - // How long we wait until giving up on an activity telling us it has - // finished destroying itself. - static final int DESTROY_TIMEOUT = 10*1000; - + // The rate at which we check for apps using excessive power -- 15 mins. + static final int POWER_CHECK_DELAY = (DEBUG_POWER_QUICK ? 2 : 15) * 60*1000; + + // The minimum sample duration we will allow before deciding we have + // enough data on wake locks to start killing things. + static final int WAKE_LOCK_MIN_CHECK_DURATION = (DEBUG_POWER_QUICK ? 1 : 5) * 60*1000; + + // The minimum sample duration we will allow before deciding we have + // enough data on CPU usage to start killing things. + static final int CPU_MIN_CHECK_DURATION = (DEBUG_POWER_QUICK ? 1 : 5) * 60*1000; + // How long we allow a receiver to run before giving up on it. static final int BROADCAST_TIMEOUT = 10*1000; @@ -284,6 +286,17 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // system/rootdir/init.rc on startup. static final int SECONDARY_SERVER_ADJ; + // This is a process with a heavy-weight application. It is in the + // background, but we want to try to avoid killing it. Value set in + // system/rootdir/init.rc on startup. + static final int HEAVY_WEIGHT_APP_ADJ; + + // This is a process only hosting components that are perceptible to the + // user, and we really want to avoid killing them, but they are not + // immediately visible. An example is background music playback. Value set in + // system/rootdir/init.rc on startup. + static final int PERCEPTIBLE_APP_ADJ; + // This is a process only hosting activities that are visible to the // user, so we'd prefer they don't disappear. Value set in // system/rootdir/init.rc on startup. @@ -309,6 +322,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen static final int HOME_APP_MEM; static final int BACKUP_APP_MEM; static final int SECONDARY_SERVER_MEM; + static final int HEAVY_WEIGHT_APP_MEM; + static final int PERCEPTIBLE_APP_MEM; static final int VISIBLE_APP_MEM; static final int FOREGROUND_APP_MEM; @@ -329,68 +344,55 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // been idle for less than 120 seconds. static final long EMPTY_APP_IDLE_OFFSET = 120*1000; + static int getIntProp(String name, boolean allowZero) { + String str = SystemProperties.get(name); + if (str == null) { + throw new IllegalArgumentException("Property not defined: " + name); + } + int val = Integer.valueOf(str); + if (val == 0 && !allowZero) { + throw new IllegalArgumentException("Property must not be zero: " + name); + } + return val; + } + static { // These values are set in system/rootdir/init.rc on startup. - FOREGROUND_APP_ADJ = - Integer.valueOf(SystemProperties.get("ro.FOREGROUND_APP_ADJ")); - VISIBLE_APP_ADJ = - Integer.valueOf(SystemProperties.get("ro.VISIBLE_APP_ADJ")); - SECONDARY_SERVER_ADJ = - Integer.valueOf(SystemProperties.get("ro.SECONDARY_SERVER_ADJ")); - BACKUP_APP_ADJ = - Integer.valueOf(SystemProperties.get("ro.BACKUP_APP_ADJ")); - HOME_APP_ADJ = - Integer.valueOf(SystemProperties.get("ro.HOME_APP_ADJ")); - HIDDEN_APP_MIN_ADJ = - Integer.valueOf(SystemProperties.get("ro.HIDDEN_APP_MIN_ADJ")); - EMPTY_APP_ADJ = - Integer.valueOf(SystemProperties.get("ro.EMPTY_APP_ADJ")); - HIDDEN_APP_MAX_ADJ = EMPTY_APP_ADJ-1; - FOREGROUND_APP_MEM = - Integer.valueOf(SystemProperties.get("ro.FOREGROUND_APP_MEM"))*PAGE_SIZE; - VISIBLE_APP_MEM = - Integer.valueOf(SystemProperties.get("ro.VISIBLE_APP_MEM"))*PAGE_SIZE; - SECONDARY_SERVER_MEM = - Integer.valueOf(SystemProperties.get("ro.SECONDARY_SERVER_MEM"))*PAGE_SIZE; - BACKUP_APP_MEM = - Integer.valueOf(SystemProperties.get("ro.BACKUP_APP_MEM"))*PAGE_SIZE; - HOME_APP_MEM = - Integer.valueOf(SystemProperties.get("ro.HOME_APP_MEM"))*PAGE_SIZE; - HIDDEN_APP_MEM = - Integer.valueOf(SystemProperties.get("ro.HIDDEN_APP_MEM"))*PAGE_SIZE; - EMPTY_APP_MEM = - Integer.valueOf(SystemProperties.get("ro.EMPTY_APP_MEM"))*PAGE_SIZE; + FOREGROUND_APP_ADJ = getIntProp("ro.FOREGROUND_APP_ADJ", true); + VISIBLE_APP_ADJ = getIntProp("ro.VISIBLE_APP_ADJ", true); + PERCEPTIBLE_APP_ADJ = getIntProp("ro.PERCEPTIBLE_APP_ADJ", true); + HEAVY_WEIGHT_APP_ADJ = getIntProp("ro.HEAVY_WEIGHT_APP_ADJ", true); + SECONDARY_SERVER_ADJ = getIntProp("ro.SECONDARY_SERVER_ADJ", true); + BACKUP_APP_ADJ = getIntProp("ro.BACKUP_APP_ADJ", true); + HOME_APP_ADJ = getIntProp("ro.HOME_APP_ADJ", true); + HIDDEN_APP_MIN_ADJ = getIntProp("ro.HIDDEN_APP_MIN_ADJ", true); + EMPTY_APP_ADJ = getIntProp("ro.EMPTY_APP_ADJ", true); + // These days we use the last empty slot for hidden apps as well. + HIDDEN_APP_MAX_ADJ = EMPTY_APP_ADJ; + FOREGROUND_APP_MEM = getIntProp("ro.FOREGROUND_APP_MEM", false)*PAGE_SIZE; + VISIBLE_APP_MEM = getIntProp("ro.VISIBLE_APP_MEM", false)*PAGE_SIZE; + PERCEPTIBLE_APP_MEM = getIntProp("ro.PERCEPTIBLE_APP_MEM", false)*PAGE_SIZE; + HEAVY_WEIGHT_APP_MEM = getIntProp("ro.HEAVY_WEIGHT_APP_MEM", false)*PAGE_SIZE; + SECONDARY_SERVER_MEM = getIntProp("ro.SECONDARY_SERVER_MEM", false)*PAGE_SIZE; + BACKUP_APP_MEM = getIntProp("ro.BACKUP_APP_MEM", false)*PAGE_SIZE; + HOME_APP_MEM = getIntProp("ro.HOME_APP_MEM", false)*PAGE_SIZE; + HIDDEN_APP_MEM = getIntProp("ro.HIDDEN_APP_MEM", false)*PAGE_SIZE; + EMPTY_APP_MEM = getIntProp("ro.EMPTY_APP_MEM", false)*PAGE_SIZE; } static final int MY_PID = Process.myPid(); static final String[] EMPTY_STRING_ARRAY = new String[0]; - enum ActivityState { - INITIALIZING, - RESUMED, - PAUSING, - PAUSED, - STOPPING, - STOPPED, - FINISHING, - DESTROYING, - DESTROYED - } - - /** - * The back history of all previous (and possibly still - * running) activities. It contains HistoryRecord objects. - */ - final ArrayList mHistory = new ArrayList(); - + public ActivityStack mMainStack; + /** * Description of a request to start a new activity, which has been held * due to app switches being disabled. */ - class PendingActivityLaunch { - HistoryRecord r; - HistoryRecord sourceRecord; + static class PendingActivityLaunch { + ActivityRecord r; + ActivityRecord sourceRecord; Uri[] grantedUriPermissions; int grantedMode; boolean onlyIfNeeded; @@ -400,18 +402,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen = new ArrayList<PendingActivityLaunch>(); /** - * List of people waiting to find out about the next launched activity. - */ - final ArrayList<IActivityManager.WaitResult> mWaitingActivityLaunched - = new ArrayList<IActivityManager.WaitResult>(); - - /** - * List of people waiting to find out about the next visible activity. - */ - final ArrayList<IActivityManager.WaitResult> mWaitingActivityVisible - = new ArrayList<IActivityManager.WaitResult>(); - - /** * List of all active broadcasts that are to be executed immediately * (without waiting for another broadcast to finish). Currently this only * contains broadcasts to registered receivers, to avoid spinning up @@ -441,56 +431,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen boolean mBroadcastsScheduled = false; /** - * Set to indicate whether to issue an onUserLeaving callback when a - * newly launched activity is being brought in front of us. - */ - boolean mUserLeaving = false; - - /** - * When we are in the process of pausing an activity, before starting the - * next one, this variable holds the activity that is currently being paused. - */ - HistoryRecord mPausingActivity = null; - - /** - * Current activity that is resumed, or null if there is none. - */ - HistoryRecord mResumedActivity = null; - - /** * Activity we have told the window manager to have key focus. */ - HistoryRecord mFocusedActivity = null; - - /** - * This is the last activity that we put into the paused state. This is - * used to determine if we need to do an activity transition while sleeping, - * when we normally hold the top activity paused. - */ - HistoryRecord mLastPausedActivity = null; - - /** - * List of activities that are waiting for a new activity - * to become visible before completing whatever operation they are - * supposed to do. - */ - final ArrayList mWaitingVisibleActivities = new ArrayList(); - - /** - * List of activities that are ready to be stopped, but waiting - * for the next activity to settle down before doing so. It contains - * HistoryRecord objects. - */ - final ArrayList<HistoryRecord> mStoppingActivities - = new ArrayList<HistoryRecord>(); - - /** - * Animations that for the current transition have requested not to - * be considered for the transition animation. - */ - final ArrayList<HistoryRecord> mNoAnimActivities - = new ArrayList<HistoryRecord>(); - + ActivityRecord mFocusedActivity = null; /** * List of intents that were used to start the most recent tasks. */ @@ -498,13 +441,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen = new ArrayList<TaskRecord>(); /** - * List of activities that are ready to be finished, but waiting - * for the previous activity to settle down before doing so. It contains - * HistoryRecord objects. - */ - final ArrayList mFinishingActivities = new ArrayList(); - - /** * All of the applications we currently have running organized by name. * The keys are strings of the application package name (as * returned by the package manager), and the keys are ApplicationRecord @@ -514,6 +450,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen = new ProcessMap<ProcessRecord>(); /** + * The currently running heavy-weight process, if any. + */ + ProcessRecord mHeavyWeightProcess = null; + + /** * The last time that various processes have crashed. */ final ProcessMap<Long> mProcessCrashTimes = new ProcessMap<Long>(); @@ -599,16 +540,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen * This is the process holding what we currently consider to be * the "home" activity. */ - private ProcessRecord mHomeProcess; + ProcessRecord mHomeProcess; /** - * List of running activities, sorted by recent usage. - * The first entry in the list is the least recently used. - * It contains HistoryRecord objects. - */ - private final ArrayList mLRUActivities = new ArrayList(); - - /** * Set of PendingResultRecord objects that are currently active. */ final HashSet mPendingResultRecords = new HashSet(); @@ -620,6 +554,29 @@ public final class ActivityManagerService extends ActivityManagerNative implemen = new HashMap<PendingIntentRecord.Key, WeakReference<PendingIntentRecord>>(); /** + * Fingerprints (String.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. + */ + private final HashSet<Integer> mAlreadyLoggedViolatedStacks = new HashSet<Integer>(); + private static final int MAX_DUP_SUPPRESSED_STACKS = 5000; + + /** + * Strict Mode background batched logging state. + * + * The string buffer is guarded by itself, and its lock is also + * used to determine if another batched write is already + * in-flight. + */ + private final StringBuilder mStrictModeBuffer = new StringBuilder(); + + /** + * True if we have a pending unexpired BROADCAST_TIMEOUT_MSG posted to our handler. + */ + private boolean mPendingBroadcastTimeoutMessage; + + /** * Intent broadcast that we have tried to start, but are * waiting for its application's process to be created. We only * need one (instead of a list) because we always process broadcasts @@ -629,6 +586,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen BroadcastRecord mPendingBroadcast = null; /** + * The receiver index that is pending, to restart the broadcast if needed. + */ + int mPendingBroadcastRecvIndex; + + /** * Keeps track of all IIntentReceivers that have been registered for * broadcasts. Hash keys are the receiver IBinder, hash value is * a ReceiverList. @@ -678,8 +640,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen * All currently bound service connections. Keys are the IBinder of * the client's IServiceConnection. */ - final HashMap<IBinder, ConnectionRecord> mServiceConnections - = new HashMap<IBinder, ConnectionRecord>(); + final HashMap<IBinder, ArrayList<ConnectionRecord>> mServiceConnections + = new HashMap<IBinder, ArrayList<ConnectionRecord>>(); /** * List of services that we have been asked to start, @@ -727,21 +689,24 @@ public final class ActivityManagerService extends ActivityManagerNative implemen * that a single provider may be published under multiple names, so * there may be multiple entries here for a single one in mProvidersByClass. */ - final HashMap mProvidersByName = new HashMap(); + final HashMap<String, ContentProviderRecord> mProvidersByName + = new HashMap<String, ContentProviderRecord>(); /** * All of the currently running global content providers. Keys are a * string containing the provider's implementation class and values are a * ContentProviderRecord object containing the data about it. */ - final HashMap mProvidersByClass = new HashMap(); + final HashMap<String, ContentProviderRecord> mProvidersByClass + = new HashMap<String, ContentProviderRecord>(); /** * List of content providers who have clients waiting for them. The * application is currently being launched and the provider will be * removed from this list once it is published. */ - final ArrayList mLaunchingProviders = new ArrayList(); + final ArrayList<ContentProviderRecord> mLaunchingProviders + = new ArrayList<ContentProviderRecord>(); /** * Global set of specific Uri permissions that have been granted. @@ -790,12 +755,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen int mConfigurationSeq = 0; /** - * Set when we know we are going to be calling updateConfiguration() - * soon, so want to skip intermediate config checks. - */ - boolean mConfigWillChange; - - /** * Hardware-reported OpenGLES version. */ final int GL_ES_VERSION; @@ -818,10 +777,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ComponentName mTopComponent; String mTopAction; String mTopData; + boolean mProcessesReady = false; boolean mSystemReady = false; boolean mBooting = false; boolean mWaitingUpdate = false; boolean mDidUpdate = false; + boolean mOnBattery = false; + boolean mLaunchWarningShown = false; Context mContext; @@ -842,30 +804,25 @@ public final class ActivityManagerService extends ActivityManagerNative implemen boolean mDidAppSwitch; /** - * Set while we are wanting to sleep, to prevent any - * activities from being started/resumed. + * Last time (in realtime) at which we checked for power usage. */ - boolean mSleeping = false; + long mLastPowerCheckRealtime; /** - * Set if we are shutting down the system, similar to sleeping. + * Last time (in uptime) at which we checked for power usage. */ - boolean mShuttingDown = false; - + long mLastPowerCheckUptime; + /** - * Set when the system is going to sleep, until we have - * successfully paused the current activity and released our wake lock. - * At that point the system is allowed to actually sleep. + * Set while we are wanting to sleep, to prevent any + * activities from being started/resumed. */ - PowerManager.WakeLock mGoingToSleep; + boolean mSleeping = false; /** - * We don't want to allow the device to go to sleep while in the process - * of launching an activity. This is primarily to allow alarm intent - * receivers to launch an activity and get that to run before the device - * goes back to sleep. + * Set if we are shutting down the system, similar to sleeping. */ - PowerManager.WakeLock mLaunchingActivity; + boolean mShuttingDown = false; /** * Task identifier that activities are currently being started @@ -945,8 +902,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen long mLastWriteTime = 0; - long mInitialStartTime = 0; - /** * Set to true after the system has finished booting. */ @@ -978,7 +933,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (localLOGV) Slog.v( TAG, "Death received in " + this + " for thread " + mAppThread.asBinder()); - removeRequestedPss(mApp); synchronized(ActivityManagerService.this) { appDiedLocked(mApp, mPid, mAppThread); } @@ -993,20 +947,18 @@ public final class ActivityManagerService extends ActivityManagerNative implemen static final int WAIT_FOR_DEBUGGER_MSG = 6; static final int BROADCAST_INTENT_MSG = 7; static final int BROADCAST_TIMEOUT_MSG = 8; - static final int PAUSE_TIMEOUT_MSG = 9; - static final int IDLE_TIMEOUT_MSG = 10; - static final int IDLE_NOW_MSG = 11; static final int SERVICE_TIMEOUT_MSG = 12; static final int UPDATE_TIME_ZONE = 13; static final int SHOW_UID_ERROR_MSG = 14; static final int IM_FEELING_LUCKY_MSG = 15; - static final int LAUNCH_TIMEOUT_MSG = 16; - static final int DESTROY_TIMEOUT_MSG = 17; - static final int RESUME_TOP_ACTIVITY_MSG = 19; static final int PROC_START_TIMEOUT_MSG = 20; static final int DO_PENDING_ACTIVITY_LAUNCHES_MSG = 21; static final int KILL_APPLICATION_MSG = 22; static final int FINALIZE_PENDING_INTENT_MSG = 23; + static final int POST_HEAVY_NOTIFICATION_MSG = 24; + 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; AlertDialog mUidAlert; @@ -1048,18 +1000,47 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return; } - broadcastIntentLocked(null, null, new Intent("android.intent.action.ANR"), + Intent intent = new Intent("android.intent.action.ANR"); + if (!mProcessesReady) { + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + } + broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, false, false, MY_PID, Process.SYSTEM_UID); Dialog d = new AppNotRespondingDialog(ActivityManagerService.this, - mContext, proc, (HistoryRecord)data.get("activity")); + mContext, proc, (ActivityRecord)data.get("activity")); d.show(); proc.anrDialog = d; } ensureBootCompleted(); } break; + case SHOW_STRICT_MODE_VIOLATION_MSG: { + HashMap<String, Object> data = (HashMap<String, Object>) msg.obj; + synchronized (ActivityManagerService.this) { + ProcessRecord proc = (ProcessRecord) data.get("app"); + if (proc == null) { + Slog.e(TAG, "App not found when showing strict mode dialog."); + break; + } + if (proc.crashDialog != null) { + Slog.e(TAG, "App already has strict mode dialog: " + proc); + return; + } + AppErrorResult res = (AppErrorResult) data.get("result"); + if (!mSleeping && !mShuttingDown) { + Dialog d = new StrictModeViolationDialog(mContext, res, proc); + d.show(); + proc.crashDialog = d; + } else { + // The device is asleep, so just pretend that the user + // saw a crash dialog and hit "force quit". + res.set(0); + } + } + ensureBootCompleted(); + } break; case SHOW_FACTORY_ERROR_MSG: { Dialog d = new FactoryErrorDialog( mContext, msg.getData().getCharSequence("msg")); @@ -1101,50 +1082,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen processNextBroadcast(true); } break; case BROADCAST_TIMEOUT_MSG: { - if (mDidDexOpt) { - mDidDexOpt = false; - Message nmsg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG); - mHandler.sendMessageDelayed(nmsg, BROADCAST_TIMEOUT); - return; - } - // Only process broadcast timeouts if the system is ready. That way - // PRE_BOOT_COMPLETED broadcasts can't timeout as they are intended - // to do heavy lifting for system up - if (mSystemReady) { - broadcastTimeout(); - } - } 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); - } break; - case IDLE_TIMEOUT_MSG: { - if (mDidDexOpt) { - mDidDexOpt = false; - Message nmsg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG); - nmsg.obj = msg.obj; - mHandler.sendMessageDelayed(nmsg, IDLE_TIMEOUT); - return; + synchronized (ActivityManagerService.this) { + broadcastTimeoutLocked(true); } - // We don't at this point know if the activity is fullscreen, - // so we need to be conservative and assume it isn't. - IBinder token = (IBinder)msg.obj; - Slog.w(TAG, "Activity idle timeout for " + token); - activityIdleInternal(token, true, null); - } break; - case DESTROY_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 destroy timeout for " + token); - activityDestroyed(token); - } break; - case IDLE_NOW_MSG: { - IBinder token = (IBinder)msg.obj; - activityIdle(token, null); } break; case SERVICE_TIMEOUT_MSG: { if (mDidDexOpt) { @@ -1188,25 +1128,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mUidAlert = null; } } break; - case LAUNCH_TIMEOUT_MSG: { - if (mDidDexOpt) { - mDidDexOpt = false; - Message nmsg = mHandler.obtainMessage(LAUNCH_TIMEOUT_MSG); - mHandler.sendMessageDelayed(nmsg, LAUNCH_TIMEOUT); - return; - } - synchronized (ActivityManagerService.this) { - if (mLaunchingActivity.isHeld()) { - Slog.w(TAG, "Launch timeout has expired, giving up wake lock!"); - mLaunchingActivity.release(); - } - } - } break; - case RESUME_TOP_ACTIVITY_MSG: { - synchronized (ActivityManagerService.this) { - resumeTopActivityLocked(null); - } - } break; case PROC_START_TIMEOUT_MSG: { if (mDidDexOpt) { mDidDexOpt = false; @@ -1236,6 +1157,70 @@ public final class ActivityManagerService extends ActivityManagerNative implemen case FINALIZE_PENDING_INTENT_MSG: { ((PendingIntentRecord)msg.obj).completeFinalize(); } break; + case POST_HEAVY_NOTIFICATION_MSG: { + INotificationManager inm = NotificationManager.getService(); + if (inm == null) { + return; + } + + ActivityRecord root = (ActivityRecord)msg.obj; + ProcessRecord process = root.app; + if (process == null) { + return; + } + + try { + Context context = mContext.createPackageContext(process.info.packageName, 0); + String text = mContext.getString(R.string.heavy_weight_notification, + context.getApplicationInfo().loadLabel(context.getPackageManager())); + Notification notification = new Notification(); + notification.icon = com.android.internal.R.drawable.stat_sys_adb; //context.getApplicationInfo().icon; + notification.when = 0; + notification.flags = Notification.FLAG_ONGOING_EVENT; + notification.tickerText = text; + notification.defaults = 0; // please be quiet + notification.sound = null; + notification.vibrate = null; + notification.setLatestEventInfo(context, text, + mContext.getText(R.string.heavy_weight_notification_detail), + PendingIntent.getActivity(mContext, 0, root.intent, + PendingIntent.FLAG_CANCEL_CURRENT)); + + try { + int[] outId = new int[1]; + inm.enqueueNotification("android", R.string.heavy_weight_notification, + notification, outId); + } catch (RuntimeException e) { + Slog.w(ActivityManagerService.TAG, + "Error showing notification for heavy-weight app", e); + } catch (RemoteException e) { + } + } catch (NameNotFoundException e) { + Slog.w(TAG, "Unable to create context for heavy notification", e); + } + } break; + case CANCEL_HEAVY_NOTIFICATION_MSG: { + INotificationManager inm = NotificationManager.getService(); + if (inm == null) { + return; + } + try { + inm.cancelNotification("android", + R.string.heavy_weight_notification); + } catch (RuntimeException e) { + Slog.w(ActivityManagerService.TAG, + "Error canceling notification for service", e); + } catch (RemoteException e) { + } + } break; + case CHECK_EXCESSIVE_WAKE_LOCKS_MSG: { + synchronized (ActivityManagerService.this) { + checkExcessivePowerUsageLocked(true); + removeMessages(CHECK_EXCESSIVE_WAKE_LOCKS_MSG); + Message nmsg = obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG); + sendMessageDelayed(nmsg, POWER_CHECK_DELAY); + } + } break; } } }; @@ -1299,11 +1284,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen Context context = at.getSystemContext(); m.mContext = context; m.mFactoryTest = factoryTest; - PowerManager pm = - (PowerManager)context.getSystemService(Context.POWER_SERVICE); - m.mGoingToSleep = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Sleep"); - m.mLaunchingActivity = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Launch"); - m.mLaunchingActivity.setReferenceCounted(false); + m.mMainStack = new ActivityStack(m, context, true); m.mBatteryStatsService.publish(context); m.mUsageStatsService.publish(context); @@ -1335,6 +1316,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen android.os.Process.setThreadPriority( android.os.Process.THREAD_PRIORITY_FOREGROUND); + android.os.Process.setCanSelfBackground(false); ActivityManagerService m = new ActivityManagerService(); @@ -1405,7 +1387,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { synchronized (mActivityManagerService.mProcessStatsThread) { - pw.print(mActivityManagerService.mProcessStats.printCurrentState()); + pw.print(mActivityManagerService.mProcessStats.printCurrentLoad()); + pw.print(mActivityManagerService.mProcessStats.printCurrentState( + SystemClock.uptimeMillis())); } } } @@ -1428,9 +1412,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mBatteryStatsService = new BatteryStatsService(new File( systemDir, "batterystats.bin").toString()); mBatteryStatsService.getActiveStatistics().readLocked(); - mBatteryStatsService.getActiveStatistics().writeLocked(); + mBatteryStatsService.getActiveStatistics().writeAsyncLocked(); + mOnBattery = DEBUG_POWER ? true + : mBatteryStatsService.getActiveStatistics().getIsOnBattery(); + mBatteryStatsService.getActiveStatistics().setCallback(this); - mUsageStatsService = new UsageStatsService( new File( + mUsageStatsService = new UsageStatsService(new File( systemDir, "usagestats").toString()); GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version", @@ -1543,37 +1530,68 @@ public final class ActivityManagerService extends ActivityManagerNative implemen synchronized(bstats) { synchronized(mPidsSelfLocked) { if (haveNewCpuStats) { - if (mBatteryStatsService.isOnBattery()) { - final int N = mProcessStats.countWorkingStats(); + if (mOnBattery) { + int perc = bstats.startAddingCpuLocked(); + int totalUTime = 0; + int totalSTime = 0; + final int N = mProcessStats.countStats(); for (int i=0; i<N; i++) { - ProcessStats.Stats st - = mProcessStats.getWorkingStats(i); + ProcessStats.Stats st = mProcessStats.getStats(i); + if (!st.working) { + continue; + } ProcessRecord pr = mPidsSelfLocked.get(st.pid); + int otherUTime = (st.rel_utime*perc)/100; + int otherSTime = (st.rel_stime*perc)/100; + totalUTime += otherUTime; + totalSTime += otherSTime; if (pr != null) { BatteryStatsImpl.Uid.Proc ps = pr.batteryStats; - ps.addCpuTimeLocked(st.rel_utime, st.rel_stime); + ps.addCpuTimeLocked(st.rel_utime-otherUTime, + st.rel_stime-otherSTime); ps.addSpeedStepTimes(cpuSpeedTimes); + pr.curCpuTime += (st.rel_utime+st.rel_stime) * 10; } else { BatteryStatsImpl.Uid.Proc ps = bstats.getProcessStatsLocked(st.name, st.pid); if (ps != null) { - ps.addCpuTimeLocked(st.rel_utime, st.rel_stime); + ps.addCpuTimeLocked(st.rel_utime-otherUTime, + st.rel_stime-otherSTime); ps.addSpeedStepTimes(cpuSpeedTimes); } } } + bstats.finishAddingCpuLocked(perc, totalUTime, + totalSTime, cpuSpeedTimes); } } } if (mLastWriteTime < (now-BATTERY_STATS_TIME)) { mLastWriteTime = now; - mBatteryStatsService.getActiveStatistics().writeLocked(); + mBatteryStatsService.getActiveStatistics().writeAsyncLocked(); } } } } + @Override + public void batteryNeedsCpuUpdate() { + updateCpuStatsNow(); + } + + @Override + public void batteryPowerChanged(boolean onBattery) { + // When plugging in, update the CPU stats first before changing + // the plug state. + updateCpuStatsNow(); + synchronized (this) { + synchronized(mPidsSelfLocked) { + mOnBattery = DEBUG_POWER ? true : onBattery; + } + } + } + /** * Initialize the application bind args. These are passed to each * process when the bindApplication() IPC is sent to the process. They're @@ -1592,7 +1610,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return mAppBindArgs; } - private final void setFocusedActivityLocked(HistoryRecord r) { + final void setFocusedActivityLocked(ActivityRecord r) { if (mFocusedActivity != r) { mFocusedActivity = r; mWindowManager.setFocusedApp(r, true); @@ -1677,65 +1695,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private final void updateLruProcessLocked(ProcessRecord app, + final void updateLruProcessLocked(ProcessRecord app, boolean oomAdj, boolean updateActivityTime) { mLruSeq++; updateLruProcessInternalLocked(app, oomAdj, updateActivityTime, 0); } - private final boolean updateLRUListLocked(HistoryRecord r) { - final boolean hadit = mLRUActivities.remove(r); - mLRUActivities.add(r); - return hadit; - } - - private final HistoryRecord topRunningActivityLocked(HistoryRecord notTop) { - int i = mHistory.size()-1; - while (i >= 0) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); - if (!r.finishing && r != notTop) { - return r; - } - i--; - } - return null; - } - - private final HistoryRecord topRunningNonDelayedActivityLocked(HistoryRecord notTop) { - int i = mHistory.size()-1; - while (i >= 0) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); - if (!r.finishing && !r.delayedResume && r != notTop) { - return r; - } - i--; - } - return null; - } - - /** - * This is a simplified version of topRunningActivityLocked that provides a number of - * optional skip-over modes. It is intended for use with the ActivityController hook only. - * - * @param token If non-null, any history records matching this token will be skipped. - * @param taskId If non-zero, we'll attempt to skip over records with the same task ID. - * - * @return Returns the HistoryRecord of the next activity on the stack. - */ - private final HistoryRecord topRunningActivityLocked(IBinder token, int taskId) { - int i = mHistory.size()-1; - while (i >= 0) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); - // Note: the taskId check depends on real taskId fields being non-zero - if (!r.finishing && (token != r) && (taskId != r.task.taskId)) { - return r; - } - i--; - } - return null; - } - - private final ProcessRecord getProcessRecordLocked( + final ProcessRecord getProcessRecordLocked( String processName, int uid) { if (uid == Process.SYSTEM_UID) { // The system gets to run in any process. If there are multiple @@ -1749,8 +1715,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return proc; } - private void ensurePackageDexOpt(String packageName) { - IPackageManager pm = ActivityThread.getPackageManager(); + void ensurePackageDexOpt(String packageName) { + IPackageManager pm = AppGlobals.getPackageManager(); try { if (pm.performDexOpt(packageName)) { mDidDexOpt = true; @@ -1759,157 +1725,14 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private boolean isNextTransitionForward() { + boolean isNextTransitionForward() { int transit = mWindowManager.getPendingAppTransition(); return transit == WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN || transit == WindowManagerPolicy.TRANSIT_TASK_OPEN || transit == WindowManagerPolicy.TRANSIT_TASK_TO_FRONT; } - private final boolean realStartActivityLocked(HistoryRecord r, - ProcessRecord app, boolean andResume, boolean checkConfig) - throws RemoteException { - - r.startFreezingScreenLocked(app, 0); - mWindowManager.setAppVisibility(r, true); - - // Have the window manager re-evaluate the orientation of - // the screen based on the new activity order. Note that - // as a result of this, it can call back into the activity - // manager with a new orientation. We don't care about that, - // because the activity is not currently running so we are - // just restarting it anyway. - if (checkConfig) { - Configuration config = mWindowManager.updateOrientationFromAppTokens( - mConfiguration, - r.mayFreezeScreenLocked(app) ? r : null); - updateConfigurationLocked(config, r); - } - - r.app = app; - - if (localLOGV) Slog.v(TAG, "Launching: " + r); - - int idx = app.activities.indexOf(r); - if (idx < 0) { - app.activities.add(r); - } - updateLruProcessLocked(app, true, true); - - try { - if (app.thread == null) { - throw new RemoteException(); - } - List<ResultInfo> results = null; - List<Intent> newIntents = null; - if (andResume) { - results = r.results; - newIntents = r.newIntents; - } - if (DEBUG_SWITCH) Slog.v(TAG, "Launching: " + r - + " icicle=" + r.icicle - + " with results=" + results + " newIntents=" + newIntents - + " andResume=" + andResume); - if (andResume) { - EventLog.writeEvent(EventLogTags.AM_RESTART_ACTIVITY, - System.identityHashCode(r), - r.task.taskId, r.shortComponentName); - } - if (r.isHomeActivity) { - mHomeProcess = app; - } - ensurePackageDexOpt(r.intent.getComponent().getPackageName()); - app.thread.scheduleLaunchActivity(new Intent(r.intent), r, - System.identityHashCode(r), - r.info, r.icicle, results, newIntents, !andResume, - isNextTransitionForward()); - } catch (RemoteException e) { - if (r.launchFailed) { - // This is the second time we failed -- finish activity - // and give up. - Slog.e(TAG, "Second failure launching " - + r.intent.getComponent().flattenToShortString() - + ", giving up", e); - appDiedLocked(app, app.pid, app.thread); - requestFinishActivityLocked(r, Activity.RESULT_CANCELED, null, - "2nd-crash"); - return false; - } - - // This is the first time we failed -- restart process and - // retry. - app.activities.remove(r); - throw e; - } - - r.launchFailed = false; - if (updateLRUListLocked(r)) { - Slog.w(TAG, "Activity " + r - + " being launched, but already in LRU list"); - } - - if (andResume) { - // 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(); - completeResumeLocked(r); - pauseIfSleepingLocked(); - } else { - // This activity is not starting in the resumed state... which - // should look like we asked it to pause+stop (but remain visible), - // and it has done so and reported back the current icicle and - // other state. - r.state = ActivityState.STOPPED; - r.stopped = true; - } - - // 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 - // switch back to it faster and look better. - startSetupActivityLocked(); - - return true; - } - - private final void startSpecificActivityLocked(HistoryRecord r, - boolean andResume, boolean checkConfig) { - // Is this activity's application already running? - ProcessRecord app = getProcessRecordLocked(r.processName, - r.info.applicationInfo.uid); - - if (r.startTime == 0) { - r.startTime = SystemClock.uptimeMillis(); - if (mInitialStartTime == 0) { - mInitialStartTime = r.startTime; - } - } else if (mInitialStartTime == 0) { - mInitialStartTime = SystemClock.uptimeMillis(); - } - - if (app != null && app.thread != null) { - try { - realStartActivityLocked(r, app, andResume, checkConfig); - return; - } catch (RemoteException e) { - Slog.w(TAG, "Exception when starting activity " - + r.intent.getComponent().flattenToShortString(), e); - } - - // If a dead object exception was thrown -- fall through to - // restart the application. - } - - startProcessLocked(r.processName, r.info.applicationInfo, true, 0, - "activity", r.intent.getComponent(), false); - } - - private final ProcessRecord startProcessLocked(String processName, + final ProcessRecord startProcessLocked(String processName, ApplicationInfo info, boolean knownToBeDead, int intentFlags, String hostingType, ComponentName hostingName, boolean allowWhileBooting) { ProcessRecord app = getProcessRecordLocked(processName, info.uid); @@ -1927,10 +1750,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (!knownToBeDead || app.thread == null) { // We already have the app running, or are waiting for it to // come up (we have a pid but not yet its thread), so keep it. + if (DEBUG_PROCESSES) Slog.v(TAG, "App already running: " + app); return app; } else { // An application record is attached to a previous process, // clean it up now. + if (DEBUG_PROCESSES) Slog.v(TAG, "App died: " + app); handleAppDiedLocked(app, true); } } @@ -1942,6 +1767,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // If we are in the background, then check to see if this process // is bad. If so, we will just silently fail. if (mBadProcesses.get(info.processName, info.uid) != null) { + if (DEBUG_PROCESSES) Slog.v(TAG, "Bad process: " + info.uid + + "/" + info.processName); return null; } } else { @@ -1949,6 +1776,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // crash count so that we won't make it bad until they see at // least one crash dialog again, and make the process good again // if it had been bad. + if (DEBUG_PROCESSES) Slog.v(TAG, "Clearing bad process: " + info.uid + + "/" + info.processName); mProcessCrashTimes.remove(info.processName, info.uid); if (mBadProcesses.get(info.processName, info.uid) != null) { EventLog.writeEvent(EventLogTags.AM_PROC_GOOD, info.uid, @@ -1970,12 +1799,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // If the system is not ready yet, then hold off on starting this // process until it is. - if (!mSystemReady + if (!mProcessesReady && !isAllowedWhileBooting(info) && !allowWhileBooting) { if (!mProcessesOnHold.contains(app)) { mProcessesOnHold.add(app); } + if (DEBUG_PROCESSES) Slog.v(TAG, "System not ready, putting on hold: " + app); return app; } @@ -1997,6 +1827,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen app.pid = 0; } + if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG, + "startProcessLocked removing on hold: " + app); mProcessesOnHold.remove(app); updateCpuStats(); @@ -2055,7 +1887,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen hostingNameStr != null ? hostingNameStr : ""); if (app.persistent) { - Watchdog.getInstance().processStarted(app, app.processName, pid); + Watchdog.getInstance().processStarted(app.processName, pid); } StringBuilder buf = mStringBuilder; @@ -2110,365 +1942,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private final void startPausingLocked(boolean userLeaving, boolean uiSleeping) { - if (mPausingActivity != null) { - RuntimeException e = new RuntimeException(); - Slog.e(TAG, "Trying to pause when pause is already pending for " - + mPausingActivity, e); - } - HistoryRecord prev = mResumedActivity; - if (prev == null) { - RuntimeException e = new RuntimeException(); - Slog.e(TAG, "Trying to pause when nothing is resumed", e); - resumeTopActivityLocked(null); - return; - } - if (DEBUG_PAUSE) Slog.v(TAG, "Start pausing: " + prev); - mResumedActivity = null; - mPausingActivity = prev; - mLastPausedActivity = prev; - prev.state = ActivityState.PAUSING; - prev.task.touchActiveTime(); - - updateCpuStats(); - - if (prev.app != null && prev.app.thread != null) { - if (DEBUG_PAUSE) Slog.v(TAG, "Enqueueing pending pause: " + prev); - try { - EventLog.writeEvent(EventLogTags.AM_PAUSE_ACTIVITY, - System.identityHashCode(prev), - prev.shortComponentName); - prev.app.thread.schedulePauseActivity(prev, prev.finishing, userLeaving, - prev.configChangeFlags); - updateUsageStats(prev, false); - } catch (Exception e) { - // Ignore exception, if process died other code will cleanup. - Slog.w(TAG, "Exception thrown during pause", e); - mPausingActivity = null; - mLastPausedActivity = null; - } - } else { - mPausingActivity = null; - mLastPausedActivity = null; - } - - // If we are not going to sleep, we want to ensure the device is - // awake until the next activity is started. - if (!mSleeping && !mShuttingDown) { - mLaunchingActivity.acquire(); - if (!mHandler.hasMessages(LAUNCH_TIMEOUT_MSG)) { - // To be safe, don't allow the wake lock to be held for too long. - Message msg = mHandler.obtainMessage(LAUNCH_TIMEOUT_MSG); - mHandler.sendMessageDelayed(msg, LAUNCH_TIMEOUT); - } - } - - - if (mPausingActivity != null) { - // Have the window manager pause its key dispatching until the new - // activity has started. If we're pausing the activity just because - // the screen is being turned off and the UI is sleeping, don't interrupt - // key dispatch; the same activity will pick it up again on wakeup. - if (!uiSleeping) { - prev.pauseKeyDispatchingLocked(); - } else { - if (DEBUG_PAUSE) Slog.v(TAG, "Key dispatch not paused for screen off"); - } - - // Schedule a pause timeout in case the app doesn't respond. - // We don't give it much time because this directly impacts the - // responsiveness seen by the user. - Message msg = mHandler.obtainMessage(PAUSE_TIMEOUT_MSG); - msg.obj = prev; - mHandler.sendMessageDelayed(msg, PAUSE_TIMEOUT); - if (DEBUG_PAUSE) Slog.v(TAG, "Waiting for pause to complete..."); - } else { - // This activity failed to schedule the - // pause, so just treat it as being paused now. - if (DEBUG_PAUSE) Slog.v(TAG, "Activity not running, resuming next."); - resumeTopActivityLocked(null); - } - } - - private final void completePauseLocked() { - HistoryRecord prev = mPausingActivity; - if (DEBUG_PAUSE) Slog.v(TAG, "Complete pause: " + prev); - - if (prev != null) { - if (prev.finishing) { - if (DEBUG_PAUSE) Slog.v(TAG, "Executing finish of activity: " + prev); - prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE); - } else if (prev.app != null) { - if (DEBUG_PAUSE) Slog.v(TAG, "Enqueueing pending stop: " + prev); - if (prev.waitingVisible) { - prev.waitingVisible = false; - mWaitingVisibleActivities.remove(prev); - if (DEBUG_SWITCH || DEBUG_PAUSE) Slog.v( - TAG, "Complete pause, no longer waiting: " + prev); - } - if (prev.configDestroy) { - // The previous is being paused because the configuration - // is changing, which means it is actually stopping... - // To juggle the fact that we are also starting a new - // instance right now, we need to first completely stop - // the current instance before starting the new one. - if (DEBUG_PAUSE) Slog.v(TAG, "Destroying after pause: " + prev); - destroyActivityLocked(prev, true); - } else { - mStoppingActivities.add(prev); - if (mStoppingActivities.size() > 3) { - // If we already have a few activities waiting to stop, - // then give up on things going idle and start clearing - // them out. - if (DEBUG_PAUSE) Slog.v(TAG, "To many pending stops, forcing idle"); - Message msg = Message.obtain(); - msg.what = ActivityManagerService.IDLE_NOW_MSG; - mHandler.sendMessage(msg); - } - } - } else { - if (DEBUG_PAUSE) Slog.v(TAG, "App died during pause, not stopping: " + prev); - prev = null; - } - mPausingActivity = null; - } - - if (!mSleeping && !mShuttingDown) { - resumeTopActivityLocked(prev); - } else { - if (mGoingToSleep.isHeld()) { - mGoingToSleep.release(); - } - if (mShuttingDown) { - notifyAll(); - } - } - - if (prev != null) { - prev.resumeKeyDispatchingLocked(); - } - - if (prev.app != null && prev.cpuTimeAtResume > 0 && mBatteryStatsService.isOnBattery()) { - long diff = 0; - synchronized (mProcessStatsThread) { - diff = mProcessStats.getCpuTimeForPid(prev.app.pid) - prev.cpuTimeAtResume; - } - if (diff > 0) { - BatteryStatsImpl bsi = mBatteryStatsService.getActiveStatistics(); - synchronized (bsi) { - BatteryStatsImpl.Uid.Proc ps = - bsi.getProcessStatsLocked(prev.info.applicationInfo.uid, - prev.info.packageName); - if (ps != null) { - ps.addForegroundTimeLocked(diff); - } - } - } - } - prev.cpuTimeAtResume = 0; // reset it - } - - /** - * Once we know that we have asked an application to put an activity in - * the resumed state (either by launching it or explicitly telling it), - * this function updates the rest of our state to match that fact. - */ - private final void completeResumeLocked(HistoryRecord next) { - next.idle = false; - next.results = null; - next.newIntents = null; - - // schedule an idle timeout in case the app doesn't do it for us. - Message msg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG); - msg.obj = next; - mHandler.sendMessageDelayed(msg, IDLE_TIMEOUT); - - if (false) { - // The activity was never told to pause, so just keep - // things going as-is. To maintain our own state, - // we need to emulate it coming back and saying it is - // idle. - msg = mHandler.obtainMessage(IDLE_NOW_MSG); - msg.obj = next; - mHandler.sendMessage(msg); - } - - reportResumedActivityLocked(next); - - next.thumbnail = null; - setFocusedActivityLocked(next); - next.resumeKeyDispatchingLocked(); - ensureActivitiesVisibleLocked(null, 0); - mWindowManager.executeAppTransition(); - mNoAnimActivities.clear(); - - // Mark the point when the activity is resuming - // TODO: To be more accurate, the mark should be before the onCreate, - // not after the onResume. But for subsequent starts, onResume is fine. - if (next.app != null) { - synchronized (mProcessStatsThread) { - next.cpuTimeAtResume = mProcessStats.getCpuTimeForPid(next.app.pid); - } - } else { - next.cpuTimeAtResume = 0; // Couldn't get the cpu time of process - } - } - - /** - * Make sure that all activities that need to be visible (that is, they - * currently can be seen by the user) actually are. - */ - private final void ensureActivitiesVisibleLocked(HistoryRecord top, - HistoryRecord starting, String onlyThisProcess, int configChanges) { - if (DEBUG_VISBILITY) Slog.v( - TAG, "ensureActivitiesVisible behind " + top - + " configChanges=0x" + Integer.toHexString(configChanges)); - - // If the top activity is not fullscreen, then we need to - // make sure any activities under it are now visible. - final int count = mHistory.size(); - int i = count-1; - while (mHistory.get(i) != top) { - i--; - } - HistoryRecord r; - boolean behindFullscreen = false; - for (; i>=0; i--) { - r = (HistoryRecord)mHistory.get(i); - if (DEBUG_VISBILITY) Slog.v( - TAG, "Make visible? " + r + " finishing=" + r.finishing - + " state=" + r.state); - if (r.finishing) { - continue; - } - - final boolean doThisProcess = onlyThisProcess == null - || onlyThisProcess.equals(r.processName); - - // First: if this is not the current activity being started, make - // sure it matches the current configuration. - if (r != starting && doThisProcess) { - ensureActivityConfigurationLocked(r, 0); - } - - if (r.app == null || r.app.thread == null) { - if (onlyThisProcess == null - || onlyThisProcess.equals(r.processName)) { - // This activity needs to be visible, but isn't even - // running... get it started, but don't resume it - // at this point. - if (DEBUG_VISBILITY) Slog.v( - TAG, "Start and freeze screen for " + r); - if (r != starting) { - r.startFreezingScreenLocked(r.app, configChanges); - } - if (!r.visible) { - if (DEBUG_VISBILITY) Slog.v( - TAG, "Starting and making visible: " + r); - mWindowManager.setAppVisibility(r, true); - } - if (r != starting) { - startSpecificActivityLocked(r, false, false); - } - } - - } else if (r.visible) { - // If this activity is already visible, then there is nothing - // else to do here. - if (DEBUG_VISBILITY) Slog.v( - TAG, "Skipping: already visible at " + r); - r.stopFreezingScreenLocked(false); - - } else if (onlyThisProcess == null) { - // This activity is not currently visible, but is running. - // Tell it to become visible. - r.visible = true; - if (r.state != ActivityState.RESUMED && r != starting) { - // If this activity is paused, tell it - // to now show its window. - if (DEBUG_VISBILITY) Slog.v( - TAG, "Making visible and scheduling visibility: " + r); - try { - mWindowManager.setAppVisibility(r, true); - r.app.thread.scheduleWindowVisibility(r, true); - r.stopFreezingScreenLocked(false); - } catch (Exception e) { - // Just skip on any failure; we'll make it - // visible when it next restarts. - Slog.w(TAG, "Exception thrown making visibile: " - + r.intent.getComponent(), e); - } - } - } - - // Aggregate current change flags. - configChanges |= r.configChangeFlags; - - if (r.fullscreen) { - // At this point, nothing else needs to be shown - if (DEBUG_VISBILITY) Slog.v( - TAG, "Stopping: fullscreen at " + r); - behindFullscreen = true; - i--; - break; - } - } - - // Now for any activities that aren't visible to the user, make - // sure they no longer are keeping the screen frozen. - while (i >= 0) { - r = (HistoryRecord)mHistory.get(i); - if (DEBUG_VISBILITY) Slog.v( - TAG, "Make invisible? " + r + " finishing=" + r.finishing - + " state=" + r.state - + " behindFullscreen=" + behindFullscreen); - if (!r.finishing) { - if (behindFullscreen) { - if (r.visible) { - if (DEBUG_VISBILITY) Slog.v( - TAG, "Making invisible: " + r); - r.visible = false; - try { - mWindowManager.setAppVisibility(r, false); - if ((r.state == ActivityState.STOPPING - || r.state == ActivityState.STOPPED) - && r.app != null && r.app.thread != null) { - if (DEBUG_VISBILITY) Slog.v( - TAG, "Scheduling invisibility: " + r); - r.app.thread.scheduleWindowVisibility(r, false); - } - } catch (Exception e) { - // Just skip on any failure; we'll make it - // visible when it next restarts. - Slog.w(TAG, "Exception thrown making hidden: " - + r.intent.getComponent(), e); - } - } else { - if (DEBUG_VISBILITY) Slog.v( - TAG, "Already invisible: " + r); - } - } else if (r.fullscreen) { - if (DEBUG_VISBILITY) Slog.v( - TAG, "Now behindFullscreen: " + r); - behindFullscreen = true; - } - } - i--; - } - } - - /** - * Version of ensureActivitiesVisible that can easily be called anywhere. - */ - private final void ensureActivitiesVisibleLocked(HistoryRecord starting, - int configChanges) { - HistoryRecord r = topRunningActivityLocked(null); - if (r != null) { - ensureActivitiesVisibleLocked(r, starting, null, configChanges); - } - } - - private void updateUsageStats(HistoryRecord resumedComponent, boolean resumed) { + void updateUsageStats(ActivityRecord resumedComponent, boolean resumed) { if (resumed) { mUsageStatsService.noteResumeComponent(resumedComponent.realActivity); } else { @@ -2476,7 +1950,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private boolean startHomeActivityLocked() { + boolean startHomeActivityLocked() { if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL && mTopAction == null) { // We are running in factory test mode, but unable to find @@ -2503,7 +1977,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen aInfo.applicationInfo.uid); if (app == null || app.instrumentationClass == null) { intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); - startActivityLocked(null, intent, null, null, 0, aInfo, + mMainStack.startActivityLocked(null, intent, null, null, 0, aInfo, null, null, 0, 0, 0, false, false); } } @@ -2515,7 +1989,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen /** * Starts the "new version setup screen" if appropriate. */ - private void startSetupActivityLocked() { + void startSetupActivityLocked() { // Only do this once per boot. if (mCheckedForSetup) { return; @@ -2559,14 +2033,14 @@ public final class ActivityManagerService extends ActivityManagerNative implemen intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setComponent(new ComponentName( ri.activityInfo.packageName, ri.activityInfo.name)); - startActivityLocked(null, intent, null, null, 0, ri.activityInfo, + mMainStack.startActivityLocked(null, intent, null, null, 0, ri.activityInfo, null, null, 0, 0, 0, false, false); } } } } - private void reportResumedActivityLocked(HistoryRecord r) { + void reportResumedActivityLocked(ActivityRecord r) { //Slog.i(TAG, "**** REPORT RESUME: " + r); final int identHash = System.identityHashCode(r); @@ -2585,1210 +2059,27 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } mWatchers.finishBroadcast(); } - - /** - * Ensure that the top activity in the stack is resumed. - * - * @param prev The previously resumed activity, for when in the process - * of pausing; can be null to call from elsewhere. - * - * @return Returns true if something is being resumed, or false if - * nothing happened. - */ - private final boolean resumeTopActivityLocked(HistoryRecord prev) { - // Find the first activity that is not finishing. - HistoryRecord next = topRunningActivityLocked(null); - - // Remember how we'll process this pause/resume situation, and ensure - // that the state is reset however we wind up proceeding. - final boolean userLeaving = mUserLeaving; - mUserLeaving = false; - - if (next == null) { - // There are no more activities! Let's just start up the - // Launcher... - return startHomeActivityLocked(); - } - - next.delayedResume = false; - - // If the top activity is the resumed one, nothing to do. - if (mResumedActivity == next && next.state == ActivityState.RESUMED) { - // Make sure we have executed any pending transitions, since there - // should be nothing left to do at this point. - mWindowManager.executeAppTransition(); - mNoAnimActivities.clear(); - return false; - } - - // If we are sleeping, and there is no resumed activity, and the top - // activity is paused, well that is the state we want. - if ((mSleeping || mShuttingDown) - && mLastPausedActivity == next && next.state == ActivityState.PAUSED) { - // Make sure we have executed any pending transitions, since there - // should be nothing left to do at this point. - mWindowManager.executeAppTransition(); - mNoAnimActivities.clear(); - return false; - } - - // The activity may be waiting for stop, but that is no longer - // appropriate for it. - mStoppingActivities.remove(next); - mWaitingVisibleActivities.remove(next); - - if (DEBUG_SWITCH) Slog.v(TAG, "Resuming " + next); - - // If we are currently pausing an activity, then don't do anything - // until that is done. - if (mPausingActivity != null) { - if (DEBUG_SWITCH) Slog.v(TAG, "Skip resume: pausing=" + mPausingActivity); - return false; - } - - // We need to start pausing the current activity so the top one - // can be resumed... - if (mResumedActivity != null) { - if (DEBUG_SWITCH) Slog.v(TAG, "Skip resume: need to start pausing"); - startPausingLocked(userLeaving, false); - return true; - } - - if (prev != null && prev != next) { - if (!prev.waitingVisible && next != null && !next.nowVisible) { - prev.waitingVisible = true; - mWaitingVisibleActivities.add(prev); - if (DEBUG_SWITCH) Slog.v( - TAG, "Resuming top, waiting visible to hide: " + prev); - } else { - // The next activity is already visible, so hide the previous - // activity's windows right now so we can show the new one ASAP. - // We only do this if the previous is finishing, which should mean - // it is on top of the one being resumed so hiding it quickly - // is good. Otherwise, we want to do the normal route of allowing - // the resumed activity to be shown so we can decide if the - // previous should actually be hidden depending on whether the - // new one is found to be full-screen or not. - if (prev.finishing) { - mWindowManager.setAppVisibility(prev, false); - if (DEBUG_SWITCH) Slog.v(TAG, "Not waiting for visible to hide: " - + prev + ", waitingVisible=" - + (prev != null ? prev.waitingVisible : null) - + ", nowVisible=" + next.nowVisible); - } else { - if (DEBUG_SWITCH) Slog.v(TAG, "Previous already visible but still waiting to hide: " - + prev + ", waitingVisible=" - + (prev != null ? prev.waitingVisible : null) - + ", nowVisible=" + next.nowVisible); - } - } - } - - // 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. - if (prev != null) { - if (prev.finishing) { - if (DEBUG_TRANSITION) Slog.v(TAG, - "Prepare close transition: prev=" + prev); - if (mNoAnimActivities.contains(prev)) { - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); - } else { - mWindowManager.prepareAppTransition(prev.task == next.task - ? WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE - : WindowManagerPolicy.TRANSIT_TASK_CLOSE); - } - mWindowManager.setAppWillBeHidden(prev); - mWindowManager.setAppVisibility(prev, false); - } else { - if (DEBUG_TRANSITION) Slog.v(TAG, - "Prepare open transition: prev=" + prev); - if (mNoAnimActivities.contains(next)) { - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); - } else { - mWindowManager.prepareAppTransition(prev.task == next.task - ? WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN - : WindowManagerPolicy.TRANSIT_TASK_OPEN); - } - } - if (false) { - mWindowManager.setAppWillBeHidden(prev); - mWindowManager.setAppVisibility(prev, false); - } - } else if (mHistory.size() > 1) { - if (DEBUG_TRANSITION) Slog.v(TAG, - "Prepare open transition: no previous"); - if (mNoAnimActivities.contains(next)) { - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); - } else { - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN); - } - } - - if (next.app != null && next.app.thread != null) { - if (DEBUG_SWITCH) Slog.v(TAG, "Resume running: " + next); - - // This activity is now becoming visible. - mWindowManager.setAppVisibility(next, true); - - HistoryRecord lastResumedActivity = mResumedActivity; - ActivityState lastState = next.state; - - updateCpuStats(); - - next.state = ActivityState.RESUMED; - mResumedActivity = next; - next.task.touchActiveTime(); - updateLruProcessLocked(next.app, true, true); - updateLRUListLocked(next); - - // Have the window manager re-evaluate the orientation of - // the screen based on the new activity order. - boolean updated; - synchronized (this) { - Configuration config = mWindowManager.updateOrientationFromAppTokens( - mConfiguration, - next.mayFreezeScreenLocked(next.app) ? next : null); - if (config != null) { - next.frozenBeforeDestroy = true; - } - updated = updateConfigurationLocked(config, next); - } - if (!updated) { - // The configuration update wasn't able to keep the existing - // instance of the activity, and instead started a new one. - // We should be all done, but let's just make sure our activity - // is still at the top and schedule another run if something - // weird happened. - HistoryRecord nextNext = topRunningActivityLocked(null); - if (DEBUG_SWITCH) Slog.i(TAG, - "Activity config changed during resume: " + next - + ", new next: " + nextNext); - if (nextNext != next) { - // Do over! - mHandler.sendEmptyMessage(RESUME_TOP_ACTIVITY_MSG); - } - setFocusedActivityLocked(next); - ensureActivitiesVisibleLocked(null, 0); - mWindowManager.executeAppTransition(); - mNoAnimActivities.clear(); - return true; - } - - try { - // Deliver all pending results. - ArrayList a = next.results; - if (a != null) { - final int N = a.size(); - if (!next.finishing && N > 0) { - if (DEBUG_RESULTS) Slog.v( - TAG, "Delivering results to " + next - + ": " + a); - next.app.thread.scheduleSendResult(next, a); - } - } - - if (next.newIntents != null) { - next.app.thread.scheduleNewIntent(next.newIntents, next); - } - EventLog.writeEvent(EventLogTags.AM_RESUME_ACTIVITY, - System.identityHashCode(next), - next.task.taskId, next.shortComponentName); - - next.app.thread.scheduleResumeActivity(next, - isNextTransitionForward()); - - pauseIfSleepingLocked(); - - } catch (Exception e) { - // Whoops, need to restart this activity! - next.state = lastState; - mResumedActivity = lastResumedActivity; - Slog.i(TAG, "Restarting because process died: " + next); - if (!next.hasBeenLaunched) { - next.hasBeenLaunched = true; - } else { - if (SHOW_APP_STARTING_ICON) { - mWindowManager.setAppStartingWindow( - next, next.packageName, next.theme, - next.nonLocalizedLabel, - next.labelRes, next.icon, null, true); - } - } - startSpecificActivityLocked(next, true, false); - return true; - } - - // From this point on, if something goes wrong there is no way - // to recover the activity. - try { - next.visible = true; - completeResumeLocked(next); - } catch (Exception e) { - // If any exception gets thrown, toss away this - // activity and try the next one. - Slog.w(TAG, "Exception thrown during resume of " + next, e); - requestFinishActivityLocked(next, Activity.RESULT_CANCELED, null, - "resume-exception"); - return true; - } - - // Didn't need to use the icicle, and it is now out of date. - next.icicle = null; - next.haveState = false; - next.stopped = false; - - } else { - // Whoops, need to restart this activity! - if (!next.hasBeenLaunched) { - next.hasBeenLaunched = true; - } else { - if (SHOW_APP_STARTING_ICON) { - mWindowManager.setAppStartingWindow( - next, next.packageName, next.theme, - next.nonLocalizedLabel, - next.labelRes, next.icon, null, true); - } - if (DEBUG_SWITCH) Slog.v(TAG, "Restarting: " + next); - } - startSpecificActivityLocked(next, true, true); - } - - return true; - } - - private final void startActivityLocked(HistoryRecord r, boolean newTask, - boolean doResume) { - final int NH = mHistory.size(); - - int addPos = -1; - - if (!newTask) { - // If starting in an existing task, find where that is... - HistoryRecord next = null; - boolean startIt = true; - for (int i = NH-1; i >= 0; i--) { - HistoryRecord p = (HistoryRecord)mHistory.get(i); - if (p.finishing) { - continue; - } - if (p.task == r.task) { - // Here it is! Now, if this is not yet visible to the - // user, then just add it without starting; it will - // get started when the user navigates back to it. - addPos = i+1; - if (!startIt) { - mHistory.add(addPos, r); - r.inHistory = true; - r.task.numActivities++; - mWindowManager.addAppToken(addPos, r, r.task.taskId, - r.info.screenOrientation, r.fullscreen); - if (VALIDATE_TOKENS) { - mWindowManager.validateAppTokens(mHistory); - } - return; - } - break; - } - if (p.fullscreen) { - startIt = false; - } - next = p; - } - } - - // Place a new activity at top of stack, so it is next to interact - // with the user. - if (addPos < 0) { - addPos = mHistory.size(); - } - - // If we are not placing the new activity frontmost, we do not want - // to deliver the onUserLeaving callback to the actual frontmost - // activity - if (addPos < NH) { - mUserLeaving = false; - if (DEBUG_USER_LEAVING) Slog.v(TAG, "startActivity() behind front, mUserLeaving=false"); - } - - // Slot the activity into the history stack and proceed - mHistory.add(addPos, r); - r.inHistory = true; - r.frontOfTask = newTask; - r.task.numActivities++; - if (NH > 0) { - // We want to show the starting preview window if we are - // switching to a new task, or the next activity's process is - // not currently running. - boolean showStartingIcon = newTask; - ProcessRecord proc = r.app; - if (proc == null) { - proc = mProcessNames.get(r.processName, r.info.applicationInfo.uid); - } - if (proc == null || proc.thread == null) { - showStartingIcon = true; - } - if (DEBUG_TRANSITION) Slog.v(TAG, - "Prepare open transition: starting " + r); - if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); - mNoAnimActivities.add(r); - } else if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) { - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_OPEN); - mNoAnimActivities.remove(r); - } else { - mWindowManager.prepareAppTransition(newTask - ? WindowManagerPolicy.TRANSIT_TASK_OPEN - : WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN); - mNoAnimActivities.remove(r); - } - mWindowManager.addAppToken( - addPos, r, r.task.taskId, r.info.screenOrientation, r.fullscreen); - boolean doShow = true; - if (newTask) { - // Even though this activity is starting fresh, we still need - // to reset it to make sure we apply affinities to move any - // existing activities from other tasks in to it. - // If the caller has requested that the target task be - // reset, then do so. - if ((r.intent.getFlags() - &Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { - resetTaskIfNeededLocked(r, r); - doShow = topRunningNonDelayedActivityLocked(null) == r; - } - } - if (SHOW_APP_STARTING_ICON && doShow) { - // Figure out if we are transitioning from another activity that is - // "has the same starting icon" as the next one. This allows the - // window manager to keep the previous window it had previously - // created, if it still had one. - HistoryRecord prev = mResumedActivity; - if (prev != null) { - // We don't want to reuse the previous starting preview if: - // (1) The current activity is in a different task. - if (prev.task != r.task) prev = null; - // (2) The current activity is already displayed. - else if (prev.nowVisible) prev = null; - } - mWindowManager.setAppStartingWindow( - r, r.packageName, r.theme, r.nonLocalizedLabel, - r.labelRes, r.icon, prev, showStartingIcon); - } - } else { - // If this is the first activity, don't do any fancy animations, - // because there is nothing for it to animate on top of. - mWindowManager.addAppToken(addPos, r, r.task.taskId, - r.info.screenOrientation, r.fullscreen); - } - if (VALIDATE_TOKENS) { - mWindowManager.validateAppTokens(mHistory); - } - - if (doResume) { - resumeTopActivityLocked(null); - } - } - - /** - * Perform clear operation as requested by - * {@link Intent#FLAG_ACTIVITY_CLEAR_TOP}: search from the top of the - * stack to the given task, then look for - * an instance of that activity in the stack and, if found, finish all - * 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, - * or null if none was found. - */ - private final HistoryRecord performClearTaskLocked(int taskId, - HistoryRecord newR, int launchFlags, boolean doClear) { - int i = mHistory.size(); - - // First find the requested task. - while (i > 0) { - i--; - HistoryRecord r = (HistoryRecord)mHistory.get(i); - if (r.task.taskId == taskId) { - i++; - break; - } - } - - // Now clear it. - while (i > 0) { - i--; - HistoryRecord r = (HistoryRecord)mHistory.get(i); - if (r.finishing) { - continue; - } - if (r.task.taskId != taskId) { - return null; - } - if (r.realActivity.equals(newR.realActivity)) { - // Here it is! Now finish everything in front... - HistoryRecord ret = r; - if (doClear) { - while (i < (mHistory.size()-1)) { - i++; - r = (HistoryRecord)mHistory.get(i); - if (r.finishing) { - continue; - } - if (finishActivityLocked(r, i, Activity.RESULT_CANCELED, - null, "clear")) { - i--; - } - } - } - - // Finally, if this is a normal launch mode (that is, not - // expecting onNewIntent()), then we will finish the current - // instance of the activity so a new fresh one can be started. - if (ret.launchMode == ActivityInfo.LAUNCH_MULTIPLE - && (launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) == 0) { - if (!ret.finishing) { - int index = indexOfTokenLocked(ret); - if (index >= 0) { - finishActivityLocked(ret, index, Activity.RESULT_CANCELED, - null, "clear"); - } - return null; - } - } - - return ret; - } - } - - return null; - } - - /** - * 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. - */ - private final int findActivityInHistoryLocked(HistoryRecord r, int task) { - int i = mHistory.size(); - while (i > 0) { - i--; - HistoryRecord candidate = (HistoryRecord)mHistory.get(i); - if (candidate.task.taskId != task) { - break; - } - if (candidate.realActivity.equals(r.realActivity)) { - return i; - } - } - - return -1; - } - - /** - * Reorder the history stack so that the activity at the given index is - * brought to the front. - */ - private final HistoryRecord moveActivityToFrontLocked(int where) { - HistoryRecord newTop = (HistoryRecord)mHistory.remove(where); - int top = mHistory.size(); - HistoryRecord oldTop = (HistoryRecord)mHistory.get(top-1); - mHistory.add(top, newTop); - oldTop.frontOfTask = false; - newTop.frontOfTask = true; - return newTop; - } - - /** - * Deliver a new Intent to an existing activity, so that its onNewIntent() - * method will be called at the proper time. - */ - private final void deliverNewIntentLocked(HistoryRecord r, Intent intent) { - boolean sent = false; - if (r.state == ActivityState.RESUMED - && r.app != null && r.app.thread != null) { - try { - ArrayList<Intent> ar = new ArrayList<Intent>(); - ar.add(new Intent(intent)); - r.app.thread.scheduleNewIntent(ar, r); - sent = true; - } catch (Exception e) { - Slog.w(TAG, "Exception thrown sending new intent to " + r, e); - } - } - if (!sent) { - r.addNewIntentLocked(new Intent(intent)); - } - } - - private final void logStartActivity(int tag, HistoryRecord r, - TaskRecord task) { - EventLog.writeEvent(tag, - System.identityHashCode(r), task.taskId, - r.shortComponentName, r.intent.getAction(), - r.intent.getType(), r.intent.getDataString(), - r.intent.getFlags()); - } - - private final int startActivityLocked(IApplicationThread caller, - Intent intent, String resolvedType, - Uri[] grantedUriPermissions, - int grantedMode, ActivityInfo aInfo, IBinder resultTo, - String resultWho, int requestCode, - int callingPid, int callingUid, boolean onlyIfNeeded, - boolean componentSpecified) { - Slog.i(TAG, "Starting activity: " + intent); - - HistoryRecord sourceRecord = null; - HistoryRecord resultRecord = null; - if (resultTo != null) { - int index = indexOfTokenLocked(resultTo); - if (DEBUG_RESULTS) Slog.v( - TAG, "Sending result to " + resultTo + " (index " + index + ")"); - if (index >= 0) { - sourceRecord = (HistoryRecord)mHistory.get(index); - if (requestCode >= 0 && !sourceRecord.finishing) { - resultRecord = sourceRecord; - } - } - } - - int launchFlags = intent.getFlags(); - - if ((launchFlags&Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 - && sourceRecord != null) { - // Transfer the result target from the source activity to the new - // one being started, including any failures. - if (requestCode >= 0) { - return START_FORWARD_AND_REQUEST_CONFLICT; - } - resultRecord = sourceRecord.resultTo; - resultWho = sourceRecord.resultWho; - requestCode = sourceRecord.requestCode; - sourceRecord.resultTo = null; - if (resultRecord != null) { - resultRecord.removeResultsLocked( - sourceRecord, resultWho, requestCode); - } - } - - int err = START_SUCCESS; - - if (intent.getComponent() == null) { - // We couldn't find a class that can handle the given Intent. - // That's the end of that! - err = START_INTENT_NOT_RESOLVED; - } - - if (err == START_SUCCESS && aInfo == null) { - // We couldn't find the specific class specified in the Intent. - // Also the end of the line. - err = START_CLASS_NOT_FOUND; - } - - ProcessRecord callerApp = null; - if (err == START_SUCCESS && caller != null) { - callerApp = getRecordForAppLocked(caller); - if (callerApp != null) { - callingPid = callerApp.pid; - callingUid = callerApp.info.uid; - } else { - Slog.w(TAG, "Unable to find app for caller " + caller - + " (pid=" + callingPid + ") when starting: " - + intent.toString()); - err = START_PERMISSION_DENIED; - } - } - - if (err != START_SUCCESS) { - if (resultRecord != null) { - sendActivityResultLocked(-1, - resultRecord, resultWho, requestCode, - Activity.RESULT_CANCELED, null); - } - return err; - } - - final int perm = checkComponentPermission(aInfo.permission, callingPid, - callingUid, aInfo.exported ? -1 : aInfo.applicationInfo.uid); - 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; - Slog.w(TAG, msg); - throw new SecurityException(msg); - } - - if (mController != null) { - boolean abort = false; - try { - // The Intent we give to the watcher has the extra data - // stripped off, since it can contain private information. - Intent watchIntent = intent.cloneFilter(); - abort = !mController.activityStarting(watchIntent, - aInfo.applicationInfo.packageName); - } catch (RemoteException e) { - mController = null; - } - - if (abort) { - if (resultRecord != null) { - sendActivityResultLocked(-1, - resultRecord, resultWho, requestCode, - Activity.RESULT_CANCELED, null); - } - // We pretend to the caller that it was really started, but - // they will just get a cancel result. - return START_SUCCESS; - } - } - - HistoryRecord r = new HistoryRecord(this, callerApp, callingUid, - intent, resolvedType, aInfo, mConfiguration, - resultRecord, resultWho, requestCode, componentSpecified); - - if (mResumedActivity == null - || mResumedActivity.info.applicationInfo.uid != callingUid) { - if (!checkAppSwitchAllowedLocked(callingPid, callingUid, "Activity start")) { - PendingActivityLaunch pal = new PendingActivityLaunch(); - pal.r = r; - pal.sourceRecord = sourceRecord; - pal.grantedUriPermissions = grantedUriPermissions; - pal.grantedMode = grantedMode; - pal.onlyIfNeeded = onlyIfNeeded; - mPendingActivityLaunches.add(pal); - return START_SWITCHES_CANCELED; - } - } - - if (mDidAppSwitch) { - // This is the second allowed switch since we stopped switches, - // so now just generally allow switches. Use case: user presses - // home (switches disabled, switch to home, mDidAppSwitch now true); - // user taps a home icon (coming from home so allowed, we hit here - // and now allow anyone to switch again). - mAppSwitchesAllowedTime = 0; - } else { - mDidAppSwitch = true; - } - - doPendingActivityLaunchesLocked(false); - - return startActivityUncheckedLocked(r, sourceRecord, - grantedUriPermissions, grantedMode, onlyIfNeeded, true); - } - - private final void doPendingActivityLaunchesLocked(boolean doResume) { + final void doPendingActivityLaunchesLocked(boolean doResume) { final int N = mPendingActivityLaunches.size(); if (N <= 0) { return; } for (int i=0; i<N; i++) { PendingActivityLaunch pal = mPendingActivityLaunches.get(i); - startActivityUncheckedLocked(pal.r, pal.sourceRecord, + mMainStack.startActivityUncheckedLocked(pal.r, pal.sourceRecord, pal.grantedUriPermissions, pal.grantedMode, pal.onlyIfNeeded, doResume && i == (N-1)); } mPendingActivityLaunches.clear(); } - private final int startActivityUncheckedLocked(HistoryRecord r, - HistoryRecord sourceRecord, Uri[] grantedUriPermissions, - int grantedMode, boolean onlyIfNeeded, boolean doResume) { - final Intent intent = r.intent; - final int callingUid = r.launchedFromUid; - - int launchFlags = intent.getFlags(); - - // We'll invoke onUserLeaving before onPause only if the launching - // activity did not explicitly state that this is an automated launch. - mUserLeaving = (launchFlags&Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0; - if (DEBUG_USER_LEAVING) Slog.v(TAG, - "startActivity() => mUserLeaving=" + mUserLeaving); - - // If the caller has asked not to resume at this point, we make note - // of this in the record so that we can skip it when trying to find - // the top running activity. - if (!doResume) { - r.delayedResume = true; - } - - HistoryRecord notTop = (launchFlags&Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) - != 0 ? r : null; - - // If the onlyIfNeeded flag is set, then we can do this if the activity - // being launched is the same as the one making the call... or, as - // a special case, if we do not know the caller then we count the - // current top activity as the caller. - if (onlyIfNeeded) { - HistoryRecord checkedCaller = sourceRecord; - if (checkedCaller == null) { - checkedCaller = topRunningNonDelayedActivityLocked(notTop); - } - if (!checkedCaller.realActivity.equals(r.realActivity)) { - // Caller is not the same as launcher, so always needed. - onlyIfNeeded = false; - } - } - - if (grantedUriPermissions != null && callingUid > 0) { - for (int i=0; i<grantedUriPermissions.length; i++) { - grantUriPermissionLocked(callingUid, r.packageName, - grantedUriPermissions[i], grantedMode, r); - } - } - - grantUriPermissionFromIntentLocked(callingUid, r.packageName, - intent, r); - - if (sourceRecord == null) { - // This activity is not being started from another... in this - // case we -always- start a new task. - if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { - Slog.w(TAG, "startActivity called from non-Activity context; forcing Intent.FLAG_ACTIVITY_NEW_TASK for: " - + intent); - launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; - } - } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { - // The original activity who is starting us is running as a single - // instance... this new activity it is starting must go on its - // own task. - launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; - } else if (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE - || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) { - // The activity being started is a single instance... it always - // gets launched into its own task. - launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; - } - - if (r.resultTo != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { - // For whatever reason this activity is being launched into a new - // task... yet the caller has requested a result back. Well, that - // is pretty messed up, so instead immediately send back a cancel - // and let the new task continue launched as normal without a - // dependency on its originator. - Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result."); - sendActivityResultLocked(-1, - r.resultTo, r.resultWho, r.requestCode, - Activity.RESULT_CANCELED, null); - r.resultTo = null; - } - - boolean addingToTask = false; - if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 && - (launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0) - || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK - || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { - // If bring to front is requested, and no result is requested, and - // we can find a task that was started with this same - // component, then instead of launching bring that one to the front. - if (r.resultTo == null) { - // See if there is a task to bring to the front. If this is - // a SINGLE_INSTANCE activity, there can be one and only one - // instance of it in the history, and it is always in its own - // unique task, so we do a special search. - HistoryRecord taskTop = r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE - ? findTaskLocked(intent, r.info) - : findActivityLocked(intent, r.info); - if (taskTop != null) { - if (taskTop.task.intent == null) { - // This task was started because of movement of - // the activity based on affinity... now that we - // are actually launching it, we can assign the - // base intent. - taskTop.task.setIntent(intent, r.info); - } - // If the target task is not in the front, then we need - // to bring it to the front... except... well, with - // SINGLE_TASK_LAUNCH it's not entirely clear. We'd like - // to have the same behavior as if a new instance was - // being started, which means not bringing it to the front - // if the caller is not itself in the front. - HistoryRecord curTop = topRunningNonDelayedActivityLocked(notTop); - if (curTop != null && curTop.task != taskTop.task) { - r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); - boolean callerAtFront = sourceRecord == null - || curTop.task == sourceRecord.task; - if (callerAtFront) { - // We really do want to push this one into the - // user's face, right now. - moveTaskToFrontLocked(taskTop.task, r); - } - } - // If the caller has requested that the target task be - // reset, then do so. - if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { - taskTop = resetTaskIfNeededLocked(taskTop, r); - } - if (onlyIfNeeded) { - // We don't need to start a new activity, and - // the client said not to do anything if that - // is the case, so this is it! And for paranoia, make - // sure we have correctly resumed the top activity. - if (doResume) { - resumeTopActivityLocked(null); - } - return START_RETURN_INTENT_TO_CALLER; - } - 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 - // from the task up to the one being started. In most - // cases this means we are resetting the task to its - // initial state. - HistoryRecord top = performClearTaskLocked( - taskTop.task.taskId, r, launchFlags, true); - if (top != null) { - if (top.frontOfTask) { - // Activity aliases may mean we use different - // intents for the top activity, so make sure - // the task now has the identity of the new - // intent. - top.task.setIntent(r.intent, r.info); - } - logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); - deliverNewIntentLocked(top, r.intent); - } else { - // A special case: we need to - // start the activity because it is not currently - // running, and the caller has asked to clear the - // current task to have this activity at the top. - addingToTask = true; - // Now pretend like this activity is being started - // by the top of its task, so it is put in the - // right place. - sourceRecord = taskTop; - } - } else if (r.realActivity.equals(taskTop.task.realActivity)) { - // In this case the top activity on the task is the - // same as the one being launched, so we take that - // as a request to bring the task to the foreground. - // If the top activity in the task is the root - // activity, deliver this new intent to it if it - // desires. - if ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 - && taskTop.realActivity.equals(r.realActivity)) { - logStartActivity(EventLogTags.AM_NEW_INTENT, r, taskTop.task); - if (taskTop.frontOfTask) { - taskTop.task.setIntent(r.intent, r.info); - } - deliverNewIntentLocked(taskTop, r.intent); - } else if (!r.intent.filterEquals(taskTop.task.intent)) { - // In this case we are launching the root activity - // of the task, but with a different intent. We - // should start a new instance on top. - addingToTask = true; - sourceRecord = taskTop; - } - } else if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) { - // In this case an activity is being launched in to an - // existing task, without resetting that task. This - // is typically the situation of launching an activity - // from a notification or shortcut. We want to place - // the new activity on top of the current task. - addingToTask = true; - sourceRecord = taskTop; - } else if (!taskTop.task.rootWasReset) { - // In this case we are launching in to an existing task - // that has not yet been started from its front door. - // The current task has been brought to the front. - // Ideally, we'd probably like to place this new task - // at the bottom of its stack, but that's a little hard - // to do with the current organization of the code so - // for now we'll just drop it. - taskTop.task.setIntent(r.intent, r.info); - } - if (!addingToTask) { - // 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. - if (doResume) { - resumeTopActivityLocked(null); - } - return START_TASK_TO_FRONT; - } - } - } - } - - //String uri = r.intent.toURI(); - //Intent intent2 = new Intent(uri); - //Slog.i(TAG, "Given intent: " + r.intent); - //Slog.i(TAG, "URI is: " + uri); - //Slog.i(TAG, "To intent: " + intent2); - - if (r.packageName != null) { - // If the activity being launched is the same as the one currently - // at the top, then we need to check if it should only be launched - // once. - HistoryRecord top = topRunningNonDelayedActivityLocked(notTop); - if (top != null && r.resultTo == null) { - if (top.realActivity.equals(r.realActivity)) { - if (top.app != null && top.app.thread != null) { - if ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 - || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP - || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) { - logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task); - // For paranoia, make sure we have correctly - // resumed the top activity. - if (doResume) { - resumeTopActivityLocked(null); - } - if (onlyIfNeeded) { - // We don't need to start a new activity, and - // the client said not to do anything if that - // is the case, so this is it! - return START_RETURN_INTENT_TO_CALLER; - } - deliverNewIntentLocked(top, r.intent); - return START_DELIVERED_TO_TOP; - } - } - } - } - - } else { - if (r.resultTo != null) { - sendActivityResultLocked(-1, - r.resultTo, r.resultWho, r.requestCode, - Activity.RESULT_CANCELED, null); - } - return START_CLASS_NOT_FOUND; - } - - boolean newTask = 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. - mCurTask++; - if (mCurTask <= 0) { - mCurTask = 1; - } - r.task = new TaskRecord(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; - addRecentTaskLocked(r.task); - - } else if (sourceRecord != null) { - if (!addingToTask && - (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { - // In this case, we are adding the activity to an existing - // task, but the caller has asked to clear that task if the - // activity is already running. - HistoryRecord top = performClearTaskLocked( - sourceRecord.task.taskId, r, launchFlags, true); - if (top != null) { - logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); - deliverNewIntentLocked(top, r.intent); - // For paranoia, make sure we have correctly - // resumed the top activity. - if (doResume) { - resumeTopActivityLocked(null); - } - return START_DELIVERED_TO_TOP; - } - } else if (!addingToTask && - (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) { - // In this case, we are launching an activity in our own task - // that may already be running somewhere in the history, and - // we want to shuffle it to the front of the stack if so. - int where = findActivityInHistoryLocked(r, sourceRecord.task.taskId); - if (where >= 0) { - HistoryRecord top = moveActivityToFrontLocked(where); - logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); - deliverNewIntentLocked(top, r.intent); - if (doResume) { - resumeTopActivityLocked(null); - } - return START_DELIVERED_TO_TOP; - } - } - // An existing activity is starting this new activity, so we want - // to keep the new one in the same task as the one that is starting - // it. - r.task = sourceRecord.task; - if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r - + " in existing task " + r.task); - - } else { - // This not being started from an existing activity, and not part - // of a new task... just put it in the top task, though these days - // this case should never happen. - final int N = mHistory.size(); - HistoryRecord prev = - N > 0 ? (HistoryRecord)mHistory.get(N-1) : null; - r.task = prev != null - ? prev.task - : new TaskRecord(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 guessed " + r.task); - } - if (newTask) { - EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.task.taskId); - } - logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task); - startActivityLocked(r, newTask, doResume); - return START_SUCCESS; - } - - void reportActivityLaunchedLocked(boolean timeout, HistoryRecord r, - long thisTime, long totalTime) { - for (int i=mWaitingActivityLaunched.size()-1; i>=0; i--) { - WaitResult w = mWaitingActivityLaunched.get(i); - w.timeout = timeout; - if (r != null) { - w.who = new ComponentName(r.info.packageName, r.info.name); - } - w.thisTime = thisTime; - w.totalTime = totalTime; - } - notify(); - } - - void reportActivityVisibleLocked(HistoryRecord r) { - for (int i=mWaitingActivityVisible.size()-1; i>=0; i--) { - WaitResult w = mWaitingActivityVisible.get(i); - w.timeout = false; - if (r != null) { - w.who = new ComponentName(r.info.packageName, r.info.name); - } - w.totalTime = SystemClock.uptimeMillis() - w.thisTime; - w.thisTime = w.totalTime; - } - notify(); - } - - private 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"); - } - - 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 = - ActivityThread.getPackageManager().resolveIntent( - intent, resolvedType, - PackageManager.MATCH_DEFAULT_ONLY - | STOCK_PM_FLAGS); - aInfo = rInfo != null ? rInfo.activityInfo : null; - } catch (RemoteException e) { - aInfo = 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)); - - // Don't debug things in the system process - if (debug) { - if (!aInfo.processName.equals("system")) { - setDebugApp(aInfo.processName, true, false); - } - } - } - - synchronized (this) { - int callingPid; - int callingUid; - if (caller == null) { - callingPid = Binder.getCallingPid(); - callingUid = Binder.getCallingUid(); - } else { - callingPid = callingUid = -1; - } - - mConfigWillChange = config != null && mConfiguration.diff(config) != 0; - if (DEBUG_CONFIGURATION) Slog.v(TAG, - "Starting activity when config will change = " + mConfigWillChange); - - final long origId = Binder.clearCallingIdentity(); - - int res = startActivityLocked(caller, intent, resolvedType, - grantedUriPermissions, grantedMode, aInfo, - resultTo, resultWho, requestCode, callingPid, callingUid, - onlyIfNeeded, componentSpecified); - - if (mConfigWillChange) { - // If the caller also wants to switch to a new configuration, - // do so now. This allows a clean switch, as we are waiting - // for the current activity to pause (so we will not destroy - // it), and have not yet started the next activity. - enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION, - "updateConfiguration()"); - mConfigWillChange = false; - if (DEBUG_CONFIGURATION) Slog.v(TAG, - "Updating to new configuration after starting activity."); - updateConfigurationLocked(config, null); - } - - Binder.restoreCallingIdentity(origId); - - if (outResult != null) { - outResult.result = res; - if (res == IActivityManager.START_SUCCESS) { - mWaitingActivityLaunched.add(outResult); - do { - try { - wait(); - } catch (InterruptedException e) { - } - } while (!outResult.timeout && outResult.who == null); - } else if (res == IActivityManager.START_TASK_TO_FRONT) { - HistoryRecord r = this.topRunningActivityLocked(null); - if (r.nowVisible) { - outResult.timeout = false; - outResult.who = new ComponentName(r.info.packageName, r.info.name); - outResult.totalTime = 0; - outResult.thisTime = 0; - } else { - outResult.thisTime = SystemClock.uptimeMillis(); - mWaitingActivityVisible.add(outResult); - do { - try { - wait(); - } catch (InterruptedException e) { - } - } while (!outResult.timeout && outResult.who == null); - } - } - } - - return res; - } - } - 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 startActivityMayWait(caller, intent, resolvedType, + return mMainStack.startActivityMayWait(caller, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, debug, null, null); } @@ -3799,7 +2090,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug) { WaitResult res = new WaitResult(); - startActivityMayWait(caller, intent, resolvedType, + mMainStack.startActivityMayWait(caller, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, debug, res, null); return res; @@ -3810,7 +2101,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, Configuration config) { - return startActivityMayWait(caller, intent, resolvedType, + return mMainStack.startActivityMayWait(caller, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, debug, null, config); } @@ -3834,8 +2125,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen synchronized (this) { // If this is coming from the currently resumed activity, it is // effectively saying that app switches are allowed at this point. - if (mResumedActivity != null - && mResumedActivity.info.applicationInfo.uid == + if (mMainStack.mResumedActivity != null + && mMainStack.mResumedActivity.info.applicationInfo.uid == Binder.getCallingUid()) { mAppSwitchesAllowedTime = 0; } @@ -3853,11 +2144,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } synchronized (this) { - int index = indexOfTokenLocked(callingActivity); + int index = mMainStack.indexOfTokenLocked(callingActivity); if (index < 0) { return false; } - HistoryRecord r = (HistoryRecord)mHistory.get(index); + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index); if (r.app == null || r.app.thread == null) { // The caller is not running... d'oh! return false; @@ -3871,7 +2162,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ActivityInfo aInfo = null; try { List<ResolveInfo> resolves = - ActivityThread.getPackageManager().queryIntentActivities( + AppGlobals.getPackageManager().queryIntentActivities( intent, r.resolvedType, PackageManager.MATCH_DEFAULT_ONLY | STOCK_PM_FLAGS); @@ -3915,7 +2206,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen r.finishing = true; // Propagate reply information over to the new activity. - final HistoryRecord resultTo = r.resultTo; + final ActivityRecord resultTo = r.resultTo; final String resultWho = r.resultWho; final int requestCode = r.requestCode; r.resultTo = null; @@ -3926,7 +2217,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen final long origId = Binder.clearCallingIdentity(); // XXX we are not dealing with propagating grantedUriPermissions... // those are not yet exposed to user code, so there is no need. - int res = startActivityLocked(r.app.thread, intent, + int res = mMainStack.startActivityLocked(r.app.thread, intent, r.resolvedType, null, 0, aInfo, resultTo, resultWho, requestCode, -1, r.launchedFromUid, false, false); Binder.restoreCallingIdentity(origId); @@ -3960,7 +2251,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ActivityInfo aInfo; try { ResolveInfo rInfo = - ActivityThread.getPackageManager().resolveIntent( + AppGlobals.getPackageManager().resolveIntent( intent, resolvedType, PackageManager.MATCH_DEFAULT_ONLY | STOCK_PM_FLAGS); aInfo = rInfo != null ? rInfo.activityInfo : null; @@ -3978,13 +2269,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } synchronized(this) { - return startActivityLocked(null, intent, resolvedType, + return mMainStack.startActivityLocked(null, intent, resolvedType, null, 0, aInfo, resultTo, resultWho, requestCode, -1, uid, onlyIfNeeded, componentSpecified); } } - private final void addRecentTaskLocked(TaskRecord task) { + final void addRecentTaskLocked(TaskRecord task) { // Remove any existing entries that are the same kind of task. int N = mRecentTasks.size(); for (int i=0; i<N; i++) { @@ -4010,11 +2301,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public void setRequestedOrientation(IBinder token, int requestedOrientation) { synchronized (this) { - int index = indexOfTokenLocked(token); + int index = mMainStack.indexOfTokenLocked(token); if (index < 0) { return; } - HistoryRecord r = (HistoryRecord)mHistory.get(index); + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index); final long origId = Binder.clearCallingIdentity(); mWindowManager.setAppOrientation(r, requestedOrientation); Configuration config = mWindowManager.updateOrientationFromAppTokens( @@ -4023,7 +2314,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (config != null) { r.frozenBeforeDestroy = true; if (!updateConfigurationLocked(config, r)) { - resumeTopActivityLocked(null); + mMainStack.resumeTopActivityLocked(null); } } Binder.restoreCallingIdentity(origId); @@ -4032,250 +2323,15 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public int getRequestedOrientation(IBinder token) { synchronized (this) { - int index = indexOfTokenLocked(token); + int index = mMainStack.indexOfTokenLocked(token); if (index < 0) { return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; } - HistoryRecord r = (HistoryRecord)mHistory.get(index); + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index); return mWindowManager.getAppOrientation(r); } } - private final void stopActivityLocked(HistoryRecord r) { - if (DEBUG_SWITCH) Slog.d(TAG, "Stopping: " + r); - if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_HISTORY) != 0 - || (r.info.flags&ActivityInfo.FLAG_NO_HISTORY) != 0) { - if (!r.finishing) { - requestFinishActivityLocked(r, Activity.RESULT_CANCELED, null, - "no-history"); - } - } else if (r.app != null && r.app.thread != null) { - if (mFocusedActivity == r) { - setFocusedActivityLocked(topRunningActivityLocked(null)); - } - r.resumeKeyDispatchingLocked(); - try { - r.stopped = false; - r.state = ActivityState.STOPPING; - if (DEBUG_VISBILITY) Slog.v( - TAG, "Stopping visible=" + r.visible + " for " + r); - if (!r.visible) { - mWindowManager.setAppVisibility(r, false); - } - r.app.thread.scheduleStopActivity(r, r.visible, r.configChangeFlags); - } catch (Exception e) { - // Maybe just ignore exceptions here... if the process - // has crashed, our death notification will clean things - // up. - Slog.w(TAG, "Exception thrown during pause", e); - // Just in case, assume it to be stopped. - r.stopped = true; - r.state = ActivityState.STOPPED; - if (r.configDestroy) { - destroyActivityLocked(r, true); - } - } - } - } - - /** - * @return Returns true if the activity is being finished, false if for - * some reason it is being left as-is. - */ - private final boolean requestFinishActivityLocked(IBinder token, int resultCode, - Intent resultData, String reason) { - if (DEBUG_RESULTS) Slog.v( - TAG, "Finishing activity: token=" + token - + ", result=" + resultCode + ", data=" + resultData); - - int index = indexOfTokenLocked(token); - if (index < 0) { - return false; - } - HistoryRecord r = (HistoryRecord)mHistory.get(index); - - // Is this the last activity left? - boolean lastActivity = true; - for (int i=mHistory.size()-1; i>=0; i--) { - HistoryRecord p = (HistoryRecord)mHistory.get(i); - if (!p.finishing && p != r) { - lastActivity = false; - break; - } - } - - // If this is the last activity, but it is the home activity, then - // just don't finish it. - if (lastActivity) { - if (r.intent.hasCategory(Intent.CATEGORY_HOME)) { - return false; - } - } - - finishActivityLocked(r, index, resultCode, resultData, reason); - return true; - } - - /** - * @return Returns true if this activity has been removed from the history - * list, or false if it is still in the list and will be removed later. - */ - private final boolean finishActivityLocked(HistoryRecord r, int index, - int resultCode, Intent resultData, String reason) { - if (r.finishing) { - Slog.w(TAG, "Duplicate finish request for " + r); - return false; - } - - r.finishing = true; - EventLog.writeEvent(EventLogTags.AM_FINISH_ACTIVITY, - System.identityHashCode(r), - r.task.taskId, r.shortComponentName, reason); - r.task.numActivities--; - if (index < (mHistory.size()-1)) { - HistoryRecord next = (HistoryRecord)mHistory.get(index+1); - if (next.task == r.task) { - if (r.frontOfTask) { - // The next activity is now the front of the task. - next.frontOfTask = true; - } - if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) { - // If the caller asked that this activity (and all above it) - // be cleared when the task is reset, don't lose that information, - // but propagate it up to the next activity. - next.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); - } - } - } - - r.pauseKeyDispatchingLocked(); - if (mFocusedActivity == r) { - setFocusedActivityLocked(topRunningActivityLocked(null)); - } - - // send the result - HistoryRecord resultTo = r.resultTo; - if (resultTo != null) { - if (DEBUG_RESULTS) Slog.v(TAG, "Adding result to " + resultTo - + " who=" + r.resultWho + " req=" + r.requestCode - + " res=" + resultCode + " data=" + resultData); - if (r.info.applicationInfo.uid > 0) { - grantUriPermissionFromIntentLocked(r.info.applicationInfo.uid, - r.packageName, resultData, r); - } - resultTo.addResultLocked(r, r.resultWho, r.requestCode, resultCode, - resultData); - r.resultTo = null; - } - else if (DEBUG_RESULTS) Slog.v(TAG, "No result destination from " + r); - - // Make sure this HistoryRecord is not holding on to other resources, - // because clients have remote IPC references to this object so we - // can't assume that will go away and want to avoid circular IPC refs. - r.results = null; - r.pendingResults = null; - r.newIntents = null; - r.icicle = null; - - if (mPendingThumbnails.size() > 0) { - // There are clients waiting to receive thumbnails so, in case - // this is an activity that someone is waiting for, add it - // to the pending list so we can correctly update the clients. - mCancelledThumbnails.add(r); - } - - if (mResumedActivity == r) { - boolean endTask = index <= 0 - || ((HistoryRecord)mHistory.get(index-1)).task != r.task; - if (DEBUG_TRANSITION) Slog.v(TAG, - "Prepare close transition: finishing " + r); - mWindowManager.prepareAppTransition(endTask - ? WindowManagerPolicy.TRANSIT_TASK_CLOSE - : WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE); - - // Tell window manager to prepare for this one to be removed. - mWindowManager.setAppVisibility(r, false); - - if (mPausingActivity == null) { - if (DEBUG_PAUSE) Slog.v(TAG, "Finish needs to pause: " + r); - if (DEBUG_USER_LEAVING) Slog.v(TAG, "finish() => pause with userLeaving=false"); - startPausingLocked(false, false); - } - - } else if (r.state != ActivityState.PAUSING) { - // If the activity is PAUSING, we will complete the finish once - // it is done pausing; else we can just directly finish it here. - if (DEBUG_PAUSE) Slog.v(TAG, "Finish not pausing: " + r); - return finishCurrentActivityLocked(r, index, - FINISH_AFTER_PAUSE) == null; - } else { - if (DEBUG_PAUSE) Slog.v(TAG, "Finish waiting for pause of: " + r); - } - - return false; - } - - private static final int FINISH_IMMEDIATELY = 0; - private static final int FINISH_AFTER_PAUSE = 1; - private static final int FINISH_AFTER_VISIBLE = 2; - - private final HistoryRecord finishCurrentActivityLocked(HistoryRecord r, - int mode) { - final int index = indexOfTokenLocked(r); - if (index < 0) { - return null; - } - - return finishCurrentActivityLocked(r, index, mode); - } - - private final HistoryRecord finishCurrentActivityLocked(HistoryRecord r, - int index, int mode) { - // First things first: if this activity is currently visible, - // and the resumed activity is not yet visible, then hold off on - // finishing until the resumed one becomes visible. - if (mode == FINISH_AFTER_VISIBLE && r.nowVisible) { - if (!mStoppingActivities.contains(r)) { - mStoppingActivities.add(r); - if (mStoppingActivities.size() > 3) { - // If we already have a few activities waiting to stop, - // then give up on things going idle and start clearing - // them out. - Message msg = Message.obtain(); - msg.what = ActivityManagerService.IDLE_NOW_MSG; - mHandler.sendMessage(msg); - } - } - r.state = ActivityState.STOPPING; - updateOomAdjLocked(); - return r; - } - - // make sure the record is cleaned out of other places. - mStoppingActivities.remove(r); - mWaitingVisibleActivities.remove(r); - if (mResumedActivity == r) { - mResumedActivity = null; - } - final ActivityState prevState = r.state; - r.state = ActivityState.FINISHING; - - if (mode == FINISH_IMMEDIATELY - || prevState == ActivityState.STOPPED - || prevState == ActivityState.INITIALIZING) { - // If this activity is already stopped, we can just finish - // it right now. - return destroyActivityLocked(r, true) ? null : r; - } else { - // Need to go through the full pause cycle to get this - // activity into the stopped state and then finish it. - if (localLOGV) Slog.v(TAG, "Enqueueing pending finish: " + r); - mFinishingActivities.add(r); - resumeTopActivityLocked(null); - } - return r; - } - /** * This is the internal entry point for handling Activity.finish(). * @@ -4294,7 +2350,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen synchronized(this) { if (mController != null) { // Find the first activity that is not finishing. - HistoryRecord next = topRunningActivityLocked(token, 0); + ActivityRecord next = mMainStack.topRunningActivityLocked(token, 0); if (next != null) { // ask watcher if this is allowed boolean resumeOK = true; @@ -4310,57 +2366,119 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } final long origId = Binder.clearCallingIdentity(); - boolean res = requestFinishActivityLocked(token, resultCode, + boolean res = mMainStack.requestFinishActivityLocked(token, resultCode, resultData, "app-request"); Binder.restoreCallingIdentity(origId); return res; } } - void sendActivityResultLocked(int callingUid, HistoryRecord r, - String resultWho, int requestCode, int resultCode, Intent data) { - - if (callingUid > 0) { - grantUriPermissionFromIntentLocked(callingUid, r.packageName, - data, r); + public final void finishHeavyWeightApp() { + if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) + != PackageManager.PERMISSION_GRANTED) { + String msg = "Permission Denial: finishHeavyWeightApp() from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " requires " + android.Manifest.permission.FORCE_STOP_PACKAGES; + Slog.w(TAG, msg); + throw new SecurityException(msg); } - - if (DEBUG_RESULTS) Slog.v(TAG, "Send activity result to " + r - + " : who=" + resultWho + " req=" + requestCode - + " res=" + resultCode + " data=" + data); - if (mResumedActivity == r && r.app != null && r.app.thread != null) { - try { - ArrayList<ResultInfo> list = new ArrayList<ResultInfo>(); - list.add(new ResultInfo(resultWho, requestCode, - resultCode, data)); - r.app.thread.scheduleSendResult(r, list); + + synchronized(this) { + if (mHeavyWeightProcess == null) { return; - } catch (Exception e) { - Slog.w(TAG, "Exception thrown sending result to " + r, e); } + + ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>( + mHeavyWeightProcess.activities); + for (int i=0; i<activities.size(); i++) { + ActivityRecord r = activities.get(i); + if (!r.finishing) { + int index = mMainStack.indexOfTokenLocked(r); + if (index >= 0) { + mMainStack.finishActivityLocked(r, index, Activity.RESULT_CANCELED, + null, "finish-heavy"); + } + } + } + + mHeavyWeightProcess = null; + mHandler.sendEmptyMessage(CANCEL_HEAVY_NOTIFICATION_MSG); } - - r.addResultLocked(null, resultWho, requestCode, resultCode, data); } - + + public void crashApplication(int uid, int initialPid, String packageName, + String message) { + if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) + != PackageManager.PERMISSION_GRANTED) { + String msg = "Permission Denial: crashApplication() from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " requires " + android.Manifest.permission.FORCE_STOP_PACKAGES; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + + synchronized(this) { + ProcessRecord proc = null; + + // Figure out which process to kill. We don't trust that initialPid + // still has any relation to current pids, so must scan through the + // list. + synchronized (mPidsSelfLocked) { + for (int i=0; i<mPidsSelfLocked.size(); i++) { + ProcessRecord p = mPidsSelfLocked.valueAt(i); + if (p.info.uid != uid) { + continue; + } + if (p.pid == initialPid) { + proc = p; + break; + } + for (String str : p.pkgList) { + if (str.equals(packageName)) { + proc = p; + } + } + } + } + + if (proc == null) { + Slog.w(TAG, "crashApplication: nothing for uid=" + uid + + " initialPid=" + initialPid + + " packageName=" + packageName); + return; + } + + if (proc.thread != null) { + long ident = Binder.clearCallingIdentity(); + try { + proc.thread.scheduleCrash(message); + } catch (RemoteException e) { + } + Binder.restoreCallingIdentity(ident); + } + } + } + public final void finishSubActivity(IBinder token, String resultWho, int requestCode) { synchronized(this) { - int index = indexOfTokenLocked(token); + int index = mMainStack.indexOfTokenLocked(token); if (index < 0) { return; } - HistoryRecord self = (HistoryRecord)mHistory.get(index); + ActivityRecord self = (ActivityRecord)mMainStack.mHistory.get(index); final long origId = Binder.clearCallingIdentity(); int i; - for (i=mHistory.size()-1; i>=0; i--) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); + for (i=mMainStack.mHistory.size()-1; i>=0; i--) { + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); if (r.resultTo == self && r.requestCode == requestCode) { if ((r.resultWho == null && resultWho == null) || (r.resultWho != null && r.resultWho.equals(resultWho))) { - finishActivityLocked(r, i, + mMainStack.finishActivityLocked(r, i, Activity.RESULT_CANCELED, null, "request-sub"); } } @@ -4373,8 +2491,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public boolean willActivityBeVisible(IBinder token) { synchronized(this) { int i; - for (i=mHistory.size()-1; i>=0; i--) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); + for (i=mMainStack.mHistory.size()-1; i>=0; i--) { + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); if (r == token) { return true; } @@ -4389,11 +2507,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public void overridePendingTransition(IBinder token, String packageName, int enterAnim, int exitAnim) { synchronized(this) { - int index = indexOfTokenLocked(token); + int index = mMainStack.indexOfTokenLocked(token); if (index < 0) { return; } - HistoryRecord self = (HistoryRecord)mHistory.get(index); + ActivityRecord self = (ActivityRecord)mMainStack.mHistory.get(index); final long origId = Binder.clearCallingIdentity(); @@ -4408,188 +2526,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } /** - * Perform clean-up of service connections in an activity record. - */ - private final void cleanUpActivityServicesLocked(HistoryRecord r) { - // Throw away any services that have been bound by this activity. - if (r.connections != null) { - Iterator<ConnectionRecord> it = r.connections.iterator(); - while (it.hasNext()) { - ConnectionRecord c = it.next(); - removeConnectionLocked(c, null, r); - } - r.connections = null; - } - } - - /** - * Perform the common clean-up of an activity record. This is called both - * as part of destroyActivityLocked() (when destroying the client-side - * representation) and cleaning things up as a result of its hosting - * processing going away, in which case there is no remaining client-side - * state to destroy so only the cleanup here is needed. - */ - private final void cleanUpActivityLocked(HistoryRecord r, boolean cleanServices) { - if (mResumedActivity == r) { - mResumedActivity = null; - } - if (mFocusedActivity == r) { - mFocusedActivity = null; - } - - r.configDestroy = false; - r.frozenBeforeDestroy = false; - - // Make sure this record is no longer in the pending finishes list. - // This could happen, for example, if we are trimming activities - // down to the max limit while they are still waiting to finish. - mFinishingActivities.remove(r); - mWaitingVisibleActivities.remove(r); - - // Remove any pending results. - if (r.finishing && r.pendingResults != null) { - for (WeakReference<PendingIntentRecord> apr : r.pendingResults) { - PendingIntentRecord rec = apr.get(); - if (rec != null) { - cancelIntentSenderLocked(rec, false); - } - } - r.pendingResults = null; - } - - if (cleanServices) { - cleanUpActivityServicesLocked(r); - } - - if (mPendingThumbnails.size() > 0) { - // There are clients waiting to receive thumbnails so, in case - // this is an activity that someone is waiting for, add it - // to the pending list so we can correctly update the clients. - mCancelledThumbnails.add(r); - } - - // Get rid of any pending idle timeouts. - mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); - mHandler.removeMessages(IDLE_TIMEOUT_MSG, r); - } - - private final void removeActivityFromHistoryLocked(HistoryRecord r) { - if (r.state != ActivityState.DESTROYED) { - mHistory.remove(r); - r.inHistory = false; - r.state = ActivityState.DESTROYED; - mWindowManager.removeAppToken(r); - if (VALIDATE_TOKENS) { - mWindowManager.validateAppTokens(mHistory); - } - cleanUpActivityServicesLocked(r); - removeActivityUriPermissionsLocked(r); - } - } - - /** - * Destroy the current CLIENT SIDE instance of an activity. This may be - * called both when actually finishing an activity, or when performing - * a configuration switch where we destroy the current client-side object - * but then create a new client-side object for this same HistoryRecord. - */ - private final boolean destroyActivityLocked(HistoryRecord r, - boolean removeFromApp) { - if (DEBUG_SWITCH) Slog.v( - TAG, "Removing activity: token=" + r - + ", app=" + (r.app != null ? r.app.processName : "(null)")); - EventLog.writeEvent(EventLogTags.AM_DESTROY_ACTIVITY, - System.identityHashCode(r), - r.task.taskId, r.shortComponentName); - - boolean removedFromHistory = false; - - cleanUpActivityLocked(r, false); - - final boolean hadApp = r.app != null; - - if (hadApp) { - if (removeFromApp) { - int idx = r.app.activities.indexOf(r); - if (idx >= 0) { - r.app.activities.remove(idx); - } - if (r.persistent) { - decPersistentCountLocked(r.app); - } - if (r.app.activities.size() == 0) { - // No longer have activities, so update location in - // LRU list. - updateLruProcessLocked(r.app, true, false); - } - } - - boolean skipDestroy = false; - - try { - if (DEBUG_SWITCH) Slog.i(TAG, "Destroying: " + r); - r.app.thread.scheduleDestroyActivity(r, r.finishing, - r.configChangeFlags); - } catch (Exception e) { - // We can just ignore exceptions here... if the process - // has crashed, our death notification will clean things - // up. - //Slog.w(TAG, "Exception thrown during finish", e); - if (r.finishing) { - removeActivityFromHistoryLocked(r); - removedFromHistory = true; - skipDestroy = true; - } - } - - r.app = null; - r.nowVisible = false; - - if (r.finishing && !skipDestroy) { - r.state = ActivityState.DESTROYING; - Message msg = mHandler.obtainMessage(DESTROY_TIMEOUT_MSG); - msg.obj = r; - mHandler.sendMessageDelayed(msg, DESTROY_TIMEOUT); - } else { - r.state = ActivityState.DESTROYED; - } - } else { - // remove this record from the history. - if (r.finishing) { - removeActivityFromHistoryLocked(r); - removedFromHistory = true; - } else { - r.state = ActivityState.DESTROYED; - } - } - - r.configChangeFlags = 0; - - if (!mLRUActivities.remove(r) && hadApp) { - Slog.w(TAG, "Activity " + r + " being finished, but not in LRU list"); - } - - return removedFromHistory; - } - - private static void removeHistoryRecordsForAppLocked(ArrayList list, ProcessRecord app) { - int i = list.size(); - if (localLOGV) Slog.v( - TAG, "Removing app " + app + " from list " + list - + " with " + i + " entries"); - while (i > 0) { - i--; - HistoryRecord r = (HistoryRecord)list.get(i); - if (localLOGV) Slog.v( - TAG, "Record #" + i + " " + r + ": app=" + r.app); - if (r.app == app) { - if (localLOGV) Slog.v(TAG, "Removing this entry!"); - list.remove(i); - } - } - } - - /** * Main function for removing an existing process from the activity manager * as a result of that process going away. Clears out all connections * to the process. @@ -4602,30 +2538,27 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } // Just in case... - if (mPausingActivity != null && mPausingActivity.app == app) { - if (DEBUG_PAUSE) Slog.v(TAG, "App died while pausing: " + mPausingActivity); - mPausingActivity = null; + if (mMainStack.mPausingActivity != null && mMainStack.mPausingActivity.app == app) { + if (DEBUG_PAUSE) Slog.v(TAG, "App died while pausing: " +mMainStack.mPausingActivity); + mMainStack.mPausingActivity = null; } - if (mLastPausedActivity != null && mLastPausedActivity.app == app) { - mLastPausedActivity = null; + if (mMainStack.mLastPausedActivity != null && mMainStack.mLastPausedActivity.app == app) { + mMainStack.mLastPausedActivity = null; } // Remove this application's activities from active lists. - removeHistoryRecordsForAppLocked(mLRUActivities, app); - removeHistoryRecordsForAppLocked(mStoppingActivities, app); - removeHistoryRecordsForAppLocked(mWaitingVisibleActivities, app); - removeHistoryRecordsForAppLocked(mFinishingActivities, app); + mMainStack.removeHistoryRecordsForAppLocked(app); boolean atTop = true; boolean hasVisibleActivities = false; // Clean out the history list. - int i = mHistory.size(); + int i = mMainStack.mHistory.size(); if (localLOGV) Slog.v( TAG, "Removing app " + app + " from history with " + i + " entries"); while (i > 0) { i--; - HistoryRecord r = (HistoryRecord)mHistory.get(i); + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); if (localLOGV) Slog.v( TAG, "Record #" + i + " " + r + ": app=" + r.app); if (r.app == app) { @@ -4633,14 +2566,14 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (localLOGV) Slog.v( TAG, "Removing this entry! frozen=" + r.haveState + " finishing=" + r.finishing); - mHistory.remove(i); + mMainStack.mHistory.remove(i); r.inHistory = false; mWindowManager.removeAppToken(r); if (VALIDATE_TOKENS) { - mWindowManager.validateAppTokens(mHistory); + mWindowManager.validateAppTokens(mMainStack.mHistory); } - removeActivityUriPermissionsLocked(r); + r.removeUriPermissionsLocked(); } else { // We have the current state for this activity, so @@ -4657,7 +2590,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - cleanUpActivityLocked(r, true); + r.stack.cleanUpActivityLocked(r, true); r.state = ActivityState.STOPPED; } atTop = false; @@ -4674,14 +2607,14 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } if (!restarting) { - if (!resumeTopActivityLocked(null)) { + if (!mMainStack.resumeTopActivityLocked(null)) { // If there was nothing to resume, and we are not already // restarting this process, but there is a visible activity that // is hosted by the process... then make sure all visible // activities are running, taking care of restarting this // process. if (hasVisibleActivities) { - ensureActivitiesVisibleLocked(null, 0); + mMainStack.ensureActivitiesVisibleLocked(null, 0); } } } @@ -4700,7 +2633,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return -1; } - private final ProcessRecord getRecordForAppLocked( + final ProcessRecord getRecordForAppLocked( IApplicationThread thread) { if (thread == null) { return null; @@ -4710,11 +2643,16 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return appIndex >= 0 ? mLruProcesses.get(appIndex) : null; } - private final void appDiedLocked(ProcessRecord app, int pid, + final void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread) { mProcDeaths[0]++; + BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); + synchronized (stats) { + stats.noteProcessDiedLocked(app.info.uid, pid); + } + // Clean up already done if the process has been re-started. if (app.pid == pid && app.thread != null && app.thread.asBinder() == thread.asBinder()) { @@ -4752,8 +2690,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen (rec.lastLowMemory+GC_MIN_INTERVAL) <= now) { // The low memory report is overriding any current // state for a GC request. Make sure to do - // visible/foreground processes first. - if (rec.setAdj <= VISIBLE_APP_ADJ) { + // heavy/important/visible/foreground processes first. + if (rec.setAdj <= HEAVY_WEIGHT_APP_ADJ) { rec.lastRequestedGc = 0; } else { rec.lastRequestedGc = rec.lastLowMemory; @@ -4783,10 +2721,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen * @param clearTraces causes the dump file to be erased prior to the new * traces being written, if true; when false, the new traces will be * appended to any existing file content. - * @param pids of dalvik VM processes to dump stack traces for + * @param firstPids of dalvik VM processes to dump stack traces for first + * @param lastPids of dalvik VM processes to dump stack traces for last * @return file containing stack traces, or null if no dump file is configured */ - public static File dumpStackTraces(boolean clearTraces, ArrayList<Integer> pids) { + public static File dumpStackTraces(boolean clearTraces, ArrayList<Integer> firstPids, + ProcessStats processStats, SparseArray<Boolean> lastPids) { String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null); if (tracesPath == null || tracesPath.length() == 0) { return null; @@ -4814,25 +2754,94 @@ public final class ActivityManagerService extends ActivityManagerNative implemen try { observer.startWatching(); - int num = pids.size(); - for (int i = 0; i < num; i++) { - synchronized (observer) { - Process.sendSignal(pids.get(i), Process.SIGNAL_QUIT); - observer.wait(200); // Wait for write-close, give up after 200msec + + // First collect all of the stacks of the most important pids. + try { + int num = firstPids.size(); + for (int i = 0; i < num; i++) { + synchronized (observer) { + Process.sendSignal(firstPids.get(i), Process.SIGNAL_QUIT); + observer.wait(200); // Wait for write-close, give up after 200msec + } } + } catch (InterruptedException e) { + Log.wtf(TAG, e); } - } catch (InterruptedException e) { - Log.wtf(TAG, e); + + // Next measure CPU usage. + if (processStats != null) { + processStats.init(); + System.gc(); + processStats.update(); + try { + synchronized (processStats) { + processStats.wait(500); // measure over 1/2 second. + } + } catch (InterruptedException e) { + } + processStats.update(); + + // We'll take the stack crawls of just the top apps using CPU. + final int N = processStats.countWorkingStats(); + int numProcs = 0; + for (int i=0; i<N && numProcs<5; i++) { + ProcessStats.Stats stats = processStats.getWorkingStats(i); + if (lastPids.indexOfKey(stats.pid) >= 0) { + numProcs++; + try { + synchronized (observer) { + Process.sendSignal(stats.pid, Process.SIGNAL_QUIT); + observer.wait(200); // Wait for write-close, give up after 200msec + } + } catch (InterruptedException e) { + Log.wtf(TAG, e); + } + + } + } + } + + return tracesFile; + } finally { observer.stopWatching(); } + } + + private final class AppNotResponding implements Runnable { + private final ProcessRecord mApp; + private final String mAnnotation; + + public AppNotResponding(ProcessRecord app, String annotation) { + mApp = app; + mAnnotation = annotation; + } - return tracesFile; + @Override + public void run() { + appNotResponding(mApp, null, null, mAnnotation); + } } - final void appNotResponding(ProcessRecord app, HistoryRecord activity, - HistoryRecord parent, final String annotation) { - ArrayList<Integer> pids = new ArrayList<Integer>(20); + final void appNotResponding(ProcessRecord app, ActivityRecord activity, + ActivityRecord parent, final String annotation) { + ArrayList<Integer> firstPids = new ArrayList<Integer>(5); + SparseArray<Boolean> lastPids = new SparseArray<Boolean>(20); + + if (mController != null) { + try { + // 0 == continue, -1 = kill process immediately + int res = mController.appEarlyNotResponding(app.processName, app.pid, annotation); + if (res < 0 && app.pid != MY_PID) Process.killProcess(app.pid); + } catch (RemoteException e) { + mController = null; + } + } + + long anrTime = SystemClock.uptimeMillis(); + if (MONITOR_CPU_USAGE) { + updateCpuStatsNow(); + } synchronized (this) { // PowerManager.reboot() can block for a long time, so ignore ANRs while shutting down. @@ -4856,25 +2865,29 @@ public final class ActivityManagerService extends ActivityManagerNative implemen annotation); // Dump thread traces as quickly as we can, starting with "interesting" processes. - pids.add(app.pid); + firstPids.add(app.pid); int parentPid = app.pid; if (parent != null && parent.app != null && parent.app.pid > 0) parentPid = parent.app.pid; - if (parentPid != app.pid) pids.add(parentPid); + if (parentPid != app.pid) firstPids.add(parentPid); - if (MY_PID != app.pid && MY_PID != parentPid) pids.add(MY_PID); + if (MY_PID != app.pid && MY_PID != parentPid) firstPids.add(MY_PID); for (int i = mLruProcesses.size() - 1; i >= 0; i--) { ProcessRecord r = mLruProcesses.get(i); if (r != null && r.thread != null) { int pid = r.pid; - if (pid > 0 && pid != app.pid && pid != parentPid && pid != MY_PID) pids.add(pid); + if (pid > 0 && pid != app.pid && pid != parentPid && pid != MY_PID) { + if (r.persistent) { + firstPids.add(pid); + } else { + lastPids.put(pid, Boolean.TRUE); + } + } } } } - File tracesFile = dumpStackTraces(true, pids); - // Log the ANR to the main log. StringBuilder info = mStringBuilder; info.setLength(0); @@ -4890,15 +2903,22 @@ public final class ActivityManagerService extends ActivityManagerNative implemen info.append("Parent: ").append(parent.shortComponentName).append("\n"); } + final ProcessStats processStats = new ProcessStats(true); + + File tracesFile = dumpStackTraces(true, firstPids, processStats, lastPids); + String cpuInfo = null; if (MONITOR_CPU_USAGE) { updateCpuStatsNow(); synchronized (mProcessStatsThread) { - cpuInfo = mProcessStats.printCurrentState(); + cpuInfo = mProcessStats.printCurrentState(anrTime); } + info.append(processStats.printCurrentLoad()); info.append(cpuInfo); } + info.append(processStats.printCurrentState(anrTime)); + Slog.e(TAG, info.toString()); if (tracesFile == null) { // There is no trace file, so dump (only) the alleged culprit's threads to the log @@ -4950,82 +2970,27 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private final void decPersistentCountLocked(ProcessRecord app) - { - app.persistentActivities--; - if (app.persistentActivities > 0) { - // Still more of 'em... - return; - } - if (app.persistent) { - // Ah, but the application itself is persistent. Whatever! - return; - } - - // App is no longer persistent... make sure it and the ones - // following it in the LRU list have the correc oom_adj. - updateOomAdjLocked(); - } - - public void setPersistent(IBinder token, boolean isPersistent) { - if (checkCallingPermission(android.Manifest.permission.PERSISTENT_ACTIVITY) - != PackageManager.PERMISSION_GRANTED) { - String msg = "Permission Denial: setPersistent() from pid=" - + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid() - + " requires " + android.Manifest.permission.PERSISTENT_ACTIVITY; - Slog.w(TAG, msg); - throw new SecurityException(msg); - } - - synchronized(this) { - int index = indexOfTokenLocked(token); - if (index < 0) { - return; - } - HistoryRecord r = (HistoryRecord)mHistory.get(index); - ProcessRecord app = r.app; - - if (localLOGV) Slog.v( - TAG, "Setting persistence " + isPersistent + ": " + r); - - if (isPersistent) { - if (r.persistent) { - // Okay okay, I heard you already! - if (localLOGV) Slog.v(TAG, "Already persistent!"); - return; - } - r.persistent = true; - app.persistentActivities++; - if (localLOGV) Slog.v(TAG, "Num persistent now: " + app.persistentActivities); - if (app.persistentActivities > 1) { - // We aren't the first... - if (localLOGV) Slog.v(TAG, "Not the first!"); - return; - } - if (app.persistent) { - // This would be redundant. - if (localLOGV) Slog.v(TAG, "App is persistent!"); - return; - } - - // App is now persistent... make sure it and the ones - // following it now have the correct oom_adj. - final long origId = Binder.clearCallingIdentity(); - updateOomAdjLocked(); - Binder.restoreCallingIdentity(origId); - - } else { - if (!r.persistent) { - // Okay okay, I heard you already! - return; + final void showLaunchWarningLocked(final ActivityRecord cur, final ActivityRecord next) { + if (!mLaunchWarningShown) { + mLaunchWarningShown = true; + mHandler.post(new Runnable() { + @Override + public void run() { + synchronized (ActivityManagerService.this) { + final Dialog d = new LaunchWarningWindow(mContext, cur, next); + d.show(); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + synchronized (ActivityManagerService.this) { + d.dismiss(); + mLaunchWarningShown = false; + } + } + }, 4000); + } } - r.persistent = false; - final long origId = Binder.clearCallingIdentity(); - decPersistentCountLocked(app); - Binder.restoreCallingIdentity(origId); - - } + }); } } @@ -5035,7 +3000,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen int pid = Binder.getCallingPid(); long callingId = Binder.clearCallingIdentity(); try { - IPackageManager pm = ActivityThread.getPackageManager(); + IPackageManager pm = AppGlobals.getPackageManager(); int pkgUid = -1; synchronized(this) { try { @@ -5064,11 +3029,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED, Uri.fromParts("package", packageName, null)); intent.putExtra(Intent.EXTRA_UID, pkgUid); - synchronized (this) { - broadcastIntentLocked(null, null, intent, - null, null, 0, null, null, null, - false, false, MY_PID, Process.SYSTEM_UID); - } + broadcastIntentInPackage("android", Process.SYSTEM_UID, intent, + null, null, 0, null, null, null, false, false); } catch (RemoteException e) { } } finally { @@ -5092,7 +3054,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen long callingId = Binder.clearCallingIdentity(); try { - IPackageManager pm = ActivityThread.getPackageManager(); + IPackageManager pm = AppGlobals.getPackageManager(); int pkgUid = -1; synchronized(this) { try { @@ -5124,7 +3086,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen long callingId = Binder.clearCallingIdentity(); try { - IPackageManager pm = ActivityThread.getPackageManager(); + IPackageManager pm = AppGlobals.getPackageManager(); int pkgUid = -1; synchronized(this) { try { @@ -5172,6 +3134,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public void closeSystemDialogs(String reason) { Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); if (reason != null) { intent.putExtra("reason", reason); } @@ -5194,10 +3157,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mWindowManager.closeSystemDialogs(reason); - for (i=mHistory.size()-1; i>=0; i--) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); + for (i=mMainStack.mHistory.size()-1; i>=0; i--) { + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); if ((r.info.flags&ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS) != 0) { - finishActivityLocked(r, i, + r.stack.finishActivityLocked(r, i, Activity.RESULT_CANCELED, null, "close-sys"); } } @@ -5249,6 +3212,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen forceStopPackageLocked(packageName, uid, false, false, true); Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED, Uri.fromParts("package", packageName, null)); + if (!mProcessesReady) { + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + } intent.putExtra(Intent.EXTRA_UID, uid); broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, @@ -5298,7 +3264,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (uid < 0) { try { - uid = ActivityThread.getPackageManager().getPackageUid(name); + uid = AppGlobals.getPackageManager().getPackageUid(name); } catch (RemoteException e) { } } @@ -5318,8 +3284,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen boolean didSomething = killPackageProcessesLocked(name, uid, -100, callerWillRestart, doit); - for (i=mHistory.size()-1; i>=0; i--) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); + for (i=mMainStack.mHistory.size()-1; i>=0; i--) { + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); if (r.packageName.equals(name)) { if (!doit) { return true; @@ -5330,7 +3296,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen r.app.removed = true; } r.app = null; - finishActivityLocked(r, i, Activity.RESULT_CANCELED, null, "uninstall"); + r.stack.finishActivityLocked(r, i, Activity.RESULT_CANCELED, null, "uninstall"); } } @@ -5362,7 +3328,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ac.removePackage(name); } } - resumeTopActivityLocked(null); + mMainStack.resumeTopActivityLocked(null); } return didSomething; @@ -5376,6 +3342,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen + "/" + uid + ")"); mProcessNames.remove(name, uid); + if (mHeavyWeightProcess == app) { + mHeavyWeightProcess = null; + mHandler.sendEmptyMessage(CANCEL_HEAVY_NOTIFICATION_MSG); + } boolean needRestart = false; if (app.pid > 0 && app.pid != MY_PID) { int pid = app.pid; @@ -5417,6 +3387,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen EventLog.writeEvent(EventLogTags.AM_PROCESS_START_TIMEOUT, pid, app.info.uid, app.processName); mProcessNames.remove(app.processName, app.info.uid); + if (mHeavyWeightProcess == app) { + mHeavyWeightProcess = null; + mHandler.sendEmptyMessage(CANCEL_HEAVY_NOTIFICATION_MSG); + } // Take care of any launching providers waiting for this process. checkAppInLaunchingProvidersLocked(app, true); // Take care of any services that are waiting for the process. @@ -5443,6 +3417,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } if (mPendingBroadcast != null && mPendingBroadcast.curApp.pid == pid) { Slog.w(TAG, "Unattached app died before broadcast acknowledged, skipping"); + mPendingBroadcast.state = BroadcastRecord.IDLE; + mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex; mPendingBroadcast = null; scheduleBroadcastsLocked(); } @@ -5518,7 +3494,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); - boolean normalMode = mSystemReady || isAllowedWhileBooting(app.info); + boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info); List providers = normalMode ? generateApplicationProvidersLocked(app) : null; if (!normalMode) { @@ -5577,18 +3553,20 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Remove this record from the list of starting applications. mPersistentStartingProcesses.remove(app); + if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG, + "Attach application locked removing on hold: " + app); mProcessesOnHold.remove(app); boolean badApp = false; boolean didSomething = false; // See if the top visible activity is waiting to run in this process... - HistoryRecord hr = topRunningActivityLocked(null); + ActivityRecord hr = mMainStack.topRunningActivityLocked(null); if (hr != null && normalMode) { if (hr.app == null && app.info.uid == hr.info.applicationInfo.uid && processName.equals(hr.processName)) { try { - if (realStartActivityLocked(hr, app, true, true)) { + if (mMainStack.realStartActivityLocked(hr, app, true, true)) { didSomething = true; } } catch (Exception e) { @@ -5597,7 +3575,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen badApp = true; } } else { - ensureActivitiesVisibleLocked(hr, null, processName, 0); + mMainStack.ensureActivitiesVisibleLocked(hr, null, processName, 0); } } @@ -5635,7 +3613,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen Slog.w(TAG, "Exception in new application when starting receiver " + br.curComponent.flattenToShortString(), e); badApp = true; - logBroadcastReceiverDiscard(br); + logBroadcastReceiverDiscardLocked(br); finishReceiverLocked(br.receiver, br.resultCode, br.resultData, br.resultExtras, br.resultAbort, true); scheduleBroadcastsLocked(); @@ -5681,195 +3659,16 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public final void activityIdle(IBinder token, Configuration config) { final long origId = Binder.clearCallingIdentity(); - activityIdleInternal(token, false, config); + mMainStack.activityIdleInternal(token, false, config); Binder.restoreCallingIdentity(origId); } - final ArrayList<HistoryRecord> processStoppingActivitiesLocked( - boolean remove) { - int N = mStoppingActivities.size(); - if (N <= 0) return null; - - ArrayList<HistoryRecord> stops = null; - - final boolean nowVisible = mResumedActivity != null - && mResumedActivity.nowVisible - && !mResumedActivity.waitingVisible; - for (int i=0; i<N; i++) { - HistoryRecord s = mStoppingActivities.get(i); - if (localLOGV) Slog.v(TAG, "Stopping " + s + ": nowVisible=" - + nowVisible + " waitingVisible=" + s.waitingVisible - + " finishing=" + s.finishing); - if (s.waitingVisible && nowVisible) { - mWaitingVisibleActivities.remove(s); - s.waitingVisible = false; - if (s.finishing) { - // If this activity is finishing, it is sitting on top of - // everyone else but we now know it is no longer needed... - // so get rid of it. Otherwise, we need to go through the - // normal flow and hide it once we determine that it is - // hidden by the activities in front of it. - if (localLOGV) Slog.v(TAG, "Before stopping, can hide: " + s); - mWindowManager.setAppVisibility(s, false); - } - } - if (!s.waitingVisible && remove) { - if (localLOGV) Slog.v(TAG, "Ready to stop: " + s); - if (stops == null) { - stops = new ArrayList<HistoryRecord>(); - } - stops.add(s); - mStoppingActivities.remove(i); - N--; - i--; - } - } - - return stops; - } - void enableScreenAfterBoot() { EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_ENABLE_SCREEN, SystemClock.uptimeMillis()); mWindowManager.enableScreenAfterBoot(); } - final void activityIdleInternal(IBinder token, boolean fromTimeout, - Configuration config) { - if (localLOGV) Slog.v(TAG, "Activity idle: " + token); - - ArrayList<HistoryRecord> stops = null; - ArrayList<HistoryRecord> finishes = null; - ArrayList<HistoryRecord> thumbnails = null; - int NS = 0; - int NF = 0; - int NT = 0; - IApplicationThread sendThumbnail = null; - boolean booting = false; - boolean enableScreen = false; - - synchronized (this) { - if (token != null) { - mHandler.removeMessages(IDLE_TIMEOUT_MSG, token); - } - - // Get the activity record. - int index = indexOfTokenLocked(token); - if (index >= 0) { - HistoryRecord r = (HistoryRecord)mHistory.get(index); - - if (fromTimeout) { - reportActivityLaunchedLocked(fromTimeout, r, -1, -1); - } - - // This is a hack to semi-deal with a race condition - // in the client where it can be constructed with a - // newer configuration from when we asked it to launch. - // We'll update with whatever configuration it now says - // it used to launch. - if (config != null) { - r.configuration = config; - } - - // No longer need to keep the device awake. - if (mResumedActivity == r && mLaunchingActivity.isHeld()) { - mHandler.removeMessages(LAUNCH_TIMEOUT_MSG); - mLaunchingActivity.release(); - } - - // We are now idle. If someone is waiting for a thumbnail from - // us, we can now deliver. - r.idle = true; - scheduleAppGcsLocked(); - if (r.thumbnailNeeded && r.app != null && r.app.thread != null) { - sendThumbnail = r.app.thread; - r.thumbnailNeeded = false; - } - - // If this activity is fullscreen, set up to hide those under it. - - if (DEBUG_VISBILITY) Slog.v(TAG, "Idle activity for " + r); - ensureActivitiesVisibleLocked(null, 0); - - //Slog.i(TAG, "IDLE: mBooted=" + mBooted + ", fromTimeout=" + fromTimeout); - if (!mBooted && !fromTimeout) { - mBooted = true; - enableScreen = true; - } - - } else if (fromTimeout) { - reportActivityLaunchedLocked(fromTimeout, null, -1, -1); - } - - // Atomically retrieve all of the other things to do. - stops = processStoppingActivitiesLocked(true); - NS = stops != null ? stops.size() : 0; - if ((NF=mFinishingActivities.size()) > 0) { - finishes = new ArrayList<HistoryRecord>(mFinishingActivities); - mFinishingActivities.clear(); - } - if ((NT=mCancelledThumbnails.size()) > 0) { - thumbnails = new ArrayList<HistoryRecord>(mCancelledThumbnails); - mCancelledThumbnails.clear(); - } - - booting = mBooting; - mBooting = false; - } - - int i; - - // Send thumbnail if requested. - if (sendThumbnail != null) { - try { - sendThumbnail.requestThumbnail(token); - } catch (Exception e) { - Slog.w(TAG, "Exception thrown when requesting thumbnail", e); - sendPendingThumbnail(null, token, null, null, true); - } - } - - // Stop any activities that are scheduled to do so but have been - // waiting for the next one to start. - for (i=0; i<NS; i++) { - HistoryRecord r = (HistoryRecord)stops.get(i); - synchronized (this) { - if (r.finishing) { - finishCurrentActivityLocked(r, FINISH_IMMEDIATELY); - } else { - stopActivityLocked(r); - } - } - } - - // Finish any activities that are scheduled to do so but have been - // waiting for the next one to start. - for (i=0; i<NF; i++) { - HistoryRecord r = (HistoryRecord)finishes.get(i); - synchronized (this) { - destroyActivityLocked(r, true); - } - } - - // Report back to any thumbnail receivers. - for (i=0; i<NT; i++) { - HistoryRecord r = (HistoryRecord)thumbnails.get(i); - sendPendingThumbnail(r, null, null, null, true); - } - - if (booting) { - finishBooting(); - } - - trimApplications(); - //dump(); - //mWindowManager.dump(); - - if (enableScreen) { - enableScreenAfterBoot(); - } - } - final void finishBooting() { IntentFilter pkgFilter = new IntentFilter(); pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART); @@ -5899,12 +3698,18 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>(mProcessesOnHold); for (int ip=0; ip<NP; ip++) { - this.startProcessLocked(procs.get(ip), "on-hold", null); + if (DEBUG_PROCESSES) Slog.v(TAG, "Starting process on hold: " + + procs.get(ip)); + startProcessLocked(procs.get(ip), "on-hold", null); } } if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { + // Start looking for apps that are abusing wake locks. + Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG); + mHandler.sendMessageDelayed(nmsg, POWER_CHECK_DELAY); // Tell anyone interested that we are done booting! + SystemProperties.set("sys.boot_completed", "1"); broadcastIntentLocked(null, null, new Intent(Intent.ACTION_BOOT_COMPLETED, null), null, null, 0, null, null, @@ -5940,60 +3745,31 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } final long origId = Binder.clearCallingIdentity(); - activityPaused(token, icicle, false); + mMainStack.activityPaused(token, icicle, false); Binder.restoreCallingIdentity(origId); } - final void activityPaused(IBinder token, Bundle icicle, boolean timeout) { - if (DEBUG_PAUSE) Slog.v( - TAG, "Activity paused: token=" + token + ", icicle=" + icicle - + ", timeout=" + timeout); - - HistoryRecord r = null; - - synchronized (this) { - int index = indexOfTokenLocked(token); - if (index >= 0) { - r = (HistoryRecord)mHistory.get(index); - if (!timeout) { - r.icicle = icicle; - r.haveState = true; - } - mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); - if (mPausingActivity == r) { - r.state = ActivityState.PAUSED; - completePauseLocked(); - } else { - EventLog.writeEvent(EventLogTags.AM_FAILED_TO_PAUSE, - System.identityHashCode(r), r.shortComponentName, - mPausingActivity != null - ? mPausingActivity.shortComponentName : "(none)"); - } - } - } - } - public final void activityStopped(IBinder token, Bitmap thumbnail, CharSequence description) { if (localLOGV) Slog.v( TAG, "Activity stopped: token=" + token); - HistoryRecord r = null; + ActivityRecord r = null; final long origId = Binder.clearCallingIdentity(); synchronized (this) { - int index = indexOfTokenLocked(token); + int index = mMainStack.indexOfTokenLocked(token); if (index >= 0) { - r = (HistoryRecord)mHistory.get(index); + r = (ActivityRecord)mMainStack.mHistory.get(index); r.thumbnail = thumbnail; r.description = description; r.stopped = true; r.state = ActivityState.STOPPED; if (!r.finishing) { if (r.configDestroy) { - destroyActivityLocked(r, true); - resumeTopActivityLocked(null); + r.stack.destroyActivityLocked(r, true); + r.stack.resumeTopActivityLocked(null); } } } @@ -6010,39 +3786,27 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public final void activityDestroyed(IBinder token) { if (DEBUG_SWITCH) Slog.v(TAG, "ACTIVITY DESTROYED: " + token); - synchronized (this) { - mHandler.removeMessages(DESTROY_TIMEOUT_MSG, token); - - int index = indexOfTokenLocked(token); - if (index >= 0) { - HistoryRecord r = (HistoryRecord)mHistory.get(index); - if (r.state == ActivityState.DESTROYING) { - final long origId = Binder.clearCallingIdentity(); - removeActivityFromHistoryLocked(r); - Binder.restoreCallingIdentity(origId); - } - } - } + mMainStack.activityDestroyed(token); } public String getCallingPackage(IBinder token) { synchronized (this) { - HistoryRecord r = getCallingRecordLocked(token); + ActivityRecord r = getCallingRecordLocked(token); return r != null && r.app != null ? r.info.packageName : null; } } public ComponentName getCallingActivity(IBinder token) { synchronized (this) { - HistoryRecord r = getCallingRecordLocked(token); + ActivityRecord r = getCallingRecordLocked(token); return r != null ? r.intent.getComponent() : null; } } - private HistoryRecord getCallingRecordLocked(IBinder token) { - int index = indexOfTokenLocked(token); + private ActivityRecord getCallingRecordLocked(IBinder token) { + int index = mMainStack.indexOfTokenLocked(token); if (index >= 0) { - HistoryRecord r = (HistoryRecord)mHistory.get(index); + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index); if (r != null) { return r.resultTo; } @@ -6052,9 +3816,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public ComponentName getActivityClassForToken(IBinder token) { synchronized(this) { - int index = indexOfTokenLocked(token); + int index = mMainStack.indexOfTokenLocked(token); if (index >= 0) { - HistoryRecord r = (HistoryRecord)mHistory.get(index); + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index); return r.intent.getComponent(); } return null; @@ -6063,9 +3827,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public String getPackageForToken(IBinder token) { synchronized(this) { - int index = indexOfTokenLocked(token); + int index = mMainStack.indexOfTokenLocked(token); if (index >= 0) { - HistoryRecord r = (HistoryRecord)mHistory.get(index); + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index); return r.packageName; } return null; @@ -6092,7 +3856,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen try { if (callingUid != 0 && callingUid != Process.SYSTEM_UID && Process.supportsProcesses()) { - int uid = ActivityThread.getPackageManager() + int uid = AppGlobals.getPackageManager() .getPackageUid(packageName); if (uid != Binder.getCallingUid()) { String msg = "Permission Denial: getIntentSender() from pid=" @@ -6104,57 +3868,66 @@ public final class ActivityManagerService extends ActivityManagerNative implemen throw new SecurityException(msg); } } + + return getIntentSenderLocked(type, packageName, callingUid, + token, resultWho, requestCode, intent, resolvedType, flags); + } catch (RemoteException e) { throw new SecurityException(e); } - HistoryRecord activity = null; - if (type == INTENT_SENDER_ACTIVITY_RESULT) { - int index = indexOfTokenLocked(token); - if (index < 0) { - return null; - } - activity = (HistoryRecord)mHistory.get(index); - if (activity.finishing) { - return null; - } + } + } + + IIntentSender getIntentSenderLocked(int type, + String packageName, int callingUid, IBinder token, String resultWho, + int requestCode, Intent intent, String resolvedType, int flags) { + ActivityRecord activity = null; + if (type == INTENT_SENDER_ACTIVITY_RESULT) { + int index = mMainStack.indexOfTokenLocked(token); + if (index < 0) { + return null; + } + activity = (ActivityRecord)mMainStack.mHistory.get(index); + if (activity.finishing) { + return null; } + } - final boolean noCreate = (flags&PendingIntent.FLAG_NO_CREATE) != 0; - final boolean cancelCurrent = (flags&PendingIntent.FLAG_CANCEL_CURRENT) != 0; - final boolean updateCurrent = (flags&PendingIntent.FLAG_UPDATE_CURRENT) != 0; - flags &= ~(PendingIntent.FLAG_NO_CREATE|PendingIntent.FLAG_CANCEL_CURRENT - |PendingIntent.FLAG_UPDATE_CURRENT); + final boolean noCreate = (flags&PendingIntent.FLAG_NO_CREATE) != 0; + final boolean cancelCurrent = (flags&PendingIntent.FLAG_CANCEL_CURRENT) != 0; + final boolean updateCurrent = (flags&PendingIntent.FLAG_UPDATE_CURRENT) != 0; + flags &= ~(PendingIntent.FLAG_NO_CREATE|PendingIntent.FLAG_CANCEL_CURRENT + |PendingIntent.FLAG_UPDATE_CURRENT); - PendingIntentRecord.Key key = new PendingIntentRecord.Key( - type, packageName, activity, resultWho, - requestCode, intent, resolvedType, 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); - } - return rec; + PendingIntentRecord.Key key = new PendingIntentRecord.Key( + type, packageName, activity, resultWho, + requestCode, intent, resolvedType, 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); } - rec.canceled = true; - mIntentSenderRecords.remove(key); - } - if (noCreate) { return rec; } - rec = new PendingIntentRecord(this, key, callingUid); - mIntentSenderRecords.put(key, rec.ref); - if (type == INTENT_SENDER_ACTIVITY_RESULT) { - if (activity.pendingResults == null) { - activity.pendingResults - = new HashSet<WeakReference<PendingIntentRecord>>(); - } - activity.pendingResults.add(rec.ref); - } + rec.canceled = true; + mIntentSenderRecords.remove(key); + } + if (noCreate) { return rec; } + rec = new PendingIntentRecord(this, key, callingUid); + mIntentSenderRecords.put(key, rec.ref); + if (type == INTENT_SENDER_ACTIVITY_RESULT) { + if (activity.pendingResults == null) { + activity.pendingResults + = new HashSet<WeakReference<PendingIntentRecord>>(); + } + activity.pendingResults.add(rec.ref); + } + return rec; } public void cancelIntentSender(IIntentSender sender) { @@ -6164,7 +3937,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen synchronized(this) { PendingIntentRecord rec = (PendingIntentRecord)sender; try { - int uid = ActivityThread.getPackageManager() + int uid = AppGlobals.getPackageManager() .getPackageUid(rec.key.packageName); if (uid != Binder.getCallingUid()) { String msg = "Permission Denial: cancelIntentSender() from pid=" @@ -6323,7 +4096,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return PackageManager.PERMISSION_GRANTED; } try { - return ActivityThread.getPackageManager() + return AppGlobals.getPackageManager() .checkUidPermission(permission, uid); } catch (RemoteException e) { // Should never happen, but if it does... deny! @@ -6376,26 +4149,75 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } private final boolean checkHoldingPermissionsLocked(IPackageManager pm, - ProviderInfo pi, int uid, int modeFlags) { + ProviderInfo pi, Uri uri, int uid, int modeFlags) { + boolean readPerm = (modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0; + boolean writePerm = (modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0; + if (DEBUG_URI_PERMISSION) Slog.v(TAG, + "checkHoldingPermissionsLocked: uri=" + uri + " uid=" + uid); try { - if ((modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) { - if ((pi.readPermission != null) && + // Is the component private from the target uid? + final boolean prv = !pi.exported && pi.applicationInfo.uid != uid; + + // Acceptable if the there is no read permission needed from the + // target or the target is holding the read permission. + if (!readPerm) { + if ((!prv && pi.readPermission == null) || (pm.checkUidPermission(pi.readPermission, uid) - != PackageManager.PERMISSION_GRANTED)) { - return false; + == PackageManager.PERMISSION_GRANTED)) { + readPerm = true; } } - if ((modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) { - if ((pi.writePermission != null) && + + // Acceptable if the there is no write permission needed from the + // target or the target is holding the read permission. + if (!writePerm) { + if (!prv && (pi.writePermission == null) || (pm.checkUidPermission(pi.writePermission, uid) - != PackageManager.PERMISSION_GRANTED)) { - return false; + == PackageManager.PERMISSION_GRANTED)) { + writePerm = true; + } + } + + // Acceptable if there is a path permission matching the URI that + // the target holds the permission on. + PathPermission[] pps = pi.pathPermissions; + if (pps != null && (!readPerm || !writePerm)) { + final String path = uri.getPath(); + int i = pps.length; + while (i > 0 && (!readPerm || !writePerm)) { + i--; + PathPermission pp = pps[i]; + if (!readPerm) { + final String pprperm = pp.getReadPermission(); + if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Checking read perm for " + + pprperm + " for " + pp.getPath() + + ": match=" + pp.match(path) + + " check=" + pm.checkUidPermission(pprperm, uid)); + if (pprperm != null && pp.match(path) && + (pm.checkUidPermission(pprperm, uid) + == PackageManager.PERMISSION_GRANTED)) { + readPerm = true; + } + } + if (!writePerm) { + final String ppwperm = pp.getWritePermission(); + if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Checking write perm " + + ppwperm + " for " + pp.getPath() + + ": match=" + pp.match(path) + + " check=" + pm.checkUidPermission(ppwperm, uid)); + if (ppwperm != null && pp.match(path) && + (pm.checkUidPermission(ppwperm, uid) + == PackageManager.PERMISSION_GRANTED)) { + writePerm = true; + } + } } } - return true; } catch (RemoteException e) { return false; } + + return readPerm && writePerm; } private final boolean checkUriPermissionLocked(Uri uri, int uid, @@ -6431,30 +4253,36 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private void grantUriPermissionLocked(int callingUid, - String targetPkg, Uri uri, int modeFlags, HistoryRecord activity) { + /** + * Check if the targetPkg can be granted permission to access uri by + * the callingUid using the given modeFlags. Throws a security exception + * if callingUid is not allowed to do this. Returns the uid of the target + * if the URI permission grant should be performed; returns -1 if it is not + * needed (for example targetPkg already has permission to access the URI). + */ + int checkGrantUriPermissionLocked(int callingUid, String targetPkg, + Uri uri, int modeFlags) { modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); if (modeFlags == 0) { - return; + return -1; } if (DEBUG_URI_PERMISSION) Slog.v(TAG, - "Requested grant " + targetPkg + " permission to " + uri); + "Checking grant " + targetPkg + " permission to " + uri); - final IPackageManager pm = ActivityThread.getPackageManager(); + final IPackageManager pm = AppGlobals.getPackageManager(); // If this is not a content: uri, we can't do anything with it. if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Can't grant URI permission for non-content URI: " + uri); - return; + return -1; } String name = uri.getAuthority(); ProviderInfo pi = null; - ContentProviderRecord cpr - = (ContentProviderRecord)mProvidersByName.get(name); + ContentProviderRecord cpr = mProvidersByName.get(name); if (cpr != null) { pi = cpr.info; } else { @@ -6466,7 +4294,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } if (pi == null) { Slog.w(TAG, "No content provider found for: " + name); - return; + return -1; } int targetUid; @@ -6475,18 +4303,18 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (targetUid < 0) { if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Can't grant URI permission no uid for: " + targetPkg); - return; + return -1; } } catch (RemoteException ex) { - return; + return -1; } // First... does the target actually need this permission? - if (checkHoldingPermissionsLocked(pm, pi, targetUid, modeFlags)) { + 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; + return -1; } // Second... is the provider allowing granting of URI permissions? @@ -6516,19 +4344,30 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Third... does the caller itself have permission to access // this uri? - if (!checkHoldingPermissionsLocked(pm, pi, callingUid, modeFlags)) { + if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) { if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) { throw new SecurityException("Uid " + callingUid + " does not have permission to uri " + uri); } } - // Okay! So here we are: the caller has the assumed permission + return targetUid; + } + + void grantUriPermissionUncheckedLocked(int targetUid, String targetPkg, + Uri uri, int modeFlags, UriPermissionOwner owner) { + modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + if (modeFlags == 0) { + return; + } + + // So here we are: the caller has the assumed permission // to the uri, and the target doesn't. Let's now give this to // the target. if (DEBUG_URI_PERMISSION) Slog.v(TAG, - "Granting " + targetPkg + " permission to " + uri); + "Granting " + targetPkg + "/" + targetUid + " permission to " + uri); HashMap<Uri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid); @@ -6541,37 +4380,68 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (perm == null) { perm = new UriPermission(targetUid, uri); targetUris.put(uri, perm); - } + perm.modeFlags |= modeFlags; - if (activity == null) { + if (owner == null) { perm.globalModeFlags |= modeFlags; } else if ((modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) { - perm.readActivities.add(activity); - if (activity.readUriPermissions == null) { - activity.readUriPermissions = new HashSet<UriPermission>(); - } - activity.readUriPermissions.add(perm); + perm.readOwners.add(owner); + owner.addReadPermission(perm); } else if ((modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) { - perm.writeActivities.add(activity); - if (activity.writeUriPermissions == null) { - activity.writeUriPermissions = new HashSet<UriPermission>(); - } - activity.writeUriPermissions.add(perm); + perm.writeOwners.add(owner); + owner.addWritePermission(perm); } } - private void grantUriPermissionFromIntentLocked(int callingUid, - String targetPkg, Intent intent, HistoryRecord activity) { - if (intent == null) { + void grantUriPermissionLocked(int callingUid, + String targetPkg, Uri uri, int modeFlags, UriPermissionOwner owner) { + int targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags); + if (targetUid < 0) { return; } + + grantUriPermissionUncheckedLocked(targetUid, targetPkg, uri, modeFlags, owner); + } + + /** + * Like checkGrantUriPermissionLocked, but takes an Intent. + */ + int checkGrantUriPermissionFromIntentLocked(int callingUid, + String targetPkg, Intent intent) { + if (DEBUG_URI_PERMISSION) Slog.v(TAG, + "Checking URI perm to " + (intent != null ? intent.getData() : null) + + " from " + intent + "; flags=0x" + + Integer.toHexString(intent != null ? intent.getFlags() : 0)); + + if (intent == null) { + return -1; + } Uri data = intent.getData(); if (data == null) { + return -1; + } + return checkGrantUriPermissionLocked(callingUid, targetPkg, data, + intent.getFlags()); + } + + /** + * Like grantUriPermissionUncheckedLocked, but takes an Intent. + */ + void grantUriPermissionUncheckedFromIntentLocked(int targetUid, + String targetPkg, Intent intent, UriPermissionOwner owner) { + grantUriPermissionUncheckedLocked(targetUid, targetPkg, intent.getData(), + intent.getFlags(), owner); + } + + void grantUriPermissionFromIntentLocked(int callingUid, + String targetPkg, Intent intent, UriPermissionOwner owner) { + int targetUid = checkGrantUriPermissionFromIntentLocked(callingUid, targetPkg, intent); + if (targetUid < 0) { return; } - grantUriPermissionLocked(callingUid, targetPkg, data, - intent.getFlags(), activity); + + grantUriPermissionUncheckedFromIntentLocked(targetUid, targetPkg, intent, owner); } public void grantUriPermission(IApplicationThread caller, String targetPkg, @@ -6584,12 +4454,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen + " when granting permission to uri " + uri); } if (targetPkg == null) { - Slog.w(TAG, "grantUriPermission: null target"); - return; + throw new IllegalArgumentException("null target"); } if (uri == null) { - Slog.w(TAG, "grantUriPermission: null uri"); - return; + throw new IllegalArgumentException("null uri"); } grantUriPermissionLocked(r.info.uid, targetPkg, uri, modeFlags, @@ -6597,7 +4465,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private void removeUriPermissionIfNeededLocked(UriPermission perm) { + void removeUriPermissionIfNeededLocked(UriPermission perm) { if ((perm.modeFlags&(Intent.FLAG_GRANT_READ_URI_PERMISSION |Intent.FLAG_GRANT_WRITE_URI_PERMISSION)) == 0) { HashMap<Uri, UriPermission> perms @@ -6613,29 +4481,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private void removeActivityUriPermissionsLocked(HistoryRecord activity) { - if (activity.readUriPermissions != null) { - for (UriPermission perm : activity.readUriPermissions) { - perm.readActivities.remove(activity); - if (perm.readActivities.size() == 0 && (perm.globalModeFlags - &Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0) { - perm.modeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION; - removeUriPermissionIfNeededLocked(perm); - } - } - } - if (activity.writeUriPermissions != null) { - for (UriPermission perm : activity.writeUriPermissions) { - perm.writeActivities.remove(activity); - if (perm.writeActivities.size() == 0 && (perm.globalModeFlags - &Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0) { - perm.modeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION; - removeUriPermissionIfNeededLocked(perm); - } - } - } - } - private void revokeUriPermissionLocked(int callingUid, Uri uri, int modeFlags) { modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION @@ -6647,12 +4492,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Revoking all granted permissions to " + uri); - final IPackageManager pm = ActivityThread.getPackageManager(); + final IPackageManager pm = AppGlobals.getPackageManager(); final String authority = uri.getAuthority(); ProviderInfo pi = null; - ContentProviderRecord cpr - = (ContentProviderRecord)mProvidersByName.get(authority); + ContentProviderRecord cpr = mProvidersByName.get(authority); if (cpr != null) { pi = cpr.info; } else { @@ -6668,7 +4512,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } // Does the caller have this permission on the URI? - if (!checkHoldingPermissionsLocked(pm, pi, callingUid, modeFlags)) { + if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) { // Right now, if you are not the original owner of the permission, // you are not allowed to revoke it. //if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) { @@ -6742,12 +4586,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return; } - final IPackageManager pm = ActivityThread.getPackageManager(); + final IPackageManager pm = AppGlobals.getPackageManager(); final String authority = uri.getAuthority(); ProviderInfo pi = null; - ContentProviderRecord cpr - = (ContentProviderRecord)mProvidersByName.get(authority); + ContentProviderRecord cpr = mProvidersByName.get(authority); if (cpr != null) { pi = cpr.info; } else { @@ -6766,6 +4609,56 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + @Override + public IBinder newUriPermissionOwner(String name) { + synchronized(this) { + UriPermissionOwner owner = new UriPermissionOwner(this, name); + return owner.getExternalTokenLocked(); + } + } + + @Override + public void grantUriPermissionFromOwner(IBinder token, int fromUid, String targetPkg, + Uri uri, int modeFlags) { + synchronized(this) { + UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token); + if (owner == null) { + throw new IllegalArgumentException("Unknown owner: " + token); + } + if (fromUid != Binder.getCallingUid()) { + if (Binder.getCallingUid() != Process.myUid()) { + // Only system code can grant URI permissions on behalf + // of other users. + throw new SecurityException("nice try"); + } + } + if (targetPkg == null) { + throw new IllegalArgumentException("null target"); + } + if (uri == null) { + throw new IllegalArgumentException("null uri"); + } + + grantUriPermissionLocked(fromUid, targetPkg, uri, modeFlags, owner); + } + } + + @Override + public void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode) { + synchronized(this) { + UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token); + if (owner == null) { + throw new IllegalArgumentException("Unknown owner: " + token); + } + + if (uri == null) { + owner.removeUriPermissionsLocked(mode); + } else { + owner.removeUriPermissionLocked(uri, mode); + } + } + } + public void showWaitingForDebugger(IApplicationThread who, boolean waiting) { synchronized (this) { ProcessRecord app = @@ -6797,7 +4690,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen PendingThumbnailsRecord pending = null; IApplicationThread topThumbnail = null; - HistoryRecord topRecord = null; + ActivityRecord topRecord = null; synchronized(this) { if (localLOGV) Slog.v( @@ -6822,18 +4715,18 @@ public final class ActivityManagerService extends ActivityManagerNative implemen throw new SecurityException(msg); } - int pos = mHistory.size()-1; - HistoryRecord next = - pos >= 0 ? (HistoryRecord)mHistory.get(pos) : null; - HistoryRecord top = null; + int pos = mMainStack.mHistory.size()-1; + ActivityRecord next = + pos >= 0 ? (ActivityRecord)mMainStack.mHistory.get(pos) : null; + ActivityRecord top = null; CharSequence topDescription = null; TaskRecord curTask = null; int numActivities = 0; int numRunning = 0; while (pos >= 0 && maxNum > 0) { - final HistoryRecord r = next; + final ActivityRecord r = next; pos--; - next = pos >= 0 ? (HistoryRecord)mHistory.get(pos) : null; + next = pos >= 0 ? (ActivityRecord)mMainStack.mHistory.get(pos) : null; // Initialize state for next task if needed. if (top == null || @@ -6935,7 +4828,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen enforceCallingPermission(android.Manifest.permission.GET_TASKS, "getRecentTasks()"); - IPackageManager pm = ActivityThread.getPackageManager(); + IPackageManager pm = AppGlobals.getPackageManager(); final int N = mRecentTasks.size(); ArrayList<ActivityManager.RecentTaskInfo> res @@ -6982,12 +4875,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen private final int findAffinityTaskTopLocked(int startIndex, String affinity) { int j; - TaskRecord startTask = ((HistoryRecord)mHistory.get(startIndex)).task; + TaskRecord startTask = ((ActivityRecord)mMainStack.mHistory.get(startIndex)).task; TaskRecord jt = startTask; // First look backwards for (j=startIndex-1; j>=0; j--) { - HistoryRecord r = (HistoryRecord)mHistory.get(j); + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(j); if (r.task != jt) { jt = r.task; if (affinity.equals(jt.affinity)) { @@ -6997,10 +4890,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } // Now look forwards - final int N = mHistory.size(); + final int N = mMainStack.mHistory.size(); jt = startTask; for (j=startIndex+1; j<N; j++) { - HistoryRecord r = (HistoryRecord)mHistory.get(j); + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(j); if (r.task != jt) { if (affinity.equals(jt.affinity)) { return j; @@ -7010,7 +4903,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } // Might it be at the top? - if (affinity.equals(((HistoryRecord)mHistory.get(N-1)).task.affinity)) { + if (affinity.equals(((ActivityRecord)mMainStack.mHistory.get(N-1)).task.affinity)) { return N-1; } @@ -7018,293 +4911,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } /** - * Perform a reset of the given task, if needed as part of launching it. - * Returns the new HistoryRecord at the top of the task. - */ - private final HistoryRecord resetTaskIfNeededLocked(HistoryRecord taskTop, - HistoryRecord newActivity) { - boolean forceReset = (newActivity.info.flags - &ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0; - if (taskTop.task.getInactiveDuration() > ACTIVITY_INACTIVE_RESET_TIME) { - if ((newActivity.info.flags - &ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE) == 0) { - forceReset = true; - } - } - - final TaskRecord task = taskTop.task; - - // We are going to move through the history list so that we can look - // at each activity 'target' with 'below' either the interesting - // activity immediately below it in the stack or null. - HistoryRecord target = null; - int targetI = 0; - int taskTopI = -1; - int replyChainEnd = -1; - int lastReparentPos = -1; - for (int i=mHistory.size()-1; i>=-1; i--) { - HistoryRecord below = i >= 0 ? (HistoryRecord)mHistory.get(i) : null; - - if (below != null && below.finishing) { - continue; - } - if (target == null) { - target = below; - targetI = i; - // If we were in the middle of a reply chain before this - // task, it doesn't appear like the root of the chain wants - // anything interesting, so drop it. - replyChainEnd = -1; - continue; - } - - final int flags = target.info.flags; - - final boolean finishOnTaskLaunch = - (flags&ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH) != 0; - final boolean allowTaskReparenting = - (flags&ActivityInfo.FLAG_ALLOW_TASK_REPARENTING) != 0; - - if (target.task == task) { - // We are inside of the task being reset... we'll either - // finish this activity, push it out for another task, - // or leave it as-is. We only do this - // for activities that are not the root of the task (since - // if we finish the root, we may no longer have the task!). - if (taskTopI < 0) { - taskTopI = targetI; - } - if (below != null && below.task == task) { - final boolean clearWhenTaskReset = - (target.intent.getFlags() - &Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0; - if (!finishOnTaskLaunch && !clearWhenTaskReset && target.resultTo != null) { - // If this activity is sending a reply to a previous - // activity, we can't do anything with it now until - // we reach the start of the reply chain. - // XXX note that we are assuming the result is always - // to the previous activity, which is almost always - // the case but we really shouldn't count on. - if (replyChainEnd < 0) { - replyChainEnd = targetI; - } - } else if (!finishOnTaskLaunch && !clearWhenTaskReset && allowTaskReparenting - && target.taskAffinity != null - && !target.taskAffinity.equals(task.affinity)) { - // If this activity has an affinity for another - // task, then we need to move it out of here. We will - // move it as far out of the way as possible, to the - // bottom of the activity stack. This also keeps it - // correctly ordered with any activities we previously - // moved. - HistoryRecord p = (HistoryRecord)mHistory.get(0); - if (target.taskAffinity != null - && target.taskAffinity.equals(p.task.affinity)) { - // If the activity currently at the bottom has the - // same task affinity as the one we are moving, - // then merge it into the same task. - target.task = p.task; - if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target - + " out to bottom task " + p.task); - } else { - mCurTask++; - if (mCurTask <= 0) { - mCurTask = 1; - } - target.task = new TaskRecord(mCurTask, target.info, null, - (target.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0); - target.task.affinityIntent = target.intent; - if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target - + " out to new task " + target.task); - } - mWindowManager.setAppGroupId(target, task.taskId); - if (replyChainEnd < 0) { - replyChainEnd = targetI; - } - int dstPos = 0; - for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) { - p = (HistoryRecord)mHistory.get(srcPos); - if (p.finishing) { - continue; - } - if (DEBUG_TASKS) Slog.v(TAG, "Pushing next activity " + p - + " out to target's task " + target.task); - task.numActivities--; - p.task = target.task; - target.task.numActivities++; - mHistory.remove(srcPos); - mHistory.add(dstPos, p); - mWindowManager.moveAppToken(dstPos, p); - mWindowManager.setAppGroupId(p, p.task.taskId); - dstPos++; - if (VALIDATE_TOKENS) { - mWindowManager.validateAppTokens(mHistory); - } - i++; - } - if (taskTop == p) { - taskTop = below; - } - if (taskTopI == replyChainEnd) { - taskTopI = -1; - } - replyChainEnd = -1; - addRecentTaskLocked(target.task); - } else if (forceReset || finishOnTaskLaunch - || clearWhenTaskReset) { - // If the activity should just be removed -- either - // because it asks for it, or the task should be - // cleared -- then finish it and anything that is - // part of its reply chain. - if (clearWhenTaskReset) { - // In this case, we want to finish this activity - // and everything above it, so be sneaky and pretend - // like these are all in the reply chain. - replyChainEnd = targetI+1; - while (replyChainEnd < mHistory.size() && - ((HistoryRecord)mHistory.get( - replyChainEnd)).task == task) { - replyChainEnd++; - } - replyChainEnd--; - } else if (replyChainEnd < 0) { - replyChainEnd = targetI; - } - HistoryRecord p = null; - for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) { - p = (HistoryRecord)mHistory.get(srcPos); - if (p.finishing) { - continue; - } - if (finishActivityLocked(p, srcPos, - Activity.RESULT_CANCELED, null, "reset")) { - replyChainEnd--; - srcPos--; - } - } - if (taskTop == p) { - taskTop = below; - } - if (taskTopI == replyChainEnd) { - taskTopI = -1; - } - replyChainEnd = -1; - } else { - // If we were in the middle of a chain, well the - // activity that started it all doesn't want anything - // special, so leave it all as-is. - replyChainEnd = -1; - } - } else { - // Reached the bottom of the task -- any reply chain - // should be left as-is. - replyChainEnd = -1; - } - - } else if (target.resultTo != null) { - // If this activity is sending a reply to a previous - // activity, we can't do anything with it now until - // we reach the start of the reply chain. - // XXX note that we are assuming the result is always - // to the previous activity, which is almost always - // the case but we really shouldn't count on. - if (replyChainEnd < 0) { - replyChainEnd = targetI; - } - - } else if (taskTopI >= 0 && allowTaskReparenting - && task.affinity != null - && task.affinity.equals(target.taskAffinity)) { - // We are inside of another task... if this activity has - // an affinity for our task, then either remove it if we are - // clearing or move it over to our task. Note that - // we currently punt on the case where we are resetting a - // task that is not at the top but who has activities above - // with an affinity to it... this is really not a normal - // case, and we will need to later pull that task to the front - // and usually at that point we will do the reset and pick - // up those remaining activities. (This only happens if - // someone starts an activity in a new task from an activity - // in a task that is not currently on top.) - if (forceReset || finishOnTaskLaunch) { - if (replyChainEnd < 0) { - replyChainEnd = targetI; - } - HistoryRecord p = null; - for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) { - p = (HistoryRecord)mHistory.get(srcPos); - if (p.finishing) { - continue; - } - if (finishActivityLocked(p, srcPos, - Activity.RESULT_CANCELED, null, "reset")) { - taskTopI--; - lastReparentPos--; - replyChainEnd--; - srcPos--; - } - } - replyChainEnd = -1; - } else { - if (replyChainEnd < 0) { - replyChainEnd = targetI; - } - for (int srcPos=replyChainEnd; srcPos>=targetI; srcPos--) { - HistoryRecord p = (HistoryRecord)mHistory.get(srcPos); - if (p.finishing) { - continue; - } - if (lastReparentPos < 0) { - lastReparentPos = taskTopI; - taskTop = p; - } else { - lastReparentPos--; - } - mHistory.remove(srcPos); - p.task.numActivities--; - p.task = task; - mHistory.add(lastReparentPos, p); - if (DEBUG_TASKS) Slog.v(TAG, "Pulling activity " + p - + " in to resetting task " + task); - task.numActivities++; - mWindowManager.moveAppToken(lastReparentPos, p); - mWindowManager.setAppGroupId(p, p.task.taskId); - if (VALIDATE_TOKENS) { - mWindowManager.validateAppTokens(mHistory); - } - } - replyChainEnd = -1; - - // Now we've moved it in to place... but what if this is - // a singleTop activity and we have put it on top of another - // instance of the same activity? Then we drop the instance - // below so it remains singleTop. - if (target.info.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) { - for (int j=lastReparentPos-1; j>=0; j--) { - HistoryRecord p = (HistoryRecord)mHistory.get(j); - if (p.finishing) { - continue; - } - if (p.intent.getComponent().equals(target.intent.getComponent())) { - if (finishActivityLocked(p, j, - Activity.RESULT_CANCELED, null, "replace")) { - taskTopI--; - lastReparentPos--; - } - } - } - } - } - } - - target = below; - targetI = i; - } - - return taskTop; - } - - /** * TODO: Add mController hook */ public void moveTaskToFront(int task) { @@ -7322,14 +4928,14 @@ public final class ActivityManagerService extends ActivityManagerNative implemen for (int i=0; i<N; i++) { TaskRecord tr = mRecentTasks.get(i); if (tr.taskId == task) { - moveTaskToFrontLocked(tr, null); + mMainStack.moveTaskToFrontLocked(tr, null); return; } } - for (int i=mHistory.size()-1; i>=0; i--) { - HistoryRecord hr = (HistoryRecord)mHistory.get(i); + for (int i=mMainStack.mHistory.size()-1; i>=0; i--) { + ActivityRecord hr = (ActivityRecord)mMainStack.mHistory.get(i); if (hr.task.taskId == task) { - moveTaskToFrontLocked(hr.task, null); + mMainStack.moveTaskToFrontLocked(hr.task, null); return; } } @@ -7339,84 +4945,20 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private final void moveTaskToFrontLocked(TaskRecord tr, HistoryRecord reason) { - if (DEBUG_SWITCH) Slog.v(TAG, "moveTaskToFront: " + tr); - - final int task = tr.taskId; - int top = mHistory.size()-1; - - if (top < 0 || ((HistoryRecord)mHistory.get(top)).task.taskId == task) { - // nothing to do! - return; - } - - ArrayList moved = new ArrayList(); - - // Applying the affinities may have removed entries from the history, - // so get the size again. - top = mHistory.size()-1; - int pos = top; - - // Shift all activities with this task up to the top - // of the stack, keeping them in the same internal order. - while (pos >= 0) { - HistoryRecord r = (HistoryRecord)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) { - addRecentTaskLocked(r.task); - first = false; - } - } - pos--; - } - - if (DEBUG_TRANSITION) Slog.v(TAG, - "Prepare to front transition: task=" + tr); - if (reason != null && - (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); - HistoryRecord r = topRunningActivityLocked(null); - if (r != null) { - mNoAnimActivities.add(r); - } - } else { - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_TO_FRONT); - } - - mWindowManager.moveAppTokensToTop(moved); - if (VALIDATE_TOKENS) { - mWindowManager.validateAppTokens(mHistory); - } - - finishTaskMoveLocked(task); - EventLog.writeEvent(EventLogTags.AM_TASK_TO_FRONT, task); - } - - private final void finishTaskMoveLocked(int task) { - resumeTopActivityLocked(null); - } - public void moveTaskToBack(int task) { enforceCallingPermission(android.Manifest.permission.REORDER_TASKS, "moveTaskToBack()"); synchronized(this) { - if (mResumedActivity != null && mResumedActivity.task.taskId == task) { + if (mMainStack.mResumedActivity != null + && mMainStack.mResumedActivity.task.taskId == task) { if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(), Binder.getCallingUid(), "Task to back")) { return; } } final long origId = Binder.clearCallingIdentity(); - moveTaskToBackLocked(task, null); + mMainStack.moveTaskToBackLocked(task, null); Binder.restoreCallingIdentity(origId); } } @@ -7435,93 +4977,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen final long origId = Binder.clearCallingIdentity(); int taskId = getTaskForActivityLocked(token, !nonRoot); if (taskId >= 0) { - return moveTaskToBackLocked(taskId, null); + return mMainStack.moveTaskToBackLocked(taskId, null); } Binder.restoreCallingIdentity(origId); } return false; } - /** - * Worker method for rearranging history stack. Implements the function of moving all - * activities for a specific task (gathering them if disjoint) into a single group at the - * bottom of the stack. - * - * If a watcher is installed, the action is preflighted and the watcher has an opportunity - * to premeptively cancel the move. - * - * @param task The taskId to collect and move to the bottom. - * @return Returns true if the move completed, false if not. - */ - private final boolean moveTaskToBackLocked(int task, HistoryRecord reason) { - Slog.i(TAG, "moveTaskToBack: " + task); - - // If we have a watcher, preflight the move before committing to it. First check - // for *other* available tasks, but if none are available, then try again allowing the - // current task to be selected. - if (mController != null) { - HistoryRecord next = topRunningActivityLocked(null, task); - if (next == null) { - next = topRunningActivityLocked(null, 0); - } - if (next != null) { - // ask watcher if this is allowed - boolean moveOK = true; - try { - moveOK = mController.activityResuming(next.packageName); - } catch (RemoteException e) { - mController = null; - } - if (!moveOK) { - return false; - } - } - } - - ArrayList moved = new ArrayList(); - - if (DEBUG_TRANSITION) Slog.v(TAG, - "Prepare to back transition: task=" + task); - - final int N = mHistory.size(); - int bottom = 0; - int pos = 0; - - // Shift all activities with this task down to the bottom - // of the stack, keeping them in the same internal order. - while (pos < N) { - HistoryRecord r = (HistoryRecord)mHistory.get(pos); - if (localLOGV) Slog.v( - TAG, "At " + pos + " ckp " + r.task + ": " + r); - if (r.task.taskId == task) { - if (localLOGV) Slog.v(TAG, "Removing and adding at " + (N-1)); - mHistory.remove(pos); - mHistory.add(bottom, r); - moved.add(r); - bottom++; - } - pos++; - } - - if (reason != null && - (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); - HistoryRecord r = topRunningActivityLocked(null); - if (r != null) { - mNoAnimActivities.add(r); - } - } else { - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_TO_BACK); - } - mWindowManager.moveAppTokensToBottom(moved); - if (VALIDATE_TOKENS) { - mWindowManager.validateAppTokens(mHistory); - } - - finishTaskMoveLocked(task); - return true; - } - public void moveTaskBackwards(int task) { enforceCallingPermission(android.Manifest.permission.REORDER_TASKS, "moveTaskBackwards()"); @@ -7548,10 +5010,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } int getTaskForActivityLocked(IBinder token, boolean onlyRoot) { - final int N = mHistory.size(); + final int N = mMainStack.mHistory.size(); TaskRecord lastTask = null; for (int i=0; i<N; i++) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); if (r == token) { if (!onlyRoot || lastTask != r.task) { return r.task.taskId; @@ -7564,89 +5026,17 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return -1; } - /** - * Returns the top activity in any existing task matching the given - * Intent. Returns null if no such task is found. - */ - private HistoryRecord findTaskLocked(Intent intent, ActivityInfo info) { - ComponentName cls = intent.getComponent(); - if (info.targetActivity != null) { - cls = new ComponentName(info.packageName, info.targetActivity); - } - - TaskRecord cp = null; - - final int N = mHistory.size(); - for (int i=(N-1); i>=0; i--) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); - if (!r.finishing && r.task != cp - && r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE) { - cp = r.task; - //Slog.i(TAG, "Comparing existing cls=" + r.task.intent.getComponent().flattenToShortString() - // + "/aff=" + r.task.affinity + " to new cls=" - // + intent.getComponent().flattenToShortString() + "/aff=" + taskAffinity); - if (r.task.affinity != null) { - if (r.task.affinity.equals(info.taskAffinity)) { - //Slog.i(TAG, "Found matching affinity!"); - return r; - } - } else if (r.task.intent != null - && r.task.intent.getComponent().equals(cls)) { - //Slog.i(TAG, "Found matching class!"); - //dump(); - //Slog.i(TAG, "For Intent " + intent + " bringing to top: " + r.intent); - return r; - } else if (r.task.affinityIntent != null - && r.task.affinityIntent.getComponent().equals(cls)) { - //Slog.i(TAG, "Found matching class!"); - //dump(); - //Slog.i(TAG, "For Intent " + intent + " bringing to top: " + r.intent); - return r; - } - } - } - - return null; - } - - /** - * Returns the first activity (starting from the top of the stack) that - * is the same as the given activity. Returns null if no such activity - * is found. - */ - private HistoryRecord findActivityLocked(Intent intent, ActivityInfo info) { - ComponentName cls = intent.getComponent(); - if (info.targetActivity != null) { - cls = new ComponentName(info.packageName, info.targetActivity); - } - - final int N = mHistory.size(); - for (int i=(N-1); i>=0; i--) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); - if (!r.finishing) { - if (r.intent.getComponent().equals(cls)) { - //Slog.i(TAG, "Found matching class!"); - //dump(); - //Slog.i(TAG, "For Intent " + intent + " bringing to top: " + r.intent); - return r; - } - } - } - - return null; - } - public void finishOtherInstances(IBinder token, ComponentName className) { synchronized(this) { final long origId = Binder.clearCallingIdentity(); - int N = mHistory.size(); + int N = mMainStack.mHistory.size(); TaskRecord lastTask = null; for (int i=0; i<N; i++) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); if (r.realActivity.equals(className) && r != token && lastTask != r.task) { - if (finishActivityLocked(r, i, Activity.RESULT_CANCELED, + if (r.stack.finishActivityLocked(r, i, Activity.RESULT_CANCELED, null, "others")) { i--; N--; @@ -7671,7 +5061,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen Binder.restoreCallingIdentity(origId); } - final void sendPendingThumbnail(HistoryRecord r, IBinder token, + final void sendPendingThumbnail(ActivityRecord r, IBinder token, Bitmap thumbnail, CharSequence description, boolean always) { TaskRecord task = null; ArrayList receivers = null; @@ -7680,11 +5070,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen synchronized(this) { if (r == null) { - int index = indexOfTokenLocked(token); + int index = mMainStack.indexOfTokenLocked(token); if (index < 0) { return; } - r = (HistoryRecord)mHistory.get(index); + r = (ActivityRecord)mMainStack.mHistory.get(index); } if (thumbnail == null) { thumbnail = r.thumbnail; @@ -7745,7 +5135,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen private final List generateApplicationProvidersLocked(ProcessRecord app) { List providers = null; try { - providers = ActivityThread.getPackageManager(). + providers = AppGlobals.getPackageManager(). queryContentProviders(app.processName, app.info.uid, STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS); } catch (RemoteException ex) { @@ -7755,8 +5145,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen for (int i=0; i<N; i++) { ProviderInfo cpi = (ProviderInfo)providers.get(i); - ContentProviderRecord cpr = - (ContentProviderRecord)mProvidersByClass.get(cpi.name); + ContentProviderRecord cpr = mProvidersByClass.get(cpi.name); if (cpr == null) { cpr = new ContentProviderRecord(cpi, app.info); mProvidersByClass.put(cpi.name, cpr); @@ -7770,13 +5159,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } private final String checkContentProviderPermissionLocked( - ProviderInfo cpi, ProcessRecord r, int mode) { + ProviderInfo cpi, ProcessRecord r) { 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) - == PackageManager.PERMISSION_GRANTED - && mode == ParcelFileDescriptor.MODE_READ_ONLY || mode == -1) { + == PackageManager.PERMISSION_GRANTED) { return null; } if (checkComponentPermission(cpi.writePermission, callingPid, callingUid, @@ -7793,8 +5181,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen PathPermission pp = pps[i]; if (checkComponentPermission(pp.getReadPermission(), callingPid, callingUid, cpi.exported ? -1 : cpi.applicationInfo.uid) - == PackageManager.PERMISSION_GRANTED - && mode == ParcelFileDescriptor.MODE_READ_ONLY || mode == -1) { + == PackageManager.PERMISSION_GRANTED) { return null; } if (checkComponentPermission(pp.getWritePermission(), callingPid, callingUid, @@ -7805,6 +5192,15 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + HashMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(callingUid); + if (perms != null) { + for (Map.Entry<Uri, UriPermission> uri : perms.entrySet()) { + if (uri.getKey().getAuthority().equals(cpi.authority)) { + return null; + } + } + } + String msg = "Permission Denial: opening provider " + cpi.name + " from " + (r != null ? r : "(null)") + " (pid=" + callingPid + ", uid=" + callingUid + ") requires " @@ -7831,13 +5227,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } // First check if this content provider has been published... - cpr = (ContentProviderRecord)mProvidersByName.get(name); + cpr = mProvidersByName.get(name); if (cpr != null) { cpi = cpr.info; - if (checkContentProviderPermissionLocked(cpi, r, -1) != null) { - return new ContentProviderHolder(cpi, - cpi.readPermission != null - ? cpi.readPermission : cpi.writePermission); + String msg; + if ((msg=checkContentProviderPermissionLocked(cpi, r)) != null) { + throw new SecurityException(msg); } if (r != null && cpr.canRunHere(r)) { @@ -7869,8 +5264,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen r.conProviders.put(cpr, new Integer(cnt.intValue()+1)); } cpr.clients.add(r); - if (cpr.app != null && r.setAdj >= VISIBLE_APP_ADJ) { - // If this is a visible app accessing the provider, + if (cpr.app != null && r.setAdj <= PERCEPTIBLE_APP_ADJ) { + // If this is a perceptible app accessing the provider, // make sure to count it as being accessed and thus // back up on the LRU list. This is good because // content providers are often expensive to start. @@ -7888,7 +5283,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } else { try { - cpi = ActivityThread.getPackageManager(). + cpi = AppGlobals.getPackageManager(). resolveContentProvider(name, STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS); } catch (RemoteException ex) { @@ -7897,13 +5292,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return null; } - if (checkContentProviderPermissionLocked(cpi, r, -1) != null) { - return new ContentProviderHolder(cpi, - cpi.readPermission != null - ? cpi.readPermission : cpi.writePermission); + String msg; + if ((msg=checkContentProviderPermissionLocked(cpi, r)) != null) { + throw new SecurityException(msg); } - if (!mSystemReady && !mDidUpdate && !mWaitingUpdate + if (!mProcessesReady && !mDidUpdate && !mWaitingUpdate && !cpi.processName.equals("system")) { // If this content provider does not run in the system // process, and the system is not yet ready to run other @@ -7912,12 +5306,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen "Attempt to launch content provider before system ready"); } - cpr = (ContentProviderRecord)mProvidersByClass.get(cpi.name); + cpr = mProvidersByClass.get(cpi.name); final boolean firstClass = cpr == null; if (firstClass) { try { ApplicationInfo ai = - ActivityThread.getPackageManager(). + AppGlobals.getPackageManager(). getApplicationInfo( cpi.applicationInfo.packageName, STOCK_PM_FLAGS); @@ -8046,7 +5440,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen */ public void removeContentProvider(IApplicationThread caller, String name) { synchronized (this) { - ContentProviderRecord cpr = (ContentProviderRecord)mProvidersByName.get(name); + ContentProviderRecord cpr = mProvidersByName.get(name); if(cpr == null) { // remove from mProvidersByClass if (DEBUG_PROVIDER) Slog.v(TAG, name + @@ -8060,8 +5454,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen " when removing content provider " + name); } //update content provider record entry info - ContentProviderRecord localCpr = (ContentProviderRecord) - mProvidersByClass.get(cpr.info.name); + ContentProviderRecord localCpr = mProvidersByClass.get(cpr.info.name); if (DEBUG_PROVIDER) Slog.v(TAG, "Removing provider requested by " + r.info.processName + " from process " + localCpr.appInfo.processName); @@ -8085,7 +5478,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen private void removeContentProviderExternal(String name) { synchronized (this) { - ContentProviderRecord cpr = (ContentProviderRecord)mProvidersByName.get(name); + ContentProviderRecord cpr = mProvidersByName.get(name); if(cpr == null) { //remove from mProvidersByClass if(localLOGV) Slog.v(TAG, name+" content provider not found in providers list"); @@ -8093,7 +5486,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } //update content provider record entry info - ContentProviderRecord localCpr = (ContentProviderRecord) mProvidersByClass.get(cpr.info.name); + ContentProviderRecord localCpr = mProvidersByClass.get(cpr.info.name); localCpr.externals--; if (localCpr.externals < 0) { Slog.e(TAG, "Externals < 0 for content provider " + localCpr); @@ -8125,8 +5518,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (src == null || src.info == null || src.provider == null) { continue; } - ContentProviderRecord dst = - (ContentProviderRecord)r.pubProviders.get(src.info.name); + ContentProviderRecord dst = r.pubProviders.get(src.info.name); if (dst != null) { mProvidersByClass.put(dst.info.name, dst); String names[] = dst.info.authority.split(";"); @@ -8177,6 +5569,38 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + /** + * Allows app to retrieve the MIME type of a URI without having permission + * to access its content provider. + * + * CTS tests for this functionality can be run with "runtest cts-appsecurity". + * + * Test cases are at cts/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/ + * src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java + */ + public String getProviderMimeType(Uri uri) { + final String name = uri.getAuthority(); + final long ident = Binder.clearCallingIdentity(); + ContentProviderHolder holder = null; + + try { + holder = getContentProviderExternal(name); + if (holder != null) { + return holder.provider.getType(uri); + } + } catch (RemoteException e) { + Log.w(TAG, "Content provider dead retrieving " + uri, e); + return null; + } finally { + if (holder != null) { + removeContentProviderExternal(name); + } + Binder.restoreCallingIdentity(ident); + } + + return null; + } + // ========================================================= // GLOBAL MANAGEMENT // ========================================================= @@ -8219,12 +5643,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen "unhandledBack()"); synchronized(this) { - int count = mHistory.size(); + int count = mMainStack.mHistory.size(); if (DEBUG_SWITCH) Slog.d( TAG, "Performing unhandledBack(): stack size = " + count); if (count > 1) { final long origId = Binder.clearCallingIdentity(); - finishActivityLocked((HistoryRecord)mHistory.get(count-1), + mMainStack.finishActivityLocked((ActivityRecord)mMainStack.mHistory.get(count-1), count-1, Activity.RESULT_CANCELED, null, "unhandled-back"); Binder.restoreCallingIdentity(origId); } @@ -8267,11 +5691,17 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mSleeping = true; mWindowManager.setEventDispatching(false); - if (mResumedActivity != null) { - pauseIfSleepingLocked(); + if (mMainStack.mResumedActivity != null) { + mMainStack.pauseIfSleepingLocked(); } else { Slog.w(TAG, "goingToSleep with no resumed activity!"); } + + // Initialize the wake times of all processes. + checkExcessivePowerUsageLocked(false); + mHandler.removeMessages(CHECK_EXCESSIVE_WAKE_LOCKS_MSG); + Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG); + mHandler.sendMessageDelayed(nmsg, POWER_CHECK_DELAY); } } @@ -8288,10 +5718,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mShuttingDown = true; mWindowManager.setEventDispatching(false); - if (mResumedActivity != null) { - pauseIfSleepingLocked(); + if (mMainStack.mResumedActivity != null) { + mMainStack.pauseIfSleepingLocked(); final long endTime = System.currentTimeMillis() + timeout; - while (mResumedActivity != null || mPausingActivity != null) { + while (mMainStack.mResumedActivity != null + || mMainStack.mPausingActivity != null) { long delay = endTime - System.currentTimeMillis(); if (delay <= 0) { Slog.w(TAG, "Activity manager shutdown timed out"); @@ -8312,35 +5743,14 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return timedout; } - void pauseIfSleepingLocked() { - if (mSleeping || mShuttingDown) { - if (!mGoingToSleep.isHeld()) { - mGoingToSleep.acquire(); - if (mLaunchingActivity.isHeld()) { - mLaunchingActivity.release(); - mHandler.removeMessages(LAUNCH_TIMEOUT_MSG); - } - } - - // 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..."); - if (DEBUG_USER_LEAVING) Slog.v(TAG, "Sleep => pause with userLeaving=false"); - startPausingLocked(false, true); - } - } - } - public void wakingUp() { synchronized(this) { - if (mGoingToSleep.isHeld()) { - mGoingToSleep.release(); + if (mMainStack.mGoingToSleep.isHeld()) { + mMainStack.mGoingToSleep.release(); } mWindowManager.setEventDispatching(true); mSleeping = false; - resumeTopActivityLocked(null); + mMainStack.resumeTopActivityLocked(null); } } @@ -8474,14 +5884,14 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // and started launching other packages. if (!mSystemReady) { try { - ActivityThread.getPackageManager().enterSafeMode(); + 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_SYSTEM_OVERLAY; + 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; @@ -8560,123 +5970,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return killed; } - public void reportPss(IApplicationThread caller, int pss) { - Watchdog.PssRequestor req; - String name; - ProcessRecord callerApp; - synchronized (this) { - if (caller == null) { - return; - } - callerApp = getRecordForAppLocked(caller); - if (callerApp == null) { - return; - } - callerApp.lastPss = pss; - req = callerApp; - name = callerApp.processName; - } - Watchdog.getInstance().reportPss(req, name, pss); - if (!callerApp.persistent) { - removeRequestedPss(callerApp); - } - } - - public void requestPss(Runnable completeCallback) { - ArrayList<ProcessRecord> procs; - synchronized (this) { - mRequestPssCallback = completeCallback; - mRequestPssList.clear(); - for (int i=mLruProcesses.size()-1; i>=0; i--) { - ProcessRecord proc = mLruProcesses.get(i); - if (!proc.persistent) { - mRequestPssList.add(proc); - } - } - procs = new ArrayList<ProcessRecord>(mRequestPssList); - } - - int oldPri = Process.getThreadPriority(Process.myTid()); - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - for (int i=procs.size()-1; i>=0; i--) { - ProcessRecord proc = procs.get(i); - proc.lastPss = 0; - proc.requestPss(); - } - Process.setThreadPriority(oldPri); - } - - void removeRequestedPss(ProcessRecord proc) { - Runnable callback = null; - synchronized (this) { - if (mRequestPssList.remove(proc)) { - if (mRequestPssList.size() == 0) { - callback = mRequestPssCallback; - mRequestPssCallback = null; - } - } - } - - if (callback != null) { - callback.run(); - } - } - - public void collectPss(Watchdog.PssStats stats) { - stats.mEmptyPss = 0; - stats.mEmptyCount = 0; - stats.mBackgroundPss = 0; - stats.mBackgroundCount = 0; - stats.mServicePss = 0; - stats.mServiceCount = 0; - stats.mVisiblePss = 0; - stats.mVisibleCount = 0; - stats.mForegroundPss = 0; - stats.mForegroundCount = 0; - stats.mNoPssCount = 0; - synchronized (this) { - int i; - int NPD = mProcDeaths.length < stats.mProcDeaths.length - ? mProcDeaths.length : stats.mProcDeaths.length; - int aggr = 0; - for (i=0; i<NPD; i++) { - aggr += mProcDeaths[i]; - stats.mProcDeaths[i] = aggr; - } - while (i<stats.mProcDeaths.length) { - stats.mProcDeaths[i] = 0; - i++; - } - - for (i=mLruProcesses.size()-1; i>=0; i--) { - ProcessRecord proc = mLruProcesses.get(i); - if (proc.persistent) { - continue; - } - //Slog.i(TAG, "Proc " + proc + ": pss=" + proc.lastPss); - if (proc.lastPss == 0) { - stats.mNoPssCount++; - continue; - } - if (proc.setAdj >= HIDDEN_APP_MIN_ADJ) { - if (proc.empty) { - stats.mEmptyPss += proc.lastPss; - stats.mEmptyCount++; - } else { - stats.mBackgroundPss += proc.lastPss; - stats.mBackgroundCount++; - } - } else if (proc.setAdj >= VISIBLE_APP_ADJ) { - stats.mVisiblePss += proc.lastPss; - stats.mVisibleCount++; - } else { - stats.mForegroundPss += proc.lastPss; - stats.mForegroundCount++; - } - } - } - } - public final void startRunning(String pkg, String cls, String action, String data) { synchronized(this) { @@ -8725,6 +6018,77 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return mSystemReady; } + private static File getCalledPreBootReceiversFile() { + File dataDir = Environment.getDataDirectory(); + File systemDir = new File(dataDir, "system"); + File fname = new File(systemDir, "called_pre_boots.dat"); + return fname; + } + + private static ArrayList<ComponentName> readLastDonePreBootReceivers() { + ArrayList<ComponentName> lastDoneReceivers = new ArrayList<ComponentName>(); + File file = getCalledPreBootReceiversFile(); + FileInputStream fis = null; + try { + fis = new FileInputStream(file); + DataInputStream dis = new DataInputStream(new BufferedInputStream(fis, 2048)); + int vers = dis.readInt(); + String codename = dis.readUTF(); + if (vers == android.os.Build.VERSION.SDK_INT + && codename.equals(android.os.Build.VERSION.CODENAME)) { + int num = dis.readInt(); + while (num > 0) { + num--; + String pkg = dis.readUTF(); + String cls = dis.readUTF(); + lastDoneReceivers.add(new ComponentName(pkg, cls)); + } + } + } catch (FileNotFoundException e) { + } catch (IOException e) { + Slog.w(TAG, "Failure reading last done pre-boot receivers", e); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + } + } + } + return lastDoneReceivers; + } + + private static void writeLastDonePreBootReceivers(ArrayList<ComponentName> list) { + File file = getCalledPreBootReceiversFile(); + FileOutputStream fos = null; + DataOutputStream dos = null; + try { + Slog.i(TAG, "Writing new set of last done pre-boot receivers..."); + fos = new FileOutputStream(file); + dos = new DataOutputStream(new BufferedOutputStream(fos, 2048)); + dos.writeInt(android.os.Build.VERSION.SDK_INT); + dos.writeUTF(android.os.Build.VERSION.CODENAME); + dos.writeInt(list.size()); + for (int i=0; i<list.size(); i++) { + dos.writeUTF(list.get(i).getPackageName()); + dos.writeUTF(list.get(i).getClassName()); + } + } catch (IOException e) { + Slog.w(TAG, "Failure writing last done pre-boot receivers", e); + file.delete(); + } finally { + FileUtils.sync(fos); + if (dos != null) { + try { + dos.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + } + public void systemReady(final Runnable goingCallback) { // In the simulator, startRunning will never have been called, which // normally sets a few crucial variables. Do it here instead. @@ -8747,7 +6111,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED); List<ResolveInfo> ris = null; try { - ris = ActivityThread.getPackageManager().queryIntentReceivers( + ris = AppGlobals.getPackageManager().queryIntentReceivers( intent, null, 0); } catch (RemoteException e) { } @@ -8759,20 +6123,42 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } intent.addFlags(Intent.FLAG_RECEIVER_BOOT_UPGRADE); + + ArrayList<ComponentName> lastDoneReceivers = readLastDonePreBootReceivers(); + + final ArrayList<ComponentName> doneReceivers = new ArrayList<ComponentName>(); + for (int i=0; i<ris.size(); i++) { + ActivityInfo ai = ris.get(i).activityInfo; + ComponentName comp = new ComponentName(ai.packageName, ai.name); + if (lastDoneReceivers.contains(comp)) { + ris.remove(i); + i--; + } + } + for (int i=0; i<ris.size(); i++) { ActivityInfo ai = ris.get(i).activityInfo; - intent.setComponent(new ComponentName(ai.packageName, ai.name)); + ComponentName comp = new ComponentName(ai.packageName, ai.name); + doneReceivers.add(comp); + intent.setComponent(comp); IIntentReceiver finisher = null; if (i == ris.size()-1) { finisher = new IIntentReceiver.Stub() { public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, - boolean sticky) - throws RemoteException { - synchronized (ActivityManagerService.this) { - mDidUpdate = true; - } - systemReady(goingCallback); + boolean sticky) { + // The raw IIntentReceiver interface is called + // with the AM lock held, so redispatch to + // execute our code without the lock. + mHandler.post(new Runnable() { + public void run() { + synchronized (ActivityManagerService.this) { + mDidUpdate = true; + } + writeLastDonePreBootReceivers(doneReceivers); + systemReady(goingCallback); + } + }); } }; } @@ -8809,14 +6195,19 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - if (procsToKill != null) { - synchronized(this) { + synchronized(this) { + if (procsToKill != null) { for (int i=procsToKill.size()-1; i>=0; i--) { ProcessRecord proc = procsToKill.get(i); Slog.i(TAG, "Removing system update proc: " + proc); removeProcessLocked(proc, true); } } + + // Now that we have cleaned up any update processes, we + // are ready to start launching real processes and know that + // we won't trample on them any more. + mProcessesReady = true; } Slog.i(TAG, "System now ready"); @@ -8866,7 +6257,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen synchronized (this) { if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { try { - List apps = ActivityThread.getPackageManager(). + List apps = AppGlobals.getPackageManager(). getPersistentApplications(STOCK_PM_FLAGS); if (apps != null) { int N = apps.size(); @@ -8889,7 +6280,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mBooting = true; try { - if (ActivityThread.getPackageManager().hasSystemUidErrors()) { + if (AppGlobals.getPackageManager().hasSystemUidErrors()) { Message msg = Message.obtain(); msg.what = SHOW_UID_ERROR_MSG; mHandler.sendMessage(msg); @@ -8897,7 +6288,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } catch (RemoteException e) { } - resumeTopActivityLocked(null); + mMainStack.resumeTopActivityLocked(null); } } @@ -8985,12 +6376,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen EventLog.writeEvent(EventLogTags.AM_PROCESS_CRASHED_TOO_MUCH, app.info.processName, app.info.uid); killServicesLocked(app, false); - for (int i=mHistory.size()-1; i>=0; i--) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); + for (int i=mMainStack.mHistory.size()-1; i>=0; i--) { + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); if (r.app == app) { Slog.w(TAG, " Force finishing activity " + r.intent.getComponent().flattenToShortString()); - finishActivityLocked(r, i, Activity.RESULT_CANCELED, null, "crashed"); + r.stack.finishActivityLocked(r, i, Activity.RESULT_CANCELED, null, "crashed"); } } if (!app.persistent) { @@ -9008,28 +6399,28 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return false; } } else { - HistoryRecord r = topRunningActivityLocked(null); + ActivityRecord r = mMainStack.topRunningActivityLocked(null); if (r.app == app) { // If the top running activity is from this crashing // process, then terminate it to avoid getting in a loop. Slog.w(TAG, " Force finishing activity " + r.intent.getComponent().flattenToShortString()); - int index = indexOfTokenLocked(r); - finishActivityLocked(r, index, + 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 // stopped, to avoid a situation where one will get // re-start our crashing activity once it gets resumed again. index--; if (index >= 0) { - r = (HistoryRecord)mHistory.get(index); + r = (ActivityRecord)mMainStack.mHistory.get(index); if (r.state == ActivityState.RESUMED || r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED) { if (!r.isHomeActivity) { Slog.w(TAG, " Force finishing activity " + r.intent.getComponent().flattenToShortString()); - finishActivityLocked(r, index, + r.stack.finishActivityLocked(r, index, Activity.RESULT_CANCELED, null, "crashed"); } } @@ -9041,9 +6432,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (app.services.size() != 0) { // Any services running in the application need to be placed // back in the pending list. - Iterator it = app.services.iterator(); + Iterator<ServiceRecord> it = app.services.iterator(); while (it.hasNext()) { - ServiceRecord sr = (ServiceRecord)it.next(); + ServiceRecord sr = it.next(); sr.crashCount++; } } @@ -9056,7 +6447,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen && (mHomeProcess.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { Iterator it = mHomeProcess.activities.iterator(); while (it.hasNext()) { - HistoryRecord r = (HistoryRecord)it.next(); + ActivityRecord r = (ActivityRecord)it.next(); if (r.isHomeActivity) { Log.i(TAG, "Clearing package preferred activities from " + r.packageName); try { @@ -9086,7 +6477,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // The current broadcast is waiting for this app's receiver // to be finished. Looks like that's not going to happen, so // let the broadcast continue. - logBroadcastReceiverDiscard(r); + logBroadcastReceiverDiscardLocked(r); finishReceiverLocked(r.receiver, r.resultCode, r.resultData, r.resultExtras, r.resultAbort, true); reschedule = true; @@ -9095,7 +6486,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (r != null && r.curApp == app) { if (DEBUG_BROADCAST) Slog.v(TAG, "skip & discard pending app " + r); - logBroadcastReceiverDiscard(r); + logBroadcastReceiverDiscardLocked(r); finishReceiverLocked(r.receiver, r.resultCode, r.resultData, r.resultExtras, r.resultAbort, true); reschedule = true; @@ -9127,6 +6518,161 @@ public final class ActivityManagerService extends ActivityManagerNative implemen crashApplication(r, crashInfo); } + public void handleApplicationStrictModeViolation( + IBinder app, + int violationMask, + StrictMode.ViolationInfo info) { + ProcessRecord r = findAppProcess(app); + + if ((violationMask & StrictMode.PENALTY_DROPBOX) != 0) { + Integer stackFingerprint = info.crashInfo.stackTrace.hashCode(); + boolean logIt = true; + synchronized (mAlreadyLoggedViolatedStacks) { + if (mAlreadyLoggedViolatedStacks.contains(stackFingerprint)) { + logIt = false; + // TODO: sub-sample into EventLog for these, with + // the info.durationMillis? Then we'd get + // the relative pain numbers, without logging all + // the stack traces repeatedly. We'd want to do + // likewise in the client code, which also does + // dup suppression, before the Binder call. + } else { + if (mAlreadyLoggedViolatedStacks.size() >= MAX_DUP_SUPPRESSED_STACKS) { + mAlreadyLoggedViolatedStacks.clear(); + } + mAlreadyLoggedViolatedStacks.add(stackFingerprint); + } + } + if (logIt) { + logStrictModeViolationToDropBox(r, info); + } + } + + if ((violationMask & StrictMode.PENALTY_DIALOG) != 0) { + AppErrorResult result = new AppErrorResult(); + synchronized (this) { + final long origId = Binder.clearCallingIdentity(); + + Message msg = Message.obtain(); + msg.what = SHOW_STRICT_MODE_VIOLATION_MSG; + HashMap<String, Object> data = new HashMap<String, Object>(); + data.put("result", result); + data.put("app", r); + data.put("violationMask", violationMask); + data.put("info", info); + msg.obj = data; + mHandler.sendMessage(msg); + + Binder.restoreCallingIdentity(origId); + } + int res = result.get(); + Slog.w(TAG, "handleApplicationStrictModeViolation; res=" + res); + } + } + + // Depending on the policy in effect, there could be a bunch of + // these in quick succession so we try to batch these together to + // minimize disk writes, number of dropbox entries, and maximize + // compression, by having more fewer, larger records. + private void logStrictModeViolationToDropBox( + ProcessRecord process, + StrictMode.ViolationInfo info) { + if (info == null) { + return; + } + final boolean isSystemApp = process == null || + (process.info.flags & (ApplicationInfo.FLAG_SYSTEM | + ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0; + final String dropboxTag = isSystemApp ? "system_app_strictmode" : "data_app_strictmode"; + final DropBoxManager dbox = (DropBoxManager) + mContext.getSystemService(Context.DROPBOX_SERVICE); + + // Exit early if the dropbox isn't configured to accept this report type. + if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return; + + boolean bufferWasEmpty; + boolean needsFlush; + final StringBuilder sb = isSystemApp ? mStrictModeBuffer : new StringBuilder(1024); + synchronized (sb) { + bufferWasEmpty = sb.length() == 0; + appendDropBoxProcessHeaders(process, sb); + sb.append("Build: ").append(Build.FINGERPRINT).append("\n"); + sb.append("System-App: ").append(isSystemApp).append("\n"); + sb.append("Uptime-Millis: ").append(info.violationUptimeMillis).append("\n"); + if (info.violationNumThisLoop != 0) { + sb.append("Loop-Violation-Number: ").append(info.violationNumThisLoop).append("\n"); + } + if (info != null && info.durationMillis != -1) { + sb.append("Duration-Millis: ").append(info.durationMillis).append("\n"); + } + sb.append("\n"); + if (info.crashInfo != null && info.crashInfo.stackTrace != null) { + sb.append(info.crashInfo.stackTrace); + } + sb.append("\n"); + + // Only buffer up to ~64k. Various logging bits truncate + // things at 128k. + needsFlush = (sb.length() > 64 * 1024); + } + + // Flush immediately if the buffer's grown too large, or this + // is a non-system app. Non-system apps are isolated with a + // different tag & policy and not batched. + // + // Batching is useful during internal testing with + // StrictMode settings turned up high. Without batching, + // thousands of separate files could be created on boot. + if (!isSystemApp || needsFlush) { + new Thread("Error dump: " + dropboxTag) { + @Override + public void run() { + String report; + synchronized (sb) { + report = sb.toString(); + sb.delete(0, sb.length()); + sb.trimToSize(); + } + if (report.length() != 0) { + dbox.addText(dropboxTag, report); + } + } + }.start(); + return; + } + + // System app batching: + if (!bufferWasEmpty) { + // An existing dropbox-writing thread is outstanding, so + // we don't need to start it up. The existing thread will + // catch the buffer appends we just did. + return; + } + + // Worker thread to both batch writes and to avoid blocking the caller on I/O. + // (After this point, we shouldn't access AMS internal data structures.) + new Thread("Error dump: " + dropboxTag) { + @Override + public void run() { + // 5 second sleep to let stacks arrive and be batched together + try { + Thread.sleep(5000); // 5 seconds + } catch (InterruptedException e) {} + + String errorReport; + synchronized (mStrictModeBuffer) { + errorReport = mStrictModeBuffer.toString(); + if (errorReport.length() == 0) { + return; + } + mStrictModeBuffer.delete(0, mStrictModeBuffer.length()); + mStrictModeBuffer.trimToSize(); + } + dbox.addText(dropboxTag, errorReport); + } + }.start(); + } + /** * Used by {@link Log} via {@link com.android.internal.os.RuntimeInit} to report serious errors. * @param app object of the crashing app, null for the system server @@ -9180,6 +6726,54 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } /** + * Utility function for addErrorToDropBox and handleStrictModeViolation's logging + * to append various headers to the dropbox log text. + */ + private void appendDropBoxProcessHeaders(ProcessRecord process, StringBuilder sb) { + // Note: ProcessRecord 'process' is guarded by the service + // instance. (notably process.pkgList, which could otherwise change + // concurrently during execution of this method) + synchronized (this) { + if (process == null || process.pid == MY_PID) { + sb.append("Process: system_server\n"); + } else { + sb.append("Process: ").append(process.processName).append("\n"); + } + if (process == null) { + return; + } + int flags = process.info.flags; + IPackageManager pm = AppGlobals.getPackageManager(); + sb.append("Flags: 0x").append(Integer.toString(flags, 16)).append("\n"); + for (String pkg : process.pkgList) { + sb.append("Package: ").append(pkg); + try { + PackageInfo pi = pm.getPackageInfo(pkg, 0); + if (pi != null) { + sb.append(" v").append(pi.versionCode); + if (pi.versionName != null) { + sb.append(" (").append(pi.versionName).append(")"); + } + } + } catch (RemoteException e) { + Slog.e(TAG, "Error getting package info: " + pkg, e); + } + sb.append("\n"); + } + } + } + + private static String processClass(ProcessRecord process) { + if (process == null || process.pid == MY_PID) { + return "system_server"; + } else if ((process.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + return "system_app"; + } else { + return "data_app"; + } + } + + /** * Write a description of an error (crash, WTF, ANR) to the drop box. * @param eventType to include in the drop box tag ("crash", "wtf", etc.) * @param process which caused the error, null means the system server @@ -9191,22 +6785,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen * @param crashInfo giving an application stack trace, null if absent */ public void addErrorToDropBox(String eventType, - ProcessRecord process, HistoryRecord activity, HistoryRecord parent, String subject, + ProcessRecord process, ActivityRecord activity, ActivityRecord parent, String subject, final String report, final File logFile, final ApplicationErrorReport.CrashInfo crashInfo) { // NOTE -- this must never acquire the ActivityManagerService lock, // otherwise the watchdog may be prevented from resetting the system. - String prefix; - if (process == null || process.pid == MY_PID) { - prefix = "system_server_"; - } else if ((process.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { - prefix = "system_app_"; - } else { - prefix = "data_app_"; - } - - final String dropboxTag = prefix + eventType; + final String dropboxTag = processClass(process) + "_" + eventType; final DropBoxManager dbox = (DropBoxManager) mContext.getSystemService(Context.DROPBOX_SERVICE); @@ -9214,31 +6799,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return; final StringBuilder sb = new StringBuilder(1024); - if (process == null || process.pid == MY_PID) { - sb.append("Process: system_server\n"); - } else { - sb.append("Process: ").append(process.processName).append("\n"); - } - if (process != null) { - int flags = process.info.flags; - IPackageManager pm = ActivityThread.getPackageManager(); - sb.append("Flags: 0x").append(Integer.toString(flags, 16)).append("\n"); - for (String pkg : process.pkgList) { - sb.append("Package: ").append(pkg); - try { - PackageInfo pi = pm.getPackageInfo(pkg, 0); - if (pi != null) { - sb.append(" v").append(pi.versionCode); - if (pi.versionName != null) { - sb.append(" (").append(pi.versionName).append(")"); - } - } - } catch (RemoteException e) { - Slog.e(TAG, "Error getting package info: " + pkg, e); - } - sb.append("\n"); - } - } + appendDropBoxProcessHeaders(process, sb); if (activity != null) { sb.append("Activity: ").append(activity.shortComponentName).append("\n"); } @@ -9499,6 +7060,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen new ActivityManager.RunningAppProcessInfo(app.processName, app.pid, app.getPackageList()); currApp.uid = app.info.uid; + if (mHeavyWeightProcess == app) { + currApp.flags |= ActivityManager.RunningAppProcessInfo.FLAG_CANT_SAVE_STATE; + } + if (app.persistent) { + currApp.flags |= ActivityManager.RunningAppProcessInfo.FLAG_PERSISTENT; + } int adj = app.curAdj; if (adj >= EMPTY_APP_ADJ) { currApp.importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_EMPTY; @@ -9510,6 +7077,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen currApp.lru = 0; } else if (adj >= SECONDARY_SERVER_ADJ) { currApp.importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE; + } else if (adj >= HEAVY_WEIGHT_APP_ADJ) { + currApp.importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE; + } else if (adj >= PERCEPTIBLE_APP_ADJ) { + currApp.importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE; } else if (adj >= VISIBLE_APP_ADJ) { currApp.importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; } else { @@ -9518,8 +7089,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen currApp.importanceReasonCode = app.adjTypeCode; if (app.adjSource instanceof ProcessRecord) { currApp.importanceReasonPid = ((ProcessRecord)app.adjSource).pid; - } else if (app.adjSource instanceof HistoryRecord) { - HistoryRecord r = (HistoryRecord)app.adjSource; + } else if (app.adjSource instanceof ActivityRecord) { + ActivityRecord r = (ActivityRecord)app.adjSource; if (r.app != null) currApp.importanceReasonPid = r.app.pid; } if (app.adjTarget instanceof ComponentName) { @@ -9549,7 +7120,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } } - IPackageManager pm = ActivityThread.getPackageManager(); + IPackageManager pm = AppGlobals.getPackageManager(); for (String pkg : extList) { try { ApplicationInfo info = pm.getApplicationInfo(pkg, 0); @@ -9590,12 +7161,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen pw.println("Activity manager dump options:"); pw.println(" [-a] [-h] [cmd] ..."); pw.println(" cmd may be one of:"); - pw.println(" activities: activity stack state"); - pw.println(" broadcasts: broadcast state"); - pw.println(" intents: pending intent state"); - pw.println(" processes: process state"); - pw.println(" providers: content provider state"); - pw.println(" services: service state"); + pw.println(" a[ctivities]: activity stack state"); + pw.println(" b[roadcasts]: broadcast state"); + pw.println(" i[ntents]: pending intent state"); + pw.println(" p[rocesses]: process state"); + pw.println(" o[om]: out of memory management"); + pw.println(" prov[iders]: content provider state"); + pw.println(" s[ervices]: service state"); pw.println(" service [name]: service client-side state"); return; } else { @@ -9627,13 +7199,18 @@ public final class ActivityManagerService extends ActivityManagerNative implemen dumpProcessesLocked(fd, pw, args, opti, true); } return; + } else if ("oom".equals(cmd) || "o".equals(cmd)) { + synchronized (this) { + dumpOomLocked(fd, pw, args, opti, true); + } + return; } else if ("providers".equals(cmd) || "prov".equals(cmd)) { synchronized (this) { dumpProvidersLocked(fd, pw, args, opti, true); } return; } else if ("service".equals(cmd)) { - dumpService(fd, pw, args, opti, true); + dumpService(fd, pw, args, opti, dumpAll); return; } else if ("services".equals(cmd) || "s".equals(cmd)) { synchronized (this) { @@ -9698,31 +7275,31 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (needHeader) { pw.println(" Activity stack:"); } - dumpHistoryList(pw, mHistory, " ", "Hist", true); + dumpHistoryList(pw, mMainStack.mHistory, " ", "Hist", true); pw.println(" "); pw.println(" Running activities (most recent first):"); - dumpHistoryList(pw, mLRUActivities, " ", "Run", false); - if (mWaitingVisibleActivities.size() > 0) { + dumpHistoryList(pw, mMainStack.mLRUActivities, " ", "Run", false); + if (mMainStack.mWaitingVisibleActivities.size() > 0) { pw.println(" "); pw.println(" Activities waiting for another to become visible:"); - dumpHistoryList(pw, mWaitingVisibleActivities, " ", "Wait", false); + dumpHistoryList(pw, mMainStack.mWaitingVisibleActivities, " ", "Wait", false); } - if (mStoppingActivities.size() > 0) { + if (mMainStack.mStoppingActivities.size() > 0) { pw.println(" "); pw.println(" Activities waiting to stop:"); - dumpHistoryList(pw, mStoppingActivities, " ", "Stop", false); + dumpHistoryList(pw, mMainStack.mStoppingActivities, " ", "Stop", false); } - if (mFinishingActivities.size() > 0) { + if (mMainStack.mFinishingActivities.size() > 0) { pw.println(" "); pw.println(" Activities waiting to finish:"); - dumpHistoryList(pw, mFinishingActivities, " ", "Fin", false); + dumpHistoryList(pw, mMainStack.mFinishingActivities, " ", "Fin", false); } pw.println(" "); - pw.println(" mPausingActivity: " + mPausingActivity); - pw.println(" mResumedActivity: " + mResumedActivity); + pw.println(" mPausingActivity: " + mMainStack.mPausingActivity); + pw.println(" mResumedActivity: " + mMainStack.mResumedActivity); pw.println(" mFocusedActivity: " + mFocusedActivity); - pw.println(" mLastPausedActivity: " + mLastPausedActivity); + pw.println(" mLastPausedActivity: " + mMainStack.mLastPausedActivity); if (dumpAll && mRecentTasks.size() > 0) { pw.println(" "); @@ -9742,7 +7319,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return true; } - + boolean dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll) { boolean needSep = false; @@ -9772,8 +7349,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (needSep) pw.println(" "); needSep = true; pw.println(" Running processes (most recent first):"); - dumpProcessList(pw, this, mLruProcesses, " ", - "App ", "PERS", true); + dumpProcessOomList(pw, this, mLruProcesses, " ", + "Proc", "PERS", false); needSep = true; } @@ -9804,7 +7381,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen needSep = true; pw.println(" Persisent processes that are starting:"); dumpProcessList(pw, this, mPersistentStartingProcesses, " ", - "Starting Norm", "Restarting PERS", false); + "Starting Norm", "Restarting PERS"); } if (mStartingProcesses.size() > 0) { @@ -9812,7 +7389,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen needSep = true; pw.println(" Processes that are starting:"); dumpProcessList(pw, this, mStartingProcesses, " ", - "Starting Norm", "Starting PERS", false); + "Starting Norm", "Starting PERS"); } if (mRemovedProcesses.size() > 0) { @@ -9820,7 +7397,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen needSep = true; pw.println(" Processes that are being removed:"); dumpProcessList(pw, this, mRemovedProcesses, " ", - "Removed Norm", "Removed PERS", false); + "Removed Norm", "Removed PERS"); } if (mProcessesOnHold.size() > 0) { @@ -9828,26 +7405,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen needSep = true; pw.println(" Processes that are on old until the system is ready:"); dumpProcessList(pw, this, mProcessesOnHold, " ", - "OnHold Norm", "OnHold PERS", false); + "OnHold Norm", "OnHold PERS"); } - if (mProcessesToGc.size() > 0) { - if (needSep) pw.println(" "); - needSep = true; - pw.println(" Processes that are waiting to GC:"); - long now = SystemClock.uptimeMillis(); - for (int i=0; i<mProcessesToGc.size(); i++) { - ProcessRecord proc = mProcessesToGc.get(i); - pw.print(" Process "); pw.println(proc); - pw.print(" lowMem="); pw.print(proc.reportLowMemory); - pw.print(", last gced="); - pw.print(now-proc.lastRequestedGc); - pw.print(" ms ago, last lowMem="); - pw.print(now-proc.lastLowMemory); - pw.println(" ms ago"); - - } - } + needSep = dumpProcessesToGc(fd, pw, args, opti, needSep, dumpAll); if (mProcessCrashTimes.getMap().size() > 0) { if (needSep) pw.println(" "); @@ -9887,8 +7448,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen pw.println(" "); pw.println(" mHomeProcess: " + mHomeProcess); + if (mHeavyWeightProcess != null) { + pw.println(" mHeavyWeightProcess: " + mHeavyWeightProcess); + } pw.println(" mConfiguration: " + mConfiguration); - pw.println(" mConfigWillChange: " + mConfigWillChange); + pw.println(" mConfigWillChange: " + mMainStack.mConfigWillChange); pw.println(" mSleeping=" + mSleeping + " mShuttingDown=" + mShuttingDown); if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient || mOrigWaitForDebugger) { @@ -9903,18 +7467,94 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (dumpAll) { pw.println(" Total persistent processes: " + numPers); pw.println(" mStartRunning=" + mStartRunning - + " mSystemReady=" + mSystemReady - + " mBooting=" + mBooting + + " mProcessesReady=" + mProcessesReady + + " mSystemReady=" + mSystemReady); + pw.println(" mBooting=" + mBooting + " mBooted=" + mBooted + " mFactoryTest=" + mFactoryTest); - pw.println(" mGoingToSleep=" + mGoingToSleep); - pw.println(" mLaunchingActivity=" + mLaunchingActivity); + pw.print(" mLastPowerCheckRealtime="); + TimeUtils.formatDuration(mLastPowerCheckRealtime, pw); + pw.println(""); + pw.print(" mLastPowerCheckUptime="); + TimeUtils.formatDuration(mLastPowerCheckUptime, pw); + pw.println(""); + pw.println(" mGoingToSleep=" + mMainStack.mGoingToSleep); + pw.println(" mLaunchingActivity=" + mMainStack.mLaunchingActivity); pw.println(" mAdjSeq=" + mAdjSeq + " mLruSeq=" + mLruSeq); } return true; } + boolean dumpProcessesToGc(FileDescriptor fd, PrintWriter pw, String[] args, + int opti, boolean needSep, boolean dumpAll) { + if (mProcessesToGc.size() > 0) { + if (needSep) pw.println(" "); + needSep = true; + pw.println(" Processes that are waiting to GC:"); + long now = SystemClock.uptimeMillis(); + for (int i=0; i<mProcessesToGc.size(); i++) { + ProcessRecord proc = mProcessesToGc.get(i); + pw.print(" Process "); pw.println(proc); + pw.print(" lowMem="); pw.print(proc.reportLowMemory); + pw.print(", last gced="); + pw.print(now-proc.lastRequestedGc); + pw.print(" ms ago, last lowMem="); + pw.print(now-proc.lastLowMemory); + pw.println(" ms ago"); + + } + } + return needSep; + } + + boolean dumpOomLocked(FileDescriptor fd, PrintWriter pw, String[] args, + int opti, boolean dumpAll) { + boolean needSep = false; + + if (mLruProcesses.size() > 0) { + ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>(mLruProcesses); + + Comparator<ProcessRecord> comparator = new Comparator<ProcessRecord>() { + @Override + public int compare(ProcessRecord object1, ProcessRecord object2) { + if (object1.setAdj != object2.setAdj) { + return object1.setAdj > object2.setAdj ? -1 : 1; + } + if (object1.setSchedGroup != object2.setSchedGroup) { + return object1.setSchedGroup > object2.setSchedGroup ? -1 : 1; + } + if (object1.keeping != object2.keeping) { + return object1.keeping ? -1 : 1; + } + if (object1.pid != object2.pid) { + return object1.pid > object2.pid ? -1 : 1; + } + return 0; + } + }; + + Collections.sort(procs, comparator); + + if (needSep) pw.println(" "); + needSep = true; + pw.println(" Process OOM control:"); + dumpProcessOomList(pw, this, procs, " ", + "Proc", "PERS", true); + needSep = true; + } + + needSep = dumpProcessesToGc(fd, pw, args, opti, needSep, dumpAll); + + pw.println(" "); + pw.println(" mHomeProcess: " + mHomeProcess); + if (mHeavyWeightProcess != null) { + pw.println(" mHeavyWeightProcess: " + mHeavyWeightProcess); + } + + return true; + } + /** * There are three ways to call this: * - no service specified: dump all the services @@ -9936,20 +7576,28 @@ public final class ActivityManagerService extends ActivityManagerNative implemen componentNameString = args[opti]; opti++; ComponentName componentName = ComponentName.unflattenFromString(componentNameString); - r = componentName != null ? mServices.get(componentName) : null; + synchronized (this) { + r = componentName != null ? mServices.get(componentName) : null; + } newArgs = new String[args.length - opti]; if (args.length > 2) System.arraycopy(args, opti, newArgs, 0, args.length - opti); } if (r != null) { - dumpService(fd, pw, r, newArgs); + dumpService(fd, pw, r, newArgs, dumpAll); } else { - for (ServiceRecord r1 : mServices.values()) { - if (componentNameString == null - || r1.name.flattenToString().contains(componentNameString)) { - dumpService(fd, pw, r1, newArgs); + ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); + synchronized (this) { + for (ServiceRecord r1 : mServices.values()) { + if (componentNameString == null + || r1.name.flattenToString().contains(componentNameString)) { + services.add(r1); + } } } + for (int i=0; i<services.size(); i++) { + dumpService(fd, pw, services.get(i), newArgs, dumpAll); + } } } @@ -9957,8 +7605,16 @@ public final class ActivityManagerService extends ActivityManagerNative implemen * 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) { + 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(""); + } if (r.app != null && r.app.thread != null) { try { // flush anything that is already in the PrintWriter since the thread is going @@ -9966,6 +7622,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen pw.flush(); r.app.thread.dumpService(fd, r, args); pw.print("\n"); + pw.flush(); } catch (RemoteException e) { pw.println("got a RemoteException while dumping the service"); } @@ -9990,7 +7647,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen pw.println(" "); pw.println("Receiver Resolver Table:"); - mReceiverResolver.dump(pw, null, " ", null); + mReceiverResolver.dump(pw, null, " ", null, false); needSep = true; } @@ -10126,12 +7783,14 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (mServiceConnections.size() > 0) { if (needSep) pw.println(" "); pw.println(" Connection bindings to services:"); - Iterator<ConnectionRecord> it + Iterator<ArrayList<ConnectionRecord>> it = mServiceConnections.values().iterator(); while (it.hasNext()) { - ConnectionRecord r = it.next(); - pw.print(" * "); pw.println(r); - r.dump(pw, " "); + ArrayList<ConnectionRecord> r = it.next(); + for (int i=0; i<r.size(); i++) { + pw.print(" * "); pw.println(r.get(i)); + r.get(i).dump(pw, " "); + } } needSep = true; } @@ -10148,10 +7807,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (mProvidersByClass.size() > 0) { if (needSep) pw.println(" "); pw.println(" Published content providers (by class):"); - Iterator it = mProvidersByClass.entrySet().iterator(); + Iterator<Map.Entry<String, ContentProviderRecord>> it + = mProvidersByClass.entrySet().iterator(); while (it.hasNext()) { - Map.Entry e = (Map.Entry)it.next(); - ContentProviderRecord r = (ContentProviderRecord)e.getValue(); + Map.Entry<String, ContentProviderRecord> e = it.next(); + ContentProviderRecord r = e.getValue(); pw.print(" * "); pw.println(r); r.dump(pw, " "); } @@ -10161,10 +7821,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (mProvidersByName.size() > 0) { pw.println(" "); pw.println(" Authority to provider mappings:"); - Iterator it = mProvidersByName.entrySet().iterator(); + Iterator<Map.Entry<String, ContentProviderRecord>> it + = mProvidersByName.entrySet().iterator(); while (it.hasNext()) { - Map.Entry e = (Map.Entry)it.next(); - ContentProviderRecord r = (ContentProviderRecord)e.getValue(); + Map.Entry<String, ContentProviderRecord> e = it.next(); + ContentProviderRecord r = e.getValue(); pw.print(" "); pw.print(e.getKey()); pw.print(": "); pw.println(r); } @@ -10231,7 +7892,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen String prefix, String label, boolean complete) { TaskRecord lastTask = null; for (int i=list.size()-1; i>=0; i--) { - HistoryRecord r = (HistoryRecord)list.get(i); + ActivityRecord r = (ActivityRecord)list.get(i); final boolean full = complete || !r.inHistory; if (lastTask != r.task) { lastTask = r.task; @@ -10261,62 +7922,14 @@ public final class ActivityManagerService extends ActivityManagerNative implemen private static final int dumpProcessList(PrintWriter pw, ActivityManagerService service, List list, - String prefix, String normalLabel, String persistentLabel, - boolean inclOomAdj) { + String prefix, String normalLabel, String persistentLabel) { int numPers = 0; - for (int i=list.size()-1; i>=0; i--) { + final int N = list.size()-1; + for (int i=N; i>=0; i--) { ProcessRecord r = (ProcessRecord)list.get(i); - if (false) { - pw.println(prefix + (r.persistent ? persistentLabel : normalLabel) - + " #" + i + ":"); - r.dump(pw, prefix + " "); - } else if (inclOomAdj) { - String oomAdj; - if (r.setAdj >= EMPTY_APP_ADJ) { - oomAdj = buildOomTag("empty", null, r.setAdj, EMPTY_APP_ADJ); - } else if (r.setAdj >= HIDDEN_APP_MIN_ADJ) { - oomAdj = buildOomTag("bak", " ", r.setAdj, HIDDEN_APP_MIN_ADJ); - } else if (r.setAdj >= HOME_APP_ADJ) { - oomAdj = buildOomTag("home ", null, r.setAdj, HOME_APP_ADJ); - } else if (r.setAdj >= SECONDARY_SERVER_ADJ) { - oomAdj = buildOomTag("svc", " ", r.setAdj, SECONDARY_SERVER_ADJ); - } else if (r.setAdj >= BACKUP_APP_ADJ) { - oomAdj = buildOomTag("bckup", null, r.setAdj, BACKUP_APP_ADJ); - } else if (r.setAdj >= VISIBLE_APP_ADJ) { - oomAdj = buildOomTag("vis ", null, r.setAdj, VISIBLE_APP_ADJ); - } else if (r.setAdj >= FOREGROUND_APP_ADJ) { - oomAdj = buildOomTag("fore ", null, r.setAdj, FOREGROUND_APP_ADJ); - } else if (r.setAdj >= CORE_SERVER_ADJ) { - oomAdj = buildOomTag("core ", null, r.setAdj, CORE_SERVER_ADJ); - } else if (r.setAdj >= SYSTEM_ADJ) { - oomAdj = buildOomTag("sys ", null, r.setAdj, SYSTEM_ADJ); - } else { - oomAdj = Integer.toString(r.setAdj); - } - String schedGroup; - switch (r.setSchedGroup) { - case Process.THREAD_GROUP_BG_NONINTERACTIVE: - schedGroup = "B"; - break; - case Process.THREAD_GROUP_DEFAULT: - schedGroup = "F"; - break; - default: - schedGroup = Integer.toString(r.setSchedGroup); - break; - } - pw.println(String.format("%s%s #%2d: adj=%s/%s %s (%s)", - prefix, (r.persistent ? persistentLabel : normalLabel), - i, oomAdj, schedGroup, r.toShortString(), r.adjType)); - if (r.adjSource != null || r.adjTarget != null) { - pw.println(prefix + " " + r.adjTarget - + "<=" + r.adjSource); - } - } else { - pw.println(String.format("%s%s #%2d: %s", - prefix, (r.persistent ? persistentLabel : normalLabel), - i, r.toString())); - } + pw.println(String.format("%s%s #%2d: %s", + prefix, (r.persistent ? persistentLabel : normalLabel), + i, r.toString())); if (r.persistent) { numPers++; } @@ -10324,6 +7937,132 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return numPers; } + private static final void dumpProcessOomList(PrintWriter pw, + ActivityManagerService service, List<ProcessRecord> list, + String prefix, String normalLabel, String persistentLabel, + boolean inclDetails) { + + final long curRealtime = SystemClock.elapsedRealtime(); + final long realtimeSince = curRealtime - service.mLastPowerCheckRealtime; + final long curUptime = SystemClock.uptimeMillis(); + final long uptimeSince = curUptime - service.mLastPowerCheckUptime; + + final int N = list.size()-1; + for (int i=N; i>=0; i--) { + ProcessRecord r = list.get(i); + String oomAdj; + if (r.setAdj >= EMPTY_APP_ADJ) { + oomAdj = buildOomTag("empty", null, r.setAdj, EMPTY_APP_ADJ); + } else if (r.setAdj >= HIDDEN_APP_MIN_ADJ) { + oomAdj = buildOomTag("bak", " ", r.setAdj, HIDDEN_APP_MIN_ADJ); + } else if (r.setAdj >= HOME_APP_ADJ) { + oomAdj = buildOomTag("home ", null, r.setAdj, HOME_APP_ADJ); + } else if (r.setAdj >= SECONDARY_SERVER_ADJ) { + oomAdj = buildOomTag("svc", " ", r.setAdj, SECONDARY_SERVER_ADJ); + } else if (r.setAdj >= BACKUP_APP_ADJ) { + oomAdj = buildOomTag("bckup", null, r.setAdj, BACKUP_APP_ADJ); + } else if (r.setAdj >= HEAVY_WEIGHT_APP_ADJ) { + oomAdj = buildOomTag("hvy ", null, r.setAdj, HEAVY_WEIGHT_APP_ADJ); + } else if (r.setAdj >= PERCEPTIBLE_APP_ADJ) { + oomAdj = buildOomTag("prcp ", null, r.setAdj, PERCEPTIBLE_APP_ADJ); + } else if (r.setAdj >= VISIBLE_APP_ADJ) { + oomAdj = buildOomTag("vis ", null, r.setAdj, VISIBLE_APP_ADJ); + } else if (r.setAdj >= FOREGROUND_APP_ADJ) { + oomAdj = buildOomTag("fore ", null, r.setAdj, FOREGROUND_APP_ADJ); + } else if (r.setAdj >= CORE_SERVER_ADJ) { + oomAdj = buildOomTag("core ", null, r.setAdj, CORE_SERVER_ADJ); + } else if (r.setAdj >= SYSTEM_ADJ) { + oomAdj = buildOomTag("sys ", null, r.setAdj, SYSTEM_ADJ); + } else { + oomAdj = Integer.toString(r.setAdj); + } + String schedGroup; + switch (r.setSchedGroup) { + case Process.THREAD_GROUP_BG_NONINTERACTIVE: + schedGroup = "B"; + break; + case Process.THREAD_GROUP_DEFAULT: + schedGroup = "F"; + break; + default: + schedGroup = Integer.toString(r.setSchedGroup); + break; + } + pw.println(String.format("%s%s #%2d: adj=%s/%s %s (%s)", + prefix, (r.persistent ? persistentLabel : normalLabel), + N-i, oomAdj, schedGroup, r.toShortString(), r.adjType)); + if (r.adjSource != null || r.adjTarget != null) { + pw.print(prefix); + pw.print(" "); + if (r.adjTarget instanceof ComponentName) { + pw.print(((ComponentName)r.adjTarget).flattenToShortString()); + } else if (r.adjTarget != null) { + pw.print(r.adjTarget.toString()); + } else { + pw.print("{null}"); + } + pw.print("<="); + if (r.adjSource instanceof ProcessRecord) { + pw.print("Proc{"); + pw.print(((ProcessRecord)r.adjSource).toShortString()); + pw.println("}"); + } else if (r.adjSource != null) { + pw.println(r.adjSource.toString()); + } else { + pw.println("{null}"); + } + } + if (inclDetails) { + pw.print(prefix); + pw.print(" "); + pw.print("oom: max="); pw.print(r.maxAdj); + pw.print(" hidden="); pw.print(r.hiddenAdj); + pw.print(" curRaw="); pw.print(r.curRawAdj); + pw.print(" setRaw="); pw.print(r.setRawAdj); + pw.print(" cur="); pw.print(r.curAdj); + pw.print(" set="); pw.println(r.setAdj); + pw.print(prefix); + pw.print(" "); + pw.print("keeping="); pw.print(r.keeping); + pw.print(" hidden="); pw.print(r.hidden); + pw.print(" empty="); pw.println(r.empty); + + if (!r.keeping) { + if (r.lastWakeTime != 0) { + long wtime; + BatteryStatsImpl stats = service.mBatteryStatsService.getActiveStatistics(); + synchronized (stats) { + wtime = stats.getProcessWakeTime(r.info.uid, + r.pid, curRealtime); + } + long timeUsed = wtime - r.lastWakeTime; + pw.print(prefix); + pw.print(" "); + pw.print("keep awake over "); + TimeUtils.formatDuration(realtimeSince, pw); + pw.print(" used "); + TimeUtils.formatDuration(timeUsed, pw); + pw.print(" ("); + pw.print((timeUsed*100)/realtimeSince); + pw.println("%)"); + } + if (r.lastCpuTime != 0) { + long timeUsed = r.curCpuTime - r.lastCpuTime; + pw.print(prefix); + pw.print(" "); + pw.print("run cpu over "); + TimeUtils.formatDuration(uptimeSince, pw); + pw.print(" used "); + TimeUtils.formatDuration(timeUsed, pw); + pw.print(" ("); + pw.print((timeUsed*100)/uptimeSince); + pw.println("%)"); + } + } + } + } + } + static final void dumpApplicationMemoryUsage(FileDescriptor fd, PrintWriter pw, List list, String prefix, String[] args) { final boolean isCheckinRequest = scanArgs(args, "--checkin"); @@ -10374,22 +8113,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return false; } - private final int indexOfTokenLocked(IBinder token) { - int count = mHistory.size(); - - // convert the token to an entry in the history. - int index = -1; - for (int i=count-1; i>=0; i--) { - Object o = mHistory.get(i); - if (o == token) { - index = i; - break; - } - } - - return index; - } - private final void killServicesLocked(ProcessRecord app, boolean allowRestart) { // Report disconnected services. @@ -10397,22 +8120,25 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // XXX we are letting the client link to the service for // death notifications. if (app.services.size() > 0) { - Iterator it = app.services.iterator(); + Iterator<ServiceRecord> it = app.services.iterator(); while (it.hasNext()) { - ServiceRecord r = (ServiceRecord)it.next(); + ServiceRecord r = it.next(); if (r.connections.size() > 0) { - Iterator<ConnectionRecord> jt + Iterator<ArrayList<ConnectionRecord>> jt = r.connections.values().iterator(); while (jt.hasNext()) { - ConnectionRecord c = jt.next(); - if (c.binding.client != app) { - try { - //c.conn.connected(r.className, null); - } catch (Exception e) { - // todo: this should be asynchronous! - Slog.w(TAG, "Exception thrown disconnected servce " - + r.shortName - + " from app " + app.processName, e); + ArrayList<ConnectionRecord> cl = jt.next(); + for (int i=0; i<cl.size(); i++) { + ConnectionRecord c = cl.get(i); + if (c.binding.client != app) { + try { + //c.conn.connected(r.className, null); + } catch (Exception e) { + // todo: this should be asynchronous! + Slog.w(TAG, "Exception thrown disconnected servce " + + r.shortName + + " from app " + app.processName, e); + } } } } @@ -10434,15 +8160,17 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (app.services.size() != 0) { // Any services running in the application need to be placed // back in the pending list. - Iterator it = app.services.iterator(); + Iterator<ServiceRecord> it = app.services.iterator(); while (it.hasNext()) { - ServiceRecord sr = (ServiceRecord)it.next(); + ServiceRecord sr = it.next(); synchronized (sr.stats.getBatteryStats()) { sr.stats.stopLaunchedLocked(); } sr.app = null; sr.executeNesting = 0; - mStoppingServices.remove(sr); + if (mStoppingServices.remove(sr)) { + if (DEBUG_SERVICE) Slog.v(TAG, "killServices remove stopping " + sr); + } boolean hasClients = sr.bindings.size() > 0; if (hasClients) { @@ -10495,6 +8223,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ServiceRecord sr = mStoppingServices.get(i); if (sr.app == app) { mStoppingServices.remove(i); + if (DEBUG_SERVICE) Slog.v(TAG, "killServices remove stopping " + sr); } } @@ -10575,9 +8304,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Remove published content providers. if (!app.pubProviders.isEmpty()) { - Iterator it = app.pubProviders.values().iterator(); + Iterator<ContentProviderRecord> it = app.pubProviders.values().iterator(); while (it.hasNext()) { - ContentProviderRecord cpr = (ContentProviderRecord)it.next(); + ContentProviderRecord cpr = it.next(); cpr.provider = null; cpr.app = null; @@ -10669,6 +8398,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (DEBUG_PROCESSES) Slog.v(TAG, "Removing non-persistent process during cleanup: " + app); mProcessNames.remove(app.processName, app.info.uid); + if (mHeavyWeightProcess == app) { + mHeavyWeightProcess = null; + mHandler.sendEmptyMessage(CANCEL_HEAVY_NOTIFICATION_MSG); + } } else if (!app.removed) { // This app is persistent, so we need to keep its record around. // If it is not already on the pending app list, add it there @@ -10681,6 +8414,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen restart = true; } } + if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG, + "Clean-up removing on hold: " + app); mProcessesOnHold.remove(app); if (app == mHomeProcess) { @@ -10710,8 +8445,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen int NL = mLaunchingProviders.size(); boolean restart = false; for (int i=0; i<NL; i++) { - ContentProviderRecord cpr = (ContentProviderRecord) - mLaunchingProviders.get(i); + ContentProviderRecord cpr = mLaunchingProviders.get(i); if (cpr.launchingApp == app) { if (!alwaysBad && !app.bad) { restart = true; @@ -10755,11 +8489,15 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (r.app != null && r.app.persistent) { info.flags |= ActivityManager.RunningServiceInfo.FLAG_PERSISTENT_PROCESS; } - for (ConnectionRecord conn : r.connections.values()) { - if (conn.clientLabel != 0) { - info.clientPackage = conn.binding.client.info.packageName; - info.clientLabel = conn.clientLabel; - break; + + for (ArrayList<ConnectionRecord> connl : r.connections.values()) { + for (int i=0; i<connl.size(); i++) { + ConnectionRecord conn = connl.get(i); + if (conn.clientLabel != 0) { + info.clientPackage = conn.binding.client.info.packageName; + info.clientLabel = conn.clientLabel; + return info; + } } } return info; @@ -10794,9 +8532,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen synchronized (this) { ServiceRecord r = mServices.get(name); if (r != null) { - for (ConnectionRecord conn : r.connections.values()) { - if (conn.clientIntent != null) { - return conn.clientIntent; + for (ArrayList<ConnectionRecord> conn : r.connections.values()) { + for (int i=0; i<conn.size(); i++) { + if (conn.get(i).clientIntent != null) { + return conn.get(i).clientIntent; + } } } } @@ -10834,7 +8574,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (r == null) { try { ResolveInfo rInfo = - ActivityThread.getPackageManager().resolveService( + AppGlobals.getPackageManager().resolveService( service, resolvedType, 0); ServiceInfo sInfo = rInfo != null ? rInfo.serviceInfo : null; @@ -10891,7 +8631,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (r == null) { try { ResolveInfo rInfo = - ActivityThread.getPackageManager().resolveService( + AppGlobals.getPackageManager().resolveService( service, resolvedType, STOCK_PM_FLAGS); ServiceInfo sInfo = rInfo != null ? rInfo.serviceInfo : null; @@ -10949,7 +8689,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return null; } - private final void bumpServiceExecutingLocked(ServiceRecord r) { + private final void bumpServiceExecutingLocked(ServiceRecord r, String why) { + if (DEBUG_SERVICE) Log.v(TAG, ">>> EXECUTING " + + why + " of " + r + " in app " + r.app); + else if (DEBUG_SERVICE_EXECUTING) Log.v(TAG, ">>> EXECUTING " + + why + " of " + r.shortName); long now = SystemClock.uptimeMillis(); if (r.executeNesting == 0 && r.app != null) { if (r.app.executingServices.size() == 0) { @@ -10970,19 +8714,24 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return; } - int i = 0; - while (i < N) { + while (r.pendingStarts.size() > 0) { try { - ServiceRecord.StartItem si = r.pendingStarts.get(i); - if (DEBUG_SERVICE) Slog.v(TAG, "Sending arguments to service: " - + r.name + " " + r.intent + " args=" + si.intent); - if (si.intent == null && N > 1) { + 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. - i++; continue; } - bumpServiceExecutingLocked(r); + si.deliveredTime = SystemClock.uptimeMillis(); + r.deliveredStarts.add(si); + si.deliveryCount++; + if (si.targetPermissionUid >= 0) { + grantUriPermissionUncheckedFromIntentLocked(si.targetPermissionUid, + r.packageName, si.intent, si.getUriPermissionsLocked()); + } + bumpServiceExecutingLocked(r, "start"); if (!oomAdjusted) { oomAdjusted = true; updateOomAdjLocked(r.app); @@ -10995,27 +8744,16 @@ public final class ActivityManagerService extends ActivityManagerNative implemen flags |= Service.START_FLAG_REDELIVERY; } r.app.thread.scheduleServiceArgs(r, si.id, flags, si.intent); - si.deliveredTime = SystemClock.uptimeMillis(); - r.deliveredStarts.add(si); - si.deliveryCount++; - i++; } catch (RemoteException e) { // Remote process gone... we'll let the normal cleanup take // care of this. + if (DEBUG_SERVICE) Slog.v(TAG, "Crashed while scheduling start: " + r); break; } catch (Exception e) { Slog.w(TAG, "Unexpected exception", e); break; } } - if (i == N) { - r.pendingStarts.clear(); - } else { - while (i > 0) { - i--; - r.pendingStarts.remove(i); - } - } } private final boolean requestServiceBindingLocked(ServiceRecord r, @@ -11026,9 +8764,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } if ((!i.requested || rebind) && i.apps.size() > 0) { try { - bumpServiceExecutingLocked(r); - if (DEBUG_SERVICE) Slog.v(TAG, "Connecting binding " + i - + ": shouldUnbind=" + i.hasBound); + bumpServiceExecutingLocked(r, "bind"); r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind); if (!rebind) { i.requested = true; @@ -11036,6 +8772,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen i.hasBound = true; i.doRebind = false; } catch (RemoteException e) { + if (DEBUG_SERVICE) Slog.v(TAG, "Crashed while binding " + r); return false; } } @@ -11062,13 +8799,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen r.restartTime = r.lastActivity = SystemClock.uptimeMillis(); app.services.add(r); - bumpServiceExecutingLocked(r); + bumpServiceExecutingLocked(r, "create"); updateLruProcessLocked(app, true, true); boolean created = false; try { - if (DEBUG_SERVICE) Slog.v(TAG, "Scheduling start service: " - + r.name + " " + r.intent); mStringBuilder.setLength(0); r.intent.getIntent().toShortString(mStringBuilder, false, true); EventLog.writeEvent(EventLogTags.AM_CREATE_SERVICE, @@ -11098,7 +8833,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (r.lastStartId < 1) { r.lastStartId = 1; } - r.pendingStarts.add(new ServiceRecord.StartItem(r.lastStartId, null)); + r.pendingStarts.add(new ServiceRecord.StartItem(r, r.lastStartId, null, -1)); } sendServiceArgsLocked(r, true); @@ -11118,6 +8853,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (N > 0) { for (int i=N-1; i>=0; i--) { ServiceRecord.StartItem si = r.deliveredStarts.get(i); + si.removeUriPermissionsLocked(); if (si.intent == null) { // We'll generate this again if needed. } else if (!allowCancel || (si.deliveryCount < ServiceRecord.MAX_DELIVERY_COUNT @@ -11227,8 +8963,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return true; } - if (DEBUG_SERVICE) Slog.v(TAG, "Bringing up service " + r.name - + " " + r.intent); + if (DEBUG_SERVICE) Slog.v(TAG, "Bringing up " + r + " " + r.intent); // We are now bringing the service up, so no longer in the // restarting state. @@ -11279,27 +9014,30 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (!force) { // XXX should probably keep a count of the number of auto-create // connections directly in the service. - Iterator<ConnectionRecord> it = r.connections.values().iterator(); + Iterator<ArrayList<ConnectionRecord>> it = r.connections.values().iterator(); while (it.hasNext()) { - ConnectionRecord cr = it.next(); - if ((cr.flags&Context.BIND_AUTO_CREATE) != 0) { - return; + ArrayList<ConnectionRecord> cr = it.next(); + for (int i=0; i<cr.size(); i++) { + if ((cr.get(i).flags&Context.BIND_AUTO_CREATE) != 0) { + return; + } } } } // Report to all of the connections that the service is no longer // available. - Iterator<ConnectionRecord> it = r.connections.values().iterator(); + Iterator<ArrayList<ConnectionRecord>> it = r.connections.values().iterator(); while (it.hasNext()) { - ConnectionRecord c = it.next(); - try { - // todo: shouldn't be a synchronous call! - c.conn.connected(r.name, null); - } catch (Exception e) { - Slog.w(TAG, "Failure disconnecting service " + r.name + - " to connection " + c.conn.asBinder() + - " (in " + c.binding.client.processName + ")", e); + ArrayList<ConnectionRecord> c = it.next(); + for (int i=0; i<c.size(); i++) { + try { + c.get(i).conn.connected(r.name, null); + } catch (Exception e) { + Slog.w(TAG, "Failure disconnecting service " + r.name + + " to connection " + c.get(i).conn.asBinder() + + " (in " + c.get(i).binding.client.processName + ")", e); + } } } } @@ -11313,7 +9051,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen + ": hasBound=" + ibr.hasBound); if (r.app != null && r.app.thread != null && ibr.hasBound) { try { - bumpServiceExecutingLocked(r); + bumpServiceExecutingLocked(r, "bring down unbind"); updateOomAdjLocked(r.app); ibr.hasBound = false; r.app.thread.scheduleUnbindService(r, @@ -11327,15 +9065,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - if (DEBUG_SERVICE) Slog.v(TAG, "Bringing down service " + r.name - + " " + r.intent); + if (DEBUG_SERVICE) Slog.v(TAG, "Bringing down " + r + " " + r.intent); EventLog.writeEvent(EventLogTags.AM_DESTROY_SERVICE, System.identityHashCode(r), r.shortName, (r.app != null) ? r.app.pid : -1); mServices.remove(r.name); mServicesByIntent.remove(r.intent); - if (localLOGV) Slog.v(TAG, "BRING DOWN SERVICE: " + r.shortName); r.totalRestartCount = 0; unscheduleServiceRestartLocked(r); @@ -11344,8 +9080,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen for (int i=0; i<N; i++) { if (mPendingServices.get(i) == r) { mPendingServices.remove(i); - if (DEBUG_SERVICE) Slog.v( - TAG, "Removed pending service: " + r.shortName); + if (DEBUG_SERVICE) Slog.v(TAG, "Removed pending: " + r); i--; N--; } @@ -11357,7 +9092,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen r.foregroundNoti = null; // Clear start entries. - r.deliveredStarts.clear(); + r.clearDeliveredStartsLocked(); r.pendingStarts.clear(); if (r.app != null) { @@ -11367,9 +9102,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen r.app.services.remove(r); if (r.app.thread != null) { try { - if (DEBUG_SERVICE) Slog.v(TAG, - "Stopping service: " + r.shortName); - bumpServiceExecutingLocked(r); + bumpServiceExecutingLocked(r, "stop"); mStoppingServices.add(r); updateOomAdjLocked(r.app); r.app.thread.scheduleStopService(r); @@ -11381,11 +9114,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen updateServiceForegroundLocked(r.app, false); } else { if (DEBUG_SERVICE) Slog.v( - TAG, "Removed service that has no process: " + r.shortName); + TAG, "Removed service that has no process: " + r); } } else { if (DEBUG_SERVICE) Slog.v( - TAG, "Removed service that is not running: " + r.shortName); + TAG, "Removed service that is not running: " + r); } } @@ -11417,9 +9150,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ? res.permission : "private to package"); } ServiceRecord r = res.record; + int targetPermissionUid = checkGrantUriPermissionFromIntentLocked( + callingUid, r.packageName, service); if (unscheduleServiceRestartLocked(r)) { - if (DEBUG_SERVICE) Slog.v(TAG, "START SERVICE WHILE RESTART PENDING: " - + r.shortName); + if (DEBUG_SERVICE) Slog.v(TAG, "START SERVICE WHILE RESTART PENDING: " + r); } r.startRequested = true; r.callStart = false; @@ -11427,7 +9161,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (r.lastStartId < 1) { r.lastStartId = 1; } - r.pendingStarts.add(new ServiceRecord.StartItem(r.lastStartId, service)); + r.pendingStarts.add(new ServiceRecord.StartItem(r, r.lastStartId, + service, targetPermissionUid)); r.lastActivity = SystemClock.uptimeMillis(); synchronized (r.stats.getBatteryStats()) { r.stats.startRunningLocked(); @@ -11552,7 +9287,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ServiceRecord.StartItem si = r.findDeliveredStart(startId, false); if (si != null) { while (r.deliveredStarts.size() > 0) { - if (r.deliveredStarts.remove(0) == si) { + ServiceRecord.StartItem cur = r.deliveredStarts.remove(0); + cur.removeUriPermissionsLocked(); + if (cur == si) { break; } } @@ -11628,7 +9365,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public void updateServiceForegroundLocked(ProcessRecord proc, boolean oomAdj) { boolean anyForeground = false; - for (ServiceRecord sr : (HashSet<ServiceRecord>)proc.services) { + for (ServiceRecord sr : proc.services) { if (sr.isForeground) { anyForeground = true; break; @@ -11662,14 +9399,14 @@ public final class ActivityManagerService extends ActivityManagerNative implemen + ") when binding service " + service); } - HistoryRecord activity = null; + ActivityRecord activity = null; if (token != null) { - int aindex = indexOfTokenLocked(token); + int aindex = mMainStack.indexOfTokenLocked(token); if (aindex < 0) { Slog.w(TAG, "Binding with unknown activity: " + token); return 0; } - activity = (HistoryRecord)mHistory.get(aindex); + activity = (ActivityRecord)mMainStack.mHistory.get(aindex); } int clientLabel = 0; @@ -11710,7 +9447,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (unscheduleServiceRestartLocked(s)) { if (DEBUG_SERVICE) Slog.v(TAG, "BIND SERVICE WHILE RESTART PENDING: " - + s.shortName); + + s); } AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp); @@ -11718,7 +9455,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen connection, flags, clientLabel, clientIntent); IBinder binder = connection.asBinder(); - s.connections.put(binder, c); + ArrayList<ConnectionRecord> clist = s.connections.get(binder); + if (clist == null) { + clist = new ArrayList<ConnectionRecord>(); + s.connections.put(binder, clist); + } + clist.add(c); b.connections.add(c); if (activity != null) { if (activity.connections == null) { @@ -11727,7 +9469,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen activity.connections.add(c); } b.client.connections.add(c); - mServiceConnections.put(binder, c); + clist = mServiceConnections.get(binder); + if (clist == null) { + clist = new ArrayList<ConnectionRecord>(); + mServiceConnections.put(binder, clist); + } + clist.add(c); if ((flags&Context.BIND_AUTO_CREATE) != 0) { s.lastActivity = SystemClock.uptimeMillis(); @@ -11773,12 +9520,18 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return 1; } - private void removeConnectionLocked( - ConnectionRecord c, ProcessRecord skipApp, HistoryRecord skipAct) { + void removeConnectionLocked( + ConnectionRecord c, ProcessRecord skipApp, ActivityRecord skipAct) { IBinder binder = c.conn.asBinder(); AppBindRecord b = c.binding; ServiceRecord s = b.service; - s.connections.remove(binder); + ArrayList<ConnectionRecord> clist = s.connections.get(binder); + if (clist != null) { + clist.remove(c); + if (clist.size() == 0) { + s.connections.remove(binder); + } + } b.connections.remove(c); if (c.activity != null && c.activity != skipAct) { if (c.activity.connections != null) { @@ -11788,7 +9541,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (b.client != skipApp) { b.client.connections.remove(c); } - mServiceConnections.remove(binder); + clist = mServiceConnections.get(binder); + if (clist != null) { + clist.remove(c); + if (clist.size() == 0) { + mServiceConnections.remove(binder); + } + } if (b.connections.size() == 0) { b.intent.apps.remove(b.client); @@ -11799,7 +9558,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (s.app != null && s.app.thread != null && b.intent.apps.size() == 0 && b.intent.hasBound) { try { - bumpServiceExecutingLocked(s); + bumpServiceExecutingLocked(s, "unbind"); updateOomAdjLocked(s.app); b.intent.hasBound = false; // Assume the client doesn't want to know about a rebind; @@ -11821,8 +9580,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen synchronized (this) { IBinder binder = connection.asBinder(); if (DEBUG_SERVICE) Slog.v(TAG, "unbindService: conn=" + binder); - ConnectionRecord r = mServiceConnections.get(binder); - if (r == null) { + ArrayList<ConnectionRecord> clist = mServiceConnections.get(binder); + if (clist == null) { Slog.w(TAG, "Unbind failed: could not find connection for " + connection.asBinder()); return false; @@ -11830,11 +9589,14 @@ public final class ActivityManagerService extends ActivityManagerNative implemen final long origId = Binder.clearCallingIdentity(); - removeConnectionLocked(r, null, null); + while (clist.size() > 0) { + ConnectionRecord r = clist.get(0); + removeConnectionLocked(r, null, null); - if (r.binding.service.app != null) { - // This could have made the service less important. - updateOomAdjLocked(r.binding.service.app); + if (r.binding.service.app != null) { + // This could have made the service less important. + updateOomAdjLocked(r.binding.service.app); + } } Binder.restoreCallingIdentity(origId); @@ -11857,7 +9619,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen final long origId = Binder.clearCallingIdentity(); - if (DEBUG_SERVICE) Slog.v(TAG, "PUBLISHING SERVICE " + r.name + if (DEBUG_SERVICE) Slog.v(TAG, "PUBLISHING " + r + " " + intent + ": " + service); if (r != null) { Intent.FilterComparison filter @@ -11868,26 +9630,29 @@ public final class ActivityManagerService extends ActivityManagerNative implemen b.requested = true; b.received = true; if (r.connections.size() > 0) { - Iterator<ConnectionRecord> it + Iterator<ArrayList<ConnectionRecord>> it = r.connections.values().iterator(); while (it.hasNext()) { - ConnectionRecord c = it.next(); - if (!filter.equals(c.binding.intent.intent)) { - if (DEBUG_SERVICE) Slog.v( - TAG, "Not publishing to: " + c); - if (DEBUG_SERVICE) Slog.v( - TAG, "Bound intent: " + c.binding.intent.intent); - if (DEBUG_SERVICE) Slog.v( - TAG, "Published intent: " + intent); - continue; - } - if (DEBUG_SERVICE) Slog.v(TAG, "Publishing to: " + c); - try { - c.conn.connected(r.name, service); - } catch (Exception e) { - Slog.w(TAG, "Failure sending service " + r.name + - " to connection " + c.conn.asBinder() + - " (in " + c.binding.client.processName + ")", e); + ArrayList<ConnectionRecord> clist = it.next(); + for (int i=0; i<clist.size(); i++) { + ConnectionRecord c = clist.get(i); + if (!filter.equals(c.binding.intent.intent)) { + if (DEBUG_SERVICE) Slog.v( + TAG, "Not publishing to: " + c); + if (DEBUG_SERVICE) Slog.v( + TAG, "Bound intent: " + c.binding.intent.intent); + if (DEBUG_SERVICE) Slog.v( + TAG, "Published intent: " + intent); + continue; + } + if (DEBUG_SERVICE) Slog.v(TAG, "Publishing to: " + c); + try { + c.conn.connected(r.name, service); + } catch (Exception e) { + Slog.w(TAG, "Failure sending service " + r.name + + " to connection " + c.conn.asBinder() + + " (in " + c.binding.client.processName + ")", e); + } } } } @@ -11948,9 +9713,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ServiceRecord r = (ServiceRecord)token; boolean inStopping = mStoppingServices.contains(token); if (r != null) { - if (DEBUG_SERVICE) Slog.v(TAG, "DONE EXECUTING SERVICE " + r.name - + ": nesting=" + r.executeNesting - + ", inStopping=" + inStopping); if (r != token) { Slog.w(TAG, "Done executing service " + r.name + " with incorrect token: given " + token @@ -12007,20 +9769,30 @@ public final class ActivityManagerService extends ActivityManagerNative implemen serviceDoneExecutingLocked(r, inStopping); Binder.restoreCallingIdentity(origId); } else { - Slog.w(TAG, "Done executing unknown service " + r.name - + " with token " + token); + Slog.w(TAG, "Done executing unknown service from pid " + + Binder.getCallingPid()); } } } public void serviceDoneExecutingLocked(ServiceRecord r, boolean inStopping) { + if (DEBUG_SERVICE) Slog.v(TAG, "<<< DONE EXECUTING " + r + + ": nesting=" + r.executeNesting + + ", inStopping=" + inStopping + ", app=" + r.app); + else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG, "<<< DONE EXECUTING " + r.shortName); r.executeNesting--; if (r.executeNesting <= 0 && r.app != null) { + if (DEBUG_SERVICE) Slog.v(TAG, + "Nesting at 0 of " + r.shortName); r.app.executingServices.remove(r); if (r.app.executingServices.size() == 0) { + if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG, + "No more executingServices of " + r.shortName); mHandler.removeMessages(SERVICE_TIMEOUT_MSG, r.app); } if (inStopping) { + if (DEBUG_SERVICE) Slog.v(TAG, + "doneExecuting remove stopping " + r); mStoppingServices.remove(r); } updateOomAdjLocked(r.app); @@ -12130,20 +9902,20 @@ public final class ActivityManagerService extends ActivityManagerNative implemen Slog.e(TAG, "Backup agent created for " + agentPackageName + " but not requested!"); return; } + } - long oldIdent = Binder.clearCallingIdentity(); - try { - IBackupManager bm = IBackupManager.Stub.asInterface( - ServiceManager.getService(Context.BACKUP_SERVICE)); - bm.agentConnected(agentPackageName, agent); - } catch (RemoteException e) { - // can't happen; the backup manager service is local - } catch (Exception e) { - Slog.w(TAG, "Exception trying to deliver BackupAgent binding: "); - e.printStackTrace(); - } finally { - Binder.restoreCallingIdentity(oldIdent); - } + long oldIdent = Binder.clearCallingIdentity(); + try { + IBackupManager bm = IBackupManager.Stub.asInterface( + ServiceManager.getService(Context.BACKUP_SERVICE)); + bm.agentConnected(agentPackageName, agent); + } catch (RemoteException e) { + // can't happen; the backup manager service is local + } catch (Exception e) { + Slog.w(TAG, "Exception trying to deliver BackupAgent binding: "); + e.printStackTrace(); + } finally { + Binder.restoreCallingIdentity(oldIdent); } } @@ -12455,7 +10227,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Always okay. } else if (callerApp == null || !callerApp.persistent) { try { - if (ActivityThread.getPackageManager().isProtectedBroadcast( + if (AppGlobals.getPackageManager().isProtectedBroadcast( intent.getAction())) { String msg = "Permission Denial: not allowed to send broadcast " + intent.getAction() + " from pid=" @@ -12514,7 +10286,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen try { if (intent.getComponent() != null) { // Broadcast is going to one specific receiver class... - ActivityInfo ai = ActivityThread.getPackageManager(). + ActivityInfo ai = AppGlobals.getPackageManager(). getReceiverInfo(intent.getComponent(), STOCK_PM_FLAGS); if (ai != null) { receivers = new ArrayList(); @@ -12527,7 +10299,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) { receivers = - ActivityThread.getPackageManager().queryIntentReceivers( + AppGlobals.getPackageManager().queryIntentReceivers( intent, resolvedType, STOCK_PM_FLAGS); } registeredReceivers = mReceiverResolver.queryIntent(intent, resolvedType, false); @@ -12661,7 +10433,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } boolean replaced = false; if (replacePending) { - for (int i=mOrderedBroadcasts.size()-1; i>=0; i--) { + for (int i=mOrderedBroadcasts.size()-1; i>0; i--) { if (intent.filterEquals(mOrderedBroadcasts.get(i).intent)) { if (DEBUG_BROADCAST) Slog.v(TAG, "***** DROPPING ORDERED: " + intent); @@ -12680,35 +10452,41 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return BROADCAST_SUCCESS; } - public final int broadcastIntent(IApplicationThread caller, - Intent intent, String resolvedType, IIntentReceiver resultTo, - int resultCode, String resultData, Bundle map, - String requiredPermission, boolean serialized, boolean sticky) { + final Intent verifyBroadcastLocked(Intent intent) { // Refuse possible leaked file descriptors if (intent != null && intent.hasFileDescriptors() == true) { throw new IllegalArgumentException("File descriptors passed in Intent"); } - synchronized(this) { - int flags = intent.getFlags(); - - if (!mSystemReady) { - // if the caller really truly claims to know what they're doing, go - // ahead and allow the broadcast without launching any receivers - if ((flags&Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT) != 0) { - intent = new Intent(intent); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - } else if ((flags&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0){ - Slog.e(TAG, "Attempt to launch receivers of broadcast intent " + intent - + " before boot completion"); - throw new IllegalStateException("Cannot broadcast before boot completed"); - } - } - - if ((flags&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) { - throw new IllegalArgumentException( - "Can't use FLAG_RECEIVER_BOOT_UPGRADE here"); + int flags = intent.getFlags(); + + if (!mProcessesReady) { + // if the caller really truly claims to know what they're doing, go + // ahead and allow the broadcast without launching any receivers + if ((flags&Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT) != 0) { + intent = new Intent(intent); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + } else if ((flags&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) { + Slog.e(TAG, "Attempt to launch receivers of broadcast intent " + intent + + " before boot completion"); + throw new IllegalStateException("Cannot broadcast before boot completed"); } + } + + if ((flags&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) { + throw new IllegalArgumentException( + "Can't use FLAG_RECEIVER_BOOT_UPGRADE here"); + } + + return intent; + } + + public final int broadcastIntent(IApplicationThread caller, + Intent intent, String resolvedType, IIntentReceiver resultTo, + int resultCode, String resultData, Bundle map, + String requiredPermission, boolean serialized, boolean sticky) { + synchronized(this) { + intent = verifyBroadcastLocked(intent); final ProcessRecord callerApp = getRecordForAppLocked(caller); final int callingPid = Binder.getCallingPid(); @@ -12729,6 +10507,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen int resultCode, String resultData, Bundle map, String requiredPermission, boolean serialized, boolean sticky) { synchronized(this) { + intent = verifyBroadcastLocked(intent); + final long origId = Binder.clearCallingIdentity(); int res = broadcastIntentLocked(null, packageName, intent, resolvedType, resultTo, resultCode, resultData, map, requiredPermission, @@ -12848,7 +10628,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen Binder.restoreCallingIdentity(origId); } - private final void logBroadcastReceiverDiscard(BroadcastRecord r) { + private final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) { if (r.nextReceiver > 0) { Object curReceiver = r.receivers.get(r.nextReceiver-1); if (curReceiver instanceof BroadcastFilter) { @@ -12876,72 +10656,115 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private final void broadcastTimeout() { - ProcessRecord app = null; - String anrMessage = null; + private final void setBroadcastTimeoutLocked(long timeoutTime) { + if (! mPendingBroadcastTimeoutMessage) { + Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG); + mHandler.sendMessageAtTime(msg, timeoutTime); + mPendingBroadcastTimeoutMessage = true; + } + } - synchronized (this) { - if (mOrderedBroadcasts.size() == 0) { + private final void cancelBroadcastTimeoutLocked() { + if (mPendingBroadcastTimeoutMessage) { + mHandler.removeMessages(BROADCAST_TIMEOUT_MSG); + mPendingBroadcastTimeoutMessage = false; + } + } + + private final void broadcastTimeoutLocked(boolean fromMsg) { + if (fromMsg) { + mPendingBroadcastTimeoutMessage = false; + } + + if (mOrderedBroadcasts.size() == 0) { + return; + } + + long now = SystemClock.uptimeMillis(); + BroadcastRecord r = mOrderedBroadcasts.get(0); + if (fromMsg) { + if (mDidDexOpt) { + // Delay timeouts until dexopt finishes. + mDidDexOpt = false; + long timeoutTime = SystemClock.uptimeMillis() + BROADCAST_TIMEOUT; + setBroadcastTimeoutLocked(timeoutTime); return; } - long now = SystemClock.uptimeMillis(); - BroadcastRecord r = mOrderedBroadcasts.get(0); - if ((r.receiverTime+BROADCAST_TIMEOUT) > now) { + if (! mProcessesReady) { + // Only process broadcast timeouts if the system is ready. That way + // PRE_BOOT_COMPLETED broadcasts can't timeout as they are intended + // to do heavy lifting for system up. + return; + } + + long timeoutTime = r.receiverTime + BROADCAST_TIMEOUT; + if (timeoutTime > now) { + // We can observe premature timeouts because we do not cancel and reset the + // broadcast timeout message after each receiver finishes. Instead, we set up + // an initial timeout then kick it down the road a little further as needed + // when it expires. if (DEBUG_BROADCAST) Slog.v(TAG, "Premature timeout @ " + now + ": resetting BROADCAST_TIMEOUT_MSG for " - + (r.receiverTime + BROADCAST_TIMEOUT)); - Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG); - mHandler.sendMessageAtTime(msg, r.receiverTime+BROADCAST_TIMEOUT); + + timeoutTime); + setBroadcastTimeoutLocked(timeoutTime); return; } + } - Slog.w(TAG, "Timeout of broadcast " + r + " - receiver=" + r.receiver); - r.receiverTime = now; - r.anrCount++; + Slog.w(TAG, "Timeout of broadcast " + r + " - receiver=" + r.receiver + + ", started " + (now - r.receiverTime) + "ms ago"); + r.receiverTime = now; + r.anrCount++; - // Current receiver has passed its expiration date. - if (r.nextReceiver <= 0) { - Slog.w(TAG, "Timeout on receiver with nextReceiver <= 0"); - return; - } + // Current receiver has passed its expiration date. + if (r.nextReceiver <= 0) { + Slog.w(TAG, "Timeout on receiver with nextReceiver <= 0"); + return; + } - Object curReceiver = r.receivers.get(r.nextReceiver-1); - Slog.w(TAG, "Receiver during timeout: " + curReceiver); - logBroadcastReceiverDiscard(r); - if (curReceiver instanceof BroadcastFilter) { - BroadcastFilter bf = (BroadcastFilter)curReceiver; - if (bf.receiverList.pid != 0 - && bf.receiverList.pid != MY_PID) { - synchronized (this.mPidsSelfLocked) { - app = this.mPidsSelfLocked.get( - bf.receiverList.pid); - } - } - } else { - app = r.curApp; - } - - if (app != null) { - anrMessage = "Broadcast of " + r.intent.toString(); - } + ProcessRecord app = null; + String anrMessage = null; - if (mPendingBroadcast == r) { - mPendingBroadcast = null; + Object curReceiver = r.receivers.get(r.nextReceiver-1); + Slog.w(TAG, "Receiver during timeout: " + curReceiver); + logBroadcastReceiverDiscardLocked(r); + if (curReceiver instanceof BroadcastFilter) { + BroadcastFilter bf = (BroadcastFilter)curReceiver; + if (bf.receiverList.pid != 0 + && bf.receiverList.pid != MY_PID) { + synchronized (this.mPidsSelfLocked) { + app = this.mPidsSelfLocked.get( + bf.receiverList.pid); + } } - - // Move on to the next receiver. - finishReceiverLocked(r.receiver, r.resultCode, r.resultData, - r.resultExtras, r.resultAbort, true); - scheduleBroadcastsLocked(); + } else { + app = r.curApp; } + if (app != null) { + anrMessage = "Broadcast of " + r.intent.toString(); + } + + if (mPendingBroadcast == r) { + mPendingBroadcast = null; + } + + // Move on to the next receiver. + finishReceiverLocked(r.receiver, r.resultCode, r.resultData, + r.resultExtras, r.resultAbort, true); + scheduleBroadcastsLocked(); + if (anrMessage != null) { - appNotResponding(app, null, null, anrMessage); + // Post the ANR to the handler since we do not want to process ANRs while + // potentially holding our lock. + mHandler.post(new AppNotResponding(app, anrMessage)); } } private final void processCurBroadcastLocked(BroadcastRecord r, ProcessRecord app) throws RemoteException { + if (DEBUG_BROADCAST) Slog.v(TAG, + "Process cur broadcast " + r + " for app " + app); if (app.thread == null) { throw new RemoteException(); } @@ -12961,9 +10784,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ensurePackageDexOpt(r.intent.getComponent().getPackageName()); app.thread.scheduleReceiver(new Intent(r.intent), r.curReceiver, r.resultCode, r.resultData, r.resultExtras, r.ordered); + if (DEBUG_BROADCAST) Slog.v(TAG, + "Process cur broadcast " + r + " DELIVERED for app " + app); started = true; } finally { if (!started) { + if (DEBUG_BROADCAST) Slog.v(TAG, + "Process cur broadcast " + r + ": NOT STARTED!"); r.receiver = null; r.curApp = null; app.curReceiver = null; @@ -12972,9 +10799,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } - static void performReceive(ProcessRecord app, IIntentReceiver receiver, + static void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver, Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky) throws RemoteException { + // Send the intent to the receiver asynchronously using one-way binder calls. if (app != null && app.thread != null) { // If we have an app thread, do the call through that so it is // correctly ordered with other one-way calls. @@ -12985,7 +10813,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private final void deliverToRegisteredReceiver(BroadcastRecord r, + private final void deliverToRegisteredReceiverLocked(BroadcastRecord r, BroadcastFilter filter, boolean ordered) { boolean skip = false; if (filter.requiredPermission != null) { @@ -13043,7 +10871,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen Slog.i(TAG, "Delivering to " + filter + " (seq=" + seq + "): " + r); } - performReceive(filter.receiverList.app, filter.receiverList.receiver, + performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver, new Intent(r.intent), r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky); if (ordered) { @@ -13100,7 +10928,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (DEBUG_BROADCAST) Slog.v(TAG, "Delivering non-ordered to registered " + target + ": " + r); - deliverToRegisteredReceiver(r, (BroadcastFilter)target, false); + deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false); } addBroadcastToHistoryLocked(r); if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Done with parallel broadcast " @@ -13128,6 +10956,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } else { Slog.w(TAG, "pending app " + mPendingBroadcast.curApp + " died before responding to broadcast"); + mPendingBroadcast.state = BroadcastRecord.IDLE; + mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex; mPendingBroadcast = null; } } @@ -13154,11 +10984,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // and continue to make progress. // // This is only done if the system is ready so that PRE_BOOT_COMPLETED - // receivers don't get executed with with timeouts. They're intended for + // receivers don't get executed with timeouts. They're intended for // one time heavy lifting after system upgrades and can take // significant amounts of time. int numReceivers = (r.receivers != null) ? r.receivers.size() : 0; - if (mSystemReady && r.dispatchTime > 0) { + if (mProcessesReady && r.dispatchTime > 0) { long now = SystemClock.uptimeMillis(); if ((numReceivers > 0) && (now > r.dispatchTime + (2*BROADCAST_TIMEOUT*numReceivers))) { @@ -13170,7 +11000,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen + " numReceivers=" + numReceivers + " nextReceiver=" + r.nextReceiver + " state=" + r.state); - broadcastTimeout(); // forcibly finish this broadcast + broadcastTimeoutLocked(false); // forcibly finish this broadcast forceReceive = true; r.state = BroadcastRecord.IDLE; } @@ -13194,7 +11024,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen Slog.i(TAG, "Finishing broadcast " + r.intent.getAction() + " seq=" + seq + " app=" + r.callerApp); } - performReceive(r.callerApp, r.resultTo, + performReceiveLocked(r.callerApp, r.resultTo, new Intent(r.intent), r.resultCode, r.resultData, r.resultExtras, false, false); } catch (RemoteException e) { @@ -13203,7 +11033,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } if (DEBUG_BROADCAST) Slog.v(TAG, "Cancelling BROADCAST_TIMEOUT_MSG"); - mHandler.removeMessages(BROADCAST_TIMEOUT_MSG); + cancelBroadcastTimeoutLocked(); if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Finished with ordered broadcast " + r); @@ -13228,11 +11058,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing ordered broadcast " + r); + } + if (! mPendingBroadcastTimeoutMessage) { + long timeoutTime = r.receiverTime + BROADCAST_TIMEOUT; if (DEBUG_BROADCAST) Slog.v(TAG, - "Submitting BROADCAST_TIMEOUT_MSG for " - + (r.receiverTime + BROADCAST_TIMEOUT)); - Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG); - mHandler.sendMessageAtTime(msg, r.receiverTime+BROADCAST_TIMEOUT); + "Submitting BROADCAST_TIMEOUT_MSG for " + r + " at " + timeoutTime); + setBroadcastTimeoutLocked(timeoutTime); } Object nextReceiver = r.receivers.get(recIdx); @@ -13243,7 +11074,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (DEBUG_BROADCAST) Slog.v(TAG, "Delivering ordered to registered " + filter + ": " + r); - deliverToRegisteredReceiver(r, filter, r.ordered); + deliverToRegisteredReceiverLocked(r, filter, r.ordered); if (r.receiver == null || !r.ordered) { // The receiver has already finished, so schedule to // process the next one. @@ -13279,7 +11110,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (r.callingUid != Process.SYSTEM_UID && r.requiredPermission != null) { try { - perm = ActivityThread.getPackageManager(). + perm = AppGlobals.getPackageManager(). checkPermission(r.requiredPermission, info.activityInfo.applicationInfo.packageName); } catch (RemoteException e) { @@ -13297,10 +11128,15 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } if (r.curApp != null && r.curApp.crashing) { // If the target process is crashing, just skip it. + if (DEBUG_BROADCAST) Slog.v(TAG, + "Skipping deliver ordered " + r + " to " + r.curApp + + ": process crashing"); skip = true; } if (skip) { + if (DEBUG_BROADCAST) Slog.v(TAG, + "Skipping delivery of ordered " + r + " for whatever reason"); r.receiver = null; r.curFilter = null; r.state = BroadcastRecord.IDLE; @@ -13332,6 +11168,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } // Not running -- get it started, to be executed when the app comes up. + if (DEBUG_BROADCAST) Slog.v(TAG, + "Need to start app " + targetProcess + " for broadcast " + r); if ((r.curApp=startProcessLocked(targetProcess, info.activityInfo.applicationInfo, true, r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND, @@ -13344,7 +11182,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen + info.activityInfo.applicationInfo.packageName + "/" + info.activityInfo.applicationInfo.uid + " for broadcast " + r.intent + ": process is bad"); - logBroadcastReceiverDiscard(r); + logBroadcastReceiverDiscardLocked(r); finishReceiverLocked(r.receiver, r.resultCode, r.resultData, r.resultExtras, r.resultAbort, true); scheduleBroadcastsLocked(); @@ -13353,6 +11191,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } mPendingBroadcast = r; + mPendingBroadcastRecvIndex = recIdx; } } @@ -13536,7 +11375,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen * configuration. */ public boolean updateConfigurationLocked(Configuration values, - HistoryRecord starting) { + ActivityRecord starting) { int changes = 0; boolean kept = true; @@ -13605,18 +11444,18 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // If the configuration changed, and the caller is not already // in the process of starting an activity, then find the top // activity to check if its configuration needs to change. - starting = topRunningActivityLocked(null); + starting = mMainStack.topRunningActivityLocked(null); } if (starting != null) { - kept = ensureActivityConfigurationLocked(starting, changes); + 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."); - ensureActivitiesVisibleLocked(starting, changes); + mMainStack.ensureActivitiesVisibleLocked(starting, changes); } } @@ -13626,160 +11465,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return kept; } - - private final boolean relaunchActivityLocked(HistoryRecord r, - int changes, boolean andResume) { - List<ResultInfo> results = null; - List<Intent> newIntents = null; - if (andResume) { - results = r.results; - newIntents = r.newIntents; - } - if (DEBUG_SWITCH) Slog.v(TAG, "Relaunching: " + r - + " with results=" + results + " newIntents=" + newIntents - + " andResume=" + andResume); - EventLog.writeEvent(andResume ? EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY - : EventLogTags.AM_RELAUNCH_ACTIVITY, System.identityHashCode(r), - r.task.taskId, r.shortComponentName); - - r.startFreezingScreenLocked(r.app, 0); - - try { - if (DEBUG_SWITCH) Slog.i(TAG, "Switch is restarting resumed " + r); - r.app.thread.scheduleRelaunchActivity(r, results, newIntents, - changes, !andResume, mConfiguration); - // Note: don't need to call pauseIfSleepingLocked() here, because - // the caller will only pass in 'andResume' if this activity is - // currently resumed, which implies we aren't sleeping. - } catch (RemoteException e) { - return false; - } - - if (andResume) { - r.results = null; - r.newIntents = null; - reportResumedActivityLocked(r); - } - - return true; - } - - /** - * Make sure the given activity matches the current configuration. Returns - * false if the activity had to be destroyed. Returns true if the - * configuration is the same, or the activity will remain running as-is - * for whatever reason. Ensures the HistoryRecord is updated with the - * correct configuration and all other bookkeeping is handled. - */ - private final boolean ensureActivityConfigurationLocked(HistoryRecord r, - int globalChanges) { - if (mConfigWillChange) { - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, - "Skipping config check (will change): " + r); - return true; - } - - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, - "Ensuring correct configuration: " + r); - - // Short circuit: if the two configurations are the exact same - // object (the common case), then there is nothing to do. - Configuration newConfig = mConfiguration; - if (r.configuration == newConfig) { - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, - "Configuration unchanged in " + r); - return true; - } - - // We don't worry about activities that are finishing. - if (r.finishing) { - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, - "Configuration doesn't matter in finishing " + r); - r.stopFreezingScreenLocked(false); - return true; - } - - // Okay we now are going to make this activity have the new config. - // But then we need to figure out how it needs to deal with that. - Configuration oldConfig = r.configuration; - r.configuration = newConfig; - - // If the activity isn't currently running, just leave the new - // configuration and it will pick that up next time it starts. - if (r.app == null || r.app.thread == null) { - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, - "Configuration doesn't matter not running " + r); - r.stopFreezingScreenLocked(false); - return true; - } - - // If the activity isn't persistent, there is a chance we will - // need to restart it. - if (!r.persistent) { - - // Figure out what has changed between the two configurations. - int changes = oldConfig.diff(newConfig); - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) { - Slog.v(TAG, "Checking to restart " + r.info.name + ": changed=0x" - + Integer.toHexString(changes) + ", handles=0x" - + Integer.toHexString(r.info.configChanges) - + ", newConfig=" + newConfig); - } - if ((changes&(~r.info.configChanges)) != 0) { - // Aha, the activity isn't handling the change, so DIE DIE DIE. - r.configChangeFlags |= changes; - r.startFreezingScreenLocked(r.app, globalChanges); - if (r.app == null || r.app.thread == null) { - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, - "Switch is destroying non-running " + r); - destroyActivityLocked(r, true); - } else if (r.state == ActivityState.PAUSING) { - // A little annoying: we are waiting for this activity to - // finish pausing. Let's not do anything now, but just - // flag that it needs to be restarted when done pausing. - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, - "Switch is skipping already pausing " + r); - r.configDestroy = true; - return true; - } else if (r.state == ActivityState.RESUMED) { - // Try to optimize this case: the configuration is changing - // and we need to restart the top, resumed activity. - // Instead of doing the normal handshaking, just say - // "restart!". - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, - "Switch is restarting resumed " + r); - relaunchActivityLocked(r, r.configChangeFlags, true); - r.configChangeFlags = 0; - } else { - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, - "Switch is restarting non-resumed " + r); - relaunchActivityLocked(r, r.configChangeFlags, false); - r.configChangeFlags = 0; - } - - // All done... tell the caller we weren't able to keep this - // activity around. - return false; - } - } - - // Default case: the activity can handle this new configuration, so - // hand it over. Note that we don't need to give it the new - // configuration, since we always send configuration changes to all - // process when they happen so it can just use whatever configuration - // it last got. - if (r.app != null && r.app.thread != null) { - try { - if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending new config to " + r); - r.app.thread.scheduleActivityConfigurationChanged(r); - } catch (RemoteException e) { - // If process died, whatever. - } - } - r.stopFreezingScreenLocked(false); - - return true; - } /** * Save the locale. You must be inside a synchronized (this) block. @@ -13826,6 +11511,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen app.adjType = "fixed"; app.adjSeq = mAdjSeq; app.curRawAdj = app.maxAdj; + app.keeping = true; app.curSchedGroup = Process.THREAD_GROUP_DEFAULT; return (app.curAdj=app.maxAdj); } @@ -13833,6 +11519,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen app.adjTypeCode = ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN; app.adjSource = null; app.adjTarget = null; + app.keeping = false; app.empty = false; app.hidden = false; @@ -13851,11 +11538,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen adj = FOREGROUND_APP_ADJ; schedGroup = Process.THREAD_GROUP_DEFAULT; app.adjType = "instrumentation"; - } else if (app.persistentActivities > 0) { - // Special persistent activities... shouldn't be used these days. - adj = FOREGROUND_APP_ADJ; - schedGroup = Process.THREAD_GROUP_DEFAULT; - app.adjType = "persistent"; } else if (app.curReceiver != null || (mPendingBroadcast != null && mPendingBroadcast.curApp == app)) { // An app that is currently receiving a broadcast also @@ -13871,15 +11553,20 @@ public final class ActivityManagerService extends ActivityManagerNative implemen app.adjType = "exec-service"; } else if (app.foregroundServices) { // The user is aware of this app, so make it visible. - adj = VISIBLE_APP_ADJ; + 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 = VISIBLE_APP_ADJ; + 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. @@ -13894,7 +11581,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen app.adjType = "bg-activities"; N = app.activities.size(); for (int j=0; j<N; j++) { - if (((HistoryRecord)app.activities.get(j)).visible) { + if (app.activities.get(j).visible) { // This app has a visible activity! app.hidden = false; adj = VISIBLE_APP_ADJ; @@ -13937,9 +11624,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen final long now = SystemClock.uptimeMillis(); // This process is more important if the top activity is // bound to the service. - Iterator jt = app.services.iterator(); + Iterator<ServiceRecord> jt = app.services.iterator(); while (jt.hasNext() && adj > FOREGROUND_APP_ADJ) { - ServiceRecord s = (ServiceRecord)jt.next(); + ServiceRecord s = jt.next(); if (s.startRequested) { if (now < (s.lastActivity+MAX_SERVICE_INACTIVITY)) { // This service has seen some activity within @@ -13957,70 +11644,79 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (adj > SECONDARY_SERVER_ADJ) { app.adjType = "started-bg-services"; } + // Don't kill this process because it is doing work; it + // has said it is doing work. + app.keeping = true; } if (s.connections.size() > 0 && (adj > FOREGROUND_APP_ADJ || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE)) { - Iterator<ConnectionRecord> kt + Iterator<ArrayList<ConnectionRecord>> kt = s.connections.values().iterator(); while (kt.hasNext() && adj > FOREGROUND_APP_ADJ) { - // XXX should compute this based on the max of - // all connected clients. - ConnectionRecord cr = kt.next(); - if (cr.binding.client == app) { - // Binding to ourself is not interesting. - continue; - } - if ((cr.flags&Context.BIND_AUTO_CREATE) != 0) { - ProcessRecord client = cr.binding.client; - int myHiddenAdj = hiddenAdj; - if (myHiddenAdj > client.hiddenAdj) { - if (client.hiddenAdj > VISIBLE_APP_ADJ) { - myHiddenAdj = client.hiddenAdj; - } else { - myHiddenAdj = VISIBLE_APP_ADJ; - } + ArrayList<ConnectionRecord> clist = kt.next(); + for (int i=0; i<clist.size() && adj > FOREGROUND_APP_ADJ; i++) { + // XXX should compute this based on the max of + // all connected clients. + ConnectionRecord cr = clist.get(i); + if (cr.binding.client == app) { + // Binding to ourself is not interesting. + continue; } - int clientAdj = computeOomAdjLocked( - client, myHiddenAdj, TOP_APP, true); - if (adj > clientAdj) { - adj = clientAdj > VISIBLE_APP_ADJ - ? clientAdj : VISIBLE_APP_ADJ; - if (!client.hidden) { - app.hidden = false; + if ((cr.flags&Context.BIND_AUTO_CREATE) != 0) { + ProcessRecord client = cr.binding.client; + int myHiddenAdj = hiddenAdj; + if (myHiddenAdj > client.hiddenAdj) { + if (client.hiddenAdj >= VISIBLE_APP_ADJ) { + myHiddenAdj = client.hiddenAdj; + } else { + myHiddenAdj = VISIBLE_APP_ADJ; + } + } + int clientAdj = computeOomAdjLocked( + client, myHiddenAdj, TOP_APP, true); + if (adj > clientAdj) { + adj = clientAdj >= VISIBLE_APP_ADJ + ? clientAdj : VISIBLE_APP_ADJ; + if (!client.hidden) { + app.hidden = false; + } + if (client.keeping) { + app.keeping = true; + } + app.adjType = "service"; + app.adjTypeCode = ActivityManager.RunningAppProcessInfo + .REASON_SERVICE_IN_USE; + app.adjSource = cr.binding.client; + app.adjTarget = s.name; } + if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) { + if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) { + schedGroup = Process.THREAD_GROUP_DEFAULT; + } + } + } + ActivityRecord a = cr.activity; + //if (a != null) { + // Slog.i(TAG, "Connection to " + a ": state=" + a.state); + //} + if (a != null && adj > FOREGROUND_APP_ADJ && + (a.state == ActivityState.RESUMED + || a.state == ActivityState.PAUSING)) { + adj = FOREGROUND_APP_ADJ; + schedGroup = Process.THREAD_GROUP_DEFAULT; + app.hidden = false; app.adjType = "service"; app.adjTypeCode = ActivityManager.RunningAppProcessInfo .REASON_SERVICE_IN_USE; - app.adjSource = cr.binding.client; - app.adjTarget = s.serviceInfo.name; - } - if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) { - if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) { - schedGroup = Process.THREAD_GROUP_DEFAULT; - } + app.adjSource = a; + app.adjTarget = s.name; } } - HistoryRecord a = cr.activity; - //if (a != null) { - // Slog.i(TAG, "Connection to " + a ": state=" + a.state); - //} - if (a != null && adj > FOREGROUND_APP_ADJ && - (a.state == ActivityState.RESUMED - || a.state == ActivityState.PAUSING)) { - adj = FOREGROUND_APP_ADJ; - schedGroup = Process.THREAD_GROUP_DEFAULT; - app.hidden = false; - app.adjType = "service"; - app.adjTypeCode = ActivityManager.RunningAppProcessInfo - .REASON_SERVICE_IN_USE; - app.adjSource = a; - app.adjTarget = s.serviceInfo.name; - } } } } - // Finally, f this process has active services running in it, we + // Finally, if this process has active services running in it, we // would like to avoid killing it unless it would prevent the current // application from running. By default we put the process in // with the rest of the background processes; as we scan through @@ -14034,10 +11730,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (app.pubProviders.size() != 0 && (adj > FOREGROUND_APP_ADJ || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE)) { - Iterator jt = app.pubProviders.values().iterator(); + Iterator<ContentProviderRecord> jt = app.pubProviders.values().iterator(); while (jt.hasNext() && (adj > FOREGROUND_APP_ADJ || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE)) { - ContentProviderRecord cpr = (ContentProviderRecord)jt.next(); + ContentProviderRecord cpr = jt.next(); if (cpr.clients.size() != 0) { Iterator<ProcessRecord> kt = cpr.clients.iterator(); while (kt.hasNext() && adj > FOREGROUND_APP_ADJ) { @@ -14062,11 +11758,14 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (!client.hidden) { app.hidden = false; } + if (client.keeping) { + app.keeping = true; + } app.adjType = "provider"; app.adjTypeCode = ActivityManager.RunningAppProcessInfo .REASON_PROVIDER_IN_USE; app.adjSource = client; - app.adjTarget = cpr.info.name; + app.adjTarget = cpr.name; } if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) { schedGroup = Process.THREAD_GROUP_DEFAULT; @@ -14081,8 +11780,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen adj = FOREGROUND_APP_ADJ; schedGroup = Process.THREAD_GROUP_DEFAULT; app.hidden = false; + app.keeping = true; app.adjType = "provider"; - app.adjTarget = cpr.info.name; + app.adjTarget = cpr.name; } } } @@ -14094,10 +11794,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // " adj=" + adj + " curAdj=" + app.curAdj + " maxAdj=" + app.maxAdj); if (adj > app.maxAdj) { adj = app.maxAdj; - if (app.maxAdj <= VISIBLE_APP_ADJ) { + if (app.maxAdj <= PERCEPTIBLE_APP_ADJ) { schedGroup = Process.THREAD_GROUP_DEFAULT; } } + if (adj < HIDDEN_APP_MIN_ADJ) { + app.keeping = true; + } app.curAdj = adj; app.curSchedGroup = schedGroup; @@ -14130,8 +11833,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen private final boolean canGcNowLocked() { return mParallelBroadcasts.size() == 0 && mOrderedBroadcasts.size() == 0 - && (mSleeping || (mResumedActivity != null && - mResumedActivity.idle)); + && (mSleeping || (mMainStack.mResumedActivity != null && + mMainStack.mResumedActivity.idle)); } /** @@ -14146,7 +11849,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (canGcNowLocked()) { while (mProcessesToGc.size() > 0) { ProcessRecord proc = mProcessesToGc.remove(0); - if (proc.curRawAdj > VISIBLE_APP_ADJ || proc.reportLowMemory) { + if (proc.curRawAdj > PERCEPTIBLE_APP_ADJ || proc.reportLowMemory) { if ((proc.lastRequestedGc+GC_MIN_INTERVAL) <= SystemClock.uptimeMillis()) { // To avoid spamming the system, we will GC processes one @@ -14235,6 +11938,104 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + final void checkExcessivePowerUsageLocked(boolean doKills) { + updateCpuStatsNow(); + + BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); + boolean doWakeKills = doKills; + boolean doCpuKills = doKills; + if (mLastPowerCheckRealtime == 0) { + doWakeKills = false; + } + if (mLastPowerCheckUptime == 0) { + doCpuKills = false; + } + if (stats.isScreenOn()) { + doWakeKills = false; + } + final long curRealtime = SystemClock.elapsedRealtime(); + final long realtimeSince = curRealtime - mLastPowerCheckRealtime; + final long curUptime = SystemClock.uptimeMillis(); + final long uptimeSince = curUptime - mLastPowerCheckUptime; + mLastPowerCheckRealtime = curRealtime; + mLastPowerCheckUptime = curUptime; + if (realtimeSince < WAKE_LOCK_MIN_CHECK_DURATION) { + doWakeKills = false; + } + if (uptimeSince < CPU_MIN_CHECK_DURATION) { + doCpuKills = false; + } + int i = mLruProcesses.size(); + while (i > 0) { + i--; + ProcessRecord app = mLruProcesses.get(i); + if (!app.keeping) { + long wtime; + synchronized (stats) { + wtime = stats.getProcessWakeTime(app.info.uid, + app.pid, curRealtime); + } + long wtimeUsed = wtime - app.lastWakeTime; + long cputimeUsed = app.curCpuTime - app.lastCpuTime; + if (DEBUG_POWER) { + StringBuilder sb = new StringBuilder(128); + sb.append("Wake for "); + app.toShortString(sb); + sb.append(": over "); + TimeUtils.formatDuration(realtimeSince, sb); + sb.append(" used "); + TimeUtils.formatDuration(wtimeUsed, sb); + sb.append(" ("); + sb.append((wtimeUsed*100)/realtimeSince); + sb.append("%)"); + Slog.i(TAG, sb.toString()); + sb.setLength(0); + sb.append("CPU for "); + app.toShortString(sb); + sb.append(": over "); + TimeUtils.formatDuration(uptimeSince, sb); + sb.append(" used "); + TimeUtils.formatDuration(cputimeUsed, sb); + sb.append(" ("); + sb.append((cputimeUsed*100)/uptimeSince); + sb.append("%)"); + Slog.i(TAG, sb.toString()); + } + // If a process has held a wake lock for more + // than 50% of the time during this period, + // that sounds pad. Kill! + if (doWakeKills && realtimeSince > 0 + && ((wtimeUsed*100)/realtimeSince) >= 50) { + synchronized (stats) { + stats.reportExcessiveWakeLocked(app.info.uid, app.processName, + realtimeSince, wtimeUsed); + } + Slog.w(TAG, "Excessive wake lock in " + app.processName + + " (pid " + app.pid + "): held " + wtimeUsed + + " during " + realtimeSince); + EventLog.writeEvent(EventLogTags.AM_KILL, app.pid, + app.processName, app.setAdj, "excessive wake lock"); + Process.killProcessQuiet(app.pid); + } else if (doCpuKills && uptimeSince > 0 + && ((cputimeUsed*100)/uptimeSince) >= 50) { + synchronized (stats) { + stats.reportExcessiveCpuLocked(app.info.uid, app.processName, + uptimeSince, cputimeUsed); + } + Slog.w(TAG, "Excessive CPU in " + app.processName + + " (pid " + app.pid + "): used " + cputimeUsed + + " during " + uptimeSince); + EventLog.writeEvent(EventLogTags.AM_KILL, app.pid, + app.processName, app.setAdj, "excessive cpu"); + Process.killProcessQuiet(app.pid); + } else { + app.lastWakeTime = wtime; + app.lastCpuTime = app.curCpuTime; + } + } + } + } + private final boolean updateOomAdjLocked( ProcessRecord app, int hiddenAdj, ProcessRecord TOP_APP) { app.hiddenAdj = hiddenAdj; @@ -14243,6 +12044,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return true; } + final boolean wasKeeping = app.keeping; + int adj = computeOomAdjLocked(app, hiddenAdj, TOP_APP, false); if ((app.pid != 0 && app.pid != MY_PID) || Process.supportsProcesses()) { @@ -14258,6 +12061,19 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // background (such as a service stopping). scheduleAppGcLocked(app); } + + if (wasKeeping && !app.keeping) { + // This app is no longer something we want to keep. Note + // its current wake lock time to later know to kill it if + // it is not behaving well. + BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); + synchronized (stats) { + app.lastWakeTime = stats.getProcessWakeTime(app.info.uid, + app.pid, SystemClock.elapsedRealtime()); + } + app.lastCpuTime = app.curCpuTime; + } + app.setRawAdj = app.curRawAdj; } if (adj != app.setAdj) { @@ -14301,19 +12117,19 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return true; } - private final HistoryRecord resumedAppLocked() { - HistoryRecord resumedActivity = mResumedActivity; + private final ActivityRecord resumedAppLocked() { + ActivityRecord resumedActivity = mMainStack.mResumedActivity; if (resumedActivity == null || resumedActivity.app == null) { - resumedActivity = mPausingActivity; + resumedActivity = mMainStack.mPausingActivity; if (resumedActivity == null || resumedActivity.app == null) { - resumedActivity = topRunningActivityLocked(null); + resumedActivity = mMainStack.topRunningActivityLocked(null); } } return resumedActivity; } private final boolean updateOomAdjLocked(ProcessRecord app) { - final HistoryRecord TOP_ACT = resumedAppLocked(); + final ActivityRecord TOP_ACT = resumedAppLocked(); final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null; int curAdj = app.curAdj; final boolean wasHidden = app.curAdj >= HIDDEN_APP_MIN_ADJ @@ -14334,9 +12150,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return res; } - private final boolean updateOomAdjLocked() { + final boolean updateOomAdjLocked() { boolean didOomAdj = true; - final HistoryRecord TOP_ACT = resumedAppLocked(); + final ActivityRecord TOP_ACT = resumedAppLocked(); final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null; if (false) { @@ -14398,7 +12214,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return ENFORCE_PROCESS_LIMIT || mProcessLimit > 0 ? false : didOomAdj; } - private final void trimApplications() { + final void trimApplications() { synchronized (this) { int i; @@ -14445,8 +12261,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen final ProcessRecord app = mLruProcesses.get(i); if (app.persistent || app.services.size() != 0 - || app.curReceiver != null - || app.persistentActivities > 0) { + || app.curReceiver != null) { // Don't count processes holding services against our // maximum process count. if (localLOGV) Slog.v( @@ -14511,14 +12326,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Quit the application only if we have a state saved for // all of its activities. boolean canQuit = !app.persistent && app.curReceiver == null - && app.services.size() == 0 - && app.persistentActivities == 0; + && app.services.size() == 0; int NUMA = app.activities.size(); int j; if (Config.LOGV) Slog.v( TAG, "Looking to quit " + app.processName); for (j=0; j<NUMA && canQuit; j++) { - HistoryRecord r = (HistoryRecord)app.activities.get(j); + ActivityRecord r = app.activities.get(j); if (Config.LOGV) Slog.v( TAG, " " + r.intent.getComponent().flattenToShortString() + ": frozen=" + r.haveState + ", visible=" + r.visible); @@ -14528,9 +12342,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (canQuit) { // Finish all of the activities, and then the app itself. for (j=0; j<NUMA; j++) { - HistoryRecord r = (HistoryRecord)app.activities.get(j); + ActivityRecord r = app.activities.get(j); if (!r.finishing) { - destroyActivityLocked(r, false); + r.stack.destroyActivityLocked(r, false); } r.resultTo = null; } @@ -14567,25 +12381,25 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Finally, if there are too many activities now running, try to // finish as many as we can to get back down to the limit. for ( i=0; - i<mLRUActivities.size() - && mLRUActivities.size() > curMaxActivities; + i<mMainStack.mLRUActivities.size() + && mMainStack.mLRUActivities.size() > curMaxActivities; i++) { - final HistoryRecord r - = (HistoryRecord)mLRUActivities.get(i); + final ActivityRecord r + = (ActivityRecord)mMainStack.mLRUActivities.get(i); // We can finish this one if we have its icicle saved and // it is not persistent. if ((r.haveState || !r.stateNotNeeded) && !r.visible - && r.stopped && !r.persistent && !r.finishing) { - final int origSize = mLRUActivities.size(); - destroyActivityLocked(r, true); + && r.stopped && !r.finishing) { + final int origSize = mMainStack.mLRUActivities.size(); + r.stack.destroyActivityLocked(r, true); // This will remove it from the LRU list, so keep // our index at the same value. Note that this check to // see if the size changes is just paranoia -- if // something unexpected happens, we don't want to end up // in an infinite loop. - if (origSize > mLRUActivities.size()) { + if (origSize > mMainStack.mLRUActivities.size()) { i--; } } diff --git a/services/java/com/android/server/am/HistoryRecord.java b/services/java/com/android/server/am/ActivityRecord.java index dca7a99..4d773e4 100644 --- a/services/java/com/android/server/am/HistoryRecord.java +++ b/services/java/com/android/server/am/ActivityRecord.java @@ -17,7 +17,7 @@ package com.android.server.am; import com.android.server.AttributeCache; -import com.android.server.am.ActivityManagerService.ActivityState; +import com.android.server.am.ActivityStack.ActivityState; import android.app.Activity; import android.content.ComponentName; @@ -29,9 +29,12 @@ import android.graphics.Bitmap; import android.os.Bundle; import android.os.Message; import android.os.Process; +import android.os.RemoteException; import android.os.SystemClock; import android.util.EventLog; import android.util.Log; +import android.util.Slog; +import android.util.TimeUtils; import android.view.IApplicationToken; import java.io.PrintWriter; @@ -42,8 +45,9 @@ import java.util.HashSet; /** * An entry in the history stack, representing an activity. */ -class HistoryRecord extends IApplicationToken.Stub { +class ActivityRecord extends IApplicationToken.Stub { final ActivityManagerService service; // owner + final ActivityStack stack; // owner final ActivityInfo info; // all about me final int launchedFromUid; // always the uid who started the activity. final Intent intent; // the original intent that generated us @@ -65,22 +69,22 @@ class HistoryRecord extends IApplicationToken.Stub { int icon; // resource identifier of activity's icon. int theme; // resource identifier of activity's theme. TaskRecord task; // the task this is in. - long startTime; // when we starting launching this activity + long launchTime; // when we starting launching this activity + long startTime; // last time this activity was started long cpuTimeAtResume; // the cpu time of host process at the time of resuming activity Configuration configuration; // configuration activity was last running in - HistoryRecord resultTo; // who started this entry, so will get our reply + ActivityRecord resultTo; // who started this entry, so will get our reply final String resultWho; // additional identifier for use by resultTo. final int requestCode; // code given by requester (resultTo) ArrayList results; // pending ActivityResult objs we have received HashSet<WeakReference<PendingIntentRecord>> pendingResults; // all pending intents for this act ArrayList newIntents; // any pending new intents for single-top mode HashSet<ConnectionRecord> connections; // All ConnectionRecord we hold - HashSet<UriPermission> readUriPermissions; // special access to reading uris. - HashSet<UriPermission> writeUriPermissions; // special access to writing uris. + UriPermissionOwner uriPermissions; // current special URI access perms. ProcessRecord app; // if non-null, hosting application Bitmap thumbnail; // icon representation of paused screen CharSequence description; // textual description of paused screen - ActivityManagerService.ActivityState state; // current state we are in + ActivityState state; // current state we are in Bundle icicle; // last saved activity state boolean frontOfTask; // is this the root activity of its task? boolean launchFailed; // set if a launched failed, to abort on 2nd try @@ -92,7 +96,6 @@ class HistoryRecord extends IApplicationToken.Stub { int configChangeFlags; // which config values have changed boolean keysPaused; // has key dispatching been paused for it? boolean inHistory; // are we in the history stack? - boolean persistent; // requested to be persistent? int launchMode; // the launch mode activity attribute. boolean visible; // does this activity's window need to be shown? boolean waitingVisible; // true if waiting for a new act to become vis @@ -137,11 +140,15 @@ class HistoryRecord extends IApplicationToken.Stub { if (pendingResults != null) { pw.print(prefix); pw.print("pendingResults="); pw.println(pendingResults); } - if (readUriPermissions != null) { - pw.print(prefix); pw.print("readUriPermissions="); pw.println(readUriPermissions); - } - if (writeUriPermissions != null) { - pw.print(prefix); pw.print("writeUriPermissions="); pw.println(writeUriPermissions); + if (uriPermissions != null) { + if (uriPermissions.readUriPermissions != null) { + pw.print(prefix); pw.print("readUriPermissions="); + pw.println(uriPermissions.readUriPermissions); + } + if (uriPermissions.writeUriPermissions != null) { + pw.print(prefix); pw.print("writeUriPermissions="); + pw.println(uriPermissions.writeUriPermissions); + } } pw.print(prefix); pw.print("launchFailed="); pw.print(launchFailed); pw.print(" haveState="); pw.print(haveState); @@ -152,13 +159,17 @@ class HistoryRecord 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(" persistent="); pw.print(persistent); 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(" idle="); pw.println(idle); + if (launchTime != 0 || startTime != 0) { + pw.print(prefix); pw.print("launchTime="); + TimeUtils.formatDuration(launchTime, pw); pw.print(" startTime="); + TimeUtils.formatDuration(startTime, pw); pw.println(""); + } if (waitingVisible || nowVisible) { pw.print(prefix); pw.print("waitingVisible="); pw.print(waitingVisible); pw.print(" nowVisible="); pw.println(nowVisible); @@ -173,12 +184,13 @@ class HistoryRecord extends IApplicationToken.Stub { } } - HistoryRecord(ActivityManagerService _service, ProcessRecord _caller, + ActivityRecord(ActivityManagerService _service, ActivityStack _stack, ProcessRecord _caller, int _launchedFromUid, Intent _intent, String _resolvedType, ActivityInfo aInfo, Configuration _configuration, - HistoryRecord _resultTo, String _resultWho, int _reqCode, + ActivityRecord _resultTo, String _resultWho, int _reqCode, boolean _componentSpecified) { service = _service; + stack = _stack; info = aInfo; launchedFromUid = _launchedFromUid; intent = _intent; @@ -189,7 +201,7 @@ class HistoryRecord extends IApplicationToken.Stub { resultTo = _resultTo; resultWho = _resultWho; requestCode = _reqCode; - state = ActivityManagerService.ActivityState.INITIALIZING; + state = ActivityState.INITIALIZING; frontOfTask = false; launchFailed = false; haveState = false; @@ -199,7 +211,6 @@ class HistoryRecord extends IApplicationToken.Stub { configDestroy = false; keysPaused = false; inHistory = false; - persistent = false; visible = true; waitingVisible = false; nowVisible = false; @@ -292,7 +303,14 @@ class HistoryRecord extends IApplicationToken.Stub { } } - void addResultLocked(HistoryRecord from, String resultWho, + UriPermissionOwner getUriPermissionsLocked() { + if (uriPermissions == null) { + uriPermissions = new UriPermissionOwner(service, this); + } + return uriPermissions; + } + + void addResultLocked(ActivityRecord from, String resultWho, int requestCode, int resultCode, Intent resultData) { ActivityResult r = new ActivityResult(from, resultWho, @@ -303,7 +321,7 @@ class HistoryRecord extends IApplicationToken.Stub { results.add(r); } - void removeResultsLocked(HistoryRecord from, String resultWho, + void removeResultsLocked(ActivityRecord from, String resultWho, int requestCode) { if (results != null) { for (int i=results.size()-1; i>=0; i--) { @@ -327,6 +345,42 @@ class HistoryRecord extends IApplicationToken.Stub { } newIntents.add(intent); } + + /** + * Deliver a new Intent to an existing activity, so that its onNewIntent() + * method will be called at the proper time. + */ + final void deliverNewIntentLocked(int callingUid, Intent intent) { + boolean sent = false; + if (state == ActivityState.RESUMED + && app != null && app.thread != null) { + try { + ArrayList<Intent> ar = new ArrayList<Intent>(); + intent = new Intent(intent); + ar.add(intent); + service.grantUriPermissionFromIntentLocked(callingUid, packageName, + intent, getUriPermissionsLocked()); + app.thread.scheduleNewIntent(ar, this); + sent = true; + } catch (RemoteException e) { + Slog.w(ActivityManagerService.TAG, + "Exception thrown sending new intent to " + this, e); + } catch (NullPointerException e) { + Slog.w(ActivityManagerService.TAG, + "Exception thrown sending new intent to " + this, e); + } + } + if (!sent) { + addNewIntentLocked(new Intent(intent)); + } + } + + void removeUriPermissionsLocked() { + if (uriPermissions != null) { + uriPermissions.removeUriPermissionsLocked(); + uriPermissions = null; + } + } void pauseKeyDispatchingLocked() { if (!keysPaused) { @@ -367,34 +421,37 @@ class HistoryRecord extends IApplicationToken.Stub { public void windowsVisible() { synchronized(service) { - if (startTime != 0) { + if (launchTime != 0) { final long curTime = SystemClock.uptimeMillis(); - final long thisTime = curTime - startTime; - final long totalTime = service.mInitialStartTime != 0 - ? (curTime - service.mInitialStartTime) : thisTime; + final long thisTime = curTime - launchTime; + final long totalTime = stack.mInitialStartTime != 0 + ? (curTime - stack.mInitialStartTime) : thisTime; if (ActivityManagerService.SHOW_ACTIVITY_START_TIME) { EventLog.writeEvent(EventLogTags.ACTIVITY_LAUNCH_TIME, System.identityHashCode(this), shortComponentName, thisTime, totalTime); StringBuilder sb = service.mStringBuilder; sb.setLength(0); - sb.append("Displayed activity "); + sb.append("Displayed "); sb.append(shortComponentName); sb.append(": "); - sb.append(thisTime); - sb.append(" ms (total "); - sb.append(totalTime); - sb.append(" ms)"); + TimeUtils.formatDuration(thisTime, sb); + if (thisTime != totalTime) { + sb.append(" (total "); + TimeUtils.formatDuration(totalTime, sb); + sb.append(")"); + } Log.i(ActivityManagerService.TAG, sb.toString()); } - service.reportActivityLaunchedLocked(false, this, thisTime, totalTime); + stack.reportActivityLaunchedLocked(false, this, thisTime, totalTime); if (totalTime > 0) { service.mUsageStatsService.noteLaunchTime(realActivity, (int)totalTime); } - startTime = 0; - service.mInitialStartTime = 0; + launchTime = 0; + stack.mInitialStartTime = 0; } - service.reportActivityVisibleLocked(this); + startTime = 0; + stack.reportActivityVisibleLocked(this); if (ActivityManagerService.DEBUG_SWITCH) Log.v( ActivityManagerService.TAG, "windowsVisible(): " + this); if (!nowVisible) { @@ -403,27 +460,27 @@ class HistoryRecord extends IApplicationToken.Stub { // Instead of doing the full stop routine here, let's just // hide any activities we now can, and let them stop when // the normal idle happens. - service.processStoppingActivitiesLocked(false); + stack.processStoppingActivitiesLocked(false); } else { // If this activity was already idle, then we now need to // make sure we perform the full stop of any activities // that are waiting to do so. This is because we won't // do that while they are still waiting for this one to // become visible. - final int N = service.mWaitingVisibleActivities.size(); + final int N = stack.mWaitingVisibleActivities.size(); if (N > 0) { for (int i=0; i<N; i++) { - HistoryRecord r = (HistoryRecord) - service.mWaitingVisibleActivities.get(i); + ActivityRecord r = (ActivityRecord) + stack.mWaitingVisibleActivities.get(i); r.waitingVisible = false; if (ActivityManagerService.DEBUG_SWITCH) Log.v( ActivityManagerService.TAG, "Was waiting for visible: " + r); } - service.mWaitingVisibleActivities.clear(); + stack.mWaitingVisibleActivities.clear(); Message msg = Message.obtain(); - msg.what = ActivityManagerService.IDLE_NOW_MSG; - service.mHandler.sendMessage(msg); + msg.what = ActivityStack.IDLE_NOW_MSG; + stack.mHandler.sendMessage(msg); } } service.scheduleAppGcsLocked(); @@ -437,16 +494,16 @@ class HistoryRecord extends IApplicationToken.Stub { nowVisible = false; } - private HistoryRecord getWaitingHistoryRecordLocked() { + private ActivityRecord getWaitingHistoryRecordLocked() { // First find the real culprit... if we are waiting // for another app to start, then we have paused dispatching // for this activity. - HistoryRecord r = this; + ActivityRecord r = this; if (r.waitingVisible) { // Hmmm, who might we be waiting for? - r = service.mResumedActivity; + r = stack.mResumedActivity; if (r == null) { - r = service.mPausingActivity; + r = stack.mPausingActivity; } // Both of those null? Fall back to 'this' again if (r == null) { @@ -458,7 +515,7 @@ class HistoryRecord extends IApplicationToken.Stub { } public boolean keyDispatchingTimedOut() { - HistoryRecord r; + ActivityRecord r; ProcessRecord anrApp = null; synchronized(service) { r = getWaitingHistoryRecordLocked(); @@ -496,7 +553,7 @@ class HistoryRecord extends IApplicationToken.Stub { /** Returns the key dispatching timeout for this application token. */ public long getKeyDispatchingTimeout() { synchronized(service) { - HistoryRecord r = getWaitingHistoryRecordLocked(); + ActivityRecord r = getWaitingHistoryRecordLocked(); if (r == null || r.app == null || r.app.instrumentationClass == null) { return ActivityManagerService.KEY_DISPATCHING_TIMEOUT; @@ -515,7 +572,6 @@ class HistoryRecord extends IApplicationToken.Stub { state == ActivityState.RESUMED; } - public String toString() { if (stringName != null) { return stringName; diff --git a/services/java/com/android/server/am/ActivityResult.java b/services/java/com/android/server/am/ActivityResult.java index 3cc2725..12eba34 100644 --- a/services/java/com/android/server/am/ActivityResult.java +++ b/services/java/com/android/server/am/ActivityResult.java @@ -24,9 +24,9 @@ import android.os.Bundle; * Pending result information to send back to an activity. */ class ActivityResult extends ResultInfo { - final HistoryRecord mFrom; + final ActivityRecord mFrom; - public ActivityResult(HistoryRecord from, String resultWho, + public ActivityResult(ActivityRecord from, String resultWho, int requestCode, int resultCode, Intent data) { super(resultWho, requestCode, resultCode, data); mFrom = from; diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java new file mode 100644 index 0000000..463493b --- /dev/null +++ b/services/java/com/android/server/am/ActivityStack.java @@ -0,0 +1,3557 @@ +/* + * 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.am; + +import com.android.internal.app.HeavyWeightSwitcherActivity; +import com.android.internal.os.BatteryStatsImpl; +import com.android.server.am.ActivityManagerService.PendingActivityLaunch; + +import android.app.Activity; +import android.app.AppGlobals; +import android.app.IActivityManager; +import static android.app.IActivityManager.START_CLASS_NOT_FOUND; +import static android.app.IActivityManager.START_DELIVERED_TO_TOP; +import static android.app.IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT; +import static android.app.IActivityManager.START_INTENT_NOT_RESOLVED; +import static android.app.IActivityManager.START_PERMISSION_DENIED; +import static android.app.IActivityManager.START_RETURN_INTENT_TO_CALLER; +import static android.app.IActivityManager.START_SUCCESS; +import static android.app.IActivityManager.START_SWITCHES_CANCELED; +import static android.app.IActivityManager.START_TASK_TO_FRONT; +import android.app.IApplicationThread; +import android.app.PendingIntent; +import android.app.ResultInfo; +import android.app.IActivityManager.WaitResult; +import android.content.ComponentName; +import android.content.Context; +import android.content.IIntentSender; +import android.content.Intent; +import android.content.IntentSender; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.EventLog; +import android.util.Log; +import android.util.Slog; +import android.view.WindowManagerPolicy; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * State and management of a single stack of activities. + */ +public class ActivityStack { + static final String TAG = ActivityManagerService.TAG; + static final boolean localLOGV = ActivityManagerService.localLOGV; + static final boolean DEBUG_SWITCH = ActivityManagerService.DEBUG_SWITCH; + static final boolean DEBUG_PAUSE = ActivityManagerService.DEBUG_PAUSE; + static final boolean DEBUG_VISBILITY = ActivityManagerService.DEBUG_VISBILITY; + static final boolean DEBUG_USER_LEAVING = ActivityManagerService.DEBUG_USER_LEAVING; + static final boolean DEBUG_TRANSITION = ActivityManagerService.DEBUG_TRANSITION; + static final boolean DEBUG_RESULTS = ActivityManagerService.DEBUG_RESULTS; + static final boolean DEBUG_CONFIGURATION = ActivityManagerService.DEBUG_CONFIGURATION; + static final boolean DEBUG_TASKS = ActivityManagerService.DEBUG_TASKS; + + static final boolean VALIDATE_TOKENS = ActivityManagerService.VALIDATE_TOKENS; + + // How long we wait until giving up on the last activity telling us it + // is idle. + static final int IDLE_TIMEOUT = 10*1000; + + // How long we wait until giving up on the last activity to pause. This + // is short because it directly impacts the responsiveness of starting the + // next activity. + static final int PAUSE_TIMEOUT = 500; + + // How long we can hold the launch wake lock before giving up. + static final int LAUNCH_TIMEOUT = 10*1000; + + // How long we wait until giving up on an activity telling us it has + // finished destroying itself. + 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; + + // How long between activity launches that we consider safe to not warn + // the user about an unexpected activity being launched on top. + static final long START_WARN_TIME = 5*1000; + + // Set to false to disable the preview that is shown while a new activity + // is being started. + static final boolean SHOW_APP_STARTING_PREVIEW = true; + + enum ActivityState { + INITIALIZING, + RESUMED, + PAUSING, + PAUSED, + STOPPING, + STOPPED, + FINISHING, + DESTROYING, + DESTROYED + } + + final ActivityManagerService mService; + final boolean mMainStack; + + final Context mContext; + + /** + * The back history of all previous (and possibly still + * running) activities. It contains HistoryRecord objects. + */ + final ArrayList mHistory = new ArrayList(); + + /** + * List of running activities, sorted by recent usage. + * The first entry in the list is the least recently used. + * It contains HistoryRecord objects. + */ + final ArrayList mLRUActivities = new ArrayList(); + + /** + * List of activities that are waiting for a new activity + * to become visible before completing whatever operation they are + * supposed to do. + */ + final ArrayList<ActivityRecord> mWaitingVisibleActivities + = new ArrayList<ActivityRecord>(); + + /** + * List of activities that are ready to be stopped, but waiting + * for the next activity to settle down before doing so. It contains + * HistoryRecord objects. + */ + final ArrayList<ActivityRecord> mStoppingActivities + = new ArrayList<ActivityRecord>(); + + /** + * Animations that for the current transition have requested not to + * be considered for the transition animation. + */ + final ArrayList<ActivityRecord> mNoAnimActivities + = new ArrayList<ActivityRecord>(); + + /** + * List of activities that are ready to be finished, but waiting + * for the previous activity to settle down before doing so. It contains + * HistoryRecord objects. + */ + final ArrayList<ActivityRecord> mFinishingActivities + = new ArrayList<ActivityRecord>(); + + /** + * List of people waiting to find out about the next launched activity. + */ + final ArrayList<IActivityManager.WaitResult> mWaitingActivityLaunched + = new ArrayList<IActivityManager.WaitResult>(); + + /** + * List of people waiting to find out about the next visible activity. + */ + final ArrayList<IActivityManager.WaitResult> mWaitingActivityVisible + = new ArrayList<IActivityManager.WaitResult>(); + + /** + * Set when the system is going to sleep, until we have + * successfully paused the current activity and released our wake lock. + * At that point the system is allowed to actually sleep. + */ + final PowerManager.WakeLock mGoingToSleep; + + /** + * We don't want to allow the device to go to sleep while in the process + * of launching an activity. This is primarily to allow alarm intent + * receivers to launch an activity and get that to run before the device + * goes back to sleep. + */ + final PowerManager.WakeLock mLaunchingActivity; + + /** + * When we are in the process of pausing an activity, before starting the + * next one, this variable holds the activity that is currently being paused. + */ + ActivityRecord mPausingActivity = null; + + /** + * This is the last activity that we put into the paused state. This is + * used to determine if we need to do an activity transition while sleeping, + * when we normally hold the top activity paused. + */ + ActivityRecord mLastPausedActivity = null; + + /** + * Current activity that is resumed, or null if there is none. + */ + ActivityRecord mResumedActivity = null; + + /** + * This is the last activity that has been started. It is only used to + * identify when multiple activities are started at once so that the user + * can be warned they may not be in the activity they think they are. + */ + ActivityRecord mLastStartedActivity = null; + + /** + * Set when we know we are going to be calling updateConfiguration() + * soon, so want to skip intermediate config checks. + */ + boolean mConfigWillChange; + + /** + * Set to indicate whether to issue an onUserLeaving callback when a + * newly launched activity is being brought in front of us. + */ + boolean mUserLeaving = false; + + long mInitialStartTime = 0; + + static final int PAUSE_TIMEOUT_MSG = 9; + static final int IDLE_TIMEOUT_MSG = 10; + static final int IDLE_NOW_MSG = 11; + static final int LAUNCH_TIMEOUT_MSG = 16; + static final int DESTROY_TIMEOUT_MSG = 17; + static final int RESUME_TOP_ACTIVITY_MSG = 19; + + final Handler mHandler = new Handler() { + //public Handler() { + // if (localLOGV) Slog.v(TAG, "Handler started!"); + //} + + public void handleMessage(Message msg) { + switch (msg.what) { + 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); + } break; + case IDLE_TIMEOUT_MSG: { + if (mService.mDidDexOpt) { + mService.mDidDexOpt = false; + Message nmsg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG); + nmsg.obj = msg.obj; + mHandler.sendMessageDelayed(nmsg, IDLE_TIMEOUT); + return; + } + // We don't at this point know if the activity is fullscreen, + // so we need to be conservative and assume it isn't. + IBinder token = (IBinder)msg.obj; + Slog.w(TAG, "Activity idle timeout for " + token); + activityIdleInternal(token, true, null); + } break; + case DESTROY_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 destroy timeout for " + token); + activityDestroyed(token); + } break; + case IDLE_NOW_MSG: { + IBinder token = (IBinder)msg.obj; + activityIdleInternal(token, false, null); + } break; + case LAUNCH_TIMEOUT_MSG: { + if (mService.mDidDexOpt) { + mService.mDidDexOpt = false; + Message nmsg = mHandler.obtainMessage(LAUNCH_TIMEOUT_MSG); + mHandler.sendMessageDelayed(nmsg, LAUNCH_TIMEOUT); + return; + } + synchronized (mService) { + if (mLaunchingActivity.isHeld()) { + Slog.w(TAG, "Launch timeout has expired, giving up wake lock!"); + mLaunchingActivity.release(); + } + } + } break; + case RESUME_TOP_ACTIVITY_MSG: { + synchronized (mService) { + resumeTopActivityLocked(null); + } + } break; + } + } + }; + + ActivityStack(ActivityManagerService service, Context context, boolean mainStack) { + mService = service; + mContext = context; + mMainStack = mainStack; + PowerManager pm = + (PowerManager)context.getSystemService(Context.POWER_SERVICE); + mGoingToSleep = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Sleep"); + mLaunchingActivity = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Launch"); + mLaunchingActivity.setReferenceCounted(false); + } + + final ActivityRecord topRunningActivityLocked(ActivityRecord notTop) { + int i = mHistory.size()-1; + while (i >= 0) { + ActivityRecord r = (ActivityRecord)mHistory.get(i); + if (!r.finishing && r != notTop) { + return r; + } + i--; + } + return null; + } + + final ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) { + int i = mHistory.size()-1; + while (i >= 0) { + ActivityRecord r = (ActivityRecord)mHistory.get(i); + if (!r.finishing && !r.delayedResume && r != notTop) { + return r; + } + i--; + } + return null; + } + + /** + * This is a simplified version of topRunningActivityLocked that provides a number of + * optional skip-over modes. It is intended for use with the ActivityController hook only. + * + * @param token If non-null, any history records matching this token will be skipped. + * @param taskId If non-zero, we'll attempt to skip over records with the same task ID. + * + * @return Returns the HistoryRecord of the next activity on the stack. + */ + final ActivityRecord topRunningActivityLocked(IBinder token, int taskId) { + int i = mHistory.size()-1; + while (i >= 0) { + ActivityRecord r = (ActivityRecord)mHistory.get(i); + // Note: the taskId check depends on real taskId fields being non-zero + if (!r.finishing && (token != r) && (taskId != r.task.taskId)) { + return r; + } + i--; + } + return null; + } + + final int indexOfTokenLocked(IBinder token) { + int count = mHistory.size(); + + // convert the token to an entry in the history. + int index = -1; + for (int i=count-1; i>=0; i--) { + Object o = mHistory.get(i); + if (o == token) { + index = i; + break; + } + } + + return index; + } + + private final boolean updateLRUListLocked(ActivityRecord r) { + final boolean hadit = mLRUActivities.remove(r); + mLRUActivities.add(r); + return hadit; + } + + /** + * Returns the top activity in any existing task matching the given + * Intent. Returns null if no such task is found. + */ + private ActivityRecord findTaskLocked(Intent intent, ActivityInfo info) { + ComponentName cls = intent.getComponent(); + if (info.targetActivity != null) { + cls = new ComponentName(info.packageName, info.targetActivity); + } + + TaskRecord cp = null; + + final int N = mHistory.size(); + for (int i=(N-1); i>=0; i--) { + ActivityRecord r = (ActivityRecord)mHistory.get(i); + if (!r.finishing && r.task != cp + && r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE) { + cp = r.task; + //Slog.i(TAG, "Comparing existing cls=" + r.task.intent.getComponent().flattenToShortString() + // + "/aff=" + r.task.affinity + " to new cls=" + // + intent.getComponent().flattenToShortString() + "/aff=" + taskAffinity); + if (r.task.affinity != null) { + if (r.task.affinity.equals(info.taskAffinity)) { + //Slog.i(TAG, "Found matching affinity!"); + return r; + } + } else if (r.task.intent != null + && r.task.intent.getComponent().equals(cls)) { + //Slog.i(TAG, "Found matching class!"); + //dump(); + //Slog.i(TAG, "For Intent " + intent + " bringing to top: " + r.intent); + return r; + } else if (r.task.affinityIntent != null + && r.task.affinityIntent.getComponent().equals(cls)) { + //Slog.i(TAG, "Found matching class!"); + //dump(); + //Slog.i(TAG, "For Intent " + intent + " bringing to top: " + r.intent); + return r; + } + } + } + + return null; + } + + /** + * Returns the first activity (starting from the top of the stack) that + * is the same as the given activity. Returns null if no such activity + * is found. + */ + private ActivityRecord findActivityLocked(Intent intent, ActivityInfo info) { + ComponentName cls = intent.getComponent(); + if (info.targetActivity != null) { + cls = new ComponentName(info.packageName, info.targetActivity); + } + + final int N = mHistory.size(); + for (int i=(N-1); i>=0; i--) { + ActivityRecord r = (ActivityRecord)mHistory.get(i); + if (!r.finishing) { + if (r.intent.getComponent().equals(cls)) { + //Slog.i(TAG, "Found matching class!"); + //dump(); + //Slog.i(TAG, "For Intent " + intent + " bringing to top: " + r.intent); + return r; + } + } + } + + return null; + } + + final boolean realStartActivityLocked(ActivityRecord r, + ProcessRecord app, boolean andResume, boolean checkConfig) + throws RemoteException { + + r.startFreezingScreenLocked(app, 0); + mService.mWindowManager.setAppVisibility(r, true); + + // Have the window manager re-evaluate the orientation of + // the screen based on the new activity order. Note that + // as a result of this, it can call back into the activity + // manager with a new orientation. We don't care about that, + // because the activity is not currently running so we are + // just restarting it anyway. + if (checkConfig) { + Configuration config = mService.mWindowManager.updateOrientationFromAppTokens( + mService.mConfiguration, + r.mayFreezeScreenLocked(app) ? r : null); + mService.updateConfigurationLocked(config, r); + } + + r.app = app; + + if (localLOGV) Slog.v(TAG, "Launching: " + r); + + int idx = app.activities.indexOf(r); + if (idx < 0) { + app.activities.add(r); + } + mService.updateLruProcessLocked(app, true, true); + + try { + if (app.thread == null) { + throw new RemoteException(); + } + List<ResultInfo> results = null; + List<Intent> newIntents = null; + if (andResume) { + results = r.results; + newIntents = r.newIntents; + } + if (DEBUG_SWITCH) Slog.v(TAG, "Launching: " + r + + " icicle=" + r.icicle + + " with results=" + results + " newIntents=" + newIntents + + " andResume=" + andResume); + if (andResume) { + EventLog.writeEvent(EventLogTags.AM_RESTART_ACTIVITY, + System.identityHashCode(r), + r.task.taskId, r.shortComponentName); + } + if (r.isHomeActivity) { + mService.mHomeProcess = app; + } + mService.ensurePackageDexOpt(r.intent.getComponent().getPackageName()); + app.thread.scheduleLaunchActivity(new Intent(r.intent), r, + System.identityHashCode(r), + r.info, r.icicle, results, newIntents, !andResume, + mService.isNextTransitionForward()); + + if ((app.info.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { + // This may be a heavy-weight process! Note that the package + // manager will ensure that only activity can run in the main + // process of the .apk, which is the only thing that will be + // considered heavy-weight. + if (app.processName.equals(app.info.packageName)) { + if (mService.mHeavyWeightProcess != null + && mService.mHeavyWeightProcess != app) { + Log.w(TAG, "Starting new heavy weight process " + app + + " when already running " + + mService.mHeavyWeightProcess); + } + mService.mHeavyWeightProcess = app; + Message msg = mService.mHandler.obtainMessage( + ActivityManagerService.POST_HEAVY_NOTIFICATION_MSG); + msg.obj = r; + mService.mHandler.sendMessage(msg); + } + } + + } catch (RemoteException e) { + if (r.launchFailed) { + // This is the second time we failed -- finish activity + // and give up. + Slog.e(TAG, "Second failure launching " + + r.intent.getComponent().flattenToShortString() + + ", giving up", e); + mService.appDiedLocked(app, app.pid, app.thread); + requestFinishActivityLocked(r, Activity.RESULT_CANCELED, null, + "2nd-crash"); + return false; + } + + // This is the first time we failed -- restart process and + // retry. + app.activities.remove(r); + throw e; + } + + r.launchFailed = false; + if (updateLRUListLocked(r)) { + Slog.w(TAG, "Activity " + r + + " being launched, but already in LRU list"); + } + + if (andResume) { + // 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(); + completeResumeLocked(r); + pauseIfSleepingLocked(); + } else { + // This activity is not starting in the resumed state... which + // should look like we asked it to pause+stop (but remain visible), + // and it has done so and reported back the current icicle and + // other state. + r.state = ActivityState.STOPPED; + r.stopped = true; + } + + // 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 + // switch back to it faster and look better. + if (mMainStack) { + mService.startSetupActivityLocked(); + } + + return true; + } + + private final void startSpecificActivityLocked(ActivityRecord r, + boolean andResume, boolean checkConfig) { + // Is this activity's application already running? + ProcessRecord app = mService.getProcessRecordLocked(r.processName, + r.info.applicationInfo.uid); + + if (r.launchTime == 0) { + r.launchTime = SystemClock.uptimeMillis(); + if (mInitialStartTime == 0) { + mInitialStartTime = r.launchTime; + } + } else if (mInitialStartTime == 0) { + mInitialStartTime = SystemClock.uptimeMillis(); + } + + if (app != null && app.thread != null) { + try { + realStartActivityLocked(r, app, andResume, checkConfig); + return; + } catch (RemoteException e) { + Slog.w(TAG, "Exception when starting activity " + + r.intent.getComponent().flattenToShortString(), e); + } + + // If a dead object exception was thrown -- fall through to + // restart the application. + } + + mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0, + "activity", r.intent.getComponent(), false); + } + + void pauseIfSleepingLocked() { + if (mService.mSleeping || mService.mShuttingDown) { + if (!mGoingToSleep.isHeld()) { + mGoingToSleep.acquire(); + if (mLaunchingActivity.isHeld()) { + mLaunchingActivity.release(); + mService.mHandler.removeMessages(LAUNCH_TIMEOUT_MSG); + } + } + + // 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..."); + if (DEBUG_USER_LEAVING) Slog.v(TAG, "Sleep => pause with userLeaving=false"); + startPausingLocked(false, true); + } + } + } + + private final void startPausingLocked(boolean userLeaving, boolean uiSleeping) { + if (mPausingActivity != null) { + RuntimeException e = new RuntimeException(); + Slog.e(TAG, "Trying to pause when pause is already pending for " + + mPausingActivity, e); + } + ActivityRecord prev = mResumedActivity; + if (prev == null) { + RuntimeException e = new RuntimeException(); + Slog.e(TAG, "Trying to pause when nothing is resumed", e); + resumeTopActivityLocked(null); + return; + } + if (DEBUG_PAUSE) Slog.v(TAG, "Start pausing: " + prev); + mResumedActivity = null; + mPausingActivity = prev; + mLastPausedActivity = prev; + prev.state = ActivityState.PAUSING; + prev.task.touchActiveTime(); + + mService.updateCpuStats(); + + if (prev.app != null && prev.app.thread != null) { + if (DEBUG_PAUSE) Slog.v(TAG, "Enqueueing pending pause: " + prev); + try { + EventLog.writeEvent(EventLogTags.AM_PAUSE_ACTIVITY, + System.identityHashCode(prev), + prev.shortComponentName); + prev.app.thread.schedulePauseActivity(prev, prev.finishing, userLeaving, + prev.configChangeFlags); + if (mMainStack) { + mService.updateUsageStats(prev, false); + } + } catch (Exception e) { + // Ignore exception, if process died other code will cleanup. + Slog.w(TAG, "Exception thrown during pause", e); + mPausingActivity = null; + mLastPausedActivity = null; + } + } else { + mPausingActivity = null; + mLastPausedActivity = null; + } + + // If we are not going to sleep, we want to ensure the device is + // awake until the next activity is started. + if (!mService.mSleeping && !mService.mShuttingDown) { + mLaunchingActivity.acquire(); + if (!mHandler.hasMessages(LAUNCH_TIMEOUT_MSG)) { + // To be safe, don't allow the wake lock to be held for too long. + Message msg = mHandler.obtainMessage(LAUNCH_TIMEOUT_MSG); + mHandler.sendMessageDelayed(msg, LAUNCH_TIMEOUT); + } + } + + + if (mPausingActivity != null) { + // Have the window manager pause its key dispatching until the new + // activity has started. If we're pausing the activity just because + // the screen is being turned off and the UI is sleeping, don't interrupt + // key dispatch; the same activity will pick it up again on wakeup. + if (!uiSleeping) { + prev.pauseKeyDispatchingLocked(); + } else { + if (DEBUG_PAUSE) Slog.v(TAG, "Key dispatch not paused for screen off"); + } + + // Schedule a pause timeout in case the app doesn't respond. + // We don't give it much time because this directly impacts the + // responsiveness seen by the user. + Message msg = mHandler.obtainMessage(PAUSE_TIMEOUT_MSG); + msg.obj = prev; + mHandler.sendMessageDelayed(msg, PAUSE_TIMEOUT); + if (DEBUG_PAUSE) Slog.v(TAG, "Waiting for pause to complete..."); + } else { + // This activity failed to schedule the + // pause, so just treat it as being paused now. + if (DEBUG_PAUSE) Slog.v(TAG, "Activity not running, resuming next."); + resumeTopActivityLocked(null); + } + } + + final void activityPaused(IBinder token, Bundle icicle, boolean timeout) { + if (DEBUG_PAUSE) Slog.v( + TAG, "Activity paused: token=" + token + ", icicle=" + icicle + + ", timeout=" + timeout); + + ActivityRecord r = null; + + synchronized (mService) { + 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; + completePauseLocked(); + } else { + EventLog.writeEvent(EventLogTags.AM_FAILED_TO_PAUSE, + System.identityHashCode(r), r.shortComponentName, + mPausingActivity != null + ? mPausingActivity.shortComponentName : "(none)"); + } + } + } + } + + private final void completePauseLocked() { + ActivityRecord prev = mPausingActivity; + if (DEBUG_PAUSE) Slog.v(TAG, "Complete pause: " + prev); + + if (prev != null) { + if (prev.finishing) { + if (DEBUG_PAUSE) Slog.v(TAG, "Executing finish of activity: " + prev); + prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE); + } else if (prev.app != null) { + if (DEBUG_PAUSE) Slog.v(TAG, "Enqueueing pending stop: " + prev); + if (prev.waitingVisible) { + prev.waitingVisible = false; + mWaitingVisibleActivities.remove(prev); + if (DEBUG_SWITCH || DEBUG_PAUSE) Slog.v( + TAG, "Complete pause, no longer waiting: " + prev); + } + if (prev.configDestroy) { + // The previous is being paused because the configuration + // is changing, which means it is actually stopping... + // To juggle the fact that we are also starting a new + // instance right now, we need to first completely stop + // the current instance before starting the new one. + if (DEBUG_PAUSE) Slog.v(TAG, "Destroying after pause: " + prev); + destroyActivityLocked(prev, true); + } else { + mStoppingActivities.add(prev); + if (mStoppingActivities.size() > 3) { + // If we already have a few activities waiting to stop, + // then give up on things going idle and start clearing + // them out. + if (DEBUG_PAUSE) Slog.v(TAG, "To many pending stops, forcing idle"); + Message msg = Message.obtain(); + msg.what = IDLE_NOW_MSG; + mHandler.sendMessage(msg); + } + } + } else { + if (DEBUG_PAUSE) Slog.v(TAG, "App died during pause, not stopping: " + prev); + prev = null; + } + mPausingActivity = null; + } + + if (!mService.mSleeping && !mService.mShuttingDown) { + resumeTopActivityLocked(prev); + } else { + if (mGoingToSleep.isHeld()) { + mGoingToSleep.release(); + } + if (mService.mShuttingDown) { + mService.notifyAll(); + } + } + + if (prev != null) { + prev.resumeKeyDispatchingLocked(); + } + + if (prev.app != null && prev.cpuTimeAtResume > 0 + && mService.mBatteryStatsService.isOnBattery()) { + long diff = 0; + synchronized (mService.mProcessStatsThread) { + diff = mService.mProcessStats.getCpuTimeForPid(prev.app.pid) + - prev.cpuTimeAtResume; + } + if (diff > 0) { + BatteryStatsImpl bsi = mService.mBatteryStatsService.getActiveStatistics(); + synchronized (bsi) { + BatteryStatsImpl.Uid.Proc ps = + bsi.getProcessStatsLocked(prev.info.applicationInfo.uid, + prev.info.packageName); + if (ps != null) { + ps.addForegroundTimeLocked(diff); + } + } + } + } + prev.cpuTimeAtResume = 0; // reset it + } + + /** + * Once we know that we have asked an application to put an activity in + * the resumed state (either by launching it or explicitly telling it), + * this function updates the rest of our state to match that fact. + */ + private final void completeResumeLocked(ActivityRecord next) { + next.idle = false; + next.results = null; + next.newIntents = null; + + // schedule an idle timeout in case the app doesn't do it for us. + Message msg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG); + msg.obj = next; + mHandler.sendMessageDelayed(msg, IDLE_TIMEOUT); + + if (false) { + // The activity was never told to pause, so just keep + // things going as-is. To maintain our own state, + // we need to emulate it coming back and saying it is + // idle. + msg = mHandler.obtainMessage(IDLE_NOW_MSG); + msg.obj = next; + mHandler.sendMessage(msg); + } + + if (mMainStack) { + mService.reportResumedActivityLocked(next); + } + + next.thumbnail = null; + if (mMainStack) { + mService.setFocusedActivityLocked(next); + } + next.resumeKeyDispatchingLocked(); + ensureActivitiesVisibleLocked(null, 0); + mService.mWindowManager.executeAppTransition(); + mNoAnimActivities.clear(); + + // Mark the point when the activity is resuming + // TODO: To be more accurate, the mark should be before the onCreate, + // not after the onResume. But for subsequent starts, onResume is fine. + if (next.app != null) { + synchronized (mService.mProcessStatsThread) { + next.cpuTimeAtResume = mService.mProcessStats.getCpuTimeForPid(next.app.pid); + } + } else { + next.cpuTimeAtResume = 0; // Couldn't get the cpu time of process + } + } + + /** + * Make sure that all activities that need to be visible (that is, they + * currently can be seen by the user) actually are. + */ + final void ensureActivitiesVisibleLocked(ActivityRecord top, + ActivityRecord starting, String onlyThisProcess, int configChanges) { + if (DEBUG_VISBILITY) Slog.v( + TAG, "ensureActivitiesVisible behind " + top + + " configChanges=0x" + Integer.toHexString(configChanges)); + + // If the top activity is not fullscreen, then we need to + // make sure any activities under it are now visible. + final int count = mHistory.size(); + int i = count-1; + while (mHistory.get(i) != top) { + i--; + } + ActivityRecord r; + boolean behindFullscreen = false; + for (; i>=0; i--) { + r = (ActivityRecord)mHistory.get(i); + if (DEBUG_VISBILITY) Slog.v( + TAG, "Make visible? " + r + " finishing=" + r.finishing + + " state=" + r.state); + if (r.finishing) { + continue; + } + + final boolean doThisProcess = onlyThisProcess == null + || onlyThisProcess.equals(r.processName); + + // First: if this is not the current activity being started, make + // sure it matches the current configuration. + if (r != starting && doThisProcess) { + ensureActivityConfigurationLocked(r, 0); + } + + if (r.app == null || r.app.thread == null) { + if (onlyThisProcess == null + || onlyThisProcess.equals(r.processName)) { + // This activity needs to be visible, but isn't even + // running... get it started, but don't resume it + // at this point. + if (DEBUG_VISBILITY) Slog.v( + TAG, "Start and freeze screen for " + r); + if (r != starting) { + r.startFreezingScreenLocked(r.app, configChanges); + } + if (!r.visible) { + if (DEBUG_VISBILITY) Slog.v( + TAG, "Starting and making visible: " + r); + mService.mWindowManager.setAppVisibility(r, true); + } + if (r != starting) { + startSpecificActivityLocked(r, false, false); + } + } + + } else if (r.visible) { + // If this activity is already visible, then there is nothing + // else to do here. + if (DEBUG_VISBILITY) Slog.v( + TAG, "Skipping: already visible at " + r); + r.stopFreezingScreenLocked(false); + + } else if (onlyThisProcess == null) { + // This activity is not currently visible, but is running. + // Tell it to become visible. + r.visible = true; + if (r.state != ActivityState.RESUMED && r != starting) { + // If this activity is paused, tell it + // to now show its window. + if (DEBUG_VISBILITY) Slog.v( + TAG, "Making visible and scheduling visibility: " + r); + try { + mService.mWindowManager.setAppVisibility(r, true); + r.app.thread.scheduleWindowVisibility(r, true); + r.stopFreezingScreenLocked(false); + } catch (Exception e) { + // Just skip on any failure; we'll make it + // visible when it next restarts. + Slog.w(TAG, "Exception thrown making visibile: " + + r.intent.getComponent(), e); + } + } + } + + // Aggregate current change flags. + configChanges |= r.configChangeFlags; + + if (r.fullscreen) { + // At this point, nothing else needs to be shown + if (DEBUG_VISBILITY) Slog.v( + TAG, "Stopping: fullscreen at " + r); + behindFullscreen = true; + i--; + break; + } + } + + // Now for any activities that aren't visible to the user, make + // sure they no longer are keeping the screen frozen. + while (i >= 0) { + r = (ActivityRecord)mHistory.get(i); + if (DEBUG_VISBILITY) Slog.v( + TAG, "Make invisible? " + r + " finishing=" + r.finishing + + " state=" + r.state + + " behindFullscreen=" + behindFullscreen); + if (!r.finishing) { + if (behindFullscreen) { + if (r.visible) { + if (DEBUG_VISBILITY) Slog.v( + TAG, "Making invisible: " + r); + r.visible = false; + try { + mService.mWindowManager.setAppVisibility(r, false); + if ((r.state == ActivityState.STOPPING + || r.state == ActivityState.STOPPED) + && r.app != null && r.app.thread != null) { + if (DEBUG_VISBILITY) Slog.v( + TAG, "Scheduling invisibility: " + r); + r.app.thread.scheduleWindowVisibility(r, false); + } + } catch (Exception e) { + // Just skip on any failure; we'll make it + // visible when it next restarts. + Slog.w(TAG, "Exception thrown making hidden: " + + r.intent.getComponent(), e); + } + } else { + if (DEBUG_VISBILITY) Slog.v( + TAG, "Already invisible: " + r); + } + } else if (r.fullscreen) { + if (DEBUG_VISBILITY) Slog.v( + TAG, "Now behindFullscreen: " + r); + behindFullscreen = true; + } + } + i--; + } + } + + /** + * Version of ensureActivitiesVisible that can easily be called anywhere. + */ + final void ensureActivitiesVisibleLocked(ActivityRecord starting, + int configChanges) { + ActivityRecord r = topRunningActivityLocked(null); + if (r != null) { + ensureActivitiesVisibleLocked(r, starting, null, configChanges); + } + } + + /** + * Ensure that the top activity in the stack is resumed. + * + * @param prev The previously resumed activity, for when in the process + * of pausing; can be null to call from elsewhere. + * + * @return Returns true if something is being resumed, or false if + * nothing happened. + */ + final boolean resumeTopActivityLocked(ActivityRecord prev) { + // Find the first activity that is not finishing. + ActivityRecord next = topRunningActivityLocked(null); + + // Remember how we'll process this pause/resume situation, and ensure + // that the state is reset however we wind up proceeding. + final boolean userLeaving = mUserLeaving; + mUserLeaving = false; + + if (next == null) { + // There are no more activities! Let's just start up the + // Launcher... + if (mMainStack) { + return mService.startHomeActivityLocked(); + } + } + + next.delayedResume = false; + + // If the top activity is the resumed one, nothing to do. + if (mResumedActivity == next && next.state == ActivityState.RESUMED) { + // Make sure we have executed any pending transitions, since there + // should be nothing left to do at this point. + mService.mWindowManager.executeAppTransition(); + mNoAnimActivities.clear(); + return false; + } + + // If we are sleeping, and there is no resumed activity, and the top + // activity is paused, well that is the state we want. + if ((mService.mSleeping || mService.mShuttingDown) + && mLastPausedActivity == next && next.state == ActivityState.PAUSED) { + // Make sure we have executed any pending transitions, since there + // should be nothing left to do at this point. + mService.mWindowManager.executeAppTransition(); + mNoAnimActivities.clear(); + return false; + } + + // The activity may be waiting for stop, but that is no longer + // appropriate for it. + mStoppingActivities.remove(next); + mWaitingVisibleActivities.remove(next); + + if (DEBUG_SWITCH) Slog.v(TAG, "Resuming " + next); + + // If we are currently pausing an activity, then don't do anything + // until that is done. + if (mPausingActivity != null) { + if (DEBUG_SWITCH) Slog.v(TAG, "Skip resume: pausing=" + mPausingActivity); + return false; + } + + // Okay we are now going to start a switch, to 'next'. We may first + // have to pause the current activity, but this is an important point + // where we have decided to go to 'next' so keep track of that. + // XXX "App Redirected" dialog is getting too many false positives + // at this point, so turn off for now. + if (false) { + if (mLastStartedActivity != null && !mLastStartedActivity.finishing) { + long now = SystemClock.uptimeMillis(); + final boolean inTime = mLastStartedActivity.startTime != 0 + && (mLastStartedActivity.startTime + START_WARN_TIME) >= now; + final int lastUid = mLastStartedActivity.info.applicationInfo.uid; + final int nextUid = next.info.applicationInfo.uid; + if (inTime && lastUid != nextUid + && lastUid != next.launchedFromUid + && mService.checkPermission( + android.Manifest.permission.STOP_APP_SWITCHES, + -1, next.launchedFromUid) + != PackageManager.PERMISSION_GRANTED) { + mService.showLaunchWarningLocked(mLastStartedActivity, next); + } else { + next.startTime = now; + mLastStartedActivity = next; + } + } else { + next.startTime = SystemClock.uptimeMillis(); + mLastStartedActivity = next; + } + } + + // We need to start pausing the current activity so the top one + // can be resumed... + if (mResumedActivity != null) { + if (DEBUG_SWITCH) Slog.v(TAG, "Skip resume: need to start pausing"); + startPausingLocked(userLeaving, false); + return true; + } + + if (prev != null && prev != next) { + if (!prev.waitingVisible && next != null && !next.nowVisible) { + prev.waitingVisible = true; + mWaitingVisibleActivities.add(prev); + if (DEBUG_SWITCH) Slog.v( + TAG, "Resuming top, waiting visible to hide: " + prev); + } else { + // The next activity is already visible, so hide the previous + // activity's windows right now so we can show the new one ASAP. + // We only do this if the previous is finishing, which should mean + // it is on top of the one being resumed so hiding it quickly + // is good. Otherwise, we want to do the normal route of allowing + // the resumed activity to be shown so we can decide if the + // previous should actually be hidden depending on whether the + // new one is found to be full-screen or not. + if (prev.finishing) { + mService.mWindowManager.setAppVisibility(prev, false); + if (DEBUG_SWITCH) Slog.v(TAG, "Not waiting for visible to hide: " + + prev + ", waitingVisible=" + + (prev != null ? prev.waitingVisible : null) + + ", nowVisible=" + next.nowVisible); + } else { + if (DEBUG_SWITCH) Slog.v(TAG, "Previous already visible but still waiting to hide: " + + prev + ", waitingVisible=" + + (prev != null ? prev.waitingVisible : null) + + ", nowVisible=" + next.nowVisible); + } + } + } + + // 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. + if (prev != null) { + if (prev.finishing) { + if (DEBUG_TRANSITION) Slog.v(TAG, + "Prepare close transition: prev=" + prev); + if (mNoAnimActivities.contains(prev)) { + mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + } else { + mService.mWindowManager.prepareAppTransition(prev.task == next.task + ? WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE + : WindowManagerPolicy.TRANSIT_TASK_CLOSE); + } + mService.mWindowManager.setAppWillBeHidden(prev); + mService.mWindowManager.setAppVisibility(prev, false); + } else { + if (DEBUG_TRANSITION) Slog.v(TAG, + "Prepare open transition: prev=" + prev); + if (mNoAnimActivities.contains(next)) { + mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + } else { + mService.mWindowManager.prepareAppTransition(prev.task == next.task + ? WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN + : WindowManagerPolicy.TRANSIT_TASK_OPEN); + } + } + if (false) { + mService.mWindowManager.setAppWillBeHidden(prev); + mService.mWindowManager.setAppVisibility(prev, false); + } + } else if (mHistory.size() > 1) { + if (DEBUG_TRANSITION) Slog.v(TAG, + "Prepare open transition: no previous"); + if (mNoAnimActivities.contains(next)) { + mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + } else { + mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN); + } + } + + if (next.app != null && next.app.thread != null) { + if (DEBUG_SWITCH) Slog.v(TAG, "Resume running: " + next); + + // This activity is now becoming visible. + mService.mWindowManager.setAppVisibility(next, true); + + ActivityRecord lastResumedActivity = mResumedActivity; + ActivityState lastState = next.state; + + mService.updateCpuStats(); + + next.state = ActivityState.RESUMED; + mResumedActivity = next; + next.task.touchActiveTime(); + mService.updateLruProcessLocked(next.app, true, true); + updateLRUListLocked(next); + + // Have the window manager re-evaluate the orientation of + // the screen based on the new activity order. + boolean updated = false; + if (mMainStack) { + synchronized (mService) { + Configuration config = mService.mWindowManager.updateOrientationFromAppTokens( + mService.mConfiguration, + next.mayFreezeScreenLocked(next.app) ? next : null); + if (config != null) { + next.frozenBeforeDestroy = true; + } + updated = mService.updateConfigurationLocked(config, next); + } + } + if (!updated) { + // The configuration update wasn't able to keep the existing + // instance of the activity, and instead started a new one. + // We should be all done, but let's just make sure our activity + // is still at the top and schedule another run if something + // weird happened. + ActivityRecord nextNext = topRunningActivityLocked(null); + if (DEBUG_SWITCH) Slog.i(TAG, + "Activity config changed during resume: " + next + + ", new next: " + nextNext); + if (nextNext != next) { + // Do over! + mHandler.sendEmptyMessage(RESUME_TOP_ACTIVITY_MSG); + } + if (mMainStack) { + mService.setFocusedActivityLocked(next); + } + ensureActivitiesVisibleLocked(null, 0); + mService.mWindowManager.executeAppTransition(); + mNoAnimActivities.clear(); + return true; + } + + try { + // Deliver all pending results. + ArrayList a = next.results; + if (a != null) { + final int N = a.size(); + if (!next.finishing && N > 0) { + if (DEBUG_RESULTS) Slog.v( + TAG, "Delivering results to " + next + + ": " + a); + next.app.thread.scheduleSendResult(next, a); + } + } + + if (next.newIntents != null) { + next.app.thread.scheduleNewIntent(next.newIntents, next); + } + + EventLog.writeEvent(EventLogTags.AM_RESUME_ACTIVITY, + System.identityHashCode(next), + next.task.taskId, next.shortComponentName); + + next.app.thread.scheduleResumeActivity(next, + mService.isNextTransitionForward()); + + pauseIfSleepingLocked(); + + } catch (Exception e) { + // Whoops, need to restart this activity! + next.state = lastState; + mResumedActivity = lastResumedActivity; + Slog.i(TAG, "Restarting because process died: " + next); + if (!next.hasBeenLaunched) { + next.hasBeenLaunched = true; + } else { + if (SHOW_APP_STARTING_PREVIEW && mMainStack) { + mService.mWindowManager.setAppStartingWindow( + next, next.packageName, next.theme, + next.nonLocalizedLabel, + next.labelRes, next.icon, null, true); + } + } + startSpecificActivityLocked(next, true, false); + return true; + } + + // From this point on, if something goes wrong there is no way + // to recover the activity. + try { + next.visible = true; + completeResumeLocked(next); + } catch (Exception e) { + // If any exception gets thrown, toss away this + // activity and try the next one. + Slog.w(TAG, "Exception thrown during resume of " + next, e); + requestFinishActivityLocked(next, Activity.RESULT_CANCELED, null, + "resume-exception"); + return true; + } + + // Didn't need to use the icicle, and it is now out of date. + next.icicle = null; + next.haveState = false; + next.stopped = false; + + } else { + // Whoops, need to restart this activity! + if (!next.hasBeenLaunched) { + next.hasBeenLaunched = true; + } else { + if (SHOW_APP_STARTING_PREVIEW) { + mService.mWindowManager.setAppStartingWindow( + next, next.packageName, next.theme, + next.nonLocalizedLabel, + next.labelRes, next.icon, null, true); + } + if (DEBUG_SWITCH) Slog.v(TAG, "Restarting: " + next); + } + startSpecificActivityLocked(next, true, true); + } + + return true; + } + + private final void startActivityLocked(ActivityRecord r, boolean newTask, + boolean doResume) { + final int NH = mHistory.size(); + + int addPos = -1; + + if (!newTask) { + // If starting in an existing task, find where that is... + boolean startIt = true; + for (int i = NH-1; i >= 0; i--) { + ActivityRecord p = (ActivityRecord)mHistory.get(i); + if (p.finishing) { + continue; + } + if (p.task == r.task) { + // Here it is! Now, if this is not yet visible to the + // user, then just add it without starting; it will + // get started when the user navigates back to it. + addPos = i+1; + if (!startIt) { + mHistory.add(addPos, r); + r.inHistory = true; + r.task.numActivities++; + mService.mWindowManager.addAppToken(addPos, r, r.task.taskId, + r.info.screenOrientation, r.fullscreen); + if (VALIDATE_TOKENS) { + mService.mWindowManager.validateAppTokens(mHistory); + } + return; + } + break; + } + if (p.fullscreen) { + startIt = false; + } + } + } + + // Place a new activity at top of stack, so it is next to interact + // with the user. + if (addPos < 0) { + addPos = NH; + } + + // If we are not placing the new activity frontmost, we do not want + // to deliver the onUserLeaving callback to the actual frontmost + // activity + if (addPos < NH) { + mUserLeaving = false; + if (DEBUG_USER_LEAVING) Slog.v(TAG, "startActivity() behind front, mUserLeaving=false"); + } + + // Slot the activity into the history stack and proceed + mHistory.add(addPos, r); + r.inHistory = true; + r.frontOfTask = newTask; + r.task.numActivities++; + if (NH > 0) { + // We want to show the starting preview window if we are + // switching to a new task, or the next activity's process is + // not currently running. + boolean showStartingIcon = newTask; + ProcessRecord proc = r.app; + if (proc == null) { + proc = mService.mProcessNames.get(r.processName, r.info.applicationInfo.uid); + } + if (proc == null || proc.thread == null) { + showStartingIcon = true; + } + 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); + mNoAnimActivities.add(r); + } else if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) { + mService.mWindowManager.prepareAppTransition( + WindowManagerPolicy.TRANSIT_TASK_OPEN); + mNoAnimActivities.remove(r); + } else { + mService.mWindowManager.prepareAppTransition(newTask + ? WindowManagerPolicy.TRANSIT_TASK_OPEN + : WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN); + mNoAnimActivities.remove(r); + } + mService.mWindowManager.addAppToken( + addPos, r, r.task.taskId, r.info.screenOrientation, r.fullscreen); + boolean doShow = true; + if (newTask) { + // Even though this activity is starting fresh, we still need + // to reset it to make sure we apply affinities to move any + // existing activities from other tasks in to it. + // If the caller has requested that the target task be + // reset, then do so. + if ((r.intent.getFlags() + &Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { + resetTaskIfNeededLocked(r, r); + doShow = topRunningNonDelayedActivityLocked(null) == r; + } + } + if (SHOW_APP_STARTING_PREVIEW && doShow) { + // Figure out if we are transitioning from another activity that is + // "has the same starting icon" as the next one. This allows the + // window manager to keep the previous window it had previously + // created, if it still had one. + ActivityRecord prev = mResumedActivity; + if (prev != null) { + // We don't want to reuse the previous starting preview if: + // (1) The current activity is in a different task. + if (prev.task != r.task) prev = null; + // (2) The current activity is already displayed. + else if (prev.nowVisible) prev = null; + } + mService.mWindowManager.setAppStartingWindow( + r, r.packageName, r.theme, r.nonLocalizedLabel, + r.labelRes, r.icon, prev, showStartingIcon); + } + } else { + // If this is the first activity, don't do any fancy animations, + // because there is nothing for it to animate on top of. + mService.mWindowManager.addAppToken(addPos, r, r.task.taskId, + r.info.screenOrientation, r.fullscreen); + } + if (VALIDATE_TOKENS) { + mService.mWindowManager.validateAppTokens(mHistory); + } + + if (doResume) { + resumeTopActivityLocked(null); + } + } + + /** + * Perform a reset of the given task, if needed as part of launching it. + * Returns the new HistoryRecord at the top of the task. + */ + private final ActivityRecord resetTaskIfNeededLocked(ActivityRecord taskTop, + ActivityRecord newActivity) { + boolean forceReset = (newActivity.info.flags + &ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0; + if (taskTop.task.getInactiveDuration() > ACTIVITY_INACTIVE_RESET_TIME) { + if ((newActivity.info.flags + &ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE) == 0) { + forceReset = true; + } + } + + final TaskRecord task = taskTop.task; + + // We are going to move through the history list so that we can look + // at each activity 'target' with 'below' either the interesting + // activity immediately below it in the stack or null. + ActivityRecord target = null; + int targetI = 0; + int taskTopI = -1; + int replyChainEnd = -1; + int lastReparentPos = -1; + for (int i=mHistory.size()-1; i>=-1; i--) { + ActivityRecord below = i >= 0 ? (ActivityRecord)mHistory.get(i) : null; + + if (below != null && below.finishing) { + continue; + } + if (target == null) { + target = below; + targetI = i; + // If we were in the middle of a reply chain before this + // task, it doesn't appear like the root of the chain wants + // anything interesting, so drop it. + replyChainEnd = -1; + continue; + } + + final int flags = target.info.flags; + + final boolean finishOnTaskLaunch = + (flags&ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH) != 0; + final boolean allowTaskReparenting = + (flags&ActivityInfo.FLAG_ALLOW_TASK_REPARENTING) != 0; + + if (target.task == task) { + // We are inside of the task being reset... we'll either + // finish this activity, push it out for another task, + // or leave it as-is. We only do this + // for activities that are not the root of the task (since + // if we finish the root, we may no longer have the task!). + if (taskTopI < 0) { + taskTopI = targetI; + } + if (below != null && below.task == task) { + final boolean clearWhenTaskReset = + (target.intent.getFlags() + &Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0; + if (!finishOnTaskLaunch && !clearWhenTaskReset && target.resultTo != null) { + // If this activity is sending a reply to a previous + // activity, we can't do anything with it now until + // we reach the start of the reply chain. + // XXX note that we are assuming the result is always + // to the previous activity, which is almost always + // the case but we really shouldn't count on. + if (replyChainEnd < 0) { + replyChainEnd = targetI; + } + } else if (!finishOnTaskLaunch && !clearWhenTaskReset && allowTaskReparenting + && target.taskAffinity != null + && !target.taskAffinity.equals(task.affinity)) { + // If this activity has an affinity for another + // task, then we need to move it out of here. We will + // move it as far out of the way as possible, to the + // bottom of the activity stack. This also keeps it + // correctly ordered with any activities we previously + // moved. + ActivityRecord p = (ActivityRecord)mHistory.get(0); + if (target.taskAffinity != null + && target.taskAffinity.equals(p.task.affinity)) { + // If the activity currently at the bottom has the + // same task affinity as the one we are moving, + // then merge it into the same task. + target.task = p.task; + if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target + + " out to bottom task " + p.task); + } else { + mService.mCurTask++; + 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.affinityIntent = target.intent; + if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target + + " out to new task " + target.task); + } + mService.mWindowManager.setAppGroupId(target, task.taskId); + if (replyChainEnd < 0) { + replyChainEnd = targetI; + } + int dstPos = 0; + for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) { + p = (ActivityRecord)mHistory.get(srcPos); + if (p.finishing) { + continue; + } + if (DEBUG_TASKS) Slog.v(TAG, "Pushing next activity " + p + + " out to target's task " + target.task); + task.numActivities--; + p.task = target.task; + target.task.numActivities++; + mHistory.remove(srcPos); + mHistory.add(dstPos, p); + mService.mWindowManager.moveAppToken(dstPos, p); + mService.mWindowManager.setAppGroupId(p, p.task.taskId); + dstPos++; + if (VALIDATE_TOKENS) { + mService.mWindowManager.validateAppTokens(mHistory); + } + i++; + } + if (taskTop == p) { + taskTop = below; + } + if (taskTopI == replyChainEnd) { + taskTopI = -1; + } + replyChainEnd = -1; + if (mMainStack) { + mService.addRecentTaskLocked(target.task); + } + } else if (forceReset || finishOnTaskLaunch + || clearWhenTaskReset) { + // If the activity should just be removed -- either + // because it asks for it, or the task should be + // cleared -- then finish it and anything that is + // part of its reply chain. + if (clearWhenTaskReset) { + // In this case, we want to finish this activity + // and everything above it, so be sneaky and pretend + // like these are all in the reply chain. + replyChainEnd = targetI+1; + while (replyChainEnd < mHistory.size() && + ((ActivityRecord)mHistory.get( + replyChainEnd)).task == task) { + replyChainEnd++; + } + replyChainEnd--; + } else if (replyChainEnd < 0) { + replyChainEnd = targetI; + } + ActivityRecord p = null; + for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) { + p = (ActivityRecord)mHistory.get(srcPos); + if (p.finishing) { + continue; + } + if (finishActivityLocked(p, srcPos, + Activity.RESULT_CANCELED, null, "reset")) { + replyChainEnd--; + srcPos--; + } + } + if (taskTop == p) { + taskTop = below; + } + if (taskTopI == replyChainEnd) { + taskTopI = -1; + } + replyChainEnd = -1; + } else { + // If we were in the middle of a chain, well the + // activity that started it all doesn't want anything + // special, so leave it all as-is. + replyChainEnd = -1; + } + } else { + // Reached the bottom of the task -- any reply chain + // should be left as-is. + replyChainEnd = -1; + } + + } else if (target.resultTo != null) { + // If this activity is sending a reply to a previous + // activity, we can't do anything with it now until + // we reach the start of the reply chain. + // XXX note that we are assuming the result is always + // to the previous activity, which is almost always + // the case but we really shouldn't count on. + if (replyChainEnd < 0) { + replyChainEnd = targetI; + } + + } else if (taskTopI >= 0 && allowTaskReparenting + && task.affinity != null + && task.affinity.equals(target.taskAffinity)) { + // We are inside of another task... if this activity has + // an affinity for our task, then either remove it if we are + // clearing or move it over to our task. Note that + // we currently punt on the case where we are resetting a + // task that is not at the top but who has activities above + // with an affinity to it... this is really not a normal + // case, and we will need to later pull that task to the front + // and usually at that point we will do the reset and pick + // up those remaining activities. (This only happens if + // someone starts an activity in a new task from an activity + // in a task that is not currently on top.) + if (forceReset || finishOnTaskLaunch) { + if (replyChainEnd < 0) { + replyChainEnd = targetI; + } + ActivityRecord p = null; + for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) { + p = (ActivityRecord)mHistory.get(srcPos); + if (p.finishing) { + continue; + } + if (finishActivityLocked(p, srcPos, + Activity.RESULT_CANCELED, null, "reset")) { + taskTopI--; + lastReparentPos--; + replyChainEnd--; + srcPos--; + } + } + replyChainEnd = -1; + } else { + if (replyChainEnd < 0) { + replyChainEnd = targetI; + } + for (int srcPos=replyChainEnd; srcPos>=targetI; srcPos--) { + ActivityRecord p = (ActivityRecord)mHistory.get(srcPos); + if (p.finishing) { + continue; + } + if (lastReparentPos < 0) { + lastReparentPos = taskTopI; + taskTop = p; + } else { + lastReparentPos--; + } + mHistory.remove(srcPos); + p.task.numActivities--; + p.task = task; + mHistory.add(lastReparentPos, p); + if (DEBUG_TASKS) Slog.v(TAG, "Pulling activity " + p + + " in to resetting task " + task); + task.numActivities++; + mService.mWindowManager.moveAppToken(lastReparentPos, p); + mService.mWindowManager.setAppGroupId(p, p.task.taskId); + if (VALIDATE_TOKENS) { + mService.mWindowManager.validateAppTokens(mHistory); + } + } + replyChainEnd = -1; + + // Now we've moved it in to place... but what if this is + // a singleTop activity and we have put it on top of another + // instance of the same activity? Then we drop the instance + // below so it remains singleTop. + if (target.info.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) { + for (int j=lastReparentPos-1; j>=0; j--) { + ActivityRecord p = (ActivityRecord)mHistory.get(j); + if (p.finishing) { + continue; + } + if (p.intent.getComponent().equals(target.intent.getComponent())) { + if (finishActivityLocked(p, j, + Activity.RESULT_CANCELED, null, "replace")) { + taskTopI--; + lastReparentPos--; + } + } + } + } + } + } + + target = below; + targetI = i; + } + + return taskTop; + } + + /** + * Perform clear operation as requested by + * {@link Intent#FLAG_ACTIVITY_CLEAR_TOP}: search from the top of the + * stack to the given task, then look for + * an instance of that activity in the stack and, if found, finish all + * 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, + * or null if none was found. + */ + private final ActivityRecord performClearTaskLocked(int taskId, + ActivityRecord newR, int launchFlags, boolean doClear) { + 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) { + return null; + } + 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--; + } + } + } + + // Finally, if this is a normal launch mode (that is, not + // expecting onNewIntent()), then we will finish the current + // instance of the activity so a new fresh one can be started. + if (ret.launchMode == ActivityInfo.LAUNCH_MULTIPLE + && (launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) == 0) { + if (!ret.finishing) { + int index = indexOfTokenLocked(ret); + if (index >= 0) { + finishActivityLocked(ret, index, Activity.RESULT_CANCELED, + null, "clear"); + } + return null; + } + } + + return ret; + } + } + + return null; + } + + /** + * 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. + */ + private final int findActivityInHistoryLocked(ActivityRecord r, int task) { + int i = mHistory.size(); + while (i > 0) { + i--; + ActivityRecord candidate = (ActivityRecord)mHistory.get(i); + if (candidate.task.taskId != task) { + break; + } + if (candidate.realActivity.equals(r.realActivity)) { + return i; + } + } + + return -1; + } + + /** + * Reorder the history stack so that the activity at the given index is + * brought to the front. + */ + private final ActivityRecord moveActivityToFrontLocked(int where) { + ActivityRecord newTop = (ActivityRecord)mHistory.remove(where); + int top = mHistory.size(); + ActivityRecord oldTop = (ActivityRecord)mHistory.get(top-1); + mHistory.add(top, newTop); + oldTop.frontOfTask = false; + newTop.frontOfTask = true; + return newTop; + } + + final int startActivityLocked(IApplicationThread caller, + Intent intent, String resolvedType, + Uri[] grantedUriPermissions, + int grantedMode, ActivityInfo aInfo, IBinder resultTo, + String resultWho, int requestCode, + int callingPid, int callingUid, boolean onlyIfNeeded, + boolean componentSpecified) { + + int err = START_SUCCESS; + + ProcessRecord callerApp = null; + if (caller != null) { + callerApp = mService.getRecordForAppLocked(caller); + if (callerApp != null) { + callingPid = callerApp.pid; + callingUid = callerApp.info.uid; + } else { + Slog.w(TAG, "Unable to find app for caller " + caller + + " (pid=" + callingPid + ") when starting: " + + intent.toString()); + err = START_PERMISSION_DENIED; + } + } + + if (err == START_SUCCESS) { + Slog.i(TAG, "Starting: " + intent + " from pid " + + (callerApp != null ? callerApp.pid : callingPid)); + } + + ActivityRecord sourceRecord = null; + ActivityRecord resultRecord = null; + if (resultTo != null) { + int index = indexOfTokenLocked(resultTo); + if (DEBUG_RESULTS) Slog.v( + TAG, "Sending result to " + resultTo + " (index " + index + ")"); + if (index >= 0) { + sourceRecord = (ActivityRecord)mHistory.get(index); + if (requestCode >= 0 && !sourceRecord.finishing) { + resultRecord = sourceRecord; + } + } + } + + int launchFlags = intent.getFlags(); + + if ((launchFlags&Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 + && sourceRecord != null) { + // Transfer the result target from the source activity to the new + // one being started, including any failures. + if (requestCode >= 0) { + return START_FORWARD_AND_REQUEST_CONFLICT; + } + resultRecord = sourceRecord.resultTo; + resultWho = sourceRecord.resultWho; + requestCode = sourceRecord.requestCode; + sourceRecord.resultTo = null; + if (resultRecord != null) { + resultRecord.removeResultsLocked( + sourceRecord, resultWho, requestCode); + } + } + + if (err == START_SUCCESS && intent.getComponent() == null) { + // We couldn't find a class that can handle the given Intent. + // That's the end of that! + err = START_INTENT_NOT_RESOLVED; + } + + if (err == START_SUCCESS && aInfo == null) { + // We couldn't find the specific class specified in the Intent. + // Also the end of the line. + err = START_CLASS_NOT_FOUND; + } + + if (err != START_SUCCESS) { + if (resultRecord != null) { + sendActivityResultLocked(-1, + resultRecord, resultWho, requestCode, + Activity.RESULT_CANCELED, null); + } + return err; + } + + final int perm = mService.checkComponentPermission(aInfo.permission, callingPid, + callingUid, aInfo.exported ? -1 : aInfo.applicationInfo.uid); + 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; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + + if (mMainStack) { + if (mService.mController != null) { + boolean abort = false; + try { + // The Intent we give to the watcher has the extra data + // stripped off, since it can contain private information. + Intent watchIntent = intent.cloneFilter(); + abort = !mService.mController.activityStarting(watchIntent, + aInfo.applicationInfo.packageName); + } catch (RemoteException e) { + mService.mController = null; + } + + if (abort) { + if (resultRecord != null) { + sendActivityResultLocked(-1, + resultRecord, resultWho, requestCode, + Activity.RESULT_CANCELED, null); + } + // We pretend to the caller that it was really started, but + // they will just get a cancel result. + return START_SUCCESS; + } + } + } + + ActivityRecord r = new ActivityRecord(mService, this, callerApp, callingUid, + intent, resolvedType, aInfo, mService.mConfiguration, + resultRecord, resultWho, requestCode, componentSpecified); + + if (mMainStack) { + if (mResumedActivity == null + || mResumedActivity.info.applicationInfo.uid != callingUid) { + if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, "Activity start")) { + PendingActivityLaunch pal = new PendingActivityLaunch(); + pal.r = r; + pal.sourceRecord = sourceRecord; + pal.grantedUriPermissions = grantedUriPermissions; + pal.grantedMode = grantedMode; + pal.onlyIfNeeded = onlyIfNeeded; + mService.mPendingActivityLaunches.add(pal); + return START_SWITCHES_CANCELED; + } + } + + if (mService.mDidAppSwitch) { + // This is the second allowed switch since we stopped switches, + // so now just generally allow switches. Use case: user presses + // home (switches disabled, switch to home, mDidAppSwitch now true); + // user taps a home icon (coming from home so allowed, we hit here + // and now allow anyone to switch again). + mService.mAppSwitchesAllowedTime = 0; + } else { + mService.mDidAppSwitch = true; + } + + mService.doPendingActivityLaunchesLocked(false); + } + + return startActivityUncheckedLocked(r, sourceRecord, + grantedUriPermissions, grantedMode, onlyIfNeeded, true); + } + + final int startActivityUncheckedLocked(ActivityRecord r, + ActivityRecord sourceRecord, Uri[] grantedUriPermissions, + int grantedMode, boolean onlyIfNeeded, boolean doResume) { + final Intent intent = r.intent; + final int callingUid = r.launchedFromUid; + + int launchFlags = intent.getFlags(); + + // We'll invoke onUserLeaving before onPause only if the launching + // activity did not explicitly state that this is an automated launch. + mUserLeaving = (launchFlags&Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0; + if (DEBUG_USER_LEAVING) Slog.v(TAG, + "startActivity() => mUserLeaving=" + mUserLeaving); + + // If the caller has asked not to resume at this point, we make note + // of this in the record so that we can skip it when trying to find + // the top running activity. + if (!doResume) { + r.delayedResume = true; + } + + ActivityRecord notTop = (launchFlags&Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) + != 0 ? r : null; + + // If the onlyIfNeeded flag is set, then we can do this if the activity + // being launched is the same as the one making the call... or, as + // a special case, if we do not know the caller then we count the + // current top activity as the caller. + if (onlyIfNeeded) { + ActivityRecord checkedCaller = sourceRecord; + if (checkedCaller == null) { + checkedCaller = topRunningNonDelayedActivityLocked(notTop); + } + if (!checkedCaller.realActivity.equals(r.realActivity)) { + // Caller is not the same as launcher, so always needed. + onlyIfNeeded = false; + } + } + + if (sourceRecord == null) { + // This activity is not being started from another... in this + // case we -always- start a new task. + if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { + Slog.w(TAG, "startActivity called from non-Activity context; forcing Intent.FLAG_ACTIVITY_NEW_TASK for: " + + intent); + launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; + } + } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { + // The original activity who is starting us is running as a single + // instance... this new activity it is starting must go on its + // own task. + launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; + } else if (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) { + // The activity being started is a single instance... it always + // gets launched into its own task. + launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; + } + + if (r.resultTo != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { + // For whatever reason this activity is being launched into a new + // task... yet the caller has requested a result back. Well, that + // is pretty messed up, so instead immediately send back a cancel + // and let the new task continue launched as normal without a + // dependency on its originator. + Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result."); + sendActivityResultLocked(-1, + r.resultTo, r.resultWho, r.requestCode, + Activity.RESULT_CANCELED, null); + r.resultTo = null; + } + + boolean addingToTask = false; + if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 && + (launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0) + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { + // If bring to front is requested, and no result is requested, and + // we can find a task that was started with this same + // component, then instead of launching bring that one to the front. + if (r.resultTo == null) { + // See if there is a task to bring to the front. If this is + // a SINGLE_INSTANCE activity, there can be one and only one + // instance of it in the history, and it is always in its own + // unique task, so we do a special search. + ActivityRecord taskTop = r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE + ? findTaskLocked(intent, r.info) + : findActivityLocked(intent, r.info); + if (taskTop != null) { + if (taskTop.task.intent == null) { + // This task was started because of movement of + // the activity based on affinity... now that we + // are actually launching it, we can assign the + // base intent. + taskTop.task.setIntent(intent, r.info); + } + // If the target task is not in the front, then we need + // to bring it to the front... except... well, with + // SINGLE_TASK_LAUNCH it's not entirely clear. We'd like + // to have the same behavior as if a new instance was + // being started, which means not bringing it to the front + // if the caller is not itself in the front. + ActivityRecord curTop = topRunningNonDelayedActivityLocked(notTop); + if (curTop != null && curTop.task != taskTop.task) { + r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); + boolean callerAtFront = sourceRecord == null + || curTop.task == sourceRecord.task; + if (callerAtFront) { + // We really do want to push this one into the + // user's face, right now. + moveTaskToFrontLocked(taskTop.task, r); + } + } + // If the caller has requested that the target task be + // reset, then do so. + if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { + taskTop = resetTaskIfNeededLocked(taskTop, r); + } + if (onlyIfNeeded) { + // We don't need to start a new activity, and + // the client said not to do anything if that + // is the case, so this is it! And for paranoia, make + // sure we have correctly resumed the top activity. + if (doResume) { + resumeTopActivityLocked(null); + } + return START_RETURN_INTENT_TO_CALLER; + } + 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 + // from the task up to the one being started. In most + // cases this means we are resetting the task to its + // initial state. + ActivityRecord top = performClearTaskLocked( + taskTop.task.taskId, r, launchFlags, true); + if (top != null) { + if (top.frontOfTask) { + // Activity aliases may mean we use different + // intents for the top activity, so make sure + // the task now has the identity of the new + // intent. + top.task.setIntent(r.intent, r.info); + } + logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); + top.deliverNewIntentLocked(callingUid, r.intent); + } else { + // A special case: we need to + // start the activity because it is not currently + // running, and the caller has asked to clear the + // current task to have this activity at the top. + addingToTask = true; + // Now pretend like this activity is being started + // by the top of its task, so it is put in the + // right place. + sourceRecord = taskTop; + } + } else if (r.realActivity.equals(taskTop.task.realActivity)) { + // In this case the top activity on the task is the + // same as the one being launched, so we take that + // as a request to bring the task to the foreground. + // If the top activity in the task is the root + // activity, deliver this new intent to it if it + // desires. + if ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 + && taskTop.realActivity.equals(r.realActivity)) { + logStartActivity(EventLogTags.AM_NEW_INTENT, r, taskTop.task); + if (taskTop.frontOfTask) { + taskTop.task.setIntent(r.intent, r.info); + } + taskTop.deliverNewIntentLocked(callingUid, r.intent); + } else if (!r.intent.filterEquals(taskTop.task.intent)) { + // In this case we are launching the root activity + // of the task, but with a different intent. We + // should start a new instance on top. + addingToTask = true; + sourceRecord = taskTop; + } + } else if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) { + // In this case an activity is being launched in to an + // existing task, without resetting that task. This + // is typically the situation of launching an activity + // from a notification or shortcut. We want to place + // the new activity on top of the current task. + addingToTask = true; + sourceRecord = taskTop; + } else if (!taskTop.task.rootWasReset) { + // In this case we are launching in to an existing task + // that has not yet been started from its front door. + // The current task has been brought to the front. + // Ideally, we'd probably like to place this new task + // at the bottom of its stack, but that's a little hard + // to do with the current organization of the code so + // for now we'll just drop it. + taskTop.task.setIntent(r.intent, r.info); + } + if (!addingToTask) { + // 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. + if (doResume) { + resumeTopActivityLocked(null); + } + return START_TASK_TO_FRONT; + } + } + } + } + + //String uri = r.intent.toURI(); + //Intent intent2 = new Intent(uri); + //Slog.i(TAG, "Given intent: " + r.intent); + //Slog.i(TAG, "URI is: " + uri); + //Slog.i(TAG, "To intent: " + intent2); + + if (r.packageName != null) { + // If the activity being launched is the same as the one currently + // at the top, then we need to check if it should only be launched + // once. + ActivityRecord top = topRunningNonDelayedActivityLocked(notTop); + if (top != null && r.resultTo == null) { + if (top.realActivity.equals(r.realActivity)) { + if (top.app != null && top.app.thread != null) { + if ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) { + logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task); + // For paranoia, make sure we have correctly + // resumed the top activity. + if (doResume) { + resumeTopActivityLocked(null); + } + if (onlyIfNeeded) { + // We don't need to start a new activity, and + // the client said not to do anything if that + // is the case, so this is it! + return START_RETURN_INTENT_TO_CALLER; + } + top.deliverNewIntentLocked(callingUid, r.intent); + return START_DELIVERED_TO_TOP; + } + } + } + } + + } else { + if (r.resultTo != null) { + sendActivityResultLocked(-1, + r.resultTo, r.resultWho, r.requestCode, + Activity.RESULT_CANCELED, null); + } + return START_CLASS_NOT_FOUND; + } + + boolean newTask = 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; + } + 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); + } + + } else if (sourceRecord != null) { + if (!addingToTask && + (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { + // In this case, we are adding the activity to an existing + // 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); + if (top != null) { + logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); + top.deliverNewIntentLocked(callingUid, r.intent); + // For paranoia, make sure we have correctly + // resumed the top activity. + if (doResume) { + resumeTopActivityLocked(null); + } + return START_DELIVERED_TO_TOP; + } + } else if (!addingToTask && + (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) { + // In this case, we are launching an activity in our own task + // that may already be running somewhere in the history, and + // we want to shuffle it to the front of the stack if so. + int where = findActivityInHistoryLocked(r, sourceRecord.task.taskId); + if (where >= 0) { + ActivityRecord top = moveActivityToFrontLocked(where); + logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); + top.deliverNewIntentLocked(callingUid, r.intent); + if (doResume) { + resumeTopActivityLocked(null); + } + return START_DELIVERED_TO_TOP; + } + } + // An existing activity is starting this new activity, so we want + // to keep the new one in the same task as the one that is starting + // it. + r.task = sourceRecord.task; + if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + + " in existing task " + r.task); + + } else { + // This not being started from an existing activity, and not part + // of a new task... just put it in the top task, though these days + // this case should never happen. + final int N = mHistory.size(); + 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); + if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + + " in new guessed " + r.task); + } + + if (grantedUriPermissions != null && callingUid > 0) { + for (int i=0; i<grantedUriPermissions.length; i++) { + mService.grantUriPermissionLocked(callingUid, r.packageName, + grantedUriPermissions[i], grantedMode, r.getUriPermissionsLocked()); + } + } + + mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName, + intent, r.getUriPermissionsLocked()); + + if (newTask) { + EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.task.taskId); + } + logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task); + startActivityLocked(r, newTask, doResume); + 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); + + // Collect information about the target of the Intent. + ActivityInfo aInfo; + try { + ResolveInfo rInfo = + AppGlobals.getPackageManager().resolveIntent( + intent, resolvedType, + PackageManager.MATCH_DEFAULT_ONLY + | ActivityManagerService.STOCK_PM_FLAGS); + aInfo = rInfo != null ? rInfo.activityInfo : null; + } catch (RemoteException e) { + aInfo = 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)); + + // Don't debug things in the system process + if (debug) { + if (!aInfo.processName.equals("system")) { + mService.setDebugApp(aInfo.processName, true, false); + } + } + } + + synchronized (mService) { + int callingPid; + int callingUid; + if (caller == null) { + callingPid = Binder.getCallingPid(); + callingUid = Binder.getCallingUid(); + } else { + callingPid = callingUid = -1; + } + + mConfigWillChange = config != null + && mService.mConfiguration.diff(config) != 0; + if (DEBUG_CONFIGURATION) Slog.v(TAG, + "Starting activity when config will change = " + mConfigWillChange); + + final long origId = Binder.clearCallingIdentity(); + + if (mMainStack && aInfo != null && + (aInfo.applicationInfo.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { + // This may be a heavy-weight process! Check to see if we already + // have another, different heavy-weight process running. + if (aInfo.processName.equals(aInfo.applicationInfo.packageName)) { + if (mService.mHeavyWeightProcess != null && + (mService.mHeavyWeightProcess.info.uid != aInfo.applicationInfo.uid || + !mService.mHeavyWeightProcess.processName.equals(aInfo.processName))) { + int realCallingPid = callingPid; + int realCallingUid = callingUid; + if (caller != null) { + ProcessRecord callerApp = mService.getRecordForAppLocked(caller); + if (callerApp != null) { + realCallingPid = callerApp.pid; + realCallingUid = callerApp.info.uid; + } else { + Slog.w(TAG, "Unable to find app for caller " + caller + + " (pid=" + realCallingPid + ") when starting: " + + intent.toString()); + return START_PERMISSION_DENIED; + } + } + + IIntentSender target = mService.getIntentSenderLocked( + IActivityManager.INTENT_SENDER_ACTIVITY, "android", + realCallingUid, null, null, 0, intent, + resolvedType, PendingIntent.FLAG_CANCEL_CURRENT + | PendingIntent.FLAG_ONE_SHOT); + + Intent newIntent = new Intent(); + if (requestCode >= 0) { + // Caller is requesting a result. + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT, true); + } + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT, + new IntentSender(target)); + if (mService.mHeavyWeightProcess.activities.size() > 0) { + ActivityRecord hist = mService.mHeavyWeightProcess.activities.get(0); + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_APP, + hist.packageName); + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_TASK, + hist.task.taskId); + } + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP, + aInfo.packageName); + newIntent.setFlags(intent.getFlags()); + newIntent.setClassName("android", + HeavyWeightSwitcherActivity.class.getName()); + intent = newIntent; + resolvedType = null; + caller = null; + callingUid = Binder.getCallingUid(); + callingPid = Binder.getCallingPid(); + componentSpecified = true; + try { + ResolveInfo rInfo = + AppGlobals.getPackageManager().resolveIntent( + intent, null, + PackageManager.MATCH_DEFAULT_ONLY + | ActivityManagerService.STOCK_PM_FLAGS); + aInfo = rInfo != null ? rInfo.activityInfo : null; + } catch (RemoteException e) { + aInfo = null; + } + } + } + } + + int res = startActivityLocked(caller, intent, resolvedType, + grantedUriPermissions, grantedMode, aInfo, + resultTo, resultWho, requestCode, callingPid, callingUid, + onlyIfNeeded, componentSpecified); + + if (mConfigWillChange && mMainStack) { + // If the caller also wants to switch to a new configuration, + // do so now. This allows a clean switch, as we are waiting + // for the current activity to pause (so we will not destroy + // it), and have not yet started the next activity. + mService.enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION, + "updateConfiguration()"); + mConfigWillChange = false; + if (DEBUG_CONFIGURATION) Slog.v(TAG, + "Updating to new configuration after starting activity."); + mService.updateConfigurationLocked(config, null); + } + + Binder.restoreCallingIdentity(origId); + + if (outResult != null) { + outResult.result = res; + if (res == IActivityManager.START_SUCCESS) { + mWaitingActivityLaunched.add(outResult); + do { + try { + mService.wait(); + } catch (InterruptedException e) { + } + } while (!outResult.timeout && outResult.who == null); + } else if (res == IActivityManager.START_TASK_TO_FRONT) { + ActivityRecord r = this.topRunningActivityLocked(null); + if (r.nowVisible) { + outResult.timeout = false; + outResult.who = new ComponentName(r.info.packageName, r.info.name); + outResult.totalTime = 0; + outResult.thisTime = 0; + } else { + outResult.thisTime = SystemClock.uptimeMillis(); + mWaitingActivityVisible.add(outResult); + do { + try { + mService.wait(); + } catch (InterruptedException e) { + } + } while (!outResult.timeout && outResult.who == null); + } + } + } + + return res; + } + } + + void reportActivityLaunchedLocked(boolean timeout, ActivityRecord r, + long thisTime, long totalTime) { + for (int i=mWaitingActivityLaunched.size()-1; i>=0; i--) { + WaitResult w = mWaitingActivityLaunched.get(i); + w.timeout = timeout; + if (r != null) { + w.who = new ComponentName(r.info.packageName, r.info.name); + } + w.thisTime = thisTime; + w.totalTime = totalTime; + } + mService.notifyAll(); + } + + void reportActivityVisibleLocked(ActivityRecord r) { + for (int i=mWaitingActivityVisible.size()-1; i>=0; i--) { + WaitResult w = mWaitingActivityVisible.get(i); + w.timeout = false; + if (r != null) { + w.who = new ComponentName(r.info.packageName, r.info.name); + } + w.totalTime = SystemClock.uptimeMillis() - w.thisTime; + w.thisTime = w.totalTime; + } + mService.notifyAll(); + } + + void sendActivityResultLocked(int callingUid, ActivityRecord r, + String resultWho, int requestCode, int resultCode, Intent data) { + + if (callingUid > 0) { + mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName, + data, r.getUriPermissionsLocked()); + } + + if (DEBUG_RESULTS) Slog.v(TAG, "Send activity result to " + r + + " : who=" + resultWho + " req=" + requestCode + + " res=" + resultCode + " data=" + data); + if (mResumedActivity == r && r.app != null && r.app.thread != null) { + try { + ArrayList<ResultInfo> list = new ArrayList<ResultInfo>(); + list.add(new ResultInfo(resultWho, requestCode, + resultCode, data)); + r.app.thread.scheduleSendResult(r, list); + return; + } catch (Exception e) { + Slog.w(TAG, "Exception thrown sending result to " + r, e); + } + } + + r.addResultLocked(null, resultWho, requestCode, resultCode, data); + } + + private final void stopActivityLocked(ActivityRecord r) { + if (DEBUG_SWITCH) Slog.d(TAG, "Stopping: " + r); + if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_HISTORY) != 0 + || (r.info.flags&ActivityInfo.FLAG_NO_HISTORY) != 0) { + if (!r.finishing) { + requestFinishActivityLocked(r, Activity.RESULT_CANCELED, null, + "no-history"); + } + } else if (r.app != null && r.app.thread != null) { + if (mMainStack) { + if (mService.mFocusedActivity == r) { + mService.setFocusedActivityLocked(topRunningActivityLocked(null)); + } + } + r.resumeKeyDispatchingLocked(); + try { + r.stopped = false; + r.state = ActivityState.STOPPING; + if (DEBUG_VISBILITY) Slog.v( + TAG, "Stopping visible=" + r.visible + " for " + r); + if (!r.visible) { + mService.mWindowManager.setAppVisibility(r, false); + } + r.app.thread.scheduleStopActivity(r, r.visible, r.configChangeFlags); + } catch (Exception e) { + // Maybe just ignore exceptions here... if the process + // has crashed, our death notification will clean things + // up. + Slog.w(TAG, "Exception thrown during pause", e); + // Just in case, assume it to be stopped. + r.stopped = true; + r.state = ActivityState.STOPPED; + if (r.configDestroy) { + destroyActivityLocked(r, true); + } + } + } + } + + final ArrayList<ActivityRecord> processStoppingActivitiesLocked( + boolean remove) { + int N = mStoppingActivities.size(); + if (N <= 0) return null; + + ArrayList<ActivityRecord> stops = null; + + final boolean nowVisible = mResumedActivity != null + && mResumedActivity.nowVisible + && !mResumedActivity.waitingVisible; + for (int i=0; i<N; i++) { + ActivityRecord s = mStoppingActivities.get(i); + if (localLOGV) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + + nowVisible + " waitingVisible=" + s.waitingVisible + + " finishing=" + s.finishing); + if (s.waitingVisible && nowVisible) { + mWaitingVisibleActivities.remove(s); + s.waitingVisible = false; + if (s.finishing) { + // If this activity is finishing, it is sitting on top of + // everyone else but we now know it is no longer needed... + // so get rid of it. Otherwise, we need to go through the + // normal flow and hide it once we determine that it is + // hidden by the activities in front of it. + if (localLOGV) Slog.v(TAG, "Before stopping, can hide: " + s); + mService.mWindowManager.setAppVisibility(s, false); + } + } + if (!s.waitingVisible && remove) { + if (localLOGV) Slog.v(TAG, "Ready to stop: " + s); + if (stops == null) { + stops = new ArrayList<ActivityRecord>(); + } + stops.add(s); + mStoppingActivities.remove(i); + N--; + i--; + } + } + + return stops; + } + + final void activityIdleInternal(IBinder token, boolean fromTimeout, + Configuration config) { + if (localLOGV) Slog.v(TAG, "Activity idle: " + token); + + ArrayList<ActivityRecord> stops = null; + ArrayList<ActivityRecord> finishes = null; + ArrayList<ActivityRecord> thumbnails = null; + int NS = 0; + int NF = 0; + int NT = 0; + IApplicationThread sendThumbnail = null; + boolean booting = false; + boolean enableScreen = false; + + synchronized (mService) { + if (token != null) { + mHandler.removeMessages(IDLE_TIMEOUT_MSG, token); + } + + // Get the activity record. + int index = indexOfTokenLocked(token); + if (index >= 0) { + ActivityRecord r = (ActivityRecord)mHistory.get(index); + + if (fromTimeout) { + reportActivityLaunchedLocked(fromTimeout, r, -1, -1); + } + + // This is a hack to semi-deal with a race condition + // in the client where it can be constructed with a + // newer configuration from when we asked it to launch. + // We'll update with whatever configuration it now says + // it used to launch. + if (config != null) { + r.configuration = config; + } + + // No longer need to keep the device awake. + if (mResumedActivity == r && mLaunchingActivity.isHeld()) { + mHandler.removeMessages(LAUNCH_TIMEOUT_MSG); + mLaunchingActivity.release(); + } + + // We are now idle. If someone is waiting for a thumbnail from + // us, we can now deliver. + r.idle = true; + mService.scheduleAppGcsLocked(); + if (r.thumbnailNeeded && r.app != null && r.app.thread != null) { + sendThumbnail = r.app.thread; + r.thumbnailNeeded = false; + } + + // If this activity is fullscreen, set up to hide those under it. + + if (DEBUG_VISBILITY) Slog.v(TAG, "Idle activity for " + r); + ensureActivitiesVisibleLocked(null, 0); + + //Slog.i(TAG, "IDLE: mBooted=" + mBooted + ", fromTimeout=" + fromTimeout); + if (mMainStack) { + if (!mService.mBooted && !fromTimeout) { + mService.mBooted = true; + enableScreen = true; + } + } + + } else if (fromTimeout) { + reportActivityLaunchedLocked(fromTimeout, null, -1, -1); + } + + // Atomically retrieve all of the other things to do. + stops = processStoppingActivitiesLocked(true); + NS = stops != null ? stops.size() : 0; + if ((NF=mFinishingActivities.size()) > 0) { + finishes = new ArrayList<ActivityRecord>(mFinishingActivities); + mFinishingActivities.clear(); + } + if ((NT=mService.mCancelledThumbnails.size()) > 0) { + thumbnails = new ArrayList<ActivityRecord>(mService.mCancelledThumbnails); + mService.mCancelledThumbnails.clear(); + } + + if (mMainStack) { + booting = mService.mBooting; + mService.mBooting = false; + } + } + + int i; + + // Send thumbnail if requested. + if (sendThumbnail != null) { + try { + sendThumbnail.requestThumbnail(token); + } catch (Exception e) { + Slog.w(TAG, "Exception thrown when requesting thumbnail", e); + mService.sendPendingThumbnail(null, token, null, null, true); + } + } + + // Stop any activities that are scheduled to do so but have been + // waiting for the next one to start. + for (i=0; i<NS; i++) { + ActivityRecord r = (ActivityRecord)stops.get(i); + synchronized (mService) { + if (r.finishing) { + finishCurrentActivityLocked(r, FINISH_IMMEDIATELY); + } else { + stopActivityLocked(r); + } + } + } + + // Finish any activities that are scheduled to do so but have been + // waiting for the next one to start. + for (i=0; i<NF; i++) { + ActivityRecord r = (ActivityRecord)finishes.get(i); + synchronized (mService) { + destroyActivityLocked(r, true); + } + } + + // Report back to any thumbnail receivers. + for (i=0; i<NT; i++) { + ActivityRecord r = (ActivityRecord)thumbnails.get(i); + mService.sendPendingThumbnail(r, null, null, null, true); + } + + if (booting) { + mService.finishBooting(); + } + + mService.trimApplications(); + //dump(); + //mWindowManager.dump(); + + if (enableScreen) { + mService.enableScreenAfterBoot(); + } + } + + /** + * @return Returns true if the activity is being finished, false if for + * some reason it is being left as-is. + */ + final boolean requestFinishActivityLocked(IBinder token, int resultCode, + Intent resultData, String reason) { + if (DEBUG_RESULTS) Slog.v( + TAG, "Finishing activity: token=" + token + + ", result=" + resultCode + ", data=" + resultData); + + int index = indexOfTokenLocked(token); + if (index < 0) { + return false; + } + ActivityRecord r = (ActivityRecord)mHistory.get(index); + + // Is this the last activity left? + boolean lastActivity = true; + for (int i=mHistory.size()-1; i>=0; i--) { + ActivityRecord p = (ActivityRecord)mHistory.get(i); + if (!p.finishing && p != r) { + lastActivity = false; + break; + } + } + + // If this is the last activity, but it is the home activity, then + // just don't finish it. + if (lastActivity) { + if (r.intent.hasCategory(Intent.CATEGORY_HOME)) { + return false; + } + } + + finishActivityLocked(r, index, resultCode, resultData, reason); + return true; + } + + /** + * @return Returns true if this activity has been removed from the history + * list, or false if it is still in the list and will be removed later. + */ + final boolean finishActivityLocked(ActivityRecord r, int index, + int resultCode, Intent resultData, String reason) { + if (r.finishing) { + Slog.w(TAG, "Duplicate finish request for " + r); + return false; + } + + r.finishing = true; + 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) { + if (r.frontOfTask) { + // The next activity is now the front of the task. + next.frontOfTask = true; + } + if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) { + // If the caller asked that this activity (and all above it) + // be cleared when the task is reset, don't lose that information, + // but propagate it up to the next activity. + next.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + } + } + } + + r.pauseKeyDispatchingLocked(); + if (mMainStack) { + if (mService.mFocusedActivity == r) { + mService.setFocusedActivityLocked(topRunningActivityLocked(null)); + } + } + + // send the result + ActivityRecord resultTo = r.resultTo; + if (resultTo != null) { + if (DEBUG_RESULTS) Slog.v(TAG, "Adding result to " + resultTo + + " who=" + r.resultWho + " req=" + r.requestCode + + " res=" + resultCode + " data=" + resultData); + if (r.info.applicationInfo.uid > 0) { + mService.grantUriPermissionFromIntentLocked(r.info.applicationInfo.uid, + resultTo.packageName, resultData, + resultTo.getUriPermissionsLocked()); + } + resultTo.addResultLocked(r, r.resultWho, r.requestCode, resultCode, + resultData); + r.resultTo = null; + } + else if (DEBUG_RESULTS) Slog.v(TAG, "No result destination from " + r); + + // Make sure this HistoryRecord is not holding on to other resources, + // because clients have remote IPC references to this object so we + // can't assume that will go away and want to avoid circular IPC refs. + r.results = null; + r.pendingResults = null; + r.newIntents = null; + r.icicle = null; + + if (mService.mPendingThumbnails.size() > 0) { + // There are clients waiting to receive thumbnails so, in case + // this is an activity that someone is waiting for, add it + // to the pending list so we can correctly update the clients. + mService.mCancelledThumbnails.add(r); + } + + if (mResumedActivity == r) { + boolean endTask = index <= 0 + || ((ActivityRecord)mHistory.get(index-1)).task != r.task; + if (DEBUG_TRANSITION) Slog.v(TAG, + "Prepare close transition: finishing " + r); + mService.mWindowManager.prepareAppTransition(endTask + ? WindowManagerPolicy.TRANSIT_TASK_CLOSE + : WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE); + + // Tell window manager to prepare for this one to be removed. + mService.mWindowManager.setAppVisibility(r, false); + + if (mPausingActivity == null) { + if (DEBUG_PAUSE) Slog.v(TAG, "Finish needs to pause: " + r); + if (DEBUG_USER_LEAVING) Slog.v(TAG, "finish() => pause with userLeaving=false"); + startPausingLocked(false, false); + } + + } else if (r.state != ActivityState.PAUSING) { + // If the activity is PAUSING, we will complete the finish once + // it is done pausing; else we can just directly finish it here. + if (DEBUG_PAUSE) Slog.v(TAG, "Finish not pausing: " + r); + return finishCurrentActivityLocked(r, index, + FINISH_AFTER_PAUSE) == null; + } else { + if (DEBUG_PAUSE) Slog.v(TAG, "Finish waiting for pause of: " + r); + } + + return false; + } + + private static final int FINISH_IMMEDIATELY = 0; + private static final int FINISH_AFTER_PAUSE = 1; + private static final int FINISH_AFTER_VISIBLE = 2; + + private final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, + int mode) { + final int index = indexOfTokenLocked(r); + if (index < 0) { + return null; + } + + return finishCurrentActivityLocked(r, index, mode); + } + + private final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, + int index, int mode) { + // First things first: if this activity is currently visible, + // and the resumed activity is not yet visible, then hold off on + // finishing until the resumed one becomes visible. + if (mode == FINISH_AFTER_VISIBLE && r.nowVisible) { + if (!mStoppingActivities.contains(r)) { + mStoppingActivities.add(r); + if (mStoppingActivities.size() > 3) { + // If we already have a few activities waiting to stop, + // then give up on things going idle and start clearing + // them out. + Message msg = Message.obtain(); + msg.what = IDLE_NOW_MSG; + mHandler.sendMessage(msg); + } + } + r.state = ActivityState.STOPPING; + mService.updateOomAdjLocked(); + return r; + } + + // make sure the record is cleaned out of other places. + mStoppingActivities.remove(r); + mWaitingVisibleActivities.remove(r); + if (mResumedActivity == r) { + mResumedActivity = null; + } + final ActivityState prevState = r.state; + r.state = ActivityState.FINISHING; + + if (mode == FINISH_IMMEDIATELY + || prevState == ActivityState.STOPPED + || prevState == ActivityState.INITIALIZING) { + // If this activity is already stopped, we can just finish + // it right now. + return destroyActivityLocked(r, true) ? null : r; + } else { + // Need to go through the full pause cycle to get this + // activity into the stopped state and then finish it. + if (localLOGV) Slog.v(TAG, "Enqueueing pending finish: " + r); + mFinishingActivities.add(r); + resumeTopActivityLocked(null); + } + return r; + } + + /** + * Perform the common clean-up of an activity record. This is called both + * as part of destroyActivityLocked() (when destroying the client-side + * representation) and cleaning things up as a result of its hosting + * processing going away, in which case there is no remaining client-side + * state to destroy so only the cleanup here is needed. + */ + final void cleanUpActivityLocked(ActivityRecord r, boolean cleanServices) { + if (mResumedActivity == r) { + mResumedActivity = null; + } + if (mService.mFocusedActivity == r) { + mService.mFocusedActivity = null; + } + + r.configDestroy = false; + r.frozenBeforeDestroy = false; + + // Make sure this record is no longer in the pending finishes list. + // This could happen, for example, if we are trimming activities + // down to the max limit while they are still waiting to finish. + mFinishingActivities.remove(r); + mWaitingVisibleActivities.remove(r); + + // Remove any pending results. + if (r.finishing && r.pendingResults != null) { + for (WeakReference<PendingIntentRecord> apr : r.pendingResults) { + PendingIntentRecord rec = apr.get(); + if (rec != null) { + mService.cancelIntentSenderLocked(rec, false); + } + } + r.pendingResults = null; + } + + if (cleanServices) { + cleanUpActivityServicesLocked(r); + } + + if (mService.mPendingThumbnails.size() > 0) { + // There are clients waiting to receive thumbnails so, in case + // this is an activity that someone is waiting for, add it + // to the pending list so we can correctly update the clients. + mService.mCancelledThumbnails.add(r); + } + + // Get rid of any pending idle timeouts. + mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); + mHandler.removeMessages(IDLE_TIMEOUT_MSG, r); + } + + private final void removeActivityFromHistoryLocked(ActivityRecord r) { + if (r.state != ActivityState.DESTROYED) { + mHistory.remove(r); + r.inHistory = false; + r.state = ActivityState.DESTROYED; + mService.mWindowManager.removeAppToken(r); + if (VALIDATE_TOKENS) { + mService.mWindowManager.validateAppTokens(mHistory); + } + cleanUpActivityServicesLocked(r); + r.removeUriPermissionsLocked(); + } + } + + /** + * Perform clean-up of service connections in an activity record. + */ + final void cleanUpActivityServicesLocked(ActivityRecord r) { + // Throw away any services that have been bound by this activity. + if (r.connections != null) { + Iterator<ConnectionRecord> it = r.connections.iterator(); + while (it.hasNext()) { + ConnectionRecord c = it.next(); + mService.removeConnectionLocked(c, null, r); + } + r.connections = null; + } + } + + /** + * Destroy the current CLIENT SIDE instance of an activity. This may be + * called both when actually finishing an activity, or when performing + * a configuration switch where we destroy the current client-side object + * but then create a new client-side object for this same HistoryRecord. + */ + final boolean destroyActivityLocked(ActivityRecord r, + boolean removeFromApp) { + if (DEBUG_SWITCH) Slog.v( + TAG, "Removing activity: token=" + r + + ", app=" + (r.app != null ? r.app.processName : "(null)")); + EventLog.writeEvent(EventLogTags.AM_DESTROY_ACTIVITY, + System.identityHashCode(r), + r.task.taskId, r.shortComponentName); + + boolean removedFromHistory = false; + + cleanUpActivityLocked(r, false); + + final boolean hadApp = r.app != null; + + if (hadApp) { + if (removeFromApp) { + int idx = r.app.activities.indexOf(r); + if (idx >= 0) { + r.app.activities.remove(idx); + } + if (mService.mHeavyWeightProcess == r.app && r.app.activities.size() <= 0) { + mService.mHeavyWeightProcess = null; + mService.mHandler.sendEmptyMessage( + ActivityManagerService.CANCEL_HEAVY_NOTIFICATION_MSG); + } + if (r.app.activities.size() == 0) { + // No longer have activities, so update location in + // LRU list. + mService.updateLruProcessLocked(r.app, true, false); + } + } + + boolean skipDestroy = false; + + try { + if (DEBUG_SWITCH) Slog.i(TAG, "Destroying: " + r); + r.app.thread.scheduleDestroyActivity(r, r.finishing, + r.configChangeFlags); + } catch (Exception e) { + // We can just ignore exceptions here... if the process + // has crashed, our death notification will clean things + // up. + //Slog.w(TAG, "Exception thrown during finish", e); + if (r.finishing) { + removeActivityFromHistoryLocked(r); + removedFromHistory = true; + skipDestroy = true; + } + } + + r.app = null; + r.nowVisible = false; + + if (r.finishing && !skipDestroy) { + r.state = ActivityState.DESTROYING; + Message msg = mHandler.obtainMessage(DESTROY_TIMEOUT_MSG); + msg.obj = r; + mHandler.sendMessageDelayed(msg, DESTROY_TIMEOUT); + } else { + r.state = ActivityState.DESTROYED; + } + } else { + // remove this record from the history. + if (r.finishing) { + removeActivityFromHistoryLocked(r); + removedFromHistory = true; + } else { + r.state = ActivityState.DESTROYED; + } + } + + r.configChangeFlags = 0; + + if (!mLRUActivities.remove(r) && hadApp) { + Slog.w(TAG, "Activity " + r + " being finished, but not in LRU list"); + } + + return removedFromHistory; + } + + final void activityDestroyed(IBinder token) { + synchronized (mService) { + mHandler.removeMessages(DESTROY_TIMEOUT_MSG, token); + + int index = indexOfTokenLocked(token); + if (index >= 0) { + ActivityRecord r = (ActivityRecord)mHistory.get(index); + if (r.state == ActivityState.DESTROYING) { + final long origId = Binder.clearCallingIdentity(); + removeActivityFromHistoryLocked(r); + Binder.restoreCallingIdentity(origId); + } + } + } + } + + private static void removeHistoryRecordsForAppLocked(ArrayList list, ProcessRecord app) { + int i = list.size(); + if (localLOGV) Slog.v( + TAG, "Removing app " + app + " from list " + list + + " with " + i + " entries"); + while (i > 0) { + i--; + ActivityRecord r = (ActivityRecord)list.get(i); + if (localLOGV) Slog.v( + TAG, "Record #" + i + " " + r + ": app=" + r.app); + if (r.app == app) { + if (localLOGV) Slog.v(TAG, "Removing this entry!"); + list.remove(i); + } + } + } + + void removeHistoryRecordsForAppLocked(ProcessRecord app) { + removeHistoryRecordsForAppLocked(mLRUActivities, app); + removeHistoryRecordsForAppLocked(mStoppingActivities, app); + removeHistoryRecordsForAppLocked(mWaitingVisibleActivities, app); + removeHistoryRecordsForAppLocked(mFinishingActivities, app); + } + + final void moveTaskToFrontLocked(TaskRecord tr, ActivityRecord reason) { + if (DEBUG_SWITCH) Slog.v(TAG, "moveTaskToFront: " + tr); + + final int task = tr.taskId; + int top = mHistory.size()-1; + + if (top < 0 || ((ActivityRecord)mHistory.get(top)).task.taskId == task) { + // nothing to do! + return; + } + + ArrayList moved = new ArrayList(); + + // Applying the affinities may have removed entries from the history, + // so get the size again. + top = mHistory.size()-1; + int pos = top; + + // Shift all activities with this task up to the top + // of the stack, keeping them in the same internal order. + while (pos >= 0) { + 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--; + } + + if (DEBUG_TRANSITION) Slog.v(TAG, + "Prepare to front transition: task=" + tr); + if (reason != null && + (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { + mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + ActivityRecord r = topRunningActivityLocked(null); + if (r != null) { + mNoAnimActivities.add(r); + } + } else { + mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_TO_FRONT); + } + + mService.mWindowManager.moveAppTokensToTop(moved); + if (VALIDATE_TOKENS) { + mService.mWindowManager.validateAppTokens(mHistory); + } + + finishTaskMoveLocked(task); + EventLog.writeEvent(EventLogTags.AM_TASK_TO_FRONT, task); + } + + private final void finishTaskMoveLocked(int task) { + resumeTopActivityLocked(null); + } + + /** + * Worker method for rearranging history stack. Implements the function of moving all + * activities for a specific task (gathering them if disjoint) into a single group at the + * bottom of the stack. + * + * If a watcher is installed, the action is preflighted and the watcher has an opportunity + * to premeptively cancel the move. + * + * @param task The taskId to collect and move to the bottom. + * @return Returns true if the move completed, false if not. + */ + final boolean moveTaskToBackLocked(int task, ActivityRecord reason) { + Slog.i(TAG, "moveTaskToBack: " + task); + + // If we have a watcher, preflight the move before committing to it. First check + // for *other* available tasks, but if none are available, then try again allowing the + // current task to be selected. + if (mMainStack && mService.mController != null) { + ActivityRecord next = topRunningActivityLocked(null, task); + if (next == null) { + next = topRunningActivityLocked(null, 0); + } + if (next != null) { + // ask watcher if this is allowed + boolean moveOK = true; + try { + moveOK = mService.mController.activityResuming(next.packageName); + } catch (RemoteException e) { + mService.mController = null; + } + if (!moveOK) { + return false; + } + } + } + + ArrayList moved = new ArrayList(); + + if (DEBUG_TRANSITION) Slog.v(TAG, + "Prepare to back transition: task=" + task); + + final int N = mHistory.size(); + int bottom = 0; + int pos = 0; + + // Shift all activities with this task down to the bottom + // of the stack, keeping them in the same internal order. + while (pos < N) { + ActivityRecord r = (ActivityRecord)mHistory.get(pos); + if (localLOGV) Slog.v( + TAG, "At " + pos + " ckp " + r.task + ": " + r); + if (r.task.taskId == task) { + if (localLOGV) Slog.v(TAG, "Removing and adding at " + (N-1)); + mHistory.remove(pos); + mHistory.add(bottom, r); + moved.add(r); + bottom++; + } + pos++; + } + + if (reason != null && + (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { + mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + ActivityRecord r = topRunningActivityLocked(null); + if (r != null) { + mNoAnimActivities.add(r); + } + } else { + mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_TO_BACK); + } + mService.mWindowManager.moveAppTokensToBottom(moved); + if (VALIDATE_TOKENS) { + mService.mWindowManager.validateAppTokens(mHistory); + } + + finishTaskMoveLocked(task); + return true; + } + + private final void logStartActivity(int tag, ActivityRecord r, + TaskRecord task) { + EventLog.writeEvent(tag, + System.identityHashCode(r), task.taskId, + r.shortComponentName, r.intent.getAction(), + r.intent.getType(), r.intent.getDataString(), + r.intent.getFlags()); + } + + /** + * Make sure the given activity matches the current configuration. Returns + * false if the activity had to be destroyed. Returns true if the + * configuration is the same, or the activity will remain running as-is + * for whatever reason. Ensures the HistoryRecord is updated with the + * correct configuration and all other bookkeeping is handled. + */ + final boolean ensureActivityConfigurationLocked(ActivityRecord r, + int globalChanges) { + if (mConfigWillChange) { + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + "Skipping config check (will change): " + r); + return true; + } + + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + "Ensuring correct configuration: " + r); + + // Short circuit: if the two configurations are the exact same + // object (the common case), then there is nothing to do. + Configuration newConfig = mService.mConfiguration; + if (r.configuration == newConfig) { + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + "Configuration unchanged in " + r); + return true; + } + + // We don't worry about activities that are finishing. + if (r.finishing) { + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + "Configuration doesn't matter in finishing " + r); + r.stopFreezingScreenLocked(false); + return true; + } + + // Okay we now are going to make this activity have the new config. + // But then we need to figure out how it needs to deal with that. + Configuration oldConfig = r.configuration; + r.configuration = newConfig; + + // If the activity isn't currently running, just leave the new + // configuration and it will pick that up next time it starts. + if (r.app == null || r.app.thread == null) { + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + "Configuration doesn't matter not running " + r); + r.stopFreezingScreenLocked(false); + return true; + } + + // Figure out what has changed between the two configurations. + int changes = oldConfig.diff(newConfig); + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) { + Slog.v(TAG, "Checking to restart " + r.info.name + ": changed=0x" + + Integer.toHexString(changes) + ", handles=0x" + + Integer.toHexString(r.info.configChanges) + + ", newConfig=" + newConfig); + } + if ((changes&(~r.info.configChanges)) != 0) { + // Aha, the activity isn't handling the change, so DIE DIE DIE. + r.configChangeFlags |= changes; + r.startFreezingScreenLocked(r.app, globalChanges); + if (r.app == null || r.app.thread == null) { + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + "Switch is destroying non-running " + r); + destroyActivityLocked(r, true); + } else if (r.state == ActivityState.PAUSING) { + // A little annoying: we are waiting for this activity to + // finish pausing. Let's not do anything now, but just + // flag that it needs to be restarted when done pausing. + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + "Switch is skipping already pausing " + r); + r.configDestroy = true; + return true; + } else if (r.state == ActivityState.RESUMED) { + // Try to optimize this case: the configuration is changing + // and we need to restart the top, resumed activity. + // Instead of doing the normal handshaking, just say + // "restart!". + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + "Switch is restarting resumed " + r); + relaunchActivityLocked(r, r.configChangeFlags, true); + r.configChangeFlags = 0; + } else { + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + "Switch is restarting non-resumed " + r); + relaunchActivityLocked(r, r.configChangeFlags, false); + r.configChangeFlags = 0; + } + + // All done... tell the caller we weren't able to keep this + // activity around. + return false; + } + + // Default case: the activity can handle this new configuration, so + // hand it over. Note that we don't need to give it the new + // configuration, since we always send configuration changes to all + // process when they happen so it can just use whatever configuration + // it last got. + if (r.app != null && r.app.thread != null) { + try { + if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending new config to " + r); + r.app.thread.scheduleActivityConfigurationChanged(r); + } catch (RemoteException e) { + // If process died, whatever. + } + } + r.stopFreezingScreenLocked(false); + + return true; + } + + private final boolean relaunchActivityLocked(ActivityRecord r, + int changes, boolean andResume) { + List<ResultInfo> results = null; + List<Intent> newIntents = null; + if (andResume) { + results = r.results; + newIntents = r.newIntents; + } + if (DEBUG_SWITCH) Slog.v(TAG, "Relaunching: " + r + + " with results=" + results + " newIntents=" + newIntents + + " andResume=" + andResume); + EventLog.writeEvent(andResume ? EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY + : EventLogTags.AM_RELAUNCH_ACTIVITY, System.identityHashCode(r), + r.task.taskId, r.shortComponentName); + + r.startFreezingScreenLocked(r.app, 0); + + try { + if (DEBUG_SWITCH) Slog.i(TAG, "Switch is restarting resumed " + r); + r.app.thread.scheduleRelaunchActivity(r, results, newIntents, + changes, !andResume, mService.mConfiguration); + // Note: don't need to call pauseIfSleepingLocked() here, because + // the caller will only pass in 'andResume' if this activity is + // currently resumed, which implies we aren't sleeping. + } catch (RemoteException e) { + return false; + } + + if (andResume) { + r.results = null; + r.newIntents = null; + if (mMainStack) { + mService.reportResumedActivityLocked(r); + } + } + + return true; + } +} diff --git a/services/java/com/android/server/am/AppErrorDialog.java b/services/java/com/android/server/am/AppErrorDialog.java index 3a1aad6..a769c05 100644 --- a/services/java/com/android/server/am/AppErrorDialog.java +++ b/services/java/com/android/server/am/AppErrorDialog.java @@ -80,9 +80,6 @@ class AppErrorDialog extends BaseErrorDialog { DISMISS_TIMEOUT); } - public void onStop() { - } - private final Handler mHandler = new Handler() { public void handleMessage(Message msg) { synchronized (mProc) { diff --git a/services/java/com/android/server/am/AppNotRespondingDialog.java b/services/java/com/android/server/am/AppNotRespondingDialog.java index 9702f91..b2737dc 100644 --- a/services/java/com/android/server/am/AppNotRespondingDialog.java +++ b/services/java/com/android/server/am/AppNotRespondingDialog.java @@ -40,7 +40,7 @@ class AppNotRespondingDialog extends BaseErrorDialog { private final ProcessRecord mProc; public AppNotRespondingDialog(ActivityManagerService service, Context context, - ProcessRecord app, HistoryRecord activity) { + ProcessRecord app, ActivityRecord activity) { super(context); mService = service; diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java index 33bbc13..73a5435 100644 --- a/services/java/com/android/server/am/BatteryStatsService.java +++ b/services/java/com/android/server/am/BatteryStatsService.java @@ -23,6 +23,7 @@ import android.os.IBinder; import android.os.Parcel; import android.os.Process; import android.os.ServiceManager; +import android.os.WorkSource; import android.telephony.SignalStrength; import android.util.Slog; @@ -59,7 +60,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub { public void shutdown() { Slog.w("BatteryStats", "Writing battery stats before shutdown..."); synchronized (mStats) { - mStats.writeLocked(); + mStats.shutdownLocked(); } } @@ -93,45 +94,59 @@ public final class BatteryStatsService extends IBatteryStats.Stub { return data; } - public void noteStartWakelock(int uid, String name, int type) { + public void noteStartWakelock(int uid, int pid, String name, int type) { enforceCallingPermission(); synchronized (mStats) { - mStats.getUidStatsLocked(uid).noteStartWakeLocked(name, type); + mStats.noteStartWakeLocked(uid, pid, name, type); } } - public void noteStopWakelock(int uid, String name, int type) { + public void noteStopWakelock(int uid, int pid, String name, int type) { enforceCallingPermission(); synchronized (mStats) { - mStats.getUidStatsLocked(uid).noteStopWakeLocked(name, type); + mStats.noteStopWakeLocked(uid, pid, name, type); + } + } + + public void noteStartWakelockFromSource(WorkSource ws, int pid, String name, int type) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteStartWakeFromSourceLocked(ws, pid, name, type); + } + } + + public void noteStopWakelockFromSource(WorkSource ws, int pid, String name, int type) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteStopWakeFromSourceLocked(ws, pid, name, type); } } public void noteStartSensor(int uid, int sensor) { enforceCallingPermission(); synchronized (mStats) { - mStats.getUidStatsLocked(uid).noteStartSensor(sensor); + mStats.noteStartSensorLocked(uid, sensor); } } public void noteStopSensor(int uid, int sensor) { enforceCallingPermission(); synchronized (mStats) { - mStats.getUidStatsLocked(uid).noteStopSensor(sensor); + mStats.noteStopSensorLocked(uid, sensor); } } public void noteStartGps(int uid) { enforceCallingPermission(); synchronized (mStats) { - mStats.noteStartGps(uid); + mStats.noteStartGpsLocked(uid); } } public void noteStopGps(int uid) { enforceCallingPermission(); synchronized (mStats) { - mStats.noteStopGps(uid); + mStats.noteStopGpsLocked(uid); } } @@ -203,17 +218,17 @@ public final class BatteryStatsService extends IBatteryStats.Stub { } } - public void noteWifiOn(int uid) { + public void noteWifiOn() { enforceCallingPermission(); synchronized (mStats) { - mStats.noteWifiOnLocked(uid); + mStats.noteWifiOnLocked(); } } - public void noteWifiOff(int uid) { + public void noteWifiOff() { enforceCallingPermission(); synchronized (mStats) { - mStats.noteWifiOffLocked(uid); + mStats.noteWifiOffLocked(); } } @@ -245,17 +260,24 @@ public final class BatteryStatsService extends IBatteryStats.Stub { } } - public void noteWifiRunning() { + public void noteWifiRunning(WorkSource ws) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteWifiRunningLocked(ws); + } + } + + public void noteWifiRunningChanged(WorkSource oldWs, WorkSource newWs) { enforceCallingPermission(); synchronized (mStats) { - mStats.noteWifiRunningLocked(); + mStats.noteWifiRunningChangedLocked(oldWs, newWs); } } - public void noteWifiStopped() { + public void noteWifiStopped(WorkSource ws) { enforceCallingPermission(); synchronized (mStats) { - mStats.noteWifiStoppedLocked(); + mStats.noteWifiStoppedLocked(ws); } } @@ -317,18 +339,56 @@ public final class BatteryStatsService extends IBatteryStats.Stub { } } - public boolean isOnBattery() { - return mStats.isOnBattery(); + public void noteFullWifiLockAcquiredFromSource(WorkSource ws) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteFullWifiLockAcquiredFromSourceLocked(ws); + } } - - public void setOnBattery(boolean onBattery, int level) { + + public void noteFullWifiLockReleasedFromSource(WorkSource ws) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteFullWifiLockReleasedFromSourceLocked(ws); + } + } + + public void noteScanWifiLockAcquiredFromSource(WorkSource ws) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteScanWifiLockAcquiredFromSourceLocked(ws); + } + } + + public void noteScanWifiLockReleasedFromSource(WorkSource ws) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteScanWifiLockReleasedFromSourceLocked(ws); + } + } + + public void noteWifiMulticastEnabledFromSource(WorkSource ws) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteWifiMulticastEnabledFromSourceLocked(ws); + } + } + + public void noteWifiMulticastDisabledFromSource(WorkSource ws) { enforceCallingPermission(); - mStats.setOnBattery(onBattery, level); + synchronized (mStats) { + mStats.noteWifiMulticastDisabledFromSourceLocked(ws); + } + } + + public boolean isOnBattery() { + return mStats.isOnBattery(); } - public void recordCurrentLevel(int level) { + public void setBatteryState(int status, int health, int plugType, int level, + int temp, int volt) { enforceCallingPermission(); - mStats.recordCurrentLevel(level); + mStats.setBatteryState(status, health, plugType, level, temp, volt); } public long getAwakeTimeBattery() { @@ -359,7 +419,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub { for (String arg : args) { if ("--checkin".equals(arg)) { isCheckin = true; - break; + } else if ("--reset".equals(arg)) { + mStats.resetAllStatsLocked(); } } } diff --git a/services/java/com/android/server/am/BroadcastRecord.java b/services/java/com/android/server/am/BroadcastRecord.java index c3f0b3e..b268efa 100644 --- a/services/java/com/android/server/am/BroadcastRecord.java +++ b/services/java/com/android/server/am/BroadcastRecord.java @@ -26,6 +26,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.SystemClock; import android.util.PrintWriterPrinter; +import android.util.TimeUtils; import java.io.PrintWriter; import java.util.List; @@ -73,61 +74,65 @@ class BroadcastRecord extends Binder { ActivityInfo curReceiver; // info about the receiver that is currently running. void dump(PrintWriter pw, String prefix) { - pw.println(prefix + this); - pw.println(prefix + intent); + final long now = SystemClock.uptimeMillis(); + + pw.print(prefix); pw.println(this); + pw.print(prefix); pw.println(intent); if (sticky) { Bundle bundle = intent.getExtras(); if (bundle != null) { - pw.println(prefix + "extras: " + bundle.toString()); + pw.print(prefix); pw.print("extras: "); pw.println(bundle.toString()); } } - pw.println(prefix + "proc=" + callerApp); - pw.println(prefix + "caller=" + callerPackage - + " callingPid=" + callingPid - + " callingUid=" + callingUid); + pw.print(prefix); pw.print("caller="); pw.print(callerPackage); pw.print(" "); + pw.print(callerApp != null ? callerApp.toShortString() : "null"); + pw.print(" pid="); pw.print(callingPid); + pw.print(" uid="); pw.println(callingUid); if (requiredPermission != null) { - pw.println(prefix + "requiredPermission=" + requiredPermission); + pw.print(prefix); pw.print("requiredPermission="); pw.println(requiredPermission); } - pw.println(prefix + "dispatchTime=" + dispatchTime + " (" - + (SystemClock.uptimeMillis()-dispatchTime) + "ms since now)"); + pw.print(prefix); pw.print("dispatchTime="); + TimeUtils.formatDuration(dispatchTime, now, pw); if (finishTime != 0) { - pw.println(prefix + "finishTime=" + finishTime + " (" - + (SystemClock.uptimeMillis()-finishTime) + "ms since now)"); + pw.print(" finishTime="); TimeUtils.formatDuration(finishTime, now, pw); } else { - pw.println(prefix + "receiverTime=" + receiverTime + " (" - + (SystemClock.uptimeMillis()-receiverTime) + "ms since now)"); + pw.print(" receiverTime="); TimeUtils.formatDuration(receiverTime, now, pw); } + pw.println(""); if (anrCount != 0) { - pw.println(prefix + "anrCount=" + anrCount); + pw.print(prefix); pw.print("anrCount="); pw.println(anrCount); } if (resultTo != null || resultCode != -1 || resultData != null) { - pw.println(prefix + "resultTo=" + resultTo - + " resultCode=" + resultCode + " resultData=" + resultData); + pw.print(prefix); pw.print("resultTo="); pw.print(resultTo); + pw.print(" resultCode="); pw.print(resultCode); + pw.print(" resultData="); pw.println(resultData); } if (resultExtras != null) { - pw.println(prefix + "resultExtras=" + resultExtras); + pw.print(prefix); pw.print("resultExtras="); pw.println(resultExtras); } if (resultAbort || ordered || sticky || initialSticky) { - pw.println(prefix + "resultAbort=" + resultAbort - + " ordered=" + ordered + " sticky=" + sticky - + " initialSticky=" + initialSticky); + pw.print(prefix); pw.print("resultAbort="); pw.print(resultAbort); + pw.print(" ordered="); pw.print(ordered); + pw.print(" sticky="); pw.print(sticky); + pw.print(" initialSticky="); pw.println(initialSticky); } if (nextReceiver != 0 || receiver != null) { - pw.println(prefix + "nextReceiver=" + nextReceiver - + " receiver=" + receiver); + pw.print(prefix); pw.print("nextReceiver="); pw.print(nextReceiver); + pw.print(" receiver="); pw.println(receiver); } if (curFilter != null) { - pw.println(prefix + "curFilter=" + curFilter); + pw.print(prefix); pw.print("curFilter="); pw.println(curFilter); } if (curReceiver != null) { - pw.println(prefix + "curReceiver=" + curReceiver); + pw.print(prefix); pw.print("curReceiver="); pw.println(curReceiver); } if (curApp != null) { - pw.println(prefix + "curApp=" + curApp); - pw.println(prefix + "curComponent=" - + (curComponent != null ? curComponent.toShortString() : "--")); + pw.print(prefix); pw.print("curApp="); pw.println(curApp); + pw.print(prefix); pw.print("curComponent="); + pw.println((curComponent != null ? curComponent.toShortString() : "--")); if (curReceiver != null && curReceiver.applicationInfo != null) { - pw.println(prefix + "curSourceDir=" + curReceiver.applicationInfo.sourceDir); + pw.print(prefix); pw.print("curSourceDir="); + pw.println(curReceiver.applicationInfo.sourceDir); } } String stateStr = " (?)"; @@ -137,13 +142,14 @@ class BroadcastRecord extends Binder { case CALL_IN_RECEIVE: stateStr=" (CALL_IN_RECEIVE)"; break; case CALL_DONE_RECEIVE: stateStr=" (CALL_DONE_RECEIVE)"; break; } - pw.println(prefix + "state=" + state + stateStr); + pw.print(prefix); pw.print("state="); pw.print(state); pw.println(stateStr); final int N = receivers != null ? receivers.size() : 0; String p2 = prefix + " "; PrintWriterPrinter printer = new PrintWriterPrinter(pw); for (int i=0; i<N; i++) { Object o = receivers.get(i); - pw.println(prefix + "Receiver #" + i + ": " + o); + pw.print(prefix); pw.print("Receiver #"); pw.print(i); + pw.print(": "); pw.println(o); if (o instanceof BroadcastFilter) ((BroadcastFilter)o).dumpBrief(pw, p2); else if (o instanceof ResolveInfo) diff --git a/services/java/com/android/server/am/ConnectionRecord.java b/services/java/com/android/server/am/ConnectionRecord.java index f613b00..22acda9 100644 --- a/services/java/com/android/server/am/ConnectionRecord.java +++ b/services/java/com/android/server/am/ConnectionRecord.java @@ -26,7 +26,7 @@ import java.io.PrintWriter; */ class ConnectionRecord { final AppBindRecord binding; // The application/service binding. - final HistoryRecord activity; // If non-null, the owning activity. + final ActivityRecord activity; // If non-null, the owning activity. final IServiceConnection conn; // The client connection. final int flags; // Binding options. final int clientLabel; // String resource labeling this client. @@ -42,7 +42,7 @@ class ConnectionRecord { + " flags=0x" + Integer.toHexString(flags)); } - ConnectionRecord(AppBindRecord _binding, HistoryRecord _activity, + ConnectionRecord(AppBindRecord _binding, ActivityRecord _activity, IServiceConnection _conn, int _flags, int _clientLabel, PendingIntent _clientIntent) { binding = _binding; diff --git a/services/java/com/android/server/am/ContentProviderRecord.java b/services/java/com/android/server/am/ContentProviderRecord.java index c764635..44c9742 100644 --- a/services/java/com/android/server/am/ContentProviderRecord.java +++ b/services/java/com/android/server/am/ContentProviderRecord.java @@ -17,6 +17,7 @@ package com.android.server.am; import android.app.IActivityManager.ContentProviderHolder; +import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.content.pm.ProviderInfo; import android.os.Process; @@ -29,6 +30,7 @@ class ContentProviderRecord extends ContentProviderHolder { final HashSet<ProcessRecord> clients = new HashSet<ProcessRecord>(); final int uid; final ApplicationInfo appInfo; + final ComponentName name; int externals; // number of non-framework processes supported by this provider ProcessRecord app; // if non-null, hosting application ProcessRecord launchingApp; // if non-null, waiting for this app to be launched. @@ -38,6 +40,7 @@ class ContentProviderRecord extends ContentProviderHolder { super(_info); uid = ai.uid; appInfo = ai; + name = new ComponentName(_info.packageName, _info.name); noReleaseNeeded = uid == 0 || uid == Process.SYSTEM_UID; } @@ -45,6 +48,7 @@ class ContentProviderRecord extends ContentProviderHolder { super(cpr.info); uid = cpr.uid; appInfo = cpr.appInfo; + name = cpr.name; noReleaseNeeded = cpr.noReleaseNeeded; } diff --git a/services/java/com/android/server/am/LaunchWarningWindow.java b/services/java/com/android/server/am/LaunchWarningWindow.java new file mode 100644 index 0000000..4130e33 --- /dev/null +++ b/services/java/com/android/server/am/LaunchWarningWindow.java @@ -0,0 +1,36 @@ +package com.android.server.am; + +import com.android.internal.R; + +import android.app.Dialog; +import android.content.Context; +import android.view.Window; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.TextView; + +public class LaunchWarningWindow extends Dialog { + public LaunchWarningWindow(Context context, ActivityRecord cur, ActivityRecord next) { + super(context, R.style.Theme_Toast); + + requestWindowFeature(Window.FEATURE_LEFT_ICON); + getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); + + setContentView(R.layout.launch_warning); + setTitle(context.getText(R.string.launch_warning_title)); + getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, + R.drawable.ic_dialog_alert); + 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); + text.setText(context.getResources().getString(R.string.launch_warning_replace, + next.info.applicationInfo.loadLabel(context.getPackageManager()).toString())); + icon = (ImageView)findViewById(R.id.original_app_icon); + icon.setImageDrawable(cur.info.applicationInfo.loadIcon(context.getPackageManager())); + text = (TextView)findViewById(R.id.original_message); + text.setText(context.getResources().getString(R.string.launch_warning_original, + cur.info.applicationInfo.loadLabel(context.getPackageManager()).toString())); + } +} diff --git a/services/java/com/android/server/am/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java index 847e91b..7a85eb8 100644 --- a/services/java/com/android/server/am/PendingIntentRecord.java +++ b/services/java/com/android/server/am/PendingIntentRecord.java @@ -42,7 +42,7 @@ class PendingIntentRecord extends IIntentSender.Stub { final static class Key { final int type; final String packageName; - final HistoryRecord activity; + final ActivityRecord activity; final String who; final int requestCode; final Intent requestIntent; @@ -52,7 +52,7 @@ class PendingIntentRecord extends IIntentSender.Stub { private static final int ODD_PRIME_NUMBER = 37; - Key(int _t, String _p, HistoryRecord _a, String _w, + Key(int _t, String _p, ActivityRecord _a, String _w, int _r, Intent _i, String _it, int _f) { type = _t; packageName = _p; @@ -218,7 +218,7 @@ class PendingIntentRecord extends IIntentSender.Stub { } break; case IActivityManager.INTENT_SENDER_ACTIVITY_RESULT: - owner.sendActivityResultLocked(-1, key.activity, + key.activity.stack.sendActivityResultLocked(-1, key.activity, key.who, key.requestCode, code, finalIntent); break; case IActivityManager.INTENT_SENDER_BROADCAST: diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java index f49a182..353ff6d 100644 --- a/services/java/com/android/server/am/ProcessRecord.java +++ b/services/java/com/android/server/am/ProcessRecord.java @@ -17,7 +17,6 @@ package com.android.server.am; import com.android.internal.os.BatteryStatsImpl; -import com.android.server.Watchdog; import android.app.ActivityManager; import android.app.Dialog; @@ -27,9 +26,9 @@ import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.os.Bundle; import android.os.IBinder; -import android.os.RemoteException; import android.os.SystemClock; import android.util.PrintWriterPrinter; +import android.util.TimeUtils; import java.io.PrintWriter; import java.util.ArrayList; @@ -40,12 +39,12 @@ import java.util.HashSet; * Full information about a particular process that * is currently running. */ -class ProcessRecord implements Watchdog.PssRequestor { +class ProcessRecord { final BatteryStatsImpl.Uid.Proc batteryStats; // where to collect runtime statistics final ApplicationInfo info; // all about the first app in the process final String processName; // name of the process // List of packages running in the process - final HashSet<String> pkgList = new HashSet(); + final HashSet<String> pkgList = new HashSet<String>(); IApplicationThread thread; // the actual proc... may be null only if // 'persistent' is true (in which case we // are in the process of launching the app) @@ -61,6 +60,7 @@ class ProcessRecord implements Watchdog.PssRequestor { int setAdj; // Last set OOM adjustment for this process int curSchedGroup; // Currently desired scheduling class int setSchedGroup; // Last set to background scheduling class + boolean keeping; // Actively running code so don't kill due to that? boolean setIsForeground; // Running foreground UI when last set? boolean foregroundServices; // Running any services that are foreground? boolean bad; // True if disabled in the bad process list @@ -75,6 +75,9 @@ class ProcessRecord implements Watchdog.PssRequestor { Bundle instrumentationArguments;// as given to us ComponentName instrumentationResultClass;// copy of instrumentationClass BroadcastRecord curReceiver;// receiver currently running in the app + long lastWakeTime; // How long proc held wake lock at last check + long lastCpuTime; // How long proc has run CPU at last check + long curCpuTime; // How long proc has run CPU most recently long lastRequestedGc; // When we last asked the app to do a gc long lastLowMemory; // When we last told the app that memory is low boolean reportLowMemory; // Set to true when waiting to report low mem @@ -87,9 +90,9 @@ class ProcessRecord implements Watchdog.PssRequestor { Object adjTarget; // Debugging: target component impacting oom_adj. // contains HistoryRecord objects - final ArrayList activities = new ArrayList(); + final ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>(); // all ServiceRecord running in this process - final HashSet services = new HashSet(); + final HashSet<ServiceRecord> services = new HashSet<ServiceRecord>(); // services that are currently executing code (need to remain foreground). final HashSet<ServiceRecord> executingServices = new HashSet<ServiceRecord>(); @@ -99,7 +102,8 @@ class ProcessRecord implements Watchdog.PssRequestor { // all IIntentReceivers that are registered from this process. final HashSet<ReceiverList> receivers = new HashSet<ReceiverList>(); // class (String) -> ContentProviderRecord - final HashMap pubProviders = new HashMap(); + final HashMap<String, ContentProviderRecord> pubProviders + = new HashMap<String, ContentProviderRecord>(); // All ContentProviderRecord process is using final HashMap<ContentProviderRecord, Integer> conProviders = new HashMap<ContentProviderRecord, Integer>(); @@ -111,7 +115,6 @@ class ProcessRecord implements Watchdog.PssRequestor { Dialog anrDialog; // dialog being displayed due to app not resp. boolean removed; // has app package been removed from device? boolean debugging; // was app launched for debugging? - int persistentActivities; // number of activities that are persistent boolean waitedForDebugger; // has process show wait for debugger dialog? Dialog waitDialog; // current wait for debugger dialog @@ -128,7 +131,8 @@ class ProcessRecord implements Watchdog.PssRequestor { ComponentName errorReportReceiver; void dump(PrintWriter pw, String prefix) { - long now = SystemClock.uptimeMillis(); + final long now = SystemClock.uptimeMillis(); + if (info.className != null) { pw.print(prefix); pw.print("class="); pw.println(info.className); } @@ -158,8 +162,10 @@ class ProcessRecord implements Watchdog.PssRequestor { pw.print(" curReceiver="); pw.println(curReceiver); pw.print(prefix); pw.print("pid="); pw.print(pid); pw.print(" starting="); pw.print(starting); pw.print(" lastPss="); pw.println(lastPss); - pw.print(prefix); pw.print("lastActivityTime="); pw.print(lastActivityTime); - pw.print(" lruWeight="); pw.println(lruWeight); + pw.print(prefix); pw.print("lastActivityTime="); + TimeUtils.formatDuration(lastActivityTime, now, pw); + pw.print(" lruWeight="); pw.print(lruWeight); + pw.print(" keeping="); pw.print(keeping); pw.print(" hidden="); pw.print(hidden); pw.print(" empty="); pw.println(empty); pw.print(prefix); pw.print("oom: max="); pw.print(maxAdj); @@ -174,10 +180,28 @@ class ProcessRecord implements Watchdog.PssRequestor { pw.print(" foregroundServices="); pw.print(foregroundServices); pw.print(" forcingToForeground="); pw.println(forcingToForeground); pw.print(prefix); pw.print("persistent="); pw.print(persistent); - pw.print(" removed="); pw.print(removed); - pw.print(" persistentActivities="); pw.println(persistentActivities); + pw.print(" removed="); pw.println(removed); pw.print(prefix); pw.print("adjSeq="); pw.print(adjSeq); pw.print(" lruSeq="); pw.println(lruSeq); + if (!keeping) { + long wtime; + synchronized (batteryStats.getBatteryStats()) { + wtime = batteryStats.getBatteryStats().getProcessWakeTime(info.uid, + pid, SystemClock.elapsedRealtime()); + } + long timeUsed = wtime - lastWakeTime; + pw.print(prefix); pw.print("lastWakeTime="); pw.print(lastWakeTime); + pw.print(" time used="); + TimeUtils.formatDuration(timeUsed, pw); pw.println(""); + pw.print(prefix); pw.print("lastCpuTime="); pw.print(lastCpuTime); + pw.print(" time used="); + TimeUtils.formatDuration(curCpuTime-lastCpuTime, pw); pw.println(""); + } + pw.print(prefix); pw.print("lastRequestedGc="); + TimeUtils.formatDuration(lastRequestedGc, now, pw); + pw.print(" lastLowMemory="); + TimeUtils.formatDuration(lastLowMemory, now, pw); + pw.print(" reportLowMemory="); pw.println(reportLowMemory); if (killedBackground) { pw.print(prefix); pw.print("killedBackground="); pw.println(killedBackground); } @@ -233,7 +257,6 @@ class ProcessRecord implements Watchdog.PssRequestor { curAdj = setAdj = -100; persistent = false; removed = false; - persistentActivities = 0; } public void setPid(int _pid) { @@ -249,7 +272,7 @@ class ProcessRecord implements Watchdog.PssRequestor { public boolean isInterestingToUserLocked() { final int size = activities.size(); for (int i = 0 ; i < size ; i++) { - HistoryRecord r = (HistoryRecord) activities.get(i); + ActivityRecord r = activities.get(i); if (r.isInterestingToUserLocked()) { return true; } @@ -261,17 +284,7 @@ class ProcessRecord implements Watchdog.PssRequestor { int i = activities.size(); while (i > 0) { i--; - ((HistoryRecord)activities.get(i)).stopFreezingScreenLocked(true); - } - } - - public void requestPss() { - IApplicationThread localThread = thread; - if (localThread != null) { - try { - localThread.requestPss(); - } catch (RemoteException e) { - } + activities.get(i).stopFreezingScreenLocked(true); } } diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java index 0542497..e5aceb4 100644 --- a/services/java/com/android/server/am/ServiceRecord.java +++ b/services/java/com/android/server/am/ServiceRecord.java @@ -17,6 +17,7 @@ package com.android.server.am; import com.android.internal.os.BatteryStatsImpl; +import com.android.server.NotificationManagerService; import android.app.INotificationManager; import android.app.Notification; @@ -30,10 +31,12 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.util.Slog; +import android.util.TimeUtils; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -41,6 +44,12 @@ import java.util.List; * A running application service. */ class ServiceRecord extends Binder { + // Maximum number of delivery attempts before giving up. + static final int MAX_DELIVERY_COUNT = 3; + + // Maximum number of times it can fail during execution before giving up. + static final int MAX_DONE_EXECUTING_COUNT = 6; + final ActivityManagerService ams; final BatteryStatsImpl.Uid.Pkg.Serv stats; final ComponentName name; // service component. @@ -63,32 +72,9 @@ class ServiceRecord extends Binder { final HashMap<Intent.FilterComparison, IntentBindRecord> bindings = new HashMap<Intent.FilterComparison, IntentBindRecord>(); // All active bindings to the service. - final HashMap<IBinder, ConnectionRecord> connections - = new HashMap<IBinder, ConnectionRecord>(); + final HashMap<IBinder, ArrayList<ConnectionRecord>> connections + = new HashMap<IBinder, ArrayList<ConnectionRecord>>(); // IBinder -> ConnectionRecord of all bound clients - - // Maximum number of delivery attempts before giving up. - static final int MAX_DELIVERY_COUNT = 3; - - // Maximum number of times it can fail during execution before giving up. - static final int MAX_DONE_EXECUTING_COUNT = 6; - - static class StartItem { - final int id; - final Intent intent; - long deliveredTime; - int deliveryCount; - int doneExecutingCount; - - StartItem(int _id, Intent _intent) { - id = _id; - intent = _intent; - } - } - final ArrayList<StartItem> deliveredStarts = new ArrayList<StartItem>(); - // start() arguments which been delivered. - final ArrayList<StartItem> pendingStarts = new ArrayList<StartItem>(); - // start() arguments that haven't yet been delivered. ProcessRecord app; // where this service is running or null. boolean isForeground; // is service currently in foreground mode? @@ -110,22 +96,93 @@ class ServiceRecord extends Binder { String stringName; // caching of toString + static class StartItem { + final ServiceRecord sr; + final int id; + final Intent intent; + final int targetPermissionUid; + long deliveredTime; + int deliveryCount; + int doneExecutingCount; + UriPermissionOwner uriPermissions; + + String stringName; // caching of toString + + StartItem(ServiceRecord _sr, int _id, Intent _intent, int _targetPermissionUid) { + sr = _sr; + id = _id; + intent = _intent; + targetPermissionUid = _targetPermissionUid; + } + + UriPermissionOwner getUriPermissionsLocked() { + if (uriPermissions == null) { + uriPermissions = new UriPermissionOwner(sr.ams, this); + } + return uriPermissions; + } + + void removeUriPermissionsLocked() { + if (uriPermissions != null) { + uriPermissions.removeUriPermissionsLocked(); + uriPermissions = null; + } + } + + public String toString() { + if (stringName != null) { + return stringName; + } + StringBuilder sb = new StringBuilder(128); + sb.append("ServiceRecord{") + .append(Integer.toHexString(System.identityHashCode(sr))) + .append(' ').append(sr.shortName) + .append(" StartItem ") + .append(Integer.toHexString(System.identityHashCode(this))) + .append(" id=").append(id).append('}'); + return stringName = sb.toString(); + } + } + + final ArrayList<StartItem> deliveredStarts = new ArrayList<StartItem>(); + // start() arguments which been delivered. + final ArrayList<StartItem> pendingStarts = new ArrayList<StartItem>(); + // start() arguments that haven't yet been delivered. + void dumpStartList(PrintWriter pw, String prefix, List<StartItem> list, long now) { final int N = list.size(); for (int i=0; i<N; i++) { StartItem si = list.get(i); pw.print(prefix); pw.print("#"); pw.print(i); pw.print(" id="); pw.print(si.id); - if (now != 0) pw.print(" dur="); pw.print(now-si.deliveredTime); + if (now != 0) { + pw.print(" dur="); + TimeUtils.formatDuration(si.deliveredTime, now, pw); + } if (si.deliveryCount != 0) { pw.print(" dc="); pw.print(si.deliveryCount); } if (si.doneExecutingCount != 0) { pw.print(" dxc="); pw.print(si.doneExecutingCount); } - pw.print(" "); + pw.println(""); + pw.print(prefix); pw.print(" intent="); if (si.intent != null) pw.println(si.intent.toString()); else pw.println("null"); + if (si.targetPermissionUid >= 0) { + pw.print(prefix); pw.print(" targetPermissionUid="); + pw.println(si.targetPermissionUid); + } + if (si.uriPermissions != null) { + if (si.uriPermissions.readUriPermissions != null) { + pw.print(prefix); pw.print(" readUriPermissions="); + pw.println(si.uriPermissions.readUriPermissions); + } + if (si.uriPermissions.writeUriPermissions != null) { + pw.print(prefix); pw.print(" writeUriPermissions="); + pw.println(si.uriPermissions.writeUriPermissions); + } + } } } @@ -139,18 +196,26 @@ class ServiceRecord extends Binder { pw.print(prefix); pw.print("permission="); pw.println(permission); } long now = SystemClock.uptimeMillis(); - pw.print(prefix); pw.print("baseDir="); pw.print(baseDir); - if (!resDir.equals(baseDir)) pw.print(" resDir="); pw.print(resDir); - pw.print(" dataDir="); pw.println(dataDir); + long nowReal = SystemClock.elapsedRealtime(); + pw.print(prefix); pw.print("baseDir="); pw.println(baseDir); + if (!resDir.equals(baseDir)) pw.print(prefix); pw.print("resDir="); pw.println(resDir); + pw.print(prefix); pw.print("dataDir="); pw.println(dataDir); pw.print(prefix); pw.print("app="); pw.println(app); if (isForeground || foregroundId != 0) { pw.print(prefix); pw.print("isForeground="); pw.print(isForeground); pw.print(" foregroundId="); pw.print(foregroundId); pw.print(" foregroundNoti="); pw.println(foregroundNoti); } - pw.print(prefix); pw.print("lastActivity="); pw.print(lastActivity-now); - pw.print(" executingStart="); pw.print(executingStart-now); - pw.print(" restartTime="); pw.println(restartTime); + pw.print(prefix); pw.print("createTime="); + TimeUtils.formatDuration(createTime, nowReal, pw); + pw.print(" lastActivity="); + TimeUtils.formatDuration(lastActivity, now, pw); + pw.println(""); + pw.print(prefix); pw.print(" executingStart="); + TimeUtils.formatDuration(executingStart, now, pw); + pw.print(" restartTime="); + TimeUtils.formatDuration(restartTime, now, pw); + pw.println(""); if (startRequested || lastStartId != 0) { pw.print(prefix); pw.print("startRequested="); pw.print(startRequested); pw.print(" stopIfKilled="); pw.print(stopIfKilled); @@ -161,13 +226,15 @@ class ServiceRecord extends Binder { || restartDelay != 0 || nextRestartTime != 0) { pw.print(prefix); pw.print("executeNesting="); pw.print(executeNesting); pw.print(" restartCount="); pw.print(restartCount); - pw.print(" restartDelay="); pw.print(restartDelay-now); - pw.print(" nextRestartTime="); pw.print(nextRestartTime-now); + pw.print(" restartDelay="); + TimeUtils.formatDuration(restartDelay, now, pw); + pw.print(" nextRestartTime="); + TimeUtils.formatDuration(nextRestartTime, now, pw); pw.print(" crashCount="); pw.println(crashCount); } if (deliveredStarts.size() > 0) { pw.print(prefix); pw.println("Delivered Starts:"); - dumpStartList(pw, prefix, deliveredStarts, SystemClock.uptimeMillis()); + dumpStartList(pw, prefix, deliveredStarts, now); } if (pendingStarts.size() > 0) { pw.print(prefix); pw.println("Pending Starts:"); @@ -186,10 +253,12 @@ class ServiceRecord extends Binder { } if (connections.size() > 0) { pw.print(prefix); pw.println("All Connections:"); - Iterator<ConnectionRecord> it = connections.values().iterator(); + Iterator<ArrayList<ConnectionRecord>> it = connections.values().iterator(); while (it.hasNext()) { - ConnectionRecord c = it.next(); - pw.print(prefix); pw.print(" "); pw.println(c); + ArrayList<ConnectionRecord> c = it.next(); + for (int i=0; i<c.size(); i++) { + pw.print(prefix); pw.print(" "); pw.println(c.get(i)); + } } } } @@ -212,7 +281,8 @@ class ServiceRecord extends Binder { dataDir = sInfo.applicationInfo.dataDir; exported = sInfo.exported; this.restarter = restarter; - createTime = lastActivity = SystemClock.uptimeMillis(); + createTime = SystemClock.elapsedRealtime(); + lastActivity = SystemClock.uptimeMillis(); } public AppBindRecord retrieveAppBindingLocked(Intent intent, @@ -252,6 +322,8 @@ class ServiceRecord extends Binder { } public void postNotification() { + final int appUid = appInfo.uid; + final int appPid = app.pid; if (foregroundId != 0 && foregroundNoti != null) { // Do asynchronous communication with notification manager to // avoid deadlocks. @@ -260,22 +332,24 @@ class ServiceRecord extends Binder { final Notification localForegroundNoti = foregroundNoti; ams.mHandler.post(new Runnable() { public void run() { - INotificationManager inm = NotificationManager.getService(); - if (inm == null) { + NotificationManagerService nm = + (NotificationManagerService) NotificationManager.getService(); + if (nm == null) { return; } try { int[] outId = new int[1]; - inm.enqueueNotification(localPackageName, localForegroundId, - localForegroundNoti, outId); + nm.enqueueNotificationInternal(localPackageName, appUid, appPid, + null, localForegroundId, localForegroundNoti, outId); } catch (RuntimeException e) { Slog.w(ActivityManagerService.TAG, "Error showing notification for service", e); // If it gave us a garbage notification, it doesn't // get to be foreground. ams.setServiceForeground(name, ServiceRecord.this, - localForegroundId, null, true); - } catch (RemoteException e) { + 0, null, true); + ams.crashApplication(appUid, appPid, localPackageName, + "Bad notification for startForeground: " + e); } } }); @@ -306,6 +380,13 @@ class ServiceRecord extends Binder { } } + public void clearDeliveredStartsLocked() { + for (int i=deliveredStarts.size()-1; i>=0; i--) { + deliveredStarts.get(i).removeUriPermissionsLocked(); + } + deliveredStarts.clear(); + } + public String toString() { if (stringName != null) { return stringName; diff --git a/services/java/com/android/server/am/StrictModeViolationDialog.java b/services/java/com/android/server/am/StrictModeViolationDialog.java new file mode 100644 index 0000000..fe76d18 --- /dev/null +++ b/services/java/com/android/server/am/StrictModeViolationDialog.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 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. + */ + +package com.android.server.am; + +import static android.view.WindowManager.LayoutParams.FLAG_SYSTEM_ERROR; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.os.Handler; +import android.os.Message; +import android.util.Slog; + +class StrictModeViolationDialog extends BaseErrorDialog { + private final static String TAG = "StrictModeViolationDialog"; + + private final AppErrorResult mResult; + private final ProcessRecord mProc; + + // Event 'what' codes + static final int ACTION_OK = 0; + static final int ACTION_OK_AND_REPORT = 1; + + // 1-minute timeout, then we automatically dismiss the violation + // dialog + static final long DISMISS_TIMEOUT = 1000 * 60 * 1; + + public StrictModeViolationDialog(Context context, AppErrorResult result, ProcessRecord app) { + super(context); + + Resources res = context.getResources(); + + mProc = app; + mResult = result; + CharSequence name; + if ((app.pkgList.size() == 1) && + (name=context.getPackageManager().getApplicationLabel(app.info)) != null) { + setMessage(res.getString( + com.android.internal.R.string.smv_application, + name.toString(), app.info.processName)); + } else { + name = app.processName; + setMessage(res.getString( + com.android.internal.R.string.smv_process, + name.toString())); + } + + setCancelable(false); + + setButton(DialogInterface.BUTTON_POSITIVE, + res.getText(com.android.internal.R.string.dlg_ok), + mHandler.obtainMessage(ACTION_OK)); + + if (app.errorReportReceiver != null) { + setButton(DialogInterface.BUTTON_NEGATIVE, + res.getText(com.android.internal.R.string.report), + mHandler.obtainMessage(ACTION_OK_AND_REPORT)); + } + + setTitle(res.getText(com.android.internal.R.string.aerr_title)); + getWindow().addFlags(FLAG_SYSTEM_ERROR); + getWindow().setTitle("Strict Mode Violation: " + app.info.processName); + + // After the timeout, pretend the user clicked the quit button + mHandler.sendMessageDelayed( + mHandler.obtainMessage(ACTION_OK), + DISMISS_TIMEOUT); + } + + private final Handler mHandler = new Handler() { + public void handleMessage(Message msg) { + synchronized (mProc) { + if (mProc != null && mProc.crashDialog == StrictModeViolationDialog.this) { + mProc.crashDialog = null; + } + } + mResult.set(msg.what); + + // If this is a timeout we won't be automatically closed, so go + // ahead and explicitly dismiss ourselves just in case. + dismiss(); + } + }; +} diff --git a/services/java/com/android/server/am/UriPermission.java b/services/java/com/android/server/am/UriPermission.java index ffa8a2a..e3347cb 100644 --- a/services/java/com/android/server/am/UriPermission.java +++ b/services/java/com/android/server/am/UriPermission.java @@ -22,13 +22,21 @@ import android.net.Uri; import java.io.PrintWriter; import java.util.HashSet; +/** + * Description of a permission granted to an app to access a particular URI. + * + * CTS tests for this functionality can be run with "runtest cts-appsecurity". + * + * Test cases are at cts/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/ + * src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java + */ class UriPermission { final int uid; final Uri uri; int modeFlags = 0; int globalModeFlags = 0; - final HashSet<HistoryRecord> readActivities = new HashSet<HistoryRecord>(); - final HashSet<HistoryRecord> writeActivities = new HashSet<HistoryRecord>(); + final HashSet<UriPermissionOwner> readOwners = new HashSet<UriPermissionOwner>(); + final HashSet<UriPermissionOwner> writeOwners = new HashSet<UriPermissionOwner>(); String stringName; @@ -37,31 +45,25 @@ class UriPermission { uri = _uri; } - void clearModes(int modeFlags) { - if ((modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) { + void clearModes(int modeFlagsToClear) { + if ((modeFlagsToClear&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) { globalModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION; modeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION; - if (readActivities.size() > 0) { - for (HistoryRecord r : readActivities) { - r.readUriPermissions.remove(this); - if (r.readUriPermissions.size() == 0) { - r.readUriPermissions = null; - } + if (readOwners.size() > 0) { + for (UriPermissionOwner r : readOwners) { + r.removeReadPermission(this); } - readActivities.clear(); + readOwners.clear(); } } - if ((modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) { + if ((modeFlagsToClear&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) { globalModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION; modeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION; - if (readActivities.size() > 0) { - for (HistoryRecord r : readActivities) { - r.writeUriPermissions.remove(this); - if (r.writeUriPermissions.size() == 0) { - r.writeUriPermissions = null; - } + if (readOwners.size() > 0) { + for (UriPermissionOwner r : writeOwners) { + r.removeWritePermission(this); } - readActivities.clear(); + readOwners.clear(); } } } @@ -85,11 +87,17 @@ class UriPermission { pw.print(" uid="); pw.print(uid); pw.print(" globalModeFlags=0x"); pw.println(Integer.toHexString(globalModeFlags)); - if (readActivities.size() != 0) { - pw.print(prefix); pw.print("readActivities="); pw.println(readActivities); + if (readOwners.size() != 0) { + pw.print(prefix); pw.println("readOwners:"); + for (UriPermissionOwner owner : readOwners) { + pw.print(prefix); pw.print(" * "); pw.println(owner); + } } - if (writeActivities.size() != 0) { - pw.print(prefix); pw.print("writeActivities="); pw.println(writeActivities); + if (writeOwners.size() != 0) { + pw.print(prefix); pw.println("writeOwners:"); + for (UriPermissionOwner owner : writeOwners) { + pw.print(prefix); pw.print(" * "); pw.println(owner); + } } } } diff --git a/services/java/com/android/server/am/UriPermissionOwner.java b/services/java/com/android/server/am/UriPermissionOwner.java new file mode 100644 index 0000000..99c82e6 --- /dev/null +++ b/services/java/com/android/server/am/UriPermissionOwner.java @@ -0,0 +1,166 @@ +/* + * 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.am; + +import android.content.Intent; +import android.net.Uri; +import android.os.Binder; +import android.os.IBinder; + +import java.util.HashSet; +import java.util.Iterator; + +class UriPermissionOwner { + final ActivityManagerService service; + final Object owner; + + Binder externalToken; + + HashSet<UriPermission> readUriPermissions; // special access to reading uris. + HashSet<UriPermission> writeUriPermissions; // special access to writing uris. + + class ExternalToken extends Binder { + UriPermissionOwner getOwner() { + return UriPermissionOwner.this; + } + } + + UriPermissionOwner(ActivityManagerService _service, Object _owner) { + service = _service; + owner = _owner; + } + + Binder getExternalTokenLocked() { + if (externalToken != null) { + externalToken = new ExternalToken(); + } + return externalToken; + } + + static UriPermissionOwner fromExternalToken(IBinder token) { + if (token instanceof ExternalToken) { + return ((ExternalToken)token).getOwner(); + } + return null; + } + + void removeUriPermissionsLocked() { + removeUriPermissionsLocked(Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + } + + void removeUriPermissionsLocked(int mode) { + if ((mode&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0 + && readUriPermissions != null) { + for (UriPermission perm : readUriPermissions) { + perm.readOwners.remove(this); + if (perm.readOwners.size() == 0 && (perm.globalModeFlags + &Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0) { + perm.modeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION; + service.removeUriPermissionIfNeededLocked(perm); + } + } + readUriPermissions = null; + } + if ((mode&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0 + && writeUriPermissions != null) { + for (UriPermission perm : writeUriPermissions) { + perm.writeOwners.remove(this); + if (perm.writeOwners.size() == 0 && (perm.globalModeFlags + &Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0) { + perm.modeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION; + service.removeUriPermissionIfNeededLocked(perm); + } + } + writeUriPermissions = null; + } + } + + void removeUriPermissionLocked(Uri uri, int mode) { + if ((mode&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0 + && readUriPermissions != null) { + Iterator<UriPermission> it = readUriPermissions.iterator(); + while (it.hasNext()) { + UriPermission perm = it.next(); + if (uri.equals(perm.uri)) { + perm.readOwners.remove(this); + if (perm.readOwners.size() == 0 && (perm.globalModeFlags + &Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0) { + perm.modeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION; + service.removeUriPermissionIfNeededLocked(perm); + } + it.remove(); + } + } + if (readUriPermissions.size() == 0) { + readUriPermissions = null; + } + } + if ((mode&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0 + && writeUriPermissions != null) { + Iterator<UriPermission> it = writeUriPermissions.iterator(); + while (it.hasNext()) { + UriPermission perm = it.next(); + if (uri.equals(perm.uri)) { + perm.writeOwners.remove(this); + if (perm.writeOwners.size() == 0 && (perm.globalModeFlags + &Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0) { + perm.modeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION; + service.removeUriPermissionIfNeededLocked(perm); + } + it.remove(); + } + } + if (writeUriPermissions.size() == 0) { + writeUriPermissions = null; + } + } + } + + public void addReadPermission(UriPermission perm) { + if (readUriPermissions == null) { + readUriPermissions = new HashSet<UriPermission>(); + } + readUriPermissions.add(perm); + } + + public void addWritePermission(UriPermission perm) { + if (writeUriPermissions == null) { + writeUriPermissions = new HashSet<UriPermission>(); + } + writeUriPermissions.add(perm); + } + + public void removeReadPermission(UriPermission perm) { + readUriPermissions.remove(perm); + if (readUriPermissions.size() == 0) { + readUriPermissions = null; + } + } + + public void removeWritePermission(UriPermission perm) { + writeUriPermissions.remove(perm); + if (writeUriPermissions.size() == 0) { + writeUriPermissions = null; + } + } + + @Override + public String toString() { + return owner.toString(); + } +} diff --git a/services/java/com/android/server/am/UsageStatsService.java b/services/java/com/android/server/am/UsageStatsService.java index 1b9e1c7..6e8f248 100644 --- a/services/java/com/android/server/am/UsageStatsService.java +++ b/services/java/com/android/server/am/UsageStatsService.java @@ -23,6 +23,8 @@ import android.content.Context; import android.os.Binder; import android.os.IBinder; import com.android.internal.os.PkgUsageStats; + +import android.os.FileUtils; import android.os.Parcel; import android.os.Process; import android.os.ServiceManager; @@ -44,6 +46,9 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.TimeZone; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; /** * This service collects the statistics associated with usage @@ -54,6 +59,7 @@ import java.util.TimeZone; public final class UsageStatsService extends IUsageStats.Stub { public static final String SERVICE_NAME = "usagestats"; private static final boolean localLOGV = false; + private static final boolean REPORT_UNEXPECTED = false; private static final String TAG = "UsageStats"; // Current on-disk Parcel version @@ -88,11 +94,13 @@ public final class UsageStatsService extends IUsageStats.Stub { private boolean mIsResumed; private File mFile; private String mFileLeaf; - //private File mBackupFile; - private long mLastWriteElapsedTime; private File mDir; - private Calendar mCal; - private int mLastWriteDay; + + private Calendar mCal; // guarded by itself + + private final AtomicInteger mLastWriteDay = new AtomicInteger(-1); + private final AtomicLong mLastWriteElapsedTime = new AtomicLong(0); + private final AtomicBoolean mUnforcedDiskWriteRunning = new AtomicBoolean(false); static class TimeStats { int count; @@ -241,31 +249,33 @@ public final class UsageStatsService extends IUsageStats.Stub { mFileLeaf = getCurrentDateStr(FILE_PREFIX); mFile = new File(mDir, mFileLeaf); readStatsFromFile(); - mLastWriteElapsedTime = SystemClock.elapsedRealtime(); + mLastWriteElapsedTime.set(SystemClock.elapsedRealtime()); // mCal was set by getCurrentDateStr(), want to use that same time. - mLastWriteDay = mCal.get(Calendar.DAY_OF_YEAR); + mLastWriteDay.set(mCal.get(Calendar.DAY_OF_YEAR)); } /* * Utility method to convert date into string. */ private String getCurrentDateStr(String prefix) { - mCal.setTimeInMillis(System.currentTimeMillis()); StringBuilder sb = new StringBuilder(); - if (prefix != null) { - sb.append(prefix); - } - sb.append(mCal.get(Calendar.YEAR)); - int mm = mCal.get(Calendar.MONTH) - Calendar.JANUARY +1; - if (mm < 10) { - sb.append("0"); - } - sb.append(mm); - int dd = mCal.get(Calendar.DAY_OF_MONTH); - if (dd < 10) { - sb.append("0"); + synchronized (mCal) { + mCal.setTimeInMillis(System.currentTimeMillis()); + if (prefix != null) { + sb.append(prefix); + } + sb.append(mCal.get(Calendar.YEAR)); + int mm = mCal.get(Calendar.MONTH) - Calendar.JANUARY +1; + if (mm < 10) { + sb.append("0"); + } + sb.append(mm); + int dd = mCal.get(Calendar.DAY_OF_MONTH); + if (dd < 10) { + sb.append("0"); + } + sb.append(dd); } - sb.append(dd); return sb.toString(); } @@ -360,23 +370,56 @@ public final class UsageStatsService extends IUsageStats.Stub { file.delete(); } } - - private void writeStatsToFile(boolean force) { - synchronized (mFileLock) { + + /** + * Conditionally start up a disk write if it's been awhile, or the + * day has rolled over. + * + * This is called indirectly from user-facing actions (when + * 'force' is false) so it tries to be quick, without writing to + * disk directly or acquiring heavy locks. + * + * @params force do an unconditional, synchronous stats flush + * to disk on the current thread. + */ + private void writeStatsToFile(final boolean force) { + int curDay; + synchronized (mCal) { mCal.setTimeInMillis(System.currentTimeMillis()); - final int curDay = mCal.get(Calendar.DAY_OF_YEAR); - // Determine if the day changed... note that this will be wrong - // if the year has changed but we are in the same day of year... - // we can probably live with this. - final boolean dayChanged = curDay != mLastWriteDay; - long currElapsedTime = SystemClock.elapsedRealtime(); - if (!force) { - if (((currElapsedTime-mLastWriteElapsedTime) < FILE_WRITE_INTERVAL) && - (!dayChanged)) { - // wait till the next update - return; - } + curDay = mCal.get(Calendar.DAY_OF_YEAR); + } + final boolean dayChanged = curDay != mLastWriteDay.get(); + + // Determine if the day changed... note that this will be wrong + // if the year has changed but we are in the same day of year... + // we can probably live with this. + final long currElapsedTime = SystemClock.elapsedRealtime(); + + // Fast common path, without taking the often-contentious + // mFileLock. + if (!force) { + if (!dayChanged && + (currElapsedTime - mLastWriteElapsedTime.get()) < FILE_WRITE_INTERVAL) { + // wait till the next update + return; } + if (mUnforcedDiskWriteRunning.compareAndSet(false, true)) { + new Thread("UsageStatsService_DiskWriter") { + public void run() { + try { + if (localLOGV) Slog.d(TAG, "Disk writer thread starting."); + writeStatsToFile(true); + } finally { + mUnforcedDiskWriteRunning.set(false); + if (localLOGV) Slog.d(TAG, "Disk writer thread ending."); + } + } + }.start(); + } + return; + } + + synchronized (mFileLock) { // Get the most recent file mFileLeaf = getCurrentDateStr(FILE_PREFIX); // Copy current file to back up @@ -395,10 +438,10 @@ public final class UsageStatsService extends IUsageStats.Stub { try { // Write mStats to file - writeStatsFLOCK(); - mLastWriteElapsedTime = currElapsedTime; + writeStatsFLOCK(mFile); + mLastWriteElapsedTime.set(currElapsedTime); if (dayChanged) { - mLastWriteDay = curDay; + mLastWriteDay.set(curDay); // clear stats synchronized (mStats) { mStats.clear(); @@ -418,10 +461,11 @@ public final class UsageStatsService extends IUsageStats.Stub { } } } + if (localLOGV) Slog.d(TAG, "Dumped usage stats."); } - private void writeStatsFLOCK() throws IOException { - FileOutputStream stream = new FileOutputStream(mFile); + private void writeStatsFLOCK(File file) throws IOException { + FileOutputStream stream = new FileOutputStream(file); try { Parcel out = Parcel.obtain(); writeStatsToParcelFLOCK(out); @@ -429,6 +473,7 @@ public final class UsageStatsService extends IUsageStats.Stub { out.recycle(); stream.flush(); } finally { + FileUtils.sync(stream); stream.close(); } } @@ -452,7 +497,7 @@ public final class UsageStatsService extends IUsageStats.Stub { } public void shutdown() { - Slog.w(TAG, "Writing usage stats before shutdown..."); + Slog.i(TAG, "Writing usage stats before shutdown..."); writeStatsToFile(true); } @@ -479,7 +524,7 @@ public final class UsageStatsService extends IUsageStats.Stub { if (mLastResumedPkg != null) { // We last resumed some other package... just pause it now // to recover. - Slog.i(TAG, "Unexpected resume of " + pkgName + if (REPORT_UNEXPECTED) Slog.i(TAG, "Unexpected resume of " + pkgName + " while already resumed in " + mLastResumedPkg); PkgUsageStatsExtended pus = mStats.get(mLastResumedPkg); if (pus != null) { @@ -518,7 +563,7 @@ public final class UsageStatsService extends IUsageStats.Stub { return; } if (!mIsResumed) { - Slog.i(TAG, "Something wrong here, didn't expect " + if (REPORT_UNEXPECTED) Slog.i(TAG, "Something wrong here, didn't expect " + pkgName + " to be paused"); return; } diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index b29f875..a73a4ce 100644 --- a/services/java/com/android/server/connectivity/Tethering.java +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -26,15 +26,16 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.hardware.Usb; import android.net.ConnectivityManager; import android.net.InterfaceConfiguration; import android.net.IConnectivityManager; import android.net.INetworkManagementEventObserver; import android.net.NetworkInfo; import android.net.NetworkUtils; -import android.os.BatteryManager; import android.os.Binder; import android.os.Environment; +import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.INetworkManagementService; @@ -110,8 +111,15 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private boolean mUsbMassStorageOff; // track the status of USB Mass Storage private boolean mUsbConnected; // track the status of USB connection + // mUsbHandler message + static final int USB_STATE_CHANGE = 1; + static final int USB_DISCONNECTED = 0; + static final int USB_CONNECTED = 1; + + // Time to delay before processing USB disconnect events + static final long USB_DISCONNECT_DELAY = 1000; + public Tethering(Context context, Looper looper) { - Log.d(TAG, "Tethering starting"); mContext = context; mLooper = looper; @@ -135,7 +143,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { mStateReceiver = new StateReceiver(); IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_BATTERY_CHANGED); + filter.addAction(Usb.ACTION_USB_STATE); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); filter.addAction(Intent.ACTION_BOOT_COMPLETED); mContext.registerReceiver(mStateReceiver, filter); @@ -421,13 +429,25 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } + private Handler mUsbHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + mUsbConnected = (msg.arg1 == USB_CONNECTED); + updateUsbStatus(); + } + }; + private class StateReceiver extends BroadcastReceiver { public void onReceive(Context content, Intent intent) { String action = intent.getAction(); - if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { - mUsbConnected = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) - == BatteryManager.BATTERY_PLUGGED_USB); - Tethering.this.updateUsbStatus(); + if (action.equals(Usb.ACTION_USB_STATE)) { + // process connect events immediately, but delay handling disconnects + // to debounce USB configuration changes + boolean connected = intent.getExtras().getBoolean(Usb.USB_CONNECTED); + Message msg = Message.obtain(mUsbHandler, USB_STATE_CHANGE, + (connected ? USB_CONNECTED : USB_DISCONNECTED), 0); + mUsbHandler.removeMessages(USB_STATE_CHANGE); + mUsbHandler.sendMessageDelayed(msg, connected ? 0 : USB_DISCONNECT_DELAY); } else if (action.equals(Intent.ACTION_MEDIA_SHARED)) { mUsbMassStorageOff = false; updateUsbStatus(); @@ -436,7 +456,6 @@ public class Tethering extends INetworkManagementEventObserver.Stub { mUsbMassStorageOff = true; updateUsbStatus(); } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { - Log.d(TAG, "Tethering got CONNECTIVITY_ACTION"); mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED); } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { mBooted = true; diff --git a/services/java/com/android/server/location/GeocoderProxy.java b/services/java/com/android/server/location/GeocoderProxy.java new file mode 100644 index 0000000..e3131fe --- /dev/null +++ b/services/java/com/android/server/location/GeocoderProxy.java @@ -0,0 +1,126 @@ +/* + * 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.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.location.Address; +import android.location.GeocoderParams; +import android.location.IGeocodeProvider; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Log; + +import java.util.List; + +/** + * A class for proxying IGeocodeProvider implementations. + * + * {@hide} + */ +public class GeocoderProxy { + + private static final String TAG = "GeocoderProxy"; + + private final Context mContext; + private final Intent mIntent; + private final Object mMutex = new Object(); // synchronizes access to mServiceConnection + private Connection mServiceConnection = new Connection(); // never null + + public GeocoderProxy(Context context, String serviceName) { + mContext = context; + mIntent = new Intent(serviceName); + mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE); + } + + /** + * When unbundled NetworkLocationService package is updated, we + * need to unbind from the old version and re-bind to the new one. + */ + public void reconnect() { + synchronized (mMutex) { + mContext.unbindService(mServiceConnection); + mServiceConnection = new Connection(); + mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE); + } + } + + private class Connection implements ServiceConnection { + + private IGeocodeProvider mProvider; + + public void onServiceConnected(ComponentName className, IBinder service) { + Log.d(TAG, "onServiceConnected " + className); + synchronized (this) { + mProvider = IGeocodeProvider.Stub.asInterface(service); + } + } + + public void onServiceDisconnected(ComponentName className) { + Log.d(TAG, "onServiceDisconnected " + className); + synchronized (this) { + mProvider = null; + } + } + + public IGeocodeProvider getProvider() { + synchronized (this) { + return mProvider; + } + } + } + + public String getFromLocation(double latitude, double longitude, int maxResults, + GeocoderParams params, List<Address> addrs) { + IGeocodeProvider provider; + synchronized (mMutex) { + provider = mServiceConnection.getProvider(); + } + if (provider != null) { + try { + return provider.getFromLocation(latitude, longitude, maxResults, + params, addrs); + } catch (RemoteException e) { + Log.e(TAG, "getFromLocation failed", e); + } + } + return "Service not Available"; + } + + public String getFromLocationName(String locationName, + double lowerLeftLatitude, double lowerLeftLongitude, + double upperRightLatitude, double upperRightLongitude, int maxResults, + GeocoderParams params, List<Address> addrs) { + IGeocodeProvider provider; + synchronized (mMutex) { + provider = mServiceConnection.getProvider(); + } + if (provider != null) { + try { + return provider.getFromLocationName(locationName, lowerLeftLatitude, + lowerLeftLongitude, upperRightLatitude, upperRightLongitude, + maxResults, params, addrs); + } catch (RemoteException e) { + Log.e(TAG, "getFromLocationName failed", e); + } + } + return "Service not Available"; + } +} diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java new file mode 100755 index 0000000..3561862 --- /dev/null +++ b/services/java/com/android/server/location/GpsLocationProvider.java @@ -0,0 +1,1621 @@ +/* + * 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.location; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.location.Criteria; +import android.location.IGpsStatusListener; +import android.location.IGpsStatusProvider; +import android.location.ILocationManager; +import android.location.INetInitiatedListener; +import android.location.Location; +import android.location.LocationManager; +import android.location.LocationProvider; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.SntpClient; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +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.SystemClock; +import android.os.WorkSource; +import android.provider.Settings; +import android.provider.Telephony.Sms.Intents; +import android.telephony.TelephonyManager; +import android.telephony.gsm.GsmCellLocation; +import android.telephony.SmsMessage; +import android.util.Log; +import android.util.SparseIntArray; + +import com.android.internal.app.IBatteryStats; +import com.android.internal.telephony.Phone; +import com.android.internal.location.GpsNetInitiatedHandler; +import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification; +import com.android.internal.telephony.GsmAlphabet; +import com.android.internal.telephony.SmsHeader; +import com.android.internal.util.HexDump; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.StringBufferInputStream; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Date; +import java.util.Properties; +import java.util.Map.Entry; +import java.util.concurrent.CountDownLatch; + +/** + * A GPS implementation of LocationProvider used by LocationManager. + * + * {@hide} + */ +public class GpsLocationProvider implements LocationProviderInterface { + + private static final String TAG = "GpsLocationProvider"; + + private static final boolean DEBUG = false; + private static final boolean VERBOSE = false; + + // these need to match GpsPositionMode enum in gps.h + private static final int GPS_POSITION_MODE_STANDALONE = 0; + private static final int GPS_POSITION_MODE_MS_BASED = 1; + private static final int GPS_POSITION_MODE_MS_ASSISTED = 2; + + // these need to match GpsPositionRecurrence enum in gps.h + private static final int GPS_POSITION_RECURRENCE_PERIODIC = 0; + private static final int GPS_POSITION_RECURRENCE_SINGLE = 1; + + // these need to match GpsStatusValue defines in gps.h + private static final int GPS_STATUS_NONE = 0; + private static final int GPS_STATUS_SESSION_BEGIN = 1; + private static final int GPS_STATUS_SESSION_END = 2; + private static final int GPS_STATUS_ENGINE_ON = 3; + private static final int GPS_STATUS_ENGINE_OFF = 4; + + // these need to match GpsApgsStatusValue defines in gps.h + /** AGPS status event values. */ + private static final int GPS_REQUEST_AGPS_DATA_CONN = 1; + private static final int GPS_RELEASE_AGPS_DATA_CONN = 2; + private static final int GPS_AGPS_DATA_CONNECTED = 3; + private static final int GPS_AGPS_DATA_CONN_DONE = 4; + private static final int GPS_AGPS_DATA_CONN_FAILED = 5; + + // these need to match GpsLocationFlags enum in gps.h + private static final int LOCATION_INVALID = 0; + private static final int LOCATION_HAS_LAT_LONG = 1; + private static final int LOCATION_HAS_ALTITUDE = 2; + private static final int LOCATION_HAS_SPEED = 4; + private static final int LOCATION_HAS_BEARING = 8; + private static final int LOCATION_HAS_ACCURACY = 16; + +// IMPORTANT - the GPS_DELETE_* symbols here must match constants in gps.h + private static final int GPS_DELETE_EPHEMERIS = 0x0001; + private static final int GPS_DELETE_ALMANAC = 0x0002; + private static final int GPS_DELETE_POSITION = 0x0004; + private static final int GPS_DELETE_TIME = 0x0008; + private static final int GPS_DELETE_IONO = 0x0010; + private static final int GPS_DELETE_UTC = 0x0020; + private static final int GPS_DELETE_HEALTH = 0x0040; + private static final int GPS_DELETE_SVDIR = 0x0080; + private static final int GPS_DELETE_SVSTEER = 0x0100; + private static final int GPS_DELETE_SADATA = 0x0200; + private static final int GPS_DELETE_RTI = 0x0400; + private static final int GPS_DELETE_CELLDB_INFO = 0x8000; + private static final int GPS_DELETE_ALL = 0xFFFF; + + // The GPS_CAPABILITY_* flags must match the values in gps.h + private static final int GPS_CAPABILITY_SCHEDULING = 0x0000001; + private static final int GPS_CAPABILITY_MSB = 0x0000002; + private static final int GPS_CAPABILITY_MSA = 0x0000004; + private static final int GPS_CAPABILITY_SINGLE_SHOT = 0x0000008; + + + // these need to match AGpsType enum in gps.h + private static final int AGPS_TYPE_SUPL = 1; + private static final int AGPS_TYPE_C2K = 2; + + // for mAGpsDataConnectionState + private static final int AGPS_DATA_CONNECTION_CLOSED = 0; + private static final int AGPS_DATA_CONNECTION_OPENING = 1; + private static final int AGPS_DATA_CONNECTION_OPEN = 2; + + // Handler messages + private static final int CHECK_LOCATION = 1; + private static final int ENABLE = 2; + private static final int ENABLE_TRACKING = 3; + private static final int UPDATE_NETWORK_STATE = 4; + private static final int INJECT_NTP_TIME = 5; + private static final int DOWNLOAD_XTRA_DATA = 6; + private static final int UPDATE_LOCATION = 7; + private static final int ADD_LISTENER = 8; + private static final int REMOVE_LISTENER = 9; + private static final int REQUEST_SINGLE_SHOT = 10; + + // Request setid + private static final int AGPS_RIL_REQUEST_SETID_IMSI = 1; + private static final int AGPS_RIL_REQUEST_SETID_MSISDN = 2; + + // Request ref location + private static final int AGPS_RIL_REQUEST_REFLOC_CELLID = 1; + private static final int AGPS_RIL_REQUEST_REFLOC_MAC = 2; + + // ref. location info + private static final int AGPS_REF_LOCATION_TYPE_GSM_CELLID = 1; + private static final int AGPS_REF_LOCATION_TYPE_UMTS_CELLID = 2; + private static final int AGPS_REG_LOCATION_TYPE_MAC = 3; + + // set id info + private static final int AGPS_SETID_TYPE_NONE = 0; + private static final int AGPS_SETID_TYPE_IMSI = 1; + private static final int AGPS_SETID_TYPE_MSISDN = 2; + + private static final String PROPERTIES_FILE = "/etc/gps.conf"; + + private int mLocationFlags = LOCATION_INVALID; + + // current status + private int mStatus = LocationProvider.TEMPORARILY_UNAVAILABLE; + + // time for last status update + private long mStatusUpdateTime = SystemClock.elapsedRealtime(); + + // turn off GPS fix icon if we haven't received a fix in 10 seconds + private static final long RECENT_FIX_TIMEOUT = 10 * 1000; + + // stop trying if we do not receive a fix within 60 seconds + private static final int NO_FIX_TIMEOUT = 60 * 1000; + + // true if we are enabled + private volatile boolean mEnabled; + + // true if we have network connectivity + private boolean mNetworkAvailable; + + // flags to trigger NTP or XTRA data download when network becomes available + // initialized to true so we do NTP and XTRA when the network comes up after booting + private boolean mInjectNtpTimePending = true; + private boolean mDownloadXtraDataPending = false; + + // true if GPS is navigating + private boolean mNavigating; + + // true if GPS engine is on + private boolean mEngineOn; + + // requested frequency of fixes, in milliseconds + private int mFixInterval = 1000; + + // true if we started navigation + private boolean mStarted; + + // true if single shot request is in progress + private boolean mSingleShot; + + // capabilities of the GPS engine + private int mEngineCapabilities; + + // true if XTRA is supported + private boolean mSupportsXtra; + + // for calculating time to first fix + private long mFixRequestTime = 0; + // time to first fix for most recent session + private int mTTFF = 0; + // time we received our last fix + private long mLastFixTime; + + private int mPositionMode; + + // properties loaded from PROPERTIES_FILE + private Properties mProperties; + private String mNtpServer; + private String mSuplServerHost; + private int mSuplServerPort; + private String mC2KServerHost; + private int mC2KServerPort; + + private final Context mContext; + private final ILocationManager mLocationManager; + private Location mLocation = new Location(LocationManager.GPS_PROVIDER); + private Bundle mLocationExtras = new Bundle(); + private ArrayList<Listener> mListeners = new ArrayList<Listener>(); + + // GpsLocationProvider's handler thread + private final Thread mThread; + // Handler for processing events in mThread. + private Handler mHandler; + // Used to signal when our main thread has initialized everything + private final CountDownLatch mInitializedLatch = new CountDownLatch(1); + + private String mAGpsApn; + private int mAGpsDataConnectionState; + private final ConnectivityManager mConnMgr; + private final GpsNetInitiatedHandler mNIHandler; + + // Wakelocks + private final static String WAKELOCK_KEY = "GpsLocationProvider"; + private final PowerManager.WakeLock mWakeLock; + // bitfield of pending messages to our Handler + // used only for messages that cannot have multiple instances queued + private int mPendingMessageBits; + // separate counter for ADD_LISTENER and REMOVE_LISTENER messages, + // which might have multiple instances queued + private int mPendingListenerMessages; + + // Alarms + private final static String ALARM_WAKEUP = "com.android.internal.location.ALARM_WAKEUP"; + private final static String ALARM_TIMEOUT = "com.android.internal.location.ALARM_TIMEOUT"; + private final AlarmManager mAlarmManager; + private final PendingIntent mWakeupIntent; + private final PendingIntent mTimeoutIntent; + + private final IBatteryStats mBatteryStats; + private final SparseIntArray mClientUids = new SparseIntArray(); + + // how often to request NTP time, in milliseconds + // current setting 4 hours + private static final long NTP_INTERVAL = 4*60*60*1000; + // how long to wait if we have a network error in NTP or XTRA downloading + // current setting - 5 minutes + private static final long RETRY_INTERVAL = 5*60*1000; + + // to avoid injecting bad NTP time, we reject any time fixes that differ from system time + // by more than 5 minutes. + private static final long MAX_NTP_SYSTEM_TIME_OFFSET = 5*60*1000; + + private final IGpsStatusProvider mGpsStatusProvider = new IGpsStatusProvider.Stub() { + public void addGpsStatusListener(IGpsStatusListener listener) throws RemoteException { + if (listener == null) { + throw new NullPointerException("listener is null in addGpsStatusListener"); + } + + synchronized(mListeners) { + IBinder binder = listener.asBinder(); + int size = mListeners.size(); + for (int i = 0; i < size; i++) { + Listener test = mListeners.get(i); + if (binder.equals(test.mListener.asBinder())) { + // listener already added + return; + } + } + + Listener l = new Listener(listener); + binder.linkToDeath(l, 0); + mListeners.add(l); + } + } + + public void removeGpsStatusListener(IGpsStatusListener listener) { + if (listener == null) { + throw new NullPointerException("listener is null in addGpsStatusListener"); + } + + synchronized(mListeners) { + IBinder binder = listener.asBinder(); + Listener l = null; + int size = mListeners.size(); + for (int i = 0; i < size && l == null; i++) { + Listener test = mListeners.get(i); + if (binder.equals(test.mListener.asBinder())) { + l = test; + } + } + + if (l != null) { + mListeners.remove(l); + binder.unlinkToDeath(l, 0); + } + } + } + }; + + public IGpsStatusProvider getGpsStatusProvider() { + return mGpsStatusProvider; + } + + private final BroadcastReceiver mBroadcastReciever = new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + if (action.equals(ALARM_WAKEUP)) { + if (DEBUG) Log.d(TAG, "ALARM_WAKEUP"); + startNavigating(false); + } else if (action.equals(ALARM_TIMEOUT)) { + if (DEBUG) Log.d(TAG, "ALARM_TIMEOUT"); + hibernate(); + } else if (action.equals(Intents.DATA_SMS_RECEIVED_ACTION)) { + checkSmsSuplInit(intent); + } else if (action.equals(Intents.WAP_PUSH_RECEIVED_ACTION)) { + checkWapSuplInit(intent); + } + } + }; + + private void checkSmsSuplInit(Intent intent) { + SmsMessage[] messages = Intents.getMessagesFromIntent(intent); + for (int i=0; i <messages.length; i++) { + byte[] supl_init = messages[i].getUserData(); + native_agps_ni_message(supl_init,supl_init.length); + } + } + + private void checkWapSuplInit(Intent intent) { + byte[] supl_init = (byte[]) intent.getExtra("data"); + native_agps_ni_message(supl_init,supl_init.length); + } + + public static boolean isSupported() { + return native_is_supported(); + } + + public GpsLocationProvider(Context context, ILocationManager locationManager) { + mContext = context; + mLocationManager = locationManager; + mNIHandler = new GpsNetInitiatedHandler(context); + + mLocation.setExtras(mLocationExtras); + + // Create a wake lock + PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY); + mWakeLock.setReferenceCounted(false); + + mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); + mWakeupIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_WAKEUP), 0); + mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_TIMEOUT), 0); + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intents.DATA_SMS_RECEIVED_ACTION); + intentFilter.addDataScheme("sms"); + intentFilter.addDataAuthority("localhost","7275"); + context.registerReceiver(mBroadcastReciever, intentFilter); + + intentFilter = new IntentFilter(); + intentFilter.addAction(Intents.WAP_PUSH_RECEIVED_ACTION); + try { + intentFilter.addDataType("application/vnd.omaloc-supl-init"); + } catch (IntentFilter.MalformedMimeTypeException e) { + Log.w(TAG, "Malformed SUPL init mime type"); + } + context.registerReceiver(mBroadcastReciever, intentFilter); + + mConnMgr = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); + + // Battery statistics service to be notified when GPS turns on or off + mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo")); + + mProperties = new Properties(); + try { + File file = new File(PROPERTIES_FILE); + FileInputStream stream = new FileInputStream(file); + mProperties.load(stream); + stream.close(); + mNtpServer = mProperties.getProperty("NTP_SERVER", null); + + mSuplServerHost = mProperties.getProperty("SUPL_HOST"); + String portString = mProperties.getProperty("SUPL_PORT"); + if (mSuplServerHost != null && portString != null) { + try { + mSuplServerPort = Integer.parseInt(portString); + } catch (NumberFormatException e) { + Log.e(TAG, "unable to parse SUPL_PORT: " + portString); + } + } + + mC2KServerHost = mProperties.getProperty("C2K_HOST"); + portString = mProperties.getProperty("C2K_PORT"); + if (mC2KServerHost != null && portString != null) { + try { + mC2KServerPort = Integer.parseInt(portString); + } catch (NumberFormatException e) { + Log.e(TAG, "unable to parse C2K_PORT: " + portString); + } + } + } catch (IOException e) { + Log.w(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE); + } + + // wait until we are fully initialized before returning + mThread = new GpsLocationProviderThread(); + mThread.start(); + while (true) { + try { + mInitializedLatch.await(); + break; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + + private void initialize() { + // register our receiver on our thread rather than the main thread + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(ALARM_WAKEUP); + intentFilter.addAction(ALARM_TIMEOUT); + mContext.registerReceiver(mBroadcastReciever, intentFilter); + } + + /** + * Returns the name of this provider. + */ + public String getName() { + return LocationManager.GPS_PROVIDER; + } + + /** + * Returns true if the provider requires access to a + * data network (e.g., the Internet), false otherwise. + */ + public boolean requiresNetwork() { + return true; + } + + public void updateNetworkState(int state, NetworkInfo info) { + sendMessage(UPDATE_NETWORK_STATE, state, info); + } + + private void handleUpdateNetworkState(int state, NetworkInfo info) { + mNetworkAvailable = (state == LocationProvider.AVAILABLE); + + if (DEBUG) { + Log.d(TAG, "updateNetworkState " + (mNetworkAvailable ? "available" : "unavailable") + + " info: " + info); + } + + if (info != null) { + native_update_network_state(info.isConnected(), info.getType(), + info.isRoaming(), info.getExtraInfo()); + } + + if (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE_SUPL + && mAGpsDataConnectionState == AGPS_DATA_CONNECTION_OPENING) { + String apnName = info.getExtraInfo(); + if (mNetworkAvailable && apnName != null && apnName.length() > 0) { + mAGpsApn = apnName; + if (DEBUG) Log.d(TAG, "call native_agps_data_conn_open"); + native_agps_data_conn_open(apnName); + mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN; + } else { + if (DEBUG) Log.d(TAG, "call native_agps_data_conn_failed"); + mAGpsApn = null; + mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED; + native_agps_data_conn_failed(); + } + } + + if (mNetworkAvailable) { + if (mInjectNtpTimePending) { + sendMessage(INJECT_NTP_TIME, 0, null); + } + if (mDownloadXtraDataPending) { + sendMessage(DOWNLOAD_XTRA_DATA, 0, null); + } + } + } + + private void handleInjectNtpTime() { + if (!mNetworkAvailable) { + // try again when network is up + mInjectNtpTimePending = true; + return; + } + mInjectNtpTimePending = false; + + SntpClient client = new SntpClient(); + long delay; + + if (client.requestTime(mNtpServer, 10000)) { + long time = client.getNtpTime(); + long timeReference = client.getNtpTimeReference(); + int certainty = (int)(client.getRoundTripTime()/2); + long now = System.currentTimeMillis(); + + Log.d(TAG, "NTP server returned: " + + time + " (" + new Date(time) + + ") reference: " + timeReference + + " certainty: " + certainty + + " system time offset: " + (time - now)); + + native_inject_time(time, timeReference, certainty); + delay = NTP_INTERVAL; + } else { + if (DEBUG) Log.d(TAG, "requestTime failed"); + delay = RETRY_INTERVAL; + } + + // send delayed message for next NTP injection + // since this is delayed and not urgent we do not hold a wake lock here + mHandler.removeMessages(INJECT_NTP_TIME); + mHandler.sendMessageDelayed(Message.obtain(mHandler, INJECT_NTP_TIME), delay); + } + + private void handleDownloadXtraData() { + if (!mNetworkAvailable) { + // try again when network is up + mDownloadXtraDataPending = true; + return; + } + mDownloadXtraDataPending = false; + + + GpsXtraDownloader xtraDownloader = new GpsXtraDownloader(mContext, mProperties); + byte[] data = xtraDownloader.downloadXtraData(); + if (data != null) { + if (DEBUG) { + Log.d(TAG, "calling native_inject_xtra_data"); + } + native_inject_xtra_data(data, data.length); + } else { + // try again later + // since this is delayed and not urgent we do not hold a wake lock here + mHandler.removeMessages(DOWNLOAD_XTRA_DATA); + mHandler.sendMessageDelayed(Message.obtain(mHandler, DOWNLOAD_XTRA_DATA), RETRY_INTERVAL); + } + } + + /** + * This is called to inform us when another location provider returns a location. + * Someday we might use this for network location injection to aid the GPS + */ + public void updateLocation(Location location) { + sendMessage(UPDATE_LOCATION, 0, location); + } + + private void handleUpdateLocation(Location location) { + if (location.hasAccuracy()) { + native_inject_location(location.getLatitude(), location.getLongitude(), + location.getAccuracy()); + } + } + + /** + * Returns true if the provider requires access to a + * satellite-based positioning system (e.g., GPS), false + * otherwise. + */ + public boolean requiresSatellite() { + return true; + } + + /** + * Returns true if the provider requires access to an appropriate + * cellular network (e.g., to make use of cell tower IDs), false + * otherwise. + */ + public boolean requiresCell() { + return false; + } + + /** + * Returns true if the use of this provider may result in a + * monetary charge to the user, false if use is free. It is up to + * each provider to give accurate information. + */ + public boolean hasMonetaryCost() { + return false; + } + + /** + * Returns true if the provider is able to provide altitude + * information, false otherwise. A provider that reports altitude + * under most circumstances but may occassionally not report it + * should return true. + */ + public boolean supportsAltitude() { + return true; + } + + /** + * Returns true if the provider is able to provide speed + * information, false otherwise. A provider that reports speed + * under most circumstances but may occassionally not report it + * should return true. + */ + public boolean supportsSpeed() { + return true; + } + + /** + * Returns true if the provider is able to provide bearing + * information, false otherwise. A provider that reports bearing + * under most circumstances but may occassionally not report it + * should return true. + */ + public boolean supportsBearing() { + return true; + } + + /** + * Returns the power requirement for this provider. + * + * @return the power requirement for this provider, as one of the + * constants Criteria.POWER_REQUIREMENT_*. + */ + public int getPowerRequirement() { + return Criteria.POWER_HIGH; + } + + /** + * Returns true if this provider meets the given criteria, + * false otherwise. + */ + public boolean meetsCriteria(Criteria criteria) { + return (criteria.getPowerRequirement() != Criteria.POWER_LOW); + } + + /** + * Returns the horizontal accuracy of this provider + * + * @return the accuracy of location from this provider, as one + * of the constants Criteria.ACCURACY_*. + */ + public int getAccuracy() { + return Criteria.ACCURACY_FINE; + } + + /** + * Enables this provider. When enabled, calls to getStatus() + * must be handled. Hardware may be started up + * when the provider is enabled. + */ + public void enable() { + synchronized (mHandler) { + sendMessage(ENABLE, 1, null); + } + } + + private void handleEnable() { + if (DEBUG) Log.d(TAG, "handleEnable"); + if (mEnabled) return; + mEnabled = native_init(); + + if (mEnabled) { + mSupportsXtra = native_supports_xtra(); + if (mSuplServerHost != null) { + native_set_agps_server(AGPS_TYPE_SUPL, mSuplServerHost, mSuplServerPort); + } + if (mC2KServerHost != null) { + native_set_agps_server(AGPS_TYPE_C2K, mC2KServerHost, mC2KServerPort); + } + } else { + Log.w(TAG, "Failed to enable location provider"); + } + } + + /** + * Disables this provider. When disabled, calls to getStatus() + * need not be handled. Hardware may be shut + * down while the provider is disabled. + */ + public void disable() { + synchronized (mHandler) { + sendMessage(ENABLE, 0, null); + } + } + + private void handleDisable() { + if (DEBUG) Log.d(TAG, "handleDisable"); + if (!mEnabled) return; + + mEnabled = false; + stopNavigating(); + + // do this before releasing wakelock + native_cleanup(); + } + + public boolean isEnabled() { + return mEnabled; + } + + public int getStatus(Bundle extras) { + if (extras != null) { + extras.putInt("satellites", mSvCount); + } + return mStatus; + } + + private void updateStatus(int status, int svCount) { + if (status != mStatus || svCount != mSvCount) { + mStatus = status; + mSvCount = svCount; + mLocationExtras.putInt("satellites", svCount); + mStatusUpdateTime = SystemClock.elapsedRealtime(); + } + } + + public long getStatusUpdateTime() { + return mStatusUpdateTime; + } + + public void enableLocationTracking(boolean enable) { + // FIXME - should set a flag here to avoid race conditions with single shot request + synchronized (mHandler) { + sendMessage(ENABLE_TRACKING, (enable ? 1 : 0), null); + } + } + + private void handleEnableLocationTracking(boolean enable) { + if (enable) { + mTTFF = 0; + mLastFixTime = 0; + startNavigating(false); + } else { + if (!hasCapability(GPS_CAPABILITY_SCHEDULING)) { + mAlarmManager.cancel(mWakeupIntent); + mAlarmManager.cancel(mTimeoutIntent); + } + stopNavigating(); + } + } + + public boolean requestSingleShotFix() { + if (mStarted) { + // cannot do single shot if already navigating + return false; + } + synchronized (mHandler) { + mHandler.removeMessages(REQUEST_SINGLE_SHOT); + Message m = Message.obtain(mHandler, REQUEST_SINGLE_SHOT); + mHandler.sendMessage(m); + } + return true; + } + + private void handleRequestSingleShot() { + mTTFF = 0; + mLastFixTime = 0; + startNavigating(true); + } + + public void setMinTime(long minTime, WorkSource ws) { + if (DEBUG) Log.d(TAG, "setMinTime " + minTime); + + if (minTime >= 0) { + mFixInterval = (int)minTime; + + if (mStarted && hasCapability(GPS_CAPABILITY_SCHEDULING)) { + if (!native_set_position_mode(mPositionMode, GPS_POSITION_RECURRENCE_PERIODIC, + mFixInterval, 0, 0)) { + Log.e(TAG, "set_position_mode failed in setMinTime()"); + } + } + } + } + + public String getInternalState() { + return native_get_internal_state(); + } + + private final class Listener implements IBinder.DeathRecipient { + final IGpsStatusListener mListener; + + int mSensors = 0; + + Listener(IGpsStatusListener listener) { + mListener = listener; + } + + public void binderDied() { + if (DEBUG) Log.d(TAG, "GPS status listener died"); + + synchronized(mListeners) { + mListeners.remove(this); + } + if (mListener != null) { + mListener.asBinder().unlinkToDeath(this, 0); + } + } + } + + public void addListener(int uid) { + synchronized (mWakeLock) { + mPendingListenerMessages++; + mWakeLock.acquire(); + Message m = Message.obtain(mHandler, ADD_LISTENER); + m.arg1 = uid; + mHandler.sendMessage(m); + } + } + + private void handleAddListener(int uid) { + synchronized(mListeners) { + if (mClientUids.indexOfKey(uid) >= 0) { + // Shouldn't be here -- already have this uid. + Log.w(TAG, "Duplicate add listener for uid " + uid); + return; + } + mClientUids.put(uid, 0); + if (mNavigating) { + try { + mBatteryStats.noteStartGps(uid); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in addListener"); + } + } + } + } + + public void removeListener(int uid) { + synchronized (mWakeLock) { + mPendingListenerMessages++; + mWakeLock.acquire(); + Message m = Message.obtain(mHandler, REMOVE_LISTENER); + m.arg1 = uid; + mHandler.sendMessage(m); + } + } + + private void handleRemoveListener(int uid) { + synchronized(mListeners) { + if (mClientUids.indexOfKey(uid) < 0) { + // Shouldn't be here -- don't have this uid. + Log.w(TAG, "Unneeded remove listener for uid " + uid); + return; + } + mClientUids.delete(uid); + if (mNavigating) { + try { + mBatteryStats.noteStopGps(uid); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in removeListener"); + } + } + } + } + + public boolean sendExtraCommand(String command, Bundle extras) { + + long identity = Binder.clearCallingIdentity(); + boolean result = false; + + if ("delete_aiding_data".equals(command)) { + result = deleteAidingData(extras); + } else if ("force_time_injection".equals(command)) { + sendMessage(INJECT_NTP_TIME, 0, null); + result = true; + } else if ("force_xtra_injection".equals(command)) { + if (mSupportsXtra) { + xtraDownloadRequest(); + result = true; + } + } else { + Log.w(TAG, "sendExtraCommand: unknown command " + command); + } + + Binder.restoreCallingIdentity(identity); + return result; + } + + private boolean deleteAidingData(Bundle extras) { + int flags; + + if (extras == null) { + flags = GPS_DELETE_ALL; + } else { + flags = 0; + if (extras.getBoolean("ephemeris")) flags |= GPS_DELETE_EPHEMERIS; + if (extras.getBoolean("almanac")) flags |= GPS_DELETE_ALMANAC; + if (extras.getBoolean("position")) flags |= GPS_DELETE_POSITION; + if (extras.getBoolean("time")) flags |= GPS_DELETE_TIME; + if (extras.getBoolean("iono")) flags |= GPS_DELETE_IONO; + if (extras.getBoolean("utc")) flags |= GPS_DELETE_UTC; + if (extras.getBoolean("health")) flags |= GPS_DELETE_HEALTH; + if (extras.getBoolean("svdir")) flags |= GPS_DELETE_SVDIR; + if (extras.getBoolean("svsteer")) flags |= GPS_DELETE_SVSTEER; + if (extras.getBoolean("sadata")) flags |= GPS_DELETE_SADATA; + if (extras.getBoolean("rti")) flags |= GPS_DELETE_RTI; + if (extras.getBoolean("celldb-info")) flags |= GPS_DELETE_CELLDB_INFO; + if (extras.getBoolean("all")) flags |= GPS_DELETE_ALL; + } + + if (flags != 0) { + native_delete_aiding_data(flags); + return true; + } + + return false; + } + + private void startNavigating(boolean singleShot) { + if (!mStarted) { + if (DEBUG) Log.d(TAG, "startNavigating"); + mStarted = true; + mSingleShot = singleShot; + mPositionMode = GPS_POSITION_MODE_STANDALONE; + + if (Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ASSISTED_GPS_ENABLED, 1) != 0) { + if (singleShot && hasCapability(GPS_CAPABILITY_MSA)) { + mPositionMode = GPS_POSITION_MODE_MS_ASSISTED; + } else if (hasCapability(GPS_CAPABILITY_MSB)) { + mPositionMode = GPS_POSITION_MODE_MS_BASED; + } + } + + int interval = (hasCapability(GPS_CAPABILITY_SCHEDULING) ? mFixInterval : 1000); + if (!native_set_position_mode(mPositionMode, GPS_POSITION_RECURRENCE_PERIODIC, + interval, 0, 0)) { + mStarted = false; + Log.e(TAG, "set_position_mode failed in startNavigating()"); + return; + } + if (!native_start()) { + mStarted = false; + Log.e(TAG, "native_start failed in startNavigating()"); + return; + } + + // reset SV count to zero + updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, 0); + mFixRequestTime = System.currentTimeMillis(); + if (!hasCapability(GPS_CAPABILITY_SCHEDULING)) { + // set timer to give up if we do not receive a fix within NO_FIX_TIMEOUT + // and our fix interval is not short + if (mFixInterval >= NO_FIX_TIMEOUT) { + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT, mTimeoutIntent); + } + } + } + } + + private void stopNavigating() { + if (DEBUG) Log.d(TAG, "stopNavigating"); + if (mStarted) { + mStarted = false; + mSingleShot = false; + native_stop(); + mTTFF = 0; + mLastFixTime = 0; + mLocationFlags = LOCATION_INVALID; + + // reset SV count to zero + updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, 0); + } + } + + private void hibernate() { + // stop GPS until our next fix interval arrives + stopNavigating(); + mAlarmManager.cancel(mTimeoutIntent); + mAlarmManager.cancel(mWakeupIntent); + long now = SystemClock.elapsedRealtime(); + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + mFixInterval, mWakeupIntent); + } + + private boolean hasCapability(int capability) { + return ((mEngineCapabilities & capability) != 0); + } + + /** + * called from native code to update our position. + */ + private void reportLocation(int flags, double latitude, double longitude, double altitude, + float speed, float bearing, float accuracy, long timestamp) { + if (VERBOSE) Log.v(TAG, "reportLocation lat: " + latitude + " long: " + longitude + + " timestamp: " + timestamp); + + synchronized (mLocation) { + mLocationFlags = flags; + if ((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) { + mLocation.setLatitude(latitude); + mLocation.setLongitude(longitude); + mLocation.setTime(timestamp); + } + if ((flags & LOCATION_HAS_ALTITUDE) == LOCATION_HAS_ALTITUDE) { + mLocation.setAltitude(altitude); + } else { + mLocation.removeAltitude(); + } + if ((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) { + mLocation.setSpeed(speed); + } else { + mLocation.removeSpeed(); + } + if ((flags & LOCATION_HAS_BEARING) == LOCATION_HAS_BEARING) { + mLocation.setBearing(bearing); + } else { + mLocation.removeBearing(); + } + if ((flags & LOCATION_HAS_ACCURACY) == LOCATION_HAS_ACCURACY) { + mLocation.setAccuracy(accuracy); + } else { + mLocation.removeAccuracy(); + } + + try { + mLocationManager.reportLocation(mLocation, false); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException calling reportLocation"); + } + } + + mLastFixTime = System.currentTimeMillis(); + // report time to first fix + if (mTTFF == 0 && (flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) { + mTTFF = (int)(mLastFixTime - mFixRequestTime); + if (DEBUG) Log.d(TAG, "TTFF: " + mTTFF); + + // notify status listeners + synchronized(mListeners) { + int size = mListeners.size(); + for (int i = 0; i < size; i++) { + Listener listener = mListeners.get(i); + try { + listener.mListener.onFirstFix(mTTFF); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in stopNavigating"); + mListeners.remove(listener); + // adjust for size of list changing + size--; + } + } + } + } + + if (mSingleShot) { + stopNavigating(); + } + if (mStarted && mStatus != LocationProvider.AVAILABLE) { + // we want to time out if we do not receive a fix + // within the time out and we are requesting infrequent fixes + if (!hasCapability(GPS_CAPABILITY_SCHEDULING) && mFixInterval < NO_FIX_TIMEOUT) { + mAlarmManager.cancel(mTimeoutIntent); + } + + // send an intent to notify that the GPS is receiving fixes. + Intent intent = new Intent(LocationManager.GPS_FIX_CHANGE_ACTION); + intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, true); + mContext.sendBroadcast(intent); + updateStatus(LocationProvider.AVAILABLE, mSvCount); + } + + if (!hasCapability(GPS_CAPABILITY_SCHEDULING) && mStarted && mFixInterval > 1000) { + if (DEBUG) Log.d(TAG, "got fix, hibernating"); + hibernate(); + } + } + + /** + * called from native code to update our status + */ + private void reportStatus(int status) { + if (DEBUG) Log.v(TAG, "reportStatus status: " + status); + + synchronized(mListeners) { + boolean wasNavigating = mNavigating; + + switch (status) { + case GPS_STATUS_SESSION_BEGIN: + mNavigating = true; + mEngineOn = true; + break; + case GPS_STATUS_SESSION_END: + mNavigating = false; + break; + case GPS_STATUS_ENGINE_ON: + mEngineOn = true; + break; + case GPS_STATUS_ENGINE_OFF: + mEngineOn = false; + mNavigating = false; + break; + } + + if (wasNavigating != mNavigating) { + int size = mListeners.size(); + for (int i = 0; i < size; i++) { + Listener listener = mListeners.get(i); + try { + if (mNavigating) { + listener.mListener.onGpsStarted(); + } else { + listener.mListener.onGpsStopped(); + } + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in reportStatus"); + mListeners.remove(listener); + // adjust for size of list changing + size--; + } + } + + try { + // update battery stats + for (int i=mClientUids.size() - 1; i >= 0; i--) { + int uid = mClientUids.keyAt(i); + if (mNavigating) { + mBatteryStats.noteStartGps(uid); + } else { + mBatteryStats.noteStopGps(uid); + } + } + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in reportStatus"); + } + + // send an intent to notify that the GPS has been enabled or disabled. + Intent intent = new Intent(LocationManager.GPS_ENABLED_CHANGE_ACTION); + intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, mNavigating); + mContext.sendBroadcast(intent); + } + } + } + + /** + * called from native code to update SV info + */ + private void reportSvStatus() { + + int svCount = native_read_sv_status(mSvs, mSnrs, mSvElevations, mSvAzimuths, mSvMasks); + + synchronized(mListeners) { + int size = mListeners.size(); + for (int i = 0; i < size; i++) { + Listener listener = mListeners.get(i); + try { + listener.mListener.onSvStatusChanged(svCount, mSvs, mSnrs, + mSvElevations, mSvAzimuths, mSvMasks[EPHEMERIS_MASK], + mSvMasks[ALMANAC_MASK], mSvMasks[USED_FOR_FIX_MASK]); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in reportSvInfo"); + mListeners.remove(listener); + // adjust for size of list changing + size--; + } + } + } + + if (VERBOSE) { + Log.v(TAG, "SV count: " + svCount + + " ephemerisMask: " + Integer.toHexString(mSvMasks[EPHEMERIS_MASK]) + + " almanacMask: " + Integer.toHexString(mSvMasks[ALMANAC_MASK])); + for (int i = 0; i < svCount; i++) { + Log.v(TAG, "sv: " + mSvs[i] + + " snr: " + (float)mSnrs[i]/10 + + " elev: " + mSvElevations[i] + + " azimuth: " + mSvAzimuths[i] + + ((mSvMasks[EPHEMERIS_MASK] & (1 << (mSvs[i] - 1))) == 0 ? " " : " E") + + ((mSvMasks[ALMANAC_MASK] & (1 << (mSvs[i] - 1))) == 0 ? " " : " A") + + ((mSvMasks[USED_FOR_FIX_MASK] & (1 << (mSvs[i] - 1))) == 0 ? "" : "U")); + } + } + + // return number of sets used in fix instead of total + updateStatus(mStatus, Integer.bitCount(mSvMasks[USED_FOR_FIX_MASK])); + + if (mNavigating && mStatus == LocationProvider.AVAILABLE && mLastFixTime > 0 && + System.currentTimeMillis() - mLastFixTime > RECENT_FIX_TIMEOUT) { + // send an intent to notify that the GPS is no longer receiving fixes. + Intent intent = new Intent(LocationManager.GPS_FIX_CHANGE_ACTION); + intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, false); + mContext.sendBroadcast(intent); + updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, mSvCount); + } + } + + /** + * called from native code to update AGPS status + */ + private void reportAGpsStatus(int type, int status) { + switch (status) { + case GPS_REQUEST_AGPS_DATA_CONN: + if (DEBUG) Log.d(TAG, "GPS_REQUEST_AGPS_DATA_CONN"); + // Set mAGpsDataConnectionState before calling startUsingNetworkFeature + // to avoid a race condition with handleUpdateNetworkState() + mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPENING; + int result = mConnMgr.startUsingNetworkFeature( + ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_SUPL); + if (result == Phone.APN_ALREADY_ACTIVE) { + if (DEBUG) Log.d(TAG, "Phone.APN_ALREADY_ACTIVE"); + if (mAGpsApn != null) { + native_agps_data_conn_open(mAGpsApn); + mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN; + } else { + Log.e(TAG, "mAGpsApn not set when receiving Phone.APN_ALREADY_ACTIVE"); + mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED; + native_agps_data_conn_failed(); + } + } else if (result == Phone.APN_REQUEST_STARTED) { + if (DEBUG) Log.d(TAG, "Phone.APN_REQUEST_STARTED"); + // Nothing to do here + } else { + if (DEBUG) Log.d(TAG, "startUsingNetworkFeature failed"); + mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED; + native_agps_data_conn_failed(); + } + break; + case GPS_RELEASE_AGPS_DATA_CONN: + if (DEBUG) Log.d(TAG, "GPS_RELEASE_AGPS_DATA_CONN"); + if (mAGpsDataConnectionState != AGPS_DATA_CONNECTION_CLOSED) { + mConnMgr.stopUsingNetworkFeature( + ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_SUPL); + native_agps_data_conn_closed(); + mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED; + } + break; + case GPS_AGPS_DATA_CONNECTED: + if (DEBUG) Log.d(TAG, "GPS_AGPS_DATA_CONNECTED"); + break; + case GPS_AGPS_DATA_CONN_DONE: + if (DEBUG) Log.d(TAG, "GPS_AGPS_DATA_CONN_DONE"); + break; + case GPS_AGPS_DATA_CONN_FAILED: + if (DEBUG) Log.d(TAG, "GPS_AGPS_DATA_CONN_FAILED"); + break; + } + } + + /** + * called from native code to report NMEA data received + */ + private void reportNmea(long timestamp) { + synchronized(mListeners) { + int size = mListeners.size(); + if (size > 0) { + // don't bother creating the String if we have no listeners + int length = native_read_nmea(mNmeaBuffer, mNmeaBuffer.length); + String nmea = new String(mNmeaBuffer, 0, length); + + for (int i = 0; i < size; i++) { + Listener listener = mListeners.get(i); + try { + listener.mListener.onNmeaReceived(timestamp, nmea); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in reportNmea"); + mListeners.remove(listener); + // adjust for size of list changing + size--; + } + } + } + } + } + + /** + * called from native code to inform us what the GPS engine capabilities are + */ + private void setEngineCapabilities(int capabilities) { + mEngineCapabilities = capabilities; + } + + /** + * called from native code to request XTRA data + */ + private void xtraDownloadRequest() { + if (DEBUG) Log.d(TAG, "xtraDownloadRequest"); + sendMessage(DOWNLOAD_XTRA_DATA, 0, null); + } + + //============================================================= + // NI Client support + //============================================================= + private final INetInitiatedListener mNetInitiatedListener = new INetInitiatedListener.Stub() { + // Sends a response for an NI reqeust to HAL. + public boolean sendNiResponse(int notificationId, int userResponse) + { + // TODO Add Permission check + + StringBuilder extrasBuf = new StringBuilder(); + + if (DEBUG) Log.d(TAG, "sendNiResponse, notifId: " + notificationId + + ", response: " + userResponse); + native_send_ni_response(notificationId, userResponse); + return true; + } + }; + + public INetInitiatedListener getNetInitiatedListener() { + return mNetInitiatedListener; + } + + // Called by JNI function to report an NI request. + public void reportNiNotification( + int notificationId, + int niType, + int notifyFlags, + int timeout, + int defaultResponse, + String requestorId, + String text, + int requestorIdEncoding, + int textEncoding, + String extras // Encoded extra data + ) + { + Log.i(TAG, "reportNiNotification: entered"); + Log.i(TAG, "notificationId: " + notificationId + + ", niType: " + niType + + ", notifyFlags: " + notifyFlags + + ", timeout: " + timeout + + ", defaultResponse: " + defaultResponse); + + Log.i(TAG, "requestorId: " + requestorId + + ", text: " + text + + ", requestorIdEncoding: " + requestorIdEncoding + + ", textEncoding: " + textEncoding); + + GpsNiNotification notification = new GpsNiNotification(); + + notification.notificationId = notificationId; + notification.niType = niType; + notification.needNotify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_NOTIFY) != 0; + notification.needVerify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_VERIFY) != 0; + notification.privacyOverride = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_PRIVACY_OVERRIDE) != 0; + notification.timeout = timeout; + notification.defaultResponse = defaultResponse; + notification.requestorId = requestorId; + notification.text = text; + notification.requestorIdEncoding = requestorIdEncoding; + notification.textEncoding = textEncoding; + + // Process extras, assuming the format is + // one of more lines of "key = value" + Bundle bundle = new Bundle(); + + if (extras == null) extras = ""; + Properties extraProp = new Properties(); + + try { + extraProp.load(new StringBufferInputStream(extras)); + } + catch (IOException e) + { + Log.e(TAG, "reportNiNotification cannot parse extras data: " + extras); + } + + for (Entry<Object, Object> ent : extraProp.entrySet()) + { + bundle.putString((String) ent.getKey(), (String) ent.getValue()); + } + + notification.extras = bundle; + + mNIHandler.handleNiNotification(notification); + } + + /** + * Called from native code to request set id info. + * We should be careful about receiving null string from the TelephonyManager, + * because sending null String to JNI function would cause a crash. + */ + + private void requestSetID(int flags) { + TelephonyManager phone = (TelephonyManager) + mContext.getSystemService(Context.TELEPHONY_SERVICE); + int type = AGPS_SETID_TYPE_NONE; + String data = ""; + + if ((flags & AGPS_RIL_REQUEST_SETID_IMSI) == AGPS_RIL_REQUEST_SETID_IMSI) { + String data_temp = phone.getSubscriberId(); + if (data_temp == null) { + // This means the framework does not have the SIM card ready. + } else { + // This means the framework has the SIM card. + data = data_temp; + type = AGPS_SETID_TYPE_IMSI; + } + } + else if ((flags & AGPS_RIL_REQUEST_SETID_MSISDN) == AGPS_RIL_REQUEST_SETID_MSISDN) { + String data_temp = phone.getLine1Number(); + if (data_temp == null) { + // This means the framework does not have the SIM card ready. + } else { + // This means the framework has the SIM card. + data = data_temp; + type = AGPS_SETID_TYPE_MSISDN; + } + } + native_agps_set_id(type, data); + } + + /** + * Called from native code to request reference location info + */ + + private void requestRefLocation(int flags) { + TelephonyManager phone = (TelephonyManager) + mContext.getSystemService(Context.TELEPHONY_SERVICE); + if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) { + GsmCellLocation gsm_cell = (GsmCellLocation) phone.getCellLocation(); + if ((gsm_cell != null) && (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) && + (phone.getNetworkOperator() != null) && + (phone.getNetworkOperator().length() > 3)) { + int type; + int mcc = Integer.parseInt(phone.getNetworkOperator().substring(0,3)); + int mnc = Integer.parseInt(phone.getNetworkOperator().substring(3)); + int networkType = phone.getNetworkType(); + if (networkType == TelephonyManager.NETWORK_TYPE_UMTS + || networkType == TelephonyManager.NETWORK_TYPE_HSDPA + || networkType == TelephonyManager.NETWORK_TYPE_HSUPA + || networkType == TelephonyManager.NETWORK_TYPE_HSPA) { + type = AGPS_REF_LOCATION_TYPE_UMTS_CELLID; + } else { + type = AGPS_REF_LOCATION_TYPE_GSM_CELLID; + } + native_agps_set_ref_location_cellid(type, mcc, mnc, + gsm_cell.getLac(), gsm_cell.getCid()); + } else { + Log.e(TAG,"Error getting cell location info."); + } + } + else { + Log.e(TAG,"CDMA not supported."); + } + } + + private void sendMessage(int message, int arg, Object obj) { + // hold a wake lock while messages are pending + synchronized (mWakeLock) { + mPendingMessageBits |= (1 << message); + mWakeLock.acquire(); + mHandler.removeMessages(message); + Message m = Message.obtain(mHandler, message); + m.arg1 = arg; + m.obj = obj; + mHandler.sendMessage(m); + } + } + + private final class ProviderHandler extends Handler { + @Override + public void handleMessage(Message msg) { + int message = msg.what; + switch (message) { + case ENABLE: + if (msg.arg1 == 1) { + handleEnable(); + } else { + handleDisable(); + } + break; + case ENABLE_TRACKING: + handleEnableLocationTracking(msg.arg1 == 1); + break; + case REQUEST_SINGLE_SHOT: + handleRequestSingleShot(); + break; + case UPDATE_NETWORK_STATE: + handleUpdateNetworkState(msg.arg1, (NetworkInfo)msg.obj); + break; + case INJECT_NTP_TIME: + handleInjectNtpTime(); + break; + case DOWNLOAD_XTRA_DATA: + if (mSupportsXtra) { + handleDownloadXtraData(); + } + break; + case UPDATE_LOCATION: + handleUpdateLocation((Location)msg.obj); + break; + case ADD_LISTENER: + handleAddListener(msg.arg1); + break; + case REMOVE_LISTENER: + handleRemoveListener(msg.arg1); + break; + } + // release wake lock if no messages are pending + synchronized (mWakeLock) { + mPendingMessageBits &= ~(1 << message); + if (message == ADD_LISTENER || message == REMOVE_LISTENER) { + mPendingListenerMessages--; + } + if (mPendingMessageBits == 0 && mPendingListenerMessages == 0) { + mWakeLock.release(); + } + } + } + }; + + private final class GpsLocationProviderThread extends Thread { + + public GpsLocationProviderThread() { + super("GpsLocationProvider"); + } + + public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + initialize(); + Looper.prepare(); + mHandler = new ProviderHandler(); + // signal when we are initialized and ready to go + mInitializedLatch.countDown(); + Looper.loop(); + } + } + + // for GPS SV statistics + private static final int MAX_SVS = 32; + private static final int EPHEMERIS_MASK = 0; + private static final int ALMANAC_MASK = 1; + private static final int USED_FOR_FIX_MASK = 2; + + // preallocated arrays, to avoid memory allocation in reportStatus() + private int mSvs[] = new int[MAX_SVS]; + private float mSnrs[] = new float[MAX_SVS]; + private float mSvElevations[] = new float[MAX_SVS]; + private float mSvAzimuths[] = new float[MAX_SVS]; + private int mSvMasks[] = new int[3]; + private int mSvCount; + // preallocated to avoid memory allocation in reportNmea() + private byte[] mNmeaBuffer = new byte[120]; + + static { class_init_native(); } + private static native void class_init_native(); + private static native boolean native_is_supported(); + + private native boolean native_init(); + private native void native_cleanup(); + private native boolean native_set_position_mode(int mode, int recurrence, int min_interval, + int preferred_accuracy, int preferred_time); + private native boolean native_start(); + private native boolean native_stop(); + private native void native_delete_aiding_data(int flags); + // returns number of SVs + // mask[0] is ephemeris mask and mask[1] is almanac mask + private native int native_read_sv_status(int[] svs, float[] snrs, + float[] elevations, float[] azimuths, int[] masks); + private native int native_read_nmea(byte[] buffer, int bufferSize); + private native void native_inject_location(double latitude, double longitude, float accuracy); + + // XTRA Support + private native void native_inject_time(long time, long timeReference, int uncertainty); + private native boolean native_supports_xtra(); + private native void native_inject_xtra_data(byte[] data, int length); + + // DEBUG Support + private native String native_get_internal_state(); + + // AGPS Support + private native void native_agps_data_conn_open(String apn); + private native void native_agps_data_conn_closed(); + private native void native_agps_data_conn_failed(); + private native void native_agps_ni_message(byte [] msg, int length); + private native void native_set_agps_server(int type, String hostname, int port); + + // Network-initiated (NI) Support + private native void native_send_ni_response(int notificationId, int userResponse); + + // AGPS ril suport + private native void native_agps_set_ref_location_cellid(int type, int mcc, int mnc, + int lac, int cid); + private native void native_agps_set_id(int type, String setid); + + private native void native_update_network_state(boolean connected, int type, + boolean roaming, String extraInfo); +} diff --git a/services/java/com/android/server/location/GpsXtraDownloader.java b/services/java/com/android/server/location/GpsXtraDownloader.java new file mode 100644 index 0000000..813d255 --- /dev/null +++ b/services/java/com/android/server/location/GpsXtraDownloader.java @@ -0,0 +1,172 @@ +/* + * 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.location; + +import android.content.Context; +import android.net.Proxy; +import android.net.http.AndroidHttpClient; +import android.util.Config; +import android.util.Log; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.StatusLine; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.conn.params.ConnRouteParams; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Properties; +import java.util.Random; + +/** + * A class for downloading GPS XTRA data. + * + * {@hide} + */ +public class GpsXtraDownloader { + + private static final String TAG = "GpsXtraDownloader"; + static final boolean DEBUG = false; + + private Context mContext; + private String[] mXtraServers; + // to load balance our server requests + private int mNextServerIndex; + + GpsXtraDownloader(Context context, Properties properties) { + mContext = context; + + // read XTRA servers from the Properties object + int count = 0; + String server1 = properties.getProperty("XTRA_SERVER_1"); + String server2 = properties.getProperty("XTRA_SERVER_2"); + String server3 = properties.getProperty("XTRA_SERVER_3"); + if (server1 != null) count++; + if (server2 != null) count++; + if (server3 != null) count++; + + if (count == 0) { + Log.e(TAG, "No XTRA servers were specified in the GPS configuration"); + return; + } else { + mXtraServers = new String[count]; + count = 0; + if (server1 != null) mXtraServers[count++] = server1; + if (server2 != null) mXtraServers[count++] = server2; + if (server3 != null) mXtraServers[count++] = server3; + + // randomize first server + Random random = new Random(); + mNextServerIndex = random.nextInt(count); + } + } + + byte[] downloadXtraData() { + String proxyHost = Proxy.getHost(mContext); + int proxyPort = Proxy.getPort(mContext); + boolean useProxy = (proxyHost != null && proxyPort != -1); + byte[] result = null; + int startIndex = mNextServerIndex; + + if (mXtraServers == null) { + return null; + } + + // load balance our requests among the available servers + while (result == null) { + result = doDownload(mXtraServers[mNextServerIndex], useProxy, proxyHost, proxyPort); + + // increment mNextServerIndex and wrap around if necessary + mNextServerIndex++; + if (mNextServerIndex == mXtraServers.length) { + mNextServerIndex = 0; + } + // break if we have tried all the servers + if (mNextServerIndex == startIndex) break; + } + + return result; + } + + protected static byte[] doDownload(String url, boolean isProxySet, + String proxyHost, int proxyPort) { + if (DEBUG) Log.d(TAG, "Downloading XTRA data from " + url); + + AndroidHttpClient client = null; + try { + client = AndroidHttpClient.newInstance("Android"); + HttpUriRequest req = new HttpGet(url); + + if (isProxySet) { + HttpHost proxy = new HttpHost(proxyHost, proxyPort); + ConnRouteParams.setDefaultProxy(req.getParams(), proxy); + } + + req.addHeader( + "Accept", + "*/*, application/vnd.wap.mms-message, application/vnd.wap.sic"); + + req.addHeader( + "x-wap-profile", + "http://www.openmobilealliance.org/tech/profiles/UAPROF/ccppschema-20021212#"); + + HttpResponse response = client.execute(req); + StatusLine status = response.getStatusLine(); + if (status.getStatusCode() != 200) { // HTTP 200 is success. + if (DEBUG) Log.d(TAG, "HTTP error: " + status.getReasonPhrase()); + return null; + } + + HttpEntity entity = response.getEntity(); + byte[] body = null; + if (entity != null) { + try { + if (entity.getContentLength() > 0) { + body = new byte[(int) entity.getContentLength()]; + DataInputStream dis = new DataInputStream(entity.getContent()); + try { + dis.readFully(body); + } finally { + try { + dis.close(); + } catch (IOException e) { + Log.e(TAG, "Unexpected IOException.", e); + } + } + } + } finally { + if (entity != null) { + entity.consumeContent(); + } + } + } + return body; + } catch (Exception e) { + if (DEBUG) Log.d(TAG, "error " + e); + } finally { + if (client != null) { + client.close(); + } + } + return null; + } + +} diff --git a/services/java/com/android/server/location/LocationProviderInterface.java b/services/java/com/android/server/location/LocationProviderInterface.java new file mode 100644 index 0000000..858a582 --- /dev/null +++ b/services/java/com/android/server/location/LocationProviderInterface.java @@ -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. + */ + +package com.android.server.location; + +import android.location.Criteria; +import android.location.Location; +import android.net.NetworkInfo; +import android.os.Bundle; +import android.os.WorkSource; + +/** + * Location Manager's interface for location providers. + * + * {@hide} + */ +public interface LocationProviderInterface { + String getName(); + boolean requiresNetwork(); + boolean requiresSatellite(); + boolean requiresCell(); + boolean hasMonetaryCost(); + boolean supportsAltitude(); + boolean supportsSpeed(); + boolean supportsBearing(); + int getPowerRequirement(); + boolean meetsCriteria(Criteria criteria); + int getAccuracy(); + boolean isEnabled(); + void enable(); + void disable(); + int getStatus(Bundle extras); + long getStatusUpdateTime(); + void enableLocationTracking(boolean enable); + /* returns false if single shot is not supported */ + boolean requestSingleShotFix(); + String getInternalState(); + void setMinTime(long minTime, WorkSource ws); + void updateNetworkState(int state, NetworkInfo info); + void updateLocation(Location location); + boolean sendExtraCommand(String command, Bundle extras); + void addListener(int uid); + void removeListener(int uid); +} diff --git a/services/java/com/android/server/location/LocationProviderProxy.java b/services/java/com/android/server/location/LocationProviderProxy.java new file mode 100644 index 0000000..1a1a170 --- /dev/null +++ b/services/java/com/android/server/location/LocationProviderProxy.java @@ -0,0 +1,471 @@ +/* + * 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. + */ + +package com.android.server.location; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.location.Criteria; +import android.location.ILocationProvider; +import android.location.Location; +import android.net.NetworkInfo; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.WorkSource; +import android.util.Log; + +import com.android.internal.location.DummyLocationProvider; + +/** + * A class for proxying location providers implemented as services. + * + * {@hide} + */ +public class LocationProviderProxy implements LocationProviderInterface { + + private static final String TAG = "LocationProviderProxy"; + + private final Context mContext; + private final String mName; + private final Intent mIntent; + private final Handler mHandler; + private final Object mMutex = new Object(); // synchronizes access to non-final members + private Connection mServiceConnection = new Connection(); // never null + + // cached values set by the location manager + private boolean mLocationTracking = false; + private boolean mEnabled = false; + private long mMinTime = -1; + private WorkSource mMinTimeSource = new WorkSource(); + private int mNetworkState; + private NetworkInfo mNetworkInfo; + + // constructor for proxying location providers implemented in a separate service + public LocationProviderProxy(Context context, String name, String serviceName, + Handler handler) { + mContext = context; + mName = name; + mIntent = new Intent(serviceName); + mHandler = handler; + mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE); + } + + /** + * When unbundled NetworkLocationService package is updated, we + * need to unbind from the old version and re-bind to the new one. + */ + public void reconnect() { + synchronized (mMutex) { + mContext.unbindService(mServiceConnection); + mServiceConnection = new Connection(); + mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE); + } + } + + private class Connection implements ServiceConnection, Runnable { + + private ILocationProvider mProvider; + + // for caching requiresNetwork, requiresSatellite, etc. + private DummyLocationProvider mCachedAttributes; // synchronized by mMutex + + public void onServiceConnected(ComponentName className, IBinder service) { + Log.d(TAG, "LocationProviderProxy.onServiceConnected " + className); + synchronized (this) { + mProvider = ILocationProvider.Stub.asInterface(service); + if (mProvider != null) { + mHandler.post(this); + } + } + } + + public void onServiceDisconnected(ComponentName className) { + Log.d(TAG, "LocationProviderProxy.onServiceDisconnected " + className); + synchronized (this) { + mProvider = null; + } + } + + public synchronized ILocationProvider getProvider() { + return mProvider; + } + + public synchronized DummyLocationProvider getCachedAttributes() { + return mCachedAttributes; + } + + public void run() { + synchronized (mMutex) { + if (mServiceConnection != this) { + // This ServiceConnection no longer the one we want to bind to. + return; + } + ILocationProvider provider = getProvider(); + if (provider == null) { + return; + } + + // resend previous values from the location manager if the service has restarted + try { + if (mEnabled) { + provider.enable(); + } + if (mLocationTracking) { + provider.enableLocationTracking(true); + } + if (mMinTime >= 0) { + provider.setMinTime(mMinTime, mMinTimeSource); + } + if (mNetworkInfo != null) { + provider.updateNetworkState(mNetworkState, mNetworkInfo); + } + } catch (RemoteException e) { + } + + // init cache of parameters + if (mCachedAttributes == null) { + try { + mCachedAttributes = new DummyLocationProvider(mName, null); + mCachedAttributes.setRequiresNetwork(provider.requiresNetwork()); + mCachedAttributes.setRequiresSatellite(provider.requiresSatellite()); + mCachedAttributes.setRequiresCell(provider.requiresCell()); + mCachedAttributes.setHasMonetaryCost(provider.hasMonetaryCost()); + mCachedAttributes.setSupportsAltitude(provider.supportsAltitude()); + mCachedAttributes.setSupportsSpeed(provider.supportsSpeed()); + mCachedAttributes.setSupportsBearing(provider.supportsBearing()); + mCachedAttributes.setPowerRequirement(provider.getPowerRequirement()); + mCachedAttributes.setAccuracy(provider.getAccuracy()); + } catch (RemoteException e) { + mCachedAttributes = null; + } + } + } + } + }; + + public String getName() { + return mName; + } + + private DummyLocationProvider getCachedAttributes() { + synchronized (mMutex) { + return mServiceConnection.getCachedAttributes(); + } + } + + public boolean requiresNetwork() { + DummyLocationProvider cachedAttributes = getCachedAttributes(); + if (cachedAttributes != null) { + return cachedAttributes.requiresNetwork(); + } else { + return false; + } + } + + public boolean requiresSatellite() { + DummyLocationProvider cachedAttributes = getCachedAttributes(); + if (cachedAttributes != null) { + return cachedAttributes.requiresSatellite(); + } else { + return false; + } + } + + public boolean requiresCell() { + DummyLocationProvider cachedAttributes = getCachedAttributes(); + if (cachedAttributes != null) { + return cachedAttributes.requiresCell(); + } else { + return false; + } + } + + public boolean hasMonetaryCost() { + DummyLocationProvider cachedAttributes = getCachedAttributes(); + if (cachedAttributes != null) { + return cachedAttributes.hasMonetaryCost(); + } else { + return false; + } + } + + public boolean supportsAltitude() { + DummyLocationProvider cachedAttributes = getCachedAttributes(); + if (cachedAttributes != null) { + return cachedAttributes.supportsAltitude(); + } else { + return false; + } + } + + public boolean supportsSpeed() { + DummyLocationProvider cachedAttributes = getCachedAttributes(); + if (cachedAttributes != null) { + return cachedAttributes.supportsSpeed(); + } else { + return false; + } + } + + public boolean supportsBearing() { + DummyLocationProvider cachedAttributes = getCachedAttributes(); + if (cachedAttributes != null) { + return cachedAttributes.supportsBearing(); + } else { + return false; + } + } + + public int getPowerRequirement() { + DummyLocationProvider cachedAttributes = getCachedAttributes(); + if (cachedAttributes != null) { + return cachedAttributes.getPowerRequirement(); + } else { + return -1; + } + } + + public int getAccuracy() { + DummyLocationProvider cachedAttributes = getCachedAttributes(); + if (cachedAttributes != null) { + return cachedAttributes.getAccuracy(); + } else { + return -1; + } + } + + public boolean meetsCriteria(Criteria criteria) { + synchronized (mMutex) { + ILocationProvider provider = mServiceConnection.getProvider(); + if (provider != null) { + try { + return provider.meetsCriteria(criteria); + } catch (RemoteException e) { + } + } + } + // default implementation if we lost connection to the provider + if ((criteria.getAccuracy() != Criteria.NO_REQUIREMENT) && + (criteria.getAccuracy() < getAccuracy())) { + return false; + } + int criteriaPower = criteria.getPowerRequirement(); + if ((criteriaPower != Criteria.NO_REQUIREMENT) && + (criteriaPower < getPowerRequirement())) { + return false; + } + if (criteria.isAltitudeRequired() && !supportsAltitude()) { + return false; + } + if (criteria.isSpeedRequired() && !supportsSpeed()) { + return false; + } + if (criteria.isBearingRequired() && !supportsBearing()) { + return false; + } + return true; + } + + public void enable() { + synchronized (mMutex) { + mEnabled = true; + ILocationProvider provider = mServiceConnection.getProvider(); + if (provider != null) { + try { + provider.enable(); + } catch (RemoteException e) { + } + } + } + } + + public void disable() { + synchronized (mMutex) { + mEnabled = false; + ILocationProvider provider = mServiceConnection.getProvider(); + if (provider != null) { + try { + provider.disable(); + } catch (RemoteException e) { + } + } + } + } + + public boolean isEnabled() { + synchronized (mMutex) { + return mEnabled; + } + } + + public int getStatus(Bundle extras) { + ILocationProvider provider; + synchronized (mMutex) { + provider = mServiceConnection.getProvider(); + } + if (provider != null) { + try { + return provider.getStatus(extras); + } catch (RemoteException e) { + } + } + return 0; + } + + public long getStatusUpdateTime() { + ILocationProvider provider; + synchronized (mMutex) { + provider = mServiceConnection.getProvider(); + } + if (provider != null) { + try { + return provider.getStatusUpdateTime(); + } catch (RemoteException e) { + } + } + return 0; + } + + public String getInternalState() { + ILocationProvider provider; + synchronized (mMutex) { + provider = mServiceConnection.getProvider(); + } + if (provider != null) { + try { + return provider.getInternalState(); + } catch (RemoteException e) { + Log.e(TAG, "getInternalState failed", e); + } + } + return null; + } + + public boolean isLocationTracking() { + synchronized (mMutex) { + return mLocationTracking; + } + } + + public void enableLocationTracking(boolean enable) { + synchronized (mMutex) { + mLocationTracking = enable; + if (!enable) { + mMinTime = -1; + mMinTimeSource.clear(); + } + ILocationProvider provider = mServiceConnection.getProvider(); + if (provider != null) { + try { + provider.enableLocationTracking(enable); + } catch (RemoteException e) { + } + } + } + } + + public boolean requestSingleShotFix() { + return false; + } + + public long getMinTime() { + synchronized (mMutex) { + return mMinTime; + } + } + + public void setMinTime(long minTime, WorkSource ws) { + synchronized (mMutex) { + mMinTime = minTime; + mMinTimeSource.set(ws); + ILocationProvider provider = mServiceConnection.getProvider(); + if (provider != null) { + try { + provider.setMinTime(minTime, ws); + } catch (RemoteException e) { + } + } + } + } + + public void updateNetworkState(int state, NetworkInfo info) { + synchronized (mMutex) { + mNetworkState = state; + mNetworkInfo = info; + ILocationProvider provider = mServiceConnection.getProvider(); + if (provider != null) { + try { + provider.updateNetworkState(state, info); + } catch (RemoteException e) { + } + } + } + } + + public void updateLocation(Location location) { + synchronized (mMutex) { + ILocationProvider provider = mServiceConnection.getProvider(); + if (provider != null) { + try { + provider.updateLocation(location); + } catch (RemoteException e) { + } + } + } + } + + public boolean sendExtraCommand(String command, Bundle extras) { + synchronized (mMutex) { + ILocationProvider provider = mServiceConnection.getProvider(); + if (provider != null) { + try { + return provider.sendExtraCommand(command, extras); + } catch (RemoteException e) { + } + } + } + return false; + } + + public void addListener(int uid) { + synchronized (mMutex) { + ILocationProvider provider = mServiceConnection.getProvider(); + if (provider != null) { + try { + provider.addListener(uid); + } catch (RemoteException e) { + } + } + } + } + + public void removeListener(int uid) { + synchronized (mMutex) { + ILocationProvider provider = mServiceConnection.getProvider(); + if (provider != null) { + try { + provider.removeListener(uid); + } catch (RemoteException e) { + } + } + } + } +} diff --git a/services/java/com/android/server/location/MockProvider.java b/services/java/com/android/server/location/MockProvider.java new file mode 100644 index 0000000..09d799f --- /dev/null +++ b/services/java/com/android/server/location/MockProvider.java @@ -0,0 +1,234 @@ +/* + * 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. + */ + +package com.android.server.location; + +import android.location.Criteria; +import android.location.ILocationManager; +import android.location.Location; +import android.location.LocationProvider; +import android.net.NetworkInfo; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.WorkSource; +import android.util.Log; +import android.util.PrintWriterPrinter; + +import java.io.PrintWriter; + +/** + * A mock location provider used by LocationManagerService to implement test providers. + * + * {@hide} + */ +public class MockProvider implements LocationProviderInterface { + private final String mName; + private final ILocationManager mLocationManager; + private final boolean mRequiresNetwork; + private final boolean mRequiresSatellite; + private final boolean mRequiresCell; + private final boolean mHasMonetaryCost; + private final boolean mSupportsAltitude; + private final boolean mSupportsSpeed; + private final boolean mSupportsBearing; + private final int mPowerRequirement; + private final int mAccuracy; + private final Location mLocation; + private int mStatus; + private long mStatusUpdateTime; + private final Bundle mExtras = new Bundle(); + private boolean mHasLocation; + private boolean mHasStatus; + private boolean mEnabled; + + private static final String TAG = "MockProvider"; + + public MockProvider(String name, ILocationManager locationManager, + boolean requiresNetwork, boolean requiresSatellite, + boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude, + boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy) { + mName = name; + mLocationManager = locationManager; + mRequiresNetwork = requiresNetwork; + mRequiresSatellite = requiresSatellite; + mRequiresCell = requiresCell; + mHasMonetaryCost = hasMonetaryCost; + mSupportsAltitude = supportsAltitude; + mSupportsBearing = supportsBearing; + mSupportsSpeed = supportsSpeed; + mPowerRequirement = powerRequirement; + mAccuracy = accuracy; + mLocation = new Location(name); + } + + public String getName() { + return mName; + } + + public void disable() { + mEnabled = false; + } + + public void enable() { + mEnabled = true; + } + + public boolean isEnabled() { + return mEnabled; + } + + public int getStatus(Bundle extras) { + if (mHasStatus) { + extras.clear(); + extras.putAll(mExtras); + return mStatus; + } else { + return LocationProvider.AVAILABLE; + } + } + + public long getStatusUpdateTime() { + return mStatusUpdateTime; + } + + public int getAccuracy() { + return mAccuracy; + } + + public int getPowerRequirement() { + return mPowerRequirement; + } + + public boolean hasMonetaryCost() { + return mHasMonetaryCost; + } + + public boolean requiresCell() { + return mRequiresCell; + } + + public boolean requiresNetwork() { + return mRequiresNetwork; + } + + public boolean requiresSatellite() { + return mRequiresSatellite; + } + + public boolean supportsAltitude() { + return mSupportsAltitude; + } + + public boolean supportsBearing() { + return mSupportsBearing; + } + + public boolean supportsSpeed() { + return mSupportsSpeed; + } + + public boolean meetsCriteria(Criteria criteria) { + if ((criteria.getAccuracy() != Criteria.NO_REQUIREMENT) && + (criteria.getAccuracy() < mAccuracy)) { + return false; + } + int criteriaPower = criteria.getPowerRequirement(); + if ((criteriaPower != Criteria.NO_REQUIREMENT) && + (criteriaPower < mPowerRequirement)) { + return false; + } + if (criteria.isAltitudeRequired() && !mSupportsAltitude) { + return false; + } + if (criteria.isSpeedRequired() && !mSupportsSpeed) { + return false; + } + if (criteria.isBearingRequired() && !mSupportsBearing) { + return false; + } + return true; + } + + public void setLocation(Location l) { + mLocation.set(l); + mHasLocation = true; + try { + mLocationManager.reportLocation(mLocation, false); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException calling reportLocation"); + } + } + + public void clearLocation() { + mHasLocation = false; + } + + public void setStatus(int status, Bundle extras, long updateTime) { + mStatus = status; + mStatusUpdateTime = updateTime; + mExtras.clear(); + if (extras != null) { + mExtras.putAll(extras); + } + mHasStatus = true; + } + + public void clearStatus() { + mHasStatus = false; + mStatusUpdateTime = 0; + } + + public String getInternalState() { + return null; + } + + public void enableLocationTracking(boolean enable) { + } + + public boolean requestSingleShotFix() { + return false; + } + + public void setMinTime(long minTime, WorkSource ws) { + } + + public void updateNetworkState(int state, NetworkInfo info) { + } + + public void updateLocation(Location location) { + } + + public boolean sendExtraCommand(String command, Bundle extras) { + return false; + } + + public void addListener(int uid) { + } + + public void removeListener(int uid) { + } + + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + mName); + pw.println(prefix + "mHasLocation=" + mHasLocation); + pw.println(prefix + "mLocation:"); + mLocation.dump(new PrintWriterPrinter(pw), prefix + " "); + pw.println(prefix + "mHasStatus=" + mHasStatus); + pw.println(prefix + "mStatus=" + mStatus); + pw.println(prefix + "mStatusUpdateTime=" + mStatusUpdateTime); + pw.println(prefix + "mExtras=" + mExtras); + } +} diff --git a/services/java/com/android/server/location/PassiveProvider.java b/services/java/com/android/server/location/PassiveProvider.java new file mode 100644 index 0000000..ea0d1b0 --- /dev/null +++ b/services/java/com/android/server/location/PassiveProvider.java @@ -0,0 +1,153 @@ +/* + * 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.Criteria; +import android.location.ILocationManager; +import android.location.Location; +import android.location.LocationManager; +import android.location.LocationProvider; +import android.net.NetworkInfo; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.WorkSource; +import android.util.Log; + +/** + * A passive location provider reports locations received from other providers + * for clients that want to listen passively without actually triggering + * location updates. + * + * {@hide} + */ +public class PassiveProvider implements LocationProviderInterface { + + private static final String TAG = "PassiveProvider"; + + private final ILocationManager mLocationManager; + private boolean mTracking; + + public PassiveProvider(ILocationManager locationManager) { + mLocationManager = locationManager; + } + + public String getName() { + return LocationManager.PASSIVE_PROVIDER; + } + + public boolean requiresNetwork() { + return false; + } + + public boolean requiresSatellite() { + return false; + } + + public boolean requiresCell() { + return false; + } + + public boolean hasMonetaryCost() { + return false; + } + + public boolean supportsAltitude() { + return false; + } + + public boolean supportsSpeed() { + return false; + } + + public boolean supportsBearing() { + return false; + } + + public int getPowerRequirement() { + return -1; + } + + public boolean meetsCriteria(Criteria criteria) { + // We do not want to match the special passive provider based on criteria. + return false; + } + + public int getAccuracy() { + return -1; + } + + public boolean isEnabled() { + return true; + } + + public void enable() { + } + + public void disable() { + } + + public int getStatus(Bundle extras) { + if (mTracking) { + return LocationProvider.AVAILABLE; + } else { + return LocationProvider.TEMPORARILY_UNAVAILABLE; + } + } + + public long getStatusUpdateTime() { + return -1; + } + + public String getInternalState() { + return null; + } + + public void enableLocationTracking(boolean enable) { + mTracking = enable; + } + + public boolean requestSingleShotFix() { + return false; + } + + public void setMinTime(long minTime, WorkSource ws) { + } + + public void updateNetworkState(int state, NetworkInfo info) { + } + + public void updateLocation(Location location) { + if (mTracking) { + try { + // pass the location back to the location manager + mLocationManager.reportLocation(location, true); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException calling reportLocation"); + } + } + } + + public boolean sendExtraCommand(String command, Bundle extras) { + return false; + } + + public void addListener(int uid) { + } + + public void removeListener(int uid) { + } +} diff --git a/services/java/com/android/server/status/AnimatedImageView.java b/services/java/com/android/server/status/AnimatedImageView.java deleted file mode 100644 index 97df065..0000000 --- a/services/java/com/android/server/status/AnimatedImageView.java +++ /dev/null @@ -1,85 +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.status; - -import android.content.Context; -import android.graphics.drawable.AnimationDrawable; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.widget.ImageView; -import android.widget.RemoteViews.RemoteView; - -@RemoteView -public class AnimatedImageView extends ImageView { - AnimationDrawable mAnim; - boolean mAttached; - - public AnimatedImageView(Context context) { - super(context); - } - - public AnimatedImageView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - private void updateAnim() { - Drawable drawable = getDrawable(); - if (mAttached && mAnim != null) { - mAnim.stop(); - } - if (drawable instanceof AnimationDrawable) { - mAnim = (AnimationDrawable)drawable; - if (mAttached) { - mAnim.start(); - } - } else { - mAnim = null; - } - } - - @Override - public void setImageDrawable(Drawable drawable) { - super.setImageDrawable(drawable); - updateAnim(); - } - - @Override - @android.view.RemotableViewMethod - public void setImageResource(int resid) { - super.setImageResource(resid); - updateAnim(); - } - - @Override - public void onAttachedToWindow() { - super.onAttachedToWindow(); - if (mAnim != null) { - mAnim.start(); - } - mAttached = true; - } - - @Override - public void onDetachedFromWindow() { - super.onDetachedFromWindow(); - if (mAnim != null) { - mAnim.stop(); - } - mAttached = false; - } -} - diff --git a/services/java/com/android/server/status/CloseDragHandle.java b/services/java/com/android/server/status/CloseDragHandle.java deleted file mode 100644 index ad1ac4d..0000000 --- a/services/java/com/android/server/status/CloseDragHandle.java +++ /dev/null @@ -1,51 +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.status; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.widget.LinearLayout; - - -public class CloseDragHandle extends LinearLayout { - StatusBarService mService; - - public CloseDragHandle(Context context, AttributeSet attrs) { - super(context, attrs); - } - - /** - * Ensure that, if there is no target under us to receive the touch, - * that we process it ourself. This makes sure that onInterceptTouchEvent() - * is always called for the entire gesture. - */ - @Override - public boolean onTouchEvent(MotionEvent event) { - if (event.getAction() != MotionEvent.ACTION_DOWN) { - mService.interceptTouchEvent(event); - } - return true; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent event) { - return mService.interceptTouchEvent(event) - ? true : super.onInterceptTouchEvent(event); - } -} - diff --git a/services/java/com/android/server/status/DateView.java b/services/java/com/android/server/status/DateView.java deleted file mode 100644 index c04fb45..0000000 --- a/services/java/com/android/server/status/DateView.java +++ /dev/null @@ -1,89 +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.status; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.util.AttributeSet; -import android.util.Slog; -import android.widget.TextView; -import android.view.MotionEvent; - -import java.text.DateFormat; -import java.util.Date; - -public final class DateView extends TextView { - private static final String TAG = "DateView"; - - private boolean mUpdating = false; - - private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action.equals(Intent.ACTION_TIME_TICK) - || action.equals(Intent.ACTION_TIMEZONE_CHANGED)) { - updateClock(); - } - } - }; - - public DateView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - setUpdates(false); - } - - @Override - protected int getSuggestedMinimumWidth() { - // makes the large background bitmap not force us to full width - return 0; - } - - private final void updateClock() { - Date now = new Date(); - setText(DateFormat.getDateInstance(DateFormat.LONG).format(now)); - } - - void setUpdates(boolean update) { - if (update != mUpdating) { - mUpdating = update; - if (update) { - // Register for Intent broadcasts for the clock and battery - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_TIME_TICK); - filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); - mContext.registerReceiver(mIntentReceiver, filter, null, null); - updateClock(); - } else { - mContext.unregisterReceiver(mIntentReceiver); - } - } - } -} - diff --git a/services/java/com/android/server/status/ExpandedView.java b/services/java/com/android/server/status/ExpandedView.java deleted file mode 100644 index cb37f90..0000000 --- a/services/java/com/android/server/status/ExpandedView.java +++ /dev/null @@ -1,58 +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.status; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.Display; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.WindowManager; -import android.widget.LinearLayout; -import android.util.Slog; - - -public class ExpandedView extends LinearLayout { - StatusBarService mService; - int mPrevHeight = -1; - - public ExpandedView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - } - - /** We want to shrink down to 0, and ignore the background. */ - @Override - public int getSuggestedMinimumHeight() { - return 0; - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - int height = bottom - top; - if (height != mPrevHeight) { - //Slog.d(StatusBarService.TAG, "height changed old=" + mPrevHeight + " new=" + height); - mPrevHeight = height; - mService.updateExpandedViewPos(StatusBarService.EXPANDED_LEAVE_ALONE); - } - } -} diff --git a/services/java/com/android/server/status/FixedSizeDrawable.java b/services/java/com/android/server/status/FixedSizeDrawable.java deleted file mode 100644 index dbfcb2c..0000000 --- a/services/java/com/android/server/status/FixedSizeDrawable.java +++ /dev/null @@ -1,66 +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.status; - -import android.graphics.drawable.Drawable; -import android.graphics.Canvas; -import android.graphics.ColorFilter; -import android.graphics.Rect; -import android.util.Slog; - -class FixedSizeDrawable extends Drawable { - Drawable mDrawable; - int mLeft; - int mTop; - int mRight; - int mBottom; - - FixedSizeDrawable(Drawable that) { - mDrawable = that; - } - - public void setFixedBounds(int l, int t, int r, int b) { - mLeft = l; - mTop = t; - mRight = r; - mBottom = b; - } - - public void setBounds(Rect bounds) { - mDrawable.setBounds(mLeft, mTop, mRight, mBottom); - } - - public void setBounds(int l, int t, int r, int b) { - mDrawable.setBounds(mLeft, mTop, mRight, mBottom); - } - - public void draw(Canvas canvas) { - mDrawable.draw(canvas); - } - - public int getOpacity() { - return mDrawable.getOpacity(); - } - - public void setAlpha(int alpha) { - mDrawable.setAlpha(alpha); - } - - public void setColorFilter(ColorFilter cf) { - mDrawable.setColorFilter(cf); - } -} diff --git a/services/java/com/android/server/status/IconData.java b/services/java/com/android/server/status/IconData.java deleted file mode 100644 index fd226f9..0000000 --- a/services/java/com/android/server/status/IconData.java +++ /dev/null @@ -1,122 +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.status; - -import android.util.Slog; - -public class IconData { - /** - * Indicates ths item represents a piece of text. - */ - public static final int TEXT = 1; - - /** - * Indicates ths item represents an icon. - */ - public static final int ICON = 2; - - /** - * The type of this item. One of TEXT, ICON, or LEVEL_ICON. - */ - public int type; - - /** - * The slot that this icon will be in if it is not a notification - */ - public String slot; - - /** - * The package containting the icon to draw for this item. Valid if this is - * an ICON type. - */ - public String iconPackage; - - /** - * The icon to draw for this item. Valid if this is an ICON type. - */ - public int iconId; - - /** - * The level associated with the icon. Valid if this is a LEVEL_ICON type. - */ - public int iconLevel; - - /** - * The "count" number. - */ - public int number; - - /** - * The text associated with the icon. Valid if this is a TEXT type. - */ - public CharSequence text; - - private IconData() { - } - - public static IconData makeIcon(String slot, - String iconPackage, int iconId, int iconLevel, int number) { - IconData data = new IconData(); - data.type = ICON; - data.slot = slot; - data.iconPackage = iconPackage; - data.iconId = iconId; - data.iconLevel = iconLevel; - data.number = number; - return data; - } - - public static IconData makeText(String slot, CharSequence text) { - IconData data = new IconData(); - data.type = TEXT; - data.slot = slot; - data.text = text; - return data; - } - - public void copyFrom(IconData that) { - this.type = that.type; - this.slot = that.slot; - this.iconPackage = that.iconPackage; - this.iconId = that.iconId; - this.iconLevel = that.iconLevel; - this.number = that.number; - this.text = that.text; // should we clone this? - } - - public IconData clone() { - IconData that = new IconData(); - that.copyFrom(this); - return that; - } - - public String toString() { - if (this.type == TEXT) { - return "IconData(slot=" + (this.slot != null ? "'" + this.slot + "'" : "null") - + " text='" + this.text + "')"; - } - else if (this.type == ICON) { - return "IconData(slot=" + (this.slot != null ? "'" + this.slot + "'" : "null") - + " package=" + this.iconPackage - + " iconId=" + Integer.toHexString(this.iconId) - + " iconLevel=" + this.iconLevel + ")"; - } - else { - return "IconData(type=" + type + ")"; - } - } -} diff --git a/services/java/com/android/server/status/IconMerger.java b/services/java/com/android/server/status/IconMerger.java deleted file mode 100644 index aa702ae..0000000 --- a/services/java/com/android/server/status/IconMerger.java +++ /dev/null @@ -1,133 +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.status; - -import android.content.Context; -import android.os.Handler; -import android.util.AttributeSet; -import android.view.View; -import android.widget.LinearLayout; - - -public class IconMerger extends LinearLayout { - StatusBarService service; - StatusBarIcon moreIcon; - - public IconMerger(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - - final int maxWidth = r - l; - final int N = getChildCount(); - int i; - - // get the rightmost one, and see if we even need to do anything - int fitRight = -1; - for (i=N-1; i>=0; i--) { - final View child = getChildAt(i); - if (child.getVisibility() != GONE) { - fitRight = child.getRight(); - break; - } - } - - // find the first visible one that isn't the more icon - View moreView = null; - int fitLeft = -1; - int startIndex = -1; - for (i=0; i<N; i++) { - final View child = getChildAt(i); - if (com.android.internal.R.drawable.stat_notify_more == child.getId()) { - moreView = child; - startIndex = i+1; - } - else if (child.getVisibility() != GONE) { - fitLeft = child.getLeft(); - break; - } - } - - if (moreView == null || startIndex < 0) { - throw new RuntimeException("Status Bar / IconMerger moreView == null"); - } - - // if it fits without the more icon, then hide the more icon and update fitLeft - // so everything gets pushed left - int adjust = 0; - if (fitRight - fitLeft <= maxWidth) { - adjust = fitLeft - moreView.getLeft(); - fitLeft -= adjust; - fitRight -= adjust; - moreView.layout(0, moreView.getTop(), 0, moreView.getBottom()); - } - int extra = fitRight - r; - int shift = -1; - - int breakingPoint = fitLeft + extra + adjust; - int number = 0; - for (i=startIndex; i<N; i++) { - final View child = getChildAt(i); - if (child.getVisibility() != GONE) { - int childLeft = child.getLeft(); - int childRight = child.getRight(); - if (childLeft < breakingPoint) { - // hide this one - child.layout(0, child.getTop(), 0, child.getBottom()); - int n = this.service.getIconNumberForView(child); - if (n == 0) { - number += 1; - } else if (n > 0) { - number += n; - } - } else { - // decide how much to shift by - if (shift < 0) { - shift = childLeft - fitLeft; - } - // shift this left by shift - child.layout(childLeft-shift, child.getTop(), - childRight-shift, child.getBottom()); - } - } - } - - // BUG: Updating the text during the layout here doesn't seem to cause - // the view to be redrawn fully. The text view gets resized correctly, but the - // text contents aren't drawn properly. To work around this, we post a message - // and provide the value later. We're the only one changing this value show it - // should be ordered correctly. - if (false) { - this.moreIcon.update(number); - } else { - mBugWorkaroundNumber = number; - mBugWorkaroundHandler.post(mBugWorkaroundRunnable); - } - } - - private int mBugWorkaroundNumber; - private Handler mBugWorkaroundHandler = new Handler(); - private Runnable mBugWorkaroundRunnable = new Runnable() { - public void run() { - IconMerger.this.moreIcon.update(mBugWorkaroundNumber); - IconMerger.this.moreIcon.view.invalidate(); - } - }; -} diff --git a/services/java/com/android/server/status/LatestItemView.java b/services/java/com/android/server/status/LatestItemView.java deleted file mode 100644 index fe8d164..0000000 --- a/services/java/com/android/server/status/LatestItemView.java +++ /dev/null @@ -1,34 +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.status; - -import android.content.Context; -import android.util.AttributeSet; -import android.util.Slog; -import android.view.MotionEvent; -import android.widget.FrameLayout; - -public class LatestItemView extends FrameLayout { - - public LatestItemView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public boolean dispatchTouchEvent(MotionEvent ev) { - return onTouchEvent(ev); - } -} diff --git a/services/java/com/android/server/status/NotificationData.java b/services/java/com/android/server/status/NotificationData.java deleted file mode 100644 index 71f01ca..0000000 --- a/services/java/com/android/server/status/NotificationData.java +++ /dev/null @@ -1,44 +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.status; - -import android.app.PendingIntent; -import android.widget.RemoteViews; - -public class NotificationData { - public String pkg; - public String tag; - public int id; - public CharSequence tickerText; - - public long when; - public boolean ongoingEvent; - public boolean clearable; - - public RemoteViews contentView; - public PendingIntent contentIntent; - - public PendingIntent deleteIntent; - - public String toString() { - return "NotificationData(package=" + pkg + " id=" + id + " tickerText=" + tickerText - + " ongoingEvent=" + ongoingEvent + " contentIntent=" + contentIntent - + " deleteIntent=" + deleteIntent - + " clearable=" + clearable - + " contentView=" + contentView + " when=" + when + ")"; - } -} diff --git a/services/java/com/android/server/status/NotificationLinearLayout.java b/services/java/com/android/server/status/NotificationLinearLayout.java deleted file mode 100644 index 2fdf956..0000000 --- a/services/java/com/android/server/status/NotificationLinearLayout.java +++ /dev/null @@ -1,29 +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.status; - -import android.content.Context; -import android.util.AttributeSet; -import android.widget.LinearLayout; - - -public class NotificationLinearLayout extends LinearLayout { - public NotificationLinearLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } -} - diff --git a/services/java/com/android/server/status/NotificationViewList.java b/services/java/com/android/server/status/NotificationViewList.java deleted file mode 100644 index 1bb56a7..0000000 --- a/services/java/com/android/server/status/NotificationViewList.java +++ /dev/null @@ -1,276 +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.status; - -import android.os.IBinder; -import android.util.Slog; -import android.view.View; -import java.util.ArrayList; - -class NotificationViewList { - private ArrayList<StatusBarNotification> mOngoing = new ArrayList(); - private ArrayList<StatusBarNotification> mLatest = new ArrayList(); - - NotificationViewList() { - } - - private static final int indexInList(ArrayList<StatusBarNotification> list, NotificationData n){ - final int N = list.size(); - for (int i=0; i<N; i++) { - StatusBarNotification that = list.get(i); - if (that.data == n) { - return i; - } - } - return -1; - } - - int getIconIndex(NotificationData n) { - final int ongoingSize = mOngoing.size(); - final int latestSize = mLatest.size(); - if (n.ongoingEvent) { - int index = indexInList(mOngoing, n); - if (index >= 0) { - return latestSize + index + 1; - } else { - return -1; - } - } else { - return indexInList(mLatest, n) + 1; - } - } - - void remove(StatusBarNotification notification) { - NotificationData n = notification.data; - int index; - index = indexInList(mOngoing, n); - if (index >= 0) { - mOngoing.remove(index); - return; - } - index = indexInList(mLatest, n); - if (index >= 0) { - mLatest.remove(index); - return; - } - } - - ArrayList<StatusBarNotification> notificationsForPackage(String packageName) { - ArrayList<StatusBarNotification> list = new ArrayList<StatusBarNotification>(); - int N = mOngoing.size(); - for (int i=0; i<N; i++) { - if (matchPackage(mOngoing.get(i), packageName)) { - list.add(mOngoing.get(i)); - } - } - N = mLatest.size(); - for (int i=0; i<N; i++) { - if (matchPackage(mLatest.get(i), packageName)) { - list.add(mLatest.get(i)); - } - } - return list; - } - - private final boolean matchPackage(StatusBarNotification snb, String packageName) { - if (snb.data.contentIntent != null) { - if (snb.data.contentIntent.getTargetPackage().equals(packageName)) { - return true; - } - } else if (snb.data.pkg != null && snb.data.pkg.equals(packageName)) { - return true; - } - return false; - } - - private static final int indexForKey(ArrayList<StatusBarNotification> list, IBinder key) { - final int N = list.size(); - for (int i=0; i<N; i++) { - if (list.get(i).key == key) { - return i; - } - } - return -1; - } - - StatusBarNotification get(IBinder key) { - int index; - index = indexForKey(mOngoing, key); - if (index >= 0) { - return mOngoing.get(index); - } - index = indexForKey(mLatest, key); - if (index >= 0) { - return mLatest.get(index); - } - return null; - } - - // gets the index of the notification's view in its expanded parent view - int getExpandedIndex(StatusBarNotification notification) { - ArrayList<StatusBarNotification> list = notification.data.ongoingEvent ? mOngoing : mLatest; - final IBinder key = notification.key; - int index = 0; - // (the view order is backwards from this list order) - for (int i=list.size()-1; i>=0; i--) { - StatusBarNotification item = list.get(i); - if (item.key == key) { - return index; - } - if (item.view != null) { - index++; - } - } - Slog.e(StatusBarService.TAG, "Couldn't find notification in NotificationViewList."); - Slog.e(StatusBarService.TAG, "notification=" + notification); - dump(notification); - return 0; - } - - void clearViews() { - int N = mOngoing.size(); - for (int i=0; i<N; i++) { - mOngoing.get(i).view = null; - } - N = mLatest.size(); - for (int i=0; i<N; i++) { - mLatest.get(i).view = null; - } - } - - int ongoingCount() { - return mOngoing.size(); - } - - int latestCount() { - return mLatest.size(); - } - - StatusBarNotification getOngoing(int index) { - return mOngoing.get(index); - } - - StatusBarNotification getLatest(int index) { - return mLatest.get(index); - } - - int size() { - return mOngoing.size() + mLatest.size(); - } - - void add(StatusBarNotification notification) { - if (StatusBarService.SPEW) { - Slog.d(StatusBarService.TAG, "before add NotificationViewList" - + " notification.data.ongoingEvent=" + notification.data.ongoingEvent); - dump(notification); - } - - ArrayList<StatusBarNotification> list = notification.data.ongoingEvent ? mOngoing : mLatest; - long when = notification.data.when; - final int N = list.size(); - int index = N; - for (int i=0; i<N; i++) { - StatusBarNotification that = list.get(i); - if (that.data.when > when) { - index = i; - break; - } - } - list.add(index, notification); - - if (StatusBarService.SPEW) { - Slog.d(StatusBarService.TAG, "after add NotificationViewList index=" + index); - dump(notification); - } - } - - void dump(StatusBarNotification notification) { - if (StatusBarService.SPEW) { - boolean showTime = false; - String s = ""; - for (int i=0; i<mOngoing.size(); i++) { - StatusBarNotification that = mOngoing.get(i); - if (that.key == notification.key) { - s += "["; - } - if (showTime) { - s += that.data.when; - } else { - s += that.data.pkg + "/" + that.data.id + "/" + that.view; - } - if (that.key == notification.key) { - s += "]"; - } - s += " "; - } - Slog.d(StatusBarService.TAG, "NotificationViewList ongoing: " + s); - - s = ""; - for (int i=0; i<mLatest.size(); i++) { - StatusBarNotification that = mLatest.get(i); - if (that.key == notification.key) { - s += "["; - } - if (showTime) { - s += that.data.when; - } else { - s += that.data.pkg + "/" + that.data.id + "/" + that.view; - } - if (that.key == notification.key) { - s += "]"; - } - s += " "; - } - Slog.d(StatusBarService.TAG, "NotificationViewList latest: " + s); - } - } - - StatusBarNotification get(View view) { - int N = mOngoing.size(); - for (int i=0; i<N; i++) { - StatusBarNotification notification = mOngoing.get(i); - View v = notification.view; - if (v == view) { - return notification; - } - } - N = mLatest.size(); - for (int i=0; i<N; i++) { - StatusBarNotification notification = mLatest.get(i); - View v = notification.view; - if (v == view) { - return notification; - } - } - return null; - } - - void update(StatusBarNotification notification) { - remove(notification); - add(notification); - } - - boolean hasClearableItems() { - int N = mLatest.size(); - for (int i=0; i<N; i++) { - if (mLatest.get(i).data.clearable) { - return true; - } - } - return false; - } -} diff --git a/services/java/com/android/server/status/StatusBarIcon.java b/services/java/com/android/server/status/StatusBarIcon.java deleted file mode 100644 index 6f8b8a8..0000000 --- a/services/java/com/android/server/status/StatusBarIcon.java +++ /dev/null @@ -1,186 +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.status; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.text.TextUtils; -import android.util.Slog; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -class StatusBarIcon { - // TODO: get this from a resource - private static final int ICON_GAP = 8; - private static final int ICON_WIDTH = 25; - private static final int ICON_HEIGHT = 25; - - public View view; - - IconData mData; - - private TextView mTextView; - private AnimatedImageView mImageView; - private TextView mNumberView; - - public StatusBarIcon(Context context, IconData data, ViewGroup parent) { - mData = data.clone(); - - switch (data.type) { - case IconData.TEXT: { - TextView t; - t = new TextView(context); - mTextView = t; - LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.MATCH_PARENT); - t.setTextSize(16); - t.setTextColor(0xff000000); - t.setTypeface(Typeface.DEFAULT_BOLD); - t.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); - t.setPadding(6, 0, 0, 0); - t.setLayoutParams(layoutParams); - t.setText(data.text); - this.view = t; - break; - } - - case IconData.ICON: { - // container - LayoutInflater inflater = (LayoutInflater)context.getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - View v = inflater.inflate(com.android.internal.R.layout.status_bar_icon, parent, false); - this.view = v; - - // icon - AnimatedImageView im = (AnimatedImageView)v.findViewById(com.android.internal.R.id.image); - im.setImageDrawable(getIcon(context, data)); - im.setImageLevel(data.iconLevel); - mImageView = im; - - // number - TextView nv = (TextView)v.findViewById(com.android.internal.R.id.number); - mNumberView = nv; - if (data.number > 0) { - nv.setText("" + data.number); - nv.setVisibility(View.VISIBLE); - } else { - nv.setVisibility(View.GONE); - } - break; - } - } - } - - public void update(Context context, IconData data) throws StatusBarException { - if (mData.type != data.type) { - throw new StatusBarException("status bar entry type can't change"); - } - switch (data.type) { - case IconData.TEXT: - if (!TextUtils.equals(mData.text, data.text)) { - TextView tv = mTextView; - tv.setText(data.text); - } - break; - case IconData.ICON: - if (((mData.iconPackage != null && data.iconPackage != null) - && !mData.iconPackage.equals(data.iconPackage)) - || mData.iconId != data.iconId - || mData.iconLevel != data.iconLevel) { - ImageView im = mImageView; - im.setImageDrawable(getIcon(context, data)); - im.setImageLevel(data.iconLevel); - } - if (mData.number != data.number) { - TextView nv = mNumberView; - if (data.number > 0) { - nv.setText("" + data.number); - } else { - nv.setText(""); - } - } - break; - } - mData.copyFrom(data); - } - - public void update(int number) { - if (mData.number != number) { - TextView nv = mNumberView; - if (number > 0) { - nv.setText("" + number); - } else { - nv.setText(""); - } - } - mData.number = number; - } - - - /** - * Returns the right icon to use for this item, respecting the iconId and - * iconPackage (if set) - * - * @param context Context to use to get resources if iconPackage is not set - * @return Drawable for this item, or null if the package or item could not - * be found - */ - static Drawable getIcon(Context context, IconData data) { - - Resources r = null; - - if (data.iconPackage != null) { - try { - r = context.getPackageManager().getResourcesForApplication(data.iconPackage); - } catch (PackageManager.NameNotFoundException ex) { - Slog.e(StatusBarService.TAG, "Icon package not found: " + data.iconPackage, ex); - return null; - } - } else { - r = context.getResources(); - } - - if (data.iconId == 0) { - Slog.w(StatusBarService.TAG, "No icon ID for slot " + data.slot); - return null; - } - - try { - return r.getDrawable(data.iconId); - } catch (RuntimeException e) { - Slog.w(StatusBarService.TAG, "Icon not found in " - + (data.iconPackage != null ? data.iconId : "<system>") - + ": " + Integer.toHexString(data.iconId)); - } - - return null; - } - - int getNumber() { - return mData.number; - } -} - diff --git a/services/java/com/android/server/status/StatusBarNotification.java b/services/java/com/android/server/status/StatusBarNotification.java deleted file mode 100644 index e5773f7..0000000 --- a/services/java/com/android/server/status/StatusBarNotification.java +++ /dev/null @@ -1,27 +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.status; - -import android.os.IBinder; -import android.view.View; - -class StatusBarNotification { - IBinder key; - NotificationData data; - View view; - View contentView; -} diff --git a/services/java/com/android/server/status/StatusBarPolicy.java b/services/java/com/android/server/status/StatusBarPolicy.java deleted file mode 100644 index 3b0c436..0000000 --- a/services/java/com/android/server/status/StatusBarPolicy.java +++ /dev/null @@ -1,1390 +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.status; - -import android.app.AlertDialog; -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothHeadset; -import android.bluetooth.BluetoothPbap; -import android.content.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.TypedArray; -import android.graphics.PixelFormat; -import android.graphics.drawable.Drawable; -import android.media.AudioManager; -import android.media.Ringtone; -import android.media.RingtoneManager; -import android.net.NetworkInfo; -import android.net.Uri; -import android.net.wifi.WifiManager; -import android.os.Binder; -import android.os.Handler; -import android.os.IBinder; -import android.os.Message; -import android.os.RemoteException; -import android.os.storage.StorageManager; -import android.provider.Settings; -import android.telephony.PhoneStateListener; -import android.telephony.ServiceState; -import android.telephony.SignalStrength; -import android.telephony.TelephonyManager; -import android.text.format.DateFormat; -import android.text.style.RelativeSizeSpan; -import android.text.Spannable; -import android.text.SpannableStringBuilder; -import android.util.Slog; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.WindowManagerImpl; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.internal.R; -import com.android.internal.app.IBatteryStats; -import com.android.internal.location.GpsLocationProvider; -import com.android.internal.telephony.IccCard; -import com.android.internal.telephony.TelephonyIntents; -import com.android.internal.telephony.cdma.EriInfo; -import com.android.internal.telephony.cdma.TtyIntent; -import com.android.server.am.BatteryStatsService; - -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.TimeZone; - -/** - * This class contains all of the policy about which icons are installed in the status - * bar at boot time. In reality, it should go into the android.policy package, but - * putting it here is the first step from extracting it. - */ -public class StatusBarPolicy { - private static final String TAG = "StatusBarPolicy"; - - private static StatusBarPolicy sInstance; - - // message codes for the handler - private static final int EVENT_BATTERY_CLOSE = 4; - - private final Context mContext; - private final StatusBarService mService; - private final Handler mHandler = new StatusBarHandler(); - private final IBatteryStats mBatteryStats; - - // clock - private Calendar mCalendar; - private String mClockFormatString; - private SimpleDateFormat mClockFormat; - private IBinder mClockIcon; - private IconData mClockData; - - // storage - private StorageManager mStorageManager; - - // battery - private IBinder mBatteryIcon; - private IconData mBatteryData; - private boolean mBatteryFirst = true; - private boolean mBatteryPlugged; - private int mBatteryLevel; - private AlertDialog mLowBatteryDialog; - private TextView mBatteryLevelTextView; - private View mBatteryView; - private int mBatteryViewSequence; - private boolean mBatteryShowLowOnEndCall = false; - private static final boolean SHOW_LOW_BATTERY_WARNING = true; - private static final boolean SHOW_BATTERY_WARNINGS_IN_CALL = true; - - // phone - private TelephonyManager mPhone; - private IBinder mPhoneIcon; - - //***** Signal strength icons - private IconData mPhoneData; - //GSM/UMTS - private static final int[] sSignalImages = new int[] { - com.android.internal.R.drawable.stat_sys_signal_0, - com.android.internal.R.drawable.stat_sys_signal_1, - com.android.internal.R.drawable.stat_sys_signal_2, - com.android.internal.R.drawable.stat_sys_signal_3, - com.android.internal.R.drawable.stat_sys_signal_4 - }; - private static final int[] sSignalImages_r = new int[] { - com.android.internal.R.drawable.stat_sys_r_signal_0, - com.android.internal.R.drawable.stat_sys_r_signal_1, - com.android.internal.R.drawable.stat_sys_r_signal_2, - com.android.internal.R.drawable.stat_sys_r_signal_3, - com.android.internal.R.drawable.stat_sys_r_signal_4 - }; - private static final int[] sRoamingIndicatorImages_cdma = new int[] { - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, //Standard Roaming Indicator - // 1 is Standard Roaming Indicator OFF - // TODO T: image never used, remove and put 0 instead? - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - - // 2 is Standard Roaming Indicator FLASHING - // TODO T: image never used, remove and put 0 instead? - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - - // 3-12 Standard ERI - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, //3 - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - - // 13-63 Reserved for Standard ERI - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, //13 - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - - // 64-127 Reserved for Non Standard (Operator Specific) ERI - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, //64 - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0 //83 - - // 128-255 Reserved - }; - - //***** Data connection icons - private int[] mDataIconList = sDataNetType_g; - //GSM/UMTS - private static final int[] sDataNetType_g = new int[] { - com.android.internal.R.drawable.stat_sys_data_connected_g, - com.android.internal.R.drawable.stat_sys_data_in_g, - com.android.internal.R.drawable.stat_sys_data_out_g, - com.android.internal.R.drawable.stat_sys_data_inandout_g, - }; - private static final int[] sDataNetType_3g = new int[] { - com.android.internal.R.drawable.stat_sys_data_connected_3g, - com.android.internal.R.drawable.stat_sys_data_in_3g, - com.android.internal.R.drawable.stat_sys_data_out_3g, - com.android.internal.R.drawable.stat_sys_data_inandout_3g, - }; - private static final int[] sDataNetType_e = new int[] { - com.android.internal.R.drawable.stat_sys_data_connected_e, - com.android.internal.R.drawable.stat_sys_data_in_e, - com.android.internal.R.drawable.stat_sys_data_out_e, - com.android.internal.R.drawable.stat_sys_data_inandout_e, - }; - //3.5G - private static final int[] sDataNetType_h = new int[] { - com.android.internal.R.drawable.stat_sys_data_connected_h, - com.android.internal.R.drawable.stat_sys_data_in_h, - com.android.internal.R.drawable.stat_sys_data_out_h, - com.android.internal.R.drawable.stat_sys_data_inandout_h, - }; - - //CDMA - // Use 3G icons for EVDO data and 1x icons for 1XRTT data - private static final int[] sDataNetType_1x = new int[] { - com.android.internal.R.drawable.stat_sys_data_connected_1x, - com.android.internal.R.drawable.stat_sys_data_in_1x, - com.android.internal.R.drawable.stat_sys_data_out_1x, - com.android.internal.R.drawable.stat_sys_data_inandout_1x, - }; - - // Assume it's all good unless we hear otherwise. We don't always seem - // to get broadcasts that it *is* there. - IccCard.State mSimState = IccCard.State.READY; - int mPhoneState = TelephonyManager.CALL_STATE_IDLE; - int mDataState = TelephonyManager.DATA_DISCONNECTED; - int mDataActivity = TelephonyManager.DATA_ACTIVITY_NONE; - ServiceState mServiceState; - SignalStrength mSignalStrength; - - // data connection - private IBinder mDataIcon; - private IconData mDataData; - private boolean mDataIconVisible; - private boolean mHspaDataDistinguishable; - - // ringer volume - private IBinder mVolumeIcon; - private IconData mVolumeData; - private boolean mVolumeVisible; - - // bluetooth device status - private IBinder mBluetoothIcon; - private IconData mBluetoothData; - private int mBluetoothHeadsetState; - private boolean mBluetoothA2dpConnected; - private int mBluetoothPbapState; - private boolean mBluetoothEnabled; - - // wifi - private static final int[] sWifiSignalImages = new int[] { - com.android.internal.R.drawable.stat_sys_wifi_signal_1, - com.android.internal.R.drawable.stat_sys_wifi_signal_2, - com.android.internal.R.drawable.stat_sys_wifi_signal_3, - com.android.internal.R.drawable.stat_sys_wifi_signal_4, - }; - private static final int sWifiTemporarilyNotConnectedImage = - com.android.internal.R.drawable.stat_sys_wifi_signal_0; - - private int mLastWifiSignalLevel = -1; - private boolean mIsWifiConnected = false; - private IBinder mWifiIcon; - private IconData mWifiData; - - // gps - private IBinder mGpsIcon; - private IconData mGpsEnabledIconData; - private IconData mGpsFixIconData; - - // alarm clock - // Icon lit when clock is set - private IBinder mAlarmClockIcon; - private IconData mAlarmClockIconData; - - // sync state - // If sync is active the SyncActive icon is displayed. If sync is not active but - // sync is failing the SyncFailing icon is displayed. Otherwise neither are displayed. - private IBinder mSyncActiveIcon; - private IBinder mSyncFailingIcon; - - // TTY mode - // Icon lit when TTY mode is enabled - private IBinder mTTYModeIcon; - private IconData mTTYModeEnableIconData; - - // Cdma Roaming Indicator, ERI - private IBinder mCdmaRoamingIndicatorIcon; - private IconData mCdmaRoamingIndicatorIconData; - - private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action.equals(Intent.ACTION_TIME_TICK)) { - updateClock(); - } - else if (action.equals(Intent.ACTION_TIME_CHANGED)) { - updateClock(); - } - else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { - updateBattery(intent); - } - else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) { - updateClock(); - } - else if (action.equals(Intent.ACTION_TIMEZONE_CHANGED)) { - String tz = intent.getStringExtra("time-zone"); - mCalendar = Calendar.getInstance(TimeZone.getTimeZone(tz)); - if (mClockFormat != null) { - mClockFormat.setTimeZone(mCalendar.getTimeZone()); - } - updateClock(); - } - else if (action.equals(Intent.ACTION_ALARM_CHANGED)) { - updateAlarm(intent); - } - else if (action.equals(Intent.ACTION_SYNC_STATE_CHANGED)) { - updateSyncState(intent); - } - else if (action.equals(Intent.ACTION_BATTERY_LOW)) { - onBatteryLow(intent); - } - else if (action.equals(Intent.ACTION_BATTERY_OKAY) - || action.equals(Intent.ACTION_POWER_CONNECTED)) { - onBatteryOkay(intent); - } - else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED) || - action.equals(BluetoothHeadset.ACTION_STATE_CHANGED) || - action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED) || - action.equals(BluetoothPbap.PBAP_STATE_CHANGED_ACTION)) { - updateBluetooth(intent); - } - else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION) || - action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION) || - action.equals(WifiManager.RSSI_CHANGED_ACTION)) { - updateWifi(intent); - } - else if (action.equals(GpsLocationProvider.GPS_ENABLED_CHANGE_ACTION) || - action.equals(GpsLocationProvider.GPS_FIX_CHANGE_ACTION)) { - updateGps(intent); - } - else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION) || - action.equals(AudioManager.VIBRATE_SETTING_CHANGED_ACTION)) { - updateVolume(); - } - else if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) { - updateSimState(intent); - } - else if (action.equals(TtyIntent.TTY_ENABLED_CHANGE_ACTION)) { - updateTTY(intent); - } - } - }; - - private StatusBarPolicy(Context context, StatusBarService service) { - mContext = context; - mService = service; - mSignalStrength = new SignalStrength(); - mBatteryStats = BatteryStatsService.getService(); - - // clock - mCalendar = Calendar.getInstance(TimeZone.getDefault()); - mClockData = IconData.makeText("clock", ""); - mClockIcon = service.addIcon(mClockData, null); - updateClock(); - - // storage - mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); - mStorageManager.registerListener( - new com.android.server.status.StorageNotification(context)); - - // battery - mBatteryData = IconData.makeIcon("battery", - null, com.android.internal.R.drawable.stat_sys_battery_unknown, 0, 0); - mBatteryIcon = service.addIcon(mBatteryData, null); - - // phone_signal - mPhone = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); - mPhoneData = IconData.makeIcon("phone_signal", - null, com.android.internal.R.drawable.stat_sys_signal_null, 0, 0); - mPhoneIcon = service.addIcon(mPhoneData, null); - - // register for phone state notifications. - ((TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE)) - .listen(mPhoneStateListener, - PhoneStateListener.LISTEN_SERVICE_STATE - | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS - | PhoneStateListener.LISTEN_CALL_STATE - | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE - | PhoneStateListener.LISTEN_DATA_ACTIVITY); - - // data_connection - mDataData = IconData.makeIcon("data_connection", - null, com.android.internal.R.drawable.stat_sys_data_connected_g, 0, 0); - mDataIcon = service.addIcon(mDataData, null); - service.setIconVisibility(mDataIcon, false); - - // wifi - mWifiData = IconData.makeIcon("wifi", null, sWifiSignalImages[0], 0, 0); - mWifiIcon = service.addIcon(mWifiData, null); - service.setIconVisibility(mWifiIcon, false); - // wifi will get updated by the sticky intents - - // TTY status - mTTYModeEnableIconData = IconData.makeIcon("tty", - null, com.android.internal.R.drawable.stat_sys_tty_mode, 0, 0); - mTTYModeIcon = service.addIcon(mTTYModeEnableIconData, null); - service.setIconVisibility(mTTYModeIcon, false); - - // Cdma Roaming Indicator, ERI - mCdmaRoamingIndicatorIconData = IconData.makeIcon("cdma_eri", - null, com.android.internal.R.drawable.stat_sys_roaming_cdma_0, 0, 0); - mCdmaRoamingIndicatorIcon = service.addIcon(mCdmaRoamingIndicatorIconData, null); - service.setIconVisibility(mCdmaRoamingIndicatorIcon, false); - - // bluetooth status - mBluetoothData = IconData.makeIcon("bluetooth", - null, com.android.internal.R.drawable.stat_sys_data_bluetooth, 0, 0); - mBluetoothIcon = service.addIcon(mBluetoothData, null); - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter != null) { - mBluetoothEnabled = adapter.isEnabled(); - } else { - mBluetoothEnabled = false; - } - mBluetoothA2dpConnected = false; - mBluetoothHeadsetState = BluetoothHeadset.STATE_DISCONNECTED; - mBluetoothPbapState = BluetoothPbap.STATE_DISCONNECTED; - mService.setIconVisibility(mBluetoothIcon, mBluetoothEnabled); - - // Gps status - mGpsEnabledIconData = IconData.makeIcon("gps", - null, com.android.internal.R.drawable.stat_sys_gps_acquiring_anim, 0, 0); - mGpsFixIconData = IconData.makeIcon("gps", - null, com.android.internal.R.drawable.stat_sys_gps_on, 0, 0); - mGpsIcon = service.addIcon(mGpsEnabledIconData, null); - service.setIconVisibility(mGpsIcon, false); - - // Alarm clock - mAlarmClockIconData = IconData.makeIcon( - "alarm_clock", - null, com.android.internal.R.drawable.stat_notify_alarm, 0, 0); - mAlarmClockIcon = service.addIcon(mAlarmClockIconData, null); - service.setIconVisibility(mAlarmClockIcon, false); - - // Sync state - mSyncActiveIcon = service.addIcon(IconData.makeIcon("sync_active", - null, R.drawable.stat_notify_sync_anim0, 0, 0), null); - mSyncFailingIcon = service.addIcon(IconData.makeIcon("sync_failing", - null, R.drawable.stat_notify_sync_error, 0, 0), null); - service.setIconVisibility(mSyncActiveIcon, false); - service.setIconVisibility(mSyncFailingIcon, false); - - // volume - mVolumeData = IconData.makeIcon("volume", - null, com.android.internal.R.drawable.stat_sys_ringer_silent, 0, 0); - mVolumeIcon = service.addIcon(mVolumeData, null); - service.setIconVisibility(mVolumeIcon, false); - updateVolume(); - - IntentFilter filter = new IntentFilter(); - - // Register for Intent broadcasts for... - filter.addAction(Intent.ACTION_TIME_TICK); - filter.addAction(Intent.ACTION_TIME_CHANGED); - filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); - filter.addAction(Intent.ACTION_BATTERY_CHANGED); - filter.addAction(Intent.ACTION_BATTERY_LOW); - filter.addAction(Intent.ACTION_BATTERY_OKAY); - filter.addAction(Intent.ACTION_POWER_CONNECTED); - filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); - filter.addAction(Intent.ACTION_ALARM_CHANGED); - filter.addAction(Intent.ACTION_SYNC_STATE_CHANGED); - filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); - filter.addAction(AudioManager.VIBRATE_SETTING_CHANGED_ACTION); - filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); - filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED); - filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); - filter.addAction(BluetoothPbap.PBAP_STATE_CHANGED_ACTION); - filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); - filter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION); - filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); - filter.addAction(WifiManager.RSSI_CHANGED_ACTION); - filter.addAction(GpsLocationProvider.GPS_ENABLED_CHANGE_ACTION); - filter.addAction(GpsLocationProvider.GPS_FIX_CHANGE_ACTION); - filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); - filter.addAction(TtyIntent.TTY_ENABLED_CHANGE_ACTION); - mContext.registerReceiver(mIntentReceiver, filter, null, mHandler); - - // load config to determine if to distinguish Hspa data icon - try { - mHspaDataDistinguishable = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_hspa_data_distinguishable); - } catch (Exception e) { - mHspaDataDistinguishable = false; - } - } - - public static void installIcons(Context context, StatusBarService service) { - sInstance = new StatusBarPolicy(context, service); - } - - private final CharSequence getSmallTime() { - boolean b24 = DateFormat.is24HourFormat(mContext); - int res; - - if (b24) { - res = R.string.twenty_four_hour_time_format; - } else { - res = R.string.twelve_hour_time_format; - } - - final char MAGIC1 = '\uEF00'; - final char MAGIC2 = '\uEF01'; - - SimpleDateFormat sdf; - String format = mContext.getString(res); - if (!format.equals(mClockFormatString)) { - /* - * Search for an unquoted "a" in the format string, so we can - * add dummy characters around it to let us find it again after - * formatting and change its size. - */ - int a = -1; - boolean quoted = false; - for (int i = 0; i < format.length(); i++) { - char c = format.charAt(i); - - if (c == '\'') { - quoted = !quoted; - } - - if (!quoted && c == 'a') { - a = i; - break; - } - } - - if (a >= 0) { - // Move a back so any whitespace before the AM/PM is also in the alternate size. - final int b = a; - while (a > 0 && Character.isWhitespace(format.charAt(a-1))) { - a--; - } - format = format.substring(0, a) + MAGIC1 + format.substring(a, b) - + "a" + MAGIC2 + format.substring(b + 1); - } - - mClockFormat = sdf = new SimpleDateFormat(format); - mClockFormatString = format; - } else { - sdf = mClockFormat; - } - String result = sdf.format(mCalendar.getTime()); - - int magic1 = result.indexOf(MAGIC1); - int magic2 = result.indexOf(MAGIC2); - - if (magic1 >= 0 && magic2 > magic1) { - SpannableStringBuilder formatted = new SpannableStringBuilder(result); - - formatted.setSpan(new RelativeSizeSpan(0.7f), magic1, magic2, - Spannable.SPAN_EXCLUSIVE_INCLUSIVE); - - formatted.delete(magic2, magic2 + 1); - formatted.delete(magic1, magic1 + 1); - - return formatted; - } else { - return result; - } - } - - private final void updateClock() { - mCalendar.setTimeInMillis(System.currentTimeMillis()); - mClockData.text = getSmallTime(); - mService.updateIcon(mClockIcon, mClockData, null); - } - - private final void updateAlarm(Intent intent) { - boolean alarmSet = intent.getBooleanExtra("alarmSet", false); - mService.setIconVisibility(mAlarmClockIcon, alarmSet); - } - - private final void updateSyncState(Intent intent) { - boolean isActive = intent.getBooleanExtra("active", false); - boolean isFailing = intent.getBooleanExtra("failing", false); - mService.setIconVisibility(mSyncActiveIcon, isActive); - // Don't display sync failing icon: BUG 1297963 Set sync error timeout to "never" - //mService.setIconVisibility(mSyncFailingIcon, isFailing && !isActive); - } - - private final void updateBattery(Intent intent) { - mBatteryData.iconId = intent.getIntExtra("icon-small", 0); - mBatteryData.iconLevel = intent.getIntExtra("level", 0); - mService.updateIcon(mBatteryIcon, mBatteryData, null); - - boolean plugged = intent.getIntExtra("plugged", 0) != 0; - int level = intent.getIntExtra("level", -1); - if (false) { - Slog.d(TAG, "updateBattery level=" + level - + " plugged=" + plugged - + " mBatteryPlugged=" + mBatteryPlugged - + " mBatteryLevel=" + mBatteryLevel - + " mBatteryFirst=" + mBatteryFirst); - } - - boolean oldPlugged = mBatteryPlugged; - - mBatteryPlugged = plugged; - mBatteryLevel = level; - - if (mBatteryFirst) { - mBatteryFirst = false; - } - /* - * No longer showing the battery view because it draws attention away - * from the USB storage notification. We could still show it when - * connected to a brick, but that could lead to the user into thinking - * the device does not charge when plugged into USB (since he/she would - * not see the same battery screen on USB as he sees on brick). - */ - /* else { - if (plugged && !oldPlugged) { - showBatteryView(); - } - } - */ - if (false) { - Slog.d(TAG, "plugged=" + plugged + " oldPlugged=" + oldPlugged + " level=" + level); - } - } - - private void onBatteryLow(Intent intent) { - if (SHOW_LOW_BATTERY_WARNING) { - if (false) { - Slog.d(TAG, "mPhoneState=" + mPhoneState - + " mLowBatteryDialog=" + mLowBatteryDialog - + " mBatteryShowLowOnEndCall=" + mBatteryShowLowOnEndCall); - } - - if (SHOW_BATTERY_WARNINGS_IN_CALL || mPhoneState == TelephonyManager.CALL_STATE_IDLE) { - showLowBatteryWarning(); - } else { - mBatteryShowLowOnEndCall = true; - } - } - } - - private void onBatteryOkay(Intent intent) { - if (mLowBatteryDialog != null - && SHOW_LOW_BATTERY_WARNING) { - mLowBatteryDialog.dismiss(); - mBatteryShowLowOnEndCall = false; - } - } - - private void showBatteryView() { - closeLastBatteryView(); - if (mLowBatteryDialog != null) { - mLowBatteryDialog.dismiss(); - } - - int level = mBatteryLevel; - - View v = View.inflate(mContext, com.android.internal.R.layout.battery_status, null); - mBatteryView = v; - int pixelFormat = PixelFormat.TRANSLUCENT; - Drawable bg = v.getBackground(); - if (bg != null) { - pixelFormat = bg.getOpacity(); - } - - int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE - | WindowManager.LayoutParams.FLAG_DIM_BEHIND; - - if (!mContext.getResources().getBoolean( - com.android.internal.R.bool.config_sf_slowBlur)) { - flags |= WindowManager.LayoutParams.FLAG_BLUR_BEHIND; - } - - WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT, - WindowManager.LayoutParams.TYPE_TOAST, - flags, pixelFormat); - - // Get the dim amount from the theme - TypedArray a = mContext.obtainStyledAttributes( - com.android.internal.R.styleable.Theme); - lp.dimAmount = a.getFloat(android.R.styleable.Theme_backgroundDimAmount, 0.5f); - a.recycle(); - - lp.setTitle("Battery"); - - TextView levelTextView = (TextView)v.findViewById(com.android.internal.R.id.level_percent); - levelTextView.setText(mContext.getString( - com.android.internal.R.string.battery_status_text_percent_format, level)); - - setBatteryLevel(v, com.android.internal.R.id.spacer, 100-level, 0, 0); - setBatteryLevel(v, com.android.internal.R.id.level, level, - com.android.internal.R.drawable.battery_charge_fill, level); - - WindowManagerImpl.getDefault().addView(v, lp); - - scheduleCloseBatteryView(); - } - - private void setBatteryLevel(View parent, int id, int height, int background, int level) { - ImageView v = (ImageView)parent.findViewById(id); - LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)v.getLayoutParams(); - lp.weight = height; - if (background != 0) { - v.setBackgroundResource(background); - Drawable bkg = v.getBackground(); - bkg.setLevel(level); - } - } - - private void showLowBatteryWarning() { - closeLastBatteryView(); - - // Show exact battery level. - CharSequence levelText = mContext.getString( - com.android.internal.R.string.battery_low_percent_format, mBatteryLevel); - - if (mBatteryLevelTextView != null) { - mBatteryLevelTextView.setText(levelText); - } else { - View v = View.inflate(mContext, com.android.internal.R.layout.battery_low, null); - mBatteryLevelTextView=(TextView)v.findViewById(com.android.internal.R.id.level_percent); - - mBatteryLevelTextView.setText(levelText); - - AlertDialog.Builder b = new AlertDialog.Builder(mContext); - b.setCancelable(true); - b.setTitle(com.android.internal.R.string.battery_low_title); - b.setView(v); - b.setIcon(android.R.drawable.ic_dialog_alert); - b.setPositiveButton(android.R.string.ok, null); - - final Intent intent = new Intent(Intent.ACTION_POWER_USAGE_SUMMARY); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_MULTIPLE_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS - | Intent.FLAG_ACTIVITY_NO_HISTORY); - if (intent.resolveActivity(mContext.getPackageManager()) != null) { - b.setNegativeButton(com.android.internal.R.string.battery_low_why, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - mContext.startActivity(intent); - if (mLowBatteryDialog != null) { - mLowBatteryDialog.dismiss(); - } - } - }); - } - - AlertDialog d = b.create(); - d.setOnDismissListener(mLowBatteryListener); - d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); - d.show(); - mLowBatteryDialog = d; - } - - final ContentResolver cr = mContext.getContentResolver(); - if (Settings.System.getInt(cr, - Settings.System.POWER_SOUNDS_ENABLED, 1) == 1) - { - final String soundPath = Settings.System.getString(cr, - Settings.System.LOW_BATTERY_SOUND); - if (soundPath != null) { - final Uri soundUri = Uri.parse("file://" + soundPath); - if (soundUri != null) { - final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri); - if (sfx != null) { - sfx.setStreamType(AudioManager.STREAM_SYSTEM); - sfx.play(); - } - } - } - } - } - - private final void updateCallState(int state) { - mPhoneState = state; - if (false) { - Slog.d(TAG, "mPhoneState=" + mPhoneState - + " mLowBatteryDialog=" + mLowBatteryDialog - + " mBatteryShowLowOnEndCall=" + mBatteryShowLowOnEndCall); - } - if (mPhoneState == TelephonyManager.CALL_STATE_IDLE) { - if (mBatteryShowLowOnEndCall) { - if (!mBatteryPlugged) { - showLowBatteryWarning(); - } - mBatteryShowLowOnEndCall = false; - } - } else { - if (mLowBatteryDialog != null) { - mLowBatteryDialog.dismiss(); - mBatteryShowLowOnEndCall = true; - } - } - } - - private DialogInterface.OnDismissListener mLowBatteryListener - = new DialogInterface.OnDismissListener() { - public void onDismiss(DialogInterface dialog) { - mLowBatteryDialog = null; - mBatteryLevelTextView = null; - } - }; - - private void scheduleCloseBatteryView() { - Message m = mHandler.obtainMessage(EVENT_BATTERY_CLOSE); - m.arg1 = (++mBatteryViewSequence); - mHandler.sendMessageDelayed(m, 3000); - } - - private void closeLastBatteryView() { - if (mBatteryView != null) { - //mBatteryView.debug(); - WindowManagerImpl.getDefault().removeView(mBatteryView); - mBatteryView = null; - } - } - - private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { - @Override - public void onSignalStrengthsChanged(SignalStrength signalStrength) { - mSignalStrength = signalStrength; - updateSignalStrength(); - } - - @Override - public void onServiceStateChanged(ServiceState state) { - mServiceState = state; - updateSignalStrength(); - updateCdmaRoamingIcon(state); - updateDataIcon(); - } - - @Override - public void onCallStateChanged(int state, String incomingNumber) { - updateCallState(state); - // In cdma, if a voice call is made, RSSI should switch to 1x. - if (isCdma()) { - updateSignalStrength(); - } - } - - @Override - public void onDataConnectionStateChanged(int state, int networkType) { - mDataState = state; - updateDataNetType(networkType); - updateDataIcon(); - } - - @Override - public void onDataActivity(int direction) { - mDataActivity = direction; - updateDataIcon(); - } - }; - - private final void updateSimState(Intent intent) { - String stateExtra = intent.getStringExtra(IccCard.INTENT_KEY_ICC_STATE); - if (IccCard.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) { - mSimState = IccCard.State.ABSENT; - } - else if (IccCard.INTENT_VALUE_ICC_READY.equals(stateExtra)) { - mSimState = IccCard.State.READY; - } - else if (IccCard.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) { - final String lockedReason = intent.getStringExtra(IccCard.INTENT_KEY_LOCKED_REASON); - if (IccCard.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) { - mSimState = IccCard.State.PIN_REQUIRED; - } - else if (IccCard.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) { - mSimState = IccCard.State.PUK_REQUIRED; - } - else { - mSimState = IccCard.State.NETWORK_LOCKED; - } - } else { - mSimState = IccCard.State.UNKNOWN; - } - updateDataIcon(); - } - - private boolean isCdma() { - return (mSignalStrength != null) && !mSignalStrength.isGsm(); - } - - private boolean isEvdo() { - return ( (mServiceState != null) - && ((mServiceState.getRadioTechnology() - == ServiceState.RADIO_TECHNOLOGY_EVDO_0) - || (mServiceState.getRadioTechnology() - == ServiceState.RADIO_TECHNOLOGY_EVDO_A) - || (mServiceState.getRadioTechnology() - == ServiceState.RADIO_TECHNOLOGY_EVDO_B))); - } - - private boolean hasService() { - if (mServiceState != null) { - switch (mServiceState.getState()) { - case ServiceState.STATE_OUT_OF_SERVICE: - case ServiceState.STATE_POWER_OFF: - return false; - default: - return true; - } - } else { - return false; - } - } - - private final void updateSignalStrength() { - int iconLevel = -1; - int[] iconList; - - // Display signal strength while in "emergency calls only" mode - if (!hasService() && !mServiceState.isEmergencyOnly()) { - //Slog.d(TAG, "updateSignalStrength: no service"); - if (Settings.System.getInt(mContext.getContentResolver(), - Settings.System.AIRPLANE_MODE_ON, 0) == 1) { - mPhoneData.iconId = com.android.internal.R.drawable.stat_sys_signal_flightmode; - } else { - mPhoneData.iconId = com.android.internal.R.drawable.stat_sys_signal_null; - } - mService.updateIcon(mPhoneIcon, mPhoneData, null); - return; - } - - if (!isCdma()) { - int asu = mSignalStrength.getGsmSignalStrength(); - - // ASU ranges from 0 to 31 - TS 27.007 Sec 8.5 - // asu = 0 (-113dB or less) is very weak - // signal, its better to show 0 bars to the user in such cases. - // asu = 99 is a special case, where the signal strength is unknown. - if (asu <= 2 || asu == 99) iconLevel = 0; - else if (asu >= 12) iconLevel = 4; - else if (asu >= 8) iconLevel = 3; - else if (asu >= 5) iconLevel = 2; - else iconLevel = 1; - - // Though mPhone is a Manager, this call is not an IPC - if (mPhone.isNetworkRoaming()) { - iconList = sSignalImages_r; - } else { - iconList = sSignalImages; - } - } else { - iconList = this.sSignalImages; - - // If 3G(EV) and 1x network are available than 3G should be - // displayed, displayed RSSI should be from the EV side. - // If a voice call is made then RSSI should switch to 1x. - if ((mPhoneState == TelephonyManager.CALL_STATE_IDLE) && isEvdo()){ - iconLevel = getEvdoLevel(); - if (false) { - Slog.d(TAG, "use Evdo level=" + iconLevel + " to replace Cdma Level=" + getCdmaLevel()); - } - } else { - iconLevel = getCdmaLevel(); - } - } - mPhoneData.iconId = iconList[iconLevel]; - mService.updateIcon(mPhoneIcon, mPhoneData, null); - } - - private int getCdmaLevel() { - final int cdmaDbm = mSignalStrength.getCdmaDbm(); - final int cdmaEcio = mSignalStrength.getCdmaEcio(); - int levelDbm = 0; - int levelEcio = 0; - - if (cdmaDbm >= -75) levelDbm = 4; - else if (cdmaDbm >= -85) levelDbm = 3; - else if (cdmaDbm >= -95) levelDbm = 2; - else if (cdmaDbm >= -100) levelDbm = 1; - else levelDbm = 0; - - // Ec/Io are in dB*10 - if (cdmaEcio >= -90) levelEcio = 4; - else if (cdmaEcio >= -110) levelEcio = 3; - else if (cdmaEcio >= -130) levelEcio = 2; - else if (cdmaEcio >= -150) levelEcio = 1; - else levelEcio = 0; - - return (levelDbm < levelEcio) ? levelDbm : levelEcio; - } - - private int getEvdoLevel() { - int evdoDbm = mSignalStrength.getEvdoDbm(); - int evdoSnr = mSignalStrength.getEvdoSnr(); - int levelEvdoDbm = 0; - int levelEvdoSnr = 0; - - if (evdoDbm >= -65) levelEvdoDbm = 4; - else if (evdoDbm >= -75) levelEvdoDbm = 3; - else if (evdoDbm >= -90) levelEvdoDbm = 2; - else if (evdoDbm >= -105) levelEvdoDbm = 1; - else levelEvdoDbm = 0; - - if (evdoSnr >= 7) levelEvdoSnr = 4; - else if (evdoSnr >= 5) levelEvdoSnr = 3; - else if (evdoSnr >= 3) levelEvdoSnr = 2; - else if (evdoSnr >= 1) levelEvdoSnr = 1; - else levelEvdoSnr = 0; - - return (levelEvdoDbm < levelEvdoSnr) ? levelEvdoDbm : levelEvdoSnr; - } - - private final void updateDataNetType(int net) { - switch (net) { - case TelephonyManager.NETWORK_TYPE_EDGE: - mDataIconList = sDataNetType_e; - break; - case TelephonyManager.NETWORK_TYPE_UMTS: - mDataIconList = sDataNetType_3g; - break; - case TelephonyManager.NETWORK_TYPE_HSDPA: - case TelephonyManager.NETWORK_TYPE_HSUPA: - case TelephonyManager.NETWORK_TYPE_HSPA: - if (mHspaDataDistinguishable) { - mDataIconList = sDataNetType_h; - } else { - mDataIconList = sDataNetType_3g; - } - break; - case TelephonyManager.NETWORK_TYPE_CDMA: - // display 1xRTT for IS95A/B - mDataIconList = this.sDataNetType_1x; - break; - case TelephonyManager.NETWORK_TYPE_1xRTT: - mDataIconList = this.sDataNetType_1x; - break; - case TelephonyManager.NETWORK_TYPE_EVDO_0: //fall through - case TelephonyManager.NETWORK_TYPE_EVDO_A: - case TelephonyManager.NETWORK_TYPE_EVDO_B: - mDataIconList = sDataNetType_3g; - break; - default: - mDataIconList = sDataNetType_g; - break; - } - } - - private final void updateDataIcon() { - int iconId; - boolean visible = true; - - if (!isCdma()) { - // GSM case, we have to check also the sim state - if (mSimState == IccCard.State.READY || mSimState == IccCard.State.UNKNOWN) { - if (hasService() && mDataState == TelephonyManager.DATA_CONNECTED) { - switch (mDataActivity) { - case TelephonyManager.DATA_ACTIVITY_IN: - iconId = mDataIconList[1]; - break; - case TelephonyManager.DATA_ACTIVITY_OUT: - iconId = mDataIconList[2]; - break; - case TelephonyManager.DATA_ACTIVITY_INOUT: - iconId = mDataIconList[3]; - break; - default: - iconId = mDataIconList[0]; - break; - } - mDataData.iconId = iconId; - mService.updateIcon(mDataIcon, mDataData, null); - } else { - visible = false; - } - } else { - mDataData.iconId = com.android.internal.R.drawable.stat_sys_no_sim; - mService.updateIcon(mDataIcon, mDataData, null); - } - } else { - // CDMA case, mDataActivity can be also DATA_ACTIVITY_DORMANT - if (hasService() && mDataState == TelephonyManager.DATA_CONNECTED) { - switch (mDataActivity) { - case TelephonyManager.DATA_ACTIVITY_IN: - iconId = mDataIconList[1]; - break; - case TelephonyManager.DATA_ACTIVITY_OUT: - iconId = mDataIconList[2]; - break; - case TelephonyManager.DATA_ACTIVITY_INOUT: - iconId = mDataIconList[3]; - break; - case TelephonyManager.DATA_ACTIVITY_DORMANT: - default: - iconId = mDataIconList[0]; - break; - } - mDataData.iconId = iconId; - mService.updateIcon(mDataIcon, mDataData, null); - } else { - visible = false; - } - } - - long ident = Binder.clearCallingIdentity(); - try { - mBatteryStats.notePhoneDataConnectionState(mPhone.getNetworkType(), visible); - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(ident); - } - - if (mDataIconVisible != visible) { - mService.setIconVisibility(mDataIcon, visible); - mDataIconVisible = visible; - } - } - - private final void updateVolume() { - AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); - final int ringerMode = audioManager.getRingerMode(); - final boolean visible = ringerMode == AudioManager.RINGER_MODE_SILENT || - ringerMode == AudioManager.RINGER_MODE_VIBRATE; - final int iconId = (ringerMode == AudioManager.RINGER_MODE_VIBRATE) - ? com.android.internal.R.drawable.stat_sys_ringer_vibrate - : com.android.internal.R.drawable.stat_sys_ringer_silent; - - if (visible) { - mVolumeData.iconId = iconId; - mService.updateIcon(mVolumeIcon, mVolumeData, null); - } - if (visible != mVolumeVisible) { - mService.setIconVisibility(mVolumeIcon, visible); - mVolumeVisible = visible; - } - } - - private final void updateBluetooth(Intent intent) { - int iconId = com.android.internal.R.drawable.stat_sys_data_bluetooth; - String action = intent.getAction(); - if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { - int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); - mBluetoothEnabled = state == BluetoothAdapter.STATE_ON; - } else if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) { - mBluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, - BluetoothHeadset.STATE_ERROR); - } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) { - BluetoothA2dp a2dp = new BluetoothA2dp(mContext); - if (a2dp.getConnectedSinks().size() != 0) { - mBluetoothA2dpConnected = true; - } else { - mBluetoothA2dpConnected = false; - } - } else if (action.equals(BluetoothPbap.PBAP_STATE_CHANGED_ACTION)) { - mBluetoothPbapState = intent.getIntExtra(BluetoothPbap.PBAP_STATE, - BluetoothPbap.STATE_DISCONNECTED); - } else { - return; - } - - if (mBluetoothHeadsetState == BluetoothHeadset.STATE_CONNECTED || mBluetoothA2dpConnected || - mBluetoothPbapState == BluetoothPbap.STATE_CONNECTED) { - iconId = com.android.internal.R.drawable.stat_sys_data_bluetooth_connected; - } - - mBluetoothData.iconId = iconId; - mService.updateIcon(mBluetoothIcon, mBluetoothData, null); - mService.setIconVisibility(mBluetoothIcon, mBluetoothEnabled); - } - - private final void updateWifi(Intent intent) { - final String action = intent.getAction(); - if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { - - final boolean enabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, - WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED; - - if (!enabled) { - // If disabled, hide the icon. (We show icon when connected.) - mService.setIconVisibility(mWifiIcon, false); - } - - } else if (action.equals(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION)) { - final boolean enabled = intent.getBooleanExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, - false); - if (!enabled) { - mService.setIconVisibility(mWifiIcon, false); - } - } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { - - final NetworkInfo networkInfo = (NetworkInfo) - intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); - - int iconId; - if (networkInfo != null && networkInfo.isConnected()) { - mIsWifiConnected = true; - if (mLastWifiSignalLevel == -1) { - iconId = sWifiSignalImages[0]; - } else { - iconId = sWifiSignalImages[mLastWifiSignalLevel]; - } - - // Show the icon since wi-fi is connected - mService.setIconVisibility(mWifiIcon, true); - - } else { - mLastWifiSignalLevel = -1; - mIsWifiConnected = false; - iconId = sWifiSignalImages[0]; - - // Hide the icon since we're not connected - mService.setIconVisibility(mWifiIcon, false); - } - - mWifiData.iconId = iconId; - mService.updateIcon(mWifiIcon, mWifiData, null); - } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { - final int newRssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200); - int newSignalLevel = WifiManager.calculateSignalLevel(newRssi, - sWifiSignalImages.length); - if (newSignalLevel != mLastWifiSignalLevel) { - mLastWifiSignalLevel = newSignalLevel; - if (mIsWifiConnected) { - mWifiData.iconId = sWifiSignalImages[newSignalLevel]; - } else { - mWifiData.iconId = sWifiTemporarilyNotConnectedImage; - } - mService.updateIcon(mWifiIcon, mWifiData, null); - } - } - } - - private final void updateGps(Intent intent) { - final String action = intent.getAction(); - final boolean enabled = intent.getBooleanExtra(GpsLocationProvider.EXTRA_ENABLED, false); - - if (action.equals(GpsLocationProvider.GPS_FIX_CHANGE_ACTION) && enabled) { - // GPS is getting fixes - mService.updateIcon(mGpsIcon, mGpsFixIconData, null); - mService.setIconVisibility(mGpsIcon, true); - } else if (action.equals(GpsLocationProvider.GPS_ENABLED_CHANGE_ACTION) && !enabled) { - // GPS is off - mService.setIconVisibility(mGpsIcon, false); - } else { - // GPS is on, but not receiving fixes - mService.updateIcon(mGpsIcon, mGpsEnabledIconData, null); - mService.setIconVisibility(mGpsIcon, true); - } - } - - private final void updateTTY(Intent intent) { - final String action = intent.getAction(); - final boolean enabled = intent.getBooleanExtra(TtyIntent.TTY_ENABLED, false); - - if (false) Slog.v(TAG, "updateTTY: enabled: " + enabled); - - if (enabled) { - // TTY is on - if (false) Slog.v(TAG, "updateTTY: set TTY on"); - mService.updateIcon(mTTYModeIcon, mTTYModeEnableIconData, null); - mService.setIconVisibility(mTTYModeIcon, true); - } else { - // TTY is off - if (false) Slog.v(TAG, "updateTTY: set TTY off"); - mService.setIconVisibility(mTTYModeIcon, false); - } - } - - private final void updateCdmaRoamingIcon(ServiceState state) { - if (!hasService()) { - mService.setIconVisibility(mCdmaRoamingIndicatorIcon, false); - return; - } - - if (!isCdma()) { - mService.setIconVisibility(mCdmaRoamingIndicatorIcon, false); - return; - } - - int[] iconList = sRoamingIndicatorImages_cdma; - int iconIndex = state.getCdmaEriIconIndex(); - int iconMode = state.getCdmaEriIconMode(); - - if (iconIndex == -1) { - Slog.e(TAG, "getCdmaEriIconIndex returned null, skipping ERI icon update"); - return; - } - - if (iconMode == -1) { - Slog.e(TAG, "getCdmeEriIconMode returned null, skipping ERI icon update"); - return; - } - - if (iconIndex == EriInfo.ROAMING_INDICATOR_OFF) { - if (false) Slog.v(TAG, "Cdma ROAMING_INDICATOR_OFF, removing ERI icon"); - mService.setIconVisibility(mCdmaRoamingIndicatorIcon, false); - return; - } - - switch (iconMode) { - case EriInfo.ROAMING_ICON_MODE_NORMAL: - mCdmaRoamingIndicatorIconData.iconId = iconList[iconIndex]; - mService.updateIcon(mCdmaRoamingIndicatorIcon, mCdmaRoamingIndicatorIconData, null); - mService.setIconVisibility(mCdmaRoamingIndicatorIcon, true); - break; - case EriInfo.ROAMING_ICON_MODE_FLASH: - mCdmaRoamingIndicatorIconData.iconId = - com.android.internal.R.drawable.stat_sys_roaming_cdma_flash; - mService.updateIcon(mCdmaRoamingIndicatorIcon, mCdmaRoamingIndicatorIconData, null); - mService.setIconVisibility(mCdmaRoamingIndicatorIcon, true); - break; - - } - mService.updateIcon(mPhoneIcon, mPhoneData, null); - } - - - private class StatusBarHandler extends Handler { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case EVENT_BATTERY_CLOSE: - if (msg.arg1 == mBatteryViewSequence) { - closeLastBatteryView(); - } - break; - } - } - } -} diff --git a/services/java/com/android/server/status/StatusBarService.java b/services/java/com/android/server/status/StatusBarService.java deleted file mode 100644 index b9a57d6..0000000 --- a/services/java/com/android/server/status/StatusBarService.java +++ /dev/null @@ -1,1884 +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.status; - -import com.android.internal.R; -import com.android.internal.util.CharSequences; - -import android.app.ActivityManagerNative; -import android.app.Dialog; -import android.app.IStatusBar; -import android.app.PendingIntent; -import android.app.StatusBarManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.IBinder; -import android.os.RemoteException; -import android.os.Binder; -import android.os.Handler; -import android.os.Message; -import android.os.SystemClock; -import android.provider.Telephony; -import android.util.Slog; -import android.view.Display; -import android.view.Gravity; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowManager; -import android.view.WindowManagerImpl; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.widget.LinearLayout; -import android.widget.RemoteViews; -import android.widget.ScrollView; -import android.widget.TextView; -import android.widget.FrameLayout; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Set; - - -/** - * The public (ok, semi-public) service for the status bar. - * <p> - * This interesting thing to note about this class is that most of the methods that - * are called from other classes just post a message, and everything else is batched - * and coalesced into a series of calls to methods that all start with "perform." - * There are two reasons for this. The first is that some of the methods (activate/deactivate) - * are on IStatusBar, so they're called from the thread pool and they need to make their - * way onto the UI thread. The second is that the message queue is stopped while animations - * are happening in order to make for smoother transitions. - * <p> - * Each icon is either an icon or an icon and a notification. They're treated mostly - * separately throughout the code, although they both use the same key, which is assigned - * when they are created. - */ -public class StatusBarService extends IStatusBar.Stub -{ - static final String TAG = "StatusBar"; - static final boolean SPEW = false; - - static final int EXPANDED_LEAVE_ALONE = -10000; - static final int EXPANDED_FULL_OPEN = -10001; - - private static final int MSG_ANIMATE = 1000; - private static final int MSG_ANIMATE_REVEAL = 1001; - - private static final int OP_ADD_ICON = 1; - private static final int OP_UPDATE_ICON = 2; - private static final int OP_REMOVE_ICON = 3; - private static final int OP_SET_VISIBLE = 4; - private static final int OP_EXPAND = 5; - private static final int OP_TOGGLE = 6; - private static final int OP_DISABLE = 7; - private class PendingOp { - IBinder key; - int code; - IconData iconData; - NotificationData notificationData; - boolean visible; - int integer; - } - - private class DisableRecord implements IBinder.DeathRecipient { - String pkg; - int what; - IBinder token; - - public void binderDied() { - Slog.i(TAG, "binder died for pkg=" + pkg); - disable(0, token, pkg); - token.unlinkToDeath(this, 0); - } - } - - public interface NotificationCallbacks { - void onSetDisabled(int status); - void onClearAll(); - void onNotificationClick(String pkg, String tag, int id); - void onPanelRevealed(); - } - - private class ExpandedDialog extends Dialog { - ExpandedDialog(Context context) { - super(context, com.android.internal.R.style.Theme_Light_NoTitleBar); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - boolean down = event.getAction() == KeyEvent.ACTION_DOWN; - switch (event.getKeyCode()) { - case KeyEvent.KEYCODE_BACK: - if (!down) { - StatusBarService.this.deactivate(); - } - return true; - } - return super.dispatchKeyEvent(event); - } - } - - final Context mContext; - final Display mDisplay; - StatusBarView mStatusBarView; - int mPixelFormat; - H mHandler = new H(); - Object mQueueLock = new Object(); - ArrayList<PendingOp> mQueue = new ArrayList<PendingOp>(); - NotificationCallbacks mNotificationCallbacks; - - // All accesses to mIconMap and mNotificationData are syncronized on those objects, - // but this is only so dump() can work correctly. Modifying these outside of the UI - // thread will not work, there are places in the code that unlock and reaquire between - // reads and require them to not be modified. - - // icons - HashMap<IBinder,StatusBarIcon> mIconMap = new HashMap<IBinder,StatusBarIcon>(); - ArrayList<StatusBarIcon> mIconList = new ArrayList<StatusBarIcon>(); - String[] mRightIconSlots; - StatusBarIcon[] mRightIcons; - LinearLayout mIcons; - IconMerger mNotificationIcons; - LinearLayout mStatusIcons; - StatusBarIcon mMoreIcon; - private UninstallReceiver mUninstallReceiver; - - // expanded notifications - NotificationViewList mNotificationData = new NotificationViewList(); - Dialog mExpandedDialog; - ExpandedView mExpandedView; - WindowManager.LayoutParams mExpandedParams; - ScrollView mScrollView; - View mNotificationLinearLayout; - TextView mOngoingTitle; - LinearLayout mOngoingItems; - TextView mLatestTitle; - LinearLayout mLatestItems; - TextView mNoNotificationsTitle; - TextView mSpnLabel; - TextView mPlmnLabel; - TextView mClearButton; - View mExpandedContents; - CloseDragHandle mCloseView; - int[] mPositionTmp = new int[2]; - boolean mExpanded; - boolean mExpandedVisible; - - // the date view - DateView mDateView; - - // the tracker view - TrackingView mTrackingView; - WindowManager.LayoutParams mTrackingParams; - int mTrackingPosition; // the position of the top of the tracking view. - - // ticker - private Ticker mTicker; - private View mTickerView; - private boolean mTicking; - - // Tracking finger for opening/closing. - int mEdgeBorder; // corresponds to R.dimen.status_bar_edge_ignore - boolean mTracking; - VelocityTracker mVelocityTracker; - - static final int ANIM_FRAME_DURATION = (1000/60); - - boolean mAnimating; - long mCurAnimationTime; - float mDisplayHeight; - float mAnimY; - float mAnimVel; - float mAnimAccel; - long mAnimLastTime; - boolean mAnimatingReveal = false; - int mViewDelta; - int[] mAbsPos = new int[2]; - - // for disabling the status bar - ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>(); - int mDisabled = 0; - - /** - * Construct the service, add the status bar view to the window manager - */ - public StatusBarService(Context context) { - mContext = context; - mDisplay = ((WindowManager)context.getSystemService( - Context.WINDOW_SERVICE)).getDefaultDisplay(); - makeStatusBarView(context); - mUninstallReceiver = new UninstallReceiver(); - } - - public void setNotificationCallbacks(NotificationCallbacks listener) { - mNotificationCallbacks = listener; - } - - // ================================================================================ - // Constructing the view - // ================================================================================ - private void makeStatusBarView(Context context) { - Resources res = context.getResources(); - mRightIconSlots = res.getStringArray(com.android.internal.R.array.status_bar_icon_order); - mRightIcons = new StatusBarIcon[mRightIconSlots.length]; - - ExpandedView expanded = (ExpandedView)View.inflate(context, - com.android.internal.R.layout.status_bar_expanded, null); - expanded.mService = this; - StatusBarView sb = (StatusBarView)View.inflate(context, - com.android.internal.R.layout.status_bar, null); - sb.mService = this; - - // figure out which pixel-format to use for the status bar. - mPixelFormat = PixelFormat.TRANSLUCENT; - Drawable bg = sb.getBackground(); - if (bg != null) { - mPixelFormat = bg.getOpacity(); - } - - mStatusBarView = sb; - mStatusIcons = (LinearLayout)sb.findViewById(R.id.statusIcons); - mNotificationIcons = (IconMerger)sb.findViewById(R.id.notificationIcons); - mNotificationIcons.service = this; - mIcons = (LinearLayout)sb.findViewById(R.id.icons); - mTickerView = sb.findViewById(R.id.ticker); - mDateView = (DateView)sb.findViewById(R.id.date); - - mExpandedDialog = new ExpandedDialog(context); - mExpandedView = expanded; - mExpandedContents = expanded.findViewById(R.id.notificationLinearLayout); - mOngoingTitle = (TextView)expanded.findViewById(R.id.ongoingTitle); - mOngoingItems = (LinearLayout)expanded.findViewById(R.id.ongoingItems); - mLatestTitle = (TextView)expanded.findViewById(R.id.latestTitle); - mLatestItems = (LinearLayout)expanded.findViewById(R.id.latestItems); - mNoNotificationsTitle = (TextView)expanded.findViewById(R.id.noNotificationsTitle); - mClearButton = (TextView)expanded.findViewById(R.id.clear_all_button); - mClearButton.setOnClickListener(mClearButtonListener); - mSpnLabel = (TextView)expanded.findViewById(R.id.spnLabel); - mPlmnLabel = (TextView)expanded.findViewById(R.id.plmnLabel); - mScrollView = (ScrollView)expanded.findViewById(R.id.scroll); - mNotificationLinearLayout = expanded.findViewById(R.id.notificationLinearLayout); - - mExpandedView.setVisibility(View.GONE); - mOngoingTitle.setVisibility(View.GONE); - mLatestTitle.setVisibility(View.GONE); - - mTicker = new MyTicker(context, sb); - - TickerView tickerView = (TickerView)sb.findViewById(R.id.tickerText); - tickerView.mTicker = mTicker; - - mTrackingView = (TrackingView)View.inflate(context, - com.android.internal.R.layout.status_bar_tracking, null); - mTrackingView.mService = this; - mCloseView = (CloseDragHandle)mTrackingView.findViewById(R.id.close); - mCloseView.mService = this; - - mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore); - - // add the more icon for the notifications - IconData moreData = IconData.makeIcon(null, context.getPackageName(), - R.drawable.stat_notify_more, 0, 42); - mMoreIcon = new StatusBarIcon(context, moreData, mNotificationIcons); - mMoreIcon.view.setId(R.drawable.stat_notify_more); - mNotificationIcons.moreIcon = mMoreIcon; - mNotificationIcons.addView(mMoreIcon.view); - - // set the inital view visibility - setAreThereNotifications(); - mDateView.setVisibility(View.INVISIBLE); - - // before we register for broadcasts - mPlmnLabel.setText(R.string.lockscreen_carrier_default); - mPlmnLabel.setVisibility(View.VISIBLE); - mSpnLabel.setText(""); - mSpnLabel.setVisibility(View.GONE); - - // receive broadcasts - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); - filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - filter.addAction(Intent.ACTION_SCREEN_OFF); - filter.addAction(Telephony.Intents.SPN_STRINGS_UPDATED_ACTION); - context.registerReceiver(mBroadcastReceiver, filter); - } - - public void systemReady() { - final StatusBarView view = mStatusBarView; - WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - view.getContext().getResources().getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_height), - WindowManager.LayoutParams.TYPE_STATUS_BAR, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| - WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING, - mPixelFormat); - lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL; - lp.setTitle("StatusBar"); - lp.windowAnimations = R.style.Animation_StatusBar; - - WindowManagerImpl.getDefault().addView(view, lp); - } - - // ================================================================================ - // From IStatusBar - // ================================================================================ - public void activate() { - enforceExpandStatusBar(); - addPendingOp(OP_EXPAND, null, true); - } - - public void deactivate() { - enforceExpandStatusBar(); - addPendingOp(OP_EXPAND, null, false); - } - - public void toggle() { - enforceExpandStatusBar(); - addPendingOp(OP_TOGGLE, null, false); - } - - public void disable(int what, IBinder token, String pkg) { - enforceStatusBar(); - synchronized (mNotificationCallbacks) { - // This is a little gross, but I think it's safe as long as nobody else - // synchronizes on mNotificationCallbacks. It's important that the the callback - // and the pending op get done in the correct order and not interleaved with - // other calls, otherwise they'll get out of sync. - int net; - synchronized (mDisableRecords) { - manageDisableListLocked(what, token, pkg); - net = gatherDisableActionsLocked(); - mNotificationCallbacks.onSetDisabled(net); - } - addPendingOp(OP_DISABLE, net); - } - } - - public IBinder addIcon(String slot, String iconPackage, int iconId, int iconLevel) { - enforceStatusBar(); - return addIcon(IconData.makeIcon(slot, iconPackage, iconId, iconLevel, 0), null); - } - - public void updateIcon(IBinder key, - String slot, String iconPackage, int iconId, int iconLevel) { - enforceStatusBar(); - updateIcon(key, IconData.makeIcon(slot, iconPackage, iconId, iconLevel, 0), null); - } - - public void removeIcon(IBinder key) { - enforceStatusBar(); - addPendingOp(OP_REMOVE_ICON, key, null, null, -1); - } - - private void enforceStatusBar() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.STATUS_BAR, - "StatusBarService"); - } - - private void enforceExpandStatusBar() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.EXPAND_STATUS_BAR, - "StatusBarService"); - } - - // ================================================================================ - // Can be called from any thread - // ================================================================================ - public IBinder addIcon(IconData data, NotificationData n) { - int slot; - // assert early-on if they using a slot that doesn't exist. - if (data != null && n == null) { - slot = getRightIconIndex(data.slot); - if (slot < 0) { - throw new SecurityException("invalid status bar icon slot: " - + (data.slot != null ? "'" + data.slot + "'" : "null")); - } - } else { - slot = -1; - } - IBinder key = new Binder(); - addPendingOp(OP_ADD_ICON, key, data, n, -1); - return key; - } - - public void updateIcon(IBinder key, IconData data, NotificationData n) { - addPendingOp(OP_UPDATE_ICON, key, data, n, -1); - } - - public void setIconVisibility(IBinder key, boolean visible) { - addPendingOp(OP_SET_VISIBLE, key, visible); - } - - private void addPendingOp(int code, IBinder key, IconData data, NotificationData n, int i) { - synchronized (mQueueLock) { - PendingOp op = new PendingOp(); - op.key = key; - op.code = code; - op.iconData = data == null ? null : data.clone(); - op.notificationData = n; - op.integer = i; - mQueue.add(op); - if (mQueue.size() == 1) { - mHandler.sendEmptyMessage(2); - } - } - } - - private void addPendingOp(int code, IBinder key, boolean visible) { - synchronized (mQueueLock) { - PendingOp op = new PendingOp(); - op.key = key; - op.code = code; - op.visible = visible; - mQueue.add(op); - if (mQueue.size() == 1) { - mHandler.sendEmptyMessage(1); - } - } - } - - private void addPendingOp(int code, int integer) { - synchronized (mQueueLock) { - PendingOp op = new PendingOp(); - op.code = code; - op.integer = integer; - mQueue.add(op); - if (mQueue.size() == 1) { - mHandler.sendEmptyMessage(1); - } - } - } - - // lock on mDisableRecords - void manageDisableListLocked(int what, IBinder token, String pkg) { - if (SPEW) { - 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; - } - } - 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); - } - catch (RemoteException ex) { - return; // give up - } - mDisableRecords.add(tok); - } - tok.what = what; - tok.token = token; - tok.pkg = pkg; - } - } - } - - // lock on mDisableRecords - int gatherDisableActionsLocked() { - final int N = mDisableRecords.size(); - // gather the new net flags - int net = 0; - for (int i=0; i<N; i++) { - net |= mDisableRecords.get(i).what; - } - return net; - } - - private int getRightIconIndex(String slot) { - final int N = mRightIconSlots.length; - for (int i=0; i<N; i++) { - if (mRightIconSlots[i].equals(slot)) { - return i; - } - } - return -1; - } - - // ================================================================================ - // Always called from UI thread - // ================================================================================ - /** - * All changes to the status bar and notifications funnel through here and are batched. - */ - private class H extends Handler { - public void handleMessage(Message m) { - if (m.what == MSG_ANIMATE) { - doAnimation(); - return; - } - if (m.what == MSG_ANIMATE_REVEAL) { - doRevealAnimation(); - return; - } - - ArrayList<PendingOp> queue; - synchronized (mQueueLock) { - queue = mQueue; - mQueue = new ArrayList<PendingOp>(); - } - - boolean wasExpanded = mExpanded; - - // for each one in the queue, find all of the ones with the same key - // and collapse that down into a final op and/or call to setVisibility, etc - boolean expand = wasExpanded; - boolean doExpand = false; - boolean doDisable = false; - int disableWhat = 0; - int N = queue.size(); - while (N > 0) { - PendingOp op = queue.get(0); - boolean doOp = false; - boolean visible = false; - boolean doVisibility = false; - if (op.code == OP_SET_VISIBLE) { - doVisibility = true; - visible = op.visible; - } - else if (op.code == OP_EXPAND) { - doExpand = true; - expand = op.visible; - } - else if (op.code == OP_TOGGLE) { - doExpand = true; - expand = !expand; - } - else { - doOp = true; - } - - if (alwaysHandle(op.code)) { - // coalesce these - for (int i=1; i<N; i++) { - PendingOp o = queue.get(i); - if (!alwaysHandle(o.code) && o.key == op.key) { - if (o.code == OP_SET_VISIBLE) { - visible = o.visible; - doVisibility = true; - } - else if (o.code == OP_EXPAND) { - expand = o.visible; - doExpand = true; - } - else { - op.code = o.code; - op.iconData = o.iconData; - op.notificationData = o.notificationData; - } - queue.remove(i); - i--; - N--; - } - } - } - - queue.remove(0); - N--; - - if (doOp) { - switch (op.code) { - case OP_ADD_ICON: - case OP_UPDATE_ICON: - performAddUpdateIcon(op.key, op.iconData, op.notificationData); - break; - case OP_REMOVE_ICON: - performRemoveIcon(op.key); - break; - case OP_DISABLE: - doDisable = true; - disableWhat = op.integer; - break; - } - } - if (doVisibility && op.code != OP_REMOVE_ICON) { - performSetIconVisibility(op.key, visible); - } - } - - if (queue.size() != 0) { - throw new RuntimeException("Assertion failed: queue.size=" + queue.size()); - } - if (doExpand) { - // this is last so that we capture all of the pending changes before doing it - if (expand) { - animateExpand(); - } else { - animateCollapse(); - } - } - if (doDisable) { - performDisableActions(disableWhat); - } - } - } - - private boolean alwaysHandle(int code) { - return code == OP_DISABLE; - } - - /* private */ void performAddUpdateIcon(IBinder key, IconData data, NotificationData n) - throws StatusBarException { - if (SPEW) { - Slog.d(TAG, "performAddUpdateIcon icon=" + data + " notification=" + n + " key=" + key); - } - // notification - if (n != null) { - StatusBarNotification notification = getNotification(key); - NotificationData oldData = null; - if (notification == null) { - // add - notification = new StatusBarNotification(); - notification.key = key; - notification.data = n; - synchronized (mNotificationData) { - mNotificationData.add(notification); - } - addNotificationView(notification); - setAreThereNotifications(); - } else { - // update - oldData = notification.data; - notification.data = n; - updateNotificationView(notification, oldData); - } - // Show the ticker if one is requested, and the text is different - // than the currently displayed ticker. Also don't do this - // until status bar window is attached to the window manager, - // because... well, what's the point otherwise? And trying to - // run a ticker without being attached will crash! - if (n.tickerText != null && mStatusBarView.getWindowToken() != null - && (oldData == null - || oldData.tickerText == null - || !CharSequences.equals(oldData.tickerText, n.tickerText))) { - if (0 == (mDisabled & - (StatusBarManager.DISABLE_NOTIFICATION_ICONS | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) { - mTicker.addEntry(n, StatusBarIcon.getIcon(mContext, data), n.tickerText); - } - } - updateExpandedViewPos(EXPANDED_LEAVE_ALONE); - } - - // icon - synchronized (mIconMap) { - StatusBarIcon icon = mIconMap.get(key); - if (icon == null) { - // add - LinearLayout v = n == null ? mStatusIcons : mNotificationIcons; - - icon = new StatusBarIcon(mContext, data, v); - mIconMap.put(key, icon); - mIconList.add(icon); - - if (n == null) { - int slotIndex = getRightIconIndex(data.slot); - StatusBarIcon[] rightIcons = mRightIcons; - if (rightIcons[slotIndex] == null) { - int pos = 0; - for (int i=mRightIcons.length-1; i>slotIndex; i--) { - StatusBarIcon ic = rightIcons[i]; - if (ic != null) { - pos++; - } - } - rightIcons[slotIndex] = icon; - mStatusIcons.addView(icon.view, pos); - } else { - Slog.e(TAG, "duplicate icon in slot " + slotIndex + "/" + data.slot); - mIconMap.remove(key); - mIconList.remove(icon); - return ; - } - } else { - int iconIndex = mNotificationData.getIconIndex(n); - mNotificationIcons.addView(icon.view, iconIndex); - } - } else { - if (n == null) { - // right hand side icons -- these don't reorder - icon.update(mContext, data); - } else { - // remove old - ViewGroup parent = (ViewGroup)icon.view.getParent(); - parent.removeView(icon.view); - // add new - icon.update(mContext, data); - int iconIndex = mNotificationData.getIconIndex(n); - mNotificationIcons.addView(icon.view, iconIndex); - } - } - } - } - - /* private */ void performSetIconVisibility(IBinder key, boolean visible) { - synchronized (mIconMap) { - if (SPEW) { - Slog.d(TAG, "performSetIconVisibility key=" + key + " visible=" + visible); - } - StatusBarIcon icon = mIconMap.get(key); - icon.view.setVisibility(visible ? View.VISIBLE : View.GONE); - } - } - - /* private */ void performRemoveIcon(IBinder key) { - synchronized (this) { - if (SPEW) { - Slog.d(TAG, "performRemoveIcon key=" + key); - } - StatusBarIcon icon = mIconMap.remove(key); - mIconList.remove(icon); - if (icon != null) { - ViewGroup parent = (ViewGroup)icon.view.getParent(); - parent.removeView(icon.view); - int slotIndex = getRightIconIndex(icon.mData.slot); - if (slotIndex >= 0) { - mRightIcons[slotIndex] = null; - } - } - StatusBarNotification notification = getNotification(key); - if (notification != null) { - removeNotificationView(notification); - synchronized (mNotificationData) { - mNotificationData.remove(notification); - } - setAreThereNotifications(); - } - } - } - - int getIconNumberForView(View v) { - synchronized (mIconMap) { - StatusBarIcon icon = null; - final int N = mIconList.size(); - for (int i=0; i<N; i++) { - StatusBarIcon ic = mIconList.get(i); - if (ic.view == v) { - icon = ic; - break; - } - } - if (icon != null) { - return icon.getNumber(); - } else { - return -1; - } - } - } - - - StatusBarNotification getNotification(IBinder key) { - synchronized (mNotificationData) { - return mNotificationData.get(key); - } - } - - View.OnFocusChangeListener mFocusChangeListener = new View.OnFocusChangeListener() { - public void onFocusChange(View v, boolean hasFocus) { - // Because 'v' is a ViewGroup, all its children will be (un)selected - // too, which allows marqueeing to work. - v.setSelected(hasFocus); - } - }; - - View makeNotificationView(StatusBarNotification notification, ViewGroup parent) { - NotificationData n = notification.data; - RemoteViews remoteViews = n.contentView; - if (remoteViews == null) { - return null; - } - - // create the row view - LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - View row = inflater.inflate(com.android.internal.R.layout.status_bar_latest_event, parent, false); - - // bind the click event to the content area - ViewGroup content = (ViewGroup)row.findViewById(com.android.internal.R.id.content); - content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); - content.setOnFocusChangeListener(mFocusChangeListener); - PendingIntent contentIntent = n.contentIntent; - if (contentIntent != null) { - content.setOnClickListener(new Launcher(contentIntent, n.pkg, n.tag, n.id)); - } - - View child = null; - Exception exception = null; - try { - child = remoteViews.apply(mContext, content); - } - catch (RuntimeException e) { - exception = e; - } - if (child == null) { - Slog.e(TAG, "couldn't inflate view for package " + n.pkg, exception); - return null; - } - content.addView(child); - - row.setDrawingCacheEnabled(true); - - notification.view = row; - notification.contentView = child; - - return row; - } - - void addNotificationView(StatusBarNotification notification) { - if (notification.view != null) { - throw new RuntimeException("Assertion failed: notification.view=" - + notification.view); - } - - LinearLayout parent = notification.data.ongoingEvent ? mOngoingItems : mLatestItems; - - View child = makeNotificationView(notification, parent); - if (child == null) { - return ; - } - - int index = mNotificationData.getExpandedIndex(notification); - parent.addView(child, index); - } - - /** - * Remove the old one and put the new one in its place. - * @param notification the notification - */ - void updateNotificationView(StatusBarNotification notification, NotificationData oldData) { - NotificationData n = notification.data; - if (oldData != null && n != null - && n.when == oldData.when - && n.ongoingEvent == oldData.ongoingEvent - && n.contentView != null && oldData.contentView != null - && n.contentView.getPackage() != null - && oldData.contentView.getPackage() != null - && oldData.contentView.getPackage().equals(n.contentView.getPackage()) - && oldData.contentView.getLayoutId() == n.contentView.getLayoutId() - && notification.view != null) { - mNotificationData.update(notification); - try { - n.contentView.reapply(mContext, notification.contentView); - - // update the contentIntent - ViewGroup content = (ViewGroup)notification.view.findViewById( - com.android.internal.R.id.content); - PendingIntent contentIntent = n.contentIntent; - if (contentIntent != null) { - content.setOnClickListener(new Launcher(contentIntent, n.pkg, n.tag, n.id)); - } - } - catch (RuntimeException e) { - // It failed to add cleanly. Log, and remove the view from the panel. - Slog.w(TAG, "couldn't reapply views for package " + n.contentView.getPackage(), e); - removeNotificationView(notification); - } - } else { - mNotificationData.update(notification); - removeNotificationView(notification); - addNotificationView(notification); - } - setAreThereNotifications(); - } - - void removeNotificationView(StatusBarNotification notification) { - View v = notification.view; - if (v != null) { - ViewGroup parent = (ViewGroup)v.getParent(); - parent.removeView(v); - notification.view = null; - } - } - - private void setAreThereNotifications() { - boolean ongoing = mOngoingItems.getChildCount() != 0; - boolean latest = mLatestItems.getChildCount() != 0; - - if (mNotificationData.hasClearableItems()) { - mClearButton.setVisibility(View.VISIBLE); - } else { - mClearButton.setVisibility(View.INVISIBLE); - } - - mOngoingTitle.setVisibility(ongoing ? View.VISIBLE : View.GONE); - mLatestTitle.setVisibility(latest ? View.VISIBLE : View.GONE); - - if (ongoing || latest) { - mNoNotificationsTitle.setVisibility(View.GONE); - } else { - mNoNotificationsTitle.setVisibility(View.VISIBLE); - } - } - - private void makeExpandedVisible() { - if (SPEW) Slog.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible); - if (mExpandedVisible) { - return; - } - mExpandedVisible = true; - panelSlightlyVisible(true); - - updateExpandedViewPos(EXPANDED_LEAVE_ALONE); - mExpandedParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - mExpandedParams.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; - mExpandedDialog.getWindow().setAttributes(mExpandedParams); - mExpandedView.requestFocus(View.FOCUS_FORWARD); - mTrackingView.setVisibility(View.VISIBLE); - mExpandedView.setVisibility(View.VISIBLE); - - if (!mTicking) { - setDateViewVisibility(true, com.android.internal.R.anim.fade_in); - } - } - - void animateExpand() { - if (SPEW) Slog.d(TAG, "Animate expand: expanded=" + mExpanded); - if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { - return ; - } - if (mExpanded) { - return; - } - - prepareTracking(0, true); - performFling(0, 2000.0f, true); - } - - void animateCollapse() { - if (SPEW) { - Slog.d(TAG, "animateCollapse(): mExpanded=" + mExpanded - + " mExpandedVisible=" + mExpandedVisible - + " mExpanded=" + mExpanded - + " mAnimating=" + mAnimating - + " mAnimY=" + mAnimY - + " mAnimVel=" + mAnimVel); - } - - if (!mExpandedVisible) { - return; - } - - int y; - if (mAnimating) { - y = (int)mAnimY; - } else { - y = mDisplay.getHeight()-1; - } - // Let the fling think that we're open so it goes in the right direction - // and doesn't try to re-open the windowshade. - mExpanded = true; - prepareTracking(y, false); - performFling(y, -2000.0f, true); - } - - void performExpand() { - if (SPEW) Slog.d(TAG, "performExpand: mExpanded=" + mExpanded); - if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { - return ; - } - if (mExpanded) { - return; - } - - // It seems strange to sometimes not expand... - if (false) { - synchronized (mNotificationData) { - if (mNotificationData.size() == 0) { - return; - } - } - } - - mExpanded = true; - makeExpandedVisible(); - updateExpandedViewPos(EXPANDED_FULL_OPEN); - - if (false) postStartTracing(); - } - - void performCollapse() { - if (SPEW) Slog.d(TAG, "performCollapse: mExpanded=" + mExpanded - + " mExpandedVisible=" + mExpandedVisible); - - if (!mExpandedVisible) { - return; - } - mExpandedVisible = false; - panelSlightlyVisible(false); - mExpandedParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - mExpandedParams.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; - mExpandedDialog.getWindow().setAttributes(mExpandedParams); - mTrackingView.setVisibility(View.GONE); - mExpandedView.setVisibility(View.GONE); - - if ((mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS) == 0) { - setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in); - } - setDateViewVisibility(false, com.android.internal.R.anim.fade_out); - - if (!mExpanded) { - return; - } - mExpanded = false; - } - - void doAnimation() { - if (mAnimating) { - if (SPEW) Slog.d(TAG, "doAnimation"); - if (SPEW) Slog.d(TAG, "doAnimation before mAnimY=" + mAnimY); - incrementAnim(); - if (SPEW) Slog.d(TAG, "doAnimation after mAnimY=" + mAnimY); - if (mAnimY >= mDisplay.getHeight()-1) { - if (SPEW) Slog.d(TAG, "Animation completed to expanded state."); - mAnimating = false; - updateExpandedViewPos(EXPANDED_FULL_OPEN); - performExpand(); - } - else if (mAnimY < mStatusBarView.getHeight()) { - if (SPEW) Slog.d(TAG, "Animation completed to collapsed state."); - mAnimating = false; - updateExpandedViewPos(0); - performCollapse(); - } - else { - updateExpandedViewPos((int)mAnimY); - mCurAnimationTime += ANIM_FRAME_DURATION; - mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurAnimationTime); - } - } - } - - void stopTracking() { - mTracking = false; - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - - void incrementAnim() { - long now = SystemClock.uptimeMillis(); - float t = ((float)(now - mAnimLastTime)) / 1000; // ms -> s - final float y = mAnimY; - final float v = mAnimVel; // px/s - final float a = mAnimAccel; // px/s/s - mAnimY = y + (v*t) + (0.5f*a*t*t); // px - mAnimVel = v + (a*t); // px/s - mAnimLastTime = now; // ms - //Slog.d(TAG, "y=" + y + " v=" + v + " a=" + a + " t=" + t + " mAnimY=" + mAnimY - // + " mAnimAccel=" + mAnimAccel); - } - - void doRevealAnimation() { - final int h = mCloseView.getHeight() + mStatusBarView.getHeight(); - if (mAnimatingReveal && mAnimating && mAnimY < h) { - incrementAnim(); - if (mAnimY >= h) { - mAnimY = h; - updateExpandedViewPos((int)mAnimY); - } else { - updateExpandedViewPos((int)mAnimY); - mCurAnimationTime += ANIM_FRAME_DURATION; - mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE_REVEAL), - mCurAnimationTime); - } - } - } - - void prepareTracking(int y, boolean opening) { - mTracking = true; - mVelocityTracker = VelocityTracker.obtain(); - if (opening) { - mAnimAccel = 2000.0f; - mAnimVel = 200; - mAnimY = mStatusBarView.getHeight(); - updateExpandedViewPos((int)mAnimY); - mAnimating = true; - mAnimatingReveal = true; - mHandler.removeMessages(MSG_ANIMATE); - mHandler.removeMessages(MSG_ANIMATE_REVEAL); - long now = SystemClock.uptimeMillis(); - mAnimLastTime = now; - mCurAnimationTime = now + ANIM_FRAME_DURATION; - mAnimating = true; - mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE_REVEAL), - mCurAnimationTime); - makeExpandedVisible(); - } else { - // it's open, close it? - if (mAnimating) { - mAnimating = false; - mHandler.removeMessages(MSG_ANIMATE); - } - updateExpandedViewPos(y + mViewDelta); - } - } - - void performFling(int y, float vel, boolean always) { - mAnimatingReveal = false; - mDisplayHeight = mDisplay.getHeight(); - - mAnimY = y; - mAnimVel = vel; - - //Slog.d(TAG, "starting with mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel); - - if (mExpanded) { - if (!always && ( - vel > 200.0f - || (y > (mDisplayHeight-25) && vel > -200.0f))) { - // We are expanded, but they didn't move sufficiently to cause - // us to retract. Animate back to the expanded position. - mAnimAccel = 2000.0f; - if (vel < 0) { - mAnimVel = 0; - } - } - else { - // We are expanded and are now going to animate away. - mAnimAccel = -2000.0f; - if (vel > 0) { - mAnimVel = 0; - } - } - } else { - if (always || ( - vel > 200.0f - || (y > (mDisplayHeight/2) && vel > -200.0f))) { - // We are collapsed, and they moved enough to allow us to - // expand. Animate in the notifications. - mAnimAccel = 2000.0f; - if (vel < 0) { - mAnimVel = 0; - } - } - else { - // We are collapsed, but they didn't move sufficiently to cause - // us to retract. Animate back to the collapsed position. - mAnimAccel = -2000.0f; - if (vel > 0) { - mAnimVel = 0; - } - } - } - //Slog.d(TAG, "mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel - // + " mAnimAccel=" + mAnimAccel); - - long now = SystemClock.uptimeMillis(); - mAnimLastTime = now; - mCurAnimationTime = now + ANIM_FRAME_DURATION; - mAnimating = true; - mHandler.removeMessages(MSG_ANIMATE); - mHandler.removeMessages(MSG_ANIMATE_REVEAL); - mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurAnimationTime); - stopTracking(); - } - - boolean interceptTouchEvent(MotionEvent event) { - if (SPEW) { - Slog.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled=" - + mDisabled); - } - - if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { - return false; - } - - final int statusBarSize = mStatusBarView.getHeight(); - final int hitSize = statusBarSize*2; - if (event.getAction() == MotionEvent.ACTION_DOWN) { - final int y = (int)event.getRawY(); - - if (!mExpanded) { - mViewDelta = statusBarSize - y; - } else { - mTrackingView.getLocationOnScreen(mAbsPos); - mViewDelta = mAbsPos[1] + mTrackingView.getHeight() - y; - } - if ((!mExpanded && y < hitSize) || - (mExpanded && y > (mDisplay.getHeight()-hitSize))) { - - // We drop events at the edge of the screen to make the windowshade come - // down by accident less, especially when pushing open a device with a keyboard - // that rotates (like g1 and droid) - int x = (int)event.getRawX(); - final int edgeBorder = mEdgeBorder; - if (x >= edgeBorder && x < mDisplay.getWidth() - edgeBorder) { - prepareTracking(y, !mExpanded);// opening if we're not already fully visible - mVelocityTracker.addMovement(event); - } - } - } else if (mTracking) { - mVelocityTracker.addMovement(event); - final int minY = statusBarSize + mCloseView.getHeight(); - if (event.getAction() == MotionEvent.ACTION_MOVE) { - int y = (int)event.getRawY(); - if (mAnimatingReveal && y < minY) { - // nothing - } else { - mAnimatingReveal = false; - updateExpandedViewPos(y + mViewDelta); - } - } else if (event.getAction() == MotionEvent.ACTION_UP) { - mVelocityTracker.computeCurrentVelocity(1000); - - float yVel = mVelocityTracker.getYVelocity(); - boolean negative = yVel < 0; - - float xVel = mVelocityTracker.getXVelocity(); - if (xVel < 0) { - xVel = -xVel; - } - if (xVel > 150.0f) { - xVel = 150.0f; // limit how much we care about the x axis - } - - float vel = (float)Math.hypot(yVel, xVel); - if (negative) { - vel = -vel; - } - - performFling((int)event.getRawY(), vel, false); - } - - } - return false; - } - - private class Launcher implements View.OnClickListener { - private PendingIntent mIntent; - private String mPkg; - private String mTag; - private int mId; - - Launcher(PendingIntent intent, String pkg, String tag, int id) { - mIntent = intent; - mPkg = pkg; - mTag = tag; - mId = id; - } - - public void onClick(View v) { - try { - // The intent we are sending is for the application, which - // won't have permission to immediately start an activity after - // the user switches to home. We know it is safe to do at this - // point, so make sure new activity switches are now allowed. - ActivityManagerNative.getDefault().resumeAppSwitches(); - } catch (RemoteException e) { - } - int[] pos = new int[2]; - v.getLocationOnScreen(pos); - Intent overlay = new Intent(); - overlay.setSourceBounds( - new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); - try { - mIntent.send(mContext, 0, overlay); - mNotificationCallbacks.onNotificationClick(mPkg, mTag, mId); - } catch (PendingIntent.CanceledException e) { - // the stack trace isn't very helpful here. Just log the exception message. - Slog.w(TAG, "Sending contentIntent failed: " + e); - } - deactivate(); - } - } - - private class MyTicker extends Ticker { - MyTicker(Context context, StatusBarView sb) { - super(context, sb); - } - - @Override - void tickerStarting() { - mTicking = true; - mIcons.setVisibility(View.GONE); - mTickerView.setVisibility(View.VISIBLE); - mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_up_in, null)); - mIcons.startAnimation(loadAnim(com.android.internal.R.anim.push_up_out, null)); - if (mExpandedVisible) { - setDateViewVisibility(false, com.android.internal.R.anim.push_up_out); - } - } - - @Override - void tickerDone() { - mIcons.setVisibility(View.VISIBLE); - mTickerView.setVisibility(View.GONE); - mIcons.startAnimation(loadAnim(com.android.internal.R.anim.push_down_in, null)); - mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_down_out, - mTickingDoneListener)); - if (mExpandedVisible) { - setDateViewVisibility(true, com.android.internal.R.anim.push_down_in); - } - } - - void tickerHalting() { - mIcons.setVisibility(View.VISIBLE); - mTickerView.setVisibility(View.GONE); - mIcons.startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null)); - mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.fade_out, - mTickingDoneListener)); - if (mExpandedVisible) { - setDateViewVisibility(true, com.android.internal.R.anim.fade_in); - } - } - } - - Animation.AnimationListener mTickingDoneListener = new Animation.AnimationListener() {; - public void onAnimationEnd(Animation animation) { - mTicking = false; - } - public void onAnimationRepeat(Animation animation) { - } - public void onAnimationStart(Animation animation) { - } - }; - - private Animation loadAnim(int id, Animation.AnimationListener listener) { - Animation anim = AnimationUtils.loadAnimation(mContext, id); - if (listener != null) { - anim.setAnimationListener(listener); - } - return anim; - } - - public String viewInfo(View v) { - return "(" + v.getLeft() + "," + v.getTop() + ")(" + v.getRight() + "," + v.getBottom() - + " " + v.getWidth() + "x" + v.getHeight() + ")"; - } - - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) - != PackageManager.PERMISSION_GRANTED) { - pw.println("Permission Denial: can't dump StatusBar from from pid=" - + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid()); - return; - } - - synchronized (mQueueLock) { - pw.println("Current Status Bar state:"); - pw.println(" mExpanded=" + mExpanded - + ", mExpandedVisible=" + mExpandedVisible); - pw.println(" mTicking=" + mTicking); - pw.println(" mTracking=" + mTracking); - pw.println(" mAnimating=" + mAnimating - + ", mAnimY=" + mAnimY + ", mAnimVel=" + mAnimVel - + ", mAnimAccel=" + mAnimAccel); - pw.println(" mCurAnimationTime=" + mCurAnimationTime - + " mAnimLastTime=" + mAnimLastTime); - pw.println(" mDisplayHeight=" + mDisplayHeight - + " mAnimatingReveal=" + mAnimatingReveal - + " mViewDelta=" + mViewDelta); - pw.println(" mDisplayHeight=" + mDisplayHeight); - final int N = mQueue.size(); - pw.println(" mQueue.size=" + N); - for (int i=0; i<N; i++) { - PendingOp op = mQueue.get(i); - pw.println(" [" + i + "] key=" + op.key + " code=" + op.code + " visible=" - + op.visible); - pw.println(" iconData=" + op.iconData); - pw.println(" notificationData=" + op.notificationData); - } - pw.println(" mExpandedParams: " + mExpandedParams); - pw.println(" mExpandedView: " + viewInfo(mExpandedView)); - pw.println(" mExpandedDialog: " + mExpandedDialog); - pw.println(" mTrackingParams: " + mTrackingParams); - pw.println(" mTrackingView: " + viewInfo(mTrackingView)); - pw.println(" mOngoingTitle: " + viewInfo(mOngoingTitle)); - pw.println(" mOngoingItems: " + viewInfo(mOngoingItems)); - pw.println(" mLatestTitle: " + viewInfo(mLatestTitle)); - pw.println(" mLatestItems: " + viewInfo(mLatestItems)); - pw.println(" mNoNotificationsTitle: " + viewInfo(mNoNotificationsTitle)); - pw.println(" mCloseView: " + viewInfo(mCloseView)); - pw.println(" mTickerView: " + viewInfo(mTickerView)); - pw.println(" mScrollView: " + viewInfo(mScrollView) - + " scroll " + mScrollView.getScrollX() + "," + mScrollView.getScrollY()); - pw.println("mNotificationLinearLayout: " + viewInfo(mNotificationLinearLayout)); - } - synchronized (mIconMap) { - final int N = mIconMap.size(); - pw.println(" mIconMap.size=" + N); - Set<IBinder> keys = mIconMap.keySet(); - int i=0; - for (IBinder key: keys) { - StatusBarIcon icon = mIconMap.get(key); - pw.println(" [" + i + "] key=" + key); - pw.println(" data=" + icon.mData); - i++; - } - } - synchronized (mNotificationData) { - int N = mNotificationData.ongoingCount(); - pw.println(" ongoingCount.size=" + N); - for (int i=0; i<N; i++) { - StatusBarNotification n = mNotificationData.getOngoing(i); - pw.println(" [" + i + "] key=" + n.key + " view=" + n.view); - pw.println(" data=" + n.data); - } - N = mNotificationData.latestCount(); - pw.println(" ongoingCount.size=" + N); - for (int i=0; i<N; i++) { - StatusBarNotification n = mNotificationData.getLatest(i); - pw.println(" [" + i + "] key=" + n.key + " view=" + n.view); - pw.println(" data=" + n.data); - } - } - synchronized (mDisableRecords) { - final int N = mDisableRecords.size(); - pw.println(" mDisableRecords.size=" + N - + " mDisabled=0x" + Integer.toHexString(mDisabled)); - for (int i=0; i<N; i++) { - DisableRecord tok = mDisableRecords.get(i); - pw.println(" [" + i + "] what=0x" + Integer.toHexString(tok.what) - + " pkg=" + tok.pkg + " token=" + tok.token); - } - } - - if (false) { - pw.println("see the logcat for a dump of the views we have created."); - // must happen on ui thread - mHandler.post(new Runnable() { - public void run() { - mStatusBarView.getLocationOnScreen(mAbsPos); - Slog.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] - + ") " + mStatusBarView.getWidth() + "x" - + mStatusBarView.getHeight()); - mStatusBarView.debug(); - - mExpandedView.getLocationOnScreen(mAbsPos); - Slog.d(TAG, "mExpandedView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] - + ") " + mExpandedView.getWidth() + "x" - + mExpandedView.getHeight()); - mExpandedView.debug(); - - mTrackingView.getLocationOnScreen(mAbsPos); - Slog.d(TAG, "mTrackingView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] - + ") " + mTrackingView.getWidth() + "x" - + mTrackingView.getHeight()); - mTrackingView.debug(); - } - }); - } - } - - void onBarViewAttached() { - WindowManager.LayoutParams lp; - int pixelFormat; - Drawable bg; - - /// ---------- Tracking View -------------- - pixelFormat = PixelFormat.RGBX_8888; - bg = mTrackingView.getBackground(); - if (bg != null) { - pixelFormat = bg.getOpacity(); - } - - lp = new WindowManager.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, - WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS - | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, - pixelFormat); -// lp.token = mStatusBarView.getWindowToken(); - lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL; - lp.setTitle("TrackingView"); - lp.y = mTrackingPosition; - mTrackingParams = lp; - - WindowManagerImpl.getDefault().addView(mTrackingView, lp); - } - - void onTrackingViewAttached() { - WindowManager.LayoutParams lp; - int pixelFormat; - Drawable bg; - - /// ---------- Expanded View -------------- - pixelFormat = PixelFormat.TRANSLUCENT; - - final int disph = mDisplay.getHeight(); - lp = mExpandedDialog.getWindow().getAttributes(); - lp.width = ViewGroup.LayoutParams.MATCH_PARENT; - lp.height = getExpandedHeight(); - lp.x = 0; - mTrackingPosition = lp.y = -disph; // sufficiently large negative - lp.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL; - lp.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_DITHER - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - lp.format = pixelFormat; - lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL; - lp.setTitle("StatusBarExpanded"); - mExpandedDialog.getWindow().setAttributes(lp); - mExpandedDialog.getWindow().setFormat(pixelFormat); - mExpandedParams = lp; - - mExpandedDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); - mExpandedDialog.setContentView(mExpandedView, - new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT)); - mExpandedDialog.getWindow().setBackgroundDrawable(null); - mExpandedDialog.show(); - FrameLayout hack = (FrameLayout)mExpandedView.getParent(); - } - - void setDateViewVisibility(boolean visible, int anim) { - mDateView.setUpdates(visible); - mDateView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); - mDateView.startAnimation(loadAnim(anim, null)); - } - - void setNotificationIconVisibility(boolean visible, int anim) { - int old = mNotificationIcons.getVisibility(); - int v = visible ? View.VISIBLE : View.INVISIBLE; - if (old != v) { - mNotificationIcons.setVisibility(v); - mNotificationIcons.startAnimation(loadAnim(anim, null)); - } - } - - void updateExpandedViewPos(int expandedPosition) { - if (SPEW) { - Slog.d(TAG, "updateExpandedViewPos before expandedPosition=" + expandedPosition - + " mTrackingParams.y=" + mTrackingParams.y - + " mTrackingPosition=" + mTrackingPosition); - } - - int h = mStatusBarView.getHeight(); - int disph = mDisplay.getHeight(); - - // If the expanded view is not visible, make sure they're still off screen. - // Maybe the view was resized. - if (!mExpandedVisible) { - if (mTrackingView != null) { - mTrackingPosition = -disph; - if (mTrackingParams != null) { - mTrackingParams.y = mTrackingPosition; - WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams); - } - } - if (mExpandedParams != null) { - mExpandedParams.y = -disph; - mExpandedDialog.getWindow().setAttributes(mExpandedParams); - } - return; - } - - // tracking view... - int pos; - if (expandedPosition == EXPANDED_FULL_OPEN) { - pos = h; - } - else if (expandedPosition == EXPANDED_LEAVE_ALONE) { - pos = mTrackingPosition; - } - else { - if (expandedPosition <= disph) { - pos = expandedPosition; - } else { - pos = disph; - } - pos -= disph-h; - } - mTrackingPosition = mTrackingParams.y = pos; - mTrackingParams.height = disph-h; - WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams); - - if (mExpandedParams != null) { - mCloseView.getLocationInWindow(mPositionTmp); - final int closePos = mPositionTmp[1]; - - mExpandedContents.getLocationInWindow(mPositionTmp); - final int contentsBottom = mPositionTmp[1] + mExpandedContents.getHeight(); - - mExpandedParams.y = pos + mTrackingView.getHeight() - - (mTrackingParams.height-closePos) - contentsBottom; - int max = h; - if (mExpandedParams.y > max) { - mExpandedParams.y = max; - } - int min = mTrackingPosition; - if (mExpandedParams.y < min) { - mExpandedParams.y = min; - } - - boolean visible = (mTrackingPosition + mTrackingView.getHeight()) > h; - if (!visible) { - // if the contents aren't visible, move the expanded view way off screen - // because the window itself extends below the content view. - mExpandedParams.y = -disph; - } - panelSlightlyVisible(visible); - mExpandedDialog.getWindow().setAttributes(mExpandedParams); - } - - if (SPEW) { - Slog.d(TAG, "updateExpandedViewPos after expandedPosition=" + expandedPosition - + " mTrackingParams.y=" + mTrackingParams.y - + " mTrackingPosition=" + mTrackingPosition - + " mExpandedParams.y=" + mExpandedParams.y - + " mExpandedParams.height=" + mExpandedParams.height); - } - } - - int getExpandedHeight() { - return mDisplay.getHeight() - mStatusBarView.getHeight() - mCloseView.getHeight(); - } - - void updateExpandedHeight() { - if (mExpandedView != null) { - mExpandedParams.height = getExpandedHeight(); - mExpandedDialog.getWindow().setAttributes(mExpandedParams); - } - } - - /** - * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. - * This was added last-minute and is inconsistent with the way the rest of the notifications - * are handled, because the notification isn't really cancelled. The lights are just - * turned off. If any other notifications happen, the lights will turn back on. Steve says - * this is what he wants. (see bug 1131461) - */ - private boolean mPanelSlightlyVisible; - void panelSlightlyVisible(boolean visible) { - if (mPanelSlightlyVisible != visible) { - mPanelSlightlyVisible = visible; - if (visible) { - // tell the notification manager to turn off the lights. - mNotificationCallbacks.onPanelRevealed(); - } - } - } - - void performDisableActions(int net) { - int old = mDisabled; - int diff = net ^ old; - mDisabled = net; - - // act accordingly - if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { - if ((net & StatusBarManager.DISABLE_EXPAND) != 0) { - Slog.d(TAG, "DISABLE_EXPAND: yes"); - animateCollapse(); - } - } - if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { - if ((net & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { - Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes"); - if (mTicking) { - mNotificationIcons.setVisibility(View.INVISIBLE); - mTicker.halt(); - } else { - setNotificationIconVisibility(false, com.android.internal.R.anim.fade_out); - } - } else { - Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no"); - if (!mExpandedVisible) { - setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in); - } - } - } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { - if (mTicking && (net & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { - mTicker.halt(); - } - } - } - - private View.OnClickListener mClearButtonListener = new View.OnClickListener() { - public void onClick(View v) { - mNotificationCallbacks.onClearAll(); - addPendingOp(OP_EXPAND, null, false); - } - }; - - private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) - || Intent.ACTION_SCREEN_OFF.equals(action)) { - deactivate(); - } - else if (Telephony.Intents.SPN_STRINGS_UPDATED_ACTION.equals(action)) { - updateNetworkName(intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_SPN, false), - intent.getStringExtra(Telephony.Intents.EXTRA_SPN), - intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_PLMN, false), - intent.getStringExtra(Telephony.Intents.EXTRA_PLMN)); - } - else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { - updateResources(); - } - } - }; - - void updateNetworkName(boolean showSpn, String spn, boolean showPlmn, String plmn) { - if (false) { - Slog.d(TAG, "updateNetworkName showSpn=" + showSpn + " spn=" + spn - + " showPlmn=" + showPlmn + " plmn=" + plmn); - } - boolean something = false; - if (showPlmn) { - mPlmnLabel.setVisibility(View.VISIBLE); - if (plmn != null) { - mPlmnLabel.setText(plmn); - } else { - mPlmnLabel.setText(R.string.lockscreen_carrier_default); - } - } else { - mPlmnLabel.setText(""); - mPlmnLabel.setVisibility(View.GONE); - } - if (showSpn && spn != null) { - mSpnLabel.setText(spn); - mSpnLabel.setVisibility(View.VISIBLE); - something = true; - } else { - mSpnLabel.setText(""); - mSpnLabel.setVisibility(View.GONE); - } - } - - /** - * Reload some of our resources when the configuration changes. - * - * We don't reload everything when the configuration changes -- we probably - * should, but getting that smooth is tough. Someday we'll fix that. In the - * meantime, just update the things that we know change. - */ - void updateResources() { - Resources res = mContext.getResources(); - - mClearButton.setText(mContext.getText(R.string.status_bar_clear_all_button)); - mOngoingTitle.setText(mContext.getText(R.string.status_bar_ongoing_events_title)); - mLatestTitle.setText(mContext.getText(R.string.status_bar_latest_events_title)); - mNoNotificationsTitle.setText(mContext.getText(R.string.status_bar_no_notifications_title)); - - mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore); - - if (false) Slog.v(TAG, "updateResources"); - } - - // - // tracing - // - - void postStartTracing() { - mHandler.postDelayed(mStartTracing, 3000); - } - - void vibrate() { - android.os.Vibrator vib = (android.os.Vibrator)mContext.getSystemService( - Context.VIBRATOR_SERVICE); - vib.vibrate(250); - } - - Runnable mStartTracing = new Runnable() { - public void run() { - vibrate(); - SystemClock.sleep(250); - Slog.d(TAG, "startTracing"); - android.os.Debug.startMethodTracing("/data/statusbar-traces/trace"); - mHandler.postDelayed(mStopTracing, 10000); - } - }; - - Runnable mStopTracing = new Runnable() { - public void run() { - android.os.Debug.stopMethodTracing(); - Slog.d(TAG, "stopTracing"); - vibrate(); - } - }; - - class UninstallReceiver extends BroadcastReceiver { - public UninstallReceiver() { - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_PACKAGE_REMOVED); - filter.addAction(Intent.ACTION_PACKAGE_RESTARTED); - filter.addDataScheme("package"); - mContext.registerReceiver(this, filter); - IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); - mContext.registerReceiver(this, sdFilter); - } - - @Override - public void onReceive(Context context, Intent intent) { - String pkgList[] = null; - if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(intent.getAction())) { - pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - } else { - Uri data = intent.getData(); - if (data != null) { - String pkg = data.getSchemeSpecificPart(); - if (pkg != null) { - pkgList = new String[]{pkg}; - } - } - } - ArrayList<StatusBarNotification> list = null; - if (pkgList != null) { - synchronized (StatusBarService.this) { - for (String pkg : pkgList) { - list = mNotificationData.notificationsForPackage(pkg); - } - } - } - - if (list != null) { - final int N = list.size(); - for (int i=0; i<N; i++) { - removeIcon(list.get(i).key); - } - } - } - } -} diff --git a/services/java/com/android/server/status/StatusBarView.java b/services/java/com/android/server/status/StatusBarView.java deleted file mode 100644 index 5e1f572..0000000 --- a/services/java/com/android/server/status/StatusBarView.java +++ /dev/null @@ -1,192 +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.status; - -import android.content.Context; -import android.content.res.Configuration; -import android.graphics.Canvas; -import android.os.SystemClock; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.widget.FrameLayout; - -import com.android.internal.R; - -public class StatusBarView extends FrameLayout { - private static final String TAG = "StatusBarView"; - - static final int DIM_ANIM_TIME = 400; - - StatusBarService mService; - boolean mTracking; - int mStartX, mStartY; - ViewGroup mNotificationIcons; - ViewGroup mStatusIcons; - View mDate; - FixedSizeDrawable mBackground; - - boolean mNightMode = false; - int mStartAlpha = 0, mEndAlpha = 0; - long mEndTime = 0; - - public StatusBarView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mNotificationIcons = (ViewGroup)findViewById(R.id.notificationIcons); - mStatusIcons = (ViewGroup)findViewById(R.id.statusIcons); - mDate = findViewById(R.id.date); - - mBackground = new FixedSizeDrawable(mDate.getBackground()); - mBackground.setFixedBounds(0, 0, 0, 0); - mDate.setBackgroundDrawable(mBackground); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mService.onBarViewAttached(); - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - boolean nightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) - == Configuration.UI_MODE_NIGHT_YES; - if (mNightMode != nightMode) { - mNightMode = nightMode; - mStartAlpha = getCurAlpha(); - mEndAlpha = mNightMode ? 0x80 : 0x00; - mEndTime = SystemClock.uptimeMillis() + DIM_ANIM_TIME; - invalidate(); - } - } - - int getCurAlpha() { - long time = SystemClock.uptimeMillis(); - if (time > mEndTime) { - return mEndAlpha; - } - return mEndAlpha - - (int)(((mEndAlpha-mStartAlpha) * (mEndTime-time) / DIM_ANIM_TIME)); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - mService.updateExpandedViewPos(StatusBarService.EXPANDED_LEAVE_ALONE); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - - // put the date date view quantized to the icons - int oldDateRight = mDate.getRight(); - int newDateRight; - - newDateRight = getDateSize(mNotificationIcons, oldDateRight, - getViewOffset(mNotificationIcons)); - if (newDateRight < 0) { - int offset = getViewOffset(mStatusIcons); - if (oldDateRight < offset) { - newDateRight = oldDateRight; - } else { - newDateRight = getDateSize(mStatusIcons, oldDateRight, offset); - if (newDateRight < 0) { - newDateRight = r; - } - } - } - int max = r - getPaddingRight(); - if (newDateRight > max) { - newDateRight = max; - } - - mDate.layout(mDate.getLeft(), mDate.getTop(), newDateRight, mDate.getBottom()); - mBackground.setFixedBounds(-mDate.getLeft(), -mDate.getTop(), (r-l), (b-t)); - } - - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - int alpha = getCurAlpha(); - if (alpha != 0) { - canvas.drawARGB(alpha, 0, 0, 0); - } - if (alpha != mEndAlpha) { - invalidate(); - } - } - - /** - * Gets the left position of v in this view. Throws if v is not - * a child of this. - */ - private int getViewOffset(View v) { - int offset = 0; - while (v != this) { - offset += v.getLeft(); - ViewParent p = v.getParent(); - if (v instanceof View) { - v = (View)p; - } else { - throw new RuntimeException(v + " is not a child of " + this); - } - } - return offset; - } - - private int getDateSize(ViewGroup g, int w, int offset) { - final int N = g.getChildCount(); - for (int i=0; i<N; i++) { - View v = g.getChildAt(i); - int l = v.getLeft() + offset; - int r = v.getRight() + offset; - if (w >= l && w <= r) { - return r; - } - } - return -1; - } - - /** - * Ensure that, if there is no target under us to receive the touch, - * that we process it ourself. This makes sure that onInterceptTouchEvent() - * is always called for the entire gesture. - */ - @Override - public boolean onTouchEvent(MotionEvent event) { - if (event.getAction() != MotionEvent.ACTION_DOWN) { - mService.interceptTouchEvent(event); - } - return true; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent event) { - return mService.interceptTouchEvent(event) - ? true : super.onInterceptTouchEvent(event); - } -} - diff --git a/services/java/com/android/server/status/StorageNotification.java b/services/java/com/android/server/status/StorageNotification.java deleted file mode 100644 index 8da8cd3..0000000 --- a/services/java/com/android/server/status/StorageNotification.java +++ /dev/null @@ -1,395 +0,0 @@ -/* - * Copyright (C) 2010 Google Inc. - * - * 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.status; - -import android.app.Activity; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.os.Bundle; -import android.os.Environment; -import android.os.Handler; -import android.os.storage.IMountService; -import android.os.Message; -import android.os.ServiceManager; -import android.os.storage.StorageEventListener; -import android.os.storage.StorageManager; -import android.os.storage.StorageResultCode; -import android.provider.Settings; -import android.util.Slog; -import android.view.View; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -public class StorageNotification extends StorageEventListener { - private static final String TAG = "StorageNotification"; - - private static final boolean POP_UMS_ACTIVITY_ON_CONNECT = true; - - /** - * Binder context for this service - */ - private Context mContext; - - /** - * The notification that is shown when a USB mass storage host - * is connected. - * <p> - * This is lazily created, so use {@link #setUsbStorageNotification()}. - */ - private Notification mUsbStorageNotification; - - /** - * The notification that is shown when the following media events occur: - * - Media is being checked - * - Media is blank (or unknown filesystem) - * - Media is corrupt - * - Media is safe to unmount - * - Media is missing - * <p> - * This is lazily created, so use {@link #setMediaStorageNotification()}. - */ - private Notification mMediaStorageNotification; - private boolean mUmsAvailable; - private StorageManager mStorageManager; - - public StorageNotification(Context context) { - mContext = context; - - mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); - mUmsAvailable = mStorageManager.isUsbMassStorageConnected(); - Slog.d(TAG, String.format( "Startup with UMS connection %s (media state %s)", mUmsAvailable, - Environment.getExternalStorageState())); - } - - /* - * @override com.android.os.storage.StorageEventListener - */ - @Override - public void onUsbMassStorageConnectionChanged(boolean connected) { - mUmsAvailable = connected; - /* - * Even though we may have a UMS host connected, we the SD card - * may not be in a state for export. - */ - String st = Environment.getExternalStorageState(); - - Slog.i(TAG, String.format("UMS connection changed to %s (media state %s)", connected, st)); - - if (connected && (st.equals( - Environment.MEDIA_REMOVED) || st.equals(Environment.MEDIA_CHECKING))) { - /* - * No card or card being checked = don't display - */ - connected = false; - } - updateUsbMassStorageNotification(connected); - } - - /* - * @override com.android.os.storage.StorageEventListener - */ - @Override - public void onStorageStateChanged(String path, String oldState, String newState) { - Slog.i(TAG, String.format( - "Media {%s} state changed from {%s} -> {%s}", path, oldState, newState)); - if (newState.equals(Environment.MEDIA_SHARED)) { - /* - * Storage is now shared. Modify the UMS notification - * for stopping UMS. - */ - Intent intent = new Intent(); - intent.setClass(mContext, com.android.server.status.UsbStorageActivity.class); - PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); - setUsbStorageNotification( - com.android.internal.R.string.usb_storage_stop_notification_title, - com.android.internal.R.string.usb_storage_stop_notification_message, - com.android.internal.R.drawable.stat_sys_warning, false, true, pi); - } else if (newState.equals(Environment.MEDIA_CHECKING)) { - /* - * Storage is now checking. Update media notification and disable - * UMS notification. - */ - setMediaStorageNotification( - com.android.internal.R.string.ext_media_checking_notification_title, - com.android.internal.R.string.ext_media_checking_notification_message, - com.android.internal.R.drawable.stat_notify_sdcard_prepare, true, false, null); - updateUsbMassStorageNotification(false); - } else if (newState.equals(Environment.MEDIA_MOUNTED)) { - /* - * Storage is now mounted. Dismiss any media notifications, - * and enable UMS notification if connected. - */ - setMediaStorageNotification(0, 0, 0, false, false, null); - updateUsbMassStorageNotification(mUmsAvailable); - } else if (newState.equals(Environment.MEDIA_UNMOUNTED)) { - /* - * Storage is now unmounted. We may have been unmounted - * because the user is enabling/disabling UMS, in which case we don't - * want to display the 'safe to unmount' notification. - */ - if (!mStorageManager.isUsbMassStorageEnabled()) { - if (oldState.equals(Environment.MEDIA_SHARED)) { - /* - * The unmount was due to UMS being enabled. Dismiss any - * media notifications, and enable UMS notification if connected - */ - setMediaStorageNotification(0, 0, 0, false, false, null); - updateUsbMassStorageNotification(mUmsAvailable); - } else { - /* - * Show safe to unmount media notification, and enable UMS - * notification if connected. - */ - setMediaStorageNotification( - com.android.internal.R.string.ext_media_safe_unmount_notification_title, - com.android.internal.R.string.ext_media_safe_unmount_notification_message, - com.android.internal.R.drawable.stat_notify_sdcard, true, true, null); - updateUsbMassStorageNotification(mUmsAvailable); - } - } else { - /* - * The unmount was due to UMS being enabled. Dismiss any - * media notifications, and disable the UMS notification - */ - setMediaStorageNotification(0, 0, 0, false, false, null); - updateUsbMassStorageNotification(false); - } - } else if (newState.equals(Environment.MEDIA_NOFS)) { - /* - * Storage has no filesystem. Show blank media notification, - * and enable UMS notification if connected. - */ - Intent intent = new Intent(); - intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class); - PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); - - setMediaStorageNotification( - com.android.internal.R.string.ext_media_nofs_notification_title, - com.android.internal.R.string.ext_media_nofs_notification_message, - com.android.internal.R.drawable.stat_notify_sdcard_usb, true, false, pi); - updateUsbMassStorageNotification(mUmsAvailable); - } else if (newState.equals(Environment.MEDIA_UNMOUNTABLE)) { - /* - * Storage is corrupt. Show corrupt media notification, - * and enable UMS notification if connected. - */ - Intent intent = new Intent(); - intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class); - PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); - - setMediaStorageNotification( - com.android.internal.R.string.ext_media_unmountable_notification_title, - com.android.internal.R.string.ext_media_unmountable_notification_message, - com.android.internal.R.drawable.stat_notify_sdcard_usb, true, false, pi); - updateUsbMassStorageNotification(mUmsAvailable); - } else if (newState.equals(Environment.MEDIA_REMOVED)) { - /* - * Storage has been removed. Show nomedia media notification, - * and disable UMS notification regardless of connection state. - */ - setMediaStorageNotification( - com.android.internal.R.string.ext_media_nomedia_notification_title, - com.android.internal.R.string.ext_media_nomedia_notification_message, - com.android.internal.R.drawable.stat_notify_sdcard_usb, - true, false, null); - updateUsbMassStorageNotification(false); - } else if (newState.equals(Environment.MEDIA_BAD_REMOVAL)) { - /* - * Storage has been removed unsafely. Show bad removal media notification, - * and disable UMS notification regardless of connection state. - */ - setMediaStorageNotification( - com.android.internal.R.string.ext_media_badremoval_notification_title, - com.android.internal.R.string.ext_media_badremoval_notification_message, - com.android.internal.R.drawable.stat_sys_warning, - true, true, null); - updateUsbMassStorageNotification(false); - } else { - Slog.w(TAG, String.format("Ignoring unknown state {%s}", newState)); - } - } - - /** - * Update the state of the USB mass storage notification - */ - void updateUsbMassStorageNotification(boolean available) { - - if (available) { - Intent intent = new Intent(); - intent.setClass(mContext, com.android.server.status.UsbStorageActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - final boolean adbOn = 1 == Settings.Secure.getInt( - mContext.getContentResolver(), - Settings.Secure.ADB_ENABLED, - 0); - - PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); - setUsbStorageNotification( - com.android.internal.R.string.usb_storage_notification_title, - com.android.internal.R.string.usb_storage_notification_message, - com.android.internal.R.drawable.stat_sys_data_usb, - false, true, pi); - - if (POP_UMS_ACTIVITY_ON_CONNECT && !adbOn) { - // We assume that developers don't want to enable UMS every - // time they attach a device to a USB host. The average user, - // however, is looking to charge the phone (in which case this - // is harmless) or transfer files (in which case this coaches - // the user about how to complete that task and saves several - // steps). - mContext.startActivity(intent); - } - } else { - setUsbStorageNotification(0, 0, 0, false, false, null); - } - } - - /** - * Sets the USB storage notification. - */ - private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon, - boolean sound, boolean visible, PendingIntent pi) { - - if (!visible && mUsbStorageNotification == null) { - return; - } - - NotificationManager notificationManager = (NotificationManager) mContext - .getSystemService(Context.NOTIFICATION_SERVICE); - - if (notificationManager == null) { - return; - } - - if (visible) { - Resources r = Resources.getSystem(); - CharSequence title = r.getText(titleId); - CharSequence message = r.getText(messageId); - - if (mUsbStorageNotification == null) { - mUsbStorageNotification = new Notification(); - mUsbStorageNotification.icon = icon; - mUsbStorageNotification.when = 0; - } - - if (sound) { - mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND; - } else { - mUsbStorageNotification.defaults &= ~Notification.DEFAULT_SOUND; - } - - mUsbStorageNotification.flags = Notification.FLAG_ONGOING_EVENT; - - mUsbStorageNotification.tickerText = title; - if (pi == null) { - Intent intent = new Intent(); - pi = PendingIntent.getBroadcast(mContext, 0, intent, 0); - } - - mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi); - } - - final int notificationId = mUsbStorageNotification.icon; - if (visible) { - notificationManager.notify(notificationId, mUsbStorageNotification); - } else { - notificationManager.cancel(notificationId); - } - } - - private synchronized boolean getMediaStorageNotificationDismissable() { - if ((mMediaStorageNotification != null) && - ((mMediaStorageNotification.flags & Notification.FLAG_AUTO_CANCEL) == - Notification.FLAG_AUTO_CANCEL)) - return true; - - return false; - } - - /** - * Sets the media storage notification. - */ - private synchronized void setMediaStorageNotification(int titleId, int messageId, int icon, boolean visible, - boolean dismissable, PendingIntent pi) { - - if (!visible && mMediaStorageNotification == null) { - return; - } - - NotificationManager notificationManager = (NotificationManager) mContext - .getSystemService(Context.NOTIFICATION_SERVICE); - - if (notificationManager == null) { - return; - } - - if (mMediaStorageNotification != null && visible) { - /* - * Dismiss the previous notification - we're about to - * re-use it. - */ - final int notificationId = mMediaStorageNotification.icon; - notificationManager.cancel(notificationId); - } - - if (visible) { - Resources r = Resources.getSystem(); - CharSequence title = r.getText(titleId); - CharSequence message = r.getText(messageId); - - if (mMediaStorageNotification == null) { - mMediaStorageNotification = new Notification(); - mMediaStorageNotification.when = 0; - } - - mMediaStorageNotification.defaults &= ~Notification.DEFAULT_SOUND; - - if (dismissable) { - mMediaStorageNotification.flags = Notification.FLAG_AUTO_CANCEL; - } else { - mMediaStorageNotification.flags = Notification.FLAG_ONGOING_EVENT; - } - - mMediaStorageNotification.tickerText = title; - if (pi == null) { - Intent intent = new Intent(); - pi = PendingIntent.getBroadcast(mContext, 0, intent, 0); - } - - mMediaStorageNotification.icon = icon; - mMediaStorageNotification.setLatestEventInfo(mContext, title, message, pi); - } - - final int notificationId = mMediaStorageNotification.icon; - if (visible) { - notificationManager.notify(notificationId, mMediaStorageNotification); - } else { - notificationManager.cancel(notificationId); - } - } -} diff --git a/services/java/com/android/server/status/Ticker.java b/services/java/com/android/server/status/Ticker.java deleted file mode 100644 index e47185b..0000000 --- a/services/java/com/android/server/status/Ticker.java +++ /dev/null @@ -1,246 +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.status; - -import com.android.internal.R; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.text.StaticLayout; -import android.text.Layout.Alignment; -import android.text.TextPaint; -import android.text.TextUtils; -import android.util.Slog; -import android.view.View; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.widget.TextSwitcher; -import android.widget.TextView; -import android.widget.ImageSwitcher; - -import java.util.ArrayList; - - -abstract class Ticker { - private static final int TICKER_SEGMENT_DELAY = 3000; - - private final class Segment { - NotificationData notificationData; - Drawable icon; - CharSequence text; - int current; - int next; - boolean first; - - StaticLayout getLayout(CharSequence substr) { - int w = mTextSwitcher.getWidth() - mTextSwitcher.getPaddingLeft() - - mTextSwitcher.getPaddingRight(); - return new StaticLayout(substr, mPaint, w, Alignment.ALIGN_NORMAL, 1, 0, true); - } - - CharSequence rtrim(CharSequence substr, int start, int end) { - while (end > start && !TextUtils.isGraphic(substr.charAt(end-1))) { - end--; - } - if (end > start) { - return substr.subSequence(start, end); - } - return null; - } - - /** returns null if there is no more text */ - CharSequence getText() { - if (this.current > this.text.length()) { - return null; - } - CharSequence substr = this.text.subSequence(this.current, this.text.length()); - StaticLayout l = getLayout(substr); - int lineCount = l.getLineCount(); - if (lineCount > 0) { - int start = l.getLineStart(0); - int end = l.getLineEnd(0); - this.next = this.current + end; - return rtrim(substr, start, end); - } else { - throw new RuntimeException("lineCount=" + lineCount + " current=" + current + - " text=" + text); - } - } - - /** returns null if there is no more text */ - CharSequence advance() { - this.first = false; - int index = this.next; - final int len = this.text.length(); - while (index < len && !TextUtils.isGraphic(this.text.charAt(index))) { - index++; - } - if (index >= len) { - return null; - } - - CharSequence substr = this.text.subSequence(index, this.text.length()); - StaticLayout l = getLayout(substr); - final int lineCount = l.getLineCount(); - int i; - for (i=0; i<lineCount; i++) { - int start = l.getLineStart(i); - int end = l.getLineEnd(i); - if (i == lineCount-1) { - this.next = len; - } else { - this.next = index + l.getLineStart(i+1); - } - CharSequence result = rtrim(substr, start, end); - if (result != null) { - this.current = index + start; - return result; - } - } - this.current = len; - return null; - } - - Segment(NotificationData n, Drawable icon, CharSequence text) { - this.notificationData = n; - this.icon = icon; - this.text = text; - int index = 0; - final int len = text.length(); - while (index < len && !TextUtils.isGraphic(text.charAt(index))) { - index++; - } - this.current = index; - this.next = index; - this.first = true; - } - }; - - private Handler mHandler = new Handler(); - private ArrayList<Segment> mSegments = new ArrayList(); - private TextPaint mPaint; - private View mTickerView; - private ImageSwitcher mIconSwitcher; - private TextSwitcher mTextSwitcher; - - Ticker(Context context, StatusBarView sb) { - mTickerView = sb.findViewById(R.id.ticker); - - mIconSwitcher = (ImageSwitcher)sb.findViewById(R.id.tickerIcon); - mIconSwitcher.setInAnimation( - AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_in)); - mIconSwitcher.setOutAnimation( - AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_out)); - - mTextSwitcher = (TextSwitcher)sb.findViewById(R.id.tickerText); - mTextSwitcher.setInAnimation( - AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_in)); - mTextSwitcher.setOutAnimation( - AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_out)); - - // Copy the paint style of one of the TextSwitchers children to use later for measuring - TextView text = (TextView)mTextSwitcher.getChildAt(0); - mPaint = text.getPaint(); - } - - void addEntry(NotificationData n, Drawable icon, CharSequence text) { - int initialCount = mSegments.size(); - - Segment newSegment = new Segment(n, icon, text); - - // prune out any preexisting ones for this notification, but not the current one. - // let that finish, even if it's the same id - for (int i=1; i<initialCount; i++) { - Segment seg = mSegments.get(i); - if (n.id == seg.notificationData.id && n.pkg.equals(seg.notificationData.pkg)) { - // just update that one to use this new data instead - mSegments.set(i, newSegment); - // and since we know initialCount != 0, just return - return ; - } - } - - mSegments.add(newSegment); - - if (initialCount == 0 && mSegments.size() > 0) { - Segment seg = mSegments.get(0); - seg.first = false; - - mIconSwitcher.setAnimateFirstView(false); - mIconSwitcher.reset(); - mIconSwitcher.setImageDrawable(seg.icon); - - mTextSwitcher.setAnimateFirstView(false); - mTextSwitcher.reset(); - mTextSwitcher.setText(seg.getText()); - - tickerStarting(); - scheduleAdvance(); - } - } - - void halt() { - mHandler.removeCallbacks(mAdvanceTicker); - mSegments.clear(); - tickerHalting(); - } - - void reflowText() { - if (mSegments.size() > 0) { - Segment seg = mSegments.get(0); - CharSequence text = seg.getText(); - mTextSwitcher.setCurrentText(text); - } - } - - private Runnable mAdvanceTicker = new Runnable() { - public void run() { - while (mSegments.size() > 0) { - Segment seg = mSegments.get(0); - - if (seg.first) { - // this makes the icon slide in for the first one for a given - // notification even if there are two notifications with the - // same icon in a row - mIconSwitcher.setImageDrawable(seg.icon); - } - CharSequence text = seg.advance(); - if (text == null) { - mSegments.remove(0); - continue; - } - mTextSwitcher.setText(text); - - scheduleAdvance(); - break; - } - if (mSegments.size() == 0) { - tickerDone(); - } - } - }; - - private void scheduleAdvance() { - mHandler.postDelayed(mAdvanceTicker, TICKER_SEGMENT_DELAY); - } - - abstract void tickerStarting(); - abstract void tickerDone(); - abstract void tickerHalting(); -} - diff --git a/services/java/com/android/server/status/TickerView.java b/services/java/com/android/server/status/TickerView.java deleted file mode 100644 index 099dffb..0000000 --- a/services/java/com/android/server/status/TickerView.java +++ /dev/null @@ -1,38 +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.status; - -import android.content.Context; -import android.util.AttributeSet; -import android.widget.TextSwitcher; - - -public class TickerView extends TextSwitcher -{ - Ticker mTicker; - - public TickerView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - mTicker.reflowText(); - } -} - diff --git a/services/java/com/android/server/status/TrackingPatternView.java b/services/java/com/android/server/status/TrackingPatternView.java deleted file mode 100644 index 2c91aa4..0000000 --- a/services/java/com/android/server/status/TrackingPatternView.java +++ /dev/null @@ -1,70 +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.status; - -import android.content.Context; -import android.content.res.TypedArray; -import android.util.AttributeSet; -import android.util.Slog; -import android.view.View; -import android.graphics.BitmapFactory; -import android.graphics.Bitmap; -import android.graphics.Paint; -import android.graphics.Canvas; - -public class TrackingPatternView extends View { - private Bitmap mTexture; - private Paint mPaint; - private int mTextureWidth; - private int mTextureHeight; - - public TrackingPatternView(Context context, AttributeSet attrs) { - super(context, attrs); - - mTexture = BitmapFactory.decodeResource(getResources(), - com.android.internal.R.drawable.status_bar_background); - mTextureWidth = mTexture.getWidth(); - mTextureHeight = mTexture.getHeight(); - - mPaint = new Paint(); - mPaint.setDither(false); - } - - @Override - public void onDraw(Canvas canvas) { - final Bitmap texture = mTexture; - final Paint paint = mPaint; - - final int width = getWidth(); - final int height = getHeight(); - - final int textureWidth = mTextureWidth; - final int textureHeight = mTextureHeight; - - int x = 0; - int y; - - while (x < width) { - y = 0; - while (y < height) { - canvas.drawBitmap(texture, x, y, paint); - y += textureHeight; - } - x += textureWidth; - } - } -} diff --git a/services/java/com/android/server/status/TrackingView.java b/services/java/com/android/server/status/TrackingView.java deleted file mode 100644 index 8ec39c0..0000000 --- a/services/java/com/android/server/status/TrackingView.java +++ /dev/null @@ -1,63 +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.status; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.Display; -import android.view.KeyEvent; -import android.view.WindowManager; -import android.widget.LinearLayout; - - -public class TrackingView extends LinearLayout { - final Display mDisplay; - StatusBarService mService; - boolean mTracking; - int mStartX, mStartY; - - public TrackingView(Context context, AttributeSet attrs) { - super(context, attrs); - mDisplay = ((WindowManager)context.getSystemService( - Context.WINDOW_SERVICE)).getDefaultDisplay(); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - mService.updateExpandedHeight(); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - boolean down = event.getAction() == KeyEvent.ACTION_DOWN; - switch (event.getKeyCode()) { - case KeyEvent.KEYCODE_BACK: - if (down) { - mService.deactivate(); - } - return true; - } - return super.dispatchKeyEvent(event); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mService.onTrackingViewAttached(); - } -} diff --git a/services/java/com/android/server/status/UsbStorageActivity.java b/services/java/com/android/server/status/UsbStorageActivity.java deleted file mode 100644 index e8631c5..0000000 --- a/services/java/com/android/server/status/UsbStorageActivity.java +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright (C) 2007 Google Inc. - * - * 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.status; - -import com.android.internal.R; -import android.app.Activity; -import android.app.ActivityManager; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.DialogInterface.OnCancelListener; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.Bundle; -import android.os.Environment; -import android.os.Handler; -import android.os.IBinder; -import android.os.storage.IMountService; -import android.os.storage.StorageManager; -import android.os.storage.StorageEventListener; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.widget.ImageView; -import android.widget.Button; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.view.View; -import android.view.Window; -import android.util.Log; - -import java.util.List; - -/** - * This activity is shown to the user for him/her to enable USB mass storage - * on-demand (that is, when the USB cable is connected). It uses the alert - * dialog style. It will be launched from a notification. - */ -public class UsbStorageActivity extends Activity - implements View.OnClickListener, OnCancelListener { - private static final String TAG = "UsbStorageActivity"; - - private Button mMountButton; - private Button mUnmountButton; - private ProgressBar mProgressBar; - private TextView mBanner; - private TextView mMessage; - private ImageView mIcon; - private StorageManager mStorageManager = null; - private static final int DLG_CONFIRM_KILL_STORAGE_USERS = 1; - private static final int DLG_ERROR_SHARING = 2; - static final boolean localLOGV = false; - - /** Used to detect when the USB cable is unplugged, so we can call finish() */ - private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction() == Intent.ACTION_BATTERY_CHANGED) { - handleBatteryChanged(intent); - } - } - }; - - private StorageEventListener mStorageListener = new StorageEventListener() { - @Override - public void onStorageStateChanged(String path, String oldState, String newState) { - final boolean on = newState.equals(Environment.MEDIA_SHARED); - switchDisplay(on); - } - }; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (mStorageManager == null) { - mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE); - if (mStorageManager == null) { - Log.w(TAG, "Failed to get StorageManager"); - } - } - - requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); - setProgressBarIndeterminateVisibility(true); - - setTitle(getString(com.android.internal.R.string.usb_storage_activity_title)); - - setContentView(com.android.internal.R.layout.usb_storage_activity); - - mIcon = (ImageView) findViewById(com.android.internal.R.id.icon); - mBanner = (TextView) findViewById(com.android.internal.R.id.banner); - mMessage = (TextView) findViewById(com.android.internal.R.id.message); - - mMountButton = (Button) findViewById(com.android.internal.R.id.mount_button); - mMountButton.setOnClickListener(this); - mUnmountButton = (Button) findViewById(com.android.internal.R.id.unmount_button); - mUnmountButton.setOnClickListener(this); - mProgressBar = (ProgressBar) findViewById(com.android.internal.R.id.progress); - } - - private void switchDisplay(boolean usbStorageInUse) { - if (usbStorageInUse) { - mProgressBar.setVisibility(View.GONE); - mUnmountButton.setVisibility(View.VISIBLE); - mMountButton.setVisibility(View.GONE); - mIcon.setImageResource(com.android.internal.R.drawable.usb_android_connected); - mBanner.setText(com.android.internal.R.string.usb_storage_stop_title); - mMessage.setText(com.android.internal.R.string.usb_storage_stop_message); - } else { - mProgressBar.setVisibility(View.GONE); - mUnmountButton.setVisibility(View.GONE); - mMountButton.setVisibility(View.VISIBLE); - mIcon.setImageResource(com.android.internal.R.drawable.usb_android); - mBanner.setText(com.android.internal.R.string.usb_storage_title); - mMessage.setText(com.android.internal.R.string.usb_storage_message); - } - } - - @Override - protected void onResume() { - super.onResume(); - - mStorageManager.registerListener(mStorageListener); - registerReceiver(mBatteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); - try { - switchDisplay(mStorageManager.isUsbMassStorageEnabled()); - } catch (Exception ex) { - Log.e(TAG, "Failed to read UMS enable state", ex); - } - } - - @Override - protected void onPause() { - super.onPause(); - - unregisterReceiver(mBatteryReceiver); - if (mStorageManager == null && mStorageListener != null) { - mStorageManager.unregisterListener(mStorageListener); - } - } - - private void handleBatteryChanged(Intent intent) { - int pluggedType = intent.getIntExtra("plugged", 0); - if (pluggedType == 0) { - // It was disconnected from the plug, so finish - finish(); - } - } - - private IMountService getMountService() { - IBinder service = ServiceManager.getService("mount"); - if (service != null) { - return IMountService.Stub.asInterface(service); - } - return null; - } - - @Override - public Dialog onCreateDialog(int id, Bundle args) { - switch (id) { - case DLG_CONFIRM_KILL_STORAGE_USERS: - return new AlertDialog.Builder(this) - .setTitle(R.string.dlg_confirm_kill_storage_users_title) - .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - switchUsbMassStorageAsync(true); - }}) - .setNegativeButton(R.string.cancel, null) - .setMessage(R.string.dlg_confirm_kill_storage_users_text) - .setOnCancelListener(this) - .create(); - case DLG_ERROR_SHARING: - return new AlertDialog.Builder(this) - .setTitle(R.string.dlg_error_title) - .setNeutralButton(R.string.dlg_ok, null) - .setMessage(R.string.usb_storage_error_message) - .setOnCancelListener(this) - .create(); - } - return null; - } - - private void showDialogInner(int id) { - removeDialog(id); - showDialog(id); - } - - private void switchUsbMassStorageAsync(boolean on) { - mUnmountButton.setVisibility(View.GONE); - mMountButton.setVisibility(View.GONE); - - mProgressBar.setVisibility(View.VISIBLE); - // will be hidden once USB mass storage kicks in (or fails) - - final boolean _on = on; - new Thread() { - public void run() { - if (_on) { - mStorageManager.enableUsbMassStorage(); - } else { - mStorageManager.disableUsbMassStorage(); - } - } - }.start(); - } - - private void checkStorageUsers() { - IMountService ims = getMountService(); - if (ims == null) { - // Display error dialog - showDialogInner(DLG_ERROR_SHARING); - } - String extStoragePath = Environment.getExternalStorageDirectory().toString(); - boolean showDialog = false; - try { - int[] stUsers = ims.getStorageUsers(extStoragePath); - if (stUsers != null && stUsers.length > 0) { - showDialog = true; - } else { - // List of applications on sdcard. - ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); - List<ApplicationInfo> infoList = am.getRunningExternalApplications(); - if (infoList != null && infoList.size() > 0) { - showDialog = true; - } - } - } catch (RemoteException e) { - // Display error dialog - showDialogInner(DLG_ERROR_SHARING); - } - if (showDialog) { - // Display dialog to user - showDialogInner(DLG_CONFIRM_KILL_STORAGE_USERS); - } else { - if (localLOGV) Log.i(TAG, "Enabling UMS"); - switchUsbMassStorageAsync(true); - } - } - - public void onClick(View v) { - if (v == mMountButton) { - // Check for list of storage users and display dialog if needed. - checkStorageUsers(); - } else if (v == mUnmountButton) { - if (localLOGV) Log.i(TAG, "Disabling UMS"); - switchUsbMassStorageAsync(false); - } - } - - public void onCancel(DialogInterface dialog) { - finish(); - } - -} diff --git a/services/java/com/android/server/status/package.html b/services/java/com/android/server/status/package.html deleted file mode 100755 index c9f96a6..0000000 --- a/services/java/com/android/server/status/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<body> - -{@hide} - -</body> diff --git a/services/jni/Android.mk b/services/jni/Android.mk index 9d2760e..c90879d 100644 --- a/services/jni/Android.mk +++ b/services/jni/Android.mk @@ -4,24 +4,27 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ com_android_server_AlarmManagerService.cpp \ com_android_server_BatteryService.cpp \ - com_android_server_KeyInputQueue.cpp \ + com_android_server_InputManager.cpp \ com_android_server_LightsService.cpp \ - com_android_server_SensorService.cpp \ + com_android_server_PowerManagerService.cpp \ com_android_server_SystemServer.cpp \ com_android_server_VibratorService.cpp \ + com_android_server_location_GpsLocationProvider.cpp \ onload.cpp LOCAL_C_INCLUDES += \ $(JNI_H_INCLUDE) LOCAL_SHARED_LIBRARIES := \ + libandroid_runtime \ libcutils \ libhardware \ libhardware_legacy \ libnativehelper \ libsystem_server \ libutils \ - libui + libui \ + libsurfaceflinger_client ifeq ($(TARGET_SIMULATOR),true) ifeq ($(TARGET_OS),linux) diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp new file mode 100644 index 0000000..1bd1874 --- /dev/null +++ b/services/jni/com_android_server_InputManager.cpp @@ -0,0 +1,1541 @@ +/* + * 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-JNI" + +//#define LOG_NDEBUG 0 + +// Log debug messages about InputReaderPolicy +#define DEBUG_INPUT_READER_POLICY 0 + +// Log debug messages about InputDispatcherPolicy +#define DEBUG_INPUT_DISPATCHER_POLICY 0 + +#include "JNIHelp.h" +#include "jni.h" +#include <limits.h> +#include <android_runtime/AndroidRuntime.h> +#include <ui/InputReader.h> +#include <ui/InputDispatcher.h> +#include <ui/InputManager.h> +#include <ui/InputTransport.h> +#include <utils/Log.h> +#include <utils/threads.h> +#include "../../core/jni/android_view_KeyEvent.h" +#include "../../core/jni/android_view_MotionEvent.h" +#include "../../core/jni/android_view_InputChannel.h" +#include "com_android_server_PowerManagerService.h" + +namespace android { + +// ---------------------------------------------------------------------------- + +static struct { + jclass clazz; + + jmethodID notifyConfigurationChanged; + jmethodID notifyLidSwitchChanged; + jmethodID notifyInputChannelBroken; + jmethodID notifyANR; + jmethodID interceptKeyBeforeQueueing; + jmethodID interceptKeyBeforeDispatching; + jmethodID checkInjectEventsPermission; + jmethodID filterTouchEvents; + jmethodID filterJumpyTouchEvents; + jmethodID getVirtualKeyDefinitions; + jmethodID getInputDeviceCalibration; + jmethodID getExcludedDeviceNames; + jmethodID getMaxEventsPerSecond; +} 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 { + jclass clazz; +} gMotionEventClassInfo; + +static struct { + jclass clazz; + + jmethodID ctor; + jmethodID addMotionRange; + + jfieldID mId; + jfieldID mName; + jfieldID mSources; + jfieldID mKeyboardType; + jfieldID mMotionRanges; +} gInputDeviceClassInfo; + +static struct { + jclass clazz; + + jfieldID touchscreen; + jfieldID keyboard; + jfieldID navigation; +} gConfigurationClassInfo; + +// ---------------------------------------------------------------------------- + +static inline nsecs_t now() { + return systemTime(SYSTEM_TIME_MONOTONIC); +} + +// ---------------------------------------------------------------------------- + +class NativeInputManager : public virtual RefBase, + public virtual InputReaderPolicyInterface, + public virtual InputDispatcherPolicyInterface { +protected: + virtual ~NativeInputManager(); + +public: + NativeInputManager(jobject callbacksObj); + + inline sp<InputManager> getInputManager() const { return mInputManager; } + + void dump(String8& dump); + + void setDisplaySize(int32_t displayId, int32_t width, int32_t height); + void setDisplayOrientation(int32_t displayId, int32_t orientation); + + status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel, + jweak inputChannelObjWeak, 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); + + /* --- InputReaderPolicyInterface implementation --- */ + + virtual bool getDisplayInfo(int32_t displayId, + int32_t* width, int32_t* height, int32_t* orientation); + virtual bool filterTouchEvents(); + virtual bool filterJumpyTouchEvents(); + virtual void getVirtualKeyDefinitions(const String8& deviceName, + Vector<VirtualKeyDefinition>& outVirtualKeyDefinitions); + virtual void getInputDeviceCalibration(const String8& deviceName, + InputDeviceCalibration& outCalibration); + virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames); + + /* --- InputDispatcherPolicyInterface implementation --- */ + + virtual void notifySwitch(nsecs_t when, int32_t switchCode, int32_t switchValue, + 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); + 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, + const KeyEvent* keyEvent, uint32_t policyFlags); + 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; + + // Cached filtering policies. + int32_t mFilterTouchEvents; + int32_t mFilterJumpyTouchEvents; + + // Cached throttling policy. + int32_t mMaxEventsPerSecond; + + // Cached display state. (lock mDisplayLock) + Mutex mDisplayLock; + int32_t mDisplayWidth, mDisplayHeight; + int32_t mDisplayOrientation; + + // Power manager interactions. + bool isScreenOn(); + bool isScreenBright(); + + // Weak references to all currently registered input channels by connection pointer. + Mutex mInputChannelRegistryLock; + KeyedVector<InputChannel*, jweak> mInputChannelObjWeakTable; + + jobject getInputChannelObjLocal(JNIEnv* env, const sp<InputChannel>& inputChannel); + + static bool populateWindow(JNIEnv* env, jobject windowObj, InputWindow& outWindow); + + static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName); + + static inline JNIEnv* jniEnv() { + return AndroidRuntime::getJNIEnv(); + } +}; + +// ---------------------------------------------------------------------------- + +NativeInputManager::NativeInputManager(jobject callbacksObj) : + mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1), + mMaxEventsPerSecond(-1), + mDisplayWidth(-1), mDisplayHeight(-1), mDisplayOrientation(ROTATION_0) { + JNIEnv* env = jniEnv(); + + mCallbacksObj = env->NewGlobalRef(callbacksObj); + + sp<EventHub> eventHub = new EventHub(); + mInputManager = new InputManager(eventHub, this, this); +} + +NativeInputManager::~NativeInputManager() { + JNIEnv* env = jniEnv(); + + env->DeleteGlobalRef(mCallbacksObj); +} + +void NativeInputManager::dump(String8& dump) { + mInputManager->getReader()->dump(dump); + dump.append("\n"); + + mInputManager->getDispatcher()->dump(dump); + dump.append("\n"); +} + +bool NativeInputManager::checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) { + if (env->ExceptionCheck()) { + LOGE("An exception was thrown by callback '%s'.", methodName); + LOGE_EX(env); + env->ExceptionClear(); + return true; + } + return false; +} + +void NativeInputManager::setDisplaySize(int32_t displayId, int32_t width, int32_t height) { + if (displayId == 0) { + AutoMutex _l(mDisplayLock); + + mDisplayWidth = width; + mDisplayHeight = height; + } +} + +void NativeInputManager::setDisplayOrientation(int32_t displayId, int32_t orientation) { + if (displayId == 0) { + AutoMutex _l(mDisplayLock); + + mDisplayOrientation = 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; + } + + 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::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); + + if (mDisplayWidth > 0) { + if (width) { + *width = mDisplayWidth; + } + if (height) { + *height = mDisplayHeight; + } + if (orientation) { + *orientation = mDisplayOrientation; + } + result = true; + } + } + return result; +} + +bool NativeInputManager::filterTouchEvents() { + if (mFilterTouchEvents < 0) { + JNIEnv* env = jniEnv(); + + jboolean result = env->CallBooleanMethod(mCallbacksObj, + gCallbacksClassInfo.filterTouchEvents); + if (checkAndClearExceptionFromCallback(env, "filterTouchEvents")) { + result = false; + } + + mFilterTouchEvents = result ? 1 : 0; + } + return mFilterTouchEvents; +} + +bool NativeInputManager::filterJumpyTouchEvents() { + if (mFilterJumpyTouchEvents < 0) { + JNIEnv* env = jniEnv(); + + jboolean result = env->CallBooleanMethod(mCallbacksObj, + gCallbacksClassInfo.filterJumpyTouchEvents); + if (checkAndClearExceptionFromCallback(env, "filterJumpyTouchEvents")) { + result = false; + } + + mFilterJumpyTouchEvents = result ? 1 : 0; + } + return mFilterJumpyTouchEvents; +} + +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(); + + JNIEnv* env = jniEnv(); + + jobjectArray result = jobjectArray(env->CallObjectMethod(mCallbacksObj, + gCallbacksClassInfo.getExcludedDeviceNames)); + if (! checkAndClearExceptionFromCallback(env, "getExcludedDeviceNames") && result) { + jsize length = env->GetArrayLength(result); + for (jsize i = 0; i < length; i++) { + jstring item = jstring(env->GetObjectArrayElement(result, i)); + + const char* deviceNameChars = env->GetStringUTFChars(item, NULL); + outExcludedDeviceNames.add(String8(deviceNameChars)); + env->ReleaseStringUTFChars(item, deviceNameChars); + + env->DeleteLocalRef(item); + } + env->DeleteLocalRef(result); + } +} + +void NativeInputManager::notifySwitch(nsecs_t when, int32_t switchCode, + int32_t switchValue, uint32_t policyFlags) { +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("notifySwitch - when=%lld, switchCode=%d, switchValue=%d, policyFlags=0x%x", + when, switchCode, switchValue, policyFlags); +#endif + + JNIEnv* env = jniEnv(); + + switch (switchCode) { + case SW_LID: + env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyLidSwitchChanged, + when, switchValue == 0); + checkAndClearExceptionFromCallback(env, "notifyLidSwitchChanged"); + break; + } +} + +void NativeInputManager::notifyConfigurationChanged(nsecs_t when) { +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("notifyConfigurationChanged - when=%lld", when); +#endif + + JNIEnv* env = jniEnv(); + + env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyConfigurationChanged, when); + checkAndClearExceptionFromCallback(env, "notifyConfigurationChanged"); +} + +nsecs_t NativeInputManager::notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle, + const sp<InputChannel>& inputChannel) { +#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 inputChannelObjLocal = getInputChannelObjLocal(env, inputChannel); + jlong newTimeout = env->CallLongMethod(mCallbacksObj, + gCallbacksClassInfo.notifyANR, tokenObjLocal, inputChannelObjLocal); + if (checkAndClearExceptionFromCallback(env, "notifyANR")) { + newTimeout = 0; // abort dispatch + } else { + assert(newTimeout >= 0); + } + + env->DeleteLocalRef(tokenObjLocal); + env->DeleteLocalRef(inputChannelObjLocal); + return newTimeout; +} + +void NativeInputManager::notifyInputChannelBroken(const sp<InputChannel>& inputChannel) { +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("notifyInputChannelBroken - inputChannel='%s'", inputChannel->getName().string()); +#endif + + JNIEnv* env = jniEnv(); + + jobject inputChannelObjLocal = getInputChannelObjLocal(env, inputChannel); + if (inputChannelObjLocal) { + env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyInputChannelBroken, + inputChannelObjLocal); + checkAndClearExceptionFromCallback(env, "notifyInputChannelBroken"); + + env->DeleteLocalRef(inputChannelObjLocal); + } +} + +nsecs_t NativeInputManager::getKeyRepeatTimeout() { + if (! isScreenOn()) { + // Disable key repeat when the screen is off. + return -1; + } else { + // TODO use ViewConfiguration.getLongPressTimeout() + return milliseconds_to_nanoseconds(500); + } +} + +nsecs_t NativeInputManager::getKeyRepeatDelay() { + return milliseconds_to_nanoseconds(50); +} + +int32_t NativeInputManager::getMaxEventsPerSecond() { + if (mMaxEventsPerSecond < 0) { + JNIEnv* env = jniEnv(); + + jint result = env->CallIntMethod(mCallbacksObj, + gCallbacksClassInfo.getMaxEventsPerSecond); + if (checkAndClearExceptionFromCallback(env, "getMaxEventsPerSecond")) { + result = 60; + } + + mMaxEventsPerSecond = result; + } + return mMaxEventsPerSecond; +} + +void NativeInputManager::setInputWindows(JNIEnv* env, jobjectArray windowObjArray) { + Vector<InputWindow> windows; + + jsize length = env->GetArrayLength(windowObjArray); + for (jsize i = 0; i < length; i++) { + jobject inputTargetObj = env->GetObjectArrayElement(windowObjArray, i); + if (! inputTargetObj) { + 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) { + windows.pop(); + } + + env->DeleteLocalRef(inputTargetObj); + } + + 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); + } +} + +void NativeInputManager::setInputDispatchMode(bool enabled, bool frozen) { + mInputManager->getDispatcher()->setInputDispatchMode(enabled, frozen); +} + +bool NativeInputManager::isScreenOn() { + return android_server_PowerManagerService_isScreenOn(); +} + +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; + } + + // 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; + + bool isScreenOn = this->isScreenOn(); + bool isScreenBright = this->isScreenBright(); + + JNIEnv* env = jniEnv(); + jint wmActions = env->CallIntMethod(mCallbacksObj, + gCallbacksClassInfo.interceptKeyBeforeQueueing, + when, keyCode, action == AKEY_EVENT_ACTION_DOWN, policyFlags, isScreenOn); + if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeQueueing")) { + wmActions = 0; + } + + if (!(flags & POLICY_FLAG_INJECTED)) { + if (!isScreenOn) { + policyFlags |= POLICY_FLAG_WOKE_HERE; + flags |= AKEY_EVENT_FLAG_WOKE_HERE; + } + + if (!isScreenBright) { + policyFlags |= POLICY_FLAG_BRIGHT_HERE; + } + } + + 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; + } + } 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 + + // Policy: + // - Ignore untrusted events and pass them along. + // - No special filtering for injected events required at this time. + // - Filter normal events based on screen state. + // - For normal events brighten (but do not wake) the screen if currently dim. + if ((policyFlags & POLICY_FLAG_TRUSTED) && !(policyFlags & POLICY_FLAG_INJECTED)) { + if (isScreenOn()) { + policyFlags |= POLICY_FLAG_PASS_TO_USER; + + if (!isScreenBright()) { + policyFlags |= POLICY_FLAG_BRIGHT_HERE; + } + } + } else { + policyFlags |= POLICY_FLAG_PASS_TO_USER; + } +} + +bool NativeInputManager::interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel, + 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. + 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->getMetaState(), + keyEvent->getRepeatCount(), policyFlags); + bool error = checkAndClearExceptionFromCallback(env, "interceptKeyBeforeDispatching"); + + env->DeleteLocalRef(inputChannelObj); + return consumed && ! error; + } else { + return false; + } +} + +void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType) { + android_server_PowerManagerService_userActivity(eventTime, eventType); +} + + +bool NativeInputManager::checkInjectEventsPermissionNonReentrant( + int32_t injectorPid, int32_t injectorUid) { + JNIEnv* env = jniEnv(); + jboolean result = env->CallBooleanMethod(mCallbacksObj, + gCallbacksClassInfo.checkInjectEventsPermission, injectorPid, injectorUid); + checkAndClearExceptionFromCallback(env, "checkInjectEventsPermission"); + return result; +} + +// ---------------------------------------------------------------------------- + +static sp<NativeInputManager> gNativeInputManager; + +static bool checkInputManagerUnitialized(JNIEnv* env) { + if (gNativeInputManager == NULL) { + LOGE("Input manager not initialized."); + jniThrowRuntimeException(env, "Input manager not initialized."); + return true; + } + return false; +} + +static void android_server_InputManager_nativeInit(JNIEnv* env, jclass clazz, + jobject callbacks) { + if (gNativeInputManager == NULL) { + gNativeInputManager = new NativeInputManager(callbacks); + } else { + LOGE("Input manager already initialized."); + jniThrowRuntimeException(env, "Input manager already initialized."); + } +} + +static void android_server_InputManager_nativeStart(JNIEnv* env, jclass clazz) { + if (checkInputManagerUnitialized(env)) { + return; + } + + status_t result = gNativeInputManager->getInputManager()->start(); + if (result) { + jniThrowRuntimeException(env, "Input manager could not be started."); + } +} + +static void android_server_InputManager_nativeSetDisplaySize(JNIEnv* env, jclass clazz, + jint displayId, jint width, jint height) { + if (checkInputManagerUnitialized(env)) { + return; + } + + // XXX we could get this from the SurfaceFlinger directly instead of requiring it + // to be passed in like this, not sure which is better but leaving it like this + // keeps the window manager in direct control of when display transitions propagate down + // to the input dispatcher + gNativeInputManager->setDisplaySize(displayId, width, height); +} + +static void android_server_InputManager_nativeSetDisplayOrientation(JNIEnv* env, jclass clazz, + jint displayId, jint orientation) { + if (checkInputManagerUnitialized(env)) { + return; + } + + gNativeInputManager->setDisplayOrientation(displayId, orientation); +} + +static jint android_server_InputManager_nativeGetScanCodeState(JNIEnv* env, jclass clazz, + jint deviceId, jint sourceMask, jint scanCode) { + if (checkInputManagerUnitialized(env)) { + return AKEY_STATE_UNKNOWN; + } + + return gNativeInputManager->getInputManager()->getReader()->getScanCodeState( + deviceId, uint32_t(sourceMask), scanCode); +} + +static jint android_server_InputManager_nativeGetKeyCodeState(JNIEnv* env, jclass clazz, + jint deviceId, jint sourceMask, jint keyCode) { + if (checkInputManagerUnitialized(env)) { + return AKEY_STATE_UNKNOWN; + } + + return gNativeInputManager->getInputManager()->getReader()->getKeyCodeState( + deviceId, uint32_t(sourceMask), keyCode); +} + +static jint android_server_InputManager_nativeGetSwitchState(JNIEnv* env, jclass clazz, + jint deviceId, jint sourceMask, jint sw) { + if (checkInputManagerUnitialized(env)) { + return AKEY_STATE_UNKNOWN; + } + + return gNativeInputManager->getInputManager()->getReader()->getSwitchState( + deviceId, uint32_t(sourceMask), sw); +} + +static jboolean android_server_InputManager_nativeHasKeys(JNIEnv* env, jclass clazz, + jint deviceId, jint sourceMask, jintArray keyCodes, jbooleanArray outFlags) { + if (checkInputManagerUnitialized(env)) { + return JNI_FALSE; + } + + int32_t* codes = env->GetIntArrayElements(keyCodes, NULL); + uint8_t* flags = env->GetBooleanArrayElements(outFlags, NULL); + jsize numCodes = env->GetArrayLength(keyCodes); + jboolean result; + if (numCodes == env->GetArrayLength(keyCodes)) { + result = gNativeInputManager->getInputManager()->getReader()->hasKeys( + deviceId, uint32_t(sourceMask), numCodes, codes, flags); + } else { + result = JNI_FALSE; + } + + env->ReleaseBooleanArrayElements(outFlags, flags, 0); + env->ReleaseIntArrayElements(keyCodes, codes, 0); + return result; +} + +static void throwInputChannelNotInitialized(JNIEnv* env) { + jniThrowException(env, "java/lang/IllegalStateException", + "inputChannel is not initialized"); +} + +static void android_server_InputManager_handleInputChannelDisposed(JNIEnv* env, + jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data) { + LOGW("Input channel object '%s' was disposed without first being unregistered with " + "the input manager!", inputChannel->getName().string()); + + if (gNativeInputManager != NULL) { + gNativeInputManager->unregisterInputChannel(env, inputChannel); + } +} + +static void android_server_InputManager_nativeRegisterInputChannel(JNIEnv* env, jclass clazz, + jobject inputChannelObj, jboolean monitor) { + if (checkInputManagerUnitialized(env)) { + return; + } + + sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, + inputChannelObj); + if (inputChannel == NULL) { + throwInputChannelNotInitialized(env); + return; + } + + + status_t status = gNativeInputManager->registerInputChannel( + env, inputChannel, inputChannelObj, monitor); + if (status) { + jniThrowRuntimeException(env, "Failed to register input channel. " + "Check logs for details."); + return; + } + + if (! monitor) { + android_view_InputChannel_setDisposeCallback(env, inputChannelObj, + android_server_InputManager_handleInputChannelDisposed, NULL); + } +} + +static void android_server_InputManager_nativeUnregisterInputChannel(JNIEnv* env, jclass clazz, + jobject inputChannelObj) { + if (checkInputManagerUnitialized(env)) { + return; + } + + sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, + inputChannelObj); + if (inputChannel == NULL) { + throwInputChannelNotInitialized(env); + return; + } + + android_view_InputChannel_setDisposeCallback(env, inputChannelObj, NULL, NULL); + + status_t status = gNativeInputManager->unregisterInputChannel(env, inputChannel); + if (status) { + jniThrowRuntimeException(env, "Failed to unregister input channel. " + "Check logs for details."); + } +} + +static jint android_server_InputManager_nativeInjectInputEvent(JNIEnv* env, jclass clazz, + jobject inputEventObj, jint injectorPid, jint injectorUid, + jint syncMode, jint timeoutMillis) { + if (checkInputManagerUnitialized(env)) { + return INPUT_EVENT_INJECTION_FAILED; + } + + if (env->IsInstanceOf(inputEventObj, gKeyEventClassInfo.clazz)) { + KeyEvent keyEvent; + android_view_KeyEvent_toNative(env, inputEventObj, & keyEvent); + + 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); + + return gNativeInputManager->getInputManager()->getDispatcher()->injectInputEvent( + & motionEvent, injectorPid, injectorUid, syncMode, timeoutMillis); + } else { + jniThrowRuntimeException(env, "Invalid input event type."); + return INPUT_EVENT_INJECTION_FAILED; + } +} + +static void android_server_InputManager_nativeSetInputWindows(JNIEnv* env, jclass clazz, + jobjectArray windowObjArray) { + if (checkInputManagerUnitialized(env)) { + return; + } + + gNativeInputManager->setInputWindows(env, windowObjArray); +} + +static void android_server_InputManager_nativeSetFocusedApplication(JNIEnv* env, jclass clazz, + jobject applicationObj) { + if (checkInputManagerUnitialized(env)) { + return; + } + + gNativeInputManager->setFocusedApplication(env, applicationObj); +} + +static void android_server_InputManager_nativeSetInputDispatchMode(JNIEnv* env, + jclass clazz, jboolean enabled, jboolean frozen) { + if (checkInputManagerUnitialized(env)) { + return; + } + + gNativeInputManager->setInputDispatchMode(enabled, frozen); +} + +static jobject android_server_InputManager_nativeGetInputDevice(JNIEnv* env, + jclass clazz, jint deviceId) { + if (checkInputManagerUnitialized(env)) { + return NULL; + } + + InputDeviceInfo deviceInfo; + status_t status = gNativeInputManager->getInputManager()->getReader()->getInputDeviceInfo( + deviceId, & deviceInfo); + if (status) { + return NULL; + } + + jobject deviceObj = env->NewObject(gInputDeviceClassInfo.clazz, gInputDeviceClassInfo.ctor); + if (! deviceObj) { + return NULL; + } + + jstring deviceNameObj = env->NewStringUTF(deviceInfo.getName().string()); + if (! deviceNameObj) { + return NULL; + } + + env->SetIntField(deviceObj, gInputDeviceClassInfo.mId, deviceInfo.getId()); + env->SetObjectField(deviceObj, gInputDeviceClassInfo.mName, deviceNameObj); + env->SetIntField(deviceObj, gInputDeviceClassInfo.mSources, deviceInfo.getSources()); + env->SetIntField(deviceObj, gInputDeviceClassInfo.mKeyboardType, deviceInfo.getKeyboardType()); + + const KeyedVector<int, 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); + env->CallVoidMethod(deviceObj, gInputDeviceClassInfo.addMotionRange, + rangeType, range.min, range.max, range.flat, range.fuzz); + if (env->ExceptionCheck()) { + return NULL; + } + } + + return deviceObj; +} + +static jintArray android_server_InputManager_nativeGetInputDeviceIds(JNIEnv* env, + jclass clazz) { + if (checkInputManagerUnitialized(env)) { + return NULL; + } + + Vector<int> deviceIds; + gNativeInputManager->getInputManager()->getReader()->getInputDeviceIds(deviceIds); + + jintArray deviceIdsObj = env->NewIntArray(deviceIds.size()); + if (! deviceIdsObj) { + return NULL; + } + + env->SetIntArrayRegion(deviceIdsObj, 0, deviceIds.size(), deviceIds.array()); + return deviceIdsObj; +} + +static void android_server_InputManager_nativeGetInputConfiguration(JNIEnv* env, + jclass clazz, jobject configObj) { + if (checkInputManagerUnitialized(env)) { + return; + } + + InputConfiguration config; + gNativeInputManager->getInputManager()->getReader()->getInputConfiguration(& config); + + env->SetIntField(configObj, gConfigurationClassInfo.touchscreen, config.touchScreen); + env->SetIntField(configObj, gConfigurationClassInfo.keyboard, config.keyboard); + env->SetIntField(configObj, gConfigurationClassInfo.navigation, config.navigation); +} + +static jstring android_server_InputManager_nativeDump(JNIEnv* env, jclass clazz) { + if (checkInputManagerUnitialized(env)) { + return NULL; + } + + String8 dump; + gNativeInputManager->dump(dump); + return env->NewStringUTF(dump.string()); +} + +// ---------------------------------------------------------------------------- + +static JNINativeMethod gInputManagerMethods[] = { + /* name, signature, funcPtr */ + { "nativeInit", "(Lcom/android/server/InputManager$Callbacks;)V", + (void*) android_server_InputManager_nativeInit }, + { "nativeStart", "()V", + (void*) android_server_InputManager_nativeStart }, + { "nativeSetDisplaySize", "(III)V", + (void*) android_server_InputManager_nativeSetDisplaySize }, + { "nativeSetDisplayOrientation", "(II)V", + (void*) android_server_InputManager_nativeSetDisplayOrientation }, + { "nativeGetScanCodeState", "(III)I", + (void*) android_server_InputManager_nativeGetScanCodeState }, + { "nativeGetKeyCodeState", "(III)I", + (void*) android_server_InputManager_nativeGetKeyCodeState }, + { "nativeGetSwitchState", "(III)I", + (void*) android_server_InputManager_nativeGetSwitchState }, + { "nativeHasKeys", "(II[I[Z)Z", + (void*) android_server_InputManager_nativeHasKeys }, + { "nativeRegisterInputChannel", "(Landroid/view/InputChannel;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", + (void*) android_server_InputManager_nativeSetInputWindows }, + { "nativeSetFocusedApplication", "(Lcom/android/server/InputApplication;)V", + (void*) android_server_InputManager_nativeSetFocusedApplication }, + { "nativeSetInputDispatchMode", "(ZZ)V", + (void*) android_server_InputManager_nativeSetInputDispatchMode }, + { "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 }, + { "nativeDump", "()Ljava/lang/String;", + (void*) android_server_InputManager_nativeDump }, +}; + +#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_METHOD_ID(var, clazz, methodName, methodDescriptor) \ + var = env->GetMethodID(clazz, methodName, methodDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find method " methodName); + +#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_InputManager(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "com/android/server/InputManager", + gInputManagerMethods, NELEM(gInputManagerMethods)); + LOG_FATAL_IF(res < 0, "Unable to register native methods."); + + // Callbacks + + FIND_CLASS(gCallbacksClassInfo.clazz, "com/android/server/InputManager$Callbacks"); + + GET_METHOD_ID(gCallbacksClassInfo.notifyConfigurationChanged, gCallbacksClassInfo.clazz, + "notifyConfigurationChanged", "(J)V"); + + GET_METHOD_ID(gCallbacksClassInfo.notifyLidSwitchChanged, gCallbacksClassInfo.clazz, + "notifyLidSwitchChanged", "(JZ)V"); + + GET_METHOD_ID(gCallbacksClassInfo.notifyInputChannelBroken, gCallbacksClassInfo.clazz, + "notifyInputChannelBroken", "(Landroid/view/InputChannel;)V"); + + GET_METHOD_ID(gCallbacksClassInfo.notifyANR, gCallbacksClassInfo.clazz, + "notifyANR", "(Ljava/lang/Object;Landroid/view/InputChannel;)J"); + + GET_METHOD_ID(gCallbacksClassInfo.interceptKeyBeforeQueueing, gCallbacksClassInfo.clazz, + "interceptKeyBeforeQueueing", "(JIZIZ)I"); + + GET_METHOD_ID(gCallbacksClassInfo.interceptKeyBeforeDispatching, gCallbacksClassInfo.clazz, + "interceptKeyBeforeDispatching", "(Landroid/view/InputChannel;IIIIII)Z"); + + GET_METHOD_ID(gCallbacksClassInfo.checkInjectEventsPermission, gCallbacksClassInfo.clazz, + "checkInjectEventsPermission", "(II)Z"); + + GET_METHOD_ID(gCallbacksClassInfo.filterTouchEvents, gCallbacksClassInfo.clazz, + "filterTouchEvents", "()Z"); + + GET_METHOD_ID(gCallbacksClassInfo.filterJumpyTouchEvents, gCallbacksClassInfo.clazz, + "filterJumpyTouchEvents", "()Z"); + + GET_METHOD_ID(gCallbacksClassInfo.getVirtualKeyDefinitions, gCallbacksClassInfo.clazz, + "getVirtualKeyDefinitions", + "(Ljava/lang/String;)[Lcom/android/server/InputManager$VirtualKeyDefinition;"); + + GET_METHOD_ID(gCallbacksClassInfo.getInputDeviceCalibration, gCallbacksClassInfo.clazz, + "getInputDeviceCalibration", + "(Ljava/lang/String;)Lcom/android/server/InputManager$InputDeviceCalibration;"); + + GET_METHOD_ID(gCallbacksClassInfo.getExcludedDeviceNames, gCallbacksClassInfo.clazz, + "getExcludedDeviceNames", "()[Ljava/lang/String;"); + + GET_METHOD_ID(gCallbacksClassInfo.getMaxEventsPerSecond, gCallbacksClassInfo.clazz, + "getMaxEventsPerSecond", "()I"); + + // VirtualKeyDefinition + + FIND_CLASS(gVirtualKeyDefinitionClassInfo.clazz, + "com/android/server/InputManager$VirtualKeyDefinition"); + + GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.scanCode, gVirtualKeyDefinitionClassInfo.clazz, + "scanCode", "I"); + + GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.centerX, gVirtualKeyDefinitionClassInfo.clazz, + "centerX", "I"); + + GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.centerY, gVirtualKeyDefinitionClassInfo.clazz, + "centerY", "I"); + + GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.width, gVirtualKeyDefinitionClassInfo.clazz, + "width", "I"); + + GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.height, gVirtualKeyDefinitionClassInfo.clazz, + "height", "I"); + + // InputDeviceCalibration + + FIND_CLASS(gInputDeviceCalibrationClassInfo.clazz, + "com/android/server/InputManager$InputDeviceCalibration"); + + GET_FIELD_ID(gInputDeviceCalibrationClassInfo.keys, gInputDeviceCalibrationClassInfo.clazz, + "keys", "[Ljava/lang/String;"); + + GET_FIELD_ID(gInputDeviceCalibrationClassInfo.values, gInputDeviceCalibrationClassInfo.clazz, + "values", "[Ljava/lang/String;"); + + // InputWindow + + FIND_CLASS(gInputWindowClassInfo.clazz, "com/android/server/InputWindow"); + + GET_FIELD_ID(gInputWindowClassInfo.inputChannel, gInputWindowClassInfo.clazz, + "inputChannel", "Landroid/view/InputChannel;"); + + GET_FIELD_ID(gInputWindowClassInfo.name, gInputWindowClassInfo.clazz, + "name", "Ljava/lang/String;"); + + GET_FIELD_ID(gInputWindowClassInfo.layoutParamsFlags, gInputWindowClassInfo.clazz, + "layoutParamsFlags", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.layoutParamsType, gInputWindowClassInfo.clazz, + "layoutParamsType", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.dispatchingTimeoutNanos, gInputWindowClassInfo.clazz, + "dispatchingTimeoutNanos", "J"); + + GET_FIELD_ID(gInputWindowClassInfo.frameLeft, gInputWindowClassInfo.clazz, + "frameLeft", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.frameTop, gInputWindowClassInfo.clazz, + "frameTop", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.frameRight, gInputWindowClassInfo.clazz, + "frameRight", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.frameBottom, gInputWindowClassInfo.clazz, + "frameBottom", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.visibleFrameLeft, gInputWindowClassInfo.clazz, + "visibleFrameLeft", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.visibleFrameTop, gInputWindowClassInfo.clazz, + "visibleFrameTop", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.visibleFrameRight, gInputWindowClassInfo.clazz, + "visibleFrameRight", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.visibleFrameBottom, gInputWindowClassInfo.clazz, + "visibleFrameBottom", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.touchableAreaLeft, gInputWindowClassInfo.clazz, + "touchableAreaLeft", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.touchableAreaTop, gInputWindowClassInfo.clazz, + "touchableAreaTop", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.touchableAreaRight, gInputWindowClassInfo.clazz, + "touchableAreaRight", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.touchableAreaBottom, gInputWindowClassInfo.clazz, + "touchableAreaBottom", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.visible, gInputWindowClassInfo.clazz, + "visible", "Z"); + + GET_FIELD_ID(gInputWindowClassInfo.canReceiveKeys, gInputWindowClassInfo.clazz, + "canReceiveKeys", "Z"); + + GET_FIELD_ID(gInputWindowClassInfo.hasFocus, gInputWindowClassInfo.clazz, + "hasFocus", "Z"); + + GET_FIELD_ID(gInputWindowClassInfo.hasWallpaper, gInputWindowClassInfo.clazz, + "hasWallpaper", "Z"); + + GET_FIELD_ID(gInputWindowClassInfo.paused, gInputWindowClassInfo.clazz, + "paused", "Z"); + + GET_FIELD_ID(gInputWindowClassInfo.layer, gInputWindowClassInfo.clazz, + "layer", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.ownerPid, gInputWindowClassInfo.clazz, + "ownerPid", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.ownerUid, gInputWindowClassInfo.clazz, + "ownerUid", "I"); + + // InputApplication + + FIND_CLASS(gInputApplicationClassInfo.clazz, "com/android/server/InputApplication"); + + GET_FIELD_ID(gInputApplicationClassInfo.name, gInputApplicationClassInfo.clazz, + "name", "Ljava/lang/String;"); + + GET_FIELD_ID(gInputApplicationClassInfo.dispatchingTimeoutNanos, + gInputApplicationClassInfo.clazz, + "dispatchingTimeoutNanos", "J"); + + GET_FIELD_ID(gInputApplicationClassInfo.token, gInputApplicationClassInfo.clazz, + "token", "Ljava/lang/Object;"); + + // KeyEvent + + FIND_CLASS(gKeyEventClassInfo.clazz, "android/view/KeyEvent"); + + // MotionEvent + + FIND_CLASS(gMotionEventClassInfo.clazz, "android/view/MotionEvent"); + + // InputDevice + + FIND_CLASS(gInputDeviceClassInfo.clazz, "android/view/InputDevice"); + + GET_METHOD_ID(gInputDeviceClassInfo.ctor, gInputDeviceClassInfo.clazz, + "<init>", "()V"); + + GET_METHOD_ID(gInputDeviceClassInfo.addMotionRange, gInputDeviceClassInfo.clazz, + "addMotionRange", "(IFFFF)V"); + + GET_FIELD_ID(gInputDeviceClassInfo.mId, gInputDeviceClassInfo.clazz, + "mId", "I"); + + GET_FIELD_ID(gInputDeviceClassInfo.mName, gInputDeviceClassInfo.clazz, + "mName", "Ljava/lang/String;"); + + GET_FIELD_ID(gInputDeviceClassInfo.mSources, gInputDeviceClassInfo.clazz, + "mSources", "I"); + + 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"); + + GET_FIELD_ID(gConfigurationClassInfo.touchscreen, gConfigurationClassInfo.clazz, + "touchscreen", "I"); + + GET_FIELD_ID(gConfigurationClassInfo.keyboard, gConfigurationClassInfo.clazz, + "keyboard", "I"); + + GET_FIELD_ID(gConfigurationClassInfo.navigation, gConfigurationClassInfo.clazz, + "navigation", "I"); + + return 0; +} + +} /* namespace android */ diff --git a/services/jni/com_android_server_KeyInputQueue.cpp b/services/jni/com_android_server_KeyInputQueue.cpp deleted file mode 100644 index c92f8df..0000000 --- a/services/jni/com_android_server_KeyInputQueue.cpp +++ /dev/null @@ -1,358 +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. - */ - -#define LOG_TAG "Input" - -#include "jni.h" -#include "JNIHelp.h" -#include <utils/misc.h> -#include <utils/Log.h> - -#include <ui/EventHub.h> -#include <utils/threads.h> - -#include <stdio.h> - -namespace android { - -// ---------------------------------------------------------------------------- - -static struct input_offsets_t -{ - jfieldID mMinValue; - jfieldID mMaxValue; - jfieldID mFlat; - jfieldID mFuzz; - - jfieldID mDeviceId; - jfieldID mType; - jfieldID mScancode; - jfieldID mKeycode; - jfieldID mFlags; - jfieldID mValue; - jfieldID mWhen; -} gInputOffsets; - -// ---------------------------------------------------------------------------- - -static Mutex gLock; -static sp<EventHub> gHub; - -static jboolean -android_server_KeyInputQueue_readEvent(JNIEnv* env, jobject clazz, - jobject event) -{ - gLock.lock(); - sp<EventHub> hub = gHub; - if (hub == NULL) { - hub = new EventHub; - gHub = hub; - } - gLock.unlock(); - - int32_t deviceId; - int32_t type; - int32_t scancode, keycode; - uint32_t flags; - int32_t value; - nsecs_t when; - bool res = hub->getEvent(&deviceId, &type, &scancode, &keycode, - &flags, &value, &when); - - env->SetIntField(event, gInputOffsets.mDeviceId, (jint)deviceId); - env->SetIntField(event, gInputOffsets.mType, (jint)type); - env->SetIntField(event, gInputOffsets.mScancode, (jint)scancode); - env->SetIntField(event, gInputOffsets.mKeycode, (jint)keycode); - env->SetIntField(event, gInputOffsets.mFlags, (jint)flags); - env->SetIntField(event, gInputOffsets.mValue, value); - env->SetLongField(event, gInputOffsets.mWhen, - (jlong)(nanoseconds_to_milliseconds(when))); - - return res; -} - -static jint -android_server_KeyInputQueue_getDeviceClasses(JNIEnv* env, jobject clazz, - jint deviceId) -{ - jint classes = 0; - gLock.lock(); - if (gHub != NULL) classes = gHub->getDeviceClasses(deviceId); - gLock.unlock(); - return classes; -} - -static jstring -android_server_KeyInputQueue_getDeviceName(JNIEnv* env, jobject clazz, - jint deviceId) -{ - String8 name; - gLock.lock(); - if (gHub != NULL) name = gHub->getDeviceName(deviceId); - gLock.unlock(); - - if (name.size() > 0) { - return env->NewStringUTF(name.string()); - } - return NULL; -} - -static void -android_server_KeyInputQueue_addExcludedDevice(JNIEnv* env, jobject clazz, - jstring deviceName) -{ - gLock.lock(); - sp<EventHub> hub = gHub; - if (hub == NULL) { - hub = new EventHub; - gHub = hub; - } - gLock.unlock(); - - const char* nameStr = env->GetStringUTFChars(deviceName, NULL); - gHub->addExcludedDevice(nameStr); - env->ReleaseStringUTFChars(deviceName, nameStr); -} - -static jboolean -android_server_KeyInputQueue_getAbsoluteInfo(JNIEnv* env, jobject clazz, - jint deviceId, jint axis, - jobject info) -{ - int32_t minValue, maxValue, flat, fuzz; - int res = -1; - gLock.lock(); - if (gHub != NULL) { - res = gHub->getAbsoluteInfo(deviceId, axis, - &minValue, &maxValue, &flat, &fuzz); - } - gLock.unlock(); - - if (res < 0) return JNI_FALSE; - - env->SetIntField(info, gInputOffsets.mMinValue, (jint)minValue); - env->SetIntField(info, gInputOffsets.mMaxValue, (jint)maxValue); - env->SetIntField(info, gInputOffsets.mFlat, (jint)flat); - env->SetIntField(info, gInputOffsets.mFuzz, (jint)fuzz); - return JNI_TRUE; -} - -static jint -android_server_KeyInputQueue_getSwitchState(JNIEnv* env, jobject clazz, - jint sw) -{ - jint st = -1; - gLock.lock(); - if (gHub != NULL) st = gHub->getSwitchState(sw); - gLock.unlock(); - - return st; -} - -static jint -android_server_KeyInputQueue_getSwitchStateDevice(JNIEnv* env, jobject clazz, - jint deviceId, jint sw) -{ - jint st = -1; - gLock.lock(); - if (gHub != NULL) st = gHub->getSwitchState(deviceId, sw); - gLock.unlock(); - - return st; -} - -static jint -android_server_KeyInputQueue_getScancodeState(JNIEnv* env, jobject clazz, - jint sw) -{ - jint st = -1; - gLock.lock(); - if (gHub != NULL) st = gHub->getScancodeState(sw); - gLock.unlock(); - - return st; -} - -static jint -android_server_KeyInputQueue_getScancodeStateDevice(JNIEnv* env, jobject clazz, - jint deviceId, jint sw) -{ - jint st = -1; - gLock.lock(); - if (gHub != NULL) st = gHub->getScancodeState(deviceId, sw); - gLock.unlock(); - - return st; -} - -static jint -android_server_KeyInputQueue_getKeycodeState(JNIEnv* env, jobject clazz, - jint sw) -{ - jint st = -1; - gLock.lock(); - if (gHub != NULL) st = gHub->getKeycodeState(sw); - gLock.unlock(); - - return st; -} - -static jint -android_server_KeyInputQueue_getKeycodeStateDevice(JNIEnv* env, jobject clazz, - jint deviceId, jint sw) -{ - jint st = -1; - gLock.lock(); - if (gHub != NULL) st = gHub->getKeycodeState(deviceId, sw); - gLock.unlock(); - - return st; -} - -static jint -android_server_KeyInputQueue_scancodeToKeycode(JNIEnv* env, jobject clazz, - jint deviceId, jint scancode) -{ - jint res = 0; - gLock.lock(); - if (gHub != NULL) { - int32_t keycode; - uint32_t flags; - gHub->scancodeToKeycode(deviceId, scancode, &keycode, &flags); - res = keycode; - } - gLock.unlock(); - - return res; -} - -static jboolean -android_server_KeyInputQueue_hasKeys(JNIEnv* env, jobject clazz, - jintArray keyCodes, jbooleanArray outFlags) -{ - jboolean ret = JNI_FALSE; - - int32_t* codes = env->GetIntArrayElements(keyCodes, NULL); - uint8_t* flags = env->GetBooleanArrayElements(outFlags, NULL); - size_t numCodes = env->GetArrayLength(keyCodes); - if (numCodes == env->GetArrayLength(outFlags)) { - gLock.lock(); - if (gHub != NULL) ret = gHub->hasKeys(numCodes, codes, flags); - gLock.unlock(); - } - - env->ReleaseBooleanArrayElements(outFlags, flags, 0); - env->ReleaseIntArrayElements(keyCodes, codes, 0); - return ret; -} - -// ---------------------------------------------------------------------------- - -/* - * JNI registration. - */ -static JNINativeMethod gInputMethods[] = { - /* name, signature, funcPtr */ - { "readEvent", "(Landroid/view/RawInputEvent;)Z", - (void*) android_server_KeyInputQueue_readEvent }, - { "getDeviceClasses", "(I)I", - (void*) android_server_KeyInputQueue_getDeviceClasses }, - { "getDeviceName", "(I)Ljava/lang/String;", - (void*) android_server_KeyInputQueue_getDeviceName }, - { "addExcludedDevice", "(Ljava/lang/String;)V", - (void*) android_server_KeyInputQueue_addExcludedDevice }, - { "getAbsoluteInfo", "(IILcom/android/server/InputDevice$AbsoluteInfo;)Z", - (void*) android_server_KeyInputQueue_getAbsoluteInfo }, - { "getSwitchState", "(I)I", - (void*) android_server_KeyInputQueue_getSwitchState }, - { "getSwitchState", "(II)I", - (void*) android_server_KeyInputQueue_getSwitchStateDevice }, - { "nativeGetScancodeState", "(I)I", - (void*) android_server_KeyInputQueue_getScancodeState }, - { "nativeGetScancodeState", "(II)I", - (void*) android_server_KeyInputQueue_getScancodeStateDevice }, - { "nativeGetKeycodeState", "(I)I", - (void*) android_server_KeyInputQueue_getKeycodeState }, - { "nativeGetKeycodeState", "(II)I", - (void*) android_server_KeyInputQueue_getKeycodeStateDevice }, - { "hasKeys", "([I[Z)Z", - (void*) android_server_KeyInputQueue_hasKeys }, - { "scancodeToKeycode", "(II)I", - (void*) android_server_KeyInputQueue_scancodeToKeycode }, -}; - -int register_android_server_KeyInputQueue(JNIEnv* env) -{ - jclass input = env->FindClass("com/android/server/KeyInputQueue"); - LOG_FATAL_IF(input == NULL, "Unable to find class com/android/server/KeyInputQueue"); - int res = jniRegisterNativeMethods(env, "com/android/server/KeyInputQueue", - gInputMethods, NELEM(gInputMethods)); - - jclass absoluteInfo = env->FindClass("com/android/server/InputDevice$AbsoluteInfo"); - LOG_FATAL_IF(absoluteInfo == NULL, "Unable to find class com/android/server/InputDevice$AbsoluteInfo"); - - gInputOffsets.mMinValue - = env->GetFieldID(absoluteInfo, "minValue", "I"); - LOG_FATAL_IF(gInputOffsets.mMinValue == NULL, "Unable to find InputDevice.AbsoluteInfo.minValue"); - - gInputOffsets.mMaxValue - = env->GetFieldID(absoluteInfo, "maxValue", "I"); - LOG_FATAL_IF(gInputOffsets.mMaxValue == NULL, "Unable to find InputDevice.AbsoluteInfo.maxValue"); - - gInputOffsets.mFlat - = env->GetFieldID(absoluteInfo, "flat", "I"); - LOG_FATAL_IF(gInputOffsets.mFlat == NULL, "Unable to find InputDevice.AbsoluteInfo.flat"); - - gInputOffsets.mFuzz - = env->GetFieldID(absoluteInfo, "fuzz", "I"); - LOG_FATAL_IF(gInputOffsets.mFuzz == NULL, "Unable to find InputDevice.AbsoluteInfo.fuzz"); - - jclass inputEvent = env->FindClass("android/view/RawInputEvent"); - LOG_FATAL_IF(inputEvent == NULL, "Unable to find class android/view/RawInputEvent"); - - gInputOffsets.mDeviceId - = env->GetFieldID(inputEvent, "deviceId", "I"); - LOG_FATAL_IF(gInputOffsets.mDeviceId == NULL, "Unable to find RawInputEvent.deviceId"); - - gInputOffsets.mType - = env->GetFieldID(inputEvent, "type", "I"); - LOG_FATAL_IF(gInputOffsets.mType == NULL, "Unable to find RawInputEvent.type"); - - gInputOffsets.mScancode - = env->GetFieldID(inputEvent, "scancode", "I"); - LOG_FATAL_IF(gInputOffsets.mScancode == NULL, "Unable to find RawInputEvent.scancode"); - - gInputOffsets.mKeycode - = env->GetFieldID(inputEvent, "keycode", "I"); - LOG_FATAL_IF(gInputOffsets.mKeycode == NULL, "Unable to find RawInputEvent.keycode"); - - gInputOffsets.mFlags - = env->GetFieldID(inputEvent, "flags", "I"); - LOG_FATAL_IF(gInputOffsets.mFlags == NULL, "Unable to find RawInputEvent.flags"); - - gInputOffsets.mValue - = env->GetFieldID(inputEvent, "value", "I"); - LOG_FATAL_IF(gInputOffsets.mValue == NULL, "Unable to find RawInputEvent.value"); - - gInputOffsets.mWhen - = env->GetFieldID(inputEvent, "when", "J"); - LOG_FATAL_IF(gInputOffsets.mWhen == NULL, "Unable to find RawInputEvent.when"); - - return res; -} - -}; // namespace android - diff --git a/services/jni/com_android_server_PowerManagerService.cpp b/services/jni/com_android_server_PowerManagerService.cpp new file mode 100644 index 0000000..705be60 --- /dev/null +++ b/services/jni/com_android_server_PowerManagerService.cpp @@ -0,0 +1,182 @@ +/* + * 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 "PowerManagerService-JNI" + +//#define LOG_NDEBUG 0 + +#include "JNIHelp.h" +#include "jni.h" + +#include <limits.h> + +#include <android_runtime/AndroidRuntime.h> +#include <utils/Timers.h> +#include <surfaceflinger/ISurfaceComposer.h> +#include <surfaceflinger/SurfaceComposerClient.h> + +#include "com_android_server_PowerManagerService.h" + +namespace android { + +// ---------------------------------------------------------------------------- + +static struct { + jclass clazz; + + jmethodID goToSleep; + jmethodID userActivity; +} gPowerManagerServiceClassInfo; + +// ---------------------------------------------------------------------------- + +static jobject gPowerManagerServiceObj; + +static Mutex gPowerManagerLock; +static bool gScreenOn; +static bool gScreenBright; + +static nsecs_t gLastEventTime[POWER_MANAGER_LAST_EVENT + 1]; + +// Throttling interval for user activity calls. +static const nsecs_t MIN_TIME_BETWEEN_USERACTIVITIES = 500 * 1000000L; // 500ms + +// ---------------------------------------------------------------------------- + +static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) { + if (env->ExceptionCheck()) { + LOGE("An exception was thrown by callback '%s'.", methodName); + LOGE_EX(env); + env->ExceptionClear(); + return true; + } + return false; +} + +bool android_server_PowerManagerService_isScreenOn() { + AutoMutex _l(gPowerManagerLock); + return gScreenOn; +} + +bool android_server_PowerManagerService_isScreenBright() { + AutoMutex _l(gPowerManagerLock); + return gScreenBright; +} + +void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType) { + if (gPowerManagerServiceObj) { + // Throttle calls into user activity by event type. + // We're a little conservative about argument checking here in case the caller + // passes in bad data which could corrupt system state. + if (eventType >= 0 && eventType <= POWER_MANAGER_LAST_EVENT) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + if (eventTime > now) { + eventTime = now; + } + + if (gLastEventTime[eventType] + MIN_TIME_BETWEEN_USERACTIVITIES > eventTime) { + return; + } + gLastEventTime[eventType] = eventTime; + } + + JNIEnv* env = AndroidRuntime::getJNIEnv(); + + env->CallVoidMethod(gPowerManagerServiceObj, gPowerManagerServiceClassInfo.userActivity, + nanoseconds_to_milliseconds(eventTime), false, eventType, false); + checkAndClearExceptionFromCallback(env, "userActivity"); + } +} + +void android_server_PowerManagerService_goToSleep(nsecs_t eventTime) { + if (gPowerManagerServiceObj) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + + env->CallVoidMethod(gPowerManagerServiceObj, gPowerManagerServiceClassInfo.goToSleep, + nanoseconds_to_milliseconds(eventTime)); + checkAndClearExceptionFromCallback(env, "goToSleep"); + } +} + +// ---------------------------------------------------------------------------- + +static void android_server_PowerManagerService_nativeInit(JNIEnv* env, jobject obj) { + gPowerManagerServiceObj = env->NewGlobalRef(obj); +} + +static void android_server_PowerManagerService_nativeSetPowerState(JNIEnv* env, + jobject serviceObj, jboolean screenOn, jboolean screenBright) { + AutoMutex _l(gPowerManagerLock); + gScreenOn = screenOn; + gScreenBright = screenBright; +} + +static void android_server_PowerManagerService_nativeStartSurfaceFlingerAnimation(JNIEnv* env, + jobject obj, jint mode) { + sp<ISurfaceComposer> s(ComposerService::getComposerService()); + s->turnElectronBeamOff(mode); +} + +// ---------------------------------------------------------------------------- + +static JNINativeMethod gPowerManagerServiceMethods[] = { + /* name, signature, funcPtr */ + { "nativeInit", "()V", + (void*) android_server_PowerManagerService_nativeInit }, + { "nativeSetPowerState", "(ZZ)V", + (void*) android_server_PowerManagerService_nativeSetPowerState }, + { "nativeStartSurfaceFlingerAnimation", "(I)V", + (void*) android_server_PowerManagerService_nativeStartSurfaceFlingerAnimation }, +}; + +#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_METHOD_ID(var, clazz, methodName, methodDescriptor) \ + var = env->GetMethodID(clazz, methodName, methodDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find method " methodName); + +#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_PowerManagerService(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "com/android/server/PowerManagerService", + gPowerManagerServiceMethods, NELEM(gPowerManagerServiceMethods)); + LOG_FATAL_IF(res < 0, "Unable to register native methods."); + + // Callbacks + + FIND_CLASS(gPowerManagerServiceClassInfo.clazz, "com/android/server/PowerManagerService"); + + GET_METHOD_ID(gPowerManagerServiceClassInfo.goToSleep, gPowerManagerServiceClassInfo.clazz, + "goToSleep", "(J)V"); + + GET_METHOD_ID(gPowerManagerServiceClassInfo.userActivity, gPowerManagerServiceClassInfo.clazz, + "userActivity", "(JZIZ)V"); + + // Initialize + for (int i = 0; i < POWER_MANAGER_LAST_EVENT; i++) { + gLastEventTime[i] = LLONG_MIN; + } + gScreenOn = true; + gScreenBright = true; + return 0; +} + +} /* namespace android */ diff --git a/services/jni/com_android_server_PowerManagerService.h b/services/jni/com_android_server_PowerManagerService.h new file mode 100644 index 0000000..af10711 --- /dev/null +++ b/services/jni/com_android_server_PowerManagerService.h @@ -0,0 +1,34 @@ +/* + * 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_SERVER_POWER_MANAGER_SERVICE_H +#define _ANDROID_SERVER_POWER_MANAGER_SERVICE_H + +#include "JNIHelp.h" +#include "jni.h" + +#include <ui/PowerManager.h> + +namespace android { + +extern bool android_server_PowerManagerService_isScreenOn(); +extern bool android_server_PowerManagerService_isScreenBright(); +extern void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType); +extern void android_server_PowerManagerService_goToSleep(nsecs_t eventTime); + +} // namespace android + +#endif // _ANDROID_SERVER_POWER_MANAGER_SERVICE_H diff --git a/services/jni/com_android_server_SensorService.cpp b/services/jni/com_android_server_SensorService.cpp deleted file mode 100644 index 77db6da..0000000 --- a/services/jni/com_android_server_SensorService.cpp +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright 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. - */ - -#define LOG_TAG "SensorService" - -#include "utils/Log.h" - -#include <hardware/sensors.h> - -#include "jni.h" -#include "JNIHelp.h" - -namespace android { - -static struct file_descriptor_offsets_t -{ - jclass mClass; - jmethodID mConstructor; - jfieldID mDescriptor; -} gFileDescriptorOffsets; - -static struct parcel_file_descriptor_offsets_t -{ - jclass mClass; - jmethodID mConstructor; -} gParcelFileDescriptorOffsets; - -static struct bundle_descriptor_offsets_t -{ - jclass mClass; - jmethodID mConstructor; - jmethodID mPutIntArray; - jmethodID mPutParcelableArray; -} gBundleOffsets; - -/* - * The method below are not thread-safe and not intended to be - */ - -static sensors_control_device_t* sSensorDevice = 0; - -static jint -android_init(JNIEnv *env, jclass clazz) -{ - sensors_module_t* module; - if (hw_get_module(SENSORS_HARDWARE_MODULE_ID, (const hw_module_t**)&module) == 0) { - if (sensors_control_open(&module->common, &sSensorDevice) == 0) { - const struct sensor_t* list; - int count = module->get_sensors_list(module, &list); - return count; - } - } - return 0; -} - -static jobject -android_open(JNIEnv *env, jclass clazz) -{ - native_handle_t* handle = sSensorDevice->open_data_source(sSensorDevice); - if (!handle) { - return NULL; - } - - // new Bundle() - jobject bundle = env->NewObject( - gBundleOffsets.mClass, - gBundleOffsets.mConstructor); - - if (handle->numFds > 0) { - jobjectArray fdArray = env->NewObjectArray(handle->numFds, - gParcelFileDescriptorOffsets.mClass, NULL); - for (int i = 0; i < handle->numFds; i++) { - // new FileDescriptor() - jobject fd = env->NewObject(gFileDescriptorOffsets.mClass, - gFileDescriptorOffsets.mConstructor); - env->SetIntField(fd, gFileDescriptorOffsets.mDescriptor, handle->data[i]); - // new ParcelFileDescriptor() - jobject pfd = env->NewObject(gParcelFileDescriptorOffsets.mClass, - gParcelFileDescriptorOffsets.mConstructor, fd); - env->SetObjectArrayElement(fdArray, i, pfd); - } - // bundle.putParcelableArray("fds", fdArray); - env->CallVoidMethod(bundle, gBundleOffsets.mPutParcelableArray, - env->NewStringUTF("fds"), fdArray); - } - - if (handle->numInts > 0) { - jintArray intArray = env->NewIntArray(handle->numInts); - env->SetIntArrayRegion(intArray, 0, handle->numInts, &handle->data[handle->numInts]); - // bundle.putIntArray("ints", intArray); - env->CallVoidMethod(bundle, gBundleOffsets.mPutIntArray, - env->NewStringUTF("ints"), intArray); - } - - // delete the file handle, but don't close any file descriptors - native_handle_delete(handle); - return bundle; -} - -static jint -android_close(JNIEnv *env, jclass clazz) -{ - if (sSensorDevice->close_data_source) - return sSensorDevice->close_data_source(sSensorDevice); - else - return 0; -} - -static jboolean -android_activate(JNIEnv *env, jclass clazz, jint sensor, jboolean activate) -{ - int active = sSensorDevice->activate(sSensorDevice, sensor, activate); - return (active<0) ? false : true; -} - -static jint -android_set_delay(JNIEnv *env, jclass clazz, jint ms) -{ - return sSensorDevice->set_delay(sSensorDevice, ms); -} - -static jint -android_data_wake(JNIEnv *env, jclass clazz) -{ - int res = sSensorDevice->wake(sSensorDevice); - return res; -} - - -static JNINativeMethod gMethods[] = { - {"_sensors_control_init", "()I", (void*) android_init }, - {"_sensors_control_open", "()Landroid/os/Bundle;", (void*) android_open }, - {"_sensors_control_close", "()I", (void*) android_close }, - {"_sensors_control_activate", "(IZ)Z", (void*) android_activate }, - {"_sensors_control_wake", "()I", (void*) android_data_wake }, - {"_sensors_control_set_delay","(I)I", (void*) android_set_delay }, -}; - -int register_android_server_SensorService(JNIEnv *env) -{ - jclass clazz; - - clazz = env->FindClass("java/io/FileDescriptor"); - gFileDescriptorOffsets.mClass = (jclass)env->NewGlobalRef(clazz); - gFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "()V"); - gFileDescriptorOffsets.mDescriptor = env->GetFieldID(clazz, "descriptor", "I"); - - clazz = env->FindClass("android/os/ParcelFileDescriptor"); - gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz); - gParcelFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", - "(Ljava/io/FileDescriptor;)V"); - - clazz = env->FindClass("android/os/Bundle"); - gBundleOffsets.mClass = (jclass) env->NewGlobalRef(clazz); - gBundleOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "()V"); - gBundleOffsets.mPutIntArray = env->GetMethodID(clazz, "putIntArray", "(Ljava/lang/String;[I)V"); - gBundleOffsets.mPutParcelableArray = env->GetMethodID(clazz, "putParcelableArray", - "(Ljava/lang/String;[Landroid/os/Parcelable;)V"); - - return jniRegisterNativeMethods(env, "com/android/server/SensorService", - gMethods, NELEM(gMethods)); -} - -}; // namespace android diff --git a/services/jni/com_android_server_location_GpsLocationProvider.cpp b/services/jni/com_android_server_location_GpsLocationProvider.cpp new file mode 100755 index 0000000..a75e41d --- /dev/null +++ b/services/jni/com_android_server_location_GpsLocationProvider.cpp @@ -0,0 +1,634 @@ +/* + * 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. + */ + +#define LOG_TAG "GpsLocationProvider" + +#define LOG_NDEBUG 0 + +#include "JNIHelp.h" +#include "jni.h" +#include "hardware/hardware.h" +#include "hardware/gps.h" +#include "hardware_legacy/power.h" +#include "utils/Log.h" +#include "utils/misc.h" +#include "android_runtime/AndroidRuntime.h" + +#include <string.h> +#include <pthread.h> + +static jobject mCallbacksObj = NULL; + +static jmethodID method_reportLocation; +static jmethodID method_reportStatus; +static jmethodID method_reportSvStatus; +static jmethodID method_reportAGpsStatus; +static jmethodID method_reportNmea; +static jmethodID method_setEngineCapabilities; +static jmethodID method_xtraDownloadRequest; +static jmethodID method_reportNiNotification; +static jmethodID method_requestRefLocation; +static jmethodID method_requestSetID; + +static const GpsInterface* sGpsInterface = NULL; +static const GpsXtraInterface* sGpsXtraInterface = NULL; +static const AGpsInterface* sAGpsInterface = NULL; +static const GpsNiInterface* sGpsNiInterface = NULL; +static const GpsDebugInterface* sGpsDebugInterface = NULL; +static const AGpsRilInterface* sAGpsRilInterface = NULL; + +// temporary storage for GPS callbacks +static GpsSvStatus sGpsSvStatus; +static const char* sNmeaString; +static int sNmeaStringLength; + +#define WAKE_LOCK_NAME "GPS" + +namespace android { + +static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) { + if (env->ExceptionCheck()) { + LOGE("An exception was thrown by callback '%s'.", methodName); + LOGE_EX(env); + env->ExceptionClear(); + } +} + +static void location_callback(GpsLocation* location) +{ + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->CallVoidMethod(mCallbacksObj, method_reportLocation, location->flags, + (jdouble)location->latitude, (jdouble)location->longitude, + (jdouble)location->altitude, + (jfloat)location->speed, (jfloat)location->bearing, + (jfloat)location->accuracy, (jlong)location->timestamp); + checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + +static void status_callback(GpsStatus* status) +{ + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->CallVoidMethod(mCallbacksObj, method_reportStatus, status->status); + checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + +static void sv_status_callback(GpsSvStatus* sv_status) +{ + JNIEnv* env = AndroidRuntime::getJNIEnv(); + memcpy(&sGpsSvStatus, sv_status, sizeof(sGpsSvStatus)); + env->CallVoidMethod(mCallbacksObj, method_reportSvStatus); + checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + +static void nmea_callback(GpsUtcTime timestamp, const char* nmea, int length) +{ + JNIEnv* env = AndroidRuntime::getJNIEnv(); + // The Java code will call back to read these values + // We do this to avoid creating unnecessary String objects + sNmeaString = nmea; + sNmeaStringLength = length; + env->CallVoidMethod(mCallbacksObj, method_reportNmea, timestamp); + checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + +static void set_capabilities_callback(uint32_t capabilities) +{ + LOGD("set_capabilities_callback: %ld\n", capabilities); + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->CallVoidMethod(mCallbacksObj, method_setEngineCapabilities, capabilities); + checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + +static void acquire_wakelock_callback() +{ + acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_NAME); +} + +static void release_wakelock_callback() +{ + release_wake_lock(WAKE_LOCK_NAME); +} + +static pthread_t create_thread_callback(const char* name, void (*start)(void *), void* arg) +{ + return (pthread_t)AndroidRuntime::createJavaThread(name, start, arg); +} + +GpsCallbacks sGpsCallbacks = { + sizeof(GpsCallbacks), + location_callback, + status_callback, + sv_status_callback, + nmea_callback, + set_capabilities_callback, + acquire_wakelock_callback, + release_wakelock_callback, + create_thread_callback, +}; + +static void xtra_download_request_callback() +{ + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->CallVoidMethod(mCallbacksObj, method_xtraDownloadRequest); + checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + +GpsXtraCallbacks sGpsXtraCallbacks = { + xtra_download_request_callback, + create_thread_callback, +}; + +static void agps_status_callback(AGpsStatus* agps_status) +{ + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->CallVoidMethod(mCallbacksObj, method_reportAGpsStatus, + agps_status->type, agps_status->status); + checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + +AGpsCallbacks sAGpsCallbacks = { + agps_status_callback, + create_thread_callback, +}; + +static void gps_ni_notify_callback(GpsNiNotification *notification) +{ + LOGD("gps_ni_notify_callback\n"); + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jstring requestor_id = env->NewStringUTF(notification->requestor_id); + jstring text = env->NewStringUTF(notification->text); + jstring extras = env->NewStringUTF(notification->extras); + + if (requestor_id && text && extras) { + env->CallVoidMethod(mCallbacksObj, method_reportNiNotification, + notification->notification_id, notification->ni_type, + notification->notify_flags, notification->timeout, + notification->default_response, requestor_id, text, + notification->requestor_id_encoding, + notification->text_encoding, extras); + } else { + LOGE("out of memory in gps_ni_notify_callback\n"); + } + + if (requestor_id) + env->DeleteLocalRef(requestor_id); + if (text) + env->DeleteLocalRef(text); + if (extras) + env->DeleteLocalRef(extras); + checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + +GpsNiCallbacks sGpsNiCallbacks = { + gps_ni_notify_callback, + create_thread_callback, +}; + +static void agps_request_set_id(uint32_t flags) +{ + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->CallVoidMethod(mCallbacksObj, method_requestSetID, flags); + checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + +static void agps_request_ref_location(uint32_t flags) +{ + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->CallVoidMethod(mCallbacksObj, method_requestRefLocation, flags); + checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + +AGpsRilCallbacks sAGpsRilCallbacks = { + agps_request_set_id, + agps_request_ref_location, + create_thread_callback, +}; + +static const GpsInterface* get_gps_interface() { + int err; + hw_module_t* module; + const GpsInterface* interface = NULL; + + err = hw_get_module(GPS_HARDWARE_MODULE_ID, (hw_module_t const**)&module); + if (err == 0) { + hw_device_t* device; + err = module->methods->open(module, GPS_HARDWARE_MODULE_ID, &device); + if (err == 0) { + gps_device_t* gps_device = (gps_device_t *)device; + interface = gps_device->get_gps_interface(gps_device); + } + } + + return interface; +} + +static const GpsInterface* GetGpsInterface(JNIEnv* env, jobject obj) { + // this must be set before calling into the HAL library + if (!mCallbacksObj) + mCallbacksObj = env->NewGlobalRef(obj); + + if (!sGpsInterface) { + sGpsInterface = get_gps_interface(); + if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0) { + sGpsInterface = NULL; + return NULL; + } + } + return sGpsInterface; +} + +static const AGpsInterface* GetAGpsInterface(JNIEnv* env, jobject obj) +{ + const GpsInterface* interface = GetGpsInterface(env, obj); + if (!interface) + return NULL; + + if (!sAGpsInterface) { + sAGpsInterface = (const AGpsInterface*)interface->get_extension(AGPS_INTERFACE); + if (sAGpsInterface) + sAGpsInterface->init(&sAGpsCallbacks); + } + return sAGpsInterface; +} + +static const GpsNiInterface* GetNiInterface(JNIEnv* env, jobject obj) +{ + const GpsInterface* interface = GetGpsInterface(env, obj); + if (!interface) + return NULL; + + if (!sGpsNiInterface) { + sGpsNiInterface = (const GpsNiInterface*)interface->get_extension(GPS_NI_INTERFACE); + if (sGpsNiInterface) + sGpsNiInterface->init(&sGpsNiCallbacks); + } + return sGpsNiInterface; +} + +static const AGpsRilInterface* GetAGpsRilInterface(JNIEnv* env, jobject obj) +{ + const GpsInterface* interface = GetGpsInterface(env, obj); + if (!interface) + return NULL; + + if (!sAGpsRilInterface) { + sAGpsRilInterface = (const AGpsRilInterface*)interface->get_extension(AGPS_RIL_INTERFACE); + if (sAGpsRilInterface) + sAGpsRilInterface->init(&sAGpsRilCallbacks); + } + return sAGpsRilInterface; +} + +static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env, jclass clazz) { + method_reportLocation = env->GetMethodID(clazz, "reportLocation", "(IDDDFFFJ)V"); + method_reportStatus = env->GetMethodID(clazz, "reportStatus", "(I)V"); + method_reportSvStatus = env->GetMethodID(clazz, "reportSvStatus", "()V"); + method_reportAGpsStatus = env->GetMethodID(clazz, "reportAGpsStatus", "(II)V"); + method_reportNmea = env->GetMethodID(clazz, "reportNmea", "(J)V"); + method_setEngineCapabilities = env->GetMethodID(clazz, "setEngineCapabilities", "(I)V"); + method_xtraDownloadRequest = env->GetMethodID(clazz, "xtraDownloadRequest", "()V"); + method_reportNiNotification = env->GetMethodID(clazz, "reportNiNotification", "(IIIIILjava/lang/String;Ljava/lang/String;IILjava/lang/String;)V"); + method_requestRefLocation = env->GetMethodID(clazz,"requestRefLocation","(I)V"); + method_requestSetID = env->GetMethodID(clazz,"requestSetID","(I)V"); +} + +static jboolean android_location_GpsLocationProvider_is_supported(JNIEnv* env, jclass clazz) { + return (sGpsInterface != NULL || get_gps_interface() != NULL); +} + +static jboolean android_location_GpsLocationProvider_init(JNIEnv* env, jobject obj) +{ + const GpsInterface* interface = GetGpsInterface(env, obj); + if (!interface) + return false; + + if (!sGpsDebugInterface) + sGpsDebugInterface = (const GpsDebugInterface*)interface->get_extension(GPS_DEBUG_INTERFACE); + + return true; +} + +static void android_location_GpsLocationProvider_cleanup(JNIEnv* env, jobject obj) +{ + const GpsInterface* interface = GetGpsInterface(env, obj); + if (interface) + interface->cleanup(); +} + +static jboolean android_location_GpsLocationProvider_set_position_mode(JNIEnv* env, jobject obj, + jint mode, jint recurrence, jint min_interval, jint preferred_accuracy, jint preferred_time) +{ + const GpsInterface* interface = GetGpsInterface(env, obj); + if (interface) + return (interface->set_position_mode(mode, recurrence, min_interval, preferred_accuracy, + preferred_time) == 0); + else + return false; +} + +static jboolean android_location_GpsLocationProvider_start(JNIEnv* env, jobject obj) +{ + const GpsInterface* interface = GetGpsInterface(env, obj); + if (interface) + return (interface->start() == 0); + else + return false; +} + +static jboolean android_location_GpsLocationProvider_stop(JNIEnv* env, jobject obj) +{ + const GpsInterface* interface = GetGpsInterface(env, obj); + if (interface) + return (interface->stop() == 0); + else + return false; +} + +static void android_location_GpsLocationProvider_delete_aiding_data(JNIEnv* env, jobject obj, jint flags) +{ + const GpsInterface* interface = GetGpsInterface(env, obj); + if (interface) + interface->delete_aiding_data(flags); +} + +static jint android_location_GpsLocationProvider_read_sv_status(JNIEnv* env, jobject obj, + jintArray prnArray, jfloatArray snrArray, jfloatArray elevArray, jfloatArray azumArray, + jintArray maskArray) +{ + // this should only be called from within a call to reportSvStatus + + jint* prns = env->GetIntArrayElements(prnArray, 0); + jfloat* snrs = env->GetFloatArrayElements(snrArray, 0); + jfloat* elev = env->GetFloatArrayElements(elevArray, 0); + jfloat* azim = env->GetFloatArrayElements(azumArray, 0); + jint* mask = env->GetIntArrayElements(maskArray, 0); + + int num_svs = sGpsSvStatus.num_svs; + for (int i = 0; i < num_svs; i++) { + prns[i] = sGpsSvStatus.sv_list[i].prn; + snrs[i] = sGpsSvStatus.sv_list[i].snr; + elev[i] = sGpsSvStatus.sv_list[i].elevation; + azim[i] = sGpsSvStatus.sv_list[i].azimuth; + } + mask[0] = sGpsSvStatus.ephemeris_mask; + mask[1] = sGpsSvStatus.almanac_mask; + mask[2] = sGpsSvStatus.used_in_fix_mask; + + env->ReleaseIntArrayElements(prnArray, prns, 0); + env->ReleaseFloatArrayElements(snrArray, snrs, 0); + env->ReleaseFloatArrayElements(elevArray, elev, 0); + env->ReleaseFloatArrayElements(azumArray, azim, 0); + env->ReleaseIntArrayElements(maskArray, mask, 0); + return num_svs; +} + +static void android_location_GpsLocationProvider_agps_set_reference_location_cellid(JNIEnv* env, + jobject obj, jint type, jint mcc, jint mnc, jint lac, jint cid) +{ + AGpsRefLocation location; + const AGpsRilInterface* interface = GetAGpsRilInterface(env, obj); + if (!interface) { + LOGE("no AGPS RIL interface in agps_set_reference_location_cellid"); + return; + } + + switch(type) { + case AGPS_REF_LOCATION_TYPE_GSM_CELLID: + case AGPS_REF_LOCATION_TYPE_UMTS_CELLID: + location.type = type; + location.u.cellID.mcc = mcc; + location.u.cellID.mnc = mnc; + location.u.cellID.lac = lac; + location.u.cellID.cid = cid; + break; + default: + LOGE("Neither a GSM nor a UMTS cellid (%s:%d).",__FUNCTION__,__LINE__); + return; + break; + } + interface->set_ref_location(&location, sizeof(location)); +} + +static void android_location_GpsLocationProvider_agps_send_ni_message(JNIEnv* env, + jobject obj, jbyteArray ni_msg, jint size) +{ + size_t sz; + const AGpsRilInterface* interface = GetAGpsRilInterface(env, obj); + if (!interface) { + LOGE("no AGPS RIL interface in send_ni_message"); + return; + } + if (size < 0) + return; + sz = (size_t)size; + jbyte* b = env->GetByteArrayElements(ni_msg, 0); + interface->ni_message((uint8_t *)b,sz); + env->ReleaseByteArrayElements(ni_msg,b,0); +} + +static void android_location_GpsLocationProvider_agps_set_id(JNIEnv *env, + jobject obj, jint type, jstring setid_string) +{ + const AGpsRilInterface* interface = GetAGpsRilInterface(env, obj); + if (!interface) { + LOGE("no AGPS RIL interface in agps_set_id"); + return; + } + + const char *setid = env->GetStringUTFChars(setid_string, NULL); + interface->set_set_id(type, setid); + env->ReleaseStringUTFChars(setid_string, setid); +} + +static jint android_location_GpsLocationProvider_read_nmea(JNIEnv* env, jobject obj, + jbyteArray nmeaArray, jint buffer_size) +{ + // this should only be called from within a call to reportNmea + jbyte* nmea = (jbyte *)env->GetPrimitiveArrayCritical(nmeaArray, 0); + int length = sNmeaStringLength; + if (length > buffer_size) + length = buffer_size; + memcpy(nmea, sNmeaString, length); + env->ReleasePrimitiveArrayCritical(nmeaArray, nmea, JNI_ABORT); + return length; +} + +static void android_location_GpsLocationProvider_inject_time(JNIEnv* env, jobject obj, + jlong time, jlong timeReference, jint uncertainty) +{ + const GpsInterface* interface = GetGpsInterface(env, obj); + if (interface) + interface->inject_time(time, timeReference, uncertainty); +} + +static void android_location_GpsLocationProvider_inject_location(JNIEnv* env, jobject obj, + jdouble latitude, jdouble longitude, jfloat accuracy) +{ + const GpsInterface* interface = GetGpsInterface(env, obj); + if (interface) + interface->inject_location(latitude, longitude, accuracy); +} + +static jboolean android_location_GpsLocationProvider_supports_xtra(JNIEnv* env, jobject obj) +{ + if (!sGpsXtraInterface) { + const GpsInterface* interface = GetGpsInterface(env, obj); + if (!interface) + return false; + sGpsXtraInterface = (const GpsXtraInterface*)interface->get_extension(GPS_XTRA_INTERFACE); + if (sGpsXtraInterface) { + int result = sGpsXtraInterface->init(&sGpsXtraCallbacks); + if (result) { + sGpsXtraInterface = NULL; + } + } + } + + return (sGpsXtraInterface != NULL); +} + +static void android_location_GpsLocationProvider_inject_xtra_data(JNIEnv* env, jobject obj, + jbyteArray data, jint length) +{ + jbyte* bytes = (jbyte *)env->GetPrimitiveArrayCritical(data, 0); + sGpsXtraInterface->inject_xtra_data((char *)bytes, length); + env->ReleasePrimitiveArrayCritical(data, bytes, JNI_ABORT); +} + +static void android_location_GpsLocationProvider_agps_data_conn_open(JNIEnv* env, jobject obj, jstring apn) +{ + const AGpsInterface* interface = GetAGpsInterface(env, obj); + if (!interface) { + LOGE("no AGPS interface in agps_data_conn_open"); + return; + } + if (apn == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + const char *apnStr = env->GetStringUTFChars(apn, NULL); + interface->data_conn_open(apnStr); + env->ReleaseStringUTFChars(apn, apnStr); +} + +static void android_location_GpsLocationProvider_agps_data_conn_closed(JNIEnv* env, jobject obj) +{ + const AGpsInterface* interface = GetAGpsInterface(env, obj); + if (!interface) { + LOGE("no AGPS interface in agps_data_conn_open"); + return; + } + interface->data_conn_closed(); +} + +static void android_location_GpsLocationProvider_agps_data_conn_failed(JNIEnv* env, jobject obj) +{ + const AGpsInterface* interface = GetAGpsInterface(env, obj); + if (!interface) { + LOGE("no AGPS interface in agps_data_conn_open"); + return; + } + interface->data_conn_failed(); +} + +static void android_location_GpsLocationProvider_set_agps_server(JNIEnv* env, jobject obj, + jint type, jstring hostname, jint port) +{ + const AGpsInterface* interface = GetAGpsInterface(env, obj); + if (!interface) { + LOGE("no AGPS interface in agps_data_conn_open"); + return; + } + const char *c_hostname = env->GetStringUTFChars(hostname, NULL); + interface->set_server(type, c_hostname, port); + env->ReleaseStringUTFChars(hostname, c_hostname); +} + +static void android_location_GpsLocationProvider_send_ni_response(JNIEnv* env, jobject obj, + jint notifId, jint response) +{ + const GpsNiInterface* interface = GetNiInterface(env, obj); + if (!interface) { + LOGE("no NI interface in send_ni_response"); + return; + } + + interface->respond(notifId, response); +} + +static jstring android_location_GpsLocationProvider_get_internal_state(JNIEnv* env, jobject obj) +{ + jstring result = NULL; + if (sGpsDebugInterface) { + const size_t maxLength = 2047; + char buffer[maxLength+1]; + size_t length = sGpsDebugInterface->get_internal_state(buffer, maxLength); + if (length > maxLength) length = maxLength; + buffer[length] = 0; + result = env->NewStringUTF(buffer); + } + return result; +} + +static void android_location_GpsLocationProvider_update_network_state(JNIEnv* env, jobject obj, + jboolean connected, int type, jboolean roaming, jstring extraInfo) +{ + const AGpsRilInterface* interface = GetAGpsRilInterface(env, obj); + if (interface && interface->update_network_state) { + if (extraInfo) { + const char *extraInfoStr = env->GetStringUTFChars(extraInfo, NULL); + interface->update_network_state(connected, type, roaming, extraInfoStr); + env->ReleaseStringUTFChars(extraInfo, extraInfoStr); + } else { + interface->update_network_state(connected, type, roaming, NULL); + } + } +} + +static JNINativeMethod sMethods[] = { + /* name, signature, funcPtr */ + {"class_init_native", "()V", (void *)android_location_GpsLocationProvider_class_init_native}, + {"native_is_supported", "()Z", (void*)android_location_GpsLocationProvider_is_supported}, + {"native_init", "()Z", (void*)android_location_GpsLocationProvider_init}, + {"native_cleanup", "()V", (void*)android_location_GpsLocationProvider_cleanup}, + {"native_set_position_mode", "(IIIII)Z", (void*)android_location_GpsLocationProvider_set_position_mode}, + {"native_start", "()Z", (void*)android_location_GpsLocationProvider_start}, + {"native_stop", "()Z", (void*)android_location_GpsLocationProvider_stop}, + {"native_delete_aiding_data", "(I)V", (void*)android_location_GpsLocationProvider_delete_aiding_data}, + {"native_read_sv_status", "([I[F[F[F[I)I", (void*)android_location_GpsLocationProvider_read_sv_status}, + {"native_read_nmea", "([BI)I", (void*)android_location_GpsLocationProvider_read_nmea}, + {"native_inject_time", "(JJI)V", (void*)android_location_GpsLocationProvider_inject_time}, + {"native_inject_location", "(DDF)V", (void*)android_location_GpsLocationProvider_inject_location}, + {"native_supports_xtra", "()Z", (void*)android_location_GpsLocationProvider_supports_xtra}, + {"native_inject_xtra_data", "([BI)V", (void*)android_location_GpsLocationProvider_inject_xtra_data}, + {"native_agps_data_conn_open", "(Ljava/lang/String;)V", (void*)android_location_GpsLocationProvider_agps_data_conn_open}, + {"native_agps_data_conn_closed", "()V", (void*)android_location_GpsLocationProvider_agps_data_conn_closed}, + {"native_agps_data_conn_failed", "()V", (void*)android_location_GpsLocationProvider_agps_data_conn_failed}, + {"native_agps_set_id","(ILjava/lang/String;)V",(void*)android_location_GpsLocationProvider_agps_set_id}, + {"native_agps_set_ref_location_cellid","(IIIII)V",(void*)android_location_GpsLocationProvider_agps_set_reference_location_cellid}, + {"native_set_agps_server", "(ILjava/lang/String;I)V", (void*)android_location_GpsLocationProvider_set_agps_server}, + {"native_send_ni_response", "(II)V", (void*)android_location_GpsLocationProvider_send_ni_response}, + {"native_agps_ni_message", "([BI)V", (void *)android_location_GpsLocationProvider_agps_send_ni_message}, + {"native_get_internal_state", "()Ljava/lang/String;", (void*)android_location_GpsLocationProvider_get_internal_state}, + {"native_update_network_state", "(ZIZLjava/lang/String;)V", (void*)android_location_GpsLocationProvider_update_network_state }, +}; + +int register_android_server_location_GpsLocationProvider(JNIEnv* env) +{ + return jniRegisterNativeMethods(env, "com/android/server/location/GpsLocationProvider", sMethods, NELEM(sMethods)); +} + +} /* namespace android */ diff --git a/services/jni/onload.cpp b/services/jni/onload.cpp index c16fdb8..cd4f0a4 100644 --- a/services/jni/onload.cpp +++ b/services/jni/onload.cpp @@ -6,11 +6,12 @@ namespace android { int register_android_server_AlarmManagerService(JNIEnv* env); int register_android_server_BatteryService(JNIEnv* env); -int register_android_server_KeyInputQueue(JNIEnv* env); +int register_android_server_InputManager(JNIEnv* env); int register_android_server_LightsService(JNIEnv* env); -int register_android_server_SensorService(JNIEnv* env); +int register_android_server_PowerManagerService(JNIEnv* env); int register_android_server_VibratorService(JNIEnv* env); int register_android_server_SystemServer(JNIEnv* env); +int register_android_server_location_GpsLocationProvider(JNIEnv* env); }; using namespace android; @@ -26,13 +27,14 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) } LOG_ASSERT(env, "Could not retrieve the env!"); - register_android_server_KeyInputQueue(env); + register_android_server_PowerManagerService(env); + register_android_server_InputManager(env); register_android_server_LightsService(env); register_android_server_AlarmManagerService(env); register_android_server_BatteryService(env); - register_android_server_SensorService(env); register_android_server_VibratorService(env); register_android_server_SystemServer(env); + register_android_server_location_GpsLocationProvider(env); return JNI_VERSION_1_4; } diff --git a/services/sensorservice/Android.mk b/services/sensorservice/Android.mk new file mode 100644 index 0000000..7e17fdd --- /dev/null +++ b/services/sensorservice/Android.mk @@ -0,0 +1,34 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + GravitySensor.cpp \ + LinearAccelerationSensor.cpp \ + RotationVectorSensor.cpp \ + SensorService.cpp \ + SensorInterface.cpp \ + SensorDevice.cpp \ + SecondOrderLowPassFilter.cpp + +LOCAL_CFLAGS:= -DLOG_TAG=\"SensorService\" + +# need "-lrt" on Linux simulator to pick up clock_gettime +ifeq ($(TARGET_SIMULATOR),true) + ifeq ($(HOST_OS),linux) + LOCAL_LDLIBS += -lrt -lpthread + endif +endif + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libhardware \ + libutils \ + libbinder \ + libui \ + libgui + +LOCAL_PRELINK_MODULE := false + +LOCAL_MODULE:= libsensorservice + +include $(BUILD_SHARED_LIBRARY) diff --git a/services/sensorservice/GravitySensor.cpp b/services/sensorservice/GravitySensor.cpp new file mode 100644 index 0000000..18bd359 --- /dev/null +++ b/services/sensorservice/GravitySensor.cpp @@ -0,0 +1,112 @@ +/* + * 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 <math.h> +#include <sys/types.h> + +#include <utils/Errors.h> + +#include <hardware/sensors.h> + +#include "GravitySensor.h" + +namespace android { +// --------------------------------------------------------------------------- + +GravitySensor::GravitySensor(sensor_t const* list, size_t count) + : mSensorDevice(SensorDevice::getInstance()), + mEnabled(false), mAccTime(0), + mLowPass(M_SQRT1_2, 1), + mX(mLowPass), mY(mLowPass), mZ(mLowPass) + +{ + for (size_t i=0 ; i<count ; i++) { + if (list[i].type == SENSOR_TYPE_ACCELEROMETER) { + mAccelerometer = Sensor(list + i); + break; + } + } +} + +bool GravitySensor::process(sensors_event_t* outEvent, + const sensors_event_t& event) +{ + const static double NS2S = 1.0 / 1000000000.0; + if (event.type == SENSOR_TYPE_ACCELEROMETER) { + float x, y, z; + const double now = event.timestamp * NS2S; + if (mAccTime == 0) { + x = mX.init(event.acceleration.x); + y = mY.init(event.acceleration.y); + z = mZ.init(event.acceleration.z); + } else { + double dT = now - mAccTime; + mLowPass.setSamplingPeriod(dT); + x = mX(event.acceleration.x); + y = mY(event.acceleration.y); + z = mZ(event.acceleration.z); + } + mAccTime = now; + *outEvent = event; + outEvent->data[0] = x; + outEvent->data[1] = y; + outEvent->data[2] = z; + outEvent->sensor = '_grv'; + outEvent->type = SENSOR_TYPE_GRAVITY; + return true; + } + return false; +} + +bool GravitySensor::isEnabled() const { + return mEnabled; +} + +status_t GravitySensor::activate(void* ident, bool enabled) { + status_t err = mSensorDevice.activate(this, mAccelerometer.getHandle(), enabled); + if (err == NO_ERROR) { + mEnabled = enabled; + if (enabled) { + mAccTime = 0; + } + } + return err; +} + +status_t GravitySensor::setDelay(void* ident, int handle, int64_t ns) +{ + return mSensorDevice.setDelay(this, mAccelerometer.getHandle(), ns); +} + +Sensor GravitySensor::getSensor() const { + sensor_t hwSensor; + hwSensor.name = "Gravity Sensor"; + hwSensor.vendor = "Google Inc."; + hwSensor.version = 1; + hwSensor.handle = '_grv'; + hwSensor.type = SENSOR_TYPE_GRAVITY; + hwSensor.maxRange = mAccelerometer.getMaxValue(); + hwSensor.resolution = mAccelerometer.getResolution(); + hwSensor.power = mAccelerometer.getPowerUsage(); + hwSensor.minDelay = mAccelerometer.getMinDelay(); + Sensor sensor(&hwSensor); + return sensor; +} + +// --------------------------------------------------------------------------- +}; // namespace android + diff --git a/services/sensorservice/GravitySensor.h b/services/sensorservice/GravitySensor.h new file mode 100644 index 0000000..f9850b7 --- /dev/null +++ b/services/sensorservice/GravitySensor.h @@ -0,0 +1,56 @@ +/* + * 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_GRAVITY_SENSOR_H +#define ANDROID_GRAVITY_SENSOR_H + +#include <stdint.h> +#include <sys/types.h> + +#include <gui/Sensor.h> + +#include "SensorDevice.h" +#include "SensorInterface.h" +#include "SecondOrderLowPassFilter.h" + +// --------------------------------------------------------------------------- +namespace android { +// --------------------------------------------------------------------------- + +class GravitySensor : public SensorInterface { + SensorDevice& mSensorDevice; + Sensor mAccelerometer; + bool mEnabled; + double mAccTime; + + SecondOrderLowPassFilter mLowPass; + BiquadFilter mX, mY, mZ; + +public: + GravitySensor(sensor_t const* list, size_t count); + virtual bool process(sensors_event_t* outEvent, + const sensors_event_t& event); + virtual bool isEnabled() const; + virtual status_t activate(void* ident, bool enabled); + virtual status_t setDelay(void* ident, int handle, int64_t ns); + virtual Sensor getSensor() const; + virtual bool isVirtual() const { return true; } +}; + +// --------------------------------------------------------------------------- +}; // namespace android + +#endif // ANDROID_GRAVITY_SENSOR_H diff --git a/services/sensorservice/LinearAccelerationSensor.cpp b/services/sensorservice/LinearAccelerationSensor.cpp new file mode 100644 index 0000000..2dc12dc --- /dev/null +++ b/services/sensorservice/LinearAccelerationSensor.cpp @@ -0,0 +1,86 @@ +/* + * 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 <math.h> +#include <sys/types.h> + +#include <utils/Errors.h> + +#include <hardware/sensors.h> + +#include "LinearAccelerationSensor.h" + +namespace android { +// --------------------------------------------------------------------------- + +LinearAccelerationSensor::LinearAccelerationSensor(sensor_t const* list, size_t count) + : mSensorDevice(SensorDevice::getInstance()), + mGravitySensor(list, count) +{ + mData[0] = mData[1] = mData[2] = 0; +} + +bool LinearAccelerationSensor::process(sensors_event_t* outEvent, + const sensors_event_t& event) +{ + bool result = mGravitySensor.process(outEvent, event); + if (result) { + if (event.type == SENSOR_TYPE_ACCELEROMETER) { + mData[0] = event.acceleration.x; + mData[1] = event.acceleration.y; + mData[2] = event.acceleration.z; + } + outEvent->data[0] = mData[0] - outEvent->data[0]; + outEvent->data[1] = mData[1] - outEvent->data[1]; + outEvent->data[2] = mData[2] - outEvent->data[2]; + outEvent->sensor = '_lin'; + outEvent->type = SENSOR_TYPE_LINEAR_ACCELERATION; + } + return result; +} + +bool LinearAccelerationSensor::isEnabled() const { + return mGravitySensor.isEnabled(); +} + +status_t LinearAccelerationSensor::activate(void* ident, bool enabled) { + return mGravitySensor.activate(ident, enabled); +} + +status_t LinearAccelerationSensor::setDelay(void* ident, int handle, int64_t ns) { + return mGravitySensor.setDelay(ident, handle, ns); +} + +Sensor LinearAccelerationSensor::getSensor() const { + Sensor gsensor(mGravitySensor.getSensor()); + sensor_t hwSensor; + hwSensor.name = "Linear Acceleration Sensor"; + hwSensor.vendor = "Google Inc."; + hwSensor.version = 1; + hwSensor.handle = '_lin'; + hwSensor.type = SENSOR_TYPE_LINEAR_ACCELERATION; + hwSensor.maxRange = gsensor.getMaxValue(); + hwSensor.resolution = gsensor.getResolution(); + hwSensor.power = gsensor.getPowerUsage(); + hwSensor.minDelay = gsensor.getMinDelay(); + Sensor sensor(&hwSensor); + return sensor; +} + +// --------------------------------------------------------------------------- +}; // namespace android + diff --git a/services/sensorservice/LinearAccelerationSensor.h b/services/sensorservice/LinearAccelerationSensor.h new file mode 100644 index 0000000..ee918ce --- /dev/null +++ b/services/sensorservice/LinearAccelerationSensor.h @@ -0,0 +1,53 @@ +/* + * 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_LINEAR_ACCELERATION_SENSOR_H +#define ANDROID_LINEAR_ACCELERATION_SENSOR_H + +#include <stdint.h> +#include <sys/types.h> + +#include <gui/Sensor.h> + +#include "SensorDevice.h" +#include "SensorInterface.h" +#include "GravitySensor.h" + +// --------------------------------------------------------------------------- + +namespace android { +// --------------------------------------------------------------------------- + +class LinearAccelerationSensor : public SensorInterface { + SensorDevice& mSensorDevice; + GravitySensor mGravitySensor; + float mData[3]; + + virtual bool process(sensors_event_t* outEvent, + const sensors_event_t& event); +public: + LinearAccelerationSensor(sensor_t const* list, size_t count); + virtual bool isEnabled() const; + virtual status_t activate(void* ident, bool enabled); + virtual status_t setDelay(void* ident, int handle, int64_t ns); + virtual Sensor getSensor() const; + virtual bool isVirtual() const { return true; } +}; + +// --------------------------------------------------------------------------- +}; // namespace android + +#endif // ANDROID_LINEAR_ACCELERATION_SENSOR_H diff --git a/services/sensorservice/RotationVectorSensor.cpp b/services/sensorservice/RotationVectorSensor.cpp new file mode 100644 index 0000000..6f4b8be --- /dev/null +++ b/services/sensorservice/RotationVectorSensor.cpp @@ -0,0 +1,174 @@ +/* + * 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 <math.h> +#include <sys/types.h> + +#include <utils/Errors.h> + +#include <hardware/sensors.h> + +#include "RotationVectorSensor.h" + +namespace android { +// --------------------------------------------------------------------------- + +template <typename T> +static inline T clamp(T v) { + return v < 0 ? 0 : v; +} + +RotationVectorSensor::RotationVectorSensor(sensor_t const* list, size_t count) + : mSensorDevice(SensorDevice::getInstance()), + mEnabled(false), + mALowPass(M_SQRT1_2, 5.0f), + mAX(mALowPass), mAY(mALowPass), mAZ(mALowPass), + mMLowPass(M_SQRT1_2, 2.5f), + mMX(mMLowPass), mMY(mMLowPass), mMZ(mMLowPass) +{ + for (size_t i=0 ; i<count ; i++) { + if (list[i].type == SENSOR_TYPE_ACCELEROMETER) { + mAcc = Sensor(list + i); + } + if (list[i].type == SENSOR_TYPE_MAGNETIC_FIELD) { + mMag = Sensor(list + i); + } + } + memset(mMagData, 0, sizeof(mMagData)); +} + +bool RotationVectorSensor::process(sensors_event_t* outEvent, + const sensors_event_t& event) +{ + const static double NS2S = 1.0 / 1000000000.0; + if (event.type == SENSOR_TYPE_MAGNETIC_FIELD) { + const double now = event.timestamp * NS2S; + if (mMagTime == 0) { + mMagData[0] = mMX.init(event.magnetic.x); + mMagData[1] = mMY.init(event.magnetic.y); + mMagData[2] = mMZ.init(event.magnetic.z); + } else { + double dT = now - mMagTime; + mMLowPass.setSamplingPeriod(dT); + mMagData[0] = mMX(event.magnetic.x); + mMagData[1] = mMY(event.magnetic.y); + mMagData[2] = mMZ(event.magnetic.z); + } + mMagTime = now; + } + if (event.type == SENSOR_TYPE_ACCELEROMETER) { + const double now = event.timestamp * NS2S; + float Ax, Ay, Az; + if (mAccTime == 0) { + Ax = mAX.init(event.acceleration.x); + Ay = mAY.init(event.acceleration.y); + Az = mAZ.init(event.acceleration.z); + } else { + double dT = now - mAccTime; + mALowPass.setSamplingPeriod(dT); + Ax = mAX(event.acceleration.x); + Ay = mAY(event.acceleration.y); + Az = mAZ(event.acceleration.z); + } + mAccTime = now; + const float Ex = mMagData[0]; + const float Ey = mMagData[1]; + const float Ez = mMagData[2]; + float Hx = Ey*Az - Ez*Ay; + float Hy = Ez*Ax - Ex*Az; + float Hz = Ex*Ay - Ey*Ax; + const float normH = sqrtf(Hx*Hx + Hy*Hy + Hz*Hz); + if (normH < 0.1f) { + // device is close to free fall (or in space?), or close to + // magnetic north pole. Typical values are > 100. + return false; + } + const float invH = 1.0f / normH; + const float invA = 1.0f / sqrtf(Ax*Ax + Ay*Ay + Az*Az); + Hx *= invH; + Hy *= invH; + Hz *= invH; + Ax *= invA; + Ay *= invA; + Az *= invA; + const float Mx = Ay*Hz - Az*Hy; + const float My = Az*Hx - Ax*Hz; + const float Mz = Ax*Hy - Ay*Hx; + + // matrix to rotation vector (normalized quaternion) + float qw = sqrtf( clamp( Hx + My + Az + 1) * 0.25f ); + float qx = sqrtf( clamp( Hx - My - Az + 1) * 0.25f ); + float qy = sqrtf( clamp(-Hx + My - Az + 1) * 0.25f ); + float qz = sqrtf( clamp(-Hx - My + Az + 1) * 0.25f ); + const float n = 1.0f / (qw*qw + qx*qx + qy*qy + qz*qz); + qx = copysignf(qx, Ay - Mz) * n; + qy = copysignf(qy, Hz - Ax) * n; + qz = copysignf(qz, Mx - Hy) * n; + + *outEvent = event; + outEvent->data[0] = qx; + outEvent->data[1] = qy; + outEvent->data[2] = qz; + outEvent->sensor = '_rov'; + outEvent->type = SENSOR_TYPE_ROTATION_VECTOR; + return true; + } + return false; +} + +bool RotationVectorSensor::isEnabled() const { + return mEnabled; +} + +status_t RotationVectorSensor::activate(void* ident, bool enabled) { + if (mEnabled != enabled) { + mSensorDevice.activate(this, mAcc.getHandle(), enabled); + mSensorDevice.activate(this, mMag.getHandle(), enabled); + mEnabled = enabled; + if (enabled) { + mMagTime = 0; + mAccTime = 0; + } + } + return NO_ERROR; +} + +status_t RotationVectorSensor::setDelay(void* ident, int handle, int64_t ns) +{ + mSensorDevice.setDelay(this, mAcc.getHandle(), ns); + mSensorDevice.setDelay(this, mMag.getHandle(), ns); + return NO_ERROR; +} + +Sensor RotationVectorSensor::getSensor() const { + sensor_t hwSensor; + hwSensor.name = "Rotation Vector Sensor"; + hwSensor.vendor = "Google Inc."; + hwSensor.version = 1; + hwSensor.handle = '_rov'; + hwSensor.type = SENSOR_TYPE_ROTATION_VECTOR; + hwSensor.maxRange = 1; + hwSensor.resolution = 1.0f / (1<<24); + hwSensor.power = mAcc.getPowerUsage() + mMag.getPowerUsage(); + hwSensor.minDelay = mAcc.getMinDelay(); + Sensor sensor(&hwSensor); + return sensor; +} + +// --------------------------------------------------------------------------- +}; // namespace android + diff --git a/services/sensorservice/RotationVectorSensor.h b/services/sensorservice/RotationVectorSensor.h new file mode 100644 index 0000000..e7f28c9 --- /dev/null +++ b/services/sensorservice/RotationVectorSensor.h @@ -0,0 +1,60 @@ +/* + * 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_ROTATION_VECTOR_SENSOR_H +#define ANDROID_ROTATION_VECTOR_SENSOR_H + +#include <stdint.h> +#include <sys/types.h> + +#include <gui/Sensor.h> + +#include "SensorDevice.h" +#include "SensorInterface.h" +#include "SecondOrderLowPassFilter.h" + +// --------------------------------------------------------------------------- +namespace android { +// --------------------------------------------------------------------------- + +class RotationVectorSensor : public SensorInterface { + SensorDevice& mSensorDevice; + Sensor mAcc; + Sensor mMag; + bool mEnabled; + float mMagData[3]; + double mAccTime; + double mMagTime; + SecondOrderLowPassFilter mALowPass; + BiquadFilter mAX, mAY, mAZ; + SecondOrderLowPassFilter mMLowPass; + BiquadFilter mMX, mMY, mMZ; + +public: + RotationVectorSensor(sensor_t const* list, size_t count); + virtual bool process(sensors_event_t* outEvent, + const sensors_event_t& event); + virtual bool isEnabled() const; + virtual status_t activate(void* ident, bool enabled); + virtual status_t setDelay(void* ident, int handle, int64_t ns); + virtual Sensor getSensor() const; + virtual bool isVirtual() const { return true; } +}; + +// --------------------------------------------------------------------------- +}; // namespace android + +#endif // ANDROID_ROTATION_VECTOR_SENSOR_H diff --git a/services/sensorservice/SecondOrderLowPassFilter.cpp b/services/sensorservice/SecondOrderLowPassFilter.cpp new file mode 100644 index 0000000..e13e136 --- /dev/null +++ b/services/sensorservice/SecondOrderLowPassFilter.cpp @@ -0,0 +1,70 @@ +/* + * 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 <sys/types.h> +#include <math.h> + +#include <cutils/log.h> + +#include "SecondOrderLowPassFilter.h" + +// --------------------------------------------------------------------------- + +namespace android { +// --------------------------------------------------------------------------- + +SecondOrderLowPassFilter::SecondOrderLowPassFilter(float Q, float fc) + : iQ(1.0f / Q), fc(fc) +{ +} + +void SecondOrderLowPassFilter::setSamplingPeriod(float dT) +{ + K = tanf(float(M_PI) * fc * dT); + iD = 1.0f / (K*K + K*iQ + 1); + a0 = K*K*iD; + a1 = 2.0f * a0; + b1 = 2.0f*(K*K - 1)*iD; + b2 = (K*K - K*iQ + 1)*iD; +} + +// --------------------------------------------------------------------------- + +BiquadFilter::BiquadFilter(const SecondOrderLowPassFilter& s) + : s(s) +{ +} + +float BiquadFilter::init(float x) +{ + x1 = x2 = x; + y1 = y2 = x; + return x; +} + +float BiquadFilter::operator()(float x) +{ + float y = (x + x2)*s.a0 + x1*s.a1 - y1*s.b1 - y2*s.b2; + x2 = x1; + y2 = y1; + x1 = x; + y1 = y; + return y; +} + +// --------------------------------------------------------------------------- +}; // namespace android diff --git a/services/sensorservice/SecondOrderLowPassFilter.h b/services/sensorservice/SecondOrderLowPassFilter.h new file mode 100644 index 0000000..998ca35 --- /dev/null +++ b/services/sensorservice/SecondOrderLowPassFilter.h @@ -0,0 +1,61 @@ +/* + * 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_SECOND_ORDER_LOW_PASS_FILTER_H +#define ANDROID_SECOND_ORDER_LOW_PASS_FILTER_H + +#include <stdint.h> +#include <sys/types.h> + +// --------------------------------------------------------------------------- + +namespace android { +// --------------------------------------------------------------------------- + +class BiquadFilter; + +/* + * State of a 2nd order low-pass IIR filter + */ +class SecondOrderLowPassFilter { + friend class BiquadFilter; + float iQ, fc; + float K, iD; + float a0, a1; + float b1, b2; +public: + SecondOrderLowPassFilter(float Q, float fc); + void setSamplingPeriod(float dT); +}; + +/* + * Implements a Biquad IIR filter + */ +class BiquadFilter { + float x1, x2; + float y1, y2; + const SecondOrderLowPassFilter& s; +public: + BiquadFilter(const SecondOrderLowPassFilter& s); + float init(float in); + float operator()(float in); +}; + + +// --------------------------------------------------------------------------- +}; // namespace android + +#endif // ANDROID_SECOND_ORDER_LOW_PASS_FILTER_H diff --git a/services/sensorservice/SensorDevice.cpp b/services/sensorservice/SensorDevice.cpp new file mode 100644 index 0000000..73f85ba --- /dev/null +++ b/services/sensorservice/SensorDevice.cpp @@ -0,0 +1,239 @@ +/* + * 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 <math.h> +#include <sys/types.h> + +#include <utils/Atomic.h> +#include <utils/Errors.h> +#include <utils/Singleton.h> + +#include <binder/BinderService.h> +#include <binder/Parcel.h> +#include <binder/IServiceManager.h> + +#include <hardware/sensors.h> + +#include "SensorDevice.h" + +namespace android { +// --------------------------------------------------------------------------- +class BatteryService : public Singleton<BatteryService> { + static const int TRANSACTION_noteStartSensor = IBinder::FIRST_CALL_TRANSACTION + 3; + static const int TRANSACTION_noteStopSensor = IBinder::FIRST_CALL_TRANSACTION + 4; + static const String16 DESCRIPTOR; + + friend class Singleton<BatteryService>; + sp<IBinder> mBatteryStatService; + + BatteryService() { + const sp<IServiceManager> sm(defaultServiceManager()); + if (sm != NULL) { + const String16 name("batteryinfo"); + mBatteryStatService = sm->getService(name); + } + } + + status_t noteStartSensor(int uid, int handle) { + Parcel data, reply; + data.writeInterfaceToken(DESCRIPTOR); + data.writeInt32(uid); + data.writeInt32(handle); + status_t err = mBatteryStatService->transact( + TRANSACTION_noteStartSensor, data, &reply, 0); + err = reply.readExceptionCode(); + return err; + } + + status_t noteStopSensor(int uid, int handle) { + Parcel data, reply; + data.writeInterfaceToken(DESCRIPTOR); + data.writeInt32(uid); + data.writeInt32(handle); + status_t err = mBatteryStatService->transact( + TRANSACTION_noteStopSensor, data, &reply, 0); + err = reply.readExceptionCode(); + return err; + } + +public: + void enableSensor(int handle) { + if (mBatteryStatService != 0) { + int uid = IPCThreadState::self()->getCallingUid(); + int64_t identity = IPCThreadState::self()->clearCallingIdentity(); + noteStartSensor(uid, handle); + IPCThreadState::self()->restoreCallingIdentity(identity); + } + } + void disableSensor(int handle) { + if (mBatteryStatService != 0) { + int uid = IPCThreadState::self()->getCallingUid(); + int64_t identity = IPCThreadState::self()->clearCallingIdentity(); + noteStopSensor(uid, handle); + IPCThreadState::self()->restoreCallingIdentity(identity); + } + } +}; + +const String16 BatteryService::DESCRIPTOR("com.android.internal.app.IBatteryStats"); + +ANDROID_SINGLETON_STATIC_INSTANCE(BatteryService) + +// --------------------------------------------------------------------------- + +ANDROID_SINGLETON_STATIC_INSTANCE(SensorDevice) + +SensorDevice::SensorDevice() + : mSensorDevice(0), + mSensorModule(0) +{ + status_t err = hw_get_module(SENSORS_HARDWARE_MODULE_ID, + (hw_module_t const**)&mSensorModule); + + LOGE_IF(err, "couldn't load %s module (%s)", + SENSORS_HARDWARE_MODULE_ID, strerror(-err)); + + if (mSensorModule) { + err = sensors_open(&mSensorModule->common, &mSensorDevice); + + LOGE_IF(err, "couldn't open device for module %s (%s)", + SENSORS_HARDWARE_MODULE_ID, strerror(-err)); + + if (mSensorDevice) { + sensor_t const* list; + ssize_t count = mSensorModule->get_sensors_list(mSensorModule, &list); + mActivationCount.setCapacity(count); + Info model; + for (size_t i=0 ; i<size_t(count) ; i++) { + mActivationCount.add(list[i].handle, model); + mSensorDevice->activate(mSensorDevice, list[i].handle, 0); + } + } + } +} + +void SensorDevice::dump(String8& result, char* buffer, size_t SIZE) +{ + if (!mSensorModule) return; + sensor_t const* list; + ssize_t count = mSensorModule->get_sensors_list(mSensorModule, &list); + + snprintf(buffer, SIZE, "%d h/w sensors:\n", int(count)); + result.append(buffer); + + Mutex::Autolock _l(mLock); + for (size_t i=0 ; i<size_t(count) ; i++) { + snprintf(buffer, SIZE, "handle=0x%08x, active-count=%d / %d\n", + list[i].handle, + mActivationCount.valueFor(list[i].handle).count, + mActivationCount.valueFor(list[i].handle).rates.size()); + result.append(buffer); + } +} + +ssize_t SensorDevice::getSensorList(sensor_t const** list) { + if (!mSensorModule) return NO_INIT; + ssize_t count = mSensorModule->get_sensors_list(mSensorModule, list); + return count; +} + +status_t SensorDevice::initCheck() const { + return mSensorDevice && mSensorModule ? NO_ERROR : NO_INIT; +} + +ssize_t SensorDevice::poll(sensors_event_t* buffer, size_t count) { + if (!mSensorDevice) return NO_INIT; + return mSensorDevice->poll(mSensorDevice, buffer, count); +} + +status_t SensorDevice::activate(void* ident, int handle, int enabled) +{ + if (!mSensorDevice) return NO_INIT; + status_t err(NO_ERROR); + bool actuateHardware = false; + + Info& info( mActivationCount.editValueFor(handle) ); + int32_t& count(info.count); + if (enabled) { + if (android_atomic_inc(&count) == 0) { + actuateHardware = true; + } + Mutex::Autolock _l(mLock); + if (info.rates.indexOfKey(ident) < 0) { + info.rates.add(ident, DEFAULT_EVENTS_PERIOD); + } + } else { + if (android_atomic_dec(&count) == 1) { + actuateHardware = true; + } + Mutex::Autolock _l(mLock); + info.rates.removeItem(ident); + } + if (actuateHardware) { + err = mSensorDevice->activate(mSensorDevice, handle, enabled); + if (enabled) { + LOGE_IF(err, "Error activating sensor %d (%s)", handle, strerror(-err)); + if (err == 0) { + BatteryService::getInstance().enableSensor(handle); + } + } else { + if (err == 0) { + BatteryService::getInstance().disableSensor(handle); + } + } + } + + if (!actuateHardware || enabled) { + Mutex::Autolock _l(mLock); + nsecs_t ns = info.rates.valueAt(0); + for (size_t i=1 ; i<info.rates.size() ; i++) { + if (info.rates.valueAt(i) < ns) { + nsecs_t cur = info.rates.valueAt(i); + if (cur < ns) { + ns = cur; + } + } + } + mSensorDevice->setDelay(mSensorDevice, handle, ns); + } + + return err; +} + +status_t SensorDevice::setDelay(void* ident, int handle, int64_t ns) +{ + if (!mSensorDevice) return NO_INIT; + Info& info( mActivationCount.editValueFor(handle) ); + { // scope for lock + Mutex::Autolock _l(mLock); + ssize_t index = info.rates.indexOfKey(ident); + if (index < 0) return BAD_INDEX; + info.rates.editValueAt(index) = ns; + ns = info.rates.valueAt(0); + for (size_t i=1 ; i<info.rates.size() ; i++) { + nsecs_t cur = info.rates.valueAt(i); + if (cur < ns) { + ns = cur; + } + } + } + return mSensorDevice->setDelay(mSensorDevice, handle, ns); +} + +// --------------------------------------------------------------------------- +}; // namespace android + diff --git a/services/sensorservice/SensorDevice.h b/services/sensorservice/SensorDevice.h new file mode 100644 index 0000000..63ecbcd --- /dev/null +++ b/services/sensorservice/SensorDevice.h @@ -0,0 +1,62 @@ +/* + * 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_SENSOR_DEVICE_H +#define ANDROID_SENSOR_DEVICE_H + +#include <stdint.h> +#include <sys/types.h> + +#include <utils/KeyedVector.h> +#include <utils/Singleton.h> +#include <utils/String8.h> + +#include <gui/Sensor.h> + +// --------------------------------------------------------------------------- + +namespace android { +// --------------------------------------------------------------------------- + +static const nsecs_t DEFAULT_EVENTS_PERIOD = 200000000; // 5 Hz + +class SensorDevice : public Singleton<SensorDevice> { + friend class Singleton<SensorDevice>; + struct sensors_poll_device_t* mSensorDevice; + struct sensors_module_t* mSensorModule; + Mutex mLock; // protect mActivationCount[].rates + // fixed-size array after construction + struct Info { + Info() : count(0) { } + int32_t count; + KeyedVector<void*, nsecs_t> rates; + }; + DefaultKeyedVector<int, Info> mActivationCount; + + SensorDevice(); +public: + ssize_t getSensorList(sensor_t const** list); + status_t initCheck() const; + ssize_t poll(sensors_event_t* buffer, size_t count); + status_t activate(void* ident, int handle, int enabled); + status_t setDelay(void* ident, int handle, int64_t ns); + void dump(String8& result, char* buffer, size_t SIZE); +}; + +// --------------------------------------------------------------------------- +}; // namespace android + +#endif // ANDROID_SENSOR_DEVICE_H diff --git a/services/sensorservice/SensorInterface.cpp b/services/sensorservice/SensorInterface.cpp new file mode 100644 index 0000000..93d23d9 --- /dev/null +++ b/services/sensorservice/SensorInterface.cpp @@ -0,0 +1,70 @@ +/* + * 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 <sys/types.h> + +#include <cutils/log.h> + +#include "SensorInterface.h" + +namespace android { +// --------------------------------------------------------------------------- + +SensorInterface::~SensorInterface() +{ +} + +// --------------------------------------------------------------------------- + +HardwareSensor::HardwareSensor(const sensor_t& sensor) + : mSensorDevice(SensorDevice::getInstance()), + mSensor(&sensor), mEnabled(false) +{ + LOGI("%s", sensor.name); +} + +HardwareSensor::~HardwareSensor() { +} + +bool HardwareSensor::process(sensors_event_t* outEvent, + const sensors_event_t& event) { + *outEvent = event; + return true; +} + +bool HardwareSensor::isEnabled() const { + return mEnabled; +} + +status_t HardwareSensor::activate(void* ident,bool enabled) { + status_t err = mSensorDevice.activate(ident, mSensor.getHandle(), enabled); + if (err == NO_ERROR) + mEnabled = enabled; + return err; +} + +status_t HardwareSensor::setDelay(void* ident, int handle, int64_t ns) { + return mSensorDevice.setDelay(ident, handle, ns); +} + +Sensor HardwareSensor::getSensor() const { + return mSensor; +} + + +// --------------------------------------------------------------------------- +}; // namespace android diff --git a/services/sensorservice/SensorInterface.h b/services/sensorservice/SensorInterface.h new file mode 100644 index 0000000..eebd563 --- /dev/null +++ b/services/sensorservice/SensorInterface.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_SENSOR_INTERFACE_H +#define ANDROID_SENSOR_INTERFACE_H + +#include <stdint.h> +#include <sys/types.h> + +#include <utils/Singleton.h> + +#include <gui/Sensor.h> + +#include "SensorDevice.h" + +// --------------------------------------------------------------------------- + +namespace android { +// --------------------------------------------------------------------------- + +class SensorInterface { +public: + virtual ~SensorInterface(); + + virtual bool process(sensors_event_t* outEvent, + const sensors_event_t& event) = 0; + + virtual bool isEnabled() const = 0; + virtual status_t activate(void* ident, bool enabled) = 0; + virtual status_t setDelay(void* ident, int handle, int64_t ns) = 0; + virtual Sensor getSensor() const = 0; + virtual bool isVirtual() const = 0; +}; + +// --------------------------------------------------------------------------- + +class HardwareSensor : public SensorInterface +{ + SensorDevice& mSensorDevice; + Sensor mSensor; + bool mEnabled; + +public: + HardwareSensor(const sensor_t& sensor); + + virtual ~HardwareSensor(); + + virtual bool process(sensors_event_t* outEvent, + const sensors_event_t& event); + + virtual bool isEnabled() const; + virtual status_t activate(void* ident, bool enabled); + virtual status_t setDelay(void* ident, int handle, int64_t ns); + virtual Sensor getSensor() const; + virtual bool isVirtual() const { return false; } +}; + + +// --------------------------------------------------------------------------- +}; // namespace android + +#endif // ANDROID_SENSOR_INTERFACE_H diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp new file mode 100644 index 0000000..ea5e5cc --- /dev/null +++ b/services/sensorservice/SensorService.cpp @@ -0,0 +1,544 @@ +/* + * 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 <math.h> +#include <sys/types.h> + +#include <utils/SortedVector.h> +#include <utils/KeyedVector.h> +#include <utils/threads.h> +#include <utils/Atomic.h> +#include <utils/Errors.h> +#include <utils/RefBase.h> +#include <utils/Singleton.h> +#include <utils/String16.h> + +#include <binder/BinderService.h> +#include <binder/IServiceManager.h> + +#include <gui/ISensorServer.h> +#include <gui/ISensorEventConnection.h> + +#include <hardware/sensors.h> + +#include "SensorService.h" +#include "GravitySensor.h" +#include "LinearAccelerationSensor.h" +#include "RotationVectorSensor.h" + +namespace android { +// --------------------------------------------------------------------------- + +SensorService::SensorService() + : Thread(false), + mDump("android.permission.DUMP"), + mInitCheck(NO_INIT) +{ +} + +void SensorService::onFirstRef() +{ + LOGD("nuSensorService starting..."); + + SensorDevice& dev(SensorDevice::getInstance()); + + if (dev.initCheck() == NO_ERROR) { + uint32_t virtualSensorsNeeds = + (1<<SENSOR_TYPE_GRAVITY) | + (1<<SENSOR_TYPE_LINEAR_ACCELERATION) | + (1<<SENSOR_TYPE_ROTATION_VECTOR); + sensor_t const* list; + int count = dev.getSensorList(&list); + mLastEventSeen.setCapacity(count); + for (int i=0 ; i<count ; i++) { + registerSensor( new HardwareSensor(list[i]) ); + switch (list[i].type) { + case SENSOR_TYPE_GRAVITY: + case SENSOR_TYPE_LINEAR_ACCELERATION: + case SENSOR_TYPE_ROTATION_VECTOR: + virtualSensorsNeeds &= ~(1<<list[i].type); + break; + } + } + + if (virtualSensorsNeeds & (1<<SENSOR_TYPE_GRAVITY)) { + registerVirtualSensor( new GravitySensor(list, count) ); + } + if (virtualSensorsNeeds & (1<<SENSOR_TYPE_LINEAR_ACCELERATION)) { + registerVirtualSensor( new LinearAccelerationSensor(list, count) ); + } + if (virtualSensorsNeeds & (1<<SENSOR_TYPE_ROTATION_VECTOR)) { + registerVirtualSensor( new RotationVectorSensor(list, count) ); + } + + run("SensorService", PRIORITY_URGENT_DISPLAY); + mInitCheck = NO_ERROR; + } +} + +void SensorService::registerSensor(SensorInterface* s) +{ + sensors_event_t event; + memset(&event, 0, sizeof(event)); + + const Sensor sensor(s->getSensor()); + // add to the sensor list (returned to clients) + mSensorList.add(sensor); + // add to our handle->SensorInterface mapping + mSensorMap.add(sensor.getHandle(), s); + // create an entry in the mLastEventSeen array + mLastEventSeen.add(sensor.getHandle(), event); +} + +void SensorService::registerVirtualSensor(SensorInterface* s) +{ + registerSensor(s); + mVirtualSensorList.add( s ); +} + +SensorService::~SensorService() +{ + for (size_t i=0 ; i<mSensorMap.size() ; i++) + delete mSensorMap.valueAt(i); +} + +status_t SensorService::dump(int fd, const Vector<String16>& args) +{ + const size_t SIZE = 1024; + char buffer[SIZE]; + String8 result; + if (!mDump.checkCalling()) { + snprintf(buffer, SIZE, "Permission Denial: " + "can't dump SurfaceFlinger from pid=%d, uid=%d\n", + IPCThreadState::self()->getCallingPid(), + IPCThreadState::self()->getCallingUid()); + result.append(buffer); + } else { + Mutex::Autolock _l(mLock); + snprintf(buffer, SIZE, "Sensor List:\n"); + result.append(buffer); + for (size_t i=0 ; i<mSensorList.size() ; i++) { + const Sensor& s(mSensorList[i]); + const sensors_event_t& e(mLastEventSeen.valueFor(s.getHandle())); + snprintf(buffer, SIZE, "%-48s| %-32s | 0x%08x | maxRate=%7.2fHz | last=<%5.1f,%5.1f,%5.1f>\n", + s.getName().string(), + s.getVendor().string(), + s.getHandle(), + s.getMinDelay() ? (1000000.0f / s.getMinDelay()) : 0.0f, + e.data[0], e.data[1], e.data[2]); + result.append(buffer); + } + SensorDevice::getInstance().dump(result, buffer, SIZE); + + snprintf(buffer, SIZE, "%d active connections\n", + mActiveConnections.size()); + result.append(buffer); + snprintf(buffer, SIZE, "Active sensors:\n"); + result.append(buffer); + for (size_t i=0 ; i<mActiveSensors.size() ; i++) { + int handle = mActiveSensors.keyAt(i); + snprintf(buffer, SIZE, "%s (handle=0x%08x, connections=%d)\n", + getSensorName(handle).string(), + handle, + mActiveSensors.valueAt(i)->getNumConnections()); + result.append(buffer); + } + } + write(fd, result.string(), result.size()); + return NO_ERROR; +} + +bool SensorService::threadLoop() +{ + LOGD("nuSensorService thread starting..."); + + const size_t numEventMax = 16 * (1 + mVirtualSensorList.size()); + sensors_event_t buffer[numEventMax]; + sensors_event_t scratch[numEventMax]; + SensorDevice& device(SensorDevice::getInstance()); + const size_t vcount = mVirtualSensorList.size(); + + ssize_t count; + do { + count = device.poll(buffer, numEventMax); + if (count<0) { + LOGE("sensor poll failed (%s)", strerror(-count)); + break; + } + + recordLastValue(buffer, count); + + // handle virtual sensors + if (count && vcount) { + const DefaultKeyedVector<int, SensorInterface*> virtualSensors( + getActiveVirtualSensors()); + const size_t activeVirtualSensorCount = virtualSensors.size(); + if (activeVirtualSensorCount) { + size_t k = 0; + for (size_t i=0 ; i<size_t(count) ; i++) { + sensors_event_t const * const event = buffer; + for (size_t j=0 ; j<activeVirtualSensorCount ; j++) { + sensors_event_t out; + if (virtualSensors.valueAt(j)->process(&out, event[i])) { + buffer[count + k] = out; + k++; + } + } + } + if (k) { + // record the last synthesized values + recordLastValue(&buffer[count], k); + count += k; + // sort the buffer by time-stamps + sortEventBuffer(buffer, count); + } + } + } + + // send our events to clients... + const SortedVector< wp<SensorEventConnection> > activeConnections( + getActiveConnections()); + size_t numConnections = activeConnections.size(); + for (size_t i=0 ; i<numConnections ; i++) { + sp<SensorEventConnection> connection( + activeConnections[i].promote()); + if (connection != 0) { + connection->sendEvents(buffer, count, scratch); + } + } + } while (count >= 0 || Thread::exitPending()); + + LOGW("Exiting SensorService::threadLoop!"); + return false; +} + +void SensorService::recordLastValue( + sensors_event_t const * buffer, size_t count) +{ + Mutex::Autolock _l(mLock); + + // record the last event for each sensor + int32_t prev = buffer[0].sensor; + for (size_t i=1 ; i<count ; i++) { + // record the last event of each sensor type in this buffer + int32_t curr = buffer[i].sensor; + if (curr != prev) { + mLastEventSeen.editValueFor(prev) = buffer[i-1]; + prev = curr; + } + } + mLastEventSeen.editValueFor(prev) = buffer[count-1]; +} + +void SensorService::sortEventBuffer(sensors_event_t* buffer, size_t count) +{ + struct compar { + static int cmp(void const* lhs, void const* rhs) { + sensors_event_t const* l = static_cast<sensors_event_t const*>(lhs); + sensors_event_t const* r = static_cast<sensors_event_t const*>(rhs); + return r->timestamp - l->timestamp; + } + }; + qsort(buffer, count, sizeof(sensors_event_t), compar::cmp); +} + +SortedVector< wp<SensorService::SensorEventConnection> > +SensorService::getActiveConnections() const +{ + Mutex::Autolock _l(mLock); + return mActiveConnections; +} + +DefaultKeyedVector<int, SensorInterface*> +SensorService::getActiveVirtualSensors() const +{ + Mutex::Autolock _l(mLock); + return mActiveVirtualSensors; +} + +String8 SensorService::getSensorName(int handle) const { + size_t count = mSensorList.size(); + for (size_t i=0 ; i<count ; i++) { + const Sensor& sensor(mSensorList[i]); + if (sensor.getHandle() == handle) { + return sensor.getName(); + } + } + String8 result("unknown"); + return result; +} + +Vector<Sensor> SensorService::getSensorList() +{ + return mSensorList; +} + +sp<ISensorEventConnection> SensorService::createSensorEventConnection() +{ + sp<SensorEventConnection> result(new SensorEventConnection(this)); + return result; +} + +void SensorService::cleanupConnection(const wp<SensorEventConnection>& connection) +{ + Mutex::Autolock _l(mLock); + size_t size = mActiveSensors.size(); + for (size_t i=0 ; i<size ; ) { + SensorRecord* rec = mActiveSensors.valueAt(i); + if (rec && rec->removeConnection(connection)) { + int handle = mActiveSensors.keyAt(i); + SensorInterface* sensor = mSensorMap.valueFor( handle ); + if (sensor) { + sensor->activate(connection.unsafe_get(), false); + } + mActiveSensors.removeItemsAt(i, 1); + mActiveVirtualSensors.removeItem(handle); + delete rec; + size--; + } else { + i++; + } + } + mActiveConnections.remove(connection); +} + +status_t SensorService::enable(const sp<SensorEventConnection>& connection, + int handle) +{ + if (mInitCheck != NO_ERROR) + return mInitCheck; + + Mutex::Autolock _l(mLock); + SensorInterface* sensor = mSensorMap.valueFor(handle); + status_t err = sensor ? sensor->activate(connection.get(), true) : status_t(BAD_VALUE); + if (err == NO_ERROR) { + SensorRecord* rec = mActiveSensors.valueFor(handle); + if (rec == 0) { + rec = new SensorRecord(connection); + mActiveSensors.add(handle, rec); + if (sensor->isVirtual()) { + mActiveVirtualSensors.add(handle, sensor); + } + } else { + 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); + } + } + } + if (err == NO_ERROR) { + // connection now active + if (connection->addSensor(handle)) { + // the sensor was added (which means it wasn't already there) + // so, see if this connection becomes active + if (mActiveConnections.indexOf(connection) < 0) { + mActiveConnections.add(connection); + } + } + } + } + return err; +} + +status_t SensorService::disable(const sp<SensorEventConnection>& connection, + int handle) +{ + if (mInitCheck != NO_ERROR) + return mInitCheck; + + status_t err = NO_ERROR; + Mutex::Autolock _l(mLock); + SensorRecord* rec = mActiveSensors.valueFor(handle); + if (rec) { + // see if this connection becomes inactive + connection->removeSensor(handle); + if (connection->hasAnySensor() == false) { + mActiveConnections.remove(connection); + } + // see if this sensor becomes inactive + if (rec->removeConnection(connection)) { + mActiveSensors.removeItem(handle); + mActiveVirtualSensors.removeItem(handle); + delete rec; + } + SensorInterface* sensor = mSensorMap.valueFor(handle); + err = sensor ? sensor->activate(connection.get(), false) : status_t(BAD_VALUE); + } + return err; +} + +status_t SensorService::setEventRate(const sp<SensorEventConnection>& connection, + int handle, nsecs_t ns) +{ + if (mInitCheck != NO_ERROR) + return mInitCheck; + + if (ns < 0) + return BAD_VALUE; + + if (ns < MINIMUM_EVENTS_PERIOD) + ns = MINIMUM_EVENTS_PERIOD; + + SensorInterface* sensor = mSensorMap.valueFor(handle); + if (!sensor) return BAD_VALUE; + return sensor->setDelay(connection.get(), handle, ns); +} + +// --------------------------------------------------------------------------- + +SensorService::SensorRecord::SensorRecord( + const sp<SensorEventConnection>& connection) +{ + mConnections.add(connection); +} + +bool SensorService::SensorRecord::addConnection( + const sp<SensorEventConnection>& connection) +{ + if (mConnections.indexOf(connection) < 0) { + mConnections.add(connection); + return true; + } + return false; +} + +bool SensorService::SensorRecord::removeConnection( + const wp<SensorEventConnection>& connection) +{ + ssize_t index = mConnections.indexOf(connection); + if (index >= 0) { + mConnections.removeItemsAt(index, 1); + } + return mConnections.size() ? false : true; +} + +// --------------------------------------------------------------------------- + +SensorService::SensorEventConnection::SensorEventConnection( + const sp<SensorService>& service) + : mService(service), mChannel(new SensorChannel()) +{ +} + +SensorService::SensorEventConnection::~SensorEventConnection() +{ + mService->cleanupConnection(this); +} + +void SensorService::SensorEventConnection::onFirstRef() +{ +} + +bool SensorService::SensorEventConnection::addSensor(int32_t handle) { + Mutex::Autolock _l(mConnectionLock); + if (mSensorInfo.indexOf(handle) <= 0) { + mSensorInfo.add(handle); + return true; + } + return false; +} + +bool SensorService::SensorEventConnection::removeSensor(int32_t handle) { + Mutex::Autolock _l(mConnectionLock); + if (mSensorInfo.remove(handle) >= 0) { + return true; + } + return false; +} + +bool SensorService::SensorEventConnection::hasSensor(int32_t handle) const { + Mutex::Autolock _l(mConnectionLock); + return mSensorInfo.indexOf(handle) >= 0; +} + +bool SensorService::SensorEventConnection::hasAnySensor() const { + Mutex::Autolock _l(mConnectionLock); + return mSensorInfo.size() ? true : false; +} + +status_t SensorService::SensorEventConnection::sendEvents( + sensors_event_t const* buffer, size_t numEvents, + sensors_event_t* scratch) +{ + // filter out events not for this connection + size_t count = 0; + if (scratch) { + Mutex::Autolock _l(mConnectionLock); + size_t i=0; + while (i<numEvents) { + const int32_t curr = buffer[i].sensor; + if (mSensorInfo.indexOf(curr) >= 0) { + do { + scratch[count++] = buffer[i++]; + } while ((i<numEvents) && (buffer[i].sensor == curr)); + } else { + i++; + } + } + } else { + scratch = const_cast<sensors_event_t *>(buffer); + count = numEvents; + } + + if (count == 0) + return 0; + + ssize_t size = mChannel->write(scratch, count*sizeof(sensors_event_t)); + if (size == -EAGAIN) { + // the destination doesn't accept events anymore, it's probably + // full. For now, we just drop the events on the floor. + LOGW("dropping %d events on the floor", count); + return size; + } + + LOGE_IF(size<0, "dropping %d events on the floor (%s)", + count, strerror(-size)); + + return size < 0 ? status_t(size) : status_t(NO_ERROR); +} + +sp<SensorChannel> SensorService::SensorEventConnection::getSensorChannel() const +{ + return mChannel; +} + +status_t SensorService::SensorEventConnection::enableDisable( + int handle, bool enabled) +{ + status_t err; + if (enabled) { + err = mService->enable(this, handle); + } else { + err = mService->disable(this, handle); + } + return err; +} + +status_t SensorService::SensorEventConnection::setEventRate( + int handle, nsecs_t ns) +{ + return mService->setEventRate(this, handle, ns); +} + +// --------------------------------------------------------------------------- +}; // namespace android + diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h new file mode 100644 index 0000000..540c7e2 --- /dev/null +++ b/services/sensorservice/SensorService.h @@ -0,0 +1,141 @@ +/* + * 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_SENSOR_SERVICE_H +#define ANDROID_SENSOR_SERVICE_H + +#include <stdint.h> +#include <sys/types.h> + +#include <utils/Vector.h> +#include <utils/SortedVector.h> +#include <utils/KeyedVector.h> +#include <utils/threads.h> +#include <utils/RefBase.h> + +#include <binder/BinderService.h> +#include <binder/Permission.h> + +#include <gui/Sensor.h> +#include <gui/SensorChannel.h> +#include <gui/ISensorServer.h> +#include <gui/ISensorEventConnection.h> + +#include "SensorInterface.h" + +// --------------------------------------------------------------------------- + +struct sensors_poll_device_t; +struct sensors_module_t; + +namespace android { +// --------------------------------------------------------------------------- + +class SensorService : + public BinderService<SensorService>, + public BnSensorServer, + protected Thread +{ + friend class BinderService<SensorService>; + + static const nsecs_t MINIMUM_EVENTS_PERIOD = 1000000; // 1000 Hz + + SensorService(); + virtual ~SensorService(); + + virtual void onFirstRef(); + + // Thread interface + virtual bool threadLoop(); + + // ISensorServer interface + virtual Vector<Sensor> getSensorList(); + virtual sp<ISensorEventConnection> createSensorEventConnection(); + virtual status_t dump(int fd, const Vector<String16>& args); + + + class SensorEventConnection : public BnSensorEventConnection { + virtual ~SensorEventConnection(); + virtual void onFirstRef(); + virtual sp<SensorChannel> getSensorChannel() const; + virtual status_t enableDisable(int handle, bool enabled); + virtual status_t setEventRate(int handle, nsecs_t ns); + + sp<SensorService> const mService; + sp<SensorChannel> const mChannel; + mutable Mutex mConnectionLock; + + // protected by SensorService::mLock + SortedVector<int> mSensorInfo; + + public: + SensorEventConnection(const sp<SensorService>& service); + + status_t sendEvents(sensors_event_t const* buffer, size_t count, + sensors_event_t* scratch = NULL); + bool hasSensor(int32_t handle) const; + bool hasAnySensor() const; + bool addSensor(int32_t handle); + bool removeSensor(int32_t handle); + }; + + class SensorRecord { + SortedVector< wp<SensorEventConnection> > mConnections; + public: + SensorRecord(const sp<SensorEventConnection>& connection); + bool addConnection(const sp<SensorEventConnection>& connection); + bool removeConnection(const wp<SensorEventConnection>& connection); + size_t getNumConnections() const { return mConnections.size(); } + }; + + SortedVector< wp<SensorEventConnection> > getActiveConnections() const; + DefaultKeyedVector<int, SensorInterface*> getActiveVirtualSensors() const; + + String8 getSensorName(int handle) const; + void recordLastValue(sensors_event_t const * buffer, size_t count); + static void sortEventBuffer(sensors_event_t* buffer, size_t count); + void registerSensor(SensorInterface* sensor); + void registerVirtualSensor(SensorInterface* sensor); + + // constants + Vector<Sensor> mSensorList; + DefaultKeyedVector<int, SensorInterface*> mSensorMap; + Vector<SensorInterface *> mVirtualSensorList; + Permission mDump; + status_t mInitCheck; + + // protected by mLock + mutable Mutex mLock; + DefaultKeyedVector<int, SensorRecord*> mActiveSensors; + DefaultKeyedVector<int, SensorInterface*> mActiveVirtualSensors; + SortedVector< wp<SensorEventConnection> > mActiveConnections; + + // The size of this vector is constant, only the items are mutable + KeyedVector<int32_t, sensors_event_t> mLastEventSeen; + +public: + static char const* getServiceName() { return "sensorservice"; } + + void cleanupConnection(const wp<SensorEventConnection>& connection); + status_t enable(const sp<SensorEventConnection>& connection, int handle); + status_t disable(const sp<SensorEventConnection>& connection, int handle); + status_t setEventRate(const sp<SensorEventConnection>& connection, int handle, nsecs_t ns); +}; + +// --------------------------------------------------------------------------- +}; // namespace android + +#endif // ANDROID_SENSOR_SERVICE_H diff --git a/services/sensorservice/tests/Android.mk b/services/sensorservice/tests/Android.mk new file mode 100644 index 0000000..45296dd --- /dev/null +++ b/services/sensorservice/tests/Android.mk @@ -0,0 +1,14 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + sensorservicetest.cpp + +LOCAL_SHARED_LIBRARIES := \ + libcutils libutils libui libgui + +LOCAL_MODULE:= test-sensorservice + +LOCAL_MODULE_TAGS := optional + +include $(BUILD_EXECUTABLE) diff --git a/services/sensorservice/tests/sensorservicetest.cpp b/services/sensorservice/tests/sensorservicetest.cpp new file mode 100644 index 0000000..aea1062 --- /dev/null +++ b/services/sensorservice/tests/sensorservicetest.cpp @@ -0,0 +1,103 @@ +/* + * 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 <android/sensor.h> +#include <gui/Sensor.h> +#include <gui/SensorManager.h> +#include <gui/SensorEventQueue.h> +#include <utils/Looper.h> + +using namespace android; + +int receiver(int fd, int events, void* data) +{ + sp<SensorEventQueue> q((SensorEventQueue*)data); + ssize_t n; + ASensorEvent buffer[8]; + + static nsecs_t oldTimeStamp = 0; + + while ((n = q->read(buffer, 8)) > 0) { + for (int i=0 ; i<n ; i++) { + if (buffer[i].type == Sensor::TYPE_GYROSCOPE) { + printf("time=%lld, value=<%5.1f,%5.1f,%5.1f>\n", + buffer[i].timestamp, + buffer[i].acceleration.x, + buffer[i].acceleration.y, + buffer[i].acceleration.z); + } + + if (oldTimeStamp) { + float t = float(buffer[i].timestamp - oldTimeStamp) / s2ns(1); + printf("%f ms (%f Hz)\n", t*1000, 1.0/t); + } + oldTimeStamp = buffer[i].timestamp; + + } + } + if (n<0 && n != -EAGAIN) { + printf("error reading events (%s)\n", strerror(-n)); + } + return 1; +} + + +int main(int argc, char** argv) +{ + SensorManager& mgr(SensorManager::getInstance()); + + Sensor const* const* list; + ssize_t count = mgr.getSensorList(&list); + printf("numSensors=%d\n", int(count)); + + sp<SensorEventQueue> q = mgr.createEventQueue(); + printf("queue=%p\n", q.get()); + + Sensor const* accelerometer = mgr.getDefaultSensor(Sensor::TYPE_GYROSCOPE); + printf("accelerometer=%p (%s)\n", + accelerometer, accelerometer->getName().string()); + q->enableSensor(accelerometer); + + q->setEventRate(accelerometer, ms2ns(10)); + + sp<Looper> loop = new Looper(false); + loop->addFd(q->getFd(), 0, ALOOPER_EVENT_INPUT, receiver, q.get()); + + do { + //printf("about to poll...\n"); + int32_t ret = loop->pollOnce(-1); + switch (ret) { + case ALOOPER_POLL_WAKE: + //("ALOOPER_POLL_WAKE\n"); + break; + case ALOOPER_POLL_CALLBACK: + //("ALOOPER_POLL_CALLBACK\n"); + break; + case ALOOPER_POLL_TIMEOUT: + printf("ALOOPER_POLL_TIMEOUT\n"); + break; + case ALOOPER_POLL_ERROR: + printf("ALOOPER_POLL_TIMEOUT\n"); + break; + default: + printf("ugh? poll returned %d\n", ret); + break; + } + } while (1); + + + return 0; +} diff --git a/services/surfaceflinger/Android.mk b/services/surfaceflinger/Android.mk index 86eb78d..e2f8a74 100644 --- a/services/surfaceflinger/Android.mk +++ b/services/surfaceflinger/Android.mk @@ -6,6 +6,7 @@ LOCAL_SRC_FILES:= \ DisplayHardware/DisplayHardware.cpp \ DisplayHardware/DisplayHardwareBase.cpp \ BlurFilter.cpp.arm \ + GLExtensions.cpp \ Layer.cpp \ LayerBase.cpp \ LayerBuffer.cpp \ @@ -13,15 +14,19 @@ LOCAL_SRC_FILES:= \ LayerDim.cpp \ MessageQueue.cpp \ SurfaceFlinger.cpp \ - Tokenizer.cpp \ + TextureManager.cpp \ Transform.cpp LOCAL_CFLAGS:= -DLOG_TAG=\"SurfaceFlinger\" LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES -ifeq ($(TARGET_BOARD_PLATFORM), msm7k) - LOCAL_CFLAGS += -DDIM_WITH_TEXTURE +ifeq ($(TARGET_BOARD_PLATFORM), omap3) + LOCAL_CFLAGS += -DNO_RGBX_8888 endif +ifeq ($(TARGET_BOARD_PLATFORM), s5pc110) + LOCAL_CFLAGS += -DHAS_CONTEXT_PRIORITY +endif + # need "-lrt" on Linux simulator to pick up clock_gettime ifeq ($(TARGET_SIMULATOR),true) diff --git a/services/surfaceflinger/Barrier.h b/services/surfaceflinger/Barrier.h index e2bcf6a..6f8507e 100644 --- a/services/surfaceflinger/Barrier.h +++ b/services/surfaceflinger/Barrier.h @@ -29,10 +29,6 @@ public: inline Barrier() : state(CLOSED) { } inline ~Barrier() { } void open() { - // gcc memory barrier, this makes sure all memory writes - // have been issued by gcc. On an SMP system we'd need a real - // h/w barrier. - asm volatile ("":::"memory"); Mutex::Autolock _l(lock); state = OPENED; cv.broadcast(); diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp index ea68352..28a512e 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp +++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp @@ -40,6 +40,8 @@ #include <hardware/overlay.h> #include <hardware/gralloc.h> +#include "GLExtensions.h" + using namespace android; @@ -73,7 +75,8 @@ void checkEGLErrors(const char* token) DisplayHardware::DisplayHardware( const sp<SurfaceFlinger>& flinger, uint32_t dpy) - : DisplayHardwareBase(flinger, dpy) + : DisplayHardwareBase(flinger, dpy), + mFlags(0) { init(dpy); } @@ -97,6 +100,9 @@ void DisplayHardware::init(uint32_t dpy) { mNativeWindow = new FramebufferNativeWindow(); framebuffer_device_t const * fbDev = mNativeWindow->getDevice(); + mDpiX = mNativeWindow->xdpi; + mDpiY = mNativeWindow->ydpi; + mRefreshRate = fbDev->fps; mOverlayEngine = NULL; hw_module_t const* module; @@ -104,6 +110,11 @@ void DisplayHardware::init(uint32_t dpy) overlay_control_open(module, &mOverlayEngine); } + EGLint w, h, dummy; + EGLint numConfigs=0; + EGLSurface surface; + EGLContext context; + // initialize EGL EGLint attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, @@ -121,12 +132,6 @@ void DisplayHardware::init(uint32_t dpy) } } - EGLint w, h, dummy; - EGLint numConfigs=0; - EGLSurface surface; - EGLContext context; - mFlags = CACHED_BUFFERS; - // TODO: all the extensions below should be queried through // eglGetProcAddress(). @@ -145,22 +150,6 @@ void DisplayHardware::init(uint32_t dpy) eglGetConfigAttrib(display, config, EGL_BLUE_SIZE, &b); eglGetConfigAttrib(display, config, EGL_ALPHA_SIZE, &a); - /* - * Gather EGL extensions - */ - - const char* const egl_extensions = eglQueryString( - display, EGL_EXTENSIONS); - - LOGI("EGL informations:"); - LOGI("# of configs : %d", numConfigs); - LOGI("vendor : %s", eglQueryString(display, EGL_VENDOR)); - LOGI("version : %s", eglQueryString(display, EGL_VERSION)); - LOGI("extensions: %s", egl_extensions); - LOGI("Client API: %s", eglQueryString(display, EGL_CLIENT_APIS)?:"Not Supported"); - LOGI("EGLSurface: %d-%d-%d-%d, config=%p", r, g, b, a, config); - - if (mNativeWindow->isUpdateOnDemand()) { mFlags |= PARTIAL_UPDATES; } @@ -175,6 +164,8 @@ void DisplayHardware::init(uint32_t dpy) */ surface = eglCreateWindowSurface(display, config, mNativeWindow.get(), NULL); + eglQuerySurface(display, surface, EGL_WIDTH, &mWidth); + eglQuerySurface(display, surface, EGL_HEIGHT, &mHeight); if (mFlags & PARTIAL_UPDATES) { // if we have partial updates, we definitely don't need to @@ -188,31 +179,6 @@ void DisplayHardware::init(uint32_t dpy) mFlags |= BUFFER_PRESERVED; } } - - eglQuerySurface(display, surface, EGL_WIDTH, &mWidth); - eglQuerySurface(display, surface, EGL_HEIGHT, &mHeight); - -#ifdef EGL_ANDROID_swap_rectangle - if (strstr(egl_extensions, "EGL_ANDROID_swap_rectangle")) { - if (eglSetSwapRectangleANDROID(display, surface, - 0, 0, mWidth, mHeight) == EGL_TRUE) { - // This could fail if this extension is not supported by this - // specific surface (of config) - mFlags |= SWAP_RECTANGLE; - } - } - // when we have the choice between PARTIAL_UPDATES and SWAP_RECTANGLE - // choose PARTIAL_UPDATES, which should be more efficient - if (mFlags & PARTIAL_UPDATES) - mFlags &= ~SWAP_RECTANGLE; -#endif - - - LOGI("flags : %08x", mFlags); - - mDpiX = mNativeWindow->xdpi; - mDpiY = mNativeWindow->ydpi; - mRefreshRate = fbDev->fps; /* Read density from build-specific ro.sf.lcd_density property * except if it is overridden by qemu.sf.lcd_density. @@ -233,63 +199,79 @@ void DisplayHardware::init(uint32_t dpy) * Create our OpenGL ES context */ - context = eglCreateContext(display, config, NULL, NULL); - + + EGLint contextAttributes[] = { +#ifdef EGL_IMG_context_priority +#ifdef HAS_CONTEXT_PRIORITY +#warning "using EGL_IMG_context_priority" + EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG, +#endif +#endif + EGL_NONE, EGL_NONE + }; + context = eglCreateContext(display, config, NULL, contextAttributes); + + mDisplay = display; + mConfig = config; + mSurface = surface; + mContext = context; + mFormat = fbDev->format; + mPageFlipCount = 0; + /* * Gather OpenGL ES extensions */ eglMakeCurrent(display, surface, surface, context); - const char* const gl_extensions = (const char*)glGetString(GL_EXTENSIONS); - const char* const gl_renderer = (const char*)glGetString(GL_RENDERER); - LOGI("OpenGL informations:"); - LOGI("vendor : %s", glGetString(GL_VENDOR)); - LOGI("renderer : %s", gl_renderer); - LOGI("version : %s", glGetString(GL_VERSION)); - LOGI("extensions: %s", gl_extensions); + + GLExtensions& extensions(GLExtensions::getInstance()); + extensions.initWithGLStrings( + glGetString(GL_VENDOR), + glGetString(GL_RENDERER), + glGetString(GL_VERSION), + glGetString(GL_EXTENSIONS), + eglQueryString(display, EGL_VENDOR), + eglQueryString(display, EGL_VERSION), + eglQueryString(display, EGL_EXTENSIONS)); glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize); glGetIntegerv(GL_MAX_VIEWPORT_DIMS, &mMaxViewportDims); - LOGI("GL_MAX_TEXTURE_SIZE = %d", mMaxTextureSize); - LOGI("GL_MAX_VIEWPORT_DIMS = %d", mMaxViewportDims); -#if 0 - // for drivers that don't have proper support for flushing cached buffers - // on gralloc unlock, uncomment this block and test for the specific - // renderer substring - if (strstr(gl_renderer, "<some vendor string>")) { - LOGD("Assuming uncached graphics buffers."); - mFlags &= ~CACHED_BUFFERS; - } -#endif - if (strstr(gl_extensions, "GL_ARB_texture_non_power_of_two")) { - mFlags |= NPOT_EXTENSION; - } - if (strstr(gl_extensions, "GL_OES_draw_texture")) { - mFlags |= DRAW_TEXTURE_EXTENSION; - } -#ifdef EGL_ANDROID_image_native_buffer - if (strstr( gl_extensions, "GL_OES_EGL_image") && - (strstr(egl_extensions, "EGL_KHR_image_base") || - strstr(egl_extensions, "EGL_KHR_image")) && - strstr(egl_extensions, "EGL_ANDROID_image_native_buffer")) { - mFlags |= DIRECT_TEXTURE; +#ifdef EGL_ANDROID_swap_rectangle + if (extensions.hasExtension("EGL_ANDROID_swap_rectangle")) { + if (eglSetSwapRectangleANDROID(display, surface, + 0, 0, mWidth, mHeight) == EGL_TRUE) { + // This could fail if this extension is not supported by this + // specific surface (of config) + mFlags |= SWAP_RECTANGLE; + } } -#else -#warning "EGL_ANDROID_image_native_buffer not supported" + // when we have the choice between PARTIAL_UPDATES and SWAP_RECTANGLE + // choose PARTIAL_UPDATES, which should be more efficient + if (mFlags & PARTIAL_UPDATES) + mFlags &= ~SWAP_RECTANGLE; #endif + LOGI("EGL informations:"); + LOGI("# of configs : %d", numConfigs); + LOGI("vendor : %s", extensions.getEglVendor()); + LOGI("version : %s", extensions.getEglVersion()); + LOGI("extensions: %s", extensions.getEglExtension()); + LOGI("Client API: %s", eglQueryString(display, EGL_CLIENT_APIS)?:"Not Supported"); + LOGI("EGLSurface: %d-%d-%d-%d, config=%p", r, g, b, a, config); + + LOGI("OpenGL informations:"); + LOGI("vendor : %s", extensions.getVendor()); + LOGI("renderer : %s", extensions.getRenderer()); + LOGI("version : %s", extensions.getVersion()); + LOGI("extensions: %s", extensions.getExtension()); + LOGI("GL_MAX_TEXTURE_SIZE = %d", mMaxTextureSize); + LOGI("GL_MAX_VIEWPORT_DIMS = %d", mMaxViewportDims); + LOGI("flags = %08x", mFlags); // Unbind the context from this thread eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - - mDisplay = display; - mConfig = config; - mSurface = surface; - mContext = context; - mFormat = fbDev->format; - mPageFlipCount = 0; } /* @@ -324,6 +306,10 @@ status_t DisplayHardware::compositionComplete() const { return mNativeWindow->compositionComplete(); } +int DisplayHardware::getCurrentBufferIndex() const { + return mNativeWindow->getCurrentBufferIndex(); +} + void DisplayHardware::flip(const Region& dirty) const { checkGLErrors(); diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.h b/services/surfaceflinger/DisplayHardware/DisplayHardware.h index df046af..2d7900c 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardware.h +++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.h @@ -29,6 +29,8 @@ #include <pixelflinger/pixelflinger.h> +#include "GLExtensions.h" + #include "DisplayHardware/DisplayHardwareBase.h" struct overlay_control_device_t; @@ -43,15 +45,11 @@ class DisplayHardware : public DisplayHardwareBase { public: enum { - DIRECT_TEXTURE = 0x00000002, - COPY_BITS_EXTENSION = 0x00000008, - NPOT_EXTENSION = 0x00000100, - DRAW_TEXTURE_EXTENSION = 0x00000200, - BUFFER_PRESERVED = 0x00010000, - PARTIAL_UPDATES = 0x00020000, // video driver feature - SLOW_CONFIG = 0x00040000, // software - SWAP_RECTANGLE = 0x00080000, - CACHED_BUFFERS = 0x00100000 + COPY_BITS_EXTENSION = 0x00000008, + BUFFER_PRESERVED = 0x00010000, + PARTIAL_UPDATES = 0x00020000, // video driver feature + SLOW_CONFIG = 0x00040000, // software + SWAP_RECTANGLE = 0x00080000, }; DisplayHardware( @@ -89,6 +87,9 @@ public: return Rect(mWidth, mHeight); } + // only for debugging + int getCurrentBufferIndex() const; + private: void init(uint32_t displayIndex) __attribute__((noinline)); void fini() __attribute__((noinline)); diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp index 1d09f84..90865da 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp +++ b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp @@ -359,7 +359,7 @@ status_t DisplayHardwareBase::ConsoleManagerThread::initCheck() const DisplayHardwareBase::DisplayHardwareBase(const sp<SurfaceFlinger>& flinger, uint32_t displayIndex) - : mCanDraw(true) + : mCanDraw(true), mScreenAcquired(true) { mDisplayEventThread = new DisplayEventThread(flinger); if (mDisplayEventThread->initCheck() != NO_ERROR) { @@ -374,18 +374,21 @@ DisplayHardwareBase::~DisplayHardwareBase() mDisplayEventThread->requestExitAndWait(); } +void DisplayHardwareBase::setCanDraw(bool canDraw) +{ + mCanDraw = canDraw; +} bool DisplayHardwareBase::canDraw() const { - return mCanDraw; + return mCanDraw && mScreenAcquired; } void DisplayHardwareBase::releaseScreen() const { status_t err = mDisplayEventThread->releaseScreen(); if (err >= 0) { - //LOGD("screen given-up"); - mCanDraw = false; + mScreenAcquired = false; } } @@ -393,9 +396,13 @@ void DisplayHardwareBase::acquireScreen() const { status_t err = mDisplayEventThread->acquireScreen(); if (err >= 0) { - //LOGD("screen returned"); - mCanDraw = true; + mScreenAcquired = true; } } +bool DisplayHardwareBase::isScreenAcquired() const +{ + return mScreenAcquired; +} + }; // namespace android diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h index 8369bb8..fa6a0c4 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h +++ b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h @@ -40,7 +40,11 @@ public: // console managment void releaseScreen() const; void acquireScreen() const; + bool isScreenAcquired() const; + bool canDraw() const; + void setCanDraw(bool canDraw); + private: class DisplayEventThreadBase : public Thread { @@ -89,6 +93,7 @@ private: sp<DisplayEventThreadBase> mDisplayEventThread; mutable int mCanDraw; + mutable int mScreenAcquired; }; }; // namespace android diff --git a/services/surfaceflinger/GLExtensions.cpp b/services/surfaceflinger/GLExtensions.cpp new file mode 100644 index 0000000..493122d --- /dev/null +++ b/services/surfaceflinger/GLExtensions.cpp @@ -0,0 +1,137 @@ +/* + * 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 <stdlib.h> +#include <stdio.h> +#include <stdint.h> + +#include "GLExtensions.h" + +namespace android { +// --------------------------------------------------------------------------- + +ANDROID_SINGLETON_STATIC_INSTANCE( GLExtensions ) + +GLExtensions::GLExtensions() + : mHaveTextureExternal(false), + mHaveNpot(false), + mHaveDirectTexture(false) +{ +} + +void GLExtensions::initWithGLStrings( + GLubyte const* vendor, + GLubyte const* renderer, + GLubyte const* version, + GLubyte const* extensions, + char const* egl_vendor, + char const* egl_version, + char const* egl_extensions) +{ + mVendor = (char const*)vendor; + mRenderer = (char const*)renderer; + mVersion = (char const*)version; + mExtensions = (char const*)extensions; + mEglVendor = egl_vendor; + mEglVersion = egl_version; + mEglExtensions = egl_extensions; + + char const* curr = (char const*)extensions; + char const* head = curr; + do { + head = strchr(curr, ' '); + String8 s(curr, head ? head-curr : strlen(curr)); + if (s.length()) { + mExtensionList.add(s); + } + curr = head+1; + } while (head); + + curr = egl_extensions; + head = curr; + do { + head = strchr(curr, ' '); + String8 s(curr, head ? head-curr : strlen(curr)); + if (s.length()) { + mExtensionList.add(s); + } + curr = head+1; + } while (head); + +#ifdef EGL_ANDROID_image_native_buffer + if (hasExtension("GL_OES_EGL_image") && + (hasExtension("EGL_KHR_image_base") || hasExtension("EGL_KHR_image")) && + hasExtension("EGL_ANDROID_image_native_buffer")) + { + mHaveDirectTexture = true; + } +#else +#warning "EGL_ANDROID_image_native_buffer not supported" +#endif + + if (hasExtension("GL_ARB_texture_non_power_of_two")) { + mHaveNpot = true; + } + + if (hasExtension("GL_OES_EGL_image_external")) { + mHaveTextureExternal = true; + } else if (strstr(mRenderer.string(), "Adreno")) { + // hack for Adreno 200 + mHaveTextureExternal = true; + } + + if (hasExtension("GL_OES_framebuffer_object")) { + mHaveFramebufferObject = true; + } +} + +bool GLExtensions::hasExtension(char const* extension) const +{ + const String8 s(extension); + return mExtensionList.indexOf(s) >= 0; +} + +char const* GLExtensions::getVendor() const { + return mVendor.string(); +} + +char const* GLExtensions::getRenderer() const { + return mRenderer.string(); +} + +char const* GLExtensions::getVersion() const { + return mVersion.string(); +} + +char const* GLExtensions::getExtension() const { + return mExtensions.string(); +} + +char const* GLExtensions::getEglVendor() const { + return mEglVendor.string(); +} + +char const* GLExtensions::getEglVersion() const { + return mEglVersion.string(); +} + +char const* GLExtensions::getEglExtension() const { + return mEglExtensions.string(); +} + + +// --------------------------------------------------------------------------- +}; // namespace android diff --git a/services/surfaceflinger/GLExtensions.h b/services/surfaceflinger/GLExtensions.h new file mode 100644 index 0000000..c86c66a --- /dev/null +++ b/services/surfaceflinger/GLExtensions.h @@ -0,0 +1,99 @@ +/* + * 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_GLEXTENSION_H +#define ANDROID_SF_GLEXTENSION_H + +#include <stdint.h> +#include <sys/types.h> + +#include <utils/String8.h> +#include <utils/SortedVector.h> +#include <utils/Singleton.h> + +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <GLES/gl.h> +#include <GLES/glext.h> + +namespace android { +// --------------------------------------------------------------------------- + +class GLExtensions : public Singleton<GLExtensions> +{ + friend class Singleton<GLExtensions>; + + bool mHaveTextureExternal : 1; + bool mHaveNpot : 1; + bool mHaveDirectTexture : 1; + bool mHaveFramebufferObject : 1; + + String8 mVendor; + String8 mRenderer; + String8 mVersion; + String8 mExtensions; + String8 mEglVendor; + String8 mEglVersion; + String8 mEglExtensions; + SortedVector<String8> mExtensionList; + + GLExtensions(const GLExtensions&); + GLExtensions& operator = (const GLExtensions&); + +protected: + GLExtensions(); + +public: + inline bool haveTextureExternal() const { + return mHaveTextureExternal; + } + inline bool haveNpot() const { + return mHaveNpot; + } + inline bool haveDirectTexture() const { + return mHaveDirectTexture; + } + + inline bool haveFramebufferObject() const { + return mHaveFramebufferObject; + } + + void initWithGLStrings( + GLubyte const* vendor, + GLubyte const* renderer, + GLubyte const* version, + GLubyte const* extensions, + char const* egl_vendor, + char const* egl_version, + char const* egl_extensions); + + char const* getVendor() const; + char const* getRenderer() const; + char const* getVersion() const; + char const* getExtension() const; + + char const* getEglVendor() const; + char const* getEglVersion() const; + char const* getEglExtension() const; + + bool hasExtension(char const* extension) const; +}; + + +// --------------------------------------------------------------------------- +}; // namespace android + +#endif // ANDROID_SF_GLEXTENSION_H diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index ce7e9aa..a060d31 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -31,6 +31,7 @@ #include <surfaceflinger/Surface.h> #include "clz.h" +#include "GLExtensions.h" #include "Layer.h" #include "SurfaceFlinger.h" #include "DisplayHardware/DisplayHardware.h" @@ -47,47 +48,73 @@ template <typename T> inline T min(T a, T b) { // --------------------------------------------------------------------------- -const uint32_t Layer::typeInfo = LayerBaseClient::typeInfo | 4; -const char* const Layer::typeID = "Layer"; - -// --------------------------------------------------------------------------- - -Layer::Layer(SurfaceFlinger* flinger, DisplayID display, - const sp<Client>& c, int32_t i) - : LayerBaseClient(flinger, display, c, i), - mSecure(false), - mNoEGLImageForSwBuffers(false), +Layer::Layer(SurfaceFlinger* flinger, + DisplayID display, const sp<Client>& client) + : LayerBaseClient(flinger, display, client), + mGLExtensions(GLExtensions::getInstance()), mNeedsBlending(true), - mNeedsDithering(false) + mNeedsDithering(false), + mSecure(false), + mTextureManager(), + mBufferManager(mTextureManager), + mWidth(0), mHeight(0), mFixedSize(false) { - // no OpenGL operation is possible here, since we might not be - // in the OpenGL thread. - mFrontBufferIndex = lcblk->getFrontBuffer(); } Layer::~Layer() { - destroy(); - // the actual buffers will be destroyed here + // FIXME: must be called from the main UI thread + EGLDisplay dpy(mFlinger->graphicPlane(0).getEGLDisplay()); + mBufferManager.destroy(dpy); + + // we can use getUserClientUnsafe here because we know we're + // single-threaded at that point. + sp<UserClient> ourClient(mUserClientRef.getUserClientUnsafe()); + if (ourClient != 0) { + ourClient->detachLayer(this); + } } -void Layer::destroy() +status_t Layer::setToken(const sp<UserClient>& userClient, + SharedClient* sharedClient, int32_t token) { - for (size_t i=0 ; i<NUM_BUFFERS ; i++) { - if (mTextures[i].name != -1U) { - glDeleteTextures(1, &mTextures[i].name); - mTextures[i].name = -1U; - } - if (mTextures[i].image != EGL_NO_IMAGE_KHR) { - EGLDisplay dpy(mFlinger->graphicPlane(0).getEGLDisplay()); - eglDestroyImageKHR(dpy, mTextures[i].image); - mTextures[i].image = EGL_NO_IMAGE_KHR; - } - Mutex::Autolock _l(mLock); - mBuffers[i].clear(); - mWidth = mHeight = 0; + sp<SharedBufferServer> lcblk = new SharedBufferServer( + sharedClient, token, mBufferManager.getDefaultBufferCount(), + getIdentity()); + + status_t err = mUserClientRef.setToken(userClient, lcblk, token); + + LOGE_IF(err != NO_ERROR, + "ClientRef::setToken(%p, %p, %u) failed", + userClient.get(), lcblk.get(), token); + + if (err == NO_ERROR) { + // we need to free the buffers associated with this surface + } + + return err; +} + +int32_t Layer::getToken() const +{ + return mUserClientRef.getToken(); +} + +sp<UserClient> Layer::getClient() const +{ + return mUserClientRef.getClient(); +} + +// called with SurfaceFlinger::mStateLock as soon as the layer is entered +// in the purgatory list +void Layer::onRemoved() +{ + ClientRef::Access sharedClient(mUserClientRef); + SharedBufferServer* lcblk(sharedClient.get()); + if (lcblk) { + // wake up the condition + lcblk->setStatus(NO_INIT); } - mSurface.clear(); } sp<LayerBaseClient::Surface> Layer::createSurface() const @@ -97,9 +124,17 @@ sp<LayerBaseClient::Surface> Layer::createSurface() const status_t Layer::ditch() { + // NOTE: Called from the main UI thread + // the layer is not on screen anymore. free as much resources as possible mFreezeLock.clear(); - destroy(); + + EGLDisplay dpy(mFlinger->graphicPlane(0).getEGLDisplay()); + mBufferManager.destroy(dpy); + mSurface.clear(); + + Mutex::Autolock _l(mLock); + mWidth = mHeight = 0; return NO_ERROR; } @@ -129,26 +164,26 @@ status_t Layer::setBuffers( uint32_t w, uint32_t h, mFormat = format; mWidth = w; mHeight = h; + + mReqFormat = format; + mReqWidth = w; + mReqHeight = h; + mSecure = (flags & ISurfaceComposer::eSecure) ? true : false; mNeedsBlending = (info.h_alpha - info.l_alpha) > 0; - mNoEGLImageForSwBuffers = !(hwFlags & DisplayHardware::CACHED_BUFFERS); // we use the red index int displayRedSize = displayInfo.getSize(PixelFormatInfo::INDEX_RED); int layerRedsize = info.getSize(PixelFormatInfo::INDEX_RED); mNeedsDithering = layerRedsize > displayRedSize; - for (size_t i=0 ; i<NUM_BUFFERS ; i++) { - mBuffers[i] = new GraphicBuffer(); - } - mSurface = new SurfaceLayer(mFlinger, clientIndex(), this); + mSurface = new SurfaceLayer(mFlinger, this); return NO_ERROR; } void Layer::reloadTexture(const Region& dirty) { - Mutex::Autolock _l(mLock); - sp<GraphicBuffer> buffer(getFrontBufferLocked()); + sp<GraphicBuffer> buffer(mBufferManager.getActiveBuffer()); if (buffer == NULL) { // this situation can happen if we ran out of memory for instance. // not much we can do. continue to use whatever texture was bound @@ -156,130 +191,44 @@ void Layer::reloadTexture(const Region& dirty) return; } - const int index = mFrontBufferIndex; - - // create the new texture name if needed - if (UNLIKELY(mTextures[index].name == -1U)) { - mTextures[index].name = createTexture(); - mTextures[index].width = 0; - mTextures[index].height = 0; - } - -#ifdef EGL_ANDROID_image_native_buffer - if (mFlags & DisplayHardware::DIRECT_TEXTURE) { - if (buffer->usage & GraphicBuffer::USAGE_HW_TEXTURE) { - if (mTextures[index].dirty) { - if (initializeEglImage(buffer, &mTextures[index]) != NO_ERROR) { - // not sure what we can do here... - mFlags &= ~DisplayHardware::DIRECT_TEXTURE; - goto slowpath; - } - } - } else { - if (mHybridBuffer==0 || (mHybridBuffer->width != buffer->width || - mHybridBuffer->height != buffer->height)) { - mHybridBuffer.clear(); - mHybridBuffer = new GraphicBuffer( - buffer->width, buffer->height, buffer->format, - GraphicBuffer::USAGE_SW_WRITE_OFTEN | - GraphicBuffer::USAGE_HW_TEXTURE); - if (initializeEglImage( - mHybridBuffer, &mTextures[0]) != NO_ERROR) { - // not sure what we can do here... - mFlags &= ~DisplayHardware::DIRECT_TEXTURE; - mHybridBuffer.clear(); - goto slowpath; - } - } - - GGLSurface t; + if (mGLExtensions.haveDirectTexture()) { + EGLDisplay dpy(mFlinger->graphicPlane(0).getEGLDisplay()); + if (mBufferManager.initEglImage(dpy, buffer) != NO_ERROR) { + // not sure what we can do here... + goto slowpath; + } + } else { +slowpath: + GGLSurface t; + if (buffer->usage & GRALLOC_USAGE_SW_READ_MASK) { status_t res = buffer->lock(&t, GRALLOC_USAGE_SW_READ_OFTEN); LOGE_IF(res, "error %d (%s) locking buffer %p", res, strerror(res), buffer.get()); if (res == NO_ERROR) { - Texture* const texture(&mTextures[0]); - - glBindTexture(GL_TEXTURE_2D, texture->name); - - sp<GraphicBuffer> buf(mHybridBuffer); - void* vaddr; - res = buf->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN, &vaddr); - if (res == NO_ERROR) { - int bpp = 0; - switch (t.format) { - case HAL_PIXEL_FORMAT_RGB_565: - case HAL_PIXEL_FORMAT_RGBA_4444: - bpp = 2; - break; - case HAL_PIXEL_FORMAT_RGBA_8888: - case HAL_PIXEL_FORMAT_RGBX_8888: - bpp = 4; - break; - default: - if (isSupportedYuvFormat(t.format)) { - // just show the Y plane of YUV buffers - bpp = 1; - break; - } - // oops, we don't handle this format! - LOGE("layer %p, texture=%d, using format %d, which is not " - "supported by the GL", this, texture->name, t.format); - } - if (bpp) { - const Rect bounds(dirty.getBounds()); - size_t src_stride = t.stride; - size_t dst_stride = buf->stride; - if (src_stride == dst_stride && - bounds.width() == t.width && - bounds.height() == t.height) - { - memcpy(vaddr, t.data, t.height * t.stride * bpp); - } else { - GLubyte const * src = t.data + - (bounds.left + bounds.top * src_stride) * bpp; - GLubyte * dst = (GLubyte *)vaddr + - (bounds.left + bounds.top * dst_stride) * bpp; - const size_t length = bounds.width() * bpp; - size_t h = bounds.height(); - src_stride *= bpp; - dst_stride *= bpp; - while (h--) { - memcpy(dst, src, length); - dst += dst_stride; - src += src_stride; - } - } - } - buf->unlock(); - } + mBufferManager.loadTexture(dirty, t); buffer->unlock(); } - } - } else -#endif - { -slowpath: - for (size_t i=0 ; i<NUM_BUFFERS ; i++) { - mTextures[i].image = EGL_NO_IMAGE_KHR; - } - GGLSurface t; - status_t res = buffer->lock(&t, GRALLOC_USAGE_SW_READ_OFTEN); - LOGE_IF(res, "error %d (%s) locking buffer %p", - res, strerror(res), buffer.get()); - if (res == NO_ERROR) { - loadTexture(&mTextures[0], dirty, t); - buffer->unlock(); + } else { + // we can't do anything } } } +void Layer::drawForSreenShot() const +{ + bool currentFixedSize = mFixedSize; + bool currentBlending = mNeedsBlending; + const_cast<Layer*>(this)->mFixedSize = false; + const_cast<Layer*>(this)->mFixedSize = true; + LayerBase::drawForSreenShot(); + const_cast<Layer*>(this)->mFixedSize = currentFixedSize; + const_cast<Layer*>(this)->mNeedsBlending = currentBlending; +} + void Layer::onDraw(const Region& clip) const { - int index = mFrontBufferIndex; - if (mTextures[index].image == EGL_NO_IMAGE_KHR) - index = 0; - GLuint textureName = mTextures[index].name; - if (UNLIKELY(textureName == -1LU)) { + Texture tex(mBufferManager.getActiveTexture()); + if (tex.name == -1LU) { // the texture has not been created yet, this Layer has // in fact never been drawn into. This happens frequently with // SurfaceView because the WindowManager can't know when the client @@ -301,21 +250,61 @@ void Layer::onDraw(const Region& clip) const // if not everything below us is covered, we plug the holes! Region holes(clip.subtract(under)); if (!holes.isEmpty()) { - clearWithOpenGL(holes); + clearWithOpenGL(holes, 0, 0, 0, 1); } return; } - drawWithOpenGL(clip, mTextures[index]); + drawWithOpenGL(clip, tex); } -sp<GraphicBuffer> Layer::requestBuffer(int index, int usage) +bool Layer::needsFiltering() const +{ + if (!(mFlags & DisplayHardware::SLOW_CONFIG)) { + // NOTE: there is a race here, because mFixedSize is updated in a + // binder transaction. however, it doesn't really matter since it is + // evaluated each time we draw. To be perfectly correct, this flag + // would have to be associated with a buffer. + if (mFixedSize) + return true; + } + return LayerBase::needsFiltering(); +} + + +status_t Layer::setBufferCount(int bufferCount) +{ + ClientRef::Access sharedClient(mUserClientRef); + SharedBufferServer* lcblk(sharedClient.get()); + if (!lcblk) { + // oops, the client is already gone + return DEAD_OBJECT; + } + + // NOTE: lcblk->resize() is protected by an internal lock + status_t err = lcblk->resize(bufferCount); + if (err == NO_ERROR) + mBufferManager.resize(bufferCount); + + return err; +} + +sp<GraphicBuffer> Layer::requestBuffer(int index, + uint32_t reqWidth, uint32_t reqHeight, uint32_t reqFormat, + uint32_t usage) { sp<GraphicBuffer> buffer; + if (int32_t(reqWidth | reqHeight | reqFormat) < 0) + return buffer; + + if ((!reqWidth && reqHeight) || (reqWidth && !reqHeight)) + return buffer; + // this ensures our client doesn't go away while we're accessing // the shared area. - sp<Client> ourClient(client.promote()); - if (ourClient == 0) { + ClientRef::Access sharedClient(mUserClientRef); + SharedBufferServer* lcblk(sharedClient.get()); + if (!lcblk) { // oops, the client is already gone return buffer; } @@ -324,46 +313,38 @@ sp<GraphicBuffer> Layer::requestBuffer(int index, int usage) * This is called from the client's Surface::dequeue(). This can happen * at any time, especially while we're in the middle of using the * buffer 'index' as our front buffer. - * - * Make sure the buffer we're resizing is not the front buffer and has been - * dequeued. Once this condition is asserted, we are guaranteed that this - * buffer cannot become the front buffer under our feet, since we're called - * from Surface::dequeue() */ - status_t err = lcblk->assertReallocate(index); - LOGE_IF(err, "assertReallocate(%d) failed (%s)", index, strerror(-err)); - if (err != NO_ERROR) { - // the surface may have died - return buffer; - } - uint32_t w, h; + status_t err = NO_ERROR; + uint32_t w, h, f; { // scope for the lock Mutex::Autolock _l(mLock); - w = mWidth; - h = mHeight; - buffer = mBuffers[index]; - - // destroy() could have been called before we get here, we log it - // because it's uncommon, and the code below should handle it - LOGW_IF(buffer==0, - "mBuffers[%d] is null (mWidth=%d, mHeight=%d)", - index, w, h); - - mBuffers[index].clear(); + + // zero means default + if (!reqFormat) reqFormat = mFormat; + if (!reqWidth) reqWidth = mWidth; + if (!reqHeight) reqHeight = mHeight; + + w = reqWidth; + h = reqHeight; + f = reqFormat; + + if ((reqWidth != mReqWidth) || (reqHeight != mReqHeight) || + (reqFormat != mReqFormat)) { + mReqWidth = reqWidth; + mReqHeight = reqHeight; + mReqFormat = reqFormat; + + lcblk->reallocateAllExcept(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. const uint32_t effectiveUsage = getEffectiveUsage(usage); - if (buffer!=0 && buffer->getStrongCount() == 1) { - err = buffer->reallocate(w, h, mFormat, effectiveUsage); - } else { - // here we have to reallocate a new buffer because we could have a - // client in our process with a reference to it (eg: status bar), - // and we can't release the handle under its feet. - buffer.clear(); - buffer = new GraphicBuffer(w, h, mFormat, effectiveUsage); - err = buffer->initCheck(); - } + buffer = new GraphicBuffer(w, h, f, effectiveUsage); + err = buffer->initCheck(); if (err || buffer->handle == 0) { LOGE_IF(err || buffer->handle == 0, @@ -377,15 +358,7 @@ sp<GraphicBuffer> Layer::requestBuffer(int index, int usage) if (err == NO_ERROR && buffer->handle != 0) { Mutex::Autolock _l(mLock); - if (mWidth && mHeight) { - // and we have new buffer - mBuffers[index] = buffer; - // texture is now dirty... - mTextures[index].dirty = true; - } else { - // oops we got killed while we were allocating the buffer - buffer.clear(); - } + mBufferManager.attachBuffer(index, buffer); } return buffer; } @@ -411,15 +384,8 @@ uint32_t Layer::getEffectiveUsage(uint32_t usage) const } else { // it's allowed to modify the usage flags here, but generally // the requested flags should be honored. - if (mNoEGLImageForSwBuffers) { - if (usage & GraphicBuffer::USAGE_HW_MASK) { - // request EGLImage for h/w buffers only - usage |= GraphicBuffer::USAGE_HW_TEXTURE; - } - } else { - // request EGLImage for all buffers - usage |= GraphicBuffer::USAGE_HW_TEXTURE; - } + // request EGLImage for all buffers + usage |= GraphicBuffer::USAGE_HW_TEXTURE; } return usage; } @@ -429,42 +395,50 @@ uint32_t Layer::doTransaction(uint32_t flags) const Layer::State& front(drawingState()); const Layer::State& temp(currentState()); - if ((front.requested_w != temp.requested_w) || - (front.requested_h != temp.requested_h)) { + const bool sizeChanged = (front.requested_w != temp.requested_w) || + (front.requested_h != temp.requested_h); + + if (sizeChanged) { // the size changed, we need to ask our client to request a new buffer LOGD_IF(DEBUG_RESIZE, - "resize (layer=%p), requested (%dx%d), " - "drawing (%d,%d), (%dx%d), (%dx%d)", - this, - int(temp.requested_w), int(temp.requested_h), - int(front.requested_w), int(front.requested_h), - int(mBuffers[0]->getWidth()), int(mBuffers[0]->getHeight()), - int(mBuffers[1]->getWidth()), int(mBuffers[1]->getHeight())); - - // we're being resized and there is a freeze display request, - // acquire a freeze lock, so that the screen stays put - // until we've redrawn at the new size; this is to avoid - // glitches upon orientation changes. - if (mFlinger->hasFreezeRequest()) { - // if the surface is hidden, don't try to acquire the - // freeze lock, since hidden surfaces may never redraw - if (!(front.flags & ISurfaceComposer::eLayerHidden)) { - mFreezeLock = mFlinger->getFreezeLock(); + "resize (layer=%p), requested (%dx%d), drawing (%d,%d)", + this, + int(temp.requested_w), int(temp.requested_h), + int(front.requested_w), int(front.requested_h)); + + if (!isFixedSize()) { + // we're being resized and there is a freeze display request, + // acquire a freeze lock, so that the screen stays put + // until we've redrawn at the new size; this is to avoid + // glitches upon orientation changes. + if (mFlinger->hasFreezeRequest()) { + // if the surface is hidden, don't try to acquire the + // freeze lock, since hidden surfaces may never redraw + if (!(front.flags & ISurfaceComposer::eLayerHidden)) { + mFreezeLock = mFlinger->getFreezeLock(); + } } - } - - // this will make sure LayerBase::doTransaction doesn't update - // the drawing state's size - Layer::State& editDraw(mDrawingState); - editDraw.requested_w = temp.requested_w; - editDraw.requested_h = temp.requested_h; - // record the new size, form this point on, when the client request a - // buffer, it'll get the new size. - setDrawingSize(temp.requested_w, temp.requested_h); - - // all buffers need reallocation - lcblk->reallocate(); + // this will make sure LayerBase::doTransaction doesn't update + // the drawing state's size + Layer::State& editDraw(mDrawingState); + editDraw.requested_w = temp.requested_w; + editDraw.requested_h = temp.requested_h; + + // record the new size, form this point on, when the client request + // a buffer, it'll get the new size. + setBufferSize(temp.requested_w, temp.requested_h); + + ClientRef::Access sharedClient(mUserClientRef); + SharedBufferServer* lcblk(sharedClient.get()); + if (lcblk) { + // all buffers need reallocation + lcblk->reallocateAll(); + } + } else { + // record the new size + setBufferSize(temp.requested_w, temp.requested_h); + } } if (temp.sequence != front.sequence) { @@ -478,39 +452,55 @@ uint32_t Layer::doTransaction(uint32_t flags) return LayerBase::doTransaction(flags); } -void Layer::setDrawingSize(uint32_t w, uint32_t h) { +void Layer::setBufferSize(uint32_t w, uint32_t h) { Mutex::Autolock _l(mLock); mWidth = w; mHeight = h; } +bool Layer::isFixedSize() const { + Mutex::Autolock _l(mLock); + return mFixedSize; +} + // ---------------------------------------------------------------------------- // pageflip handling... // ---------------------------------------------------------------------------- void Layer::lockPageFlip(bool& recomputeVisibleRegions) { + ClientRef::Access sharedClient(mUserClientRef); + SharedBufferServer* lcblk(sharedClient.get()); + if (!lcblk) { + // client died + recomputeVisibleRegions = true; + return; + } + ssize_t buf = lcblk->retireAndLock(); - if (buf < NO_ERROR) { - //LOGW("nothing to retire (%s)", strerror(-buf)); - // NOTE: here the buffer is locked because we will used + if (buf == NOT_ENOUGH_DATA) { + // NOTE: This is not an error, it simply means there is nothing to + // retire. The buffer is locked because we will use it // for composition later in the loop return; } - // ouch, this really should never happen - if (uint32_t(buf)>=NUM_BUFFERS) { - LOGE("retireAndLock() buffer index (%d) out of range", buf); + if (buf < NO_ERROR) { + LOGE("retireAndLock() buffer index (%d) out of range", int(buf)); mPostedDirtyRegion.clear(); return; } // we retired a buffer, which becomes the new front buffer - mFrontBufferIndex = buf; + if (mBufferManager.setActiveBufferIndex(buf) < NO_ERROR) { + LOGE("retireAndLock() buffer index (%d) out of range", int(buf)); + mPostedDirtyRegion.clear(); + return; + } - // get the dirty region sp<GraphicBuffer> newFrontBuffer(getBuffer(buf)); if (newFrontBuffer != NULL) { + // get the dirty region // compute the posted region const Region dirty(lcblk->getDirtyRegion(buf)); mPostedDirtyRegion = dirty.intersect( newFrontBuffer->getBounds() ); @@ -546,6 +536,13 @@ void Layer::lockPageFlip(bool& recomputeVisibleRegions) // we now have the correct size, unfreeze the screen mFreezeLock.clear(); } + + // get the crop region + setBufferCrop( lcblk->getCrop(buf) ); + + // get the transformation + setBufferTransform( lcblk->getTransform(buf) ); + } else { // this should not happen unless we ran out of memory while // allocating the buffer. we're hoping that things will get back @@ -559,9 +556,15 @@ void Layer::lockPageFlip(bool& recomputeVisibleRegions) mFlinger->signalEvent(); } - if (!mPostedDirtyRegion.isEmpty()) { - reloadTexture( mPostedDirtyRegion ); - } + /* a buffer was posted, so we need to call reloadTexture(), which + * will update our internal data structures (eg: EGLImageKHR or + * texture names). we need to do this even if mPostedDirtyRegion is + * empty -- it's orthogonal to the fact that a new buffer was posted, + * for instance, a degenerate case could be that the user did an empty + * update but repainted the buffer with appropriate content (after a + * resize for instance). + */ + reloadTexture( mPostedDirtyRegion ); } void Layer::unlockPageFlip( @@ -585,24 +588,265 @@ void Layer::unlockPageFlip( } if (visibleRegionScreen.isEmpty()) { // an invisible layer should not hold a freeze-lock - // (because it may never be updated and thereore never release it) + // (because it may never be updated and therefore never release it) mFreezeLock.clear(); } } void Layer::finishPageFlip() { - status_t err = lcblk->unlock( mFrontBufferIndex ); - LOGE_IF(err!=NO_ERROR, - "layer %p, buffer=%d wasn't locked!", - this, mFrontBufferIndex); + ClientRef::Access sharedClient(mUserClientRef); + SharedBufferServer* lcblk(sharedClient.get()); + if (lcblk) { + int buf = mBufferManager.getActiveBufferIndex(); + if (buf >= 0) { + status_t err = lcblk->unlock( buf ); + LOGE_IF(err!=NO_ERROR, + "layer %p, buffer=%d wasn't locked!", + this, buf); + } + } +} + + +void Layer::dump(String8& result, char* buffer, size_t SIZE) const +{ + LayerBaseClient::dump(result, buffer, SIZE); + + ClientRef::Access sharedClient(mUserClientRef); + SharedBufferServer* lcblk(sharedClient.get()); + uint32_t totalTime = 0; + if (lcblk) { + SharedBufferStack::Statistics stats = lcblk->getStats(); + totalTime= stats.totalTime; + result.append( lcblk->dump(" ") ); + } + + sp<const GraphicBuffer> buf0(getBuffer(0)); + sp<const GraphicBuffer> buf1(getBuffer(1)); + uint32_t w0=0, h0=0, s0=0; + uint32_t w1=0, h1=0, s1=0; + if (buf0 != 0) { + w0 = buf0->getWidth(); + h0 = buf0->getHeight(); + s0 = buf0->getStride(); + } + if (buf1 != 0) { + w1 = buf1->getWidth(); + h1 = buf1->getHeight(); + s1 = buf1->getStride(); + } + snprintf(buffer, SIZE, + " " + "format=%2d, [%3ux%3u:%3u] [%3ux%3u:%3u]," + " freezeLock=%p, dq-q-time=%u us\n", + mFormat, w0, h0, s0, w1, h1, s1, + getFreezeLock().get(), totalTime); + + result.append(buffer); +} + +// --------------------------------------------------------------------------- + +Layer::ClientRef::ClientRef() + : mControlBlock(0), mToken(-1) { +} + +Layer::ClientRef::~ClientRef() { +} + +int32_t Layer::ClientRef::getToken() const { + Mutex::Autolock _l(mLock); + return mToken; +} + +sp<UserClient> Layer::ClientRef::getClient() const { + Mutex::Autolock _l(mLock); + return mUserClient.promote(); +} + +status_t Layer::ClientRef::setToken(const sp<UserClient>& uc, + const sp<SharedBufferServer>& sharedClient, int32_t token) { + Mutex::Autolock _l(mLock); + + { // scope for strong mUserClient reference + sp<UserClient> userClient(mUserClient.promote()); + if (mUserClient != 0 && mControlBlock != 0) { + mControlBlock->setStatus(NO_INIT); + } + } + + mUserClient = uc; + mToken = token; + mControlBlock = sharedClient; + return NO_ERROR; +} + +sp<UserClient> Layer::ClientRef::getUserClientUnsafe() const { + return mUserClient.promote(); +} + +// this class gives us access to SharedBufferServer safely +// it makes sure the UserClient (and its associated shared memory) +// won't go away while we're accessing it. +Layer::ClientRef::Access::Access(const ClientRef& ref) + : mControlBlock(0) +{ + Mutex::Autolock _l(ref.mLock); + mUserClientStrongRef = ref.mUserClient.promote(); + if (mUserClientStrongRef != 0) + mControlBlock = ref.mControlBlock; +} + +Layer::ClientRef::Access::~Access() +{ +} + +// --------------------------------------------------------------------------- + +Layer::BufferManager::BufferManager(TextureManager& tm) + : mNumBuffers(NUM_BUFFERS), mTextureManager(tm), + mActiveBuffer(-1), mFailover(false) +{ +} + +Layer::BufferManager::~BufferManager() +{ +} + +status_t Layer::BufferManager::resize(size_t size) +{ + Mutex::Autolock _l(mLock); + mNumBuffers = size; + return NO_ERROR; +} + +// only for debugging +sp<GraphicBuffer> Layer::BufferManager::getBuffer(size_t index) const { + return mBufferData[index].buffer; +} + +status_t Layer::BufferManager::setActiveBufferIndex(size_t index) { + mActiveBuffer = index; + return NO_ERROR; +} + +size_t Layer::BufferManager::getActiveBufferIndex() const { + return mActiveBuffer; +} + +Texture Layer::BufferManager::getActiveTexture() const { + Texture res; + if (mFailover || mActiveBuffer<0) { + res = mFailoverTexture; + } else { + static_cast<Image&>(res) = mBufferData[mActiveBuffer].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; +} + +sp<GraphicBuffer> Layer::BufferManager::detachBuffer(size_t index) +{ + BufferData* const buffers = mBufferData; + sp<GraphicBuffer> buffer; + Mutex::Autolock _l(mLock); + buffer = buffers[index].buffer; + buffers[index].buffer = 0; + return buffer; +} + +status_t Layer::BufferManager::attachBuffer(size_t index, + const sp<GraphicBuffer>& buffer) +{ + BufferData* const buffers = mBufferData; + Mutex::Autolock _l(mLock); + buffers[index].buffer = buffer; + buffers[index].texture.dirty = true; + return NO_ERROR; +} + +status_t Layer::BufferManager::destroy(EGLDisplay dpy) +{ + BufferData* const buffers = mBufferData; + size_t num; + { // scope for the lock + Mutex::Autolock _l(mLock); + num = mNumBuffers; + for (size_t i=0 ; i<num ; i++) { + buffers[i].buffer = 0; + } + } + for (size_t i=0 ; i<num ; i++) { + destroyTexture(&buffers[i].texture, dpy); + } + destroyTexture(&mFailoverTexture, dpy); + return NO_ERROR; +} + +status_t Layer::BufferManager::initEglImage(EGLDisplay dpy, + const sp<GraphicBuffer>& buffer) +{ + status_t err = NO_INIT; + ssize_t index = mActiveBuffer; + if (index >= 0) { + if (!mFailover) { + Image& texture(mBufferData[index].texture); + err = mTextureManager.initEglImage(&texture, dpy, buffer); + // if EGLImage fails, we switch to regular texture mode, and we + // free all resources associated with using EGLImages. + if (err == NO_ERROR) { + mFailover = false; + destroyTexture(&mFailoverTexture, dpy); + } else { + mFailover = true; + const size_t num = mNumBuffers; + for (size_t i=0 ; i<num ; i++) { + destroyTexture(&mBufferData[i].texture, dpy); + } + } + } else { + // we failed once, don't try again + err = BAD_VALUE; + } + } + return err; +} + +status_t Layer::BufferManager::loadTexture( + const Region& dirty, const GGLSurface& t) +{ + return mTextureManager.loadTexture(&mFailoverTexture, dirty, t); +} + +status_t Layer::BufferManager::destroyTexture(Image* tex, EGLDisplay dpy) +{ + if (tex->name != -1U) { + glDeleteTextures(1, &tex->name); + tex->name = -1U; + } + if (tex->image != EGL_NO_IMAGE_KHR) { + eglDestroyImageKHR(dpy, tex->image); + tex->image = EGL_NO_IMAGE_KHR; + } + return NO_ERROR; } // --------------------------------------------------------------------------- Layer::SurfaceLayer::SurfaceLayer(const sp<SurfaceFlinger>& flinger, - SurfaceID id, const sp<Layer>& owner) - : Surface(flinger, id, owner->getIdentity(), owner) + const sp<Layer>& owner) + : Surface(flinger, owner->getIdentity(), owner) { } @@ -610,20 +854,37 @@ Layer::SurfaceLayer::~SurfaceLayer() { } -sp<GraphicBuffer> Layer::SurfaceLayer::requestBuffer(int index, int usage) +sp<GraphicBuffer> Layer::SurfaceLayer::requestBuffer(int index, + uint32_t w, uint32_t h, uint32_t format, uint32_t usage) { sp<GraphicBuffer> buffer; sp<Layer> owner(getOwner()); if (owner != 0) { - LOGE_IF(uint32_t(index)>=NUM_BUFFERS, - "getBuffer() index (%d) out of range", index); - if (uint32_t(index) < NUM_BUFFERS) { - buffer = owner->requestBuffer(index, usage); - } + /* + * requestBuffer() cannot be called from the main thread + * as it could cause a dead-lock, since it may have to wait + * on conditions updated my the main thread. + */ + buffer = owner->requestBuffer(index, w, h, format, usage); } return buffer; } +status_t Layer::SurfaceLayer::setBufferCount(int bufferCount) +{ + status_t err = DEAD_OBJECT; + sp<Layer> owner(getOwner()); + if (owner != 0) { + /* + * setBufferCount() cannot be called from the main thread + * as it could cause a dead-lock, since it may have to wait + * on conditions updated my the main thread. + */ + err = owner->setBufferCount(bufferCount); + } + return err; +} + // --------------------------------------------------------------------------- diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 743afb4..263c372 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -31,36 +31,44 @@ #include "LayerBase.h" #include "Transform.h" +#include "TextureManager.h" namespace android { // --------------------------------------------------------------------------- -class Client; class FreezeLock; +class Client; +class GLExtensions; +class UserClient; // --------------------------------------------------------------------------- -const size_t NUM_BUFFERS = 2; - class Layer : public LayerBaseClient { -public: - static const uint32_t typeInfo; - static const char* const typeID; - virtual char const* getTypeID() const { return typeID; } - virtual uint32_t getTypeInfo() const { return typeInfo; } - - Layer(SurfaceFlinger* flinger, DisplayID display, - const sp<Client>& client, int32_t i); +public: + Layer(SurfaceFlinger* flinger, DisplayID display, + const sp<Client>& client); - virtual ~Layer(); + virtual ~Layer(); + virtual const char* getTypeId() const { return "Layer"; } + + // the this layer's size and format status_t setBuffers(uint32_t w, uint32_t h, PixelFormat format, uint32_t flags=0); - void setDrawingSize(uint32_t w, uint32_t h); + // associate a UserClient to this Layer + status_t setToken(const sp<UserClient>& uc, SharedClient* sc, int32_t idx); + int32_t getToken() const; + sp<UserClient> getClient() const; + + // Set this Layer's buffers size + void setBufferSize(uint32_t w, uint32_t h); + bool isFixedSize() const; + // LayerBase interface + virtual void drawForSreenShot() const; virtual void onDraw(const Region& clip) const; virtual uint32_t doTransaction(uint32_t transactionFlags); virtual void lockPageFlip(bool& recomputeVisibleRegions); @@ -68,63 +76,161 @@ public: virtual void finishPageFlip(); virtual bool needsBlending() const { return mNeedsBlending; } virtual bool needsDithering() const { return mNeedsDithering; } + virtual bool needsFiltering() const; virtual bool isSecure() const { return mSecure; } virtual sp<Surface> createSurface() const; virtual status_t ditch(); - - // only for debugging - inline sp<GraphicBuffer> getBuffer(int i) { return mBuffers[i]; } - // only for debugging - inline const sp<FreezeLock>& getFreezeLock() const { return mFreezeLock; } + virtual void onRemoved(); + // only for debugging - inline PixelFormat pixelFormat() const { return mFormat; } + inline sp<GraphicBuffer> getBuffer(int i) const { + return mBufferManager.getBuffer(i); } // only for debugging - inline int getFrontBufferIndex() const { return mFrontBufferIndex; } + inline const sp<FreezeLock>& getFreezeLock() const { + return mFreezeLock; } + +protected: + virtual void dump(String8& result, char* scratch, size_t size) const; private: - inline sp<GraphicBuffer> getFrontBufferLocked() { - return mBuffers[mFrontBufferIndex]; - } - void reloadTexture(const Region& dirty); - uint32_t getEffectiveUsage(uint32_t usage) const; + sp<GraphicBuffer> requestBuffer(int bufferIdx, + uint32_t w, uint32_t h, uint32_t format, uint32_t usage); + status_t setBufferCount(int bufferCount); - sp<GraphicBuffer> requestBuffer(int index, int usage); - void destroy(); + // ----------------------------------------------------------------------- class SurfaceLayer : public LayerBaseClient::Surface { public: - SurfaceLayer(const sp<SurfaceFlinger>& flinger, - SurfaceID id, const sp<Layer>& owner); + SurfaceLayer(const sp<SurfaceFlinger>& flinger, const sp<Layer>& owner); ~SurfaceLayer(); private: - virtual sp<GraphicBuffer> requestBuffer(int index, int usage); + virtual sp<GraphicBuffer> requestBuffer(int bufferIdx, + uint32_t w, uint32_t h, uint32_t format, uint32_t usage); + virtual status_t setBufferCount(int bufferCount); sp<Layer> getOwner() const { return static_cast<Layer*>(Surface::getOwner().get()); } }; friend class SurfaceLayer; - - sp<Surface> mSurface; - - bool mSecure; - bool mNoEGLImageForSwBuffers; - int32_t mFrontBufferIndex; - bool mNeedsBlending; - bool mNeedsDithering; - Region mPostedDirtyRegion; - sp<FreezeLock> mFreezeLock; - PixelFormat mFormat; - - // protected by mLock - sp<GraphicBuffer> mBuffers[NUM_BUFFERS]; - Texture mTextures[NUM_BUFFERS]; - sp<GraphicBuffer> mHybridBuffer; - uint32_t mWidth; - uint32_t mHeight; - - mutable Mutex mLock; + + // ----------------------------------------------------------------------- + + class ClientRef { + ClientRef(const ClientRef& rhs); + ClientRef& operator = (const ClientRef& rhs); + mutable Mutex mLock; + // binder thread, page-flip thread + sp<SharedBufferServer> mControlBlock; + wp<UserClient> mUserClient; + int32_t mToken; + public: + ClientRef(); + ~ClientRef(); + int32_t getToken() const; + sp<UserClient> getClient() const; + status_t setToken(const sp<UserClient>& uc, + const sp<SharedBufferServer>& sharedClient, int32_t token); + sp<UserClient> getUserClientUnsafe() const; + class Access { + Access(const Access& rhs); + Access& operator = (const Access& rhs); + sp<UserClient> mUserClientStrongRef; + sp<SharedBufferServer> mControlBlock; + public: + Access(const ClientRef& ref); + ~Access(); + inline SharedBufferServer* get() const { return mControlBlock.get(); } + }; + friend class Access; + }; + + // ----------------------------------------------------------------------- + + class BufferManager { + static const size_t NUM_BUFFERS = 2; + struct BufferData { + sp<GraphicBuffer> buffer; + Image texture; + }; + // this lock protect mBufferData[].buffer but since there + // is very little contention, we have only one like for + // the whole array, we also use it to protect mNumBuffers. + mutable Mutex mLock; + BufferData mBufferData[SharedBufferStack::NUM_BUFFER_MAX]; + size_t mNumBuffers; + Texture mFailoverTexture; + TextureManager& mTextureManager; + ssize_t mActiveBuffer; + bool mFailover; + static status_t destroyTexture(Image* tex, EGLDisplay dpy); + + public: + static size_t getDefaultBufferCount() { return NUM_BUFFERS; } + BufferManager(TextureManager& tm); + ~BufferManager(); + + // detach/attach buffer from/to given index + 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); + + // ---------------------------------------------- + // must be called from GL thread + + // set/get active buffer index + status_t setActiveBufferIndex(size_t index); + size_t getActiveBufferIndex() const; + // return the active buffer + sp<GraphicBuffer> getActiveBuffer() const; + // return the active texture (or fail-over) + Texture getActiveTexture() const; + // frees resources associated with all buffers + status_t destroy(EGLDisplay dpy); + // load bitmap data into the active buffer + status_t loadTexture(const Region& dirty, const GGLSurface& t); + // make active buffer an EGLImage if needed + status_t initEglImage(EGLDisplay dpy, + const sp<GraphicBuffer>& buffer); + + // ---------------------------------------------- + // only for debugging + sp<GraphicBuffer> getBuffer(size_t index) const; + }; + + // ----------------------------------------------------------------------- + + // thread-safe + ClientRef mUserClientRef; + + // constants + sp<Surface> mSurface; + PixelFormat mFormat; + const GLExtensions& mGLExtensions; + bool mNeedsBlending; + bool mNeedsDithering; + + // page-flip thread (currently main thread) + bool mSecure; + Region mPostedDirtyRegion; + + // page-flip thread and transaction thread (currently main thread) + sp<FreezeLock> mFreezeLock; + + // see threading usage in declaration + TextureManager mTextureManager; + BufferManager mBufferManager; + + // binder thread, transaction thread + mutable Mutex mLock; + uint32_t mWidth; + uint32_t mHeight; + uint32_t mReqWidth; + uint32_t mReqHeight; + uint32_t mReqFormat; + bool mFixedSize; }; // --------------------------------------------------------------------------- diff --git a/services/surfaceflinger/LayerBase.cpp b/services/surfaceflinger/LayerBase.cpp index a8b735e..64eed4b 100644 --- a/services/surfaceflinger/LayerBase.cpp +++ b/services/surfaceflinger/LayerBase.cpp @@ -32,33 +32,30 @@ #include "LayerBase.h" #include "SurfaceFlinger.h" #include "DisplayHardware/DisplayHardware.h" +#include "TextureManager.h" namespace android { // --------------------------------------------------------------------------- -const uint32_t LayerBase::typeInfo = 1; -const char* const LayerBase::typeID = "LayerBase"; - -const uint32_t LayerBaseClient::typeInfo = LayerBase::typeInfo | 2; -const char* const LayerBaseClient::typeID = "LayerBaseClient"; - -// --------------------------------------------------------------------------- +int32_t LayerBase::sSequence = 1; LayerBase::LayerBase(SurfaceFlinger* flinger, DisplayID display) : dpy(display), contentDirty(false), + sequence(uint32_t(android_atomic_inc(&sSequence))), mFlinger(flinger), - mTransformed(false), - mUseLinearFiltering(false), + mNeedsFiltering(false), mOrientation(0), mLeft(0), mTop(0), mTransactionFlags(0), - mPremultipliedAlpha(true), mDebug(false), + mPremultipliedAlpha(true), mName("unnamed"), mDebug(false), mInvalidate(0) { const DisplayHardware& hw(flinger->graphicPlane(0).displayHardware()); mFlags = hw.getFlags(); + mBufferCrop.makeInvalid(); + mBufferTransform = 0; } LayerBase::~LayerBase() @@ -159,7 +156,6 @@ bool LayerBase::setAlpha(uint8_t alpha) { return true; } bool LayerBase::setMatrix(const layer_state_t::matrix22_t& matrix) { - // TODO: check the matrix has changed mCurrentState.sequence++; mCurrentState.transform.set( matrix.dsdx, matrix.dsdy, matrix.dtdx, matrix.dtdy); @@ -167,7 +163,6 @@ bool LayerBase::setMatrix(const layer_state_t::matrix22_t& matrix) { return true; } bool LayerBase::setTransparentRegionHint(const Region& transparent) { - // TODO: check the region has changed mCurrentState.sequence++; mCurrentState.transparentRegion = transparent; requestTransaction(); @@ -221,13 +216,12 @@ uint32_t LayerBase::doTransaction(uint32_t flags) flags |= eVisibleRegion; this->contentDirty = true; - const bool linearFiltering = mUseLinearFiltering; - mUseLinearFiltering = false; + mNeedsFiltering = false; if (!(mFlags & DisplayHardware::SLOW_CONFIG)) { // we may use linear filtering, if the matrix scales us const uint8_t type = temp.transform.getType(); if (!temp.transform.preserveRects() || (type >= Transform::SCALE)) { - mUseLinearFiltering = true; + mNeedsFiltering = true; } } } @@ -267,7 +261,6 @@ void LayerBase::validateVisibility(const Transform& planeTransform) // cache a few things... mOrientation = tr.getOrientation(); mTransformedBounds = tr.makeBounds(w, h); - mTransformed = transformed; mLeft = tr.tx(); mTop = tr.ty(); } @@ -316,65 +309,37 @@ void LayerBase::drawRegion(const Region& reg) const } } -void LayerBase::draw(const Region& inClip) const +void LayerBase::draw(const Region& clip) const { - // invalidate the region we'll update - Region clip(inClip); // copy-on-write, so no-op most of the time - - // Remove the transparent area from the clipping region - const State& s = drawingState(); - if (LIKELY(!s.transparentRegion.isEmpty())) { - clip.subtract(transparentRegionScreen); - if (clip.isEmpty()) { - // usually this won't happen because this should be taken care of - // by SurfaceFlinger::computeVisibleRegions() - return; - } - } - // reset GL state glEnable(GL_SCISSOR_TEST); onDraw(clip); - - /* - glDisable(GL_TEXTURE_2D); - glDisable(GL_DITHER); - glEnable(GL_BLEND); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - glColor4x(0, 0x8000, 0, 0x10000); - drawRegion(transparentRegionScreen); - glDisable(GL_BLEND); - */ } -GLuint LayerBase::createTexture() const +void LayerBase::drawForSreenShot() const { - GLuint textureName = -1; - glGenTextures(1, &textureName); - glBindTexture(GL_TEXTURE_2D, textureName); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - return textureName; -} - -void LayerBase::clearWithOpenGL(const Region& clip, GLclampx red, - GLclampx green, GLclampx blue, - GLclampx alpha) const + const DisplayHardware& hw(graphicPlane(0).displayHardware()); + onDraw( Region(hw.bounds()) ); +} + +void LayerBase::clearWithOpenGL(const Region& clip, GLclampf red, + GLclampf green, GLclampf blue, + GLclampf alpha) const { const DisplayHardware& hw(graphicPlane(0).displayHardware()); const uint32_t fbHeight = hw.getHeight(); - glColor4x(red,green,blue,alpha); - glDisable(GL_TEXTURE_2D); + glColor4f(red,green,blue,alpha); + + TextureManager::deactivateTextures(); + glDisable(GL_BLEND); glDisable(GL_DITHER); Region::const_iterator it = clip.begin(); Region::const_iterator const end = clip.end(); glEnable(GL_SCISSOR_TEST); - glVertexPointer(2, GL_FIXED, 0, mVertices); + glVertexPointer(2, GL_FLOAT, 0, mVertices); while (it != end) { const Rect& r = *it++; const GLint sy = fbHeight - (r.top + r.height()); @@ -388,6 +353,14 @@ void LayerBase::clearWithOpenGL(const Region& clip) const clearWithOpenGL(clip,0,0,0,0); } +template <typename T> +static inline +void swap(T& a, T& b) { + T t(a); + a = b; + b = t; +} + void LayerBase::drawWithOpenGL(const Region& clip, const Texture& texture) const { const DisplayHardware& hw(graphicPlane(0).displayHardware()); @@ -395,39 +368,25 @@ void LayerBase::drawWithOpenGL(const Region& clip, const Texture& texture) const const State& s(drawingState()); // bind our texture - validateTexture(texture.name); + TextureManager::activateTexture(texture, needsFiltering()); uint32_t width = texture.width; uint32_t height = texture.height; - - glEnable(GL_TEXTURE_2D); + GLenum src = mPremultipliedAlpha ? GL_ONE : GL_SRC_ALPHA; if (UNLIKELY(s.alpha < 0xFF)) { - // We have an alpha-modulation. We need to modulate all - // texture components by alpha because we're always using - // premultiplied alpha. - - // If the texture doesn't have an alpha channel we can - // use REPLACE and switch to non premultiplied alpha - // blending (SRCA/ONE_MINUS_SRCA). - - GLenum env, src; - if (needsBlending()) { - env = GL_MODULATE; - src = mPremultipliedAlpha ? GL_ONE : GL_SRC_ALPHA; + const GLfloat alpha = s.alpha * (1.0f/255.0f); + if (mPremultipliedAlpha) { + glColor4f(alpha, alpha, alpha, alpha); } else { - env = GL_REPLACE; - src = GL_SRC_ALPHA; + glColor4f(1, 1, 1, alpha); } - const GGLfixed alpha = (s.alpha << 16)/255; - glColor4x(alpha, alpha, alpha, alpha); glEnable(GL_BLEND); glBlendFunc(src, GL_ONE_MINUS_SRC_ALPHA); - glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, env); + glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); } else { + glColor4f(1, 1, 1, 1); glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - glColor4x(0x10000, 0x10000, 0x10000, 0x10000); if (needsBlending()) { - GLenum src = mPremultipliedAlpha ? GL_ONE : GL_SRC_ALPHA; glEnable(GL_BLEND); glBlendFunc(src, GL_ONE_MINUS_SRC_ALPHA); } else { @@ -435,44 +394,99 @@ void LayerBase::drawWithOpenGL(const Region& clip, const Texture& texture) const } } - Region::const_iterator it = clip.begin(); - Region::const_iterator const end = clip.end(); + /* + * compute texture coordinates + * here, we handle NPOT, cropping and buffer transformations + */ + + GLfloat cl, ct, cr, cb; + if (!mBufferCrop.isEmpty()) { + // source is cropped + const GLfloat us = (texture.NPOTAdjust ? texture.wScale : 1.0f) / width; + const GLfloat vs = (texture.NPOTAdjust ? texture.hScale : 1.0f) / height; + cl = mBufferCrop.left * us; + ct = mBufferCrop.top * vs; + cr = mBufferCrop.right * us; + cb = mBufferCrop.bottom * vs; + } else { + cl = 0; + ct = 0; + cr = (texture.NPOTAdjust ? texture.wScale : 1.0f); + cb = (texture.NPOTAdjust ? texture.hScale : 1.0f); + } - //StopWatch watch("GL transformed"); - const GLfixed texCoords[4][2] = { - { 0, 0 }, - { 0, 0x10000 }, - { 0x10000, 0x10000 }, - { 0x10000, 0 } + /* + * For the buffer transformation, we apply the rotation last. + * Since we're transforming the texture-coordinates, we need + * to apply the inverse of the buffer transformation: + * inverse( FLIP_V -> FLIP_H -> ROT_90 ) + * <=> inverse( ROT_90 * FLIP_H * FLIP_V ) + * = inverse(FLIP_V) * inverse(FLIP_H) * inverse(ROT_90) + * = FLIP_V * FLIP_H * ROT_270 + * <=> ROT_270 -> FLIP_H -> FLIP_V + * + * The rotation is performed first, in the texture coordinate space. + * + */ + + struct TexCoords { + GLfloat u; + GLfloat v; + }; + + enum { + // name of the corners in the texture map + LB = 0, // left-bottom + LT = 1, // left-top + RT = 2, // right-top + RB = 3 // right-bottom }; - glMatrixMode(GL_TEXTURE); - glLoadIdentity(); + // vertices in screen space + int vLT = LB; + int vLB = LT; + int vRB = RT; + int vRT = RB; // the texture's source is rotated - switch (texture.transform) { - case HAL_TRANSFORM_ROT_90: - glTranslatef(0, 1, 0); - glRotatef(-90, 0, 0, 1); - break; - case HAL_TRANSFORM_ROT_180: - glTranslatef(1, 1, 0); - glRotatef(-180, 0, 0, 1); - break; - case HAL_TRANSFORM_ROT_270: - glTranslatef(1, 0, 0); - glRotatef(-270, 0, 0, 1); - break; + uint32_t transform = mBufferTransform; + if (transform & HAL_TRANSFORM_ROT_90) { + vLT = RB; + vLB = LB; + vRB = LT; + vRT = RT; } + if (transform & HAL_TRANSFORM_FLIP_V) { + swap(vLT, vLB); + swap(vRT, vRB); + } + if (transform & HAL_TRANSFORM_FLIP_H) { + swap(vLT, vRT); + swap(vLB, vRB); + } + + TexCoords texCoords[4]; + texCoords[vLT].u = cl; + texCoords[vLT].v = ct; + texCoords[vLB].u = cl; + texCoords[vLB].v = cb; + texCoords[vRB].u = cr; + texCoords[vRB].v = cb; + texCoords[vRT].u = cr; + texCoords[vRT].v = ct; - if (texture.NPOTAdjust) { - glScalef(texture.wScale, texture.hScale, 1.0f); + if (needsDithering()) { + glEnable(GL_DITHER); + } else { + glDisable(GL_DITHER); } glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glVertexPointer(2, GL_FIXED, 0, mVertices); - glTexCoordPointer(2, GL_FIXED, 0, texCoords); + glVertexPointer(2, GL_FLOAT, 0, mVertices); + glTexCoordPointer(2, GL_FLOAT, 0, texCoords); + Region::const_iterator it = clip.begin(); + Region::const_iterator const end = clip.end(); while (it != end) { const Rect& r = *it++; const GLint sy = fbHeight - (r.top + r.height()); @@ -482,246 +496,50 @@ void LayerBase::drawWithOpenGL(const Region& clip, const Texture& texture) const glDisableClientState(GL_TEXTURE_COORD_ARRAY); } -void LayerBase::validateTexture(GLint textureName) const -{ - glBindTexture(GL_TEXTURE_2D, textureName); - // TODO: reload the texture if needed - // this is currently done in loadTexture() below - if (mUseLinearFiltering) { - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - } else { - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - } - - if (needsDithering()) { - glEnable(GL_DITHER); - } else { - glDisable(GL_DITHER); - } -} - -bool LayerBase::isSupportedYuvFormat(int format) const -{ - switch (format) { - case HAL_PIXEL_FORMAT_YCbCr_422_SP: - case HAL_PIXEL_FORMAT_YCbCr_420_SP: - case HAL_PIXEL_FORMAT_YCbCr_422_P: - case HAL_PIXEL_FORMAT_YCbCr_420_P: - case HAL_PIXEL_FORMAT_YCbCr_422_I: - case HAL_PIXEL_FORMAT_YCbCr_420_I: - case HAL_PIXEL_FORMAT_YCrCb_420_SP: - return true; +void LayerBase::setBufferCrop(const Rect& crop) { + if (!crop.isEmpty()) { + mBufferCrop = crop; } - return false; } -void LayerBase::loadTexture(Texture* texture, - const Region& dirty, const GGLSurface& t) const -{ - if (texture->name == -1U) { - // uh? - return; - } - - glBindTexture(GL_TEXTURE_2D, texture->name); - - /* - * In OpenGL ES we can't specify a stride with glTexImage2D (however, - * GL_UNPACK_ALIGNMENT is a limited form of stride). - * So if the stride here isn't representable with GL_UNPACK_ALIGNMENT, we - * need to do something reasonable (here creating a bigger texture). - * - * extra pixels = (((stride - width) * pixelsize) / GL_UNPACK_ALIGNMENT); - * - * This situation doesn't happen often, but some h/w have a limitation - * for their framebuffer (eg: must be multiple of 8 pixels), and - * we need to take that into account when using these buffers as - * textures. - * - * This should never be a problem with POT textures - */ - - int unpack = __builtin_ctz(t.stride * bytesPerPixel(t.format)); - unpack = 1 << ((unpack > 3) ? 3 : unpack); - glPixelStorei(GL_UNPACK_ALIGNMENT, unpack); - - /* - * round to POT if needed - */ - if (!(mFlags & DisplayHardware::NPOT_EXTENSION)) { - texture->NPOTAdjust = true; - } - - if (texture->NPOTAdjust) { - // find the smallest power-of-two that will accommodate our surface - texture->potWidth = 1 << (31 - clz(t.width)); - texture->potHeight = 1 << (31 - clz(t.height)); - if (texture->potWidth < t.width) texture->potWidth <<= 1; - if (texture->potHeight < t.height) texture->potHeight <<= 1; - texture->wScale = float(t.width) / texture->potWidth; - texture->hScale = float(t.height) / texture->potHeight; - } else { - texture->potWidth = t.width; - texture->potHeight = t.height; - } - - Rect bounds(dirty.bounds()); - GLvoid* data = 0; - if (texture->width != t.width || texture->height != t.height) { - texture->width = t.width; - texture->height = t.height; - - // texture size changed, we need to create a new one - bounds.set(Rect(t.width, t.height)); - if (t.width == texture->potWidth && - t.height == texture->potHeight) { - // we can do it one pass - data = t.data; - } - - if (t.format == HAL_PIXEL_FORMAT_RGB_565) { - glTexImage2D(GL_TEXTURE_2D, 0, - GL_RGB, texture->potWidth, texture->potHeight, 0, - GL_RGB, GL_UNSIGNED_SHORT_5_6_5, data); - } else if (t.format == HAL_PIXEL_FORMAT_RGBA_4444) { - glTexImage2D(GL_TEXTURE_2D, 0, - GL_RGBA, texture->potWidth, texture->potHeight, 0, - GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, data); - } else if (t.format == HAL_PIXEL_FORMAT_RGBA_8888 || - t.format == HAL_PIXEL_FORMAT_RGBX_8888) { - glTexImage2D(GL_TEXTURE_2D, 0, - GL_RGBA, texture->potWidth, texture->potHeight, 0, - GL_RGBA, GL_UNSIGNED_BYTE, data); - } else if (isSupportedYuvFormat(t.format)) { - // just show the Y plane of YUV buffers - glTexImage2D(GL_TEXTURE_2D, 0, - GL_LUMINANCE, texture->potWidth, texture->potHeight, 0, - GL_LUMINANCE, GL_UNSIGNED_BYTE, data); - } else { - // oops, we don't handle this format! - LOGE("layer %p, texture=%d, using format %d, which is not " - "supported by the GL", this, texture->name, t.format); - } - } - if (!data) { - if (t.format == HAL_PIXEL_FORMAT_RGB_565) { - glTexSubImage2D(GL_TEXTURE_2D, 0, - 0, bounds.top, t.width, bounds.height(), - GL_RGB, GL_UNSIGNED_SHORT_5_6_5, - t.data + bounds.top*t.stride*2); - } else if (t.format == HAL_PIXEL_FORMAT_RGBA_4444) { - glTexSubImage2D(GL_TEXTURE_2D, 0, - 0, bounds.top, t.width, bounds.height(), - GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, - t.data + bounds.top*t.stride*2); - } else if (t.format == HAL_PIXEL_FORMAT_RGBA_8888 || - t.format == HAL_PIXEL_FORMAT_RGBX_8888) { - glTexSubImage2D(GL_TEXTURE_2D, 0, - 0, bounds.top, t.width, bounds.height(), - GL_RGBA, GL_UNSIGNED_BYTE, - t.data + bounds.top*t.stride*4); - } else if (isSupportedYuvFormat(t.format)) { - // just show the Y plane of YUV buffers - glTexSubImage2D(GL_TEXTURE_2D, 0, - 0, bounds.top, t.width, bounds.height(), - GL_LUMINANCE, GL_UNSIGNED_BYTE, - t.data + bounds.top*t.stride); - } - } +void LayerBase::setBufferTransform(uint32_t transform) { + mBufferTransform = transform; } -status_t LayerBase::initializeEglImage( - const sp<GraphicBuffer>& buffer, Texture* texture) +void LayerBase::dump(String8& result, char* buffer, size_t SIZE) const { - status_t err = NO_ERROR; - - // we need to recreate the texture - EGLDisplay dpy(mFlinger->graphicPlane(0).getEGLDisplay()); - - // free the previous image - if (texture->image != EGL_NO_IMAGE_KHR) { - eglDestroyImageKHR(dpy, texture->image); - texture->image = EGL_NO_IMAGE_KHR; - } - - // construct an EGL_NATIVE_BUFFER_ANDROID - android_native_buffer_t* clientBuf = buffer->getNativeBuffer(); - - // create the new EGLImageKHR - const EGLint attrs[] = { - EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, - EGL_NONE, EGL_NONE - }; - texture->image = eglCreateImageKHR( - dpy, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, - (EGLClientBuffer)clientBuf, attrs); - - if (texture->image != EGL_NO_IMAGE_KHR) { - glBindTexture(GL_TEXTURE_2D, texture->name); - glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, - (GLeglImageOES)texture->image); - GLint error = glGetError(); - if (UNLIKELY(error != GL_NO_ERROR)) { - LOGE("layer=%p, glEGLImageTargetTexture2DOES(%p) " - "failed err=0x%04x", - this, texture->image, error); - err = INVALID_OPERATION; - } else { - // Everything went okay! - texture->NPOTAdjust = false; - texture->dirty = false; - texture->width = clientBuf->width; - texture->height = clientBuf->height; - } - } else { - LOGE("layer=%p, eglCreateImageKHR() failed. err=0x%4x", - this, eglGetError()); - err = INVALID_OPERATION; - } - return err; + const Layer::State& s(drawingState()); + snprintf(buffer, SIZE, + "+ %s %p\n" + " " + "z=%9d, pos=(%4d,%4d), size=(%4d,%4d), " + "needsBlending=%1d, needsDithering=%1d, invalidate=%1d, " + "alpha=0x%02x, flags=0x%08x, tr=[%.2f, %.2f][%.2f, %.2f]\n", + getTypeId(), this, s.z, tx(), ty(), s.w, s.h, + needsBlending(), needsDithering(), contentDirty, + s.alpha, s.flags, + s.transform[0][0], s.transform[0][1], + s.transform[1][0], s.transform[1][1]); + result.append(buffer); } - // --------------------------------------------------------------------------- -int32_t LayerBaseClient::sIdentity = 0; +int32_t LayerBaseClient::sIdentity = 1; LayerBaseClient::LayerBaseClient(SurfaceFlinger* flinger, DisplayID display, - const sp<Client>& client, int32_t i) - : LayerBase(flinger, display), lcblk(NULL), client(client), mIndex(i), + const sp<Client>& client) + : LayerBase(flinger, display), mClientRef(client), mIdentity(uint32_t(android_atomic_inc(&sIdentity))) { - lcblk = new SharedBufferServer( - client->ctrlblk, i, NUM_BUFFERS, - mIdentity); -} - -void LayerBaseClient::onFirstRef() -{ - sp<Client> client(this->client.promote()); - if (client != 0) { - client->bindLayer(this, mIndex); - } } LayerBaseClient::~LayerBaseClient() { - sp<Client> client(this->client.promote()); - if (client != 0) { - client->free(mIndex); - } - delete lcblk; -} - -int32_t LayerBaseClient::serverIndex() const -{ - sp<Client> client(this->client.promote()); - if (client != 0) { - return (client->cid<<16)|mIndex; + sp<Client> c(mClientRef.promote()); + if (c != 0) { + c->detachLayer(this); } - return 0xFFFF0000 | mIndex; } sp<LayerBaseClient::Surface> LayerBaseClient::getSurface() @@ -738,25 +556,31 @@ sp<LayerBaseClient::Surface> LayerBaseClient::getSurface() sp<LayerBaseClient::Surface> LayerBaseClient::createSurface() const { - return new Surface(mFlinger, clientIndex(), mIdentity, + return new Surface(mFlinger, mIdentity, const_cast<LayerBaseClient *>(this)); } -// called with SurfaceFlinger::mStateLock as soon as the layer is entered -// in the purgatory list -void LayerBaseClient::onRemoved() +void LayerBaseClient::dump(String8& result, char* buffer, size_t SIZE) const { - // wake up the condition - lcblk->setStatus(NO_INIT); + LayerBase::dump(result, buffer, SIZE); + + sp<Client> client(mClientRef.promote()); + snprintf(buffer, SIZE, + " name=%s\n" + " client=%p, identity=%u\n", + getName().string(), + client.get(), getIdentity()); + + result.append(buffer); } // --------------------------------------------------------------------------- LayerBaseClient::Surface::Surface( const sp<SurfaceFlinger>& flinger, - SurfaceID id, int identity, + int identity, const sp<LayerBaseClient>& owner) - : mFlinger(flinger), mToken(id), mIdentity(identity), mOwner(owner) + : mFlinger(flinger), mIdentity(identity), mOwner(owner) { } @@ -799,11 +623,17 @@ status_t LayerBaseClient::Surface::onTransact( return BnSurface::onTransact(code, data, reply, flags); } -sp<GraphicBuffer> LayerBaseClient::Surface::requestBuffer(int index, int usage) +sp<GraphicBuffer> LayerBaseClient::Surface::requestBuffer(int bufferIdx, + uint32_t w, uint32_t h, uint32_t format, uint32_t usage) { return NULL; } +status_t LayerBaseClient::Surface::setBufferCount(int bufferCount) +{ + return INVALID_OPERATION; +} + status_t LayerBaseClient::Surface::registerBuffers( const ISurface::BufferHeap& buffers) { diff --git a/services/surfaceflinger/LayerBase.h b/services/surfaceflinger/LayerBase.h index 62ec839..d688f65 100644 --- a/services/surfaceflinger/LayerBase.h +++ b/services/surfaceflinger/LayerBase.h @@ -29,7 +29,7 @@ #include <ui/Region.h> #include <ui/Overlay.h> -#include <surfaceflinger/ISurfaceFlingerClient.h> +#include <surfaceflinger/ISurfaceComposerClient.h> #include <private/surfaceflinger/SharedBufferStack.h> #include <private/surfaceflinger/LayerState.h> @@ -45,46 +45,25 @@ class DisplayHardware; class Client; class GraphicBuffer; class GraphicPlane; +class LayerBaseClient; class SurfaceFlinger; +class Texture; // --------------------------------------------------------------------------- class LayerBase : public RefBase { - // poor man's dynamic_cast below - template<typename T> - struct getTypeInfoOfAnyType { - static uint32_t get() { return T::typeInfo; } - }; - - template<typename T> - struct getTypeInfoOfAnyType<T*> { - static uint32_t get() { return getTypeInfoOfAnyType<T>::get(); } - }; + static int32_t sSequence; public: - static const uint32_t typeInfo; - static const char* const typeID; - virtual char const* getTypeID() const { return typeID; } - virtual uint32_t getTypeInfo() const { return typeInfo; } - - template<typename T> - static T dynamicCast(LayerBase* base) { - uint32_t mostDerivedInfo = base->getTypeInfo(); - uint32_t castToInfo = getTypeInfoOfAnyType<T>::get(); - if ((mostDerivedInfo & castToInfo) == castToInfo) - return static_cast<T>(base); - return 0; - } + LayerBase(SurfaceFlinger* flinger, DisplayID display); - - LayerBase(SurfaceFlinger* flinger, DisplayID display); - DisplayID dpy; mutable bool contentDirty; Region visibleRegionScreen; Region transparentRegionScreen; Region coveredRegionScreen; + int32_t sequence; struct State { uint32_t w; @@ -125,6 +104,10 @@ public: void invalidate(); + virtual sp<LayerBaseClient> getLayerBaseClient() const { return 0; } + + virtual const char* getTypeId() const { return "LayerBase"; } + /** * draw - performs some global clipping optimizations * and calls onDraw(). @@ -132,6 +115,7 @@ public: * to perform the actual drawing. */ virtual void draw(const Region& clip) const; + virtual void drawForSreenShot() const; /** * onDraw - draws the surface. @@ -199,9 +183,9 @@ public: virtual bool needsDithering() const { return false; } /** - * transformed -- true is this surface needs a to be transformed + * needsLinearFiltering - true if this surface needs filtering */ - virtual bool transformed() const { return mTransformed; } + virtual bool needsFiltering() const { return mNeedsFiltering; } /** * isSecure - true if this surface is secure, that is if it prevents @@ -217,7 +201,10 @@ public: * current list */ virtual void onRemoved() { }; - + /** always call base class first */ + virtual void dump(String8& result, char* scratch, size_t size) const; + + enum { // flags for doTransaction() eVisibleRegion = 0x00000002, }; @@ -227,12 +214,6 @@ public: inline const State& currentState() const { return mCurrentState; } inline State& currentState() { return mCurrentState; } - static int compareCurrentStateZ( - sp<LayerBase> const * layerA, - sp<LayerBase> const * layerB) { - return layerA[0]->currentState().z - layerB[0]->currentState().z; - } - int32_t getOrientation() const { return mOrientation; } int tx() const { return mLeft; } int ty() const { return mTop; } @@ -241,44 +222,26 @@ protected: const GraphicPlane& graphicPlane(int dpy) const; GraphicPlane& graphicPlane(int dpy); - GLuint createTexture() const; - - struct Texture { - Texture() : name(-1U), width(0), height(0), - image(EGL_NO_IMAGE_KHR), transform(0), - NPOTAdjust(false), dirty(true) { } - GLuint name; - GLuint width; - GLuint height; - GLuint potWidth; - GLuint potHeight; - GLfloat wScale; - GLfloat hScale; - EGLImageKHR image; - uint32_t transform; - bool NPOTAdjust; - bool dirty; - }; - - void clearWithOpenGL(const Region& clip, GLclampx r, GLclampx g, - GLclampx b, GLclampx alpha) const; + void clearWithOpenGL(const Region& clip, GLclampf r, GLclampf g, + GLclampf b, GLclampf alpha) const; void clearWithOpenGL(const Region& clip) const; void drawWithOpenGL(const Region& clip, const Texture& texture) const; - void loadTexture(Texture* texture, - const Region& dirty, const GGLSurface& t) const; - status_t initializeEglImage( - const sp<GraphicBuffer>& buffer, Texture* texture); - - bool isSupportedYuvFormat(int format) const; + // these must be called from the post/drawing thread + void setBufferCrop(const Rect& crop); + void setBufferTransform(uint32_t transform); + sp<SurfaceFlinger> mFlinger; uint32_t mFlags; + // post/drawing thread + Rect mBufferCrop; + uint32_t mBufferTransform; + // cached during validateVisibility() - bool mTransformed; - bool mUseLinearFiltering; + bool mNeedsFiltering; int32_t mOrientation; - GLfixed mVertices[4][2]; + GLfloat mVertices[4][2]; Rect mTransformedBounds; int mLeft; int mTop; @@ -303,7 +266,6 @@ protected: private: LayerBase(const LayerBase& rhs); - void validateTexture(GLint textureName) const; }; @@ -313,42 +275,25 @@ class LayerBaseClient : public LayerBase { public: class Surface; - static const uint32_t typeInfo; - static const char* const typeID; - virtual char const* getTypeID() const { return typeID; } - virtual uint32_t getTypeInfo() const { return typeInfo; } - - // lcblk is (almost) only accessed from the main SF thread, in the places - // where it's not, a reference to Client must be held - SharedBufferServer* lcblk; - LayerBaseClient(SurfaceFlinger* flinger, DisplayID display, - const sp<Client>& client, int32_t i); + LayerBaseClient(SurfaceFlinger* flinger, DisplayID display, + const sp<Client>& client); virtual ~LayerBaseClient(); - virtual void onFirstRef(); - - const wp<Client> client; - inline uint32_t getIdentity() const { return mIdentity; } - inline int32_t clientIndex() const { return mIndex; } - int32_t serverIndex() const; - - sp<Surface> getSurface(); virtual sp<Surface> createSurface() const; - - virtual void onRemoved(); + virtual sp<LayerBaseClient> getLayerBaseClient() const { + return const_cast<LayerBaseClient*>(this); } + virtual const char* getTypeId() const { return "LayerBaseClient"; } + uint32_t getIdentity() const { return mIdentity; } - class Surface : public BnSurface - { + class Surface : public BnSurface { public: - int32_t getToken() const { return mToken; } int32_t getIdentity() const { return mIdentity; } protected: - Surface(const sp<SurfaceFlinger>& flinger, - SurfaceID id, int identity, + Surface(const sp<SurfaceFlinger>& flinger, int identity, const sp<LayerBaseClient>& owner); virtual ~Surface(); virtual status_t onTransact(uint32_t code, const Parcel& data, @@ -356,7 +301,10 @@ public: sp<LayerBaseClient> getOwner() const; private: - virtual sp<GraphicBuffer> requestBuffer(int index, int usage); + virtual sp<GraphicBuffer> requestBuffer(int bufferIdx, + 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(); @@ -366,20 +314,22 @@ public: protected: friend class LayerBaseClient; sp<SurfaceFlinger> mFlinger; - int32_t mToken; int32_t mIdentity; wp<LayerBaseClient> mOwner; }; friend class Surface; +protected: + virtual void dump(String8& result, char* scratch, size_t size) const; + private: - int32_t mIndex; - mutable Mutex mLock; - mutable wp<Surface> mClientSurface; + mutable Mutex mLock; + mutable wp<Surface> mClientSurface; + const wp<Client> mClientRef; // only read - const uint32_t mIdentity; - static int32_t sIdentity; + const uint32_t mIdentity; + static int32_t sIdentity; }; // --------------------------------------------------------------------------- diff --git a/services/surfaceflinger/LayerBlur.cpp b/services/surfaceflinger/LayerBlur.cpp index 5fd7904..4cfcfe3 100644 --- a/services/surfaceflinger/LayerBlur.cpp +++ b/services/surfaceflinger/LayerBlur.cpp @@ -33,14 +33,9 @@ namespace android { // --------------------------------------------------------------------------- -const uint32_t LayerBlur::typeInfo = LayerBaseClient::typeInfo | 8; -const char* const LayerBlur::typeID = "LayerBlur"; - -// --------------------------------------------------------------------------- - LayerBlur::LayerBlur(SurfaceFlinger* flinger, DisplayID display, - const sp<Client>& client, int32_t i) - : LayerBaseClient(flinger, display, client, i), mCacheDirty(true), + 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) @@ -100,7 +95,9 @@ void LayerBlur::unlockPageFlip(const Transform& planeTransform, Region& outDirty mCacheDirty = false; } else { if (!mAutoRefreshPending) { - mFlinger->signalDelayedEvent(ms2ns(500)); + mFlinger->postMessageAsync( + new MessageBase(MessageQueue::INVALIDATE), + ms2ns(500)); mAutoRefreshPending = true; } } @@ -149,6 +146,11 @@ void LayerBlur::onDraw(const Region& clip) const 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); @@ -181,7 +183,7 @@ void LayerBlur::onDraw(const Region& clip) const bl.data = (GGLubyte*)pixels; blurFilter(&bl, 8, 2); - if (mFlags & (DisplayHardware::NPOT_EXTENSION)) { + if (GLExtensions::getInstance().haveNpot()) { glTexImage2D(GL_TEXTURE_2D, 0, mReadFormat, w, h, 0, mReadFormat, mReadType, pixels); mWidthScale = 1.0f / w; @@ -206,8 +208,8 @@ void LayerBlur::onDraw(const Region& clip) const const State& s = drawingState(); if (UNLIKELY(s.alpha < 0xFF)) { - const GGLfixed alpha = (s.alpha << 16)/255; - glColor4x(0, 0, 0, alpha); + 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); @@ -225,38 +227,22 @@ void LayerBlur::onDraw(const Region& clip) const glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - if (UNLIKELY(transformed() - || !(mFlags & DisplayHardware::DRAW_TEXTURE_EXTENSION) )) { - // This is a very rare scenario. - glMatrixMode(GL_TEXTURE); - glLoadIdentity(); - glScalef(mWidthScale, mHeightScale, 1); - glTranslatef(-x, mYOffset - y, 0); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glVertexPointer(2, GL_FIXED, 0, mVertices); - glTexCoordPointer(2, GL_FIXED, 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); - } else { - // NOTE: this is marginally faster with the software gl, because - // glReadPixels() reads the fb bottom-to-top, however we'll - // skip all the jaccobian computations. - Rect r; - GLint crop[4] = { 0, 0, w, h }; - glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop); - y = fbHeight - (y + h); - while (it != end) { - const Rect& r = *it++; - const GLint sy = fbHeight - (r.top + r.height()); - glScissor(r.left, sy, r.width(), r.height()); - glDrawTexiOES(x, y, 0, w, h); - } + 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); } } diff --git a/services/surfaceflinger/LayerBlur.h b/services/surfaceflinger/LayerBlur.h index 5b63dec..4c9ec64 100644 --- a/services/surfaceflinger/LayerBlur.h +++ b/services/surfaceflinger/LayerBlur.h @@ -31,18 +31,14 @@ namespace android { class LayerBlur : public LayerBaseClient { public: - static const uint32_t typeInfo; - static const char* const typeID; - virtual char const* getTypeID() const { return typeID; } - virtual uint32_t getTypeInfo() const { return typeInfo; } - LayerBlur(SurfaceFlinger* flinger, DisplayID display, - const sp<Client>& client, int32_t i); + 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); diff --git a/services/surfaceflinger/LayerBuffer.cpp b/services/surfaceflinger/LayerBuffer.cpp index 0869283..23506cf 100644 --- a/services/surfaceflinger/LayerBuffer.cpp +++ b/services/surfaceflinger/LayerBuffer.cpp @@ -39,15 +39,13 @@ namespace android { // --------------------------------------------------------------------------- -const uint32_t LayerBuffer::typeInfo = LayerBaseClient::typeInfo | 0x20; -const char* const LayerBuffer::typeID = "LayerBuffer"; gralloc_module_t const* LayerBuffer::sGrallocModule = 0; // --------------------------------------------------------------------------- LayerBuffer::LayerBuffer(SurfaceFlinger* flinger, DisplayID display, - const sp<Client>& client, int32_t i) - : LayerBaseClient(flinger, display, client, i), + const sp<Client>& client) + : LayerBaseClient(flinger, display, client), mNeedsBlending(false), mBlitEngine(0) { } @@ -62,8 +60,7 @@ LayerBuffer::~LayerBuffer() void LayerBuffer::onFirstRef() { LayerBaseClient::onFirstRef(); - mSurface = new SurfaceLayerBuffer(mFlinger, clientIndex(), - const_cast<LayerBuffer *>(this)); + mSurface = new SurfaceLayerBuffer(mFlinger, this); hw_module_t const* module = (hw_module_t const*)sGrallocModule; if (!module) { @@ -120,7 +117,7 @@ uint32_t LayerBuffer::doTransaction(uint32_t flags) source->onTransaction(flags); uint32_t res = LayerBase::doTransaction(flags); // we always want filtering for these surfaces - mUseLinearFiltering = !(mFlags & DisplayHardware::SLOW_CONFIG); + mNeedsFiltering = !(mFlags & DisplayHardware::SLOW_CONFIG); return res; } @@ -135,6 +132,20 @@ void LayerBuffer::unlockPageFlip(const Transform& 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()); @@ -145,14 +156,6 @@ void LayerBuffer::onDraw(const Region& clip) const } } -bool LayerBuffer::transformed() const -{ - sp<Source> source(getSource()); - if (LIKELY(source != 0)) - return source->transformed(); - return false; -} - void LayerBuffer::serverDestroy() { sp<Source> source(clearSource()); @@ -214,9 +217,9 @@ sp<LayerBuffer::Source> LayerBuffer::clearSource() { // LayerBuffer::SurfaceLayerBuffer // ============================================================================ -LayerBuffer::SurfaceLayerBuffer::SurfaceLayerBuffer(const sp<SurfaceFlinger>& flinger, - SurfaceID id, const sp<LayerBuffer>& owner) - : LayerBaseClient::Surface(flinger, id, owner->getIdentity(), owner) +LayerBuffer::SurfaceLayerBuffer::SurfaceLayerBuffer( + const sp<SurfaceFlinger>& flinger, const sp<LayerBuffer>& owner) + : LayerBaseClient::Surface(flinger, owner->getIdentity(), owner) { } @@ -321,16 +324,12 @@ void LayerBuffer::Source::postBuffer(ssize_t offset) { } void LayerBuffer::Source::unregisterBuffers() { } -bool LayerBuffer::Source::transformed() const { - return mLayer.mTransformed; -} // --------------------------------------------------------------------------- LayerBuffer::BufferSource::BufferSource(LayerBuffer& layer, const ISurface::BufferHeap& buffers) - : Source(layer), mStatus(NO_ERROR), mBufferSize(0), - mUseEGLImageDirectly(true) + : Source(layer), mStatus(NO_ERROR), mBufferSize(0) { if (buffers.heap == NULL) { // this is allowed, but in this case, it is illegal to receive @@ -388,11 +387,11 @@ LayerBuffer::BufferSource::~BufferSource() if (mTexture.name != -1U) { // GL textures can only be destroyed from the GL thread - mLayer.mFlinger->mEventQueue.postMessage( - new MessageDestroyTexture(mLayer.mFlinger.get(), mTexture.name) ); + getFlinger()->mEventQueue.postMessage( + new MessageDestroyTexture(getFlinger(), mTexture.name) ); } if (mTexture.image != EGL_NO_IMAGE_KHR) { - EGLDisplay dpy(mLayer.mFlinger->graphicPlane(0).getEGLDisplay()); + EGLDisplay dpy(getFlinger()->graphicPlane(0).getEGLDisplay()); eglDestroyImageKHR(dpy, mTexture.image); } } @@ -444,11 +443,6 @@ void LayerBuffer::BufferSource::setBuffer(const sp<LayerBuffer::Buffer>& buffer) mBuffer = buffer; } -bool LayerBuffer::BufferSource::transformed() const -{ - return mBufferHeap.transform ? true : Source::transformed(); -} - void LayerBuffer::BufferSource::onDraw(const Region& clip) const { sp<Buffer> ourBuffer(getBuffer()); @@ -462,35 +456,10 @@ void LayerBuffer::BufferSource::onDraw(const Region& clip) const NativeBuffer src(ourBuffer->getBuffer()); const Rect transformedBounds(mLayer.getTransformedBounds()); - if (UNLIKELY(mTexture.name == -1LU)) { - mTexture.name = mLayer.createTexture(); - } - #if defined(EGL_ANDROID_image_native_buffer) - if (mLayer.mFlags & DisplayHardware::DIRECT_TEXTURE) { + if (GLExtensions::getInstance().haveDirectTexture()) { err = INVALID_OPERATION; if (ourBuffer->supportsCopybit()) { - - // there are constraints on buffers used by the GPU and these may not - // be honored here. We need to change the API so the buffers - // are allocated with gralloc. For now disable this code-path -#if 0 - // First, try to use the buffer as an EGLImage directly - if (mUseEGLImageDirectly) { - // NOTE: Assume the buffer is allocated with the proper USAGE flags - - sp<GraphicBuffer> buffer = new GraphicBuffer( - src.img.w, src.img.h, src.img.format, - GraphicBuffer::USAGE_HW_TEXTURE, - src.img.w, src.img.handle, false); - - err = mLayer.initializeEglImage(buffer, &mTexture); - if (err != NO_ERROR) { - mUseEGLImageDirectly = false; - } - } -#endif - copybit_device_t* copybit = mLayer.mBlitEngine; if (copybit && err != NO_ERROR) { // create our EGLImageKHR the first time @@ -527,10 +496,10 @@ void LayerBuffer::BufferSource::onDraw(const Region& clip) const t.format = src.img.format; t.data = (GGLubyte*)src.img.base; const Region dirty(Rect(t.width, t.height)); - mLayer.loadTexture(&mTexture, dirty, t); + mTextureManager.loadTexture(&mTexture, dirty, t); } - mTexture.transform = mBufferHeap.transform; + mLayer.setBufferTransform(mBufferHeap.transform); mLayer.drawWithOpenGL(clip, mTexture); } @@ -569,7 +538,7 @@ status_t LayerBuffer::BufferSource::initTempBuffer() const // figure out if we need linear filtering if (buffers.w * h == buffers.h * w) { // same pixel area, don't use filtering - mLayer.mUseLinearFiltering = false; + mLayer.mNeedsFiltering = false; } // Allocate a temporary buffer and create the corresponding EGLImageKHR @@ -593,7 +562,8 @@ status_t LayerBuffer::BufferSource::initTempBuffer() const dst.crop.r = w; dst.crop.b = h; - err = mLayer.initializeEglImage(buffer, &mTexture); + EGLDisplay dpy(getFlinger()->graphicPlane(0).getEGLDisplay()); + err = mTextureManager.initEglImage(&mTexture, dpy, buffer); } return err; @@ -602,14 +572,13 @@ status_t LayerBuffer::BufferSource::initTempBuffer() const void LayerBuffer::BufferSource::clearTempBufferImage() const { // delete the image - EGLDisplay dpy(mLayer.mFlinger->graphicPlane(0).getEGLDisplay()); + 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; - mTexture.name = mLayer.createTexture(); } // --------------------------------------------------------------------------- @@ -620,7 +589,7 @@ LayerBuffer::OverlaySource::OverlaySource(LayerBuffer& layer, : Source(layer), mVisibilityChanged(false), mOverlay(0), mOverlayHandle(0), mOverlayDevice(0), mOrientation(orientation) { - overlay_control_device_t* overlay_dev = mLayer.mFlinger->getOverlayEngine(); + overlay_control_device_t* overlay_dev = getFlinger()->getOverlayEngine(); if (overlay_dev == NULL) { // overlays not supported return; @@ -651,7 +620,7 @@ LayerBuffer::OverlaySource::OverlaySource(LayerBuffer& layer, *overlayRef = new OverlayRef(mOverlayHandle, channel, mWidth, mHeight, mFormat, mWidthStride, mHeightStride); - mLayer.mFlinger->signalEvent(); + getFlinger()->signalEvent(); } LayerBuffer::OverlaySource::~OverlaySource() @@ -665,9 +634,9 @@ LayerBuffer::OverlaySource::~OverlaySource() void LayerBuffer::OverlaySource::onDraw(const Region& clip) const { // this would be where the color-key would be set, should we need it. - GLclampx red = 0; - GLclampx green = 0; - GLclampx blue = 0; + GLclampf red = 0; + GLclampf green = 0; + GLclampf blue = 0; mLayer.clearWithOpenGL(clip, red, green, blue, 0); } @@ -680,6 +649,11 @@ void LayerBuffer::OverlaySource::onTransaction(uint32_t flags) } } +void LayerBuffer::OverlaySource::onvalidateVisibility(const Transform&) +{ + mVisibilityChanged = true; +} + void LayerBuffer::OverlaySource::onVisibilityResolved( const Transform& planeTransform) { @@ -702,8 +676,8 @@ void LayerBuffer::OverlaySource::onVisibilityResolved( 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(mOrientation) * - Transform(mLayer.getOrientation()); + Transform finalTransform(Transform(mLayer.getOrientation()) * + Transform(mOrientation)); overlay_dev->setParameter(overlay_dev, mOverlay, OVERLAY_TRANSFORM, finalTransform.getOrientation()); overlay_dev->commit(overlay_dev, mOverlay); diff --git a/services/surfaceflinger/LayerBuffer.h b/services/surfaceflinger/LayerBuffer.h index b176623..a89d8fe 100644 --- a/services/surfaceflinger/LayerBuffer.h +++ b/services/surfaceflinger/LayerBuffer.h @@ -21,6 +21,7 @@ #include <sys/types.h> #include "LayerBase.h" +#include "TextureManager.h" struct copybit_device_t; @@ -43,33 +44,31 @@ class LayerBuffer : public LayerBaseClient 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 bool transformed() const; virtual void destroy() { } + SurfaceFlinger* getFlinger() const { return mLayer.mFlinger.get(); } protected: LayerBuffer& mLayer; }; public: - static const uint32_t typeInfo; - static const char* const typeID; - virtual char const* getTypeID() const { return typeID; } - virtual uint32_t getTypeInfo() const { return typeInfo; } - LayerBuffer(SurfaceFlinger* flinger, DisplayID display, - const sp<Client>& client, int32_t i); + 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 bool transformed() const; + virtual void validateVisibility(const Transform& globalTransform); status_t registerBuffers(const ISurface::BufferHeap& buffers); void postBuffer(ssize_t offset); @@ -133,7 +132,6 @@ private: virtual void onDraw(const Region& clip) const; virtual void postBuffer(ssize_t offset); virtual void unregisterBuffers(); - virtual bool transformed() const; virtual void destroy() { } private: status_t initTempBuffer() const; @@ -143,9 +141,9 @@ private: status_t mStatus; ISurface::BufferHeap mBufferHeap; size_t mBufferSize; - mutable LayerBase::Texture mTexture; + mutable Texture mTexture; mutable NativeBuffer mTempBuffer; - mutable bool mUseEGLImageDirectly; + mutable TextureManager mTextureManager; }; class OverlaySource : public Source { @@ -157,6 +155,7 @@ private: 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: @@ -195,7 +194,7 @@ private: { public: SurfaceLayerBuffer(const sp<SurfaceFlinger>& flinger, - SurfaceID id, const sp<LayerBuffer>& owner); + const sp<LayerBuffer>& owner); virtual ~SurfaceLayerBuffer(); virtual status_t registerBuffers(const ISurface::BufferHeap& buffers); diff --git a/services/surfaceflinger/LayerDim.cpp b/services/surfaceflinger/LayerDim.cpp index fd61e30..80cc52c 100644 --- a/services/surfaceflinger/LayerDim.cpp +++ b/services/surfaceflinger/LayerDim.cpp @@ -30,9 +30,6 @@ namespace android { // --------------------------------------------------------------------------- -const uint32_t LayerDim::typeInfo = LayerBaseClient::typeInfo | 0x10; -const char* const LayerDim::typeID = "LayerDim"; - bool LayerDim::sUseTexture; GLuint LayerDim::sTexId; EGLImageKHR LayerDim::sImage; @@ -42,8 +39,8 @@ int32_t LayerDim::sHeight; // --------------------------------------------------------------------------- LayerDim::LayerDim(SurfaceFlinger* flinger, DisplayID display, - const sp<Client>& client, int32_t i) - : LayerBaseClient(flinger, display, client, i) + const sp<Client>& client) + : LayerBaseClient(flinger, display, client) { } @@ -54,54 +51,6 @@ void LayerDim::initDimmer(SurfaceFlinger* flinger, uint32_t w, uint32_t h) sWidth = w; sHeight = h; sUseTexture = false; - -#if defined(DIM_WITH_TEXTURE) && defined(EGL_ANDROID_image_native_buffer) - -#warning "using a texture to implement LayerDim" - - /* On some h/w like msm7K, it is faster to use a texture because the - * software renderer will defer to copybit, for this to work we need to - * use an EGLImage texture so copybit can actually make use of it. - * This burns a full-screen worth of graphic memory. - */ - - const DisplayHardware& hw(flinger->graphicPlane(0).displayHardware()); - uint32_t flags = hw.getFlags(); - - if (LIKELY(flags & DisplayHardware::DIRECT_TEXTURE)) { - sp<GraphicBuffer> buffer = new GraphicBuffer(w, h, PIXEL_FORMAT_RGB_565, - GraphicBuffer::USAGE_SW_WRITE_OFTEN | - GraphicBuffer::USAGE_HW_TEXTURE); - - android_native_buffer_t* clientBuf = buffer->getNativeBuffer(); - - glGenTextures(1, &sTexId); - glBindTexture(GL_TEXTURE_2D, sTexId); - - EGLDisplay dpy = eglGetCurrentDisplay(); - sImage = eglCreateImageKHR(dpy, EGL_NO_CONTEXT, - EGL_NATIVE_BUFFER_ANDROID, (EGLClientBuffer)clientBuf, 0); - if (sImage == EGL_NO_IMAGE_KHR) { - LOGE("eglCreateImageKHR() failed. err=0x%4x", eglGetError()); - return; - } - - glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)sImage); - GLint error = glGetError(); - if (error != GL_NO_ERROR) { - eglDestroyImageKHR(dpy, sImage); - LOGE("glEGLImageTargetTexture2DOES() failed. err=0x%4x", error); - return; - } - - // initialize the texture with zeros - GGLSurface t; - buffer->lock(&t, GRALLOC_USAGE_SW_WRITE_OFTEN); - memset(t.data, 0, t.stride * t.height * 2); - buffer->unlock(); - sUseTexture = true; - } -#endif } LayerDim::~LayerDim() @@ -115,33 +64,19 @@ void LayerDim::onDraw(const Region& clip) const Region::const_iterator const end = clip.end(); if (s.alpha>0 && (it != end)) { const DisplayHardware& hw(graphicPlane(0).displayHardware()); - const GGLfixed alpha = (s.alpha << 16)/255; + 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); - glColor4x(0, 0, 0, alpha); - -#if defined(DIM_WITH_TEXTURE) && defined(EGL_ANDROID_image_native_buffer) - if (sUseTexture) { - glBindTexture(GL_TEXTURE_2D, sTexId); - glEnable(GL_TEXTURE_2D); - glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - const GLshort texCoords[4][2] = { - { 0, 0 }, - { 0, 1 }, - { 1, 1 }, - { 1, 0 } - }; - glMatrixMode(GL_TEXTURE); - glLoadIdentity(); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glTexCoordPointer(2, GL_SHORT, 0, texCoords); - } else -#endif - { - glDisable(GL_TEXTURE_2D); + glColor4f(0, 0, 0, alpha); + +#if defined(GL_OES_EGL_image_external) + if (GLExtensions::getInstance().haveTextureExternal()) { + glDisable(GL_TEXTURE_EXTERNAL_OES); } +#endif + glDisable(GL_TEXTURE_2D); GLshort w = sWidth; GLshort h = sHeight; diff --git a/services/surfaceflinger/LayerDim.h b/services/surfaceflinger/LayerDim.h index d4672a1..f032314 100644 --- a/services/surfaceflinger/LayerDim.h +++ b/services/surfaceflinger/LayerDim.h @@ -37,18 +37,14 @@ class LayerDim : public LayerBaseClient static int32_t sWidth; static int32_t sHeight; public: - static const uint32_t typeInfo; - static const char* const typeID; - virtual char const* getTypeID() const { return typeID; } - virtual uint32_t getTypeInfo() const { return typeInfo; } - LayerDim(SurfaceFlinger* flinger, DisplayID display, - const sp<Client>& client, int32_t i); + 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 const char* getTypeId() const { return "LayerDim"; } static void initDimmer(SurfaceFlinger* flinger, uint32_t w, uint32_t h); }; diff --git a/services/surfaceflinger/MessageQueue.cpp b/services/surfaceflinger/MessageQueue.cpp index b43d801..aebe1b8 100644 --- a/services/surfaceflinger/MessageQueue.cpp +++ b/services/surfaceflinger/MessageQueue.cpp @@ -60,9 +60,9 @@ MessageQueue::~MessageQueue() { } -MessageList::value_type MessageQueue::waitMessage(nsecs_t timeout) +sp<MessageBase> MessageQueue::waitMessage(nsecs_t timeout) { - MessageList::value_type result; + sp<MessageBase> result; bool again; do { @@ -72,14 +72,6 @@ MessageList::value_type MessageQueue::waitMessage(nsecs_t timeout) nsecs_t now = systemTime(); nsecs_t nextEventTime = -1; - // invalidate messages are always handled first - if (mInvalidate) { - mInvalidate = false; - mInvalidateMessage->when = now; - result = mInvalidateMessage; - break; - } - LIST::iterator cur(mMessages.begin()); if (cur != mMessages.end()) { result = *cur; @@ -91,17 +83,29 @@ MessageList::value_type MessageQueue::waitMessage(nsecs_t timeout) mMessages.remove(cur); break; } - if (timeout>=0 && timeoutTime < now) { - // we timed-out, return a NULL message - result = 0; - break; - } nextEventTime = result->when; result = 0; } - if (timeout >= 0 && nextEventTime > 0) { - if (nextEventTime > timeoutTime) { + // see if we have an invalidate message + if (mInvalidate) { + mInvalidate = false; + mInvalidateMessage->when = now; + result = mInvalidateMessage; + break; + } + + if (timeout >= 0) { + if (timeoutTime < now) { + // we timed-out, return a NULL message + result = 0; + break; + } + if (nextEventTime > 0) { + if (nextEventTime > timeoutTime) { + nextEventTime = timeoutTime; + } + } else { nextEventTime = timeoutTime; } } @@ -132,6 +136,7 @@ MessageList::value_type MessageQueue::waitMessage(nsecs_t timeout) if (again) { // the message has been processed. release our reference to it // without holding the lock. + result->notify(); result = 0; } @@ -141,7 +146,7 @@ MessageList::value_type MessageQueue::waitMessage(nsecs_t timeout) } status_t MessageQueue::postMessage( - const MessageList::value_type& message, nsecs_t relTime, uint32_t flags) + const sp<MessageBase>& message, nsecs_t relTime, uint32_t flags) { return queueMessage(message, relTime, flags); } @@ -154,7 +159,7 @@ status_t MessageQueue::invalidate() { } status_t MessageQueue::queueMessage( - const MessageList::value_type& message, nsecs_t relTime, uint32_t flags) + const sp<MessageBase>& message, nsecs_t relTime, uint32_t flags) { Mutex::Autolock _l(mLock); message->when = systemTime() + relTime; @@ -167,13 +172,13 @@ status_t MessageQueue::queueMessage( return NO_ERROR; } -void MessageQueue::dump(const MessageList::value_type& message) +void MessageQueue::dump(const sp<MessageBase>& message) { Mutex::Autolock _l(mLock); dumpLocked(message); } -void MessageQueue::dumpLocked(const MessageList::value_type& message) +void MessageQueue::dumpLocked(const sp<MessageBase>& message) { LIST::const_iterator cur(mMessages.begin()); LIST::const_iterator end(mMessages.end()); diff --git a/services/surfaceflinger/MessageQueue.h b/services/surfaceflinger/MessageQueue.h index dc8138d..890f809 100644 --- a/services/surfaceflinger/MessageQueue.h +++ b/services/surfaceflinger/MessageQueue.h @@ -25,6 +25,7 @@ #include <utils/Timers.h> #include <utils/List.h> +#include "Barrier.h" namespace android { @@ -37,7 +38,6 @@ class MessageList List< sp<MessageBase> > mList; typedef List< sp<MessageBase> > LIST; public: - typedef sp<MessageBase> value_type; inline LIST::iterator begin() { return mList.begin(); } inline LIST::const_iterator begin() const { return mList.begin(); } inline LIST::iterator end() { return mList.end(); } @@ -63,11 +63,19 @@ public: // return true if message has a handler virtual bool handler() { return false; } + + // waits for the handler to be processed + void wait() const { barrier.wait(); } + // releases all waiters. this is done automatically if + // handler returns true + void notify() const { barrier.open(); } + protected: virtual ~MessageBase() { } private: + mutable Barrier barrier; friend class LightRefBase<MessageBase>; }; @@ -82,42 +90,33 @@ class MessageQueue typedef List< sp<MessageBase> > LIST; public: - // this is a work-around the multichar constant warning. A macro would - // work too, but would pollute the namespace. - template <int a, int b, int c, int d> - struct WHAT { - static const uint32_t Value = - (uint32_t(a&0xff)<<24)|(uint32_t(b&0xff)<<16)| - (uint32_t(c&0xff)<<8)|uint32_t(d&0xff); - }; - MessageQueue(); ~MessageQueue(); // pre-defined messages enum { - INVALIDATE = WHAT<'_','p','d','t'>::Value + INVALIDATE = '_upd' }; - MessageList::value_type waitMessage(nsecs_t timeout = -1); + sp<MessageBase> waitMessage(nsecs_t timeout = -1); - status_t postMessage(const MessageList::value_type& message, + status_t postMessage(const sp<MessageBase>& message, nsecs_t reltime=0, uint32_t flags = 0); - + status_t invalidate(); - void dump(const MessageList::value_type& message); + void dump(const sp<MessageBase>& message); private: - status_t queueMessage(const MessageList::value_type& message, + status_t queueMessage(const sp<MessageBase>& message, nsecs_t reltime, uint32_t flags); - void dumpLocked(const MessageList::value_type& message); + void dumpLocked(const sp<MessageBase>& message); Mutex mLock; Condition mCondition; MessageList mMessages; bool mInvalidate; - MessageList::value_type mInvalidateMessage; + sp<MessageBase> mInvalidateMessage; }; // --------------------------------------------------------------------------- diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 0722fda..a9b3965 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -38,12 +38,14 @@ #include <utils/StopWatch.h> #include <ui/GraphicBufferAllocator.h> +#include <ui/GraphicLog.h> #include <ui/PixelFormat.h> #include <pixelflinger/pixelflinger.h> #include <GLES/gl.h> #include "clz.h" +#include "GLExtensions.h" #include "Layer.h" #include "LayerBlur.h" #include "LayerBuffer.h" @@ -62,110 +64,7 @@ #define DISPLAY_COUNT 1 namespace android { - -// --------------------------------------------------------------------------- - -void SurfaceFlinger::instantiate() { - defaultServiceManager()->addService( - String16("SurfaceFlinger"), new SurfaceFlinger()); -} - -void SurfaceFlinger::shutdown() { - // we should unregister here, but not really because - // when (if) the service manager goes away, all the services - // it has a reference to will leave too. -} - -// --------------------------------------------------------------------------- - -SurfaceFlinger::LayerVector::LayerVector(const SurfaceFlinger::LayerVector& rhs) - : lookup(rhs.lookup), layers(rhs.layers) -{ -} - -ssize_t SurfaceFlinger::LayerVector::indexOf( - const sp<LayerBase>& key, size_t guess) const -{ - if (guess<size() && lookup.keyAt(guess) == key) - return guess; - const ssize_t i = lookup.indexOfKey(key); - if (i>=0) { - const size_t idx = lookup.valueAt(i); - LOGE_IF(layers[idx]!=key, - "LayerVector[%p]: layers[%d]=%p, key=%p", - this, int(idx), layers[idx].get(), key.get()); - return idx; - } - return i; -} - -ssize_t SurfaceFlinger::LayerVector::add( - const sp<LayerBase>& layer, - Vector< sp<LayerBase> >::compar_t cmp) -{ - size_t count = layers.size(); - ssize_t l = 0; - ssize_t h = count-1; - ssize_t mid; - sp<LayerBase> const* a = layers.array(); - while (l <= h) { - mid = l + (h - l)/2; - const int c = cmp(a+mid, &layer); - if (c == 0) { l = mid; break; } - else if (c<0) { l = mid+1; } - else { h = mid-1; } - } - size_t order = l; - while (order<count && !cmp(&layer, a+order)) { - order++; - } - count = lookup.size(); - for (size_t i=0 ; i<count ; i++) { - if (lookup.valueAt(i) >= order) { - lookup.editValueAt(i)++; - } - } - layers.insertAt(layer, order); - lookup.add(layer, order); - return order; -} - -ssize_t SurfaceFlinger::LayerVector::remove(const sp<LayerBase>& layer) -{ - const ssize_t keyIndex = lookup.indexOfKey(layer); - if (keyIndex >= 0) { - const size_t index = lookup.valueAt(keyIndex); - LOGE_IF(layers[index]!=layer, - "LayerVector[%p]: layers[%u]=%p, layer=%p", - this, int(index), layers[index].get(), layer.get()); - layers.removeItemsAt(index); - lookup.removeItemsAt(keyIndex); - const size_t count = lookup.size(); - for (size_t i=0 ; i<count ; i++) { - if (lookup.valueAt(i) >= size_t(index)) { - lookup.editValueAt(i)--; - } - } - return index; - } - return NAME_NOT_FOUND; -} - -ssize_t SurfaceFlinger::LayerVector::reorder( - const sp<LayerBase>& layer, - Vector< sp<LayerBase> >::compar_t cmp) -{ - // XXX: it's a little lame. but oh well... - ssize_t err = remove(layer); - if (err >=0) - err = add(layer, cmp); - return err; -} - // --------------------------------------------------------------------------- -#if 0 -#pragma mark - -#endif SurfaceFlinger::SurfaceFlinger() : BnSurfaceComposer(), Thread(false), @@ -176,10 +75,12 @@ SurfaceFlinger::SurfaceFlinger() mBootTime(systemTime()), mHardwareTest("android.permission.HARDWARE_TEST"), mAccessSurfaceFlinger("android.permission.ACCESS_SURFACE_FLINGER"), + mReadFramebuffer("android.permission.READ_FRAME_BUFFER"), mDump("android.permission.DUMP"), mVisibleRegionsDirty(false), mDeferReleaseConsole(false), mFreezeDisplay(false), + mElectronBeamAnimationMode(0), mFreezeCount(0), mFreezeDisplayTime(0), mDebugRegion(0), @@ -206,8 +107,8 @@ void SurfaceFlinger::init() property_get("debug.sf.showbackground", value, "0"); mDebugBackground = atoi(value); - LOGI_IF(mDebugRegion, "showupdates enabled"); - LOGI_IF(mDebugBackground, "showbackground enabled"); + LOGI_IF(mDebugRegion, "showupdates enabled"); + LOGI_IF(mDebugBackground, "showbackground enabled"); } SurfaceFlinger::~SurfaceFlinger() @@ -225,56 +126,29 @@ sp<IMemoryHeap> SurfaceFlinger::getCblk() const return mServerHeap; } -sp<ISurfaceFlingerClient> SurfaceFlinger::createConnection() +sp<ISurfaceComposerClient> SurfaceFlinger::createConnection() { - Mutex::Autolock _l(mStateLock); - uint32_t token = mTokens.acquire(); - - sp<Client> client = new Client(token, this); - if (client->ctrlblk == 0) { - mTokens.release(token); - return 0; - } - status_t err = mClientsMap.add(token, client); - if (err < 0) { - mTokens.release(token); - return 0; + sp<ISurfaceComposerClient> bclient; + sp<Client> client(new Client(this)); + status_t err = client->initCheck(); + if (err == NO_ERROR) { + bclient = client; } - sp<BClient> bclient = - new BClient(this, token, client->getControlBlockMemory()); return bclient; } -void SurfaceFlinger::destroyConnection(ClientID cid) +sp<ISurfaceComposerClient> SurfaceFlinger::createClientConnection() { - Mutex::Autolock _l(mStateLock); - sp<Client> client = mClientsMap.valueFor(cid); - if (client != 0) { - // free all the layers this client owns - Vector< wp<LayerBaseClient> > layers(client->getLayers()); - const size_t count = layers.size(); - for (size_t i=0 ; i<count ; i++) { - sp<LayerBaseClient> layer(layers[i].promote()); - if (layer != 0) { - purgatorizeLayer_l(layer); - } - } - - // the resources associated with this client will be freed - // during the next transaction, after these surfaces have been - // properly removed from the screen - - // remove this client from our ClientID->Client mapping. - mClientsMap.removeItem(cid); - - // and add it to the list of disconnected clients - mDisconnectedClients.add(client); - - // request a transaction - setTransactionFlags(eTransactionNeeded); + sp<ISurfaceComposerClient> bclient; + sp<UserClient> client(new UserClient(this)); + status_t err = client->initCheck(); + if (err == NO_ERROR) { + bclient = client; } + return bclient; } + const GraphicPlane& SurfaceFlinger::graphicPlane(int dpy) const { LOGE_IF(uint32_t(dpy) >= DISPLAY_COUNT, "Invalid DisplayID %d", dpy); @@ -357,16 +231,8 @@ status_t SurfaceFlinger::readyToRun() dcblk->ydpi = hw.getDpiY(); dcblk->fps = hw.getRefreshRate(); dcblk->density = hw.getDensity(); - asm volatile ("":::"memory"); // Initialize OpenGL|ES - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, 0); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); glPixelStorei(GL_PACK_ALIGNMENT, 4); glEnableClientState(GL_VERTEX_ARRAY); @@ -427,7 +293,7 @@ void SurfaceFlinger::waitForEvent() timeout = waitTime>0 ? waitTime : 0; } - MessageList::value_type msg = mEventQueue.waitMessage(timeout); + sp<MessageBase> msg = mEventQueue.waitMessage(timeout); // see if we timed out if (isFrozen()) { @@ -462,9 +328,20 @@ void SurfaceFlinger::signal() const { const_cast<SurfaceFlinger*>(this)->signalEvent(); } -void SurfaceFlinger::signalDelayedEvent(nsecs_t delay) +status_t SurfaceFlinger::postMessageAsync(const sp<MessageBase>& msg, + nsecs_t reltime, uint32_t flags) { - mEventQueue.postMessage( new MessageBase(MessageQueue::INVALIDATE), delay); + return mEventQueue.postMessage(msg, reltime, flags); +} + +status_t SurfaceFlinger::postMessageSync(const sp<MessageBase>& msg, + nsecs_t reltime, uint32_t flags) +{ + status_t res = mEventQueue.postMessage(msg, reltime, flags); + if (res == NO_ERROR) { + msg->wait(); + } + return res; } // ---------------------------------------------------------------------------- @@ -497,15 +374,25 @@ bool SurfaceFlinger::threadLoop() const DisplayHardware& hw(graphicPlane(0).displayHardware()); if (LIKELY(hw.canDraw() && !isFrozen())) { // repaint the framebuffer (if needed) + + const int index = hw.getCurrentBufferIndex(); + GraphicLog& logger(GraphicLog::getInstance()); + + logger.log(GraphicLog::SF_REPAINT, index); handleRepaint(); // inform the h/w that we're done compositing + logger.log(GraphicLog::SF_COMPOSITION_COMPLETE, index); hw.compositionComplete(); // release the clients before we flip ('cause flip might block) + logger.log(GraphicLog::SF_UNLOCK_CLIENTS, index); unlockClients(); + logger.log(GraphicLog::SF_SWAP_BUFFERS, index); postFramebuffer(); + + logger.log(GraphicLog::SF_REPAINT_DONE, index); } else { // pretend we did the post unlockClients(); @@ -535,16 +422,19 @@ void SurfaceFlinger::handleConsoleEvents() int what = android_atomic_and(0, &mConsoleSignals); if (what & eConsoleAcquired) { hw.acquireScreen(); + // this is a temporary work-around, eventually this should be called + // by the power-manager + SurfaceFlinger::turnElectronBeamOn(mElectronBeamAnimationMode); } - if (mDeferReleaseConsole && hw.canDraw()) { + if (mDeferReleaseConsole && hw.isScreenAcquired()) { // We got the release signal before the acquire signal mDeferReleaseConsole = false; hw.releaseScreen(); } if (what & eConsoleReleased) { - if (hw.canDraw()) { + if (hw.isScreenAcquired()) { hw.releaseScreen(); } else { mDeferReleaseConsole = true; @@ -558,6 +448,10 @@ void SurfaceFlinger::handleTransaction(uint32_t transactionFlags) { Vector< sp<LayerBase> > ditchedLayers; + /* + * Perform and commit the transaction + */ + { // scope for the lock Mutex::Autolock _l(mStateLock); const nsecs_t now = systemTime(); @@ -565,9 +459,13 @@ void SurfaceFlinger::handleTransaction(uint32_t transactionFlags) handleTransactionLocked(transactionFlags, ditchedLayers); mLastTransactionTime = systemTime() - now; mDebugInTransaction = 0; + // here the transaction has been committed } - // do this without lock held + /* + * 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) { @@ -655,10 +553,6 @@ void SurfaceFlinger::handleTransactionLocked( } } } - - // get rid of all resources we don't need anymore - // (layers and clients) - free_resources_l(); } commitTransaction(); @@ -805,7 +699,8 @@ void SurfaceFlinger::commitTransaction() 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(); @@ -813,6 +708,19 @@ void SurfaceFlinger::handlePageFlip() if (visibleRegions) { Region opaqueRegion; computeVisibleRegions(currentLayers, mDirtyRegion, opaqueRegion); + + /* + * rebuild the visible layer list + */ + mVisibleLayersSortedByZ.clear(); + const LayerVector& currentLayers(mDrawingState.layersSortedByZ); + size_t count = currentLayers.size(); + mVisibleLayersSortedByZ.setCapacity(count); + for (size_t i=0 ; i<count ; i++) { + if (!currentLayers[i]->visibleRegionScreen.isEmpty()) + mVisibleLayersSortedByZ.add(currentLayers[i]); + } + mWormholeRegion = screenRegion.subtract(opaqueRegion); mVisibleRegionsDirty = false; } @@ -827,7 +735,7 @@ bool SurfaceFlinger::lockPageFlip(const LayerVector& currentLayers) size_t count = currentLayers.size(); sp<LayerBase> const* layers = currentLayers.array(); for (size_t i=0 ; i<count ; i++) { - const sp<LayerBase>& layer = layers[i]; + const sp<LayerBase>& layer(layers[i]); layer->lockPageFlip(recomputeVisibleRegions); } return recomputeVisibleRegions; @@ -840,7 +748,7 @@ void SurfaceFlinger::unlockPageFlip(const LayerVector& currentLayers) size_t count = currentLayers.size(); sp<LayerBase> const* layers = currentLayers.array(); for (size_t i=0 ; i<count ; i++) { - const sp<LayerBase>& layer = layers[i]; + const sp<LayerBase>& layer(layers[i]); layer->unlockPageFlip(planeTransform, mDirtyRegion); } } @@ -872,7 +780,7 @@ void SurfaceFlinger::handleRepaint() // takes a rectangle, we must make sure to update that whole // rectangle in that case if (flags & DisplayHardware::SWAP_RECTANGLE) { - // FIXME: we really should be able to pass a region to + // TODO: we really should be able to pass a region to // SWAP_RECTANGLE so that we don't have to redraw all this. mDirtyRegion.set(mInvalidRegion.bounds()); } else { @@ -909,18 +817,13 @@ void SurfaceFlinger::composeSurfaces(const Region& dirty) // draw something... drawWormhole(); } - const SurfaceFlinger& flinger(*this); - const LayerVector& drawingLayers(mDrawingState.layersSortedByZ); - const size_t count = drawingLayers.size(); - sp<LayerBase> const* const layers = drawingLayers.array(); + 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 Region& visibleRegion(layer->visibleRegionScreen); - if (!visibleRegion.isEmpty()) { - const Region clip(dirty.intersect(visibleRegion)); - if (!clip.isEmpty()) { - layer->draw(clip); - } + const sp<LayerBase>& layer(layers[i]); + const Region clip(dirty.intersect(layer->visibleRegionScreen)); + if (!clip.isEmpty()) { + layer->draw(clip); } } } @@ -938,17 +841,18 @@ void SurfaceFlinger::unlockClients() void SurfaceFlinger::debugFlashRegions() { - const DisplayHardware& hw(graphicPlane(0).displayHardware()); - const uint32_t flags = hw.getFlags(); + const DisplayHardware& hw(graphicPlane(0).displayHardware()); + const uint32_t flags = hw.getFlags(); + + if (!((flags & DisplayHardware::SWAP_RECTANGLE) || + (flags & DisplayHardware::BUFFER_PRESERVED))) { + const Region repaint((flags & DisplayHardware::PARTIAL_UPDATES) ? + mDirtyRegion.bounds() : hw.bounds()); + composeSurfaces(repaint); + } + + TextureManager::deactivateTextures(); - if (!((flags & DisplayHardware::SWAP_RECTANGLE) || - (flags & DisplayHardware::BUFFER_PRESERVED))) { - const Region repaint((flags & DisplayHardware::PARTIAL_UPDATES) ? - mDirtyRegion.bounds() : hw.bounds()); - composeSurfaces(repaint); - } - - glDisable(GL_TEXTURE_2D); glDisable(GL_BLEND); glDisable(GL_DITHER); glDisable(GL_SCISSOR_TEST); @@ -956,9 +860,9 @@ void SurfaceFlinger::debugFlashRegions() static int toggle = 0; toggle = 1 - toggle; if (toggle) { - glColor4x(0x10000, 0, 0x10000, 0x10000); + glColor4f(1, 0, 1, 1); } else { - glColor4x(0x10000, 0x10000, 0, 0x10000); + glColor4f(1, 1, 0, 1); } Region::const_iterator it = mDirtyRegion.begin(); @@ -974,7 +878,7 @@ void SurfaceFlinger::debugFlashRegions() glVertexPointer(2, GL_FLOAT, 0, vertices); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } - + if (mInvalidRegion.isEmpty()) { mDirtyRegion.dump("mDirtyRegion"); mInvalidRegion.dump("mInvalidRegion"); @@ -982,7 +886,7 @@ void SurfaceFlinger::debugFlashRegions() hw.flip(mInvalidRegion); if (mDebugRegion > 1) - usleep(mDebugRegion * 1000); + usleep(mDebugRegion * 1000); glEnable(GL_SCISSOR_TEST); //mDirtyRegion.dump("mDirtyRegion"); @@ -1002,7 +906,7 @@ void SurfaceFlinger::drawWormhole() const glDisable(GL_DITHER); if (LIKELY(!mDebugBackground)) { - glClearColorx(0,0,0,0); + glClearColor(0,0,0,0); Region::const_iterator it = region.begin(); Region::const_iterator const end = region.end(); while (it != end) { @@ -1018,6 +922,11 @@ void SurfaceFlinger::drawWormhole() const glVertexPointer(2, GL_SHORT, 0, vertices); glTexCoordPointer(2, GL_SHORT, 0, tcoords); glEnableClientState(GL_TEXTURE_COORD_ARRAY); +#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, mWormholeTexName); glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); @@ -1061,6 +970,26 @@ status_t SurfaceFlinger::addLayer(const sp<LayerBase>& layer) return NO_ERROR; } +status_t SurfaceFlinger::addLayer_l(const sp<LayerBase>& layer) +{ + ssize_t i = mCurrentState.layersSortedByZ.add(layer); + return (i < 0) ? status_t(i) : status_t(NO_ERROR); +} + +ssize_t SurfaceFlinger::addClientLayer(const sp<Client>& client, + const sp<LayerBaseClient>& lbc) +{ + Mutex::Autolock _l(mStateLock); + + // attach this layer to the client + ssize_t name = client->attachLayer(lbc); + + // add this layer to the current state list + addLayer_l(lbc); + + return name; +} + status_t SurfaceFlinger::removeLayer(const sp<LayerBase>& layer) { Mutex::Autolock _l(mStateLock); @@ -1070,36 +999,15 @@ status_t SurfaceFlinger::removeLayer(const sp<LayerBase>& layer) return err; } -status_t SurfaceFlinger::invalidateLayerVisibility(const sp<LayerBase>& layer) -{ - layer->forceVisibilityTransaction(); - setTransactionFlags(eTraversalNeeded); - return NO_ERROR; -} - -status_t SurfaceFlinger::addLayer_l(const sp<LayerBase>& layer) +status_t SurfaceFlinger::removeLayer_l(const sp<LayerBase>& layerBase) { - if (layer == 0) - return BAD_VALUE; - ssize_t i = mCurrentState.layersSortedByZ.add( - layer, &LayerBase::compareCurrentStateZ); - sp<LayerBaseClient> lbc = LayerBase::dynamicCast< LayerBaseClient* >(layer.get()); + sp<LayerBaseClient> lbc(layerBase->getLayerBaseClient()); if (lbc != 0) { - mLayerMap.add(lbc->serverIndex(), lbc); + mLayerMap.removeItem( lbc->getSurface()->asBinder() ); } - return NO_ERROR; -} - -status_t SurfaceFlinger::removeLayer_l(const sp<LayerBase>& layerBase) -{ ssize_t index = mCurrentState.layersSortedByZ.remove(layerBase); if (index >= 0) { mLayersRemoved = true; - sp<LayerBaseClient> layer = - LayerBase::dynamicCast< LayerBaseClient* >(layerBase.get()); - if (layer != 0) { - mLayerMap.removeItem(layer->serverIndex()); - } return NO_ERROR; } return status_t(index); @@ -1114,22 +1022,16 @@ status_t SurfaceFlinger::purgatorizeLayer_l(const sp<LayerBase>& layerBase) // it's possible that we don't find a layer, because it might // have been destroyed already -- this is not technically an error - // from the user because there is a race between BClient::destroySurface(), - // ~BClient() and ~ISurface(). + // from the user because there is a race between Client::destroySurface(), + // ~Client() and ~ISurface(). return (err == NAME_NOT_FOUND) ? status_t(NO_ERROR) : err; } - -void SurfaceFlinger::free_resources_l() +status_t SurfaceFlinger::invalidateLayerVisibility(const sp<LayerBase>& layer) { - // free resources associated with disconnected clients - Vector< sp<Client> >& disconnectedClients(mDisconnectedClients); - const size_t count = disconnectedClients.size(); - for (size_t i=0 ; i<count ; i++) { - sp<Client> client = disconnectedClients[i]; - mTokens.release(client->cid); - } - disconnectedClients.clear(); + layer->forceVisibilityTransaction(); + setTransactionFlags(eTraversalNeeded); + return NO_ERROR; } uint32_t SurfaceFlinger::getTransactionFlags(uint32_t flags) @@ -1137,15 +1039,11 @@ uint32_t SurfaceFlinger::getTransactionFlags(uint32_t flags) return android_atomic_and(~flags, &mTransactionFlags) & flags; } -uint32_t SurfaceFlinger::setTransactionFlags(uint32_t flags, nsecs_t delay) +uint32_t SurfaceFlinger::setTransactionFlags(uint32_t flags) { uint32_t old = android_atomic_or(flags, &mTransactionFlags); if ((old & flags)==0) { // wake the server up - if (delay > 0) { - signalDelayedEvent(delay); - } else { - signalEvent(); - } + signalEvent(); } return old; } @@ -1224,8 +1122,8 @@ int SurfaceFlinger::setOrientation(DisplayID dpy, return orientation; } -sp<ISurface> SurfaceFlinger::createSurface(ClientID clientId, int pid, - const String8& name, ISurfaceFlingerClient::surface_data_t* params, +sp<ISurface> SurfaceFlinger::createSurface(const sp<Client>& client, int pid, + const String8& name, ISurfaceComposerClient::surface_data_t* params, DisplayID d, uint32_t w, uint32_t h, PixelFormat format, uint32_t flags) { @@ -1238,57 +1136,52 @@ sp<ISurface> SurfaceFlinger::createSurface(ClientID clientId, int pid, return surfaceHandle; } - Mutex::Autolock _l(mStateLock); - sp<Client> client = mClientsMap.valueFor(clientId); - if (UNLIKELY(client == 0)) { - LOGE("createSurface() failed, client not found (id=%d)", clientId); - return surfaceHandle; - } - //LOGD("createSurface for pid %d (%d x %d)", pid, w, h); - int32_t id = client->generateId(pid); - if (uint32_t(id) >= NUM_LAYERS_MAX) { - LOGE("createSurface() failed, generateId = %d", id); - return surfaceHandle; - } - + sp<Layer> normalLayer; switch (flags & eFXSurfaceMask) { case eFXSurfaceNormal: if (UNLIKELY(flags & ePushBuffers)) { - layer = createPushBuffersSurfaceLocked(client, d, id, - w, h, flags); + layer = createPushBuffersSurface(client, d, w, h, flags); } else { - layer = createNormalSurfaceLocked(client, d, id, - w, h, flags, format); + normalLayer = createNormalSurface(client, d, w, h, flags, format); + layer = normalLayer; } break; case eFXSurfaceBlur: - layer = createBlurSurfaceLocked(client, d, id, w, h, flags); + layer = createBlurSurface(client, d, w, h, flags); break; case eFXSurfaceDim: - layer = createDimSurfaceLocked(client, d, id, w, h, flags); + layer = createDimSurface(client, d, w, h, flags); break; } if (layer != 0) { + layer->initStates(w, h, flags); layer->setName(name); - setTransactionFlags(eTransactionNeeded); + ssize_t token = addClientLayer(client, layer); + surfaceHandle = layer->getSurface(); if (surfaceHandle != 0) { - params->token = surfaceHandle->getToken(); + params->token = token; params->identity = surfaceHandle->getIdentity(); params->width = w; params->height = h; params->format = format; + if (normalLayer != 0) { + Mutex::Autolock _l(mStateLock); + mLayerMap.add(surfaceHandle->asBinder(), normalLayer); + } } + + setTransactionFlags(eTransactionNeeded); } return surfaceHandle; } -sp<LayerBaseClient> SurfaceFlinger::createNormalSurfaceLocked( +sp<Layer> SurfaceFlinger::createNormalSurface( const sp<Client>& client, DisplayID display, - int32_t id, uint32_t w, uint32_t h, uint32_t flags, + uint32_t w, uint32_t h, uint32_t flags, PixelFormat& format) { // initialize the surfaces @@ -1298,53 +1191,56 @@ sp<LayerBaseClient> SurfaceFlinger::createNormalSurfaceLocked( format = PIXEL_FORMAT_RGBA_8888; break; case PIXEL_FORMAT_OPAQUE: +#ifdef NO_RGBX_8888 format = PIXEL_FORMAT_RGB_565; +#else + format = PIXEL_FORMAT_RGBX_8888; +#endif break; } - sp<Layer> layer = new Layer(this, display, client, id); +#ifdef NO_RGBX_8888 + if (format == PIXEL_FORMAT_RGBX_8888) + format = PIXEL_FORMAT_RGBA_8888; +#endif + + sp<Layer> layer = new Layer(this, display, client); status_t err = layer->setBuffers(w, h, format, flags); - if (LIKELY(err == NO_ERROR)) { - layer->initStates(w, h, flags); - addLayer_l(layer); - } else { + if (LIKELY(err != NO_ERROR)) { LOGE("createNormalSurfaceLocked() failed (%s)", strerror(-err)); layer.clear(); } return layer; } -sp<LayerBaseClient> SurfaceFlinger::createBlurSurfaceLocked( +sp<LayerBlur> SurfaceFlinger::createBlurSurface( const sp<Client>& client, DisplayID display, - int32_t id, uint32_t w, uint32_t h, uint32_t flags) + uint32_t w, uint32_t h, uint32_t flags) { - sp<LayerBlur> layer = new LayerBlur(this, display, client, id); + sp<LayerBlur> layer = new LayerBlur(this, display, client); layer->initStates(w, h, flags); - addLayer_l(layer); return layer; } -sp<LayerBaseClient> SurfaceFlinger::createDimSurfaceLocked( +sp<LayerDim> SurfaceFlinger::createDimSurface( const sp<Client>& client, DisplayID display, - int32_t id, uint32_t w, uint32_t h, uint32_t flags) + uint32_t w, uint32_t h, uint32_t flags) { - sp<LayerDim> layer = new LayerDim(this, display, client, id); + sp<LayerDim> layer = new LayerDim(this, display, client); layer->initStates(w, h, flags); - addLayer_l(layer); return layer; } -sp<LayerBaseClient> SurfaceFlinger::createPushBuffersSurfaceLocked( +sp<LayerBuffer> SurfaceFlinger::createPushBuffersSurface( const sp<Client>& client, DisplayID display, - int32_t id, uint32_t w, uint32_t h, uint32_t flags) + uint32_t w, uint32_t h, uint32_t flags) { - sp<LayerBuffer> layer = new LayerBuffer(this, display, client, id); + sp<LayerBuffer> layer = new LayerBuffer(this, display, client); layer->initStates(w, h, flags); - addLayer_l(layer); return layer; } -status_t SurfaceFlinger::removeSurface(SurfaceID index) +status_t SurfaceFlinger::removeSurface(const sp<Client>& client, SurfaceID sid) { /* * called by the window manager, when a surface should be marked for @@ -1357,7 +1253,7 @@ status_t SurfaceFlinger::removeSurface(SurfaceID index) status_t err = NAME_NOT_FOUND; Mutex::Autolock _l(mStateLock); - sp<LayerBaseClient> layer = getLayerUser_l(index); + sp<LayerBaseClient> layer = client->getLayerUser(sid); if (layer != 0) { err = purgatorizeLayer_l(layer); if (err == NO_ERROR) { @@ -1397,21 +1293,20 @@ status_t SurfaceFlinger::destroySurface(const sp<LayerBaseClient>& layer) } }; - mEventQueue.postMessage( new MessageDestroySurface(this, layer) ); + postMessageAsync( new MessageDestroySurface(this, layer) ); return NO_ERROR; } status_t SurfaceFlinger::setClientState( - ClientID cid, + const sp<Client>& client, int32_t count, const layer_state_t* states) { Mutex::Autolock _l(mStateLock); uint32_t flags = 0; - cid <<= 16; for (int i=0 ; i<count ; i++) { - const layer_state_t& s = states[i]; - sp<LayerBaseClient> layer(getLayerUser_l(s.surface | cid)); + const layer_state_t& s(states[i]); + sp<LayerBaseClient> layer(client->getLayerUser(s.surface)); if (layer != 0) { const uint32_t what = s.what; if (what & ePositionChanged) { @@ -1419,9 +1314,10 @@ status_t SurfaceFlinger::setClientState( flags |= eTraversalNeeded; } if (what & eLayerChanged) { + ssize_t idx = mCurrentState.layersSortedByZ.indexOf(layer); if (layer->setLayer(s.z)) { - mCurrentState.layersSortedByZ.reorder( - layer, &Layer::compareCurrentStateZ); + mCurrentState.layersSortedByZ.removeAt(idx); + mCurrentState.layersSortedByZ.add(layer); // we need traversal (state changed) // AND transaction (list changed) flags |= eTransactionNeeded|eTraversalNeeded; @@ -1457,12 +1353,6 @@ status_t SurfaceFlinger::setClientState( return NO_ERROR; } -sp<LayerBaseClient> SurfaceFlinger::getLayerUser_l(SurfaceID s) const -{ - sp<LayerBaseClient> layer = mLayerMap.valueFor(s); - return layer; -} - void SurfaceFlinger::screenReleased(int dpy) { // this may be called by a signal handler, we can't do too much in here @@ -1512,83 +1402,17 @@ status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args) result.append(buffer); } - size_t s = mClientsMap.size(); - char name[64]; - for (size_t i=0 ; i<s ; i++) { - sp<Client> client = mClientsMap.valueAt(i); - sprintf(name, " Client (id=0x%08x)", client->cid); - client->dump(name); - } const LayerVector& currentLayers = mCurrentState.layersSortedByZ; const size_t count = currentLayers.size(); for (size_t i=0 ; i<count ; i++) { - /*** LayerBase ***/ - const sp<LayerBase>& layer = currentLayers[i]; - const Layer::State& s = layer->drawingState(); - snprintf(buffer, SIZE, - "+ %s %p\n" - " " - "z=%9d, pos=(%4d,%4d), size=(%4d,%4d), " - "needsBlending=%1d, needsDithering=%1d, invalidate=%1d, " - "alpha=0x%02x, flags=0x%08x, tr=[%.2f, %.2f][%.2f, %.2f]\n", - layer->getTypeID(), layer.get(), - s.z, layer->tx(), layer->ty(), s.w, s.h, - layer->needsBlending(), layer->needsDithering(), - layer->contentDirty, - s.alpha, s.flags, - s.transform[0][0], s.transform[0][1], - s.transform[1][0], s.transform[1][1]); - result.append(buffer); - buffer[0] = 0; - /*** LayerBaseClient ***/ - sp<LayerBaseClient> lbc = - LayerBase::dynamicCast< LayerBaseClient* >(layer.get()); - if (lbc != 0) { - sp<Client> client(lbc->client.promote()); - snprintf(buffer, SIZE, - " name=%s\n", lbc->getName().string()); - result.append(buffer); - snprintf(buffer, SIZE, - " id=0x%08x, client=0x%08x, identity=%u\n", - lbc->clientIndex(), client.get() ? client->cid : 0, - lbc->getIdentity()); - - result.append(buffer); - buffer[0] = 0; - } - /*** Layer ***/ - sp<Layer> l = LayerBase::dynamicCast< Layer* >(layer.get()); - if (l != 0) { - SharedBufferStack::Statistics stats = l->lcblk->getStats(); - result.append( l->lcblk->dump(" ") ); - sp<const GraphicBuffer> buf0(l->getBuffer(0)); - sp<const GraphicBuffer> buf1(l->getBuffer(1)); - uint32_t w0=0, h0=0, s0=0; - uint32_t w1=0, h1=0, s1=0; - if (buf0 != 0) { - w0 = buf0->getWidth(); - h0 = buf0->getHeight(); - s0 = buf0->getStride(); - } - if (buf1 != 0) { - w1 = buf1->getWidth(); - h1 = buf1->getHeight(); - s1 = buf1->getStride(); - } - snprintf(buffer, SIZE, - " " - "format=%2d, [%3ux%3u:%3u] [%3ux%3u:%3u]," - " freezeLock=%p, dq-q-time=%u us\n", - l->pixelFormat(), - w0, h0, s0, w1, h1, s1, - l->getFreezeLock().get(), stats.totalTime); - result.append(buffer); - buffer[0] = 0; - } + const sp<LayerBase>& layer(currentLayers[i]); + layer->dump(result, buffer, SIZE); + const Layer::State& s(layer->drawingState()); s.transparentRegion.dump(result, "transparentRegion"); layer->transparentRegionScreen.dump(result, "transparentRegionScreen"); layer->visibleRegionScreen.dump(result, "visibleRegionScreen"); } + mWormholeRegion.dump(result, "WormholeRegion"); const DisplayHardware& hw(graphicPlane(0).displayHardware()); snprintf(buffer, SIZE, @@ -1601,18 +1425,19 @@ status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args) " last transaction time : %f us\n", mLastSwapBufferTime/1000.0, mLastTransactionTime/1000.0); result.append(buffer); + if (inSwapBuffersDuration || !locked) { snprintf(buffer, SIZE, " eglSwapBuffers time: %f us\n", inSwapBuffersDuration/1000.0); result.append(buffer); } + if (inTransactionDuration || !locked) { snprintf(buffer, SIZE, " transaction time: %f us\n", inTransactionDuration/1000.0); result.append(buffer); } - snprintf(buffer, SIZE, " client count: %d\n", mClientsMap.size()); - result.append(buffer); + const GraphicBufferAllocator& alloc(GraphicBufferAllocator::get()); alloc.dump(result); @@ -1635,6 +1460,8 @@ status_t SurfaceFlinger::onTransact( case FREEZE_DISPLAY: case UNFREEZE_DISPLAY: case BOOT_FINISHED: + case TURN_ELECTRON_BEAM_OFF: + case TURN_ELECTRON_BEAM_ON: { // codes that require permission check IPCThreadState* ipc = IPCThreadState::self(); @@ -1645,8 +1472,23 @@ status_t SurfaceFlinger::onTransact( "can't access SurfaceFlinger pid=%d, uid=%d", pid, uid); return PERMISSION_DENIED; } + break; + } + case CAPTURE_SCREEN: + { + // codes that require permission check + IPCThreadState* ipc = IPCThreadState::self(); + const int pid = ipc->getCallingPid(); + const int uid = ipc->getCallingUid(); + if ((uid != AID_GRAPHICS) && !mReadFramebuffer.check(pid, uid)) { + LOGE("Permission Denial: " + "can't read framebuffer pid=%d, uid=%d", pid, uid); + return PERMISSION_DENIED; + } + break; } } + status_t err = BnSurfaceComposer::onTransact(code, data, reply, flags); if (err == UNKNOWN_TRANSACTION || err == PERMISSION_DENIED) { CHECK_INTERFACE(ISurfaceComposer, data, reply); @@ -1661,8 +1503,7 @@ status_t SurfaceFlinger::onTransact( int n; switch (code) { case 1000: // SHOW_CPU, NOT SUPPORTED ANYMORE - return NO_ERROR; - case 1001: // SHOW_FPS, NOT SUPPORTED ANYMORE + case 1001: // SHOW_FPS, NOT SUPPORTED ANYMORE return NO_ERROR; case 1002: // SHOW_UPDATES n = data.readInt32(); @@ -1683,6 +1524,11 @@ status_t SurfaceFlinger::onTransact( setTransactionFlags(eTransactionNeeded|eTraversalNeeded); return NO_ERROR; } + case 1006:{ // enable/disable GraphicLog + int enabled = data.readInt32(); + GraphicLog::getInstance().setEnabled(enabled); + return NO_ERROR; + } case 1007: // set mFreezeCount mFreezeCount = data.readInt32(); mFreezeDisplayTime = 0; @@ -1705,116 +1551,805 @@ status_t SurfaceFlinger::onTransact( } // --------------------------------------------------------------------------- -#if 0 -#pragma mark - -#endif -Client::Client(ClientID clientID, const sp<SurfaceFlinger>& flinger) - : ctrlblk(0), cid(clientID), mPid(0), mBitmap(0), mFlinger(flinger) +status_t SurfaceFlinger::renderScreenToTextureLocked(DisplayID dpy, + GLuint* textureName, GLfloat* uOut, GLfloat* vOut) +{ + if (!GLExtensions::getInstance().haveFramebufferObject()) + return INVALID_OPERATION; + + // get screen geometry + const DisplayHardware& hw(graphicPlane(dpy).displayHardware()); + const uint32_t hw_w = hw.getWidth(); + const uint32_t hw_h = hw.getHeight(); + GLfloat u = 1; + GLfloat v = 1; + + // make sure to clear all GL error flags + while ( glGetError() != GL_NO_ERROR ) ; + + // create a FBO + GLuint name, tname; + glGenTextures(1, &tname); + glBindTexture(GL_TEXTURE_2D, tname); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, + hw_w, hw_h, 0, GL_RGB, GL_UNSIGNED_BYTE, 0); + if (glGetError() != GL_NO_ERROR) { + while ( glGetError() != GL_NO_ERROR ) ; + GLint tw = (2 << (31 - clz(hw_w))); + GLint th = (2 << (31 - clz(hw_h))); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, + tw, th, 0, GL_RGB, GL_UNSIGNED_BYTE, 0); + u = GLfloat(hw_w) / tw; + v = GLfloat(hw_h) / th; + } + glGenFramebuffersOES(1, &name); + glBindFramebufferOES(GL_FRAMEBUFFER_OES, name); + glFramebufferTexture2DOES(GL_FRAMEBUFFER_OES, + GL_COLOR_ATTACHMENT0_OES, GL_TEXTURE_2D, tname, 0); + + // redraw the screen entirely... + 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(); + } + + // back to main framebuffer + glBindFramebufferOES(GL_FRAMEBUFFER_OES, 0); + glDisable(GL_SCISSOR_TEST); + glDeleteFramebuffersOES(1, &name); + + *textureName = tname; + *uOut = u; + *vOut = v; + return NO_ERROR; +} + +// --------------------------------------------------------------------------- + +status_t SurfaceFlinger::electronBeamOffAnimationImplLocked() { - const int pgsize = getpagesize(); - const int cblksize = ((sizeof(SharedClient)+(pgsize-1))&~(pgsize-1)); + status_t result = PERMISSION_DENIED; - mCblkHeap = new MemoryHeapBase(cblksize, 0, - "SurfaceFlinger Client control-block"); + if (!GLExtensions::getInstance().haveFramebufferObject()) + return INVALID_OPERATION; - ctrlblk = static_cast<SharedClient *>(mCblkHeap->getBase()); - if (ctrlblk) { // construct the shared structure in-place. - new(ctrlblk) SharedClient; + // get screen geometry + const DisplayHardware& hw(graphicPlane(0).displayHardware()); + const uint32_t hw_w = hw.getWidth(); + const uint32_t hw_h = hw.getHeight(); + const Region screenBounds(hw.bounds()); + + GLfloat u, v; + GLuint tname; + result = renderScreenToTextureLocked(0, &tname, &u, &v); + if (result != NO_ERROR) { + return result; } + + GLfloat vtx[8]; + const GLfloat texCoords[4][2] = { {0,v}, {0,0}, {u,0}, {u,v} }; + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, tname); + glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexCoordPointer(2, GL_FLOAT, 0, texCoords); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glVertexPointer(2, GL_FLOAT, 0, vtx); + + class s_curve_interpolator { + const float nbFrames, s, v; + public: + s_curve_interpolator(int nbFrames, float s) + : nbFrames(1.0f / (nbFrames-1)), s(s), + v(1.0f + expf(-s + 0.5f*s)) { + } + float operator()(int f) { + const float x = f * nbFrames; + return ((1.0f/(1.0f + expf(-x*s + 0.5f*s))) - 0.5f) * v + 0.5f; + } + }; + + class v_stretch { + const GLfloat hw_w, hw_h; + public: + v_stretch(uint32_t hw_w, uint32_t hw_h) + : hw_w(hw_w), hw_h(hw_h) { + } + void operator()(GLfloat* vtx, float v) { + const GLfloat w = hw_w + (hw_w * v); + const GLfloat h = hw_h - (hw_h * v); + const GLfloat x = (hw_w - w) * 0.5f; + const GLfloat y = (hw_h - h) * 0.5f; + vtx[0] = x; vtx[1] = y; + vtx[2] = x; vtx[3] = y + h; + vtx[4] = x + w; vtx[5] = y + h; + vtx[6] = x + w; vtx[7] = y; + } + }; + + class h_stretch { + const GLfloat hw_w, hw_h; + public: + h_stretch(uint32_t hw_w, uint32_t hw_h) + : hw_w(hw_w), hw_h(hw_h) { + } + void operator()(GLfloat* vtx, float v) { + const GLfloat w = hw_w - (hw_w * v); + const GLfloat h = 1.0f; + const GLfloat x = (hw_w - w) * 0.5f; + const GLfloat y = (hw_h - h) * 0.5f; + vtx[0] = x; vtx[1] = y; + vtx[2] = x; vtx[3] = y + h; + vtx[4] = x + w; vtx[5] = y + h; + vtx[6] = x + w; vtx[7] = y; + } + }; + + // the full animation is 24 frames + const int nbFrames = 12; + s_curve_interpolator itr(nbFrames, 7.5f); + s_curve_interpolator itg(nbFrames, 8.0f); + s_curve_interpolator itb(nbFrames, 8.5f); + + v_stretch vverts(hw_w, hw_h); + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE); + for (int i=0 ; i<nbFrames ; i++) { + float x, y, w, h; + const float vr = itr(i); + const float vg = itg(i); + const float vb = itb(i); + + // clear screen + glColorMask(1,1,1,1); + glClear(GL_COLOR_BUFFER_BIT); + glEnable(GL_TEXTURE_2D); + + // draw the red plane + vverts(vtx, vr); + glColorMask(1,0,0,1); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + + // draw the green plane + vverts(vtx, vg); + glColorMask(0,1,0,1); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + + // draw the blue plane + vverts(vtx, vb); + glColorMask(0,0,1,1); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + + // draw the white highlight (we use the last vertices) + glDisable(GL_TEXTURE_2D); + glColorMask(1,1,1,1); + glColor4f(vg, vg, vg, 1); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + hw.flip(screenBounds); + } + + h_stretch hverts(hw_w, hw_h); + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + glColorMask(1,1,1,1); + for (int i=0 ; i<nbFrames ; i++) { + const float v = itg(i); + hverts(vtx, v); + glClear(GL_COLOR_BUFFER_BIT); + glColor4f(1-v, 1-v, 1-v, 1); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + hw.flip(screenBounds); + } + + glColorMask(1,1,1,1); + glEnable(GL_SCISSOR_TEST); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDeleteTextures(1, &tname); + return NO_ERROR; } -Client::~Client() { - if (ctrlblk) { - ctrlblk->~SharedClient(); // destroy our shared-structure. +status_t SurfaceFlinger::electronBeamOnAnimationImplLocked() +{ + status_t result = PERMISSION_DENIED; + + if (!GLExtensions::getInstance().haveFramebufferObject()) + return INVALID_OPERATION; + + + // get screen geometry + const DisplayHardware& hw(graphicPlane(0).displayHardware()); + const uint32_t hw_w = hw.getWidth(); + const uint32_t hw_h = hw.getHeight(); + const Region screenBounds(hw.bounds()); + + GLfloat u, v; + GLuint tname; + result = renderScreenToTextureLocked(0, &tname, &u, &v); + if (result != NO_ERROR) { + return result; } + + // back to main framebuffer + glBindFramebufferOES(GL_FRAMEBUFFER_OES, 0); + glDisable(GL_SCISSOR_TEST); + + GLfloat vtx[8]; + const GLfloat texCoords[4][2] = { {0,v}, {0,0}, {u,0}, {u,v} }; + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, tname); + glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexCoordPointer(2, GL_FLOAT, 0, texCoords); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glVertexPointer(2, GL_FLOAT, 0, vtx); + + class s_curve_interpolator { + const float nbFrames, s, v; + public: + s_curve_interpolator(int nbFrames, float s) + : nbFrames(1.0f / (nbFrames-1)), s(s), + v(1.0f + expf(-s + 0.5f*s)) { + } + float operator()(int f) { + const float x = f * nbFrames; + return ((1.0f/(1.0f + expf(-x*s + 0.5f*s))) - 0.5f) * v + 0.5f; + } + }; + + class v_stretch { + const GLfloat hw_w, hw_h; + public: + v_stretch(uint32_t hw_w, uint32_t hw_h) + : hw_w(hw_w), hw_h(hw_h) { + } + void operator()(GLfloat* vtx, float v) { + const GLfloat w = hw_w + (hw_w * v); + const GLfloat h = hw_h - (hw_h * v); + const GLfloat x = (hw_w - w) * 0.5f; + const GLfloat y = (hw_h - h) * 0.5f; + vtx[0] = x; vtx[1] = y; + vtx[2] = x; vtx[3] = y + h; + vtx[4] = x + w; vtx[5] = y + h; + vtx[6] = x + w; vtx[7] = y; + } + }; + + class h_stretch { + const GLfloat hw_w, hw_h; + public: + h_stretch(uint32_t hw_w, uint32_t hw_h) + : hw_w(hw_w), hw_h(hw_h) { + } + void operator()(GLfloat* vtx, float v) { + const GLfloat w = hw_w - (hw_w * v); + const GLfloat h = 1.0f; + const GLfloat x = (hw_w - w) * 0.5f; + const GLfloat y = (hw_h - h) * 0.5f; + vtx[0] = x; vtx[1] = y; + vtx[2] = x; vtx[3] = y + h; + vtx[4] = x + w; vtx[5] = y + h; + vtx[6] = x + w; vtx[7] = y; + } + }; + + // the full animation is 12 frames + int nbFrames = 8; + s_curve_interpolator itr(nbFrames, 7.5f); + s_curve_interpolator itg(nbFrames, 8.0f); + s_curve_interpolator itb(nbFrames, 8.5f); + + h_stretch hverts(hw_w, hw_h); + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + glColorMask(1,1,1,1); + for (int i=nbFrames-1 ; i>=0 ; i--) { + const float v = itg(i); + hverts(vtx, v); + glClear(GL_COLOR_BUFFER_BIT); + glColor4f(1-v, 1-v, 1-v, 1); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + hw.flip(screenBounds); + } + + nbFrames = 4; + v_stretch vverts(hw_w, hw_h); + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE); + for (int i=nbFrames-1 ; i>=0 ; i--) { + float x, y, w, h; + const float vr = itr(i); + const float vg = itg(i); + const float vb = itb(i); + + // clear screen + glColorMask(1,1,1,1); + glClear(GL_COLOR_BUFFER_BIT); + glEnable(GL_TEXTURE_2D); + + // draw the red plane + vverts(vtx, vr); + glColorMask(1,0,0,1); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + + // draw the green plane + vverts(vtx, vg); + glColorMask(0,1,0,1); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + + // draw the blue plane + vverts(vtx, vb); + glColorMask(0,0,1,1); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + + hw.flip(screenBounds); + } + + glColorMask(1,1,1,1); + glEnable(GL_SCISSOR_TEST); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDeleteTextures(1, &tname); + + return NO_ERROR; } -int32_t Client::generateId(int pid) +// --------------------------------------------------------------------------- + +status_t SurfaceFlinger::turnElectronBeamOffImplLocked(int32_t mode) { - const uint32_t i = clz( ~mBitmap ); - if (i >= NUM_LAYERS_MAX) { - return NO_MEMORY; + DisplayHardware& hw(graphicPlane(0).editDisplayHardware()); + if (!hw.canDraw()) { + // we're already off + return NO_ERROR; } - mPid = pid; - mInUse.add(uint8_t(i)); - mBitmap |= 1<<(31-i); - return i; + if (mode & ISurfaceComposer::eElectronBeamAnimationOff) { + electronBeamOffAnimationImplLocked(); + } + + // always clear the whole screen at the end of the animation + glClearColor(0,0,0,1); + glDisable(GL_SCISSOR_TEST); + glClear(GL_COLOR_BUFFER_BIT); + glEnable(GL_SCISSOR_TEST); + hw.flip( Region(hw.bounds()) ); + + hw.setCanDraw(false); + return NO_ERROR; } -status_t Client::bindLayer(const sp<LayerBaseClient>& layer, int32_t id) +status_t SurfaceFlinger::turnElectronBeamOff(int32_t mode) { - ssize_t idx = mInUse.indexOf(id); - if (idx < 0) - return NAME_NOT_FOUND; - return mLayers.insertAt(layer, idx); + class MessageTurnElectronBeamOff : public MessageBase { + SurfaceFlinger* flinger; + int32_t mode; + status_t result; + public: + MessageTurnElectronBeamOff(SurfaceFlinger* flinger, int32_t mode) + : flinger(flinger), mode(mode), result(PERMISSION_DENIED) { + } + status_t getResult() const { + return result; + } + virtual bool handler() { + Mutex::Autolock _l(flinger->mStateLock); + result = flinger->turnElectronBeamOffImplLocked(mode); + return true; + } + }; + + sp<MessageBase> msg = new MessageTurnElectronBeamOff(this, mode); + status_t res = postMessageSync(msg); + if (res == NO_ERROR) { + res = static_cast<MessageTurnElectronBeamOff*>( msg.get() )->getResult(); + + // work-around: when the power-manager calls us we activate the + // animation. eventually, the "on" animation will be called + // by the power-manager itself + mElectronBeamAnimationMode = mode; + } + return res; } -void Client::free(int32_t id) +// --------------------------------------------------------------------------- + +status_t SurfaceFlinger::turnElectronBeamOnImplLocked(int32_t mode) { - ssize_t idx = mInUse.remove(uint8_t(id)); - if (idx >= 0) { - mBitmap &= ~(1<<(31-id)); - mLayers.removeItemsAt(idx); + DisplayHardware& hw(graphicPlane(0).editDisplayHardware()); + if (hw.canDraw()) { + // we're already on + return NO_ERROR; + } + if (mode & ISurfaceComposer::eElectronBeamAnimationOn) { + electronBeamOnAnimationImplLocked(); } + hw.setCanDraw(true); + + // make sure to redraw the whole screen when the animation is done + mDirtyRegion.set(hw.bounds()); + signalEvent(); + + return NO_ERROR; } -bool Client::isValid(int32_t i) const { - return (uint32_t(i)<NUM_LAYERS_MAX) && (mBitmap & (1<<(31-i))); +status_t SurfaceFlinger::turnElectronBeamOn(int32_t mode) +{ + class MessageTurnElectronBeamOn : public MessageBase { + SurfaceFlinger* flinger; + int32_t mode; + status_t result; + public: + MessageTurnElectronBeamOn(SurfaceFlinger* flinger, int32_t mode) + : flinger(flinger), mode(mode), result(PERMISSION_DENIED) { + } + status_t getResult() const { + return result; + } + virtual bool handler() { + Mutex::Autolock _l(flinger->mStateLock); + result = flinger->turnElectronBeamOnImplLocked(mode); + return true; + } + }; + + postMessageAsync( new MessageTurnElectronBeamOn(this, mode) ); + return NO_ERROR; } -sp<LayerBaseClient> Client::getLayerUser(int32_t i) const { - sp<LayerBaseClient> lbc; - ssize_t idx = mInUse.indexOf(uint8_t(i)); - if (idx >= 0) { - lbc = mLayers[idx].promote(); - LOGE_IF(lbc==0, "getLayerUser(i=%d), idx=%d is dead", int(i), int(idx)); +// --------------------------------------------------------------------------- + +status_t SurfaceFlinger::captureScreenImplLocked(DisplayID dpy, + sp<IMemoryHeap>* heap, + uint32_t* w, uint32_t* h, PixelFormat* f, + uint32_t sw, uint32_t sh) +{ + status_t result = PERMISSION_DENIED; + + // only one display supported for now + if (UNLIKELY(uint32_t(dpy) >= DISPLAY_COUNT)) + return BAD_VALUE; + + if (!GLExtensions::getInstance().haveFramebufferObject()) + return INVALID_OPERATION; + + // get screen geometry + const DisplayHardware& hw(graphicPlane(dpy).displayHardware()); + const uint32_t hw_w = hw.getWidth(); + const uint32_t hw_h = hw.getHeight(); + + if ((sw > hw_w) || (sh > hw_h)) + return BAD_VALUE; + + sw = (!sw) ? hw_w : sw; + sh = (!sh) ? hw_h : sh; + const size_t size = sw * sh * 4; + + // make sure to clear all GL error flags + while ( glGetError() != GL_NO_ERROR ) ; + + // create a FBO + GLuint name, tname; + glGenRenderbuffersOES(1, &tname); + glBindRenderbufferOES(GL_RENDERBUFFER_OES, tname); + glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_RGBA8_OES, sw, sh); + glGenFramebuffersOES(1, &name); + glBindFramebufferOES(GL_FRAMEBUFFER_OES, name); + glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, + 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 + glViewport(0, 0, sw, sh); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrthof(0, hw_w, 0, hw_h, 0, 1); + glMatrixMode(GL_MODELVIEW); + + // redraw the screen entirely... + 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(); + } + + // XXX: this is needed on tegra + glScissor(0, 0, sw, sh); + + // check for errors and return screen capture + if (glGetError() != GL_NO_ERROR) { + // error while rendering + result = INVALID_OPERATION; + } else { + // allocate shared memory large enough to hold the + // screen capture + sp<MemoryHeapBase> base( + new MemoryHeapBase(size, 0, "screen-capture") ); + void* const ptr = base->getBase(); + if (ptr) { + // capture the screen with glReadPixels() + glReadPixels(0, 0, sw, sh, GL_RGBA, GL_UNSIGNED_BYTE, ptr); + if (glGetError() == GL_NO_ERROR) { + *heap = base; + *w = sw; + *h = sh; + *f = PIXEL_FORMAT_RGBA_8888; + result = NO_ERROR; + } + } else { + result = NO_MEMORY; + } + } + + glEnable(GL_SCISSOR_TEST); + glViewport(0, 0, hw_w, hw_h); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + + + } else { + result = BAD_VALUE; } - return lbc; + + // release FBO resources + glBindFramebufferOES(GL_FRAMEBUFFER_OES, 0); + glDeleteRenderbuffersOES(1, &tname); + glDeleteFramebuffersOES(1, &name); + return result; } -void Client::dump(const char* what) + +status_t SurfaceFlinger::captureScreen(DisplayID dpy, + sp<IMemoryHeap>* heap, + uint32_t* width, uint32_t* height, PixelFormat* format, + uint32_t sw, uint32_t sh) { + // only one display supported for now + if (UNLIKELY(uint32_t(dpy) >= DISPLAY_COUNT)) + return BAD_VALUE; + + if (!GLExtensions::getInstance().haveFramebufferObject()) + return INVALID_OPERATION; + + class MessageCaptureScreen : public MessageBase { + SurfaceFlinger* flinger; + DisplayID dpy; + sp<IMemoryHeap>* heap; + uint32_t* w; + uint32_t* h; + PixelFormat* f; + uint32_t sw; + uint32_t sh; + 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) + : flinger(flinger), dpy(dpy), + heap(heap), w(w), h(h), f(f), sw(sw), sh(sh), result(PERMISSION_DENIED) + { + } + status_t getResult() const { + return result; + } + virtual bool handler() { + Mutex::Autolock _l(flinger->mStateLock); + + // if we have secure windows, never allow the screen capture + if (flinger->mSecureFrameBuffer) + return true; + + result = flinger->captureScreenImplLocked(dpy, + heap, w, h, f, sw, sh); + + return true; + } + }; + + sp<MessageBase> msg = new MessageCaptureScreen(this, + dpy, heap, width, height, format, sw, sh); + status_t res = postMessageSync(msg); + if (res == NO_ERROR) { + res = static_cast<MessageCaptureScreen*>( msg.get() )->getResult(); + } + return res; +} + +// --------------------------------------------------------------------------- + +sp<Layer> SurfaceFlinger::getLayer(const sp<ISurface>& sur) const +{ + sp<Layer> result; + Mutex::Autolock _l(mStateLock); + result = mLayerMap.valueFor( sur->asBinder() ).promote(); + return result; } // --------------------------------------------------------------------------- -#if 0 -#pragma mark - -#endif -BClient::BClient(SurfaceFlinger *flinger, ClientID cid, const sp<IMemoryHeap>& cblk) - : mId(cid), mFlinger(flinger), mCblk(cblk) +Client::Client(const sp<SurfaceFlinger>& flinger) + : mFlinger(flinger), mNameGenerator(1) { } -BClient::~BClient() { - // destroy all resources attached to this client - mFlinger->destroyConnection(mId); +Client::~Client() +{ + const size_t count = mLayers.size(); + for (size_t i=0 ; i<count ; i++) { + sp<LayerBaseClient> layer(mLayers.valueAt(i).promote()); + if (layer != 0) { + mFlinger->removeLayer(layer); + } + } +} + +status_t Client::initCheck() const { + return NO_ERROR; +} + +ssize_t Client::attachLayer(const sp<LayerBaseClient>& layer) +{ + int32_t name = android_atomic_inc(&mNameGenerator); + mLayers.add(name, layer); + return name; } -sp<IMemoryHeap> BClient::getControlBlock() const { - return mCblk; +void Client::detachLayer(const LayerBaseClient* layer) +{ + // we do a linear search here, because this doesn't happen often + const size_t count = mLayers.size(); + for (size_t i=0 ; i<count ; i++) { + if (mLayers.valueAt(i) == layer) { + mLayers.removeItemsAt(i, 1); + break; + } + } +} +sp<LayerBaseClient> Client::getLayerUser(int32_t i) const { + sp<LayerBaseClient> lbc; + const wp<LayerBaseClient>& layer(mLayers.valueFor(i)); + if (layer != 0) { + lbc = layer.promote(); + LOGE_IF(lbc==0, "getLayerUser(name=%d) is dead", int(i)); + } + return lbc; } -sp<ISurface> BClient::createSurface( - ISurfaceFlingerClient::surface_data_t* params, int pid, +sp<IMemoryHeap> Client::getControlBlock() const { + return 0; +} +ssize_t Client::getTokenForSurface(const sp<ISurface>& sur) const { + return -1; +} +sp<ISurface> Client::createSurface( + ISurfaceComposerClient::surface_data_t* params, int pid, const String8& name, DisplayID display, uint32_t w, uint32_t h, PixelFormat format, uint32_t flags) { - return mFlinger->createSurface(mId, pid, name, params, display, w, h, - format, flags); + return mFlinger->createSurface(this, pid, name, params, + display, w, h, format, flags); +} +status_t Client::destroySurface(SurfaceID sid) { + return mFlinger->removeSurface(this, sid); +} +status_t Client::setState(int32_t count, const layer_state_t* states) { + return mFlinger->setClientState(this, count, states); } -status_t BClient::destroySurface(SurfaceID sid) +// --------------------------------------------------------------------------- + +UserClient::UserClient(const sp<SurfaceFlinger>& flinger) + : ctrlblk(0), mBitmap(0), mFlinger(flinger) { - sid |= (mId << 16); // add the client-part to id - return mFlinger->removeSurface(sid); + const int pgsize = getpagesize(); + const int cblksize = ((sizeof(SharedClient)+(pgsize-1))&~(pgsize-1)); + + mCblkHeap = new MemoryHeapBase(cblksize, 0, + "SurfaceFlinger Client control-block"); + + ctrlblk = static_cast<SharedClient *>(mCblkHeap->getBase()); + if (ctrlblk) { // construct the shared structure in-place. + new(ctrlblk) SharedClient; + } } -status_t BClient::setState(int32_t count, const layer_state_t* states) +UserClient::~UserClient() { - return mFlinger->setClientState(mId, count, states); + if (ctrlblk) { + ctrlblk->~SharedClient(); // destroy our shared-structure. + } + + /* + * When a UserClient dies, it's unclear what to do exactly. + * We could go ahead and destroy all surfaces linked to that client + * however, it wouldn't be fair to the main Client + * (usually the the window-manager), which might want to re-target + * the layer to another UserClient. + * I think the best is to do nothing, or not much; in most cases the + * WM itself will go ahead and clean things up when it detects a client of + * his has died. + * The remaining question is what to display? currently we keep + * just keep the current buffer. + */ +} + +status_t UserClient::initCheck() const { + return ctrlblk == 0 ? NO_INIT : NO_ERROR; +} + +void UserClient::detachLayer(const Layer* layer) +{ + int32_t name = layer->getToken(); + if (name >= 0) { + int32_t mask = 1LU<<name; + if ((android_atomic_and(~mask, &mBitmap) & mask) == 0) { + LOGW("token %d wasn't marked as used %08x", name, int(mBitmap)); + } + } +} + +sp<IMemoryHeap> UserClient::getControlBlock() const { + return mCblkHeap; +} + +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 this layer already has a token, just return it + name = layer->getToken(); + if ((name >= 0) && (layer->getClient() == this)) + return name; + + name = 0; + do { + int32_t mask = 1LU<<name; + if ((android_atomic_or(mask, &mBitmap) & mask) == 0) { + // we found and locked that name + status_t err = layer->setToken( + const_cast<UserClient*>(this), ctrlblk, name); + if (err != NO_ERROR) { + // free the name + android_atomic_and(~mask, &mBitmap); + name = err; + } + break; + } + if (++name > 31) + name = NO_MEMORY; + } while(name >= 0); + + //LOGD("getTokenForSurface(%p) => %d (client=%p, bitmap=%08lx)", + // sur->asBinder().get(), name, this, mBitmap); + return name; +} + +sp<ISurface> UserClient::createSurface( + ISurfaceComposerClient::surface_data_t* params, int pid, + const String8& name, + DisplayID display, uint32_t w, uint32_t h, PixelFormat format, + uint32_t flags) { + return 0; +} +status_t UserClient::destroySurface(SurfaceID sid) { + return INVALID_OPERATION; +} +status_t UserClient::setState(int32_t count, const layer_state_t* states) { + return INVALID_OPERATION; } // --------------------------------------------------------------------------- @@ -1927,6 +2462,10 @@ const DisplayHardware& GraphicPlane::displayHardware() const { return *mHw; } +DisplayHardware& GraphicPlane::editDisplayHardware() { + return *mHw; +} + const Transform& GraphicPlane::transform() const { return mGlobalTransform; } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index d75dc15..4262175 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -29,74 +29,96 @@ #include <binder/IMemory.h> #include <binder/Permission.h> +#include <binder/BinderService.h> #include <ui/PixelFormat.h> #include <surfaceflinger/ISurfaceComposer.h> -#include <surfaceflinger/ISurfaceFlingerClient.h> +#include <surfaceflinger/ISurfaceComposerClient.h> #include "Barrier.h" #include "Layer.h" -#include "Tokenizer.h" #include "MessageQueue.h" -struct copybit_device_t; -struct overlay_device_t; - namespace android { // --------------------------------------------------------------------------- class Client; -class BClient; class DisplayHardware; class FreezeLock; class Layer; +class LayerBlur; +class LayerDim; class LayerBuffer; -typedef int32_t ClientID; - #define LIKELY( exp ) (__builtin_expect( (exp) != 0, true )) #define UNLIKELY( exp ) (__builtin_expect( (exp) != 0, false )) // --------------------------------------------------------------------------- -class Client : public RefBase +class Client : public BnSurfaceComposerClient +{ +public: + Client(const sp<SurfaceFlinger>& flinger); + ~Client(); + + status_t initCheck() const; + + // protected by SurfaceFlinger::mStateLock + ssize_t attachLayer(const sp<LayerBaseClient>& layer); + void detachLayer(const LayerBaseClient* layer); + sp<LayerBaseClient> getLayerUser(int32_t i) const; + +private: + + // ISurfaceComposerClient interface + virtual sp<IMemoryHeap> getControlBlock() const; + virtual ssize_t getTokenForSurface(const sp<ISurface>& sur) const; + virtual sp<ISurface> createSurface( + surface_data_t* params, int pid, const String8& name, + DisplayID display, uint32_t w, uint32_t h,PixelFormat format, + uint32_t flags); + virtual status_t destroySurface(SurfaceID surfaceId); + virtual status_t setState(int32_t count, const layer_state_t* states); + + DefaultKeyedVector< size_t, wp<LayerBaseClient> > mLayers; + sp<SurfaceFlinger> mFlinger; + int32_t mNameGenerator; +}; + +class UserClient : public BnSurfaceComposerClient { public: - Client(ClientID cid, const sp<SurfaceFlinger>& flinger); - ~Client(); - - int32_t generateId(int pid); - void free(int32_t id); - status_t bindLayer(const sp<LayerBaseClient>& layer, int32_t id); - - inline bool isValid(int32_t i) const; - sp<LayerBaseClient> getLayerUser(int32_t i) const; - void dump(const char* what); - - const Vector< wp<LayerBaseClient> >& getLayers() const { - return mLayers; - } - - const sp<IMemoryHeap>& getControlBlockMemory() const { - return mCblkHeap; - } - // pointer to this client's control block - SharedClient* ctrlblk; - ClientID cid; + SharedClient* ctrlblk; + +public: + UserClient(const sp<SurfaceFlinger>& flinger); + ~UserClient(); + + status_t initCheck() const; + + // protected by SurfaceFlinger::mStateLock + void detachLayer(const Layer* layer); - private: - int getClientPid() const { return mPid; } - - int mPid; - uint32_t mBitmap; - SortedVector<uint8_t> mInUse; - Vector< wp<LayerBaseClient> > mLayers; - sp<IMemoryHeap> mCblkHeap; - sp<SurfaceFlinger> mFlinger; + + // ISurfaceComposerClient interface + virtual sp<IMemoryHeap> getControlBlock() const; + virtual ssize_t getTokenForSurface(const sp<ISurface>& sur) const; + virtual sp<ISurface> createSurface( + surface_data_t* params, int pid, const String8& name, + DisplayID display, uint32_t w, uint32_t h,PixelFormat format, + uint32_t flags); + virtual status_t destroySurface(SurfaceID surfaceId); + virtual status_t setState(int32_t count, const layer_state_t* states); + + // atomic-ops + mutable volatile int32_t mBitmap; + + sp<IMemoryHeap> mCblkHeap; + sp<SurfaceFlinger> mFlinger; }; // --------------------------------------------------------------------------- @@ -119,6 +141,7 @@ public: int getHeight() const; const DisplayHardware& displayHardware() const; + DisplayHardware& editDisplayHardware(); const Transform& transform() const; EGLDisplay getEGLDisplay() const; @@ -143,11 +166,13 @@ enum { eTraversalNeeded = 0x02 }; -class SurfaceFlinger : public BnSurfaceComposer, protected Thread +class SurfaceFlinger : + public BinderService<SurfaceFlinger>, + public BnSurfaceComposer, + protected Thread { public: - static void instantiate(); - static void shutdown(); + static char const* getServiceName() { return "SurfaceFlinger"; } SurfaceFlinger(); virtual ~SurfaceFlinger(); @@ -159,7 +184,8 @@ public: virtual status_t dump(int fd, const Vector<String16>& args); // ISurfaceComposer interface - virtual sp<ISurfaceFlingerClient> createConnection(); + virtual sp<ISurfaceComposerClient> createConnection(); + virtual sp<ISurfaceComposerClient> createClientConnection(); virtual sp<IMemoryHeap> getCblk() const; virtual void bootFinished(); virtual void openGlobalTransaction(); @@ -168,19 +194,29 @@ public: virtual status_t unfreezeDisplay(DisplayID dpy, uint32_t flags); virtual int setOrientation(DisplayID dpy, int orientation, uint32_t flags); virtual void signal() const; + virtual status_t captureScreen(DisplayID dpy, + sp<IMemoryHeap>* heap, + uint32_t* width, + uint32_t* height, + PixelFormat* format, + uint32_t reqWidth, + uint32_t reqHeight); + virtual status_t 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); - + + sp<Layer> getLayer(const sp<ISurface>& sur) const; + private: - friend class BClient; + friend class Client; friend class LayerBase; friend class LayerBuffer; friend class LayerBaseClient; @@ -189,47 +225,47 @@ private: friend class LayerBlur; friend class LayerDim; - sp<ISurface> createSurface(ClientID client, int pid, const String8& name, - ISurfaceFlingerClient::surface_data_t* params, + sp<ISurface> createSurface(const sp<Client>& client, + int pid, const String8& name, + ISurfaceComposerClient::surface_data_t* params, DisplayID display, uint32_t w, uint32_t h, PixelFormat format, uint32_t flags); - sp<LayerBaseClient> createNormalSurfaceLocked( + sp<Layer> createNormalSurface( const sp<Client>& client, DisplayID display, - int32_t id, uint32_t w, uint32_t h, uint32_t flags, + uint32_t w, uint32_t h, uint32_t flags, PixelFormat& format); - sp<LayerBaseClient> createBlurSurfaceLocked( + sp<LayerBlur> createBlurSurface( const sp<Client>& client, DisplayID display, - int32_t id, uint32_t w, uint32_t h, uint32_t flags); + uint32_t w, uint32_t h, uint32_t flags); - sp<LayerBaseClient> createDimSurfaceLocked( + sp<LayerDim> createDimSurface( const sp<Client>& client, DisplayID display, - int32_t id, uint32_t w, uint32_t h, uint32_t flags); + uint32_t w, uint32_t h, uint32_t flags); - sp<LayerBaseClient> createPushBuffersSurfaceLocked( + sp<LayerBuffer> createPushBuffersSurface( const sp<Client>& client, DisplayID display, - int32_t id, uint32_t w, uint32_t h, uint32_t flags); + uint32_t w, uint32_t h, uint32_t flags); - status_t removeSurface(SurfaceID surface_id); + status_t removeSurface(const sp<Client>& client, SurfaceID sid); status_t destroySurface(const sp<LayerBaseClient>& layer); - status_t setClientState(ClientID cid, int32_t count, const layer_state_t* states); - + status_t setClientState(const sp<Client>& client, + int32_t count, const layer_state_t* states); - class LayerVector { + class LayerVector : public SortedVector< sp<LayerBase> > { public: - inline LayerVector() { } - LayerVector(const LayerVector&); - inline size_t size() const { return layers.size(); } - inline sp<LayerBase> const* array() const { return layers.array(); } - ssize_t add(const sp<LayerBase>&, Vector< sp<LayerBase> >::compar_t); - ssize_t remove(const sp<LayerBase>&); - ssize_t reorder(const sp<LayerBase>&, Vector< sp<LayerBase> >::compar_t); - ssize_t indexOf(const sp<LayerBase>& key, size_t guess=0) const; - inline sp<LayerBase> operator [] (size_t i) const { return layers[i]; } - private: - KeyedVector< sp<LayerBase> , size_t> lookup; - Vector< sp<LayerBase> > layers; + LayerVector() { } + LayerVector(const LayerVector& rhs) : SortedVector< sp<LayerBase> >(rhs) { } + virtual int do_compare(const void* lhs, const void* rhs) const { + const sp<LayerBase>& l(*reinterpret_cast<const sp<LayerBase>*>(lhs)); + const sp<LayerBase>& r(*reinterpret_cast<const sp<LayerBase>*>(rhs)); + // sort layers by Z order + uint32_t lz = l->currentState().z; + uint32_t rz = r->currentState().z; + // then by sequence, so we get a stable ordering + return (lz != rz) ? (lz - rz) : (l->sequence - r->sequence); + } }; struct State { @@ -256,8 +292,6 @@ private: public: // hack to work around gcc 4.0.3 bug void signalEvent(); private: - void signalDelayedEvent(nsecs_t delay); - void handleConsoleEvents(); void handleTransaction(uint32_t transactionFlags); void handleTransactionLocked( @@ -278,18 +312,29 @@ private: void unlockClients(); - void destroyConnection(ClientID cid); - sp<LayerBaseClient> getLayerUser_l(SurfaceID index) const; + ssize_t addClientLayer(const sp<Client>& client, + const sp<LayerBaseClient>& lbc); status_t addLayer_l(const sp<LayerBase>& layer); status_t removeLayer_l(const sp<LayerBase>& layer); status_t purgatorizeLayer_l(const sp<LayerBase>& layer); - void free_resources_l(); uint32_t getTransactionFlags(uint32_t flags); - uint32_t setTransactionFlags(uint32_t flags, nsecs_t delay = 0); + uint32_t setTransactionFlags(uint32_t flags); void commitTransaction(); + status_t captureScreenImplLocked(DisplayID dpy, + sp<IMemoryHeap>* heap, + uint32_t* width, uint32_t* height, PixelFormat* format, + uint32_t reqWidth = 0, uint32_t reqHeight = 0); + + status_t turnElectronBeamOffImplLocked(int32_t mode); + status_t turnElectronBeamOnImplLocked(int32_t mode); + status_t electronBeamOffAnimationImplLocked(); + status_t electronBeamOnAnimationImplLocked(); + status_t renderScreenToTextureLocked(DisplayID dpy, + GLuint* textureName, GLfloat* uOut, GLfloat* vOut); + friend class FreezeLock; sp<FreezeLock> getFreezeLock() const; inline void incFreezeCount() { @@ -310,9 +355,13 @@ private: mutable MessageQueue mEventQueue; - - - + + status_t postMessageAsync(const sp<MessageBase>& msg, + nsecs_t reltime=0, uint32_t flags = 0); + + status_t postMessageSync(const sp<MessageBase>& msg, + nsecs_t reltime=0, uint32_t flags = 0); + // access must be protected by mStateLock mutable Mutex mStateLock; State mCurrentState; @@ -321,14 +370,11 @@ private: volatile int32_t mTransactionCount; Condition mTransactionCV; bool mResizeTransationPending; - + // protected by mStateLock (but we could use another lock) - Tokenizer mTokens; - DefaultKeyedVector<ClientID, sp<Client> > mClientsMap; - DefaultKeyedVector<SurfaceID, sp<LayerBaseClient> > mLayerMap; - GraphicPlane mGraphicPlanes[1]; - bool mLayersRemoved; - Vector< sp<Client> > mDisconnectedClients; + GraphicPlane mGraphicPlanes[1]; + bool mLayersRemoved; + DefaultKeyedVector< wp<IBinder>, wp<Layer> > mLayerMap; // constant members (no synchronization needed for access) sp<IMemoryHeap> mServerHeap; @@ -337,6 +383,7 @@ private: nsecs_t mBootTime; Permission mHardwareTest; Permission mAccessSurfaceFlinger; + Permission mReadFramebuffer; Permission mDump; // Can only accessed from the main thread, these members @@ -348,8 +395,11 @@ private: bool mVisibleRegionsDirty; bool mDeferReleaseConsole; bool mFreezeDisplay; + int32_t mElectronBeamAnimationMode; int32_t mFreezeCount; nsecs_t mFreezeDisplayTime; + Vector< sp<LayerBase> > mVisibleLayersSortedByZ; + // don't use a lock for these, we don't care int mDebugRegion; @@ -389,32 +439,6 @@ public: }; // --------------------------------------------------------------------------- - -class BClient : public BnSurfaceFlingerClient -{ -public: - BClient(SurfaceFlinger *flinger, ClientID cid, - const sp<IMemoryHeap>& cblk); - ~BClient(); - - // ISurfaceFlingerClient interface - virtual sp<IMemoryHeap> getControlBlock() const; - - virtual sp<ISurface> createSurface( - surface_data_t* params, int pid, const String8& name, - DisplayID display, uint32_t w, uint32_t h,PixelFormat format, - uint32_t flags); - - virtual status_t destroySurface(SurfaceID surfaceId); - virtual status_t setState(int32_t count, const layer_state_t* states); - -private: - ClientID mId; - SurfaceFlinger* mFlinger; - sp<IMemoryHeap> mCblk; -}; - -// --------------------------------------------------------------------------- }; // namespace android #endif // ANDROID_SURFACE_FLINGER_H diff --git a/services/surfaceflinger/TextureManager.cpp b/services/surfaceflinger/TextureManager.cpp new file mode 100644 index 0000000..c9a15f5 --- /dev/null +++ b/services/surfaceflinger/TextureManager.cpp @@ -0,0 +1,341 @@ +/* + * 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 <stdlib.h> +#include <stdint.h> +#include <sys/types.h> + +#include <utils/Errors.h> +#include <utils/Log.h> + +#include <ui/GraphicBuffer.h> + +#include <GLES/gl.h> +#include <GLES/glext.h> + +#include <hardware/hardware.h> + +#include "clz.h" +#include "DisplayHardware/DisplayHardware.h" +#include "GLExtensions.h" +#include "TextureManager.h" + +namespace android { + +// --------------------------------------------------------------------------- + +TextureManager::TextureManager() + : mGLExtensions(GLExtensions::getInstance()) +{ +} + +GLenum TextureManager::getTextureTarget(const Image* image) { +#if defined(GL_OES_EGL_image_external) + switch (image->target) { + case Texture::TEXTURE_EXTERNAL: + return GL_TEXTURE_EXTERNAL_OES; + } +#endif + return GL_TEXTURE_2D; +} + +status_t TextureManager::initTexture(Texture* texture) +{ + if (texture->name != -1UL) + return INVALID_OPERATION; + + GLuint textureName = -1; + glGenTextures(1, &textureName); + texture->name = textureName; + texture->width = 0; + texture->height = 0; + + const GLenum target = GL_TEXTURE_2D; + glBindTexture(target, textureName); + glTexParameterx(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterx(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameterx(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameterx(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + + return NO_ERROR; +} + +status_t TextureManager::initTexture(Image* pImage, int32_t format) +{ + if (pImage->name != -1UL) + return INVALID_OPERATION; + + GLuint textureName = -1; + glGenTextures(1, &textureName); + pImage->name = textureName; + pImage->width = 0; + pImage->height = 0; + + GLenum target = GL_TEXTURE_2D; +#if defined(GL_OES_EGL_image_external) + if (GLExtensions::getInstance().haveTextureExternal()) { + if (format && isYuvFormat(format)) { + target = GL_TEXTURE_EXTERNAL_OES; + pImage->target = Texture::TEXTURE_EXTERNAL; + } + } +#endif + + glBindTexture(target, textureName); + glTexParameterx(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterx(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameterx(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameterx(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + + return NO_ERROR; +} + +bool TextureManager::isSupportedYuvFormat(int format) +{ + switch (format) { + case HAL_PIXEL_FORMAT_YV12: + return true; + } + return false; +} + +bool TextureManager::isYuvFormat(int format) +{ + switch (format) { + // supported YUV formats + case HAL_PIXEL_FORMAT_YV12: + // Legacy/deprecated YUV formats + case HAL_PIXEL_FORMAT_YCbCr_422_SP: + case HAL_PIXEL_FORMAT_YCrCb_420_SP: + case HAL_PIXEL_FORMAT_YCbCr_422_I: + return true; + } + + // Any OEM format needs to be considered + if (format>=0x100 && format<=0x1FF) + return true; + + return false; +} + +status_t TextureManager::initEglImage(Image* pImage, + EGLDisplay dpy, const sp<GraphicBuffer>& buffer) +{ + status_t err = NO_ERROR; + if (!pImage->dirty) return err; + + // free the previous image + if (pImage->image != EGL_NO_IMAGE_KHR) { + eglDestroyImageKHR(dpy, pImage->image); + pImage->image = EGL_NO_IMAGE_KHR; + } + + // construct an EGL_NATIVE_BUFFER_ANDROID + android_native_buffer_t* clientBuf = buffer->getNativeBuffer(); + + // create the new EGLImageKHR + const EGLint attrs[] = { + EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, + EGL_NONE, EGL_NONE + }; + pImage->image = eglCreateImageKHR( + dpy, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, + (EGLClientBuffer)clientBuf, attrs); + + if (pImage->image != EGL_NO_IMAGE_KHR) { + if (pImage->name == -1UL) { + initTexture(pImage, buffer->format); + } + const GLenum target = getTextureTarget(pImage); + glBindTexture(target, pImage->name); + glEGLImageTargetTexture2DOES(target, (GLeglImageOES)pImage->image); + GLint error = glGetError(); + if (error != GL_NO_ERROR) { + LOGE("glEGLImageTargetTexture2DOES(%p) failed err=0x%04x", + pImage->image, error); + err = INVALID_OPERATION; + } else { + // Everything went okay! + pImage->dirty = false; + pImage->width = clientBuf->width; + pImage->height = clientBuf->height; + } + } else { + LOGE("eglCreateImageKHR() failed. err=0x%4x", eglGetError()); + err = INVALID_OPERATION; + } + return err; +} + +status_t TextureManager::loadTexture(Texture* texture, + const Region& dirty, const GGLSurface& t) +{ + if (texture->name == -1UL) { + status_t err = initTexture(texture); + LOGE_IF(err, "loadTexture failed in initTexture (%s)", strerror(err)); + return err; + } + + if (texture->target != Texture::TEXTURE_2D) + return INVALID_OPERATION; + + glBindTexture(GL_TEXTURE_2D, texture->name); + + /* + * In OpenGL ES we can't specify a stride with glTexImage2D (however, + * GL_UNPACK_ALIGNMENT is a limited form of stride). + * So if the stride here isn't representable with GL_UNPACK_ALIGNMENT, we + * need to do something reasonable (here creating a bigger texture). + * + * extra pixels = (((stride - width) * pixelsize) / GL_UNPACK_ALIGNMENT); + * + * This situation doesn't happen often, but some h/w have a limitation + * for their framebuffer (eg: must be multiple of 8 pixels), and + * we need to take that into account when using these buffers as + * textures. + * + * This should never be a problem with POT textures + */ + + int unpack = __builtin_ctz(t.stride * bytesPerPixel(t.format)); + unpack = 1 << ((unpack > 3) ? 3 : unpack); + glPixelStorei(GL_UNPACK_ALIGNMENT, unpack); + + /* + * round to POT if needed + */ + if (!mGLExtensions.haveNpot()) { + texture->NPOTAdjust = true; + } + + if (texture->NPOTAdjust) { + // find the smallest power-of-two that will accommodate our surface + texture->potWidth = 1 << (31 - clz(t.width)); + texture->potHeight = 1 << (31 - clz(t.height)); + if (texture->potWidth < t.width) texture->potWidth <<= 1; + if (texture->potHeight < t.height) texture->potHeight <<= 1; + texture->wScale = float(t.width) / texture->potWidth; + texture->hScale = float(t.height) / texture->potHeight; + } else { + texture->potWidth = t.width; + texture->potHeight = t.height; + } + + Rect bounds(dirty.bounds()); + GLvoid* data = 0; + if (texture->width != t.width || texture->height != t.height) { + texture->width = t.width; + texture->height = t.height; + + // texture size changed, we need to create a new one + bounds.set(Rect(t.width, t.height)); + if (t.width == texture->potWidth && + t.height == texture->potHeight) { + // we can do it one pass + data = t.data; + } + + if (t.format == HAL_PIXEL_FORMAT_RGB_565) { + glTexImage2D(GL_TEXTURE_2D, 0, + GL_RGB, texture->potWidth, texture->potHeight, 0, + GL_RGB, GL_UNSIGNED_SHORT_5_6_5, data); + } else if (t.format == HAL_PIXEL_FORMAT_RGBA_4444) { + glTexImage2D(GL_TEXTURE_2D, 0, + GL_RGBA, texture->potWidth, texture->potHeight, 0, + GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, data); + } else if (t.format == HAL_PIXEL_FORMAT_RGBA_8888 || + t.format == HAL_PIXEL_FORMAT_RGBX_8888) { + glTexImage2D(GL_TEXTURE_2D, 0, + GL_RGBA, texture->potWidth, texture->potHeight, 0, + GL_RGBA, GL_UNSIGNED_BYTE, data); + } else if (isSupportedYuvFormat(t.format)) { + // just show the Y plane of YUV buffers + glTexImage2D(GL_TEXTURE_2D, 0, + GL_LUMINANCE, texture->potWidth, texture->potHeight, 0, + GL_LUMINANCE, GL_UNSIGNED_BYTE, data); + } else { + // oops, we don't handle this format! + LOGE("texture=%d, using format %d, which is not " + "supported by the GL", texture->name, t.format); + } + } + if (!data) { + if (t.format == HAL_PIXEL_FORMAT_RGB_565) { + glTexSubImage2D(GL_TEXTURE_2D, 0, + 0, bounds.top, t.width, bounds.height(), + GL_RGB, GL_UNSIGNED_SHORT_5_6_5, + t.data + bounds.top*t.stride*2); + } else if (t.format == HAL_PIXEL_FORMAT_RGBA_4444) { + glTexSubImage2D(GL_TEXTURE_2D, 0, + 0, bounds.top, t.width, bounds.height(), + GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, + t.data + bounds.top*t.stride*2); + } else if (t.format == HAL_PIXEL_FORMAT_RGBA_8888 || + t.format == HAL_PIXEL_FORMAT_RGBX_8888) { + glTexSubImage2D(GL_TEXTURE_2D, 0, + 0, bounds.top, t.width, bounds.height(), + GL_RGBA, GL_UNSIGNED_BYTE, + t.data + bounds.top*t.stride*4); + } else if (isSupportedYuvFormat(t.format)) { + // just show the Y plane of YUV buffers + glTexSubImage2D(GL_TEXTURE_2D, 0, + 0, bounds.top, t.width, bounds.height(), + GL_LUMINANCE, GL_UNSIGNED_BYTE, + t.data + bounds.top*t.stride); + } + } + return NO_ERROR; +} + +void TextureManager::activateTexture(const Texture& texture, bool filter) +{ + const GLenum target = getTextureTarget(&texture); + if (target == GL_TEXTURE_2D) { + glBindTexture(GL_TEXTURE_2D, texture.name); + glEnable(GL_TEXTURE_2D); +#if defined(GL_OES_EGL_image_external) + if (GLExtensions::getInstance().haveTextureExternal()) { + glDisable(GL_TEXTURE_EXTERNAL_OES); + } + } else { + glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture.name); + glEnable(GL_TEXTURE_EXTERNAL_OES); + glDisable(GL_TEXTURE_2D); +#endif + } + + if (filter) { + glTexParameterx(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterx(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } else { + glTexParameterx(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameterx(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } +} + +void TextureManager::deactivateTextures() +{ + glDisable(GL_TEXTURE_2D); +#if defined(GL_OES_EGL_image_external) + if (GLExtensions::getInstance().haveTextureExternal()) { + glDisable(GL_TEXTURE_EXTERNAL_OES); + } +#endif +} + +// --------------------------------------------------------------------------- + +}; // namespace android diff --git a/services/surfaceflinger/TextureManager.h b/services/surfaceflinger/TextureManager.h new file mode 100644 index 0000000..18c4348 --- /dev/null +++ b/services/surfaceflinger/TextureManager.h @@ -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. + */ + +#ifndef ANDROID_TEXTURE_MANAGER_H +#define ANDROID_TEXTURE_MANAGER_H + +#include <stdint.h> +#include <sys/types.h> + +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <GLES/gl.h> + +#include <ui/Region.h> + +#include <pixelflinger/pixelflinger.h> + +namespace android { + +// --------------------------------------------------------------------------- + +class GLExtensions; +class GraphicBuffer; + +// --------------------------------------------------------------------------- + +struct Image { + enum { TEXTURE_2D=0, TEXTURE_EXTERNAL=1 }; + Image() : name(-1U), image(EGL_NO_IMAGE_KHR), width(0), height(0), + dirty(1), target(TEXTURE_2D) { } + GLuint name; + EGLImageKHR image; + GLuint width; + GLuint height; + unsigned dirty : 1; + unsigned target : 1; +}; + +struct Texture : public Image { + Texture() : Image(), NPOTAdjust(0) { } + GLuint potWidth; + GLuint potHeight; + GLfloat wScale; + GLfloat hScale; + unsigned NPOTAdjust : 1; +}; + +// --------------------------------------------------------------------------- + +class TextureManager { + const GLExtensions& mGLExtensions; + static status_t initTexture(Image* texture, int32_t format); + static status_t initTexture(Texture* texture); + static bool isSupportedYuvFormat(int format); + static bool isYuvFormat(int format); + static GLenum getTextureTarget(const Image* pImage); +public: + + TextureManager(); + + // load bitmap data into the active buffer + status_t loadTexture(Texture* texture, + const Region& dirty, const GGLSurface& t); + + // make active buffer an EGLImage if needed + status_t initEglImage(Image* texture, + EGLDisplay dpy, const sp<GraphicBuffer>& buffer); + + // activate a texture + static void activateTexture(const Texture& texture, bool filter); + + // deactivate a texture + static void deactivateTextures(); +}; + +// --------------------------------------------------------------------------- + +}; // namespace android + +#endif // ANDROID_TEXTURE_MANAGER_H diff --git a/services/surfaceflinger/Tokenizer.cpp b/services/surfaceflinger/Tokenizer.cpp deleted file mode 100644 index be3a239..0000000 --- a/services/surfaceflinger/Tokenizer.cpp +++ /dev/null @@ -1,173 +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 <stdio.h> - -#include "Tokenizer.h" - -// ---------------------------------------------------------------------------- - -namespace android { - -ANDROID_BASIC_TYPES_TRAITS(Tokenizer::run_t) - -Tokenizer::Tokenizer() -{ -} - -Tokenizer::Tokenizer(const Tokenizer& other) - : mRanges(other.mRanges) -{ -} - -Tokenizer::~Tokenizer() -{ -} - -uint32_t Tokenizer::acquire() -{ - if (!mRanges.size() || mRanges[0].first) { - _insertTokenAt(0,0); - return 0; - } - - // just extend the first run - const run_t& run = mRanges[0]; - uint32_t token = run.first + run.length; - _insertTokenAt(token, 1); - return token; -} - -bool Tokenizer::isAcquired(uint32_t token) const -{ - return (_indexOrderOf(token) >= 0); -} - -status_t Tokenizer::reserve(uint32_t token) -{ - size_t o; - const ssize_t i = _indexOrderOf(token, &o); - if (i >= 0) { - return BAD_VALUE; // this token is already taken - } - ssize_t err = _insertTokenAt(token, o); - return (err<0) ? err : status_t(NO_ERROR); -} - -status_t Tokenizer::release(uint32_t token) -{ - const ssize_t i = _indexOrderOf(token); - if (i >= 0) { - const run_t& run = mRanges[i]; - if ((token >= run.first) && (token < run.first+run.length)) { - // token in this range, we need to split - run_t& run = mRanges.editItemAt(i); - if ((token == run.first) || (token == run.first+run.length-1)) { - if (token == run.first) { - run.first += 1; - } - run.length -= 1; - if (run.length == 0) { - // XXX: should we systematically remove a run that's empty? - mRanges.removeItemsAt(i); - } - } else { - // split the run - run_t new_run; - new_run.first = token+1; - new_run.length = run.first+run.length - new_run.first; - run.length = token - run.first; - mRanges.insertAt(new_run, i+1); - } - return NO_ERROR; - } - } - return NAME_NOT_FOUND; -} - -ssize_t Tokenizer::_indexOrderOf(uint32_t token, size_t* order) const -{ - // binary search - ssize_t err = NAME_NOT_FOUND; - ssize_t l = 0; - ssize_t h = mRanges.size()-1; - ssize_t mid; - const run_t* a = mRanges.array(); - while (l <= h) { - mid = l + (h - l)/2; - const run_t* const curr = a + mid; - int c = 0; - if (token < curr->first) c = 1; - else if (token >= curr->first+curr->length) c = -1; - if (c == 0) { - err = l = mid; - break; - } else if (c < 0) { - l = mid + 1; - } else { - h = mid - 1; - } - } - if (order) *order = l; - return err; -} - -ssize_t Tokenizer::_insertTokenAt(uint32_t token, size_t index) -{ - const size_t c = mRanges.size(); - - if (index >= 1) { - // do we need to merge with the previous run? - run_t& p = mRanges.editItemAt(index-1); - if (p.first+p.length == token) { - p.length += 1; - if (index < c) { - const run_t& n = mRanges[index]; - if (token+1 == n.first) { - p.length += n.length; - mRanges.removeItemsAt(index); - } - } - return index; - } - } - - if (index < c) { - // do we need to merge with the next run? - run_t& n = mRanges.editItemAt(index); - if (token+1 == n.first) { - n.first -= 1; - n.length += 1; - return index; - } - } - - return mRanges.insertAt(run_t(token,1), index); -} - -void Tokenizer::dump() const -{ - const run_t* ranges = mRanges.array(); - const size_t c = mRanges.size(); - printf("Tokenizer (%p, size = %d)\n", this, int(c)); - for (size_t i=0 ; i<c ; i++) { - printf("%u: (%u, %u)\n", i, - uint32_t(ranges[i].first), uint32_t(ranges[i].length)); - } -} - -}; // namespace android - diff --git a/services/surfaceflinger/Tokenizer.h b/services/surfaceflinger/Tokenizer.h deleted file mode 100644 index 6b3057d..0000000 --- a/services/surfaceflinger/Tokenizer.h +++ /dev/null @@ -1,57 +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_TOKENIZER_H -#define ANDROID_TOKENIZER_H - -#include <utils/Vector.h> -#include <utils/Errors.h> - -// ---------------------------------------------------------------------------- - -namespace android { - -class Tokenizer -{ -public: - Tokenizer(); - Tokenizer(const Tokenizer& other); - ~Tokenizer(); - - uint32_t acquire(); - status_t reserve(uint32_t token); - status_t release(uint32_t token); - bool isAcquired(uint32_t token) const; - - void dump() const; - - struct run_t { - run_t() {}; - run_t(uint32_t f, uint32_t l) : first(f), length(l) {} - uint32_t first; - uint32_t length; - }; -private: - ssize_t _indexOrderOf(uint32_t token, size_t* order=0) const; - ssize_t _insertTokenAt(uint32_t token, size_t index); - Vector<run_t> mRanges; -}; - -}; // namespace android - -// ---------------------------------------------------------------------------- - -#endif // ANDROID_TOKENIZER_H diff --git a/services/surfaceflinger/Transform.cpp b/services/surfaceflinger/Transform.cpp index 175f989..0467a14 100644 --- a/services/surfaceflinger/Transform.cpp +++ b/services/surfaceflinger/Transform.cpp @@ -28,26 +28,40 @@ namespace android { // --------------------------------------------------------------------------- -template <typename T> inline T min(T a, T b) { +template <typename T> +static inline T min(T a, T b) { return a<b ? a : b; } -template <typename T> inline T min(T a, T b, T c) { +template <typename T> +static inline T min(T a, T b, T c) { return min(a, min(b, c)); } -template <typename T> inline T min(T a, T b, T c, T d) { +template <typename T> +static inline T min(T a, T b, T c, T d) { return min(a, b, min(c, d)); } -template <typename T> inline T max(T a, T b) { +template <typename T> +static inline T max(T a, T b) { return a>b ? a : b; } -template <typename T> inline T max(T a, T b, T c) { +template <typename T> +static inline T max(T a, T b, T c) { return max(a, max(b, c)); } -template <typename T> inline T max(T a, T b, T c, T d) { +template <typename T> +static inline T max(T a, T b, T c, T d) { return max(a, b, max(c, d)); } +template <typename T> +static inline +void swap(T& a, T& b) { + T t(a); + a = b; + b = t; +} + // --------------------------------------------------------------------------- Transform::Transform() { @@ -159,56 +173,38 @@ status_t Transform::set(uint32_t flags, float w, float h) return BAD_VALUE; } - mType = flags << 8; - float sx = (flags & FLIP_H) ? -1 : 1; - float sy = (flags & FLIP_V) ? -1 : 1; - float a=0, b=0, c=0, d=0, x=0, y=0; - int xmask = 0; - - // computation of x,y - // x y - // 0 0 0 - // w 0 ROT90 - // w h FLIPH|FLIPV - // 0 h FLIPH|FLIPV|ROT90 - + Transform H, V, R; if (flags & ROT_90) { - mType |= ROTATE; - b = -sy; - c = sx; - xmask = 1; - } else { - a = sx; - d = sy; + // w & h are inverted when rotating by 90 degrees + swap(w, h); } if (flags & FLIP_H) { - mType ^= SCALE; - xmask ^= 1; + H.mType = (FLIP_H << 8) | SCALE; + H.mType |= isZero(w) ? IDENTITY : TRANSLATE; + mat33& M(H.mMatrix); + M[0][0] = -1; + M[2][0] = w; } if (flags & FLIP_V) { - mType ^= SCALE; - y = h; - } - - if ((flags & ROT_180) == ROT_180) { - mType |= ROTATE; + V.mType = (FLIP_V << 8) | SCALE; + V.mType |= isZero(h) ? IDENTITY : TRANSLATE; + mat33& M(V.mMatrix); + M[1][1] = -1; + M[2][1] = h; } - if (xmask) { - x = w; - } - - if (!isZero(x) || !isZero(y)) { - mType |= TRANSLATE; + if (flags & ROT_90) { + const float original_w = h; + R.mType = (ROT_90 << 8) | ROTATE; + R.mType |= isZero(original_w) ? IDENTITY : TRANSLATE; + mat33& M(R.mMatrix); + M[0][0] = 0; M[1][0] =-1; M[2][0] = original_w; + M[0][1] = 1; M[1][1] = 0; } - mat33& M(mMatrix); - M[0][0] = a; M[1][0] = b; M[2][0] = x; - M[0][1] = c; M[1][1] = d; M[2][1] = y; - M[0][2] = 0; M[1][2] = 0; M[2][2] = 1; - + *this = (R*(H*V)); return NO_ERROR; } @@ -229,14 +225,13 @@ Transform::vec3 Transform::transform(const vec3& v) const { return r; } -void Transform::transform(fixed1616* point, int x, int y) const +void Transform::transform(float* point, int x, int y) const { - const float toFixed = 65536.0f; const mat33& M(mMatrix); vec2 v(x, y); v = transform(v); - point[0] = v[0] * toFixed; - point[1] = v[1] * toFixed; + point[0] = v[0]; + point[1] = v[1]; } Rect Transform::makeBounds(int w, int h) const @@ -307,8 +302,8 @@ uint32_t Transform::type() const } } else if (isZero(a) && isZero(d)) { flags |= ROT_90; - if (b>0) flags |= FLIP_H; - if (c<0) flags |= FLIP_V; + if (b>0) flags |= FLIP_V; + if (c<0) flags |= FLIP_H; if (!absIsOne(b) || !absIsOne(c)) { scale = true; } diff --git a/services/surfaceflinger/Transform.h b/services/surfaceflinger/Transform.h index 2e5b893..8fa5b86 100644 --- a/services/surfaceflinger/Transform.h +++ b/services/surfaceflinger/Transform.h @@ -23,6 +23,8 @@ #include <ui/Point.h> #include <ui/Rect.h> +#include <hardware/hardware.h> + namespace android { class Region; @@ -37,14 +39,11 @@ public: explicit Transform(uint32_t orientation); ~Transform(); - typedef int32_t fixed1616; - - // FIXME: must match OVERLAY_TRANSFORM_*, pull from hardware.h enum orientation_flags { ROT_0 = 0x00000000, - FLIP_H = 0x00000001, - FLIP_V = 0x00000002, - ROT_90 = 0x00000004, + FLIP_H = HAL_TRANSFORM_FLIP_H, + FLIP_V = HAL_TRANSFORM_FLIP_V, + ROT_90 = HAL_TRANSFORM_ROT_90, ROT_180 = FLIP_H|FLIP_V, ROT_270 = ROT_180|ROT_90, ROT_INVALID = 0x80 @@ -76,7 +75,7 @@ public: // transform data Rect makeBounds(int w, int h) const; - void transform(fixed1616* point, int x, int y) const; + void transform(float* point, int x, int y) const; Region transform(const Region& reg) const; Transform operator * (const Transform& rhs) const; diff --git a/services/surfaceflinger/tests/screencap/Android.mk b/services/surfaceflinger/tests/screencap/Android.mk new file mode 100644 index 0000000..1cfb471 --- /dev/null +++ b/services/surfaceflinger/tests/screencap/Android.mk @@ -0,0 +1,26 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + screencap.cpp + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libutils \ + libbinder \ + libskia \ + libui \ + libsurfaceflinger_client + +LOCAL_MODULE:= test-screencap + +LOCAL_MODULE_TAGS := tests + +LOCAL_C_INCLUDES += \ + external/skia/include/core \ + external/skia/include/effects \ + external/skia/include/images \ + external/skia/src/ports \ + external/skia/include/utils + +include $(BUILD_EXECUTABLE) diff --git a/services/surfaceflinger/tests/screencap/screencap.cpp b/services/surfaceflinger/tests/screencap/screencap.cpp new file mode 100644 index 0000000..6cf1504 --- /dev/null +++ b/services/surfaceflinger/tests/screencap/screencap.cpp @@ -0,0 +1,63 @@ +/* + * 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 <utils/Log.h> + +#include <binder/IPCThreadState.h> +#include <binder/ProcessState.h> +#include <binder/IServiceManager.h> + +#include <binder/IMemory.h> +#include <surfaceflinger/ISurfaceComposer.h> + +#include <SkImageEncoder.h> +#include <SkBitmap.h> + +using namespace android; + +int main(int argc, char** argv) +{ + if (argc != 2) { + printf("usage: %s path\n", argv[0]); + exit(0); + } + + const String16 name("SurfaceFlinger"); + sp<ISurfaceComposer> composer; + getService(name, &composer); + + sp<IMemoryHeap> heap; + uint32_t w, h; + PixelFormat f; + status_t err = composer->captureScreen(0, &heap, &w, &h, &f, 0, 0); + if (err != NO_ERROR) { + fprintf(stderr, "screen capture failed: %s\n", strerror(-err)); + exit(0); + } + + printf("screen capture success: w=%u, h=%u, pixels=%p\n", + w, h, heap->getBase()); + + printf("saving file as PNG in %s ...\n", argv[1]); + + SkBitmap b; + b.setConfig(SkBitmap::kARGB_8888_Config, w, h); + b.setPixels(heap->getBase()); + SkImageEncoder::EncodeFile(argv[1], b, + SkImageEncoder::kPNG_Type, SkImageEncoder::kDefaultQuality); + + return 0; +} diff --git a/services/surfaceflinger/tests/surface/Android.mk b/services/surfaceflinger/tests/surface/Android.mk new file mode 100644 index 0000000..ce0e807 --- /dev/null +++ b/services/surfaceflinger/tests/surface/Android.mk @@ -0,0 +1,18 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + surface.cpp + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libutils \ + libbinder \ + libui \ + libsurfaceflinger_client + +LOCAL_MODULE:= test-surface + +LOCAL_MODULE_TAGS := tests + +include $(BUILD_EXECUTABLE) diff --git a/services/surfaceflinger/tests/surface/surface.cpp b/services/surfaceflinger/tests/surface/surface.cpp new file mode 100644 index 0000000..b4de4b4 --- /dev/null +++ b/services/surfaceflinger/tests/surface/surface.cpp @@ -0,0 +1,54 @@ +#include <cutils/memory.h> + +#include <utils/Log.h> + +#include <binder/IPCThreadState.h> +#include <binder/ProcessState.h> +#include <binder/IServiceManager.h> + +#include <surfaceflinger/Surface.h> +#include <surfaceflinger/ISurface.h> +#include <surfaceflinger/SurfaceComposerClient.h> + +#include <ui/Overlay.h> + +using namespace android; + +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<SurfaceControl> surfaceControl = client->createSurface( + getpid(), 0, 160, 240, PIXEL_FORMAT_RGB_565); + client->openTransaction(); + surfaceControl->setLayer(100000); + client->closeTransaction(); + + // pretend it went cross-process + Parcel parcel; + SurfaceControl::writeSurfaceToParcel(surfaceControl, &parcel); + parcel.setDataPosition(0); + sp<Surface> surface = Surface::readFromParcel(parcel); + ANativeWindow* window = surface.get(); + + printf("window=%p\n", window); + + int err = native_window_set_buffer_count(window, 8); + android_native_buffer_t* buffer; + + for (int i=0 ; i<8 ; i++) { + window->dequeueBuffer(window, &buffer); + printf("buffer %d: %p\n", i, buffer); + } + + printf("test complete. CTRL+C to finish.\n"); + + IPCThreadState::self()->joinThreadPool(); + return 0; +} diff --git a/services/surfaceflinger/tests/transform/Android.mk b/services/surfaceflinger/tests/transform/Android.mk new file mode 100644 index 0000000..6219dae --- /dev/null +++ b/services/surfaceflinger/tests/transform/Android.mk @@ -0,0 +1,19 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + TransformTest.cpp \ + ../../Transform.cpp + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libutils \ + libui \ + +LOCAL_MODULE:= test-transform + +LOCAL_MODULE_TAGS := tests + +LOCAL_C_INCLUDES += ../.. + +include $(BUILD_EXECUTABLE) diff --git a/services/surfaceflinger/tests/transform/TransformTest.cpp b/services/surfaceflinger/tests/transform/TransformTest.cpp new file mode 100644 index 0000000..e112c4e --- /dev/null +++ b/services/surfaceflinger/tests/transform/TransformTest.cpp @@ -0,0 +1,45 @@ +/* + * 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 <utils/Errors.h> +#include "../../Transform.h" + +using namespace android; + +int main(int argc, char **argv) +{ + Transform tr90(Transform::ROT_90); + Transform trFH(Transform::FLIP_H); + Transform trFV(Transform::FLIP_V); + + Transform tr90FH(Transform::ROT_90 | Transform::FLIP_H); + Transform tr90FV(Transform::ROT_90 | Transform::FLIP_V); + + tr90.dump("tr90"); + trFH.dump("trFH"); + trFV.dump("trFV"); + + tr90FH.dump("tr90FH"); + tr90FV.dump("tr90FV"); + + (trFH*tr90).dump("trFH*tr90"); + (trFV*tr90).dump("trFV*tr90"); + + (tr90*trFH).dump("tr90*trFH"); + (tr90*trFV).dump("tr90*trFV"); + + return 0; +} diff --git a/services/tests/servicestests/src/com/android/server/DropBoxTest.java b/services/tests/servicestests/src/com/android/server/DropBoxTest.java index 78a90fb..f3baff4 100644 --- a/services/tests/servicestests/src/com/android/server/DropBoxTest.java +++ b/services/tests/servicestests/src/com/android/server/DropBoxTest.java @@ -20,6 +20,10 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.os.DropBoxManager; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.ParcelFileDescriptor; +import android.os.Process; import android.os.ServiceManager; import android.os.StatFs; import android.provider.Settings; @@ -27,10 +31,13 @@ import android.test.AndroidTestCase; import com.android.server.DropBoxManagerService; +import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; +import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.util.Random; import java.util.zip.GZIPOutputStream; @@ -531,6 +538,203 @@ public class DropBoxTest extends AndroidTestCase { service.stop(); } + public void testDropBoxEntrySerialization() throws Exception { + // Make sure DropBoxManager.Entry can be serialized to a Parcel and back + // under a variety of conditions. + + Parcel parcel = Parcel.obtain(); + File dir = getEmptyDir("testDropBoxEntrySerialization"); + + new DropBoxManager.Entry("empty", 1000000).writeToParcel(parcel, 0); + new DropBoxManager.Entry("string", 2000000, "String Value").writeToParcel(parcel, 0); + new DropBoxManager.Entry("bytes", 3000000, "Bytes Value".getBytes(), + DropBoxManager.IS_TEXT).writeToParcel(parcel, 0); + new DropBoxManager.Entry("zerobytes", 4000000, new byte[0], 0).writeToParcel(parcel, 0); + new DropBoxManager.Entry("emptybytes", 5000000, (byte[]) null, + DropBoxManager.IS_EMPTY).writeToParcel(parcel, 0); + + try { + new DropBoxManager.Entry("badbytes", 99999, + "Bad Bytes Value".getBytes(), + DropBoxManager.IS_EMPTY).writeToParcel(parcel, 0); + fail("IllegalArgumentException expected for non-null byte[] and IS_EMPTY flags"); + } catch (IllegalArgumentException e) { + // expected + } + + try { + new DropBoxManager.Entry("badbytes", 99999, (byte[]) null, 0).writeToParcel(parcel, 0); + fail("IllegalArgumentException expected for null byte[] and non-IS_EMPTY flags"); + } catch (IllegalArgumentException e) { + // expected + } + + File f = new File(dir, "file.dat"); + FileOutputStream os = new FileOutputStream(f); + os.write("File Value".getBytes()); + os.close(); + + new DropBoxManager.Entry("file", 6000000, f, DropBoxManager.IS_TEXT).writeToParcel( + parcel, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + new DropBoxManager.Entry("binfile", 7000000, f, 0).writeToParcel( + parcel, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + new DropBoxManager.Entry("emptyfile", 8000000, (ParcelFileDescriptor) null, + DropBoxManager.IS_EMPTY).writeToParcel(parcel, 0); + + try { + new DropBoxManager.Entry("badfile", 99999, new File(dir, "nonexist.dat"), 0); + fail("IOException expected for nonexistent file"); + } catch (IOException e) { + // expected + } + + try { + new DropBoxManager.Entry("badfile", 99999, f, DropBoxManager.IS_EMPTY).writeToParcel( + parcel, 0); + fail("IllegalArgumentException expected for non-null file and IS_EMPTY flags"); + } catch (IllegalArgumentException e) { + // expected + } + + try { + new DropBoxManager.Entry("badfile", 99999, (ParcelFileDescriptor) null, 0); + fail("IllegalArgumentException expected for null PFD and non-IS_EMPTY flags"); + } catch (IllegalArgumentException e) { + // expected + } + + File gz = new File(dir, "file.gz"); + GZIPOutputStream gzout = new GZIPOutputStream(new FileOutputStream(gz)); + gzout.write("Gzip File Value".getBytes()); + gzout.close(); + + new DropBoxManager.Entry("gzipfile", 9000000, gz, + DropBoxManager.IS_TEXT | DropBoxManager.IS_GZIPPED).writeToParcel( + parcel, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + new DropBoxManager.Entry("gzipbinfile", 10000000, gz, + DropBoxManager.IS_GZIPPED).writeToParcel( + parcel, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + + // + // Switch from writing to reading + // + + parcel.setDataPosition(0); + DropBoxManager.Entry e; + + e = DropBoxManager.Entry.CREATOR.createFromParcel(parcel); + assertEquals("empty", e.getTag()); + assertEquals(1000000, e.getTimeMillis()); + assertEquals(DropBoxManager.IS_EMPTY, e.getFlags()); + assertEquals(null, e.getText(100)); + assertEquals(null, e.getInputStream()); + e.close(); + + e = DropBoxManager.Entry.CREATOR.createFromParcel(parcel); + assertEquals("string", e.getTag()); + assertEquals(2000000, e.getTimeMillis()); + assertEquals(DropBoxManager.IS_TEXT, e.getFlags()); + assertEquals("String Value", e.getText(100)); + assertEquals("String Value", + new BufferedReader(new InputStreamReader(e.getInputStream())).readLine()); + e.close(); + + e = DropBoxManager.Entry.CREATOR.createFromParcel(parcel); + assertEquals("bytes", e.getTag()); + assertEquals(3000000, e.getTimeMillis()); + assertEquals(DropBoxManager.IS_TEXT, e.getFlags()); + assertEquals("Bytes Value", e.getText(100)); + e.close(); + + e = DropBoxManager.Entry.CREATOR.createFromParcel(parcel); + assertEquals("zerobytes", e.getTag()); + assertEquals(4000000, e.getTimeMillis()); + assertEquals(0, e.getFlags()); + assertEquals(null, e.getText(100)); + assertEquals(null, + new BufferedReader(new InputStreamReader(e.getInputStream())).readLine()); + e.close(); + + e = DropBoxManager.Entry.CREATOR.createFromParcel(parcel); + assertEquals("emptybytes", e.getTag()); + assertEquals(5000000, e.getTimeMillis()); + assertEquals(DropBoxManager.IS_EMPTY, e.getFlags()); + assertEquals(null, e.getText(100)); + assertEquals(null, e.getInputStream()); + e.close(); + + e = DropBoxManager.Entry.CREATOR.createFromParcel(parcel); + assertEquals("file", e.getTag()); + assertEquals(6000000, e.getTimeMillis()); + assertEquals(DropBoxManager.IS_TEXT, e.getFlags()); + assertEquals("File Value", e.getText(100)); + e.close(); + + e = DropBoxManager.Entry.CREATOR.createFromParcel(parcel); + assertEquals("binfile", e.getTag()); + assertEquals(7000000, e.getTimeMillis()); + assertEquals(0, e.getFlags()); + assertEquals(null, e.getText(100)); + assertEquals("File Value", + new BufferedReader(new InputStreamReader(e.getInputStream())).readLine()); + e.close(); + + e = DropBoxManager.Entry.CREATOR.createFromParcel(parcel); + assertEquals("emptyfile", e.getTag()); + assertEquals(8000000, e.getTimeMillis()); + assertEquals(DropBoxManager.IS_EMPTY, e.getFlags()); + assertEquals(null, e.getText(100)); + assertEquals(null, e.getInputStream()); + e.close(); + + e = DropBoxManager.Entry.CREATOR.createFromParcel(parcel); + assertEquals("gzipfile", e.getTag()); + assertEquals(9000000, e.getTimeMillis()); + assertEquals(DropBoxManager.IS_TEXT, e.getFlags()); + assertEquals("Gzip File Value", e.getText(100)); + e.close(); + + e = DropBoxManager.Entry.CREATOR.createFromParcel(parcel); + assertEquals("gzipbinfile", e.getTag()); + assertEquals(10000000, e.getTimeMillis()); + assertEquals(0, e.getFlags()); + assertEquals(null, e.getText(100)); + assertEquals("Gzip File Value", + new BufferedReader(new InputStreamReader(e.getInputStream())).readLine()); + e.close(); + + assertEquals(0, parcel.dataAvail()); + parcel.recycle(); + } + + public void testDropBoxEntrySerializationDoesntLeakFileDescriptors() throws Exception { + File dir = getEmptyDir("testDropBoxEntrySerialization"); + File f = new File(dir, "file.dat"); + FileOutputStream os = new FileOutputStream(f); + os.write("File Value".getBytes()); + os.close(); + + int before = countOpenFiles(); + assertTrue(before > 0); + + for (int i = 0; i < 1000; i++) { + Parcel parcel = Parcel.obtain(); + new DropBoxManager.Entry("file", 1000000, f, 0).writeToParcel( + parcel, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + + parcel.setDataPosition(0); + DropBoxManager.Entry e = DropBoxManager.Entry.CREATOR.createFromParcel(parcel); + assertEquals("file", e.getTag()); + e.close(); + + parcel.recycle(); + } + + int after = countOpenFiles(); + assertTrue(after > 0); + assertTrue(after < before + 20); + } + private void addRandomEntry(DropBoxManager dropbox, String tag, int size) throws Exception { byte[] bytes = new byte[size]; new Random(System.currentTimeMillis()).nextBytes(bytes); @@ -564,4 +768,8 @@ public class DropBoxTest extends AndroidTestCase { assertTrue(dir.listFiles().length == 0); return dir; } + + private int countOpenFiles() { + return new File("/proc/" + Process.myPid() + "/fd").listFiles().length; + } } |
